Example #1
0
    def get_metadata(self, gal=None):
        thread = QThread(self)
        thread.setObjectName("App.get_metadata")
        fetch_instance = fetch.Fetch()
        if gal:
            galleries = [gal]
        else:
            if gui_constants.CONTINUE_AUTO_METADATA_FETCHER:
                galleries = [g for g in self.manga_list_view.gallery_model._data if not g.exed]
            else:
                galleries = self.manga_list_view.gallery_model._data
            if not galleries:
                self.notification_bar.add_text("Looks like we've already gone through all galleries!")
                return None
        fetch_instance.galleries = galleries

        self.notification_bar.begin_show()
        fetch_instance.moveToThread(thread)

        def done(status):
            self.notification_bar.end_show()
            fetch_instance.deleteLater()

        fetch_instance.GALLERY_PICKER.connect(self._web_metadata_picker)
        fetch_instance.GALLERY_EMITTER.connect(self.manga_list_view.replace_edit_gallery)
        fetch_instance.AUTO_METADATA_PROGRESS.connect(self.notification_bar.add_text)
        thread.started.connect(fetch_instance.auto_web_metadata)
        fetch_instance.FINISHED.connect(done)
        thread.finished.connect(thread.deleteLater)
        thread.start()
Example #2
0
	def start_msg_thread(self, runningObjectParam, onRun = None, onDone = None, onErrorMsg = None, onSuccessMsg = None ):
		
		if self.running:
			return
					
		self.running = True
		
		thr = QThread()
		thrName = 'EndicataLabToolMainWnd start_msg_thread[%d]' % (len( self.threadPool ) + 1)
		print thrName
		thr.setObjectName( thrName )
		self.threadPool.append( thr )
		
		self.runningObject = runningObjectParam
		self.runningObject.moveToThread(thr)
		if onRun:
			thr.started.connect( onRun )
		else:
			thr.started.connect( self.runningObject.run )

		self.runningObject.finished.connect( self.onUnlock )
		
		if onDone:
			self.runningObject.done.connect( onDone )
		if onErrorMsg:
			self.runningObject.errorMsg.connect( onErrorMsg )
		if onSuccessMsg:
			self.runningObject.successMsg.connect( onSuccessMsg )
		thr.start()
Example #3
0
    def start_thread(self):
        # self.log.append('starting thread')
        self.button_start.setDisabled(True)
        self.button_stop.setEnabled(True)

        self.__worker_done = 0
        self.__thread = []
        # for idx in range(self.NUM_THREADS):
        idx = 0
        worker = Worker(idx, self.args)
        thread = QThread()
        thread.setObjectName('thread_' + str(idx))
        self.__thread.append(
            (thread,
             worker))  # need to store worker too otherwise will be gc'd
        worker.moveToThread(thread)

        # get progress messages from worker:
        worker.sig_step.connect(self.on_worker_step)
        worker.sig_done.connect(self.on_worker_done)
        # worker.sig_msg.connect(self.log.append)
        worker.sig_image.connect(self.on_image)

        # control worker:
        self.sig_abort_worker.connect(worker.abort)

        # get read to start worker:
        # self.sig_start.connect(worker.work)  # needed due to PyCharm debugger bug (!); comment out next line
        thread.started.connect(worker.work)
        thread.start(
        )  # this will emit 'started' and start thread's event loop
    def initFig(self, show_progress=True):
        """
        Initial data load from Figshare
        :return:
        """
        # Get the list of articles from
        collections = Collections(self.token)
        articles = collections.get_articles(self.collection_id)
        n_articles = len(articles)

        worker = ArticleLoadWorker(self.app, self.token, self.parent,
                                   self.collection_id, articles)

        thread = QThread()
        thread.setObjectName('thread_article_load')

        self.__threads.append((thread, worker))
        worker.moveToThread(thread)

        worker.sig_step.connect(self.add_to_tree)
        worker.sig_done.connect(self.update_search_field)
        worker.sig_done.connect(self.enable_fields)

        thread.started.connect(worker.work)
        thread.start()

        self.articles = articles
        self.article_ids = set()
        for article in articles:
            self.article_ids.add(article['id'])
Example #5
0
    def start_threads(self):
        self.log.append('starting {} threads'.format(self.NUM_THREADS))
        self.button_start_threads.setDisabled(True)
        self.button_stop_threads.setEnabled(True)

        self.__workers_done = 0
        self.__threads = []
        for idx in range(self.NUM_THREADS):
            worker = Worker(idx, app)
            thread = QThread(self)  # remember self otherwise second run will fail
            thread.setObjectName('thread_' + str(idx))
            self.__threads.append((thread, worker))  # need to store worker too otherwise will be gc'd
            worker.moveToThread(thread)

            # get progress messages from worker:
            worker.sig_step_demo.connect(self.on_worker_step)
            worker.sig_done_demo.connect(self.on_worker_done)
            worker.sig_msg_demo.connect(self.log.append)

            # control worker:
            self.sig_abort_workers.connect(worker.abort)

            # get read to start worker:
            # self.sig_start.connect(worker.work)  # needed due to PyCharm debugger bug (!); comment out next line
            thread.started.connect(worker.work)
            thread.start()  # this will emit 'started' and start thread's event loop
Example #6
0
    def startCom(self, comPort: str, baudRate: int):
        '''
    Gestion des communications serie et des timers dans des threads distincts
    '''

        # TODO : Ajout possibilité de connection réseau
        # Windows : pseudo serial over TCP driver => rien a faire
        # Linux : use socat :
        # sudo socat pty,link=/dev/ttyTCP0,raw tcp:127.0.0.1:1386
        # comPort = "ttyTCP0" (si dans /dev)
        # attention aux droits d'accès !
        # comPort = "/tmp/ttyTCP0" (chemin absolu)

        self.sig_debug.emit("grblCom.startCom(self, {}, {})".format(
            comPort, baudRate))

        self.sig_log.emit(
            logSeverity.info.value,
            'grblCom: Starting grblComSerial thread on {}.'.format(comPort))
        newComSerial = grblComSerial(self.decode, comPort, baudRate,
                                     self.__pooling)
        thread = QThread()
        thread.setObjectName('grblComSerial')
        self.__threads.append(
            (thread,
             newComSerial))  # need to store worker too otherwise will be gc'd
        newComSerial.moveToThread(thread)

        # Connecte les signaux provenant du grblComSerial
        newComSerial.sig_log.connect(self.sig_log.emit)
        newComSerial.sig_connect.connect(self.on_sig_connect)
        newComSerial.sig_init.connect(self.on_sig_init)
        newComSerial.sig_ok.connect(self.sig_ok.emit)
        newComSerial.sig_error.connect(self.sig_error.emit)
        newComSerial.sig_alarm.connect(self.sig_alarm.emit)
        newComSerial.sig_status.connect(self.on_sig_status)
        newComSerial.sig_config.connect(self.sig_config.emit)
        newComSerial.sig_data.connect(self.sig_data.emit)
        newComSerial.sig_emit.connect(self.sig_emit.emit)
        newComSerial.sig_recu.connect(self.sig_recu.emit)
        newComSerial.sig_debug.connect(self.sig_debug.emit)

        # Signaux de pilotage a envoyer au thread
        self.sig_abort.connect(newComSerial.abort)
        self.sig_gcodeInsert.connect(newComSerial.gcodeInsert)
        self.sig_gcodePush.connect(newComSerial.gcodePush)
        self.sig_realTimePush.connect(newComSerial.realTimePush)
        self.sig_clearCom.connect(newComSerial.clearCom)
        self.sig_startPooling.connect(newComSerial.startPooling)
        self.sig_stopPooling.connect(newComSerial.stopPooling)
        self.sig_resetSerial.connect(newComSerial.resetSerial)

        # Start the thread...
        thread.started.connect(newComSerial.run)
        thread.start(
        )  # this will emit 'started' and start thread's event loop

        # Memorise le communicateur
        self.__Com = newComSerial
class AsyncCharacterQueries(QObject):

    notify_update = pyqtSignal()
    finished = pyqtSignal()

    def __init__(self, shared_queue: Queue, shared_dict: dict):
        QObject.__init__(self)

        self.esi_core = None
        self.query_engine = None
        self.query_list = shared_queue
        self.shared_dict = shared_dict

        self.objThread = QThread()

        self.running = True
        # self.setDaemon(True)

    @pyqtSlot()
    def work(self) -> None:
        """

        :return:
        """
        logging.info("Character query service started.")

        self.esi_core = EsiCore()
        self.query_engine = EsiQuery(self.esi_core)

        try:
            while self.running:
                char_id = self.query_list.get(block=True)

                response = self.query_engine.get_character_public_information(
                    char_id)
                self.shared_dict[char_id] = response
                self.notify_update.emit()
        finally:
            logging.warning("Character name query service terminated.")

    @pyqtSlot()
    def exit(self):
        logging.info("Character query service has exited.")

    def start(self):
        """
        Convenience function starting the thread for the ASync character name service
        :return:
        """
        self.moveToThread(self.objThread)
        self.finished.connect(self.objThread.quit)
        self.objThread.started.connect(self.work)
        self.objThread.finished.connect(self.exit)
        self.objThread.setObjectName("AsyncCharacterThread")
        self.objThread.start()

    def abort(self):
        self.sig_msg.emit('Worker #{} notified to abort'.format(self.__id))
        self.__abort = True
Example #8
0
class MyWidget(QWidget):
    NUM_THREADS = 5

    sig_start = pyqtSignal()  # needed only due to PyCharm debugger bug (!)
    sig_abort_workers = pyqtSignal()

    def __init__(self):
        super().__init__()

        self.setWindowTitle("Thread Example")
        form_layout = QVBoxLayout()
        self.setLayout(form_layout)
        self.resize(400, 800)

        self.button_start_threads = QPushButton()
        self.button_start_threads.clicked.connect(self.start_threads)
        self.button_start_threads.setText("Start thread")
        form_layout.addWidget(self.button_start_threads)

        self.log = QTextEdit()
        form_layout.addWidget(self.log)

        QThread.currentThread().setObjectName(
            'main')  # threads can be named, useful for log output
        self.__workers_done = None
        self.__threads = None

    def start_threads(self):
        self.log.append('Starting thread')
        self.button_start_threads.setDisabled(True)
        #         self.button_stop_threads.setEnabled(True)

        self.worker = Worker()
        self.thread = QThread()
        self.thread.setObjectName('thread_')
        #         self.__threads.append((thread, worker))  # need to store worker too otherwise will be gc'd
        self.worker.moveToThread(self.thread)

        # get progress messages from worker:
        self.worker.sig_done.connect(self.on_worker_done)
        self.worker.sig_msg.connect(self.log.append)
        #         thread.started.connect(worker.work)
        # control worker:
        # get read to start worker:
        self.sig_start.connect(
            self.worker.work
        )  # needed due to PyCharm debugger bug (!); comment out next line
        self.thread.start(
        )  # this will emit 'started' and start thread's event loop

        self.sig_start.emit()  # needed due to PyCharm debugger bug (!)'''

    @pyqtSlot()
    def on_worker_done(self):
        self.log.append("Work done")
Example #9
0
                            def add_gallery(gallery_list):
                                def append_to_model(x):
                                    self.manga_list_view.gallery_model.insertRows(x, None, len(x))

                                class A(QObject):
                                    done = pyqtSignal()
                                    prog = pyqtSignal(int)

                                    def __init__(self, obj, parent=None):
                                        super().__init__(parent)
                                        self.obj = obj
                                        self.galleries = []

                                    def add_to_db(self):
                                        gui_constants.NOTIF_BAR.begin_show()
                                        gui_constants.NOTIF_BAR.add_text("Populating database...")
                                        for y, x in enumerate(self.obj):
                                            gui_constants.NOTIF_BAR.add_text(
                                                "Populating database {}/{}".format(y + 1, len(self.obj))
                                            )
                                            gallerydb.add_method_queue(gallerydb.GalleryDB.add_gallery_return, False, x)
                                            self.galleries.append(x)
                                            y += 1
                                            self.prog.emit(y)
                                        append_to_model(self.galleries)
                                        gui_constants.NOTIF_BAR.end_show()
                                        self.done.emit()

                                loading.progress.setMaximum(len(gallery_list))
                                a_instance = A(gallery_list)
                                thread = QThread(self)
                                thread.setObjectName("Database populate")

                                def loading_show():
                                    loading.setText("Populating database.\nPlease wait...")
                                    loading.show()

                                def loading_hide():
                                    loading.close()
                                    self.manga_list_view.gallery_model.ROWCOUNT_CHANGE.emit()

                                def del_later():
                                    try:
                                        a_instance.deleteLater()
                                    except NameError:
                                        pass

                                a_instance.moveToThread(thread)
                                a_instance.prog.connect(loading.progress.setValue)
                                thread.started.connect(loading_show)
                                thread.started.connect(a_instance.add_to_db)
                                a_instance.done.connect(loading_hide)
                                a_instance.done.connect(del_later)
                                thread.finished.connect(thread.deleteLater)
                                thread.start()
Example #10
0
class MainWindow(QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        uic.loadUi(qtCreatorFile, self)
        self.threads = []
        mainThreadID = int(QThread.currentThread().currentThreadId())
        print("MainThread ID: {}".format(mainThreadID))
        QThread.currentThread().setObjectName("Thread_Main")
        print("MainThread name is: {}".format(
            QThread.currentThread().objectName()))

        self.pb1.clicked.connect(self.pb1Action)
        self.pb1Object = UpdateLogger(2)
        self.pb1Thread = QThread()
        self.pb1Thread.setObjectName("Thread_pb1")
        self.threads.append(self.pb1Thread)
        self.pb1Object.moveToThread(self.pb1Thread)
        self.pb1Object.finished.connect(self.updateLogger)
        self.pb1Object.update.connect(self.updateLogger)
        self.pb1Thread.started.connect(self.pb1Object.running)

        self.pb2.clicked.connect(self.pb2Action)
        self.pb2Object = UpdateLogger(3)
        self.pb2Thread = QThread()
        self.pb2Thread.setObjectName("Thread_pb2")
        self.threads.append(self.pb2Thread)
        self.pb2Object.moveToThread(self.pb2Thread)
        print(self.pb2Object.threadName)
        self.pb2Object.finished.connect(self.updateLogger)
        self.pb2Object.update.connect(self.updateLogger)
        self.pb2Thread.started.connect(self.pb2Object.running)

        for t in self.threads:
            print(t.objectName())

        self.pb3.clicked.connect(self.pb3Action)

    def pb1Action(self):
        self.pb1Thread.start()
        self.pb1.setEnabled(False)

    def pb2Action(self):
        self.pb2Thread.start()
        self.pb2.setEnabled(False)

    def pb3Action(self):

        self.tbLog.append(self.le3.text())

    @pyqtSlot(str)
    def updateLogger(self, txt="??????"):
        self.tbLog.append(txt)
Example #11
0
    def start_up(self):
        def normalize_first_time():
            settings.set(2, "Application", "first time level")

        def done():
            self.manga_list_view.gallery_model.init_data()
            if gui_constants.ENABLE_MONITOR and gui_constants.MONITOR_PATHS and all(gui_constants.MONITOR_PATHS):
                self.init_watchers()
            if gui_constants.FIRST_TIME_LEVEL != 2:
                normalize_first_time()

        if gui_constants.FIRST_TIME_LEVEL < 2:

            class FirstTime(file_misc.BasePopup):
                def __init__(self, parent=None):
                    super().__init__(parent)
                    main_layout = QVBoxLayout()
                    info_lbl = QLabel(
                        "Hi there! Some big changes are about to occur!\n"
                        + "Please wait.. This will take at most a few minutes.\n"
                        + "If not then try restarting the application."
                    )
                    info_lbl.setAlignment(Qt.AlignCenter)
                    main_layout.addWidget(info_lbl)
                    prog = QProgressBar(self)
                    prog.setMinimum(0)
                    prog.setMaximum(0)
                    prog.setTextVisible(False)
                    main_layout.addWidget(prog)
                    main_layout.addWidget(QLabel("Note: This popup will close itself when everything is ready"))
                    self.main_widget.setLayout(main_layout)

            ft_widget = FirstTime(self)
            log_i("Invoking first time level 2")
            bridge = gallerydb.Bridge()
            thread = QThread(self)
            thread.setObjectName("Startup")
            bridge.moveToThread(thread)
            thread.started.connect(bridge.rebuild_galleries)
            bridge.DONE.connect(ft_widget.close)
            bridge.DONE.connect(self.setEnabled)
            bridge.DONE.connect(done)
            bridge.DONE.connect(bridge.deleteLater)
            thread.finished.connect(thread.deleteLater)
            thread.start()
            ft_widget.adjustSize()
            ft_widget.show()
            self.setEnabled(False)
        else:
            done()
Example #12
0
    def web_metadata(self, url, btn_widget, pgr_widget):
        self.link_lbl.setText(url)
        f = fetch.Fetch()
        thread = QThread(self)
        thread.setObjectName('Gallerydialog web metadata')
        btn_widget.hide()
        pgr_widget.show()

        def status(stat):
            def do_hide():
                try:
                    pgr_widget.hide()
                    btn_widget.show()
                except RuntimeError:
                    pass

            if stat:
                do_hide()
            else:
                danger = """QProgressBar::chunk {
					background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0,stop: 0 #FF0350,stop: 0.4999 #FF0020,stop: 0.5 #FF0019,stop: 1 #FF0000 );
					border-bottom-right-radius: 5px;
					border-bottom-left-radius: 5px;
					border: .px solid black;}"""
                pgr_widget.setStyleSheet(danger)
                QTimer.singleShot(3000, do_hide)
            f.deleteLater()

        def gallery_picker(gallery, title_url_list, q):
            self.parent_widget._web_metadata_picker(gallery, title_url_list, q,
                                                    self)

        try:
            dummy_gallery = self.make_gallery(self.gallery)
        except AttributeError:
            dummy_gallery = self.make_gallery(gallerydb.Gallery(), False)
        if not dummy_gallery:
            status(False)
            return None

        dummy_gallery.link = url
        f.galleries = [dummy_gallery]
        f.moveToThread(thread)
        f.GALLERY_PICKER.connect(gallery_picker)
        f.GALLERY_EMITTER.connect(self.set_web_metadata)
        thread.started.connect(f.auto_web_metadata)
        thread.finished.connect(thread.deleteLater)
        f.FINISHED.connect(status)
        thread.start()
Example #13
0
	def web_metadata(self, url, btn_widget, pgr_widget):
		self.link_lbl.setText(url)
		f = fetch.Fetch()
		thread = QThread(self)
		thread.setObjectName('Gallerydialog web metadata')
		btn_widget.hide()
		pgr_widget.show()

		def status(stat):
			def do_hide():
				try:
					pgr_widget.hide()
					btn_widget.show()
				except RuntimeError:
					pass

			if stat:
				do_hide()
			else:
				danger = """QProgressBar::chunk {
					background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0,stop: 0 #FF0350,stop: 0.4999 #FF0020,stop: 0.5 #FF0019,stop: 1 #FF0000 );
					border-bottom-right-radius: 5px;
					border-bottom-left-radius: 5px;
					border: .px solid black;}"""
				pgr_widget.setStyleSheet(danger)
				QTimer.singleShot(3000, do_hide)
			f.deleteLater()

		def gallery_picker(gallery, title_url_list, q):
			self.parent_widget._web_metadata_picker(gallery, title_url_list, q, self)

		try:
			dummy_gallery = self.make_gallery(self.gallery)
		except AttributeError:
			dummy_gallery = self.make_gallery(gallerydb.Gallery(), False)
		if not dummy_gallery:
			status(False)
			return None

		dummy_gallery.link = url
		f.galleries = [dummy_gallery]
		f.moveToThread(thread)
		f.GALLERY_PICKER.connect(gallery_picker)
		f.GALLERY_EMITTER.connect(self.set_web_metadata)
		thread.started.connect(f.auto_web_metadata)
		thread.finished.connect(thread.deleteLater)
		f.FINISHED.connect(status)
		thread.start()
Example #14
0
						def add_gallery(gallery_list):
							def append_to_model(x):
								self.manga_list_view.sort_model.insertRows(x, None, len(x))
								self.manga_list_view.sort_model.init_search(
									self.manga_list_view.sort_model.current_term)

							class A(QObject):
								done = pyqtSignal()
								prog = pyqtSignal(int)
								def __init__(self, obj, parent=None):
									super().__init__(parent)
									self.obj = obj
									self.galleries = []

								def add_to_db(self):
									for y, x in enumerate(self.obj):
										gallerydb.add_method_queue(
											gallerydb.GalleryDB.add_gallery_return, False, x)
										self.galleries.append(x)
										y += 1
										self.prog.emit(y)
									append_to_model(self.galleries)
									self.done.emit()
							loading.progress.setMaximum(len(gallery_list))
							self.a_instance = A(gallery_list)
							thread = QThread(self)
							thread.setObjectName('Database populate')
							def loading_show(numb):
								if loading.isHidden():
									loading.show()
								loading.setText('Populating database ({}/{})\nPlease wait...'.format(
									numb, loading.progress.maximum()))
								loading.progress.setValue(numb)
								loading.show()

							def loading_hide():
								loading.close()
								self.manga_list_view.gallery_model.ROWCOUNT_CHANGE.emit()

							self.a_instance.moveToThread(thread)
							self.a_instance.prog.connect(loading_show)
							thread.started.connect(self.a_instance.add_to_db)
							self.a_instance.done.connect(loading_hide)
							self.a_instance.done.connect(self.a_instance.deleteLater)
							#a_instance.add_to_db()
							thread.finished.connect(thread.deleteLater)
							thread.start()
Example #15
0
  def startCom(self, comPort: str, baudRate: int):
    '''
    Gestion des communications serie et des timers dans des threads distincts
    '''

    self.sig_debug.emit("grblCom.startCom(self, {}, {})".format(comPort, baudRate))

    self.sig_log.emit(logSeverity.info.value, 'grblCom: Starting grblComSerial thread.')
    newComSerial = grblComSerial(comPort, baudRate, self.__pooling)
    thread = QThread()
    thread.setObjectName('grblComSerial')
    self.__threads.append((thread, newComSerial))  # need to store worker too otherwise will be gc'd
    newComSerial.moveToThread(thread)

    # Connecte les signaux provenant du grblComSerial
    newComSerial.sig_log.connect(self.sig_log.emit)
    newComSerial.sig_connect.connect(self.on_sig_connect)
    newComSerial.sig_init.connect(self.on_sig_init)
    newComSerial.sig_ok.connect(self.sig_ok.emit)
    newComSerial.sig_error.connect(self.sig_error.emit)
    newComSerial.sig_alarm.connect(self.sig_alarm.emit)
    newComSerial.sig_status.connect(self.on_sig_status)
    newComSerial.sig_config.connect(self.sig_config.emit)
    newComSerial.sig_data.connect(self.sig_data.emit)
    newComSerial.sig_emit.connect(self.sig_emit.emit)
    newComSerial.sig_recu.connect(self.sig_recu.emit)
    newComSerial.sig_debug.connect(self.sig_debug.emit)

    # Signaux de pilotage a envoyer au thread
    self.sig_abort.connect(newComSerial.abort)
    self.sig_gcodeInsert.connect(newComSerial.gcodeInsert)
    self.sig_gcodePush.connect(newComSerial.gcodePush)
    self.sig_realTimePush.connect(newComSerial.realTimePush)
    self.sig_clearCom.connect(newComSerial.clearCom)
    self.sig_startPooling.connect(newComSerial.startPooling)
    self.sig_stopPooling.connect(newComSerial.stopPooling)


    # Start the thread...
    thread.started.connect(newComSerial.run)
    thread.start()  # this will emit 'started' and start thread's event loop

    # Memorise le communicateur
    self.__Com = newComSerial
Example #16
0
    def _setup_worker_thread(self) -> typing.Tuple[ModelWorker, QThread]:
        """
        Create the worker thread used to perform long running operations in the background.
        """
        worker = ModelWorker(self)
        worker_thread = QThread(self)
        worker_thread.setObjectName("AsynchronousImageWorker")
        worker.moveToThread(worker_thread)
        logger.debug("Created worker thread.")
        self.open_command_line_given_images.connect(
            worker.open_command_line_given_images)
        self.open_images.connect(worker.open_images)
        self.save_and_close_all_images.connect(
            worker.save_and_close_all_images)
        self.close_image.connect(worker.close_image)
        logger.debug("Connected signals to offload to the worker thread.")
        worker_thread.start()

        return worker, worker_thread
Example #17
0
 def button_start_action(self):
     """Slot for buttonStart clicked signal"""
     self.progressBar.setRange(0, 0)
     self.buttonStart.setEnabled(False)
     self.buttonClose.setEnabled(False)
     worker = Worker(20, self.__app)
     thread = QThread(self)
     thread.setObjectName("products_http")
     self.__threads.append((thread, worker))
     worker.moveToThread(thread)
     worker.sig_status.connect(self.on_status)
     worker.sig_done.connect(self.on_done)
     try:
         thread.started.connect(
             worker.import_products_http(self.products, self.settings))
         thread.start()
     except TypeError as t:
         if DBG:
             printit(
                 " ->products -> http\n ->exception handled: {}".format(t))
Example #18
0
    def start_workers(self):
        self.start_btn.setDisabled(True)
        self.stop_btn.setEnabled(True)
        self.txt_print.clear()
        self.txt_print.append("识别中..")
        self.__workers_done = 0
        self.__threads = []
        self.thread_ID=["txt_print","Camera_label","wait_label"]
        for idx in range(len(self.thread_ID)):
            worker = Worker(idx)
            thread = QThread()
            thread.setObjectName(self.thread_ID[idx])
            self.__threads.append((thread,worker))
            worker.moveToThread(thread)
            worker.sig_working.connect(self.on_woker_working)
            worker.sig_finished.connect(self.on_worker_finished)

            self.sig_abort_workers.connect(worker.abort)
            thread.started.connect(worker.work)
            thread.start()
Example #19
0
    def startThreads(self):
        global conversion_serial
        self.indicateThreadsRunning(True)
        self.setStatusBar("Working...")
        if self.queue.empty():
            self.setStatusBar(
                "{} Files converted.  You can add more, or exit.".format(
                    self.convertedCount
                )
            )
            self.indicateThreadsRunning(False)
            # self.setStatusBar("Ready.")
        try:
            while (not self.queue.empty()) and (len(self.working) < self.NUM_THREADS):
                info = self.queue.get_nowait()
                # self.addResponse('Starting conversion for "{}".'.format(info['basename']))
                conversion_serial += 1
                worker_id = conversion_serial
                worker = ConversionWorker(worker_id, info)
                thread = QThread()
                thread.setObjectName("{}".format(id))
                self.working[worker_id] = (
                    info,
                    worker,
                    thread,
                    id,
                )  # need to store worker and thread too otherwise will be gc'd
                worker.moveToThread(thread)

                # get progress messages from worker:
                worker.sig_done.connect(self.onWorkerDone)
                worker.sig_msg.connect(self.addResponse)

                # in case we need to stop the worker:
                self.sig_abort_workers.connect(worker.abort)

                # start the worker:
                thread.started.connect(worker.doConversion)
                thread.start()  # this will emit 'started' and start thread's event loop
        except queue.Empty:
            pass  # When the queue is empty, we just stop creating threads.
Example #20
0
    def start_workers(self):

        self.txt_log.append('Starting {} threads'.format(self.NUM_THREADS))
        self.btn_start.setDisabled(True)
        self.btn_stop.setEnabled(True)

        self.__workers_done = 0
        self.__threads = []
        for idx in range(self.NUM_THREADS):
            # 启动3个线程
            worker = Worker(idx)
            thread = QThread()
            thread.setObjectName('thread_' + str(idx))
            self.__threads.append((thread, worker))
            worker.moveToThread(thread)
            worker.sig_working.connect(self.on_worker_working)
            worker.sig_finished.connect(self.on_worker_finished)
            worker.sig_output.connect(self.txt_log.append)
            self.sig_abort_workers.connect(worker.abort)
            thread.started.connect(worker.work)
            thread.start()
    def on_open_selection_clicked(self):
        """
        Called when the open selection button is clicked. Either open or closes the metadata window
        :return:
        """
        # Retrieve a set of file paths
        file_paths = self.get_selection_set()

        # Locally reference the Article List Widget
        article_tree = self.parent.data_articles_window.article_tree

        # Prevent the user from editting, searching, etc. while new articles are created.
        article_tree.disable_fields()

        # Create an article creation worker
        worker = ArticleCreationWorker(self.token, self.parent, file_paths)

        # Create the thread.
        load_articles_thread = QThread()
        load_articles_thread.setObjectName('local_articles_thread')
        self.__threads.append((load_articles_thread, worker))

        # Add the worker to the thread
        worker.moveToThread(load_articles_thread)

        # Connect signals from worker
        worker.sig_step.connect(article_tree.add_to_tree)
        worker.sig_step.connect(
            lambda article_id: article_tree.add_to_articles(article_id))
        worker.sig_done.connect(article_tree.enable_fields)
        worker.sig_done.connect(article_tree.update_search_field)
        worker.sig_done.connect(self.parent.data_articles_window.check_edit)
        load_articles_thread.started.connect(worker.work)

        # Begin worker thread
        load_articles_thread.start()
Example #22
0
 def button_start_action(self):
     """Slot for buttonStart clicked signal"""
     self.progressBar.setRange(0, 0)
     worker = Worker(10, self.__app)
     thread = QThread(self)
     thread.setObjectName("customers_http")
     self.__threads.append((thread, worker))
     worker.moveToThread(thread)
     worker.sig_status.connect(self.on_status)
     worker.sig_done.connect(self.on_done)
     try:
         """
         customers object is used by the worker to insert data into customer table
         employees object is used to fetch the customer file
         settings object is used to check access
         """
         thread.started.connect(
             worker.import_customers_http(self.customers, self.employees,
                                          self.settings))
         thread.start()
     except TypeError as t:
         if DBG:
             printit(
                 " ->customers -> http\n ->exception handled: {}".format(t))
Example #23
0
class GalleryDialog(QWidget):
    """
    A window for adding/modifying gallery.
    Pass a list of QModelIndexes to edit their data
    or pass a path to preset path
    """
    def __init__(self, parent, arg=None):
        super().__init__(parent, Qt.Dialog)
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setAutoFillBackground(True)
        self.parent_widget = parent
        m_l = QVBoxLayout()
        self.main_layout = QVBoxLayout()
        dummy = QWidget(self)
        scroll_area = QScrollArea(self)
        scroll_area.setWidgetResizable(True)
        scroll_area.setFrameStyle(scroll_area.StyledPanel)
        dummy.setLayout(self.main_layout)
        scroll_area.setWidget(dummy)
        m_l.addWidget(scroll_area, 3)

        final_buttons = QHBoxLayout()
        final_buttons.setAlignment(Qt.AlignRight)
        m_l.addLayout(final_buttons)
        self.done = QPushButton("Done")
        self.done.setDefault(True)
        cancel = QPushButton("Cancel")
        final_buttons.addWidget(cancel)
        final_buttons.addWidget(self.done)
        self._multiple_galleries = False
        self._edit_galleries = []

        def new_gallery():
            self.setWindowTitle('Add a new gallery')
            self.newUI()
            self.commonUI()
            self.done.clicked.connect(self.accept)
            cancel.clicked.connect(self.reject)

        if arg:
            if isinstance(arg, (list, gallerydb.Gallery)):
                if isinstance(arg, gallerydb.Gallery):
                    self.setWindowTitle('Edit gallery')
                    self._edit_galleries.append(arg)
                else:
                    self.setWindowTitle('Edit {} galleries'.format(len(arg)))
                    self._multiple_galleries = True
                    self._edit_galleries.extend(arg)
                self.commonUI()
                self.setGallery(arg)
                self.done.clicked.connect(self.accept_edit)
                cancel.clicked.connect(self.reject_edit)
            elif isinstance(arg, str):
                new_gallery()
                self.choose_dir(arg)
        else:
            new_gallery()

        log_d('GalleryDialog: Create UI: successful')
        self.setLayout(m_l)
        if self._multiple_galleries:
            self.resize(500, 480)
        else:
            self.resize(500, 600)
        frect = self.frameGeometry()
        frect.moveCenter(QDesktopWidget().availableGeometry().center())
        self.move(frect.topLeft())
        self._fetch_inst = fetch.Fetch()
        self._fetch_thread = QThread(self)
        self._fetch_thread.setObjectName("GalleryDialog metadata thread")
        self._fetch_inst.moveToThread(self._fetch_thread)
        self._fetch_thread.started.connect(self._fetch_inst.auto_web_metadata)

    def commonUI(self):
        if not self._multiple_galleries:
            f_web = QGroupBox("Metadata from the Web")
            f_web.setCheckable(False)
            self.main_layout.addWidget(f_web)
            web_main_layout = QVBoxLayout()
            web_info = misc.ClickedLabel(
                "Which gallery URLs are supported? (hover)", parent=self)
            web_info.setToolTip(app_constants.SUPPORTED_METADATA_URLS)
            web_info.setToolTipDuration(999999999)
            web_main_layout.addWidget(web_info)
            web_layout = QHBoxLayout()
            web_main_layout.addLayout(web_layout)
            f_web.setLayout(web_main_layout)

            def basic_web(name):
                return QLabel(name), QLineEdit(), QPushButton(
                    "Get metadata"), QProgressBar()

            url_lbl, self.url_edit, url_btn, url_prog = basic_web("URL:")
            url_btn.clicked.connect(lambda: self.web_metadata(
                self.url_edit.text(), url_btn, url_prog))
            url_prog.setTextVisible(False)
            url_prog.setMinimum(0)
            url_prog.setMaximum(0)
            web_layout.addWidget(url_lbl, 0, Qt.AlignLeft)
            web_layout.addWidget(self.url_edit, 0)
            web_layout.addWidget(url_btn, 0, Qt.AlignRight)
            web_layout.addWidget(url_prog, 0, Qt.AlignRight)
            self.url_edit.setPlaceholderText(
                "Insert supported gallery URLs or just press the button!")
            url_prog.hide()

        f_gallery = QGroupBox("Gallery Info")
        f_gallery.setCheckable(False)
        self.main_layout.addWidget(f_gallery)
        gallery_layout = QFormLayout()
        f_gallery.setLayout(gallery_layout)

        def checkbox_layout(widget):
            if self._multiple_galleries:
                l = QHBoxLayout()
                l.addWidget(widget.g_check)
                widget.setSizePolicy(QSizePolicy.Expanding,
                                     QSizePolicy.Preferred)
                l.addWidget(widget)
                return l
            else:
                widget.g_check.setChecked(True)
                widget.g_check.hide()
                return widget

        def add_check(widget):
            widget.g_check = QCheckBox(self)
            return widget

        self.title_edit = add_check(QLineEdit())
        self.author_edit = add_check(QLineEdit())
        author_completer = misc.GCompleter(self, False, True, False)
        author_completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.author_edit.setCompleter(author_completer)
        self.descr_edit = add_check(QTextEdit())
        self.descr_edit.setAcceptRichText(True)
        self.lang_box = add_check(QComboBox())
        self.lang_box.addItems(app_constants.G_LANGUAGES)
        self.lang_box.addItems(app_constants.G_CUSTOM_LANGUAGES)
        self.rating_box = add_check(QSpinBox())
        self.rating_box.setMaximum(5)
        self.rating_box.setMinimum(0)
        self._find_combobox_match(self.lang_box, app_constants.G_DEF_LANGUAGE,
                                  0)
        tags_l = QVBoxLayout()
        tag_info = misc.ClickedLabel(
            "How do i write namespace & tags? (hover)", parent=self)
        tag_info.setToolTip(
            "Ways to write tags:\n\nNormal tags:\ntag1, tag2, tag3\n\n" +
            "Namespaced tags:\nns1:tag1, ns1:tag2\n\nNamespaced tags with one or more"
            +
            " tags under same namespace:\nns1:[tag1, tag2, tag3], ns2:[tag1, tag2]\n\n"
            +
            "Those three ways of writing namespace & tags can be combined freely.\n"
            +
            "Tags are seperated by a comma, NOT whitespace.\nNamespaces will be capitalized while tags"
            + " will be lowercased.")
        tag_info.setToolTipDuration(99999999)
        tags_l.addWidget(tag_info)
        self.tags_edit = add_check(misc.CompleterTextEdit())
        self.tags_edit.setCompleter(misc.GCompleter(self, False, False))
        self.tags_append = QCheckBox("Append tags", self)
        self.tags_append.setChecked(False)
        if not self._multiple_galleries:
            self.tags_append.hide()
        if self._multiple_galleries:
            self.tags_append.setChecked(app_constants.APPEND_TAGS_GALLERIES)
            tags_ml = QVBoxLayout()
            tags_ml.addWidget(self.tags_append)
            tags_ml.addLayout(checkbox_layout(self.tags_edit), 5)
            tags_l.addLayout(tags_ml, 3)
        else:
            tags_l.addWidget(checkbox_layout(self.tags_edit), 5)
        self.tags_edit.setPlaceholderText(
            "Press Tab to autocomplete (Ctrl + E to show popup)")
        self.type_box = add_check(QComboBox())
        self.type_box.addItems(app_constants.G_TYPES)
        self._find_combobox_match(self.type_box, app_constants.G_DEF_TYPE, 0)
        #self.type_box.currentIndexChanged[int].connect(self.doujin_show)
        #self.doujin_parent = QLineEdit()
        #self.doujin_parent.setVisible(False)
        self.status_box = add_check(QComboBox())
        self.status_box.addItems(app_constants.G_STATUS)
        self._find_combobox_match(self.status_box, app_constants.G_DEF_STATUS,
                                  0)
        self.pub_edit = add_check(QDateEdit())
        self.pub_edit.setCalendarPopup(True)
        self.pub_edit.setDate(QDate.currentDate())
        self.path_lbl = misc.ClickedLabel("")
        self.path_lbl.setWordWrap(True)
        self.path_lbl.clicked.connect(lambda a: utils.open_path(a, a)
                                      if a else None)

        link_layout = QHBoxLayout()
        self.link_lbl = add_check(QLabel(""))
        self.link_lbl.setWordWrap(True)
        self.link_edit = QLineEdit()
        link_layout.addWidget(self.link_edit)
        if self._multiple_galleries:
            link_layout.addLayout(checkbox_layout(self.link_lbl))
        else:
            link_layout.addWidget(checkbox_layout(self.link_lbl))
        self.link_edit.hide()
        self.link_btn = QPushButton("Modify")
        self.link_btn.setFixedWidth(50)
        self.link_btn2 = QPushButton("Set")
        self.link_btn2.setFixedWidth(40)
        self.link_btn.clicked.connect(self.link_modify)
        self.link_btn2.clicked.connect(self.link_set)
        link_layout.addWidget(self.link_btn)
        link_layout.addWidget(self.link_btn2)
        self.link_btn2.hide()

        rating_ = checkbox_layout(self.rating_box)
        lang_ = checkbox_layout(self.lang_box)
        if self._multiple_galleries:
            rating_.insertWidget(0, QLabel("Rating:"))
            lang_.addLayout(rating_)
            lang_l = lang_
        else:
            lang_l = QHBoxLayout()
            lang_l.addWidget(lang_)
            lang_l.addWidget(QLabel("Rating:"), 0, Qt.AlignRight)
            lang_l.addWidget(rating_)

        gallery_layout.addRow("Title:", checkbox_layout(self.title_edit))
        gallery_layout.addRow("Author:", checkbox_layout(self.author_edit))
        gallery_layout.addRow("Description:", checkbox_layout(self.descr_edit))
        gallery_layout.addRow("Language:", lang_l)
        gallery_layout.addRow("Tags:", tags_l)
        gallery_layout.addRow("Type:", checkbox_layout(self.type_box))
        gallery_layout.addRow("Status:", checkbox_layout(self.status_box))
        gallery_layout.addRow("Publication Date:",
                              checkbox_layout(self.pub_edit))
        gallery_layout.addRow("Path:", self.path_lbl)
        gallery_layout.addRow("URL:", link_layout)

        self.title_edit.setFocus()

    def resizeEvent(self, event):
        self.tags_edit.setFixedHeight(event.size().height() // 8)
        self.descr_edit.setFixedHeight(event.size().height() // 12.5)
        return super().resizeEvent(event)

    def _find_combobox_match(self, combobox, key, default):
        f_index = combobox.findText(key, Qt.MatchFixedString)
        if f_index != -1:
            combobox.setCurrentIndex(f_index)
            return True
        else:
            combobox.setCurrentIndex(default)
            return False

    def setGallery(self, gallery):
        "To be used for when editing a gallery"
        if isinstance(gallery, gallerydb.Gallery):
            self.gallery = gallery

            if not self._multiple_galleries:
                self.url_edit.setText(gallery.link)

            self.title_edit.setText(gallery.title)
            self.author_edit.setText(gallery.artist)
            self.descr_edit.setText(gallery.info)
            self.rating_box.setValue(gallery.rating)

            self.tags_edit.setText(utils.tag_to_string(gallery.tags))

            if not self._find_combobox_match(self.lang_box, gallery.language,
                                             1):
                self._find_combobox_match(self.lang_box,
                                          app_constants.G_DEF_LANGUAGE, 1)
            if not self._find_combobox_match(self.type_box, gallery.type, 0):
                self._find_combobox_match(self.type_box,
                                          app_constants.G_DEF_TYPE, 0)
            if not self._find_combobox_match(self.status_box, gallery.status,
                                             0):
                self._find_combobox_match(self.status_box,
                                          app_constants.G_DEF_STATUS, 0)

            gallery_pub_date = "{}".format(gallery.pub_date).split(' ')
            try:
                self.gallery_time = datetime.strptime(gallery_pub_date[1],
                                                      '%H:%M:%S').time()
            except IndexError:
                pass
            qdate_pub_date = QDate.fromString(gallery_pub_date[0],
                                              "yyyy-MM-dd")
            self.pub_edit.setDate(qdate_pub_date)

            self.link_lbl.setText(gallery.link)
            self.path_lbl.setText(gallery.path)

        elif isinstance(gallery, list):
            g = gallery[0]
            if all(map(lambda x: x.title == g.title, gallery)):
                self.title_edit.setText(g.title)
                self.title_edit.g_check.setChecked(True)
            if all(map(lambda x: x.artist == g.artist, gallery)):
                self.author_edit.setText(g.artist)
                self.author_edit.g_check.setChecked(True)
            if all(map(lambda x: x.info == g.info, gallery)):
                self.descr_edit.setText(g.info)
                self.descr_edit.g_check.setChecked(True)
            if all(map(lambda x: x.tags == g.tags, gallery)):
                self.tags_edit.setText(utils.tag_to_string(g.tags))
                self.tags_edit.g_check.setChecked(True)
            if all(map(lambda x: x.language == g.language, gallery)):
                if not self._find_combobox_match(self.lang_box, g.language, 1):
                    self._find_combobox_match(self.lang_box,
                                              app_constants.G_DEF_LANGUAGE, 1)
                self.lang_box.g_check.setChecked(True)
            if all(map(lambda x: x.rating == g.rating, gallery)):
                self.rating_box.setValue(g.rating)
                self.rating_box.g_check.setChecked(True)
            if all(map(lambda x: x.type == g.type, gallery)):
                if not self._find_combobox_match(self.type_box, g.type, 0):
                    self._find_combobox_match(self.type_box,
                                              app_constants.G_DEF_TYPE, 0)
                self.type_box.g_check.setChecked(True)
            if all(map(lambda x: x.status == g.status, gallery)):
                if not self._find_combobox_match(self.status_box, g.status, 0):
                    self._find_combobox_match(self.status_box,
                                              app_constants.G_DEF_STATUS, 0)
                self.status_box.g_check.setChecked(True)
            if all(map(lambda x: x.pub_date == g.pub_date, gallery)):
                gallery_pub_date = "{}".format(g.pub_date).split(' ')
                try:
                    self.gallery_time = datetime.strptime(
                        gallery_pub_date[1], '%H:%M:%S').time()
                except IndexError:
                    pass
                qdate_pub_date = QDate.fromString(gallery_pub_date[0],
                                                  "yyyy-MM-dd")
                self.pub_edit.setDate(qdate_pub_date)
                self.pub_edit.g_check.setChecked(True)
            if all(map(lambda x: x.link == g.link, gallery)):
                self.link_lbl.setText(g.link)
                self.link_lbl.g_check.setChecked(True)

    def newUI(self):

        f_local = QGroupBox("Directory/Archive")
        f_local.setCheckable(False)
        self.main_layout.addWidget(f_local)
        local_layout = QHBoxLayout()
        f_local.setLayout(local_layout)

        choose_folder = QPushButton("From Directory")
        choose_folder.clicked.connect(lambda: self.choose_dir('f'))
        local_layout.addWidget(choose_folder)

        choose_archive = QPushButton("From Archive")
        choose_archive.clicked.connect(lambda: self.choose_dir('a'))
        local_layout.addWidget(choose_archive)

        self.file_exists_lbl = QLabel()
        local_layout.addWidget(self.file_exists_lbl)
        self.file_exists_lbl.hide()

    def choose_dir(self, mode):
        """
        Pass which mode to open the folder explorer in:
        'f': directory
        'a': files
        Or pass a predefined path
        """
        self.done.show()
        self.file_exists_lbl.hide()
        if mode == 'a':
            name = QFileDialog.getOpenFileName(self,
                                               'Choose archive',
                                               filter=utils.FILE_FILTER)
            name = name[0]
        elif mode == 'f':
            name = QFileDialog.getExistingDirectory(self, 'Choose folder')
        elif mode:
            if os.path.exists(mode):
                name = mode
            else:
                return None
        if not name:
            return
        head, tail = os.path.split(name)
        name = os.path.join(head, tail)
        parsed = utils.title_parser(tail)
        self.title_edit.setText(parsed['title'])
        self.author_edit.setText(parsed['artist'])
        self.path_lbl.setText(name)
        if not parsed['language']:
            parsed['language'] = app_constants.G_DEF_LANGUAGE
        l_i = self.lang_box.findText(parsed['language'])
        if l_i != -1:
            self.lang_box.setCurrentIndex(l_i)
        if gallerydb.GalleryDB.check_exists(name):
            self.file_exists_lbl.setText(
                '<font color="red">Gallery already exists.</font>')
            self.file_exists_lbl.show()
        # check galleries
        gs = 1
        if name.endswith(utils.ARCHIVE_FILES):
            gs = len(utils.check_archive(name))
        elif os.path.isdir(name):
            g_dirs, g_archs = utils.recursive_gallery_check(name)
            gs = len(g_dirs) + len(g_archs)
        if gs == 0:
            self.file_exists_lbl.setText(
                '<font color="red">Invalid gallery source.</font>')
            self.file_exists_lbl.show()
            self.done.hide()
        if app_constants.SUBFOLDER_AS_GALLERY:
            if gs > 1:
                self.file_exists_lbl.setText(
                    '<font color="red">More than one galleries detected in source! Use other methods to add.</font>'
                )
                self.file_exists_lbl.show()
                self.done.hide()

    def check(self):
        if not self._multiple_galleries:
            if len(self.title_edit.text()) is 0:
                self.title_edit.setFocus()
                self.title_edit.setStyleSheet(
                    "border-style:outset;border-width:2px;border-color:red;")
                return False
            elif len(self.author_edit.text()) is 0:
                self.author_edit.setText("Unknown")

            if len(self.path_lbl.text()) == 0 or self.path_lbl.text(
            ) == 'No path specified':
                self.path_lbl.setStyleSheet("color:red")
                self.path_lbl.setText('No path specified')
                return False

        return True

    def reject(self):
        if self.check():
            msgbox = QMessageBox()
            msgbox.setText(
                "<font color='red'><b>Noo oniichan! You were about to add a new gallery.</b></font>"
            )
            msgbox.setInformativeText("Do you really want to discard?")
            msgbox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
            msgbox.setDefaultButton(QMessageBox.No)
            if msgbox.exec() == QMessageBox.Yes:
                self.close()
        else:
            self.close()

    def web_metadata(self, url, btn_widget, pgr_widget):
        if not self.path_lbl.text():
            return
        self.link_lbl.setText(url)
        btn_widget.hide()
        pgr_widget.show()

        def status(stat):
            def do_hide():
                try:
                    pgr_widget.hide()
                    btn_widget.show()
                except RuntimeError:
                    pass

            if stat:
                do_hide()
            else:
                danger = """QProgressBar::chunk {
                    background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0,stop: 0 #FF0350,stop: 0.4999 #FF0020,stop: 0.5 #FF0019,stop: 1 #FF0000 );
                    border-bottom-right-radius: 5px;
                    border-bottom-left-radius: 5px;
                    border: .px solid black;}"""
                pgr_widget.setStyleSheet(danger)
                QTimer.singleShot(3000, do_hide)

        def gallery_picker(gallery, title_url_list, q):
            self.parent_widget._web_metadata_picker(gallery, title_url_list, q,
                                                    self)

        try:
            dummy_gallery = self.make_gallery(self.gallery, False)
        except AttributeError:
            dummy_gallery = self.make_gallery(gallerydb.Gallery(), False, True)
        if not dummy_gallery:
            status(False)
            return None

        dummy_gallery._g_dialog_url = url
        self._fetch_inst.galleries = [dummy_gallery]
        self._disconnect()
        self._fetch_inst.GALLERY_PICKER.connect(gallery_picker)
        self._fetch_inst.GALLERY_EMITTER.connect(self.set_web_metadata)
        self._fetch_inst.FINISHED.connect(status)
        self._fetch_thread.start()

    def set_web_metadata(self, metadata):
        assert isinstance(metadata, gallerydb.Gallery)
        self.link_lbl.setText(metadata.link)
        self.title_edit.setText(metadata.title)
        self.author_edit.setText(metadata.artist)
        tags = ""
        lang = ['English', 'Japanese']
        self._find_combobox_match(self.lang_box, metadata.language, 2)
        self.tags_edit.setText(utils.tag_to_string(metadata.tags))
        pub_string = "{}".format(metadata.pub_date)
        pub_date = QDate.fromString(pub_string.split()[0], "yyyy-MM-dd")
        self.pub_edit.setDate(pub_date)
        self._find_combobox_match(self.type_box, metadata.type, 0)

    def make_gallery(self, new_gallery, add_to_model=True, new=False):
        def is_checked(widget):
            return widget.g_check.isChecked()

        if self.check():
            if is_checked(self.title_edit):
                new_gallery.title = self.title_edit.text()
                log_d('Adding gallery title')
            if is_checked(self.author_edit):
                new_gallery.artist = self.author_edit.text()
                log_d('Adding gallery artist')
            if not self._multiple_galleries:
                new_gallery.path = self.path_lbl.text()
                log_d('Adding gallery path')
            if is_checked(self.descr_edit):
                new_gallery.info = self.descr_edit.toPlainText()
                log_d('Adding gallery descr')
            if is_checked(self.type_box):
                new_gallery.type = self.type_box.currentText()
                log_d('Adding gallery type')
            if is_checked(self.lang_box):
                new_gallery.language = self.lang_box.currentText()
                log_d('Adding gallery lang')
            if is_checked(self.rating_box):
                new_gallery.rating = self.rating_box.value()
                log_d('Adding gallery rating')
            if is_checked(self.status_box):
                new_gallery.status = self.status_box.currentText()
                log_d('Adding gallery status')
            if is_checked(self.tags_edit):
                if self.tags_append.isChecked():
                    new_gallery.tags = utils.tag_to_dict(
                        utils.tag_to_string(new_gallery.tags) + "," +
                        self.tags_edit.toPlainText())
                else:
                    new_gallery.tags = utils.tag_to_dict(
                        self.tags_edit.toPlainText())
                log_d('Adding gallery: tagging to dict')
            if is_checked(self.pub_edit):
                qpub_d = self.pub_edit.date().toString("ddMMyyyy")
                dpub_d = datetime.strptime(qpub_d, "%d%m%Y").date()
                try:
                    d_t = self.gallery_time
                except AttributeError:
                    d_t = datetime.now().time().replace(microsecond=0)
                dpub_d = datetime.combine(dpub_d, d_t)
                new_gallery.pub_date = dpub_d
                log_d('Adding gallery pub date')
            if is_checked(self.link_lbl):
                new_gallery.link = self.link_lbl.text()
                log_d('Adding gallery link')

            if new:
                if not new_gallery.chapters:
                    log_d('Starting chapters')
                    thread = threading.Thread(target=utils.make_chapters,
                                              args=(new_gallery, ))
                    thread.start()
                    thread.join()
                    log_d('Finished chapters')
                    if new and app_constants.MOVE_IMPORTED_GALLERIES:
                        app_constants.OVERRIDE_MONITOR = True
                        new_gallery.move_gallery()
                if add_to_model:
                    self.parent_widget.default_manga_view.add_gallery(
                        new_gallery, True)
                    log_i('Sent gallery to model')
            else:
                if add_to_model:
                    self.parent_widget.default_manga_view.replace_gallery(
                        [new_gallery], False)
            return new_gallery

    def link_set(self):
        t = self.link_edit.text()
        self.link_edit.hide()
        self.link_lbl.show()
        self.link_lbl.setText(t)
        self.link_btn2.hide()
        self.link_btn.show()

    def link_modify(self):
        t = self.link_lbl.text()
        self.link_lbl.hide()
        self.link_edit.show()
        self.link_edit.setText(t)
        self.link_btn.hide()
        self.link_btn2.show()

    def _disconnect(self):
        try:
            self._fetch_inst.GALLERY_PICKER.disconnect()
            self._fetch_inst.GALLERY_EMITTER.disconnect()
            self._fetch_inst.FINISHED.disconnect()
        except TypeError:
            pass

    def delayed_close(self):
        if self._fetch_thread.isRunning():
            self._fetch_thread.finished.connect(self.close)
            self.hide()
        else:
            self.close()

    def accept(self):
        self.make_gallery(gallerydb.Gallery(), new=True)
        self.delayed_close()

    def accept_edit(self):
        gallerydb.execute(database.db.DBBase.begin, True)
        app_constants.APPEND_TAGS_GALLERIES = self.tags_append.isChecked()
        settings.set(app_constants.APPEND_TAGS_GALLERIES, 'Application',
                     'append tags to gallery')
        for g in self._edit_galleries:
            self.make_gallery(g)
        self.delayed_close()
        gallerydb.execute(database.db.DBBase.end, True)

    def reject_edit(self):
        self.delayed_close()
Example #24
0
class MainWindow(QMainWindow, Ui_MainWindow):
    qtcb_enumerate = pyqtSignal(str, str, str, type((0, )), type((0, )), int,
                                int)
    qtcb_connected = pyqtSignal(int)
    qtcb_disconnected = pyqtSignal(int)

    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)

        self.setupUi(self)

        # Setting the minimum width of the setup tab ensures, that other tabs can grow
        # the window if more space is required, but we have a sane default for status
        # messages. Setting the minimum width of the main window itself would enfoce
        # it, even if children (i.e. tabs) need more space.
        self.tab_setup.setMinimumWidth(550)

        signal.signal(signal.SIGINT, self.exit_brickv)
        signal.signal(signal.SIGTERM, self.exit_brickv)

        self.async_thread = async_start_thread(self)

        title = 'Brick Viewer ' + config.BRICKV_FULL_VERSION

        self.setWindowTitle(title)

        self.delayed_update_tree_view_timer = QTimer(self)
        self.delayed_update_tree_view_timer.timeout.connect(
            self.update_tree_view)
        self.delayed_update_tree_view_timer.setInterval(100)

        self.tree_view_model_labels = ['Name', 'UID', 'Position', 'FW Version']
        self.tree_view_model = QStandardItemModel(self)
        self.tree_view_proxy_model = DevicesProxyModel(self)
        self.tree_view_proxy_model.setSourceModel(self.tree_view_model)
        self.tree_view.setModel(self.tree_view_proxy_model)
        self.tree_view.activated.connect(self.item_activated)
        self.set_tree_view_defaults()

        inventory.info_changed.connect(
            lambda: self.delayed_update_tree_view_timer.start())

        self.tab_widget.removeTab(1)  # remove dummy tab
        self.tab_widget.setUsesScrollButtons(True)  # force scroll buttons

        self.update_tab_button = IconButton(
            QIcon(load_pixmap('update-icon-normal.png')),
            QIcon(load_pixmap('update-icon-hover.png')),
            parent=self.tab_setup)
        self.update_tab_button.setToolTip('Updates available')
        self.update_tab_button.clicked.connect(self.flashing_clicked)
        self.update_tab_button.hide()

        self.name = '<unknown>'
        self.uid = '<unknown>'
        self.version = (0, 0, 0)

        self.disconnect_times = []

        self.qtcb_enumerate.connect(self.cb_enumerate)
        self.qtcb_connected.connect(self.cb_connected)
        self.qtcb_disconnected.connect(self.cb_disconnected)

        self.ipcon = IPConnection()
        self.ipcon.register_callback(IPConnection.CALLBACK_ENUMERATE,
                                     self.qtcb_enumerate.emit)
        self.ipcon.register_callback(IPConnection.CALLBACK_CONNECTED,
                                     self.qtcb_connected.emit)
        self.ipcon.register_callback(IPConnection.CALLBACK_DISCONNECTED,
                                     self.qtcb_disconnected.emit)

        self.current_device_info = None
        self.flashing_window = None
        self.advanced_window = None
        self.data_logger_window = None
        self.delayed_refresh_updates_timer = QTimer(self)
        self.delayed_refresh_updates_timer.timeout.connect(
            self.delayed_refresh_updates)
        self.delayed_refresh_updates_timer.setInterval(100)
        self.reset_view()
        self.button_advanced.setDisabled(True)

        self.fw_version_fetcher = LatestFWVersionFetcher()
        self.fw_version_fetcher.fw_versions_avail.connect(
            self.fw_versions_fetched)
        self.fw_version_fetcher_thread = QThread(self)
        self.fw_version_fetcher_thread.setObjectName(
            "fw_version_fetcher_thread")

        if config.get_auto_search_for_updates():
            self.enable_auto_search_for_updates()
        else:
            self.disable_auto_search_for_updates()

        self.tab_widget.currentChanged.connect(self.tab_changed)
        self.tab_widget.setMovable(True)
        self.tab_widget.tabBar().installEventFilter(self)

        self.button_connect.clicked.connect(self.connect_clicked)
        self.button_flashing.clicked.connect(self.flashing_clicked)
        self.button_advanced.clicked.connect(self.advanced_clicked)
        self.button_data_logger.clicked.connect(self.data_logger_clicked)
        self.plugin_manager = PluginManager()

        # host info
        self.host_infos = config.get_host_infos(config.HOST_INFO_COUNT)
        self.host_index_changing = True

        for host_info in self.host_infos:
            self.combo_host.addItem(host_info.host)

        self.last_host = None
        self.combo_host.installEventFilter(self)
        self.combo_host.currentIndexChanged.connect(self.host_index_changed)

        self.spinbox_port.setValue(self.host_infos[0].port)
        self.spinbox_port.valueChanged.connect(self.port_changed)
        self.spinbox_port.installEventFilter(self)

        self.checkbox_authentication.stateChanged.connect(
            self.authentication_state_changed)

        self.label_secret.hide()
        self.edit_secret.hide()
        self.edit_secret.setEchoMode(QLineEdit.Password)
        self.edit_secret.textEdited.connect(self.secret_changed)
        self.edit_secret.installEventFilter(self)

        self.checkbox_secret_show.hide()
        self.checkbox_secret_show.stateChanged.connect(
            self.secret_show_state_changed)

        self.checkbox_remember_secret.hide()
        self.checkbox_remember_secret.stateChanged.connect(
            self.remember_secret_state_changed)

        self.checkbox_authentication.setChecked(
            self.host_infos[0].use_authentication)
        self.edit_secret.setText(self.host_infos[0].secret)
        self.checkbox_remember_secret.setChecked(
            self.host_infos[0].remember_secret)

        self.host_index_changing = False

        # auto-reconnect
        self.label_auto_reconnects.hide()
        self.auto_reconnects = 0

        # RED Session losts
        self.label_red_session_losts.hide()
        self.red_session_losts = 0

        # fusion style
        self.check_fusion_gui_style.setChecked(
            config.get_use_fusion_gui_style())
        self.check_fusion_gui_style.stateChanged.connect(
            self.gui_style_changed)

        self.checkbox_auto_search_for_updates.setChecked(
            config.get_auto_search_for_updates())
        self.checkbox_auto_search_for_updates.stateChanged.connect(
            self.auto_search_for_updates_changed)

        self.button_update_pixmap_normal = load_pixmap(
            'update-icon-normal.png')
        self.button_update_pixmap_hover = load_pixmap('update-icon-hover.png')

        self.last_status_message_id = ''

    def disable_auto_search_for_updates(self):
        self.fw_version_fetcher.abort()

    def enable_auto_search_for_updates(self):
        self.fw_version_fetcher.reset()
        self.fw_version_fetcher.moveToThread(self.fw_version_fetcher_thread)
        self.fw_version_fetcher_thread.started.connect(
            self.fw_version_fetcher.run)
        self.fw_version_fetcher.finished.connect(
            self.fw_version_fetcher_thread.quit)
        self.fw_version_fetcher_thread.start()

    # override QMainWindow.closeEvent
    def closeEvent(self, event):
        if not self.exit_logger():
            event.ignore()
            return

        self.exit_brickv()
        event.accept()
        async_stop_thread()

        # Without this, the quit event seems to not reach the main loop under OSX.
        QApplication.quit()

    def exit_brickv(self, signl=None, frme=None):
        self.update_current_host_info()
        config.set_host_infos(self.host_infos)

        self.do_disconnect()

        if signl != None and frme != None:
            print("Received SIGINT or SIGTERM, shutting down.")
            sys.exit()

    def exit_logger(self):
        exitBrickv = True
        if (self.data_logger_window is not None) and \
           (self.data_logger_window.data_logger_thread is not None) and \
           (not self.data_logger_window.data_logger_thread.stopped):
            quit_msg = "The Data Logger is running. Are you sure you want to exit the program?"
            reply = QMessageBox.question(self, 'Message', quit_msg,
                                         QMessageBox.Yes, QMessageBox.No)

            if reply == QMessageBox.Yes:
                self.data_logger_window.data_logger_thread.stop()
            else:
                exitBrickv = False

        return exitBrickv

    def host_index_changed(self, i):
        if i < 0:
            return

        self.host_index_changing = True

        self.spinbox_port.setValue(self.host_infos[i].port)
        self.checkbox_authentication.setChecked(
            self.host_infos[i].use_authentication)
        self.edit_secret.setText(self.host_infos[i].secret)
        self.checkbox_remember_secret.setChecked(
            self.host_infos[i].remember_secret)

        self.host_index_changing = False

    def port_changed(self, _value):
        self.update_current_host_info()

    def authentication_state_changed(self, state):
        visible = state == Qt.Checked

        self.label_secret.setVisible(visible)
        self.edit_secret.setVisible(visible)
        self.checkbox_secret_show.setVisible(visible)
        self.checkbox_remember_secret.setVisible(visible)

        self.update_current_host_info()

    def secret_changed(self):
        self.update_current_host_info()

    def secret_show_state_changed(self, state):
        if state == Qt.Checked:
            self.edit_secret.setEchoMode(QLineEdit.Normal)
        else:
            self.edit_secret.setEchoMode(QLineEdit.Password)

        self.update_current_host_info()

    def remember_secret_state_changed(self, _state):
        self.update_current_host_info()

    def tab_changed(self, i):
        if not hasattr(self.tab_widget.widget(i), '_info'):
            new_current_device_info = None
        else:
            new_current_device_info = self.tab_widget.widget(i)._info
            new_current_device_info.plugin.start_plugin()

        # stop the now deselected plugin, if there is one that's running
        if self.current_device_info is not None:
            self.current_device_info.plugin.stop_plugin()

        self.current_device_info = new_current_device_info

    def update_current_host_info(self):
        if self.host_index_changing:
            return

        i = self.combo_host.currentIndex()

        if i < 0:
            return

        self.host_infos[i].port = self.spinbox_port.value()
        self.host_infos[
            i].use_authentication = self.checkbox_authentication.isChecked()
        self.host_infos[i].secret = self.edit_secret.text()
        self.host_infos[
            i].remember_secret = self.checkbox_remember_secret.isChecked()

    def gui_style_changed(self):
        config.set_use_fusion_gui_style(
            self.check_fusion_gui_style.isChecked())

        QMessageBox.information(
            self, 'GUI Style',
            'GUI style change will be applied on next Brick Viewer start.',
            QMessageBox.Ok)

    def auto_search_for_updates_changed(self):
        config.set_auto_search_for_updates(
            self.checkbox_auto_search_for_updates.isChecked())
        if self.checkbox_auto_search_for_updates.isChecked():
            self.enable_auto_search_for_updates()
        else:
            self.disable_auto_search_for_updates()

    def remove_all_device_infos(self):
        for device_info in inventory.get_device_infos():
            self.remove_device_info(device_info.uid)

    def remove_device_info(self, uid):
        tab_id = self.tab_for_uid(uid)
        device_info = inventory.get_info(uid)

        device_info.plugin.stop_plugin()
        device_info.plugin.destroy_plugin()

        if tab_id >= 0:
            self.tab_widget.removeTab(tab_id)

        # ensure that the widget gets correctly destroyed. otherwise QWidgets
        # tend to leak as Python is not able to collect their PyQt object
        tab_window = device_info.tab_window
        device_info.tab_window = None

        # If we reboot the RED Brick, the tab_window sometimes is
        # already None here
        if tab_window != None:
            tab_window.hide()
            tab_window.setParent(None)

        plugin = device_info.plugin
        device_info.plugin = None

        if plugin != None:
            plugin.hide()
            plugin.setParent(None)

        inventory.remove_info(uid)

    def reset_view(self):
        self.tab_widget.setCurrentIndex(0)
        self.remove_all_device_infos()
        self.update_tree_view()

    def do_disconnect(self):
        self.auto_reconnects = 0
        self.label_auto_reconnects.hide()

        self.red_session_losts = 0
        self.label_red_session_losts.hide()

        self.reset_view()
        async_next_session()

        # force garbage collection, to ensure that all plugin related objects
        # got destroyed before disconnect is called. this is especially
        # important for the RED Brick plugin because its relies on releasing
        # the the RED Brick API objects in the __del__ method as a last resort
        # to avoid leaking object references. but this only works if garbage
        # collection is done before disconnect is called
        gc.collect()

        try:
            self.ipcon.disconnect()
        except:
            pass

    def do_authenticate(self, is_auto_reconnect):
        if not self.checkbox_authentication.isChecked():
            return True

        try:
            secret = self.edit_secret.text()
            # Try to encode the secret, as only ASCII chars are allowed. Don't save the result, as the IP Connection does the same.
            secret.encode('ascii')
        except:
            self.do_disconnect()

            QMessageBox.critical(
                self, 'Connection',
                'Authentication secret cannot contain non-ASCII characters.',
                QMessageBox.Ok)
            return False

        self.ipcon.set_auto_reconnect(
            False)  # don't auto-reconnect on authentication error

        try:
            self.ipcon.authenticate(secret)
        except:
            self.do_disconnect()

            if is_auto_reconnect:
                extra = ' after auto-reconnect'
            else:
                extra = ''

            QMessageBox.critical(
                self, 'Connection', 'Could not authenticate' + extra +
                '. Check secret and ensure ' +
                'authentication for Brick Daemon is enabled.', QMessageBox.Ok)
            return False

        self.ipcon.set_auto_reconnect(True)

        return True

    def flashing_clicked(self):
        if self.flashing_window is None:
            self.flashing_window = FlashingWindow(self)
        else:
            self.flashing_window.refresh_update_tree_view()

        self.flashing_window.show()
        self.flashing_window.tab_widget.setCurrentWidget(
            self.flashing_window.tab_updates)

    def advanced_clicked(self):
        if self.advanced_window is None:
            self.advanced_window = AdvancedWindow(self)

        self.advanced_window.show()

    def data_logger_clicked(self):
        if self.data_logger_window is None:
            self.data_logger_window = DataLoggerWindow(self, self.host_infos)

        self.data_logger_window.show()

    def connect_error(self):
        self.setDisabled(False)
        self.button_connect.setText("Connect")
        QMessageBox.critical(
            self, 'Connection',
            'Could not connect. Please check host, check ' +
            'port and ensure that Brick Daemon is running.')

    def connect_clicked(self):
        if self.ipcon.get_connection_state(
        ) == IPConnection.CONNECTION_STATE_DISCONNECTED:
            self.last_host = self.combo_host.currentText()
            self.setDisabled(True)
            self.button_connect.setText("Connecting...")

            async_call(self.ipcon.connect,
                       (self.last_host, self.spinbox_port.value()), None,
                       self.connect_error)
        else:
            self.do_disconnect()

    def item_activated(self, index):
        index = self.tree_view_proxy_model.mapToSource(index)
        position_index = index.sibling(index.row(), 2)

        extension_clicked = position_index.isValid() and position_index.data(
        ).startswith('Ext')
        if extension_clicked:
            extension_index = int(position_index.data().replace('Ext', ''))
            index = index.parent()

        uid_index = index.sibling(index.row(), 1)

        if uid_index.isValid():
            plugin = self.show_plugin(uid_index.data())
            if extension_clicked:
                plugin.show_extension(extension_index)

    def show_brick_update(self, url_part):
        if self.flashing_window is None:
            self.flashing_window = FlashingWindow(self)

        self.flashing_window.show()
        self.flashing_window.refresh_update_tree_view()
        self.flashing_window.show_brick_update(url_part)

    def show_bricklet_update(self, parent_uid, port):
        if self.flashing_window is None:
            self.flashing_window = FlashingWindow(self)

        self.flashing_window.show()
        self.flashing_window.refresh_update_tree_view()
        self.flashing_window.show_bricklet_update(parent_uid, port)

    def show_extension_update(self, master_uid):
        if self.flashing_window is None:
            self.flashing_window = FlashingWindow(self)

        self.flashing_window.show()
        self.flashing_window.refresh_update_tree_view()
        self.flashing_window.show_extension_update(master_uid)

    def show_red_brick_update(self):
        text = "To update the RED Brick Image, please follow the instructions " + \
               "<a href=https://www.tinkerforge.com/en/doc/Hardware/Bricks/RED_Brick.html#red-brick-copy-image>here</a>."
        QMessageBox.information(self, "RED Brick Update", text)

    def create_tab_window(self, device_info, ipcon):
        tab_window = TabWindow(self.tab_widget, device_info.name, self.untab)
        tab_window._info = device_info
        tab_window.add_callback_on_tab(lambda index:
                                       self.ipcon.get_connection_state() == IPConnection.CONNECTION_STATE_PENDING and \
                                       self.tab_widget.setTabEnabled(index, False),
                                       'main_window_disable_tab_if_connection_pending')

        layout = QVBoxLayout(tab_window)
        info_bars = [QHBoxLayout(), QHBoxLayout()]

        # uid
        info_bars[0].addWidget(QLabel('UID:'))

        label = QLabel('{0}'.format(device_info.uid))
        label.setTextInteractionFlags(Qt.TextSelectableByMouse
                                      | Qt.TextSelectableByKeyboard)

        info_bars[0].addWidget(label)
        info_bars[0].addSpacerItem(QSpacerItem(20, 1, QSizePolicy.Preferred))

        # firmware version
        label_version_name = QLabel('Version:')
        label_version = QLabel('Querying...')

        button_update = QPushButton(QIcon(self.button_update_pixmap_normal),
                                    'Update')
        button_update.installEventFilter(self)

        if isinstance(device_info, BrickREDInfo):
            button_update.clicked.connect(self.show_red_brick_update)
        elif device_info.flashable_like_bricklet:
            button_update.clicked.connect(lambda: self.show_bricklet_update(
                device_info.connected_uid, device_info.position))
        elif device_info.kind == 'brick':
            button_update.clicked.connect(
                lambda: self.show_brick_update(device_info.url_part))

        if not device_info.plugin.has_custom_version(label_version_name,
                                                     label_version):
            label_version_name.setText('FW Version:')
            label_version.setText(
                get_version_string(device_info.plugin.firmware_version))

        info_bars[0].addWidget(label_version_name)
        info_bars[0].addWidget(label_version)
        info_bars[0].addWidget(button_update)
        button_update.hide()
        tab_window.button_update = button_update
        info_bars[0].addSpacerItem(QSpacerItem(20, 1, QSizePolicy.Preferred))

        # timeouts
        info_bars[0].addWidget(QLabel('Timeouts:'))

        label_timeouts = QLabel('0')

        info_bars[0].addWidget(label_timeouts)
        info_bars[0].addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding))

        # connected uid
        if device_info.connected_uid != '0':
            info_bars[1].addWidget(QLabel('Connected to:'))

            button = QToolButton()
            button.setText(device_info.connected_uid)
            button.clicked.connect(
                lambda: self.show_plugin(device_info.connected_uid))
            device_info.plugin.button_parent = button

            info_bars[1].addWidget(button)
            info_bars[1].addSpacerItem(
                QSpacerItem(20, 1, QSizePolicy.Preferred))

        # position
        info_bars[1].addWidget(QLabel('Position:'))
        label_position = QLabel('{0}'.format(device_info.position.title()))
        device_info.plugin.label_position = label_position
        info_bars[1].addWidget(label_position)
        info_bars[1].addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding))

        # configs
        configs = device_info.plugin.get_configs()

        def config_changed(combobox):
            i = combobox.currentIndex()

            if i < 0:
                return

            combobox.itemData(i).trigger()

        if len(configs) > 0:
            for cfg in configs:
                if cfg[1] != None:
                    combobox = QComboBox()

                    for i, item in enumerate(cfg[2]):
                        combobox.addItem(item.text(), item)
                        item.triggered.connect(
                            functools.partial(combobox.setCurrentIndex, i))

                    combobox.currentIndexChanged.connect(
                        functools.partial(config_changed, combobox))

                    info_bars[cfg[0]].addWidget(QLabel(cfg[1]))
                    info_bars[cfg[0]].addWidget(combobox)
                elif len(cfg[2]) > 0:
                    checkbox = QCheckBox(cfg[2][0].text())
                    cfg[2][0].toggled.connect(checkbox.setChecked)
                    checkbox.toggled.connect(cfg[2][0].setChecked)

                    info_bars[cfg[0]].addWidget(checkbox)

        # actions
        actions = device_info.plugin.get_actions()

        if len(actions) > 0:
            for action in actions:
                if action[1] != None:
                    button = QPushButton(action[1])
                    menu = QMenu()

                    for item in action[2]:
                        menu.addAction(item)

                    button.setMenu(menu)
                elif len(action[2]) > 0:
                    button = QPushButton(action[2][0].text())
                    button.clicked.connect(action[2][0].trigger)

                info_bars[action[0]].addWidget(button)

        def more_clicked(button, info_bar):
            visible = button.text().replace(
                '&', '') == 'More'  # remove &s, they mark the buttons hotkey

            if visible:
                button.setText('Less')
            else:
                button.setText('More')

            for i in range(info_bar.count()):
                widget = info_bar.itemAt(i).widget()

                if widget != None:
                    widget.setVisible(visible)

        more_button = QPushButton('More')
        more_button.clicked.connect(
            lambda: more_clicked(more_button, info_bars[1]))

        info_bars[0].addWidget(more_button)

        for i in range(info_bars[1].count()):
            widget = info_bars[1].itemAt(i).widget()

            if widget != None:
                widget.hide()

        layout.addLayout(info_bars[0])
        layout.addLayout(info_bars[1])

        line = QFrame()
        line.setObjectName("MainWindow_line")
        line.setFrameShape(QFrame.HLine)
        line.setFrameShadow(QFrame.Sunken)

        device_info.plugin.label_timeouts = label_timeouts
        device_info.plugin.label_version = label_version
        device_info.plugin.layout().setContentsMargins(0, 0, 0, 0)

        layout.addWidget(line)

        if device_info.plugin.has_comcu:
            device_info.plugin.widget_bootloader = COMCUBootloader(
                ipcon, device_info)
            device_info.plugin.widget_bootloader.hide()
            layout.addWidget(device_info.plugin.widget_bootloader)

        layout.addWidget(device_info.plugin, 1)

        return tab_window

    def tab_move(self, event):
        # visualize rearranging of tabs (if allowed by tab_widget)
        if self.tab_widget.isMovable():
            if event.type(
            ) == QEvent.MouseButtonPress and event.button() & Qt.LeftButton:
                QApplication.setOverrideCursor(QCursor(Qt.SizeHorCursor))
            elif event.type(
            ) == QEvent.MouseButtonRelease and event.button() & Qt.LeftButton:
                QApplication.restoreOverrideCursor()

        return False

    def untab(self, tab_index):
        tab = self.tab_widget.widget(tab_index)
        tab.untab()
        tab._info.plugin.start_plugin()
        self.tab_widget.setCurrentIndex(0)

    def connect_on_return(self, event):
        if event.type() == QEvent.KeyPress and (event.key() == Qt.Key_Return or
                                                event.key() == Qt.Key_Enter):
            self.connect_clicked()
            return True

        return False

    def eventFilter(self, source, event):
        if source is self.tab_widget.tabBar():
            return self.tab_move(event)

        if source is self.combo_host or source is self.spinbox_port or source is self.edit_secret:
            return self.connect_on_return(event)

        if isinstance(source, QPushButton) and event.type() == QEvent.Enter:
            source.setIcon(QIcon(self.button_update_pixmap_hover))
        elif isinstance(source, QPushButton) and event.type() == QEvent.Leave:
            source.setIcon(QIcon(self.button_update_pixmap_normal))

        return False

    def tab_for_uid(self, uid):
        for index in range(1, self.tab_widget.count()):
            try:
                if self.tab_widget.widget(index)._info.uid == uid:
                    return index
            except:
                pass

        return -1

    def show_plugin(self, uid):
        device_info = inventory.get_info(uid)

        if device_info == None:
            return

        index = self.tab_for_uid(uid)
        tab_window = device_info.tab_window

        if index > 0 and self.tab_widget.isTabEnabled(index):
            self.tab_widget.setCurrentIndex(index)

        QApplication.setActiveWindow(tab_window)

        tab_window.show()
        tab_window.activateWindow()
        tab_window.raise_()

        return device_info.plugin

    def cb_enumerate(self, uid, connected_uid, position, hardware_version,
                     firmware_version, device_identifier, enumeration_type):
        if self.ipcon.get_connection_state(
        ) != IPConnection.CONNECTION_STATE_CONNECTED:
            # ignore enumerate callbacks that arrived after the connection got closed
            return

        if enumeration_type in [
                IPConnection.ENUMERATION_TYPE_AVAILABLE,
                IPConnection.ENUMERATION_TYPE_CONNECTED
        ]:
            device_info = inventory.get_info(uid)

            # If the enum_type is CONNECTED, the bricklet was restarted externally.
            # The plugin could now be in an inconsistent state.
            if enumeration_type == IPConnection.ENUMERATION_TYPE_CONNECTED and device_info is not None:
                if device_info.connected_uid != connected_uid:
                    # Fix connections if bricklet was connected to another brick.
                    parent_info = inventory.get_info(device_info.connected_uid)

                    if parent_info is not None:
                        parent_info.connections_remove_item(
                            (device_info.position, device_info))
                        self.show_status(
                            "Hot plugging is not supported! Please reset Brick with UID {} and reconnect Brick Viewer."
                            .format(device_info.connected_uid),
                            message_id='mainwindow_hotplug')

                    device_info.reverse_connection = connected_uid
                elif device_info.position != position:
                    # Bricklet was connected to the same brick, but to another port
                    self.show_status(
                        "Hot plugging is not supported! Please reset Brick with UID {} and reconnect Brick Viewer."
                        .format(device_info.connected_uid),
                        message_id='mainwindow_hotplug')

                # If the plugin is not running, pause will do nothing, so it is always save to call it.
                # The plugin will be (unconditionally) resumed later, as resume also only does something
                # if it was paused before (e.g. here).
                if device_info.plugin is not None:
                    device_info.plugin.pause_plugin()

            if device_info == None:
                if device_identifier == BrickMaster.DEVICE_IDENTIFIER:
                    device_info = BrickMasterInfo()
                elif device_identifier == BrickRED.DEVICE_IDENTIFIER:
                    device_info = BrickREDInfo()
                elif hat_brick_supported and device_identifier == BrickHAT.DEVICE_IDENTIFIER:
                    device_info = BrickHATInfo()
                elif hat_zero_brick_supported and device_identifier == BrickHATZero.DEVICE_IDENTIFIER:
                    device_info = BrickHATZeroInfo()
                elif device_identifier == BrickletIsolator.DEVICE_IDENTIFIER:
                    device_info = BrickletIsolatorInfo()
                elif str(device_identifier).startswith('20'):
                    device_info = TNGInfo()
                elif '0' <= position <= '9':
                    device_info = BrickInfo()
                else:
                    device_info = BrickletInfo()

            position = position.lower()

            device_info.uid = uid
            device_info.connected_uid = connected_uid
            device_info.position = position
            device_info.hardware_version = hardware_version

            if device_identifier != BrickRED.DEVICE_IDENTIFIER:
                device_info.firmware_version_installed = firmware_version

            device_info.device_identifier = device_identifier
            device_info.enumeration_type = enumeration_type

            # Update connections and reverse_connection with new device
            for info in inventory.get_device_infos():
                if info == device_info:
                    continue

                def add_to_connections(info_to_add, connected_info):
                    hotplug = connected_info.connections_add_item(
                        (info_to_add.position, info_to_add))
                    info_to_add.reverse_connection = connected_info

                    # '0' is the port where other stacks connected by RS485 extensions are connected. Multiple connections are allowed here.
                    if hotplug and info_to_add.position != '0':
                        self.show_status(
                            "Hot plugging is not supported! Please reset Brick with UID {} and reconnect Brick Viewer."
                            .format(connected_info.uid),
                            message_id='mainwindow_hotplug')

                if info.uid != '' and info.uid == device_info.connected_uid:
                    if device_info in info.connections_values(
                    ):  # device was already connected, but to another port
                        info.connections_remove_value(device_info)

                    if device_info not in info.connections_get(
                            device_info.position):
                        add_to_connections(device_info, info)

                if info.connected_uid != '' and info.connected_uid == device_info.uid:
                    if info in device_info.connections_values(
                    ):  # device was already connected, but to another port
                        device_info.connections_remove_value(info)

                    if info not in device_info.connections_get(info.position):
                        add_to_connections(info, device_info)

            if device_info.plugin == None:
                self.plugin_manager.create_plugin_instance(
                    device_identifier, self.ipcon, device_info)

                device_info.tab_window = self.create_tab_window(
                    device_info, self.ipcon)
                device_info.tab_window.setWindowFlags(Qt.Widget)
                device_info.tab_window.tab()

                inventory.add_info(device_info)

            device_info.update_firmware_version_latest()

            inventory.sync()

            # The plugin was paused before if it was reconnected.
            device_info.plugin.resume_plugin()
        elif enumeration_type == IPConnection.ENUMERATION_TYPE_DISCONNECTED:
            self.remove_device_tab(uid)

    def remove_device_tab(self, uid):
        device_info = inventory.get_info(uid)

        if device_info == None:
            return

        assert isinstance(device_info, DeviceInfo)

        self.tab_widget.setCurrentIndex(0)
        self.remove_device_info(device_info.uid)

        for other_info in inventory.get_device_infos():
            other_info.connections_remove_value(device_info)

        self.update_tree_view()

    def hack_to_remove_red_brick_tab(self, uid):
        device_info = inventory.get_info(uid)

        if device_info == None:
            return

        assert isinstance(device_info, DeviceInfo)

        self.tab_widget.setCurrentIndex(0)
        self.remove_device_info(device_info.uid)

        self.red_session_losts += 1
        self.label_red_session_losts.setText(
            'RED Brick Session Loss Count: {0}'.format(self.red_session_losts))
        self.label_red_session_losts.show()

        self.update_tree_view()

    def cb_connected(self, connect_reason):
        self.disconnect_times = []

        self.update_ui_state()

        if connect_reason == IPConnection.CONNECT_REASON_REQUEST:
            self.setDisabled(False)

            self.auto_reconnects = 0
            self.label_auto_reconnects.hide()

            self.red_session_losts = 0
            self.label_red_session_losts.hide()

            self.ipcon.set_auto_reconnect(True)

            index = self.combo_host.findText(self.last_host)

            if index >= 0:
                self.combo_host.removeItem(index)

                host_info = self.host_infos[index]

                del self.host_infos[index]
                self.host_infos.insert(0, host_info)
            else:
                index = self.combo_host.currentIndex()

                host_info = self.host_infos[index].duplicate()
                host_info.host = self.last_host

                self.host_infos.insert(0, host_info)

            self.combo_host.insertItem(-1, self.last_host)
            self.combo_host.setCurrentIndex(0)

            while self.combo_host.count() > config.HOST_INFO_COUNT:
                self.combo_host.removeItem(self.combo_host.count() - 1)

            if not self.do_authenticate(False):
                return

            try:
                self.ipcon.enumerate()
            except:
                self.update_ui_state()
        elif connect_reason == IPConnection.CONNECT_REASON_AUTO_RECONNECT:
            self.auto_reconnects += 1
            self.label_auto_reconnects.setText(
                'Auto-Reconnect Count: {0}'.format(self.auto_reconnects))
            self.label_auto_reconnects.show()

            if not self.do_authenticate(True):
                return

            try:
                self.ipcon.enumerate()
            except:
                self.update_ui_state()
        else:
            try:
                self.ipcon.enumerate()
            except:
                self.update_ui_state()

    def cb_disconnected(self, disconnect_reason):
        self.hide_status('mainwindow_hotplug')
        if disconnect_reason == IPConnection.DISCONNECT_REASON_REQUEST:
            self.auto_reconnects = 0
            self.label_auto_reconnects.hide()

            self.red_session_losts = 0
            self.label_red_session_losts.hide()

        if disconnect_reason == IPConnection.DISCONNECT_REASON_REQUEST or not self.ipcon.get_auto_reconnect(
        ):
            self.update_ui_state()
        elif len(self.disconnect_times
                 ) >= 3 and self.disconnect_times[-3] < time.time() + 1:
            self.disconnect_times = []
            self.ipcon.set_auto_reconnect(False)
            self.update_ui_state()
            self.reset_view()

            QMessageBox.critical(
                self, 'Connection',
                'Stopped automatic reconnecting due to multiple connection errors in a row.'
            )
        else:
            self.disconnect_times.append(time.time())
            self.update_ui_state(IPConnection.CONNECTION_STATE_PENDING)

    def set_tree_view_defaults(self):
        self.tree_view_model.setHorizontalHeaderLabels(
            self.tree_view_model_labels)
        self.tree_view.expandAll()
        self.tree_view.setColumnWidth(0, 280)
        self.tree_view.setColumnWidth(1, 70)
        self.tree_view.setColumnWidth(2, 90)
        self.tree_view.setColumnWidth(3, 105)
        self.tree_view.setColumnWidth(4, 105)
        self.tree_view.setExpandsOnDoubleClick(False)
        self.tree_view.setSortingEnabled(True)
        self.tree_view.header().setSortIndicator(2, Qt.AscendingOrder)

    def update_ui_state(self, connection_state=None):
        # FIXME: need to call processEvents() otherwise get_connection_state()
        #        might return the wrong value
        QApplication.processEvents()

        if connection_state is None:
            connection_state = self.ipcon.get_connection_state()

        self.button_flashing.setDisabled(False)

        if connection_state == IPConnection.CONNECTION_STATE_DISCONNECTED:
            self.button_connect.setText('Connect')
            self.combo_host.setDisabled(False)
            self.spinbox_port.setDisabled(False)
            self.checkbox_authentication.setDisabled(False)
            self.edit_secret.setDisabled(False)
            self.button_advanced.setDisabled(True)
        elif connection_state == IPConnection.CONNECTION_STATE_CONNECTED:
            self.button_connect.setText("Disconnect")
            self.combo_host.setDisabled(True)
            self.spinbox_port.setDisabled(True)
            self.checkbox_authentication.setDisabled(True)
            self.edit_secret.setDisabled(True)
            self.update_advanced_window()

            # restart all pause plugins
            for info in inventory.get_device_infos():
                info.plugin.resume_plugin()
        elif connection_state == IPConnection.CONNECTION_STATE_PENDING:
            self.button_connect.setText('Abort Pending Automatic Reconnect')
            self.combo_host.setDisabled(True)
            self.spinbox_port.setDisabled(True)
            self.checkbox_authentication.setDisabled(True)
            self.edit_secret.setDisabled(True)
            self.button_advanced.setDisabled(True)
            self.button_flashing.setDisabled(True)

            # pause all running plugins
            for info in inventory.get_device_infos():
                info.plugin.pause_plugin()

        enable = connection_state == IPConnection.CONNECTION_STATE_CONNECTED

        for i in range(1, self.tab_widget.count()):
            self.tab_widget.setTabEnabled(i, enable)

        for device_info in inventory.get_device_infos():
            device_info.tab_window.setEnabled(enable)

        QApplication.processEvents()

    def update_tree_view(self):
        self.delayed_update_tree_view_timer.stop()

        self.tree_view_model.setHorizontalHeaderLabels(
            self.tree_view_model_labels)
        self.tab_widget.tabBar().setTabButton(0, QTabBar.RightSide, None)

        sis = self.tree_view.header().sortIndicatorSection()
        sio = self.tree_view.header().sortIndicatorOrder()

        self.tree_view_model.clear()

        def get_row(info):
            replacement = '0.0.0'
            is_red_brick = isinstance(info, BrickREDInfo)

            if is_red_brick or info.url_part == 'wifi_v2':
                replacement = "Querying..."
            elif info.kind == "extension":
                replacement = ""

            fw_version = get_version_string(info.firmware_version_installed,
                                            replace_unknown=replacement,
                                            is_red_brick=is_red_brick)

            uid = info.uid if info.kind != "extension" else ''

            row = [
                QStandardItem(info.name),
                QStandardItem(uid),
                QStandardItem(info.position.title()),
                QStandardItem(fw_version)
            ]

            updateable = info.firmware_version_installed != (
                0, 0, 0
            ) and info.firmware_version_installed < info.firmware_version_latest

            if is_red_brick:
                old_updateable = updateable

                for binding in info.bindings_infos:
                    updateable |= binding.firmware_version_installed != (0, 0, 0) \
                                  and binding.firmware_version_installed < binding.firmware_version_latest

                updateable |= info.brickv_info.firmware_version_installed != (0, 0, 0) \
                              and info.brickv_info.firmware_version_installed < info.brickv_info.firmware_version_latest \
                              and not info.firmware_version_installed < (1, 14, 0) # Hide Brickv update if image is too old.

                # There are bindings/brickv updates but there is no image update
                red_brick_binding_update_only = not old_updateable and updateable
            else:
                red_brick_binding_update_only = False

            if updateable:
                self.tree_view_model.setHorizontalHeaderLabels(
                    self.tree_view_model_labels + ['Update'])
                row.append(
                    QStandardItem(
                        get_version_string(info.firmware_version_latest,
                                           is_red_brick=is_red_brick) +
                        ("+" if red_brick_binding_update_only else "")))

                self.tab_widget.tabBar().setTabButton(0, QTabBar.RightSide,
                                                      self.update_tab_button)
                self.update_tab_button.show()

            for item in row:
                item.setFlags(item.flags() & ~Qt.ItemIsEditable)
                if updateable:
                    item.setData(QBrush(QColor(255, 160, 55)),
                                 Qt.BackgroundRole)

            return row

        def recurse_on_device(info, insertion_point):
            row = get_row(info)
            insertion_point.appendRow(row)

            for child in info.connections_values():
                recurse_on_device(child, row[0])

            if info.can_have_extension:
                for extension in info.extensions.values():
                    if extension is None:
                        continue
                    ext_row = get_row(extension)
                    row[0].appendRow(ext_row)

        for info in inventory.get_device_infos():
            # If a device has a reverse connection, it will be handled as a child of another top-level brick.
            if info.reverse_connection is not None:
                continue

            recurse_on_device(info, self.tree_view_model)

        self.set_tree_view_defaults()
        self.tree_view.header().setSortIndicator(sis, sio)
        self.update_advanced_window()
        self.delayed_refresh_updates_timer.start()

    def update_advanced_window(self):
        self.button_advanced.setEnabled(len(inventory.get_brick_infos()) > 0)

    def delayed_refresh_updates(self):
        self.delayed_refresh_updates_timer.stop()

        if self.flashing_window is not None and self.flashing_window.isVisible(
        ):
            self.flashing_window.refresh_update_tree_view()

    def show_status(self, message, icon='warning', message_id=''):
        self.setStatusBar(None)

        if icon != 'none':
            icon_dict = {
                'warning': 'warning-icon-16.png',
            }

            icon_label = QLabel()
            icon_label.setPixmap(load_pixmap(icon_dict[icon]))

            self.statusBar().addWidget(icon_label)

        message_label = QLabel(message)
        message_label.setOpenExternalLinks(True)

        self.statusBar().addWidget(message_label, 1)

        self.last_status_message_id = message_id

    def hide_status(self, message_id):
        if self.last_status_message_id == message_id:
            self.setStatusBar(None)

    def fw_versions_fetched(self, firmware_info):
        if isinstance(firmware_info, int):
            if firmware_info > 0:
                if firmware_info == 1:
                    message = 'Update information could not be downloaded from tinkerforge.com.<br/>' + \
                              'Is your computer connected to the Internet?'
                else:
                    message = (
                        "Update information on tinkerforge.com is malformed " +
                        "(error code {0}).<br/>Please report this error to " +
                        "<a href='mailto:[email protected]'>[email protected]</a>."
                    ).format(firmware_info)

                self.show_status(message,
                                 message_id='fw_versions_fetched_error')

            inventory.reset_latest_fws()
        else:
            self.hide_status('fw_versions_fetched_error')
            inventory.update_latest_fws(firmware_info)
Example #25
0
class MainWindow(QMainWindow, Ui_MainWindow):
    qtcb_enumerate = pyqtSignal(str, str, str, type((0,)), type((0,)), int, int)
    qtcb_connected = pyqtSignal(int)
    qtcb_disconnected = pyqtSignal(int)

    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)

        self.setupUi(self)

        # Setting the minimum width of the setup tab ensures, that other tabs can grow
        # the window if more space is required, but we have a sane default for status
        # messages. Setting the minimum width of the main window itself would enfoce
        # it, even if children (i.e. tabs) need more space.
        self.tab_setup.setMinimumWidth(550)

        signal.signal(signal.SIGINT, self.exit_brickv)
        signal.signal(signal.SIGTERM, self.exit_brickv)

        self.async_thread = async_start_thread(self)

        title = 'Brick Viewer ' + config.BRICKV_VERSION

        if config.INTERNAL != None:
            title += '~{}'.format(config.INTERNAL)

        self.setWindowTitle(title)

        self.tree_view_model_labels = ['Name', 'UID', 'Position', 'FW Version']
        self.tree_view_model = QStandardItemModel(self)
        self.tree_view_proxy_model = DevicesProxyModel(self)
        self.tree_view_proxy_model.setSourceModel(self.tree_view_model)
        self.tree_view.setModel(self.tree_view_proxy_model)
        self.tree_view.activated.connect(self.item_activated)
        self.set_tree_view_defaults()

        self.tab_widget.removeTab(1) # remove dummy tab
        self.tab_widget.setUsesScrollButtons(True) # force scroll buttons

        self.update_tab_button = IconButton(QIcon(load_pixmap('update-icon-normal.png')),
                                            QIcon(load_pixmap('update-icon-hover.png')),
                                            parent=self.tab_setup)
        self.update_tab_button.setToolTip('Updates available')
        self.update_tab_button.clicked.connect(self.flashing_clicked)
        self.update_tab_button.hide()

        self.name = '<unknown>'
        self.uid = '<unknown>'
        self.version = (0, 0, 0)

        self.disconnect_times = []

        self.qtcb_enumerate.connect(self.cb_enumerate)
        self.qtcb_connected.connect(self.cb_connected)
        self.qtcb_disconnected.connect(self.cb_disconnected)

        self.ipcon = IPConnection()
        self.ipcon.register_callback(IPConnection.CALLBACK_ENUMERATE,
                                     self.qtcb_enumerate.emit)
        self.ipcon.register_callback(IPConnection.CALLBACK_CONNECTED,
                                     self.qtcb_connected.emit)
        self.ipcon.register_callback(IPConnection.CALLBACK_DISCONNECTED,
                                     self.qtcb_disconnected.emit)

        self.current_device_info = None
        self.flashing_window = None
        self.advanced_window = None
        self.data_logger_window = None
        self.delayed_refresh_updates_timer = QTimer(self)
        self.delayed_refresh_updates_timer.timeout.connect(self.delayed_refresh_updates)
        self.delayed_refresh_updates_timer.setInterval(100)
        self.reset_view()
        self.button_advanced.setDisabled(True)

        self.fw_version_fetcher = LatestFWVersionFetcher()
        self.fw_version_fetcher.fw_versions_avail.connect(self.fw_versions_fetched)
        self.fw_version_fetcher_thread = QThread()
        self.fw_version_fetcher_thread.setObjectName("fw_version_fetcher_thread")

        if config.get_auto_search_for_updates():
            self.enable_auto_search_for_updates()
        else:
            self.disable_auto_search_for_updates()

        self.tab_widget.currentChanged.connect(self.tab_changed)
        self.tab_widget.setMovable(True)
        self.tab_widget.tabBar().installEventFilter(self)

        self.button_connect.clicked.connect(self.connect_clicked)
        self.button_flashing.clicked.connect(self.flashing_clicked)
        self.button_advanced.clicked.connect(self.advanced_clicked)
        self.button_data_logger.clicked.connect(self.data_logger_clicked)
        self.plugin_manager = PluginManager()

        # host info
        self.host_infos = config.get_host_infos(config.HOST_INFO_COUNT)
        self.host_index_changing = True

        for host_info in self.host_infos:
            self.combo_host.addItem(host_info.host)

        self.last_host = None
        self.combo_host.installEventFilter(self)
        self.combo_host.currentIndexChanged.connect(self.host_index_changed)

        self.spinbox_port.setValue(self.host_infos[0].port)
        self.spinbox_port.valueChanged.connect(self.port_changed)
        self.spinbox_port.installEventFilter(self)

        self.checkbox_authentication.stateChanged.connect(self.authentication_state_changed)

        self.label_secret.hide()
        self.edit_secret.hide()
        self.edit_secret.setEchoMode(QLineEdit.Password)
        self.edit_secret.textEdited.connect(self.secret_changed)
        self.edit_secret.installEventFilter(self)

        self.checkbox_secret_show.hide()
        self.checkbox_secret_show.stateChanged.connect(self.secret_show_state_changed)

        self.checkbox_remember_secret.hide()
        self.checkbox_remember_secret.stateChanged.connect(self.remember_secret_state_changed)

        self.checkbox_authentication.setChecked(self.host_infos[0].use_authentication)
        self.edit_secret.setText(self.host_infos[0].secret)
        self.checkbox_remember_secret.setChecked(self.host_infos[0].remember_secret)

        self.host_index_changing = False

        # auto-reconnect
        self.label_auto_reconnects.hide()
        self.auto_reconnects = 0

        # RED Session losts
        self.label_red_session_losts.hide()
        self.red_session_losts = 0

        # fusion style
        self.check_fusion_gui_style.setChecked(config.get_use_fusion_gui_style())
        self.check_fusion_gui_style.stateChanged.connect(self.gui_style_changed)

        self.checkbox_auto_search_for_updates.setChecked(config.get_auto_search_for_updates())
        self.checkbox_auto_search_for_updates.stateChanged.connect(self.auto_search_for_updates_changed)

        self.button_update_pixmap_normal = load_pixmap('update-icon-normal.png')
        self.button_update_pixmap_hover = load_pixmap('update-icon-hover.png')

        self.last_status_message_id = ''

        infos.get_infos_changed_signal().connect(self.update_red_brick_version)

    def update_red_brick_version(self, uid):
        if not isinstance(infos.get_info(uid), infos.BrickREDInfo):
            return
        self.update_tree_view()

    def disable_auto_search_for_updates(self):
        self.fw_version_fetcher.abort()

    def enable_auto_search_for_updates(self):
        self.fw_version_fetcher.reset()
        self.fw_version_fetcher.moveToThread(self.fw_version_fetcher_thread)
        self.fw_version_fetcher_thread.started.connect(self.fw_version_fetcher.run)
        self.fw_version_fetcher.finished.connect(self.fw_version_fetcher_thread.quit)
        self.fw_version_fetcher_thread.start()

    # override QMainWindow.closeEvent
    def closeEvent(self, event):
        if not self.exit_logger():
            event.ignore()
            return

        self.exit_brickv()
        event.accept()
        async_stop_thread()

        # Without this, the quit event seems to not reach the main loop under OSX.
        QApplication.quit()

    def exit_brickv(self, signl=None, frme=None):
        self.update_current_host_info()
        config.set_host_infos(self.host_infos)

        self.do_disconnect()

        if signl != None and frme != None:
            print("Received SIGINT or SIGTERM, shutting down.")
            sys.exit()

    def exit_logger(self):
        exitBrickv = True
        if (self.data_logger_window is not None) and \
           (self.data_logger_window.data_logger_thread is not None) and \
           (not self.data_logger_window.data_logger_thread.stopped):
            quit_msg = "The Data Logger is running. Are you sure you want to exit the program?"
            reply = QMessageBox.question(self, 'Message', quit_msg, QMessageBox.Yes, QMessageBox.No)

            if reply == QMessageBox.Yes:
                self.data_logger_window.data_logger_thread.stop()
            else:
                exitBrickv = False

        return exitBrickv

    def host_index_changed(self, i):
        if i < 0:
            return

        self.host_index_changing = True

        self.spinbox_port.setValue(self.host_infos[i].port)
        self.checkbox_authentication.setChecked(self.host_infos[i].use_authentication)
        self.edit_secret.setText(self.host_infos[i].secret)
        self.checkbox_remember_secret.setChecked(self.host_infos[i].remember_secret)

        self.host_index_changing = False

    def port_changed(self, _value):
        self.update_current_host_info()

    def authentication_state_changed(self, state):
        visible = state == Qt.Checked

        self.label_secret.setVisible(visible)
        self.edit_secret.setVisible(visible)
        self.checkbox_secret_show.setVisible(visible)
        self.checkbox_remember_secret.setVisible(visible)

        self.update_current_host_info()

    def secret_changed(self):
        self.update_current_host_info()

    def secret_show_state_changed(self, state):
        if state == Qt.Checked:
            self.edit_secret.setEchoMode(QLineEdit.Normal)
        else:
            self.edit_secret.setEchoMode(QLineEdit.Password)

        self.update_current_host_info()

    def remember_secret_state_changed(self, _state):
        self.update_current_host_info()

    def tab_changed(self, i):
        if not hasattr(self.tab_widget.widget(i), '_info'):
            new_current_device_info = None
        else:
            new_current_device_info = self.tab_widget.widget(i)._info
            new_current_device_info.plugin.start_plugin()

        # stop the now deselected plugin, if there is one that's running
        if self.current_device_info is not None:
            self.current_device_info.plugin.stop_plugin()

        self.current_device_info = new_current_device_info

    def update_current_host_info(self):
        if self.host_index_changing:
            return

        i = self.combo_host.currentIndex()

        if i < 0:
            return

        self.host_infos[i].port = self.spinbox_port.value()
        self.host_infos[i].use_authentication = self.checkbox_authentication.isChecked()
        self.host_infos[i].secret = self.edit_secret.text()
        self.host_infos[i].remember_secret = self.checkbox_remember_secret.isChecked()

    def gui_style_changed(self):
        config.set_use_fusion_gui_style(self.check_fusion_gui_style.isChecked())

        QMessageBox.information(self, 'GUI Style', 'GUI style change will be applied on next Brick Viewer start.', QMessageBox.Ok)

    def auto_search_for_updates_changed(self):
        config.set_auto_search_for_updates(self.checkbox_auto_search_for_updates.isChecked())
        if self.checkbox_auto_search_for_updates.isChecked():
            self.enable_auto_search_for_updates()
        else:
            self.disable_auto_search_for_updates()

    def remove_all_device_infos(self):
        for device_info in infos.get_device_infos():
            self.remove_device_info(device_info.uid)

    def remove_device_info(self, uid):
        tab_id = self.tab_for_uid(uid)
        device_info = infos.get_info(uid)

        device_info.plugin.stop_plugin()
        device_info.plugin.destroy_plugin()

        if tab_id >= 0:
            self.tab_widget.removeTab(tab_id)

        # ensure that the widget gets correctly destroyed. otherwise QWidgets
        # tend to leak as Python is not able to collect their PyQt object
        tab_window = device_info.tab_window
        device_info.tab_window = None

        # If we reboot the RED Brick, the tab_window sometimes is
        # already None here
        if tab_window != None:
            tab_window.hide()
            tab_window.setParent(None)

        plugin = device_info.plugin
        device_info.plugin = None

        if plugin != None:
            plugin.hide()
            plugin.setParent(None)

        infos.remove_info(uid)

    def reset_view(self):
        self.tab_widget.setCurrentIndex(0)
        self.remove_all_device_infos()
        self.update_tree_view()

    def do_disconnect(self):
        self.auto_reconnects = 0
        self.label_auto_reconnects.hide()

        self.red_session_losts = 0
        self.label_red_session_losts.hide()

        self.reset_view()
        async_next_session()

        # force garbage collection, to ensure that all plugin related objects
        # got destroyed before disconnect is called. this is especially
        # important for the RED Brick plugin because its relies on releasing
        # the the RED Brick API objects in the __del__ method as a last resort
        # to avoid leaking object references. but this only works if garbage
        # collection is done before disconnect is called
        gc.collect()

        try:
            self.ipcon.disconnect()
        except:
            pass

    def do_authenticate(self, is_auto_reconnect):
        if not self.checkbox_authentication.isChecked():
            return True

        try:
            secret = self.edit_secret.text()
            # Try to encode the secret, as only ASCII chars are allowed. Don't save the result, as the IP Connection does the same.
            secret.encode('ascii')
        except:
            self.do_disconnect()

            QMessageBox.critical(self, 'Connection',
                                 'Authentication secret cannot contain non-ASCII characters.',
                                 QMessageBox.Ok)
            return False

        self.ipcon.set_auto_reconnect(False) # don't auto-reconnect on authentication error

        try:
            self.ipcon.authenticate(secret)
        except:
            self.do_disconnect()

            if is_auto_reconnect:
                extra = ' after auto-reconnect'
            else:
                extra = ''

            QMessageBox.critical(self, 'Connection',
                                 'Could not authenticate' + extra + '. Check secret and ensure ' +
                                 'authentication for Brick Daemon is enabled.',
                                 QMessageBox.Ok)
            return False

        self.ipcon.set_auto_reconnect(True)

        return True

    def flashing_clicked(self):
        if self.flashing_window is None:
            self.flashing_window = FlashingWindow(self)

        self.flashing_window.show()
        self.flashing_window.tab_widget.setCurrentWidget(self.flashing_window.tab_updates)
        self.flashing_window.update_version_info()

    def advanced_clicked(self):
        if self.advanced_window is None:
            self.advanced_window = AdvancedWindow(self)

        self.advanced_window.show()

    def data_logger_clicked(self):
        if self.data_logger_window is None:
            self.data_logger_window = DataLoggerWindow(self, self.host_infos)

        self.data_logger_window.show()

    def connect_error(self):
        self.setDisabled(False)
        self.button_connect.setText("Connect")
        QMessageBox.critical(self, 'Connection',
                             'Could not connect. Please check host, check ' +
                             'port and ensure that Brick Daemon is running.')

    def connect_clicked(self):
        if self.ipcon.get_connection_state() == IPConnection.CONNECTION_STATE_DISCONNECTED:
            self.last_host = self.combo_host.currentText()
            self.setDisabled(True)
            self.button_connect.setText("Connecting...")

            async_call(self.ipcon.connect, (self.last_host, self.spinbox_port.value()), None, self.connect_error)
        else:
            self.do_disconnect()

    def item_activated(self, index):
        index = self.tree_view_proxy_model.mapToSource(index)
        position_index = index.sibling(index.row(), 2)

        if position_index.isValid() and position_index.data().startswith('Ext'):
            index = index.parent()

        uid_index = index.sibling(index.row(), 1)

        if uid_index.isValid():
            self.show_plugin(uid_index.data())

    def show_brick_update(self, url_part):
        if self.flashing_window is None:
            self.flashing_window = FlashingWindow(self)

        self.flashing_window.show()
        self.flashing_window.update_version_info()
        self.flashing_window.show_brick_update(url_part)

    def show_bricklet_update(self, parent_uid, port):
        if self.flashing_window is None:
            self.flashing_window = FlashingWindow(self)

        self.flashing_window.show()
        self.flashing_window.update_version_info()
        self.flashing_window.show_bricklet_update(parent_uid, port)

    def show_extension_update(self, master_uid):
        if self.flashing_window is None:
            self.flashing_window = FlashingWindow(self)

        self.flashing_window.show()
        self.flashing_window.update_version_info()
        self.flashing_window.show_extension_update(master_uid)

    def show_red_brick_update(self):
        text = "To update the RED Brick Image, please follow the instructions " + \
               "<a href=https://www.tinkerforge.com/en/doc/Hardware/Bricks/RED_Brick.html#red-brick-copy-image>here</a>."
        QMessageBox.information(self, "RED Brick Update", text)

    def create_tab_window(self, device_info, ipcon):
        tab_window = TabWindow(self.tab_widget, device_info.name, self.untab)
        tab_window._info = device_info
        tab_window.add_callback_on_tab(lambda index:
                                       self.ipcon.get_connection_state() == IPConnection.CONNECTION_STATE_PENDING and \
                                       self.tab_widget.setTabEnabled(index, False),
                                       'main_window_disable_tab_if_connection_pending')

        layout = QVBoxLayout(tab_window)
        info_bars = [QHBoxLayout(), QHBoxLayout()]

        # uid
        info_bars[0].addWidget(QLabel('UID:'))

        label = QLabel('{0}'.format(device_info.uid))
        label.setTextInteractionFlags(Qt.TextSelectableByMouse |
                                      Qt.TextSelectableByKeyboard)

        info_bars[0].addWidget(label)
        info_bars[0].addSpacerItem(QSpacerItem(20, 1, QSizePolicy.Preferred))

        # firmware version
        label_version_name = QLabel('Version:')
        label_version = QLabel('Querying...')

        button_update = QPushButton(QIcon(self.button_update_pixmap_normal), 'Update')
        button_update.installEventFilter(self)

        if isinstance(device_info, infos.BrickREDInfo):
            button_update.clicked.connect(self.show_red_brick_update)
        elif device_info.type == 'brick':
            button_update.clicked.connect(lambda: self.show_brick_update(device_info.url_part))
        elif device_info.type == 'bricklet':
            button_update.clicked.connect(lambda: self.show_bricklet_update(device_info.connected_uid, device_info.position))

        if not device_info.plugin.has_custom_version(label_version_name, label_version):
            label_version_name.setText('FW Version:')
            label_version.setText(infos.get_version_string(device_info.plugin.firmware_version))

        info_bars[0].addWidget(label_version_name)
        info_bars[0].addWidget(label_version)
        info_bars[0].addWidget(button_update)
        button_update.hide()
        tab_window.button_update = button_update
        info_bars[0].addSpacerItem(QSpacerItem(20, 1, QSizePolicy.Preferred))

        # timeouts
        info_bars[0].addWidget(QLabel('Timeouts:'))

        label_timeouts = QLabel('0')

        info_bars[0].addWidget(label_timeouts)
        info_bars[0].addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding))

        # connected uid
        if device_info.connected_uid != '0':
            info_bars[1].addWidget(QLabel('Connected to:'))

            button = QToolButton()
            button.setText(device_info.connected_uid)
            button.clicked.connect(lambda: self.show_plugin(device_info.connected_uid))
            device_info.plugin.button_parent = button

            info_bars[1].addWidget(button)
            info_bars[1].addSpacerItem(QSpacerItem(20, 1, QSizePolicy.Preferred))

        # position
        info_bars[1].addWidget(QLabel('Position:'))
        label_position = QLabel('{0}'.format(device_info.position.title()))
        device_info.plugin.label_position = label_position
        info_bars[1].addWidget(label_position)
        info_bars[1].addSpacerItem(QSpacerItem(1, 1, QSizePolicy.Expanding))

        # configs
        configs = device_info.plugin.get_configs()

        def config_changed(combobox):
            i = combobox.currentIndex()

            if i < 0:
                return

            combobox.itemData(i).trigger()

        if len(configs) > 0:
            for cfg in configs:
                if cfg[1] != None:
                    combobox = QComboBox()

                    for i, item in enumerate(cfg[2]):
                        combobox.addItem(item.text(), item)
                        item.triggered.connect(functools.partial(combobox.setCurrentIndex, i))

                    combobox.currentIndexChanged.connect(functools.partial(config_changed, combobox))

                    info_bars[cfg[0]].addWidget(QLabel(cfg[1]))
                    info_bars[cfg[0]].addWidget(combobox)
                elif len(cfg[2]) > 0:
                    checkbox = QCheckBox(cfg[2][0].text())
                    cfg[2][0].toggled.connect(checkbox.setChecked)
                    checkbox.toggled.connect(cfg[2][0].setChecked)

                    info_bars[cfg[0]].addWidget(checkbox)

        # actions
        actions = device_info.plugin.get_actions()

        if len(actions) > 0:
            for action in actions:
                if action[1] != None:
                    button = QPushButton(action[1])
                    menu = QMenu()

                    for item in action[2]:
                        menu.addAction(item)

                    button.setMenu(menu)
                elif len(action[2]) > 0:
                    button = QPushButton(action[2][0].text())
                    button.clicked.connect(action[2][0].trigger)

                info_bars[action[0]].addWidget(button)

        def more_clicked(button, info_bar):
            visible = button.text().replace('&', '') == 'More' # remove &s, they mark the buttons hotkey

            if visible:
                button.setText('Less')
            else:
                button.setText('More')

            for i in range(info_bar.count()):
                widget = info_bar.itemAt(i).widget()

                if widget != None:
                    widget.setVisible(visible)

        more_button = QPushButton('More')
        more_button.clicked.connect(lambda: more_clicked(more_button, info_bars[1]))

        info_bars[0].addWidget(more_button)

        for i in range(info_bars[1].count()):
            widget = info_bars[1].itemAt(i).widget()

            if widget != None:
                widget.hide()

        layout.addLayout(info_bars[0])
        layout.addLayout(info_bars[1])

        line = QFrame()
        line.setObjectName("MainWindow_line")
        line.setFrameShape(QFrame.HLine)
        line.setFrameShadow(QFrame.Sunken)

        device_info.plugin.label_timeouts = label_timeouts
        device_info.plugin.label_version = label_version
        device_info.plugin.layout().setContentsMargins(0, 0, 0, 0)

        layout.addWidget(line)
        if device_info.plugin.has_comcu:
            device_info.plugin.widget_bootloader = COMCUBootloader(ipcon, device_info)
            device_info.plugin.widget_bootloader.hide()
            layout.addWidget(device_info.plugin.widget_bootloader)
        layout.addWidget(device_info.plugin, 1)

        return tab_window

    def tab_move(self, event):
        # visualize rearranging of tabs (if allowed by tab_widget)
        if self.tab_widget.isMovable():
            if event.type() == QEvent.MouseButtonPress and event.button() & Qt.LeftButton:
                QApplication.setOverrideCursor(QCursor(Qt.SizeHorCursor))

            elif event.type() == QEvent.MouseButtonRelease and event.button() & Qt.LeftButton:
                QApplication.restoreOverrideCursor()

        return False

    def untab(self, tab_index):
        tab = self.tab_widget.widget(tab_index)
        tab.untab()
        tab._info.plugin.start_plugin()
        self.tab_widget.setCurrentIndex(0)

    def connect_on_return(self, event):
        if event.type() == QEvent.KeyPress and (event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter):
            self.connect_clicked()
            return True
        return False

    def eventFilter(self, source, event):
        if source is self.tab_widget.tabBar():
            return self.tab_move(event)

        if source is self.combo_host or source is self.spinbox_port or source is self.edit_secret:
            return self.connect_on_return(event)

        if isinstance(source, QPushButton) and event.type() == QEvent.Enter:
            source.setIcon(QIcon(self.button_update_pixmap_hover))
        elif isinstance(source, QPushButton) and  event.type() == QEvent.Leave:
            source.setIcon(QIcon(self.button_update_pixmap_normal))

        return False

    def tab_for_uid(self, uid):
        for index in range(1, self.tab_widget.count()):
            try:
                if self.tab_widget.widget(index)._info.uid == uid:
                    return index
            except:
                pass

        return -1

    def show_plugin(self, uid):
        device_info = infos.get_info(uid)

        if device_info == None:
            return

        index = self.tab_for_uid(uid)
        tab_window = device_info.tab_window

        if index > 0 and self.tab_widget.isTabEnabled(index):
            self.tab_widget.setCurrentIndex(index)

        QApplication.setActiveWindow(tab_window)

        tab_window.show()
        tab_window.activateWindow()
        tab_window.raise_()

    def cb_enumerate(self, uid, connected_uid, position,
                     hardware_version, firmware_version,
                     device_identifier, enumeration_type):
        if self.ipcon.get_connection_state() != IPConnection.CONNECTION_STATE_CONNECTED:
            # ignore enumerate callbacks that arrived after the connection got closed
            return

        if enumeration_type in [IPConnection.ENUMERATION_TYPE_AVAILABLE,
                                IPConnection.ENUMERATION_TYPE_CONNECTED]:
            device_info = infos.get_info(uid)
            something_changed_ref = [False]

            # If the enum_type is CONNECTED, the bricklet was restarted externally.
            # The plugin could now be in an inconsistent state.
            if enumeration_type == IPConnection.ENUMERATION_TYPE_CONNECTED and device_info is not None:
                if device_info.connected_uid != connected_uid:
                    # Fix connections if bricklet was connected to another brick.
                    parent_info = infos.get_info(device_info.connected_uid)
                    if parent_info is not None:
                        parent_info.connections.remove((device_info.position, device_info))
                        self.show_status("Hot plugging is not supported! Please reset the brick {} and restart brick viewer.".format(device_info.connected_uid))
                    device_info.reverse_connection = connected_uid
                elif device_info.position != position:
                    # Bricklet was connected to the same brick, but to another port
                    self.show_status("Hot plugging is not supported! Please reset the brick {} and restart brick viewer.".format(device_info.connected_uid))

                # If the plugin is not running, pause will do nothing, so it is always save to call it.
                # The plugin will be (unconditionally) resumed later, as resume also only does something
                # if it was paused before (e.g. here).
                if device_info.plugin is not None:
                    device_info.plugin.pause_plugin()

            if device_info == None:
                if device_identifier == BrickMaster.DEVICE_IDENTIFIER:
                    device_info = infos.BrickMasterInfo()
                elif device_identifier == BrickRED.DEVICE_IDENTIFIER:
                    device_info = infos.BrickREDInfo()
                elif hat_brick_supported and device_identifier == BrickHAT.DEVICE_IDENTIFIER:
                    device_info = infos.BrickHATInfo()
                elif hat_zero_brick_supported and device_identifier == BrickHATZero.DEVICE_IDENTIFIER:
                    device_info = infos.BrickHATZeroInfo()
                elif device_identifier == BrickletIsolator.DEVICE_IDENTIFIER:
                    device_info = infos.BrickletIsolatorInfo()
                elif '0' <= position <= '9':
                    device_info = infos.BrickInfo()
                    something_changed_ref[0] = True
                else:
                    device_info = infos.BrickletInfo()

            position = position.lower()

            def set_device_info_value(name, value):
                if getattr(device_info, name) != value:
                    setattr(device_info, name, value)
                    something_changed_ref[0] = True
                    infos.get_infos_changed_signal().emit(device_info.uid)

            set_device_info_value('uid', uid)
            set_device_info_value('connected_uid', connected_uid)
            set_device_info_value('position', position)
            set_device_info_value('hardware_version', hardware_version)
            if device_identifier != BrickRED.DEVICE_IDENTIFIER:
                set_device_info_value('firmware_version_installed', firmware_version)
            set_device_info_value('device_identifier', device_identifier)
            set_device_info_value('enumeration_type', enumeration_type)

            # Update connections and reverse_connection with new device
            for info in infos.get_device_infos():
                if info == device_info:
                    continue

                def add_to_connections(info_to_add, connected_info):
                    connected_info.connections.append((info_to_add.position, info_to_add))
                    info_to_add.reverse_connection = connected_info
                    something_changed_ref[0] = True
                    infos.get_infos_changed_signal().emit(connected_info.uid)

                if info.uid != '' and info.uid == device_info.connected_uid:
                    if device_info in info.connections_values(): #Device was already connected, but to another port
                        info.connections = [(pos, i) for pos, i in info.connections if i.uid != device_info.uid]
                    if device_info not in info.connections_get(device_info.position):
                        add_to_connections(device_info, info)


                if info.connected_uid != '' and info.connected_uid == device_info.uid:
                    if info in device_info.connections_values(): #Device was already connected, but to another port
                        device_info.connections = [(pos, i) for pos, i in device_info.connections if i.uid != info.uid]
                    if info not in device_info.connections_get(info.position):
                        add_to_connections(info, device_info)

            if device_info.plugin == None:
                plugin = self.plugin_manager.create_plugin_instance(device_identifier, self.ipcon, device_info)

                device_info.tab_window = self.create_tab_window(device_info, self.ipcon)
                device_info.tab_window.setWindowFlags(Qt.Widget)
                device_info.tab_window.tab()

                infos.add_info(device_info)

                something_changed_ref[0] = True

                if device_identifier == BrickRED.DEVICE_IDENTIFIER and isinstance(plugin, RED):
                    plugin.get_image_version_async()
                    plugin.get_bindings_versions_async()

            # The plugin was paused before if it was reconnected.
            device_info.plugin.resume_plugin()

            if something_changed_ref[0]:
                self.update_tree_view()
        elif enumeration_type == IPConnection.ENUMERATION_TYPE_DISCONNECTED:
            self.remove_device_tab(uid)

    def remove_device_tab(self, uid):
        for device_info in infos.get_device_infos():
            if device_info.uid == uid:
                self.tab_widget.setCurrentIndex(0)
                self.remove_device_info(device_info.uid)

            if isinstance(device_info, infos.DeviceInfo):
                to_delete = []
                for idx, tup in enumerate(device_info.connections):
                    port, info = tup
                    if info.uid == uid:
                        to_delete.append(idx)
                for idx in to_delete:
                    del device_info.connections[idx]

        self.update_tree_view()

    def hack_to_remove_red_brick_tab(self, red_brick_uid):
        for device_info in infos.get_device_infos():
            if device_info.uid == red_brick_uid:
                self.tab_widget.setCurrentIndex(0)
                self.remove_device_info(device_info.uid)

                self.red_session_losts += 1
                self.label_red_session_losts.setText('RED Brick Session Loss Count: {0}'.format(self.red_session_losts))
                self.label_red_session_losts.show()

                break

        self.update_tree_view()

    def cb_connected(self, connect_reason):
        self.disconnect_times = []

        self.update_ui_state()

        if connect_reason == IPConnection.CONNECT_REASON_REQUEST:
            self.setDisabled(False)

            self.auto_reconnects = 0
            self.label_auto_reconnects.hide()

            self.red_session_losts = 0
            self.label_red_session_losts.hide()

            self.ipcon.set_auto_reconnect(True)

            index = self.combo_host.findText(self.last_host)

            if index >= 0:
                self.combo_host.removeItem(index)

                host_info = self.host_infos[index]

                del self.host_infos[index]
                self.host_infos.insert(0, host_info)
            else:
                index = self.combo_host.currentIndex()

                host_info = self.host_infos[index].duplicate()
                host_info.host = self.last_host

                self.host_infos.insert(0, host_info)

            self.combo_host.insertItem(-1, self.last_host)
            self.combo_host.setCurrentIndex(0)

            while self.combo_host.count() > config.HOST_INFO_COUNT:
                self.combo_host.removeItem(self.combo_host.count() - 1)

            if not self.do_authenticate(False):
                return

            try:
                self.ipcon.enumerate()
            except:
                self.update_ui_state()
        elif connect_reason == IPConnection.CONNECT_REASON_AUTO_RECONNECT:
            self.auto_reconnects += 1
            self.label_auto_reconnects.setText('Auto-Reconnect Count: {0}'.format(self.auto_reconnects))
            self.label_auto_reconnects.show()

            if not self.do_authenticate(True):
                return

            try:
                self.ipcon.enumerate()
            except:
                self.update_ui_state()
        else:
            try:
                self.ipcon.enumerate()
            except:
                self.update_ui_state()

    def cb_disconnected(self, disconnect_reason):
        if disconnect_reason == IPConnection.DISCONNECT_REASON_REQUEST:
            self.auto_reconnects = 0
            self.label_auto_reconnects.hide()

            self.red_session_losts = 0
            self.label_red_session_losts.hide()

        if disconnect_reason == IPConnection.DISCONNECT_REASON_REQUEST or not self.ipcon.get_auto_reconnect():
            self.update_ui_state()
        elif len(self.disconnect_times) >= 3 and self.disconnect_times[-3] < time.time() + 1:
            self.disconnect_times = []
            self.ipcon.set_auto_reconnect(False)
            self.update_ui_state()
            self.reset_view()

            QMessageBox.critical(self, 'Connection',
                                 'Stopped automatic reconnecting due to multiple connection errors in a row.')
        else:
            self.disconnect_times.append(time.time())
            self.update_ui_state(IPConnection.CONNECTION_STATE_PENDING)

    def set_tree_view_defaults(self):
        self.tree_view_model.setHorizontalHeaderLabels(self.tree_view_model_labels)
        self.tree_view.expandAll()
        self.tree_view.setColumnWidth(0, 280)
        self.tree_view.setColumnWidth(1, 70)
        self.tree_view.setColumnWidth(2, 90)
        self.tree_view.setColumnWidth(3, 105)
        self.tree_view.setColumnWidth(4, 105)
        self.tree_view.setExpandsOnDoubleClick(False)
        self.tree_view.setSortingEnabled(True)
        self.tree_view.header().setSortIndicator(2, Qt.AscendingOrder)

    def update_ui_state(self, connection_state=None):
        # FIXME: need to call processEvents() otherwise get_connection_state()
        #        might return the wrong value
        QApplication.processEvents()

        if connection_state is None:
            connection_state = self.ipcon.get_connection_state()

        self.button_flashing.setDisabled(False)

        if connection_state == IPConnection.CONNECTION_STATE_DISCONNECTED:
            self.button_connect.setText('Connect')
            self.combo_host.setDisabled(False)
            self.spinbox_port.setDisabled(False)
            self.checkbox_authentication.setDisabled(False)
            self.edit_secret.setDisabled(False)
            self.button_advanced.setDisabled(True)
        elif connection_state == IPConnection.CONNECTION_STATE_CONNECTED:
            self.button_connect.setText("Disconnect")
            self.combo_host.setDisabled(True)
            self.spinbox_port.setDisabled(True)
            self.checkbox_authentication.setDisabled(True)
            self.edit_secret.setDisabled(True)
            self.update_advanced_window()

            # restart all pause plugins
            for info in infos.get_device_infos():
                info.plugin.resume_plugin()
        elif connection_state == IPConnection.CONNECTION_STATE_PENDING:
            self.button_connect.setText('Abort Pending Automatic Reconnect')
            self.combo_host.setDisabled(True)
            self.spinbox_port.setDisabled(True)
            self.checkbox_authentication.setDisabled(True)
            self.edit_secret.setDisabled(True)
            self.button_advanced.setDisabled(True)
            self.button_flashing.setDisabled(True)

            # pause all running plugins
            for info in infos.get_device_infos():
                info.plugin.pause_plugin()

        enable = connection_state == IPConnection.CONNECTION_STATE_CONNECTED

        for i in range(1, self.tab_widget.count()):
            self.tab_widget.setTabEnabled(i, enable)

        for device_info in infos.get_device_infos():
            device_info.tab_window.setEnabled(enable)

        QApplication.processEvents()


    def update_tree_view(self):
        self.tree_view_model.setHorizontalHeaderLabels(self.tree_view_model_labels)
        self.tab_widget.tabBar().setTabButton(0, QTabBar.RightSide, None)

        sis = self.tree_view.header().sortIndicatorSection()
        sio = self.tree_view.header().sortIndicatorOrder()

        self.tree_view_model.clear()

        def get_row(info):
            replacement = '0.0.0'
            is_red_brick = isinstance(info, infos.BrickREDInfo)

            if is_red_brick or info.url_part == 'wifi_v2':
                replacement = "Querying..."
            elif info.type == "extension":
                replacement = ""

            fw_version = infos.get_version_string(info.firmware_version_installed,
                                                  replace_unknown=replacement,
                                                  is_red_brick=is_red_brick)

            uid = info.uid if info.type != "extension" else ''

            row = [QStandardItem(info.name),
                   QStandardItem(uid),
                   QStandardItem(info.position.title()),
                   QStandardItem(fw_version)]

            updateable = info.firmware_version_installed != (0, 0, 0) and info.firmware_version_installed < info.firmware_version_latest

            if is_red_brick:
                old_updateable = updateable
                for binding in info.bindings_infos:
                    updateable |= binding.firmware_version_installed != (0, 0, 0) \
                                  and binding.firmware_version_installed < binding.firmware_version_latest
                updateable |= info.brickv_info.firmware_version_installed != (0, 0, 0) \
                              and info.brickv_info.firmware_version_installed < info.brickv_info.firmware_version_latest \
                              and not info.firmware_version_installed < (1, 14, 0) # Hide Brickv update if image is too old.
                # There are bindings/brickv updates but there is no image update
                red_brick_binding_update_only = not old_updateable and updateable
            else:
                red_brick_binding_update_only = False

            if updateable:
                self.tree_view_model.setHorizontalHeaderLabels(self.tree_view_model_labels + ['Update'])
                row.append(QStandardItem(
                    infos.get_version_string(info.firmware_version_latest, is_red_brick=is_red_brick) + ("+" if red_brick_binding_update_only else "")))

                self.tab_widget.tabBar().setTabButton(0, QTabBar.RightSide, self.update_tab_button)
                self.update_tab_button.show()

            for item in row:
                item.setFlags(item.flags() & ~Qt.ItemIsEditable)
                if updateable:
                    item.setData(QBrush(QColor(255, 160, 55)), Qt.BackgroundRole)

            return row

        def recurse_on_device(info, insertion_point):
            row = get_row(info)
            insertion_point.appendRow(row)

            for child in info.connections_values():
                recurse_on_device(child, row[0])

            if info.can_have_extension:
                for extension in info.extensions.values():
                    if extension is None:
                        continue
                    ext_row = get_row(extension)
                    row[0].appendRow(ext_row)

        for info in infos.get_device_infos():
            # If a device has a reverse connection, it will be handled as a child of another top-level brick.
            if info.reverse_connection is not None:
                continue
            recurse_on_device(info, self.tree_view_model)

        self.set_tree_view_defaults()
        self.tree_view.header().setSortIndicator(sis, sio)
        self.update_advanced_window()
        self.delayed_refresh_updates_timer.start()

    def update_advanced_window(self):
        self.button_advanced.setEnabled(len(infos.get_brick_infos()) > 0)

    def delayed_refresh_updates(self):
        self.delayed_refresh_updates_timer.stop()

        if self.flashing_window is not None and self.flashing_window.isVisible():
            self.flashing_window.refresh_update_tree_view()

    def show_status(self, message, icon='warning', message_id=''):
        self.setStatusBar(None)

        if icon != 'none':
            icon_dict = {
                'warning': 'warning-icon-16.png',
            }

            icon_label = QLabel()
            icon_label.setPixmap(load_pixmap(icon_dict[icon]))

            self.statusBar().addWidget(icon_label)

        message_label = QLabel(message)
        message_label.setOpenExternalLinks(True)

        self.statusBar().addWidget(message_label, 1)

        self.last_status_message_id = message_id

    def hide_status(self, message_id):
        if self.last_status_message_id == message_id:
            self.setStatusBar(None)

    def fw_versions_fetched(self, firmware_info):
        if isinstance(firmware_info, int):
            if firmware_info > 0:
                if firmware_info == 1:
                    message = 'Update information could not be downloaded from tinkerforge.com.<br/>' + \
                              'Is your computer connected to the Internet?'
                else:
                    message = ("Update information on tinkerforge.com is malformed " +
                               "(error code {0}).<br/>Please report this error to " +
                               "<a href='mailto:[email protected]'>[email protected]</a>.").format(firmware_info)

                self.show_status(message, message_id='fw_versions_fetched_error')

            infos.reset_latest_fws()
        else:
            self.hide_status('fw_versions_fetched_error')
            infos.update_latest_fws(firmware_info)

        self.update_tree_view()
class ProtocolDesignWidget(QWidget, LoadUIFileMixin):

    # todo - temp to let other parts of program know when protocol running. Program object itself should reside
    #         inside main window.
    programStarted = pyqtSignal()

    def __init__(self,
                 image_stack,
                 intensity_mask,
                 labjack,
                 stimulus_points,
                 selected_stimulus_points,
                 stimulus_widget,
                 workspace,
                 parent=None):
        super().__init__(parent)
        # self.fio_mappings = fio_mappings
        self.image_stack = image_stack
        self._intensity_mask = intensity_mask
        self.labjack = labjack
        self.program = None
        self.protocol_sequence = selected_stimulus_points
        self.stimulus_points = stimulus_points
        self.stimulus_points_dialog = None
        self.stimulus_sequence_thread = None
        self.stimulus_sequence_worker = None
        self.stimulus_widget = stimulus_widget
        self.workspace = workspace

        uic.loadUi(views.get('ProtocolDesignView.ui'), self)

        self.stimulus_points_dialog = StimulusPointsDialog(
            model=self.stimulus_points, parent=self)

        self.execute_loop_widget = ExecuteLoopWidget(parent=self)
        self.inputLayout.addWidget(self.execute_loop_widget)

        self.program_scroll_area_widget = ProgramScrollAreaWidget(self)

        self.program_scroll_area_widget.set_model(self.protocol_sequence)
        self.programScrollArea.setWidget(self.program_scroll_area_widget)

        self.chooseFromInput.setRange(0, 0)

        self.patterns = [IncrementByOnePattern, RandomPattern]

        self.pattern_icons = []

        for pattern in self.patterns:
            self.patternComboBox.insertItem(self.patternComboBox.count(),
                                            pattern.icon(), pattern.name,
                                            pattern)

        self.fio4Mapping.setText(self.labjack.fio4.label)
        self.fio5Mapping.setText(self.labjack.fio5.label)
        self.fio6Mapping.setText(self.labjack.fio6.label)
        self.fio7Mapping.setText(self.labjack.fio7.label)

        self.protocol_sequence.random_seed = self.randomSeedSpinBox.value()

        self.connect()

    def connect(self):

        self.execute_loop_widget.loop.valueChanged.connect(
            self.on_ExecuteLoopValue_valueChanged)
        self.execute_loop_widget.execute.pressed.connect(self.on_execute)

        self.laserInput.toggled.connect(lambda checked: self.waitInput.
                                        setChecked(False) if checked else None)
        self.pmtInput.toggled.connect(lambda checked: self.waitInput.
                                      setChecked(False) if checked else None)
        self.syncInput.toggled.connect(lambda checked: self.waitInput.
                                       setChecked(False) if checked else None)

        self.labjack.fio4.labelChanged.connect(
            lambda text: self.fio4Mapping.setText(text))
        self.labjack.fio5.labelChanged.connect(
            lambda text: self.fio5Mapping.setText(text))
        self.labjack.fio6.labelChanged.connect(
            lambda text: self.fio6Mapping.setText(text))
        self.labjack.fio7.labelChanged.connect(
            lambda text: self.fio7Mapping.setText(text))

        self.protocol_sequence.randomSeedChanged.connect(
            lambda new_seed: self.randomSeedSpinBox.setValue(new_seed))

        self.stimulus_points_dialog.stimulus_points_pattern_selected.connect(
            self.add_stimulus_point_list)

        self.stimulus_points.dataChanged.connect(
            lambda: self.chooseFromInput.setRange(
                1, self.stimulus_points.rowCount()))
        self.stimulus_points.rowsRemoved.connect(
            lambda: self.on_clearListButton_pressed())

        self.workspace.dataLoaded.connect(self.on_workspace_dataLoaded)

    def add_stimulus_point_list(self, stimulus_points_indices):

        if not stimulus_points_indices:
            return

        last_row = self.protocol_sequence.rowCount()

        selected_stimulus_points = [
            self.stimulus_points.points[i] for i in stimulus_points_indices
        ]

        if self.protocol_sequence.insertRow(self.protocol_sequence.rowCount()):
            model_index = self.protocol_sequence.index(last_row, 0)
            self.protocol_sequence.setData(model_index,
                                           value=selected_stimulus_points,
                                           role=Qt.EditRole)

    def clear_inputs(self):
        self.laserInput.setChecked(False)
        self.pmtInput.setChecked(False)
        self.syncInput.setChecked(False)
        self.waitInput.setChecked(False)
        self.durationInput.setValue(0.0)

    @pyqtSlot()
    def on_addProtocolElementButton_pressed(self):

        laser = self.get_input('laser')
        pmt = self.get_input('pmt')
        sync = self.get_input('sync')
        wait = self.get_input('wait')
        duration = self.get_input('duration')

        self.protocol_sequence.add_element(self.select_stimulus_points(),
                                           laser, pmt, sync, wait, duration)
        loop_count = self.execute_loop_widget.loop.value()
        self.update_generated_sequence_views(loop_count=loop_count)
        self.clear_inputs()

    @pyqtSlot()
    def on_execute(self):
        if len(self.protocol_sequence) == 0:
            message_boxes.warning(
                self,
                title="Empty Protocol!",
                text="No elements have been added to the protocol!")
            return

        if self.stimulus_sequence_thread and self.stimulus_sequence_thread.isRunning(
        ):
            if self.stimulus_sequence_thread.isInterruptionRequested():
                return
            if message_boxes.question(
                    self,
                    title="Busy",
                    text="Program is still executing, would you like to abort?"
            ):
                self.stimulus_sequence_thread.requestInterruption()
                self.execute_loop_widget.execute.setText('Aborting')
            return

        problems = []

        if not self.stimulus_widget.isVisible():
            problems.append("Stimulus window is not visible.")

        if not self.labjack.is_connected:
            problems.append("LabJack not found.")

        if problems:
            text = "The following problems were found:\n\n"
            for problem in problems:
                text += problem + "\n"
            text += "\nWould you like to continue?"
            if not message_boxes.question(self, "Problems Found", text):
                return

        if self.labjack.is_connected:
            self.labjack.clear()

        self.stimulus_sequence_thread = QThread()
        self.stimulus_sequence_thread.setObjectName('Program Thread')

        # if self.stimulus_widget.isVisible():
        #     self.program.generate_image_set()

        self.stimulus_sequence_worker = ExecuteProtocolSequenceWorker(
            program=self.program, labjack=self.labjack)
        self.stimulus_sequence_worker.moveToThread(
            self.stimulus_sequence_thread)

        self.stimulus_sequence_worker. \
            elementChanged.connect(lambda points: self.stimulus_widget.scene().display_points(points))
        self.stimulus_sequence_worker.loop_progress.connect(
            self.execute_loop_widget.update_progress_bar)

        self.stimulus_sequence_worker.interrupted.connect(
            self.stimulus_sequence_thread.quit)

        self.stimulus_sequence_worker.finished.connect(
            self.on_stimulus_sequence_finished)
        self.stimulus_sequence_worker.finished.connect(
            self.stimulus_sequence_thread.quit)

        if self.labjack.is_connected:
            self.stimulus_sequence_worker.finished.connect(
                lambda: self.labjack.clear())

        self.stimulus_sequence_thread.started.connect(self.on_thread_start)
        self.stimulus_sequence_thread.started.connect(
            self.stimulus_sequence_worker.run)

        self.stimulus_sequence_thread.finished.connect(self.on_thread_stop)
        self.stimulus_sequence_thread.finished.connect(
            self.stimulus_sequence_worker.deleteLater)

        self.stimulus_sequence_thread.start()
        self.programStarted.emit()

    # @pyqtSlot()
    # def on_abort_complete(self):
    #     self.on_t

    @pyqtSlot()
    def on_thread_start(self):
        self.labjack.check_device = False
        self.execute_loop_widget.execute.setText('Abort')

    @pyqtSlot()
    def on_thread_stop(self):
        self.labjack.check_device = True
        try:
            self.labjack.clear()
        except DeviceException:
            pass

        self.stimulus_widget.image = None
        self.execute_loop_widget.execute.setText('Execute')

    @pyqtSlot(int)
    def on_patternComboBox_currentIndexChanged(self, index):
        self.stimulus_points_dialog.pattern = self.pattern()
        self.update_generated_sequence_views(
            pattern=self.pattern(),
            loop_count=self.execute_loop_widget.loop.value())

    @pyqtSlot(str)
    def on_workspace_dataLoaded(self, loaded_from):
        for e in self.protocol_sequence.loaded_in_protocol['protocol']:
            stimulus_points = []

            for point in e['stimulus_points']:
                stimulus_point = self.stimulus_points[
                    point['stimulus_point_index']]
                stimulus_points.append(
                    SelectedStimulusPoint(stimulus_point=stimulus_point,
                                          pattern=point['pattern']))

            self.protocol_sequence.add_element(stimulus_points=stimulus_points,
                                               laser=e['laser'],
                                               pmt=e['pmt'],
                                               sync=e['sync'],
                                               wait=e['wait'],
                                               duration=e['duration'])
        self.protocol_sequence.pattern = self.protocol_sequence.loaded_in_protocol[
            'pattern']
        self.patternComboBox.setCurrentIndex(
            self.patternComboBox.findText(self.protocol_sequence.pattern.name))

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Delete:
            selection = self.stimulus_points.selectionModel()
            model_indices = selection.selectedRows()
            [
                self.protocol_sequence.removeRow(model_index.row() - i)
                for i, model_index in enumerate(model_indices)
            ]
        else:
            super().keyPressEvent(event)

    @pyqtSlot(int)
    def on_ExecuteLoopValue_valueChanged(self, value):
        self.update_generated_sequence_views(loop_count=value)

    @pyqtSlot(int)
    def on_patternComboBox_currentIndexChanged(self, index):
        pattern = self.patternComboBox.currentData()
        self.stimulus_points_dialog.pattern = pattern
        self.protocol_sequence.pattern = pattern
        self.update_generated_sequence_views()

    @pyqtSlot(int, int)
    def on_protocol_elementChanged(self, loop, iteration):
        self.stimulus_widget.image = self.program.get_image(loop, iteration)

    @pyqtSlot()
    def on_selectStimulusPointsButton_pressed(self):
        self.stimulus_points_dialog.show()

    @pyqtSlot(int)
    def on_randomInput_stateChanged(self, state):
        self.chooseFromInput.setEnabled(state)

    @pyqtSlot(int)
    def on_randomSeedSpinBox_valueChanged(self, value):
        self.protocol_sequence.random_seed = value

    @pyqtSlot()
    def on_stimulus_sequence_finished(self):
        self.stimulus_widget.image = None

    @pyqtSlot(bool)
    def on_waitInput_toggled(self, checked):
        if checked:
            self.laserInput.setChecked(False)
            self.pmtInput.setChecked(False)
            self.syncInput.setChecked(False)
            self.durationInput.setValue(0.00)

    @pyqtSlot()
    def on_clearListButton_pressed(self):
        self.protocol_sequence.removeRows(0, self.protocol_sequence.rowCount())
        self.update_generated_sequence_views(loop_count=0)
        self.stimulus_widget.scene().display_points([])

    def pattern(self):
        return self.patternComboBox.currentData()

    @pyqtSlot()
    def on_sendPreviewButton_pressed(self):
        iteration = self.iterationPreviewSpinBox.value()
        loop = self.loopNumberPreviewSpinBox.value()
        self.stimulus_widget.open()
        self.stimulus_widget.scene().display_points([
            p.stimulus_point
            for p in self.program[loop][iteration].stimulus_points
        ])

    def select_stimulus_points(self):
        selected_stimulus_points = []

        if self.randomInput.isChecked():
            chosen_points = self.stimulus_points.sample(
                choose=self.chooseFromInput.value())
            selected_stimulus_points = [
                SelectedStimulusPoint(stimulus_point=point,
                                      pattern=NormalPattern)
                for point in chosen_points
            ]
        elif self.stimulus_points_dialog:
            selected_stimulus_points = self.stimulus_points_dialog.selected_stimulus_points(
            )

        return selected_stimulus_points

    def update_generated_sequence_views(self, loop_count=None):
        if not loop_count:
            loop_count = self.execute_loop_widget.loop.value()
        # todo - don't remake this every time, fix and move to main window and generate sequence in place
        if self.protocol_sequence.protocol:
            self.program = Program(
                image_stack=self.image_stack,
                initial_sequence=self.protocol_sequence.protocol,
                inter_loop_delay=self.execute_loop_widget.loop_delay.value(),
                random_seed=self.protocol_sequence.random_seed,
                stimulus_points=self.stimulus_points,
                stimulus_widget=self.stimulus_widget,
                pattern=self.protocol_sequence.pattern,
                loop_count=loop_count)
            self.program.generate()
            models = [
                ProtocolSequence(sequence=sequence)
                for sequence in self.program.program
            ]
            self.program_scroll_area_widget.set_number_of_widgets(
                models=models, number=loop_count)
class main_principal(QWidget):
    fecha_actual = datetime.datetime.now(
    )  # datetime es la libreria que nos ayuda con las fechas horas minutos y hsta segundos
    #en este caso nos referimos a que nos de la fecha actual
    contador_hilo = 1
    cursor = None
    lista_hilos = []

    def banner_anonymous(self):
        #banner un bannner es solo un mensaje de texto
        #el       \32 es para indicarle que queremos un espacio en blanco
        #puedes ver que hay muchos \32 en este paratado eso es debido a que
        #se necesitan espacios para imprimir bien el banner
        self.textarea_proceso.append(
            "$$$$$\32$\32\32\32\32$\32\32$$$$$\32$\32\32\32\32\32$$$$$$$\32\32\32\32\32\32######\32#####\32######\32##\32\32\32##"
        )
        self.textarea_proceso.append(
            "$\32\32\32\32\32\32\32\32$\32\32\32\32$\32\32\32\32\32$\32\32\32\32$\32\32\32\32\32$\32\32\32\32\32$\32\32\32\32\32$\32\32\32\32\32\32#\32\32\32\32\32\32#"
        )
        self.textarea_proceso.append(
            "$$$$$\32\32\32$\32\32\32\32\32$$$$$\32$\32\32\32\32\32$\32\32\32\32\32$||||||\32\32\32\32\32######\32#####\32######\32#\32\32\32\32\32#"
        )
        self.textarea_proceso.append(
            "$\32\32\32\32\32\32$\32\32$\32\32\32$\32\32\32\32\32$\32\32\32\32\32$\32\32\32\32\32\32$\32\32\32\32\32\32\32\32\32\32\32#|#\32\32\32\32\32#\32\32\32\32#\32#\32\32\32\32\32#"
        )
        self.textarea_proceso.append(
            "$$$$$\32$\32\32\32\32$\32\32$\32\32\32\32\32$$$$$\32$$$$$$$\32\32\32\32\32\32######\32#\32\32\32\32\32#\32\32\32\32#\32#\32\32\32\32\32#"
        )
        self.textarea_proceso.append(
            "Autor: Aldair Martinez Alias Hans Krammler Junior"
        )  # Aqui declaramos mi nombre jajaj XD

    def spam_uno_a_uno(self):
        self.banner_anonymous()
        print(self.fecha_actual)

    def spam_uno_a_varios(self):
        self.banner_anonymous()
        print(self.fecha_actual)
        print(Fore.GREEN + "HAZ ELEGIDO ENVIAR DESDE TU COREO A OTROS CORREO")

    def mensaje(self):
        print("SE TERMINO EL HILO")

    contador_hilo_aux = 0
    lista_hilos = []
    workers = []
    threads = []

    def incializa_hilos(self):
        if self.contador_hilo == 1:
            self.thread = QThread()
            self.thread.setObjectName("Hilo_1")
            self.worker = Worker(self.contador_hilo, self.textarea_proceso,
                                 self.label_proceso_verificacion, None)
            self.worker.moveToThread(self.thread)
            self.thread.setTerminationEnabled(True)
            self.thread.started.connect(self.worker.run)
            self.worker.finished.connect(self.terminado)
            self.thread.finished.connect(
                lambda: self.verificar_resultado_hilo(self.thread))
            self.worker.progressed.connect(
                lambda: self.reportProgress(self.worker))
            self.worker.siguiente.connect(
                lambda: self.siguiente_hilo(self.thread))
            self.thread.finished.connect(self.thread.quit)
            self.lista_hilos.append(self.thread)
            return self.lista_hilos
        else:
            self.lista_hilos = []
            self.threads.append(QThread())
            self.threads[self.contador_hilo_aux].setObjectName("Hilo_2")
            if self.contador_hilo == 2:
                print("Aqui tambien funciona")
                self.workers.append(
                    Worker(self.contador_hilo, self.textarea_proceso, None,
                           self.label_proceso_verificacion2))
            elif self.contador_hilo == 3:
                self.workers.append(
                    Worker(self.contador_hilo, self.textarea_proceso, None,
                           self.label_proceso_verificacion3))
            self.workers[self.contador_hilo_aux].moveToThread(
                self.threads[self.contador_hilo_aux])
            self.threads[self.contador_hilo_aux].started.connect(
                self.workers[self.contador_hilo_aux].run)
            self.workers[self.contador_hilo_aux].finished.connect(
                self.terminado)
            print("Pasando terminado")
            self.threads[self.contador_hilo_aux].finished.connect(
                lambda: self.verificar_resultado_hilo(self.threads[
                    self.contador_hilo_aux]))
            print("Pasando verificar resultado")
            self.workers[self.contador_hilo_aux].progressed.connect(
                lambda: self.reportProgress(self.workers[self.contador_hilo_aux
                                                         ]))
            print("Pasando reportprogress")
            self.workers[self.contador_hilo_aux].siguiente.connect(
                lambda: self.siguiente_hilo(self.threads[self.contador_hilo_aux
                                                         ]))
            self.threads[self.contador_hilo_aux].finished.connect(
                self.threads[self.contador_hilo_aux].quit)
            print("Pasando quit")
            self.lista_hilos.append(self.threads[self.contador_hilo_aux])
            return self.lista_hilos
        return None

    def siguiente_hilo(self, hilo):
        if self.contador_hilo == 1:
            if hilo.isRunning() == False:
                if hilo.isFinished() == True:
                    print("si funciono")
                    hilo.finished.connect(hilo.deleteLater)
                    hilo.finished.connect(hilo.terminate)
                    self.contador_hilo = self.contador_hilo + 1
                    self.incializa_hilos()[0].start()
        elif self.contador_hilo > 1:
            if self.get_verificado() == -1:
                return -1
            elif self.get_verificado() == -2:
                return -2
            else:
                return -3
            if hilo.isRunning() == False:
                if hilo.isFinished() == True:
                    self.contador_hilo = self.contador_hilo + 1
                    self.contador_hilo_aux = self.contador_hilo_aux + 1
                    self.incializa_hilos()[0].start()

        return 0

    cursor = None
    estado_hilo_x = 0
    lista_verificado = []

    def set_verificado(self, estado):
        self.estado_hilo_x = estado

    def get_verificado(self):
        self.lista_verificado.append(self.estado_hilo_x)
        print("get.lista.verificado" + str(self.lista_verificado[0]))
        return int(self.lista_verificado[0])

    def terminado(self):
        if self.contador_hilo == 1:
            print("TRABAJO TERMINADO")
            self.thread.finished.emit()
        elif self.contador_hilo > 1:
            self.threads[self.contador_hilo_aux].finished.emit()

    def verificar_resultado_hilo(self, thread):
        if self.contador_hilo == 1:
            if self.thread.isRunning() == False:
                print("EL HILO 1 HA TERMINADO SU TAREA")
            if self.thread.isFinished() == True:
                print("EL HILI 1 TERMINO CON EXITO")
                self.worker.progressed.emit()
        elif self.contador_hilo > 1:
            print(str(self.contador_hilo_aux))
            if thread.isRunning() == False:
                print("EL HILO" + str(self.contador_hilo_aux) +
                      "ESTA CORRIENDO")
            if thread.isFinished() == True:
                print("EL HILO" + str(self.contador_hilo_aux) + "HA TERMINADO")
                self.workers[self.contador_hilo_aux].progressed.emit()

    porcentaje = 10
    _i = 0
    lista_progreso = ["INICIANDO HILO DE EJECUCION......"]

    def reportProgress(self, worker):
        print(self.contador_hilo)
        if self.contador_hilo == 1:
            print("REPORTANDO HILO")
            #print("POSICION ACTUAL DEL CURSOR"+str(int(self.textarea_proceso.textCursor().position())))
            worker.siguiente.emit()
        else:
            if self.contador_hilo == 2:
                email = self.line_edit_user_email.text()
                password = self.line_edit_user_password.text()
                longitud_email = len(email)
                longitud_password = len(password)
                if longitud_email == 0 or longitud_password == 0:

                    self.set_verificado(-1)
            elif self.contador_hilo == 3:
                email = self.line_edit_user_email.text()
                email_to_list = list(email)
                contador = 1
                for _x in len(email):
                    if email_to_list[_x] == "@":
                        contador = contador + 1
                if contador > 1:
                    self.set_verificado(-2)
            elif self.contador_hilo == 4:
                contador = 1
                email_dominio = email.split(".")
                textarea_proceso.append("DIVIDIENDO TU EMAIL--->" +
                                        email_dominio)
                longitud_dominio_email = len(email_dominio)
                textarea_proceso.append(
                    "VERIFICANDO COINCIDENCIA DE DOMINIO DE EMAIL....")
                dominios = [
                    "gmail.com", "outlook.com", "outlook.es", "hotmail.com",
                    "hotmail.mx", "hotmail.es"
                ]
                longitud_dominio_gmail = len(dominios[0])
                longitud_dominio_outlook_com = len(dominios[1])
                longitud_dominio_outlook_es = len(dominios[2])
                longitud_dominio_hotmail_com = len(dominios[3])
                longitud_dominio_hotmail_mx = len(dominios[4])
                longitud_dominio_hotmail_es = len(dominios[5])
                for _i in range(longitud_dominio_email):
                    for _j in (range(longitud_dominio_gmail)
                               or range(longitud_dominio_outlook_com)
                               or range(longitud_dominio_outlook_es)
                               or range(longitud_dominio_hotmail_com)
                               or range(longitud_dominio_hotmail_mx)
                               or range(longitud_dominio_hotmail_es)):
                        if _j < longitud_dominio_gmail:
                            if dominios[0][_j] == email_dominio[1][_j]:
                                print(dominios[0][j])
                                contador = contador + 1
                        elif _j < longitud_dominio_outlook_com:
                            if dominios[1][_j] == email_dominio[1][_j]:
                                contador = contador + 1
                        elif _j < longitud_dominio_outlook_es:
                            if dominios[2][_j] == email_dominio[1][_j]:
                                contador = contador + 1
                        elif _j < longitud_dominio_hotmail_com:
                            if dominios[3][_j] == email_dominio[1][_j]:
                                contador = contador + 1
                        elif _j < longitud_dominio_hotmail_mx:
                            if dominios[4][_j] == email_dominio[1][_j]:
                                contador = contador + 1
                        elif _j < longitud_dominio_hotmail_es:
                            if dominios[5][_j] == email_dominio[1][_j]:
                                contador = contador + 1
                    if contador > longitud_dominio_gmail:
                        return -3
                    else:
                        contador = 1
            print("AQUI MEN")
            posicion_textarea = len(self.lista_progreso[self._i])
            self.cursor = self.textarea_proceso.textCursor()
            print("CURSOR ACTUAL" + str(self.cursor))
            print("POSICION CURSOR ACTUAL++" + str(self.cursor.position()))
            if self.cursor.atEnd() == True:
                print("FIN DEL CURSOR True")
                print("TEXTO SELECCIONADO ANTES" + self.cursor.selectedText())
                posicion_inicial = (self.cursor.position() -
                                    1) - (posicion_textarea - 1) + 1
                print("posicion inicial" + str(posicion_inicial))
                time.sleep(0.4)
                self.cursor.setPosition(posicion_inicial,
                                        QTextCursor.KeepAnchor)
                print("TEXTO SELECCIONADO AHORA" + self.cursor.selectedText())
                print("POSICION CURSOR ACTUAL--" + str(self.cursor.position()))
                self.progreso = self.cursor.selectedText() + str(
                    self.porcentaje)
                self.porcentaje = self.porcentaje + 10
                self.textarea_proceso.append(self.progreso)
                print("POSICION CURSOR ACTUAL++" + str(self.cursor.position()))
            self._i = self._i + 1
            worker.siguiente.emit()

    def verificar_opcion(self):
        pass

    def validar_oprciones(self):
        if self.checkbox_opcion_spam_uno_a_uno.isChecked(
        ) == True and self.checkbox_opcion_spam_uno_a_varios.isChecked():
            self.label_estado_proceso.setText(
                Fore.RED +
                "LO SIENTO NO PUEDES SELECCIONAR DOS OPCIONES A LA VEZ")
            return False
        else:
            return True

    def escannear_red(self):
        scan = nmap.PortScanner()
        informacion_sistema = os.uname()

    def presionado(self):
        resultado_validar_opciones = self.validar_oprciones()
        if resultado_validar_opciones == True:
            self.inciar_hilo_principal()
            self.resultado_verificacion_email = 0
            if self.resultado_verificacion_email == 0:
                print("PASANDO AL SIGUIENTE HILO DE EJECUCION")
            if self.resultado_verificacion_email == 1:
                if self.checkbox_opcion_spam_uno_a_uno.isChecked() == True:
                    print(
                        "LAS OCPIONES SELECCIONADAAS EN LOS CASILLA SON VALIDAS"
                    )
            else:
                if self.resultado_verificacion_email == -1 or self.resultado_verificacion_email == -2 or self.resultado_verificacion_email == -3:
                    if self.resultado_verificacion_email == -1:
                        self.label_estado_proceso.setText(
                            "NO HAS INGRESADO NADA EN EL PRIMER CAMPO")
                        self.textarea_proceso.append(
                            "ERROR NO HAS INGRESADO NADA EN EL PRIMER CAMPO")
                    elif self.resultado_verificacion_email == -2:
                        self.label_estado_proceso.setText(
                            "HAS MAS DE UN ARROBA EN TU CORREO")
                        self.textarea_proceso.append(
                            "ERROR HAY MAS DE UN ARROBA EN TU CORREO")
                    elif self.resultado_verificacion_email == -3:
                        self.label_estado_proceso.setText(
                            "TERMINACION DE CORREO NO VALIDA")

        else:
            self.textarea_proceso.append(
                "INTENTE DE NUEVO RELLENAR LOS CAMPOS ADECUADAMENTE")

    def __init__(self, parent=None):
        #Parte de intefaz grafica
        super().__init__(parent)

        self.color = QColor(0, 0, 0, 0.5)
        self.setGeometry(550, 100, 1500, 500)
        self.pallette = QPalette(self.color)
        self.pallette.setColor(QPalette.Text, Qt.cyan)

        self.titulo_ventana = "ENVIO DE CORREO PARA HACER SPAM"
        self.setWindowTitle(self.titulo_ventana)
        self.setStyleSheet(
            "border-color: cyan; border-style: dashed; border-width: 2px; color:white"
        )
        self.setPalette(self.pallette)
        self.checkbox_opcion_spam_uno_a_uno = QCheckBox(
            "REALIZAR SPAM UNO A UN CORREO")
        self.checkbox_opcion_spam_uno_a_uno.clicked.connect(
            self.spam_uno_a_uno)
        self.checkbox_opcion_spam_uno_a_varios = QCheckBox(
            "REALIZAR SPAM UNO A VARIOS COOREOS")
        self.checkbox_opcion_spam_uno_a_varios.clicked.connect(
            self.spam_uno_a_varios)

        self.label_user_email = QLabel("Ingresa tu direccion de correo: ")
        self.label_user_email.setFont(QFont("Times", 14, QFont.Bold, True))
        self.label_user_email.setStyleSheet("color: white")

        self.line_edit_user_email = QLineEdit(self)
        self.line_edit_user_email.setPlaceholderText(
            "Ingresa tu direccion de cooreo:")
        self.line_edit_user_email.setFont(QFont("Times", 14, QFont.Bold, True))
        self.line_edit_user_email.setStyleSheet("color : black")

        self.label_user_password = QLabel("Ingresa tu password de correo")
        self.label_user_password.setFont(QFont("Times", 14, QFont.Bold, True))
        self.label_user_password.setStyleSheet("color: white")

        self.line_edit_user_password = QLineEdit(self)
        self.line_edit_user_password.setPlaceholderText(
            "Ingresa tu password de tu correo:")
        self.line_edit_user_password.setFont(
            QFont("Times", 14, QFont.Bold, True))
        self.line_edit_user_password.setStyleSheet("color : black")

        self.label_victima_email = QLabel(
            "Ingresa direccion de correo destinatario: ")
        self.label_victima_email.setFont(QFont("Times", 14, QFont.Bold, True))
        self.label_victima_email.setStyleSheet("color: white")

        self.line_edit_victima_email = QLineEdit(self)
        self.line_edit_victima_email.setPlaceholderText(
            "Ingresa tu direccion de cooreo:")
        self.line_edit_victima_email.setFont(
            QFont("Times", 14, QFont.Bold, True))
        self.line_edit_victima_email.setStyleSheet("color : black")

        self.label_user_asunto = QLabel("Ingresa el asunto ")
        self.label_user_asunto.setFont(QFont("Times", 14, QFont.Bold, True))
        self.label_user_asunto.setStyleSheet("color: white")

        self.line_edit_user_asunto = QLineEdit(self)
        self.line_edit_user_asunto.setPlaceholderText(
            "Ingresa tu direccion de cooreo:")
        self.line_edit_user_asunto.setFont(QFont("Times", 14, QFont.Bold,
                                                 True))
        self.line_edit_user_asunto.setStyleSheet("color : black")

        self.pushbutton_enviar = QPushButton("ENVIAR SPAM")
        self.pushbutton_enviar.setFont(QFont("Times", 14, QFont.Bold, True))
        self.pushbutton_enviar.setPalette(self.pallette)

        self.label_estado_proceso = QLabel("")
        self.label_estado_proceso.setText("ESPERANDO DATOS....")

        self.label_proceso = QLabel("Estado verificacion email y password")
        self.label_proceso_verificacion = QLabel("")
        self.label_proceso2 = QLabel(
            "Estado verificacion arroba de email origen y destino")
        self.label_proceso_verificacion2 = QLabel("")
        self.label_proceso3 = QLabel(
            "Estado verificacion terminacion correo origen y destino")
        self.label_proceso_verificacion3 = QLabel("")
        self.combobox_dominios_from = QComboBox()
        self.combobox_dominios_from.addItem("Gmail.com")
        self.combobox_dominios_from.insertSeparator(1)
        self.combobox_dominios_from.addItem("Outlook.com")
        self.combobox_dominios_from.addItem("Outlook.es")
        self.combobox_dominios_from.insertSeparator(4)
        self.combobox_dominios_from.addItem("Hotmail.com")
        self.combobox_dominios_from.addItem("Hotmail.es")
        self.combobox_dominios_from.addItem("Hotmail.mx")
        self.combobox_dominios_from.setStyleSheet(
            "background-color:black; color: cyan")

        self.combobox_dominios_to = QComboBox()
        self.combobox_dominios_to.addItem("Gmail.com")
        self.combobox_dominios_to.insertSeparator(1)
        self.combobox_dominios_to.addItem("Outlook.com")
        self.combobox_dominios_to.addItem("Outlook.es")
        self.combobox_dominios_to.insertSeparator(4)
        self.combobox_dominios_to.addItem("Hotmail.com")
        self.combobox_dominios_to.addItem("Hotmail.es")
        self.combobox_dominios_to.addItem("Hotmail.mx")
        self.combobox_dominios_to.setStyleSheet(
            "background-color:black; color: cyan")

        self.combobox_dominios_from.currentTextChanged.connect(
            self.verificar_opcion)
        self.combobox_dominios_to.currentTextChanged.connect(
            self.verificar_opcion)
        self.pushbutton_enviar.clicked.connect(self.presionado)

        self.layout_main = QHBoxLayout(self)

        self.layout_principal = QVBoxLayout()
        self.layout_principal.addWidget(self.label_user_email)
        self.layout_principal.addWidget(self.line_edit_user_email)
        self.layout_principal.addWidget(self.label_user_password)
        self.layout_principal.addWidget(self.line_edit_user_password)
        self.layout_principal.addWidget(self.label_victima_email)
        self.layout_principal.addWidget(self.line_edit_victima_email)
        self.layout_principal.addWidget(self.label_user_asunto)
        self.layout_principal.addWidget(self.line_edit_user_asunto)
        self.layout_principal.addWidget(self.checkbox_opcion_spam_uno_a_uno)
        self.layout_principal.addWidget(self.checkbox_opcion_spam_uno_a_varios)
        self.layout_principal.addWidget(self.pushbutton_enviar)
        self.layout_principal.addWidget(self.label_estado_proceso)
        self.layout_principal.addWidget(self.combobox_dominios_from)
        self.layout_principal.addWidget(self.combobox_dominios_to)
        self.layout_principal.addWidget(self.label_proceso)
        self.layout_principal.addWidget(self.label_proceso_verificacion)
        self.layout_principal.addWidget(self.label_proceso2)
        self.layout_principal.addWidget(self.label_proceso_verificacion2)
        self.layout_principal.addWidget(self.label_proceso3)
        self.layout_principal.addWidget(self.label_proceso_verificacion3)

        self.layout_secundario = QVBoxLayout()
        self.textarea_proceso = QTextEdit()
        self.textarea_proceso.setPlaceholderText("ESPERANDO PARA PROCESAR")
        self.textarea_proceso.setFont(QFont("Times", 14, QFont.Bold, True))
        self.textarea_proceso.setGeometry(0, 0, 500, 400)
        self.textarea_proceso.setStyleSheet(
            "color: yellow; background-color:black; border-style:solid")
        self.textarea_proceso.setReadOnly(False)
        self.textarea_proceso.setOverwriteMode(QTextEdit.WidgetWidth)
        self.layout_secundario.addWidget(self.textarea_proceso)

        self.layout_main.addLayout(self.layout_principal, 4)
        self.layout_main.addLayout(self.layout_secundario, 4)

    def inciar_hilo_principal(self):
        self.incializa_hilos()[0].start()
        return 0
Example #28
0
    def btn_event(self):
        if (self.btn_flag == 0):
            self.btn_flag = 1

            self.startBtn.setIcon(QtGui.QIcon('stop.png'))
            self.startBtn.setIconSize(QtCore.QSize(100, 100))

            self.__threads = []

            # create a recorder object
            record = Recorder()
            record_thread = QThread()
            record_thread.setObjectName('record thread')
            self.__threads.append(
                (record_thread,
                 record))  # need to store worker too otherwise will be gc'd

            record.moveToThread(record_thread)

            # get progress messages from worker:
            record.sig_step.connect(self.on_recorder_worker_step)

            # control worker:
            self.sig_recorder_abort_workers.connect(record.abort)

            # get read to start worker:record

            record_thread.started.connect(record.work)
            record_thread.start(
            )  # this will emit 'started' and start thread's event loop
        else:
            self.btn_flag = 0

            self.startBtn.setIcon(QtGui.QIcon('record.png'))
            self.startBtn.setIconSize(QtCore.QSize(100, 100))

            self.sig_recorder_abort_workers.emit()
            print('Asking each worker to abort')
            for record_thread, record in self.__threads:  # note nice unpacking by Python, avoids indexing
                record_thread.quit(
                )  # this will quit **as soon as thread event loop unblocks**
                record_thread.wait(
                )  # <- so you need to wait for it to *actually* quit
            self.startBtn.setDisabled(True)
            self.statusLabel.show()

            inference = Inference()
            inference_thread = QThread()
            inference_thread.setObjectName('Inference Thread')
            self.__threads.append(
                (inference_thread,
                 inference))  # need to store worker too otherwise will be gc'd

            inference.moveToThread(inference_thread)
            inference.sig_result.connect(self.on_inference_worker_end)

            self.sig_inference_abort_workers.connect(inference.abort)

            inference_thread.started.connect(inference.work)
            inference_thread.start(
            )  # this will emit 'started' and start thread's event loop
Example #29
0
	def get_metadata(self, gal=None):
		metadata_spinner = misc.Spinner(self)
		def move_md_spinner():
			metadata_spinner.update_move(
			QPoint(
				self.pos().x()+self.width()-65,
				self.pos().y()+self.toolbar.height()+55))
		metadata_spinner.set_text("Metadata")
		metadata_spinner.set_size(55)
		metadata_spinner.move(QPoint(self.pos().x()+self.width()-65,
							   self.pos().y()+self.toolbar.height()+55))
		self.move_listener.connect(move_md_spinner)
		thread = QThread(self)
		thread.setObjectName('App.get_metadata')
		fetch_instance = fetch.Fetch()
		if gal:
			if not isinstance(gal, list):
				galleries = [gal]
			else:
				galleries = gal
		else:
			if app_constants.CONTINUE_AUTO_METADATA_FETCHER:
				galleries = [g for g in self.manga_list_view.gallery_model._data if not g.exed]
			else:
				galleries = self.manga_list_view.gallery_model._data
			if not galleries:
				self.notification_bar.add_text('Looks like we\'ve already gone through all galleries!')
				return None
		fetch_instance.galleries = galleries

		self.notification_bar.begin_show()
		fetch_instance.moveToThread(thread)

		def done(status):
			self.notification_bar.end_show()
			fetch_instance.deleteLater()
			if not isinstance(status, bool):
				galleries = []
				for tup in status:
					galleries.append(tup[0])

				class GalleryContextMenu(QMenu):
					app_instance = self
					def __init__(self, parent=None):
						super().__init__(parent)
						show_in_library_act = self.addAction('Show in library')
						show_in_library_act.triggered.connect(self.show_in_library)

					def show_in_library(self):
						viewer = self.app_instance.manga_list_view
						index = viewer.find_index(self.gallery_widget.gallery.id, True)
						if index:
							self.app_instance.manga_table_view.scroll_to_index(index)
							self.app_instance.manga_list_view.scroll_to_index(index)

				g_popup = io_misc.GalleryPopup(('Fecthing metadata for these galleries failed.'+
									  ' Check happypanda.log for details.', galleries), self, menu=GalleryContextMenu())
				#errors = {g[0].id: g[1] for g in status}
				#for g_item in g_popup.get_all_items():
				#	g_item.setToolTip(errors[g_item.gallery.id])
				g_popup.graphics_blur.setEnabled(False)
				close_button = g_popup.add_buttons('Close')[0]
				close_button.clicked.connect(g_popup.close)

		fetch_instance.GALLERY_PICKER.connect(self._web_metadata_picker)
		fetch_instance.GALLERY_EMITTER.connect(self.manga_list_view.replace_edit_gallery)
		fetch_instance.AUTO_METADATA_PROGRESS.connect(self.notification_bar.add_text)
		thread.started.connect(fetch_instance.auto_web_metadata)
		fetch_instance.FINISHED.connect(done)
		fetch_instance.FINISHED.connect(metadata_spinner.close)
		fetch_instance.FINISHED.connect(lambda: self.move_listener.disconnect(move_md_spinner))
		thread.finished.connect(thread.deleteLater)
		thread.start()
		metadata_spinner.show()
Example #30
0
class MainModel:
    status_bar_progress = None
    status_bar_thread = None
    status_bar_listener = None

    def __init__(self, videos, videos_limit):
        super().__init__()
        self.logger = create_logger(__name__)
        self.videos_limit = videos_limit
        self.playview_videos_limit = videos_limit
        self.videos = videos
        self.subfeed_videos = []
        self.subfeed_videos_removed = {}
        self.playview_videos = []
        self.playview_videos_removed = {}

        self.download_progress_signals = []

        self.logger.info("Creating listeners and threads")
        self.playback_grid_view_listener = PlaybackGridViewListener(self)
        self.playback_grid_thread = QThread()
        self.playback_grid_thread.setObjectName('playback_grid_thread')
        self.playback_grid_view_listener.moveToThread(
            self.playback_grid_thread)
        self.playback_grid_thread.start()
        self.subfeed_grid_view_listener = SubfeedGridViewListener(self)
        self.subfeed_grid_thread = QThread()
        self.subfeed_grid_thread.setObjectName('subfeed_grid_thread')
        self.subfeed_grid_view_listener.moveToThread(self.subfeed_grid_thread)
        self.subfeed_grid_thread.start()

        self.database_listener = DatabaseListener(self)
        self.db_thread = QThread()
        self.db_thread.setObjectName('db_thread')
        self.database_listener.moveToThread(self.db_thread)
        self.db_thread.start()

        self.main_window_listener = MainWindowListener(self)
        self.main_w_thread = QThread()
        self.main_w_thread.setObjectName('main_w_thread')
        self.main_window_listener.moveToThread(self.main_w_thread)
        self.main_w_thread.start()

        self.download_handler = DownloadViewListener(self)
        self.download_thread = QThread()
        self.download_thread.setObjectName('download_thread')
        self.download_handler.moveToThread(self.download_thread)
        self.download_thread.start()

        if read_config("Play", "yt_file_path", literal_eval=False):
            self.yt_dir_listener = YoutubeDirListener(self)
            self.yt_dir_thread = QThread()
            self.yt_dir_thread.setObjectName('yt_dir_thread')
            self.yt_dir_listener.moveToThread(self.yt_dir_thread)
            self.yt_dir_thread.start()
        else:
            self.logger.warning(
                "No youtube file path provided, directory listener is disabled"
            )
            self.yt_dir_listener = None

    def hide_video_item(self, video, widget_id):
        """
        Hides the video from a View.
        :param widget_id: Identifier for which View called this function.
        :param video:
        :return:
        """
        if widget_id:
            self.logger.debug("Hiding video item: {}".format(video))
            if widget_id == SUBFEED_VIEW_ID:
                self.subfeed_videos_removed.update(
                    {video: remove_video(self.subfeed_videos, video)})
            elif widget_id == PLAYBACK_VIEW_ID:
                self.playview_videos_removed.update(
                    {video: remove_video(self.playview_videos, video)})
        else:
            self.logger.error("Unable to hide video item: widget_id was None!")

    def unhide_video_item(self, video, widget_id):
        """
        Shows a video previously hidden from view.
        :param widget_id: Identifier for which View called this function.
        :param video:
        :return:
        """
        if widget_id:
            self.logger.debug("Un-hiding video item: {}".format(video.title))
            if widget_id == SUBFEED_VIEW_ID:
                add_video(self.subfeed_videos, video,
                          self.subfeed_videos_removed[video])
                self.subfeed_videos_removed.pop(video)
            elif widget_id == PLAYBACK_VIEW_ID:
                add_video(self.playview_videos, video,
                          self.playview_videos_removed[video])
                self.playview_videos_removed.pop(video)
        else:
            self.logger.error("Unable to hide video item: widget_id was None!")

    def update_subfeed_videos_from_db(self, filtered=True):
        """
        Updates Subscription feed video list from DB.

        Updates the filter with values in model and calls static database (read operation)
        function which doesn't have direct access to the model object.
        :param filtered:
        :return:
        """
        self.logger.info("Getting newest stored videos from DB")
        # FIXME: only does filtered videos
        if filtered:
            show_downloaded = read_config('SubFeed', 'show_downloaded')
            show_dismissed = read_config('GridView', 'show_dismissed')
            update_filter = ()
            if not show_downloaded:
                update_filter += (~Video.downloaded, )
            if not show_dismissed:
                update_filter += (~Video.discarded, )

            self.subfeed_videos = get_db_videos_subfeed(self.videos_limit,
                                                        filters=update_filter)
            self.subfeed_grid_view_listener.videosChanged.emit()
        else:
            self.videos = get_db_videos_subfeed(self.videos_limit, filtered)

    def update_subfeed_videos_from_remote(
            self, filtered=True, refresh_type=LISTENER_SIGNAL_NORMAL_REFRESH):
        """
        Updates Subscription feed video list from a remote source (likely YouTube API).

        :param filtered: Whether to filter out certain videos based on set boolean attributes.
        :param refresh_type: A signal determining whether it is a Normal (int(0)) or Deep (int(1)) refresh.
                             This kwarg is not used here, but passed on to the refresh function.
        :return:
        """
        self.logger.info("Reloading and getting newest videos from YouTube")
        try:
            if filtered:
                show_downloaded = not read_config('SubFeed', 'show_downloaded')
                show_dismissed = not read_config('GridView', 'show_dismissed')
                self.subfeed_videos = refresh_and_get_newest_videos(
                    self.videos_limit,
                    progress_listener=self.status_bar_listener,
                    refresh_type=refresh_type,
                    filter_discarded=show_dismissed,
                    filter_downloaded=show_downloaded)
                self.subfeed_grid_view_listener.videosChanged.emit()
            else:
                self.videos = refresh_and_get_newest_videos(
                    self.videos_limit,
                    filtered,
                    self.status_bar_listener,
                    refresh_type=refresh_type)
        except SaneAbortedOperation as exc_sao:
            # FIXME: Send aborted operation signal back up to GUI
            self.logger.critical(
                "A SaneAbortedOperation exc occurred while updating subfeed from remote! Exceptions:"
            )
            for exc in exc_sao.exceptions:
                self.logger.exception(str(exc), exc_info=exc_sao)

    def update_playback_videos_from_db(self):
        """
        Update the PlaybackView video list from DB.

        Note: There's no remote update for PlaybackView like there is for SubfeedView.
        :return:
        """
        update_filter = self.filter_playback_view_videos()
        update_sort = self.sort_playback_view_videos()
        self.playview_videos = get_db_videos_playback(
            self.playview_videos_limit,
            filters=update_filter,
            sort_method=update_sort)
        self.playback_grid_view_listener.videosChanged.emit()

    def create_progressbar_on_statusbar(self, parent):
        """
        Creates a QProgressBar and attaches it to the status bar.
        :param parent:
        :return:
        """
        self.status_bar_progress = QProgressBar(parent=parent)
        palette = QPalette(self.status_bar_progress.palette())
        palette.setColor(QPalette.Highlight, QColor(24, 68, 91).lighter(200))
        self.status_bar_progress.setPalette(palette)
        self.status_bar_listener = ProgressBarListener(
            self, self.status_bar_progress)
        self.status_bar_thread = QThread()
        self.status_bar_thread.setObjectName('status_bar_thread')
        self.status_bar_listener.moveToThread(self.status_bar_thread)
        self.status_bar_thread.start()

        return self.status_bar_progress

    def update_thumbnails(self):
        """
        Updates thumbnails for downloaded and filtered videos.
        :return:
        """
        videos = []
        videos.extend(self.playview_videos)
        videos.extend(self.subfeed_videos)
        self.logger.info(
            "Updating thumbnails for downloaded and filtered videos")
        download_thumbnails_threaded(videos)
        UpdateVideosThread(videos, update_existing=True).start()

    @staticmethod
    def filter_playback_view_videos():
        """
        Applies filters to the PlaybackGridView Videos list based on config.
        :return:
        """
        show_watched = read_config('GridView', 'show_watched')
        show_dismissed = read_config('GridView', 'show_dismissed')
        update_filter = (Video.downloaded, )
        if not show_watched:
            update_filter += (or_(Video.watched == false(),
                                  Video.watched == None), )
        if not show_dismissed:
            update_filter += (~Video.discarded, )
        return update_filter

    def sort_playback_view_videos(self):
        """
        Applies a sort-by rule to the PlaybackGridView videos list.

        update_sort is a tuple of priority sort categories, first element is highest, last is lowest.
        update_sort += operations requires at least two items on rhs.
        :return:
        """
        sort_by_ascending_date = read_config('PlaySort', 'ascending_date')
        sort_by_channel = read_config('PlaySort', 'by_channel')
        self.logger.info(
            "Sorting PlaybackGridView Videos: date = {} | channel = {}".format(
                sort_by_ascending_date, sort_by_channel))
        update_sort = (asc(Video.watch_prio), )
        # Sort-by ascending date
        if sort_by_ascending_date:
            update_sort += (asc(Video.date_downloaded),
                            asc(Video.date_published))
        # Sort-by channel name (implied by default: then descending date)
        if sort_by_channel:
            update_sort += (desc(Video.channel_title), )
        # Sort-by channel name then ascending date  # FIXME: Implement handling both sorts toggled
        if sort_by_channel and sort_by_ascending_date:
            # update_sort += (asc(Video.channel_title),)
            self.logger.debug5("By-Channel|By-date update_sort: {}".format(
                str(update_sort)))
            for t in update_sort:
                self.logger.debug5(t.compile(dialect=postgresql.dialect()))

            # FIXME: workaround for not handling both: disable channel sort if both toggled, and run date sort
            set_config('PlaySort', 'by_channel',
                       format(not read_config('PlaySort', 'by_channel')))
            sort_by_channel = read_config('PlaySort', 'by_channel')
            update_sort += (asc(Video.date_downloaded),
                            asc(Video.date_published))
        # DEFAULT: Sort-by descending date
        else:
            update_sort += (desc(Video.date_downloaded),
                            desc(Video.date_published))

        self.logger.info(
            "Sorted PlaybackGridView Videos: date = {} | channel = {}".format(
                sort_by_ascending_date, sort_by_channel))
        return update_sort
Example #31
0
class MainModel:
    status_bar_progress = None
    status_bar_thread = None
    status_bar_listener = None

    def __init__(self, videos, videos_limit):
        super().__init__()
        self.logger = create_logger(__name__)
        self.videos_limit = videos_limit
        self.downloaded_videos_limit = videos_limit
        self.videos = videos
        self.filtered_videos = []
        self.downloaded_videos = []

        self.download_progress_signals = []

        self.logger.info("Creating listeners and threads")
        self.grid_view_listener = GridViewListener(self)
        self.grid_thread = QThread()
        self.grid_thread.setObjectName('grid_thread')
        self.grid_view_listener.moveToThread(self.grid_thread)
        self.grid_thread.start()

        self.database_listener = DatabaseListener(self)
        self.db_thread = QThread()
        self.db_thread.setObjectName('db_thread')
        self.database_listener.moveToThread(self.db_thread)
        self.db_thread.start()

        self.main_window_listener = MainWindowListener(self)
        self.main_w_thread = QThread()
        self.main_w_thread.setObjectName('main_w_thread')
        self.main_window_listener.moveToThread(self.main_w_thread)
        self.main_w_thread.start()

        self.download_handler = DownloadHandler(self)
        self.download_thread = QThread()
        self.download_thread.setObjectName('download_thread')
        self.download_handler.moveToThread(self.download_thread)
        self.download_thread.start()

        if read_config("Play", "yt_file_path", literal_eval=False):
            self.yt_dir_listener = YtDirListener(self)
            self.yt_dir_thread = QThread()
            self.yt_dir_thread.setObjectName('yt_dir_thread')
            self.yt_dir_listener.moveToThread(self.yt_dir_thread)
            self.yt_dir_thread.start()
        else:
            self.logger.warning(
                "No youtube file path provided, directory listener is disabled"
            )

    def hide_video_item(self, video):
        self.logger.debug("Hiding video item: {}".format(video))
        remove_video(self.filtered_videos, video)
        remove_video(self.downloaded_videos, video)

    def hide_downloaded_video_item(self, video):
        remove_video(self.downloaded_videos, video)

    def db_update_videos(self, filtered=True):
        self.logger.info("Getting newest stored videos from DB")
        # FIXME: only does filtered videos
        if filtered:
            show_downloaded = read_config('SubFeed', 'show_downloaded')
            show_dismissed = read_config('GridView', 'show_dismissed')
            update_filter = ()
            if not show_downloaded:
                update_filter += (~Video.downloaded, )
            if not show_dismissed:
                update_filter += (~Video.discarded, )

            self.filtered_videos = get_newest_stored_videos(
                self.videos_limit, filters=update_filter)
            self.grid_view_listener.hiddenVideosChanged.emit()
        else:
            self.videos = get_newest_stored_videos(self.videos_limit, filtered)

    def remote_update_videos(self,
                             filtered=True,
                             refresh_type=LISTENER_SIGNAL_NORMAL_REFRESH):
        self.logger.info("Reloading and getting newest videos from YouTube")

        if filtered:
            show_downloaded = not read_config('SubFeed', 'show_downloaded')
            show_dismissed = not read_config('GridView', 'show_dismissed')
            self.filtered_videos = refresh_and_get_newest_videos(
                self.videos_limit,
                progress_listener=self.status_bar_listener,
                refresh_type=refresh_type,
                filter_discarded=show_dismissed,
                filter_downloaded=show_downloaded)
            self.grid_view_listener.hiddenVideosChanged.emit()
        else:
            self.videos = refresh_and_get_newest_videos(
                self.videos_limit,
                filtered,
                self.status_bar_listener,
                refresh_type=refresh_type)

    def new_status_bar_progress(self, parent):
        self.status_bar_progress = QProgressBar(parent=parent)
        self.status_bar_listener = ProgressBar(self, self.status_bar_progress)
        self.status_bar_thread = QThread()
        self.status_bar_thread.setObjectName('status_bar_thread')
        self.status_bar_listener.moveToThread(self.status_bar_thread)
        self.status_bar_thread.start()
        return self.status_bar_progress

    def db_update_downloaded_videos(self):

        update_filter = self.config_get_filter_downloaded()
        update_sort = self.config_get_sort_downloaded()
        self.downloaded_videos = get_best_downloaded_videos(
            self.downloaded_videos_limit,
            filters=update_filter,
            sort_method=update_sort)
        self.grid_view_listener.downloadedVideosChanged.emit()

    def update_thumbnails(self):
        videos = []
        videos.extend(self.downloaded_videos)
        videos.extend(self.filtered_videos)
        self.logger.info(
            "Updating thumbnails for downloaded and filtered videos")
        download_thumbnails_threaded(videos)
        UpdateVideosThread(videos, update_existing=True).start()

    def config_get_filter_downloaded(self):
        show_watched = read_config('GridView', 'show_watched')
        show_dismissed = read_config('GridView', 'show_dismissed')
        update_filter = (Video.downloaded, )
        if not show_watched:
            update_filter += (or_(Video.watched == false(),
                                  Video.watched == None), )
        if not show_dismissed:
            update_filter += (~Video.discarded, )
        return update_filter

    def config_get_sort_downloaded(self):
        ascending_date = read_config('PlaySort', 'ascending_date')
        update_sort = (asc(Video.watch_prio), )
        if ascending_date:
            update_sort += (asc(Video.date_downloaded),
                            asc(Video.date_published))
        else:
            update_sort += (desc(Video.date_downloaded),
                            desc(Video.date_published))
        return update_sort
Example #32
0
    def button_import_action(self):
        """
        Slot for buttonImport clicked signal
        """
        self.buttonClose.setEnabled(False)
        headers = True
        if not self.checkHeaders.isChecked():
            headers = False
        self.progressBar.setRange(0, 0)  # set spinning progressbar

        if self.selectedFile:
            # import selected file to contact table
            if self.selectedTable == "contacts":
                worker = Worker(1, self.__app)
                thread = QThread(self)
                thread.setObjectName("contacts_csv")
                self.__threads.append((thread, worker))
                worker.moveToThread(thread)
                worker.sig_status.connect(self.on_status)
                worker.sig_done.connect(self.on_done)
                try:
                    thread.started.connect(
                        worker.import_contacts_csv(self._contacts,
                                                   self.selectedFile,
                                                   header=headers))
                    thread.start()
                except TypeError:
                    pass

            # import selected file to customer table
            if self.selectedTable == "customers":
                worker = Worker(2, self.__app)
                thread = QThread(self)
                thread.setObjectName("customers_csv")
                self.__threads.append((thread, worker))
                worker.moveToThread(thread)
                worker.sig_status.connect(self.on_status)
                worker.sig_done.connect(self.on_done)
                try:
                    thread.started.connect(
                        worker.import_customers_csv(self._customers,
                                                    self.selectedFile,
                                                    header=headers))
                    thread.start()
                except TypeError:
                    pass

            # import selected file to lines table
            if self.selectedTable == "lines":
                worker = Worker(4, self.__app)
                thread = QThread(self)
                thread.setObjectName("orderlines_csv")
                self.__threads.append((thread, worker))
                worker.moveToThread(thread)
                worker.sig_status.connect(self.on_status)
                worker.sig_done.connect(self.on_done)
                try:
                    thread.started.connect(
                        worker.import_orderlines_csv(self._orderlines,
                                                     self.selectedFile,
                                                     header=headers))
                    thread.start()
                except TypeError:
                    pass

            # import selected file to report table
            if self.selectedTable == "reports":
                worker = Worker(5, self.__app)
                thread = QThread(self)
                thread.setObjectName("reports_csv")
                self.__threads.append((thread, worker))
                worker.moveToThread(thread)
                worker.sig_status.connect(self.on_status)
                worker.sig_done.connect(self.on_done)
                try:
                    thread.started.connect(
                        worker.import_reports_csv(
                            self._employees.employee["employee_id"],
                            self._reports,
                            self.selectedFile,
                            header=headers))
                    thread.start()
                except TypeError:
                    pass

            # import selected file to visit table
            if self.selectedTable == "visits":
                worker = Worker(3, self.__app)
                thread = QThread(self)
                thread.setObjectName("visits_csv")
                self.__threads.append((thread, worker))
                worker.moveToThread(thread)
                worker.sig_status.connect(self.on_status)
                worker.sig_done.connect(self.on_done)
                try:
                    thread.started.connect(
                        worker.import_visits_csv(self._visits,
                                                 self.selectedFile,
                                                 header=headers))
                    thread.start()
                except TypeError:
                    pass

            self.selectedFile = ""
            self.txtSelectedFile.clear()
            self.comboImport.removeItem(self.comboImport.currentIndex())
            self.comboImport.setCurrentIndex(0)

        self.buttonImport.setEnabled(
            False)  # disable the button until next file is selected
        self.buttonBrowse.setEnabled(True)  # enable browse button
        self.buttonClose.setEnabled(True)  # enable close button
Example #33
0
class GalleryDialog(QWidget):
	"""
	A window for adding/modifying gallery.
	Pass a list of QModelIndexes to edit their data
	or pass a path to preset path
	"""

	SERIES = pyqtSignal(list)
	SERIES_EDIT = pyqtSignal(list, int)
	#gallery_list = [] # might want to extend this to allow mass gallery adding

	def __init__(self, parent=None, arg=None):
		super().__init__(parent, Qt.Dialog)
		self.setAttribute(Qt.WA_DeleteOnClose)
		self.parent_widget = parent
		log_d('Triggered Gallery Edit/Add Dialog')
		m_l = QVBoxLayout()
		self.main_layout = QVBoxLayout()
		dummy = QWidget(self)
		scroll_area = QScrollArea(self)
		scroll_area.setWidgetResizable(True)
		scroll_area.setFrameStyle(scroll_area.StyledPanel)
		dummy.setLayout(self.main_layout)
		scroll_area.setWidget(dummy)
		m_l.addWidget(scroll_area, 3)

		final_buttons = QHBoxLayout()
		final_buttons.setAlignment(Qt.AlignRight)
		m_l.addLayout(final_buttons)
		self.done = QPushButton("Done")
		self.done.setDefault(True)
		cancel = QPushButton("Cancel")
		final_buttons.addWidget(cancel)
		final_buttons.addWidget(self.done)

		def new_gallery():
			self.setWindowTitle('Add a new gallery')
			self.newUI()
			self.commonUI()
			self.done.clicked.connect(self.accept)
			cancel.clicked.connect(self.reject)

		if arg:
			if isinstance(arg, list):
				self.setWindowTitle('Edit gallery')
				self.position = arg[0].row()
				for index in arg:
					gallery = index.data(Qt.UserRole+1)
					self.commonUI()
					self.setGallery(gallery)
				self.done.clicked.connect(self.accept_edit)
				cancel.clicked.connect(self.reject_edit)
			elif isinstance(arg, str):
				new_gallery()
				self.choose_dir(arg)
		else:
			new_gallery()

		log_d('GalleryDialog: Create UI: successful')
		#TODO: Implement a way to mass add galleries
		#IDEA: Extend dialog in a ScrollArea with more forms...

		self.setLayout(m_l)
		self.resize(500,560)
		frect = self.frameGeometry()
		frect.moveCenter(QDesktopWidget().availableGeometry().center())
		self.move(frect.topLeft())
		#self.setAttribute(Qt.WA_DeleteOnClose)
		self._fetch_inst = fetch.Fetch()
		self._fetch_thread = QThread(self)
		self._fetch_thread.setObjectName("GalleryDialog metadata thread")
		self._fetch_inst.moveToThread(self._fetch_thread)
		self._fetch_thread.started.connect(self._fetch_inst.auto_web_metadata)

	def commonUI(self):
		f_web = QGroupBox("Metadata from the Web")
		f_web.setCheckable(False)
		self.main_layout.addWidget(f_web)
		web_main_layout = QVBoxLayout()
		web_info = misc.ClickedLabel("Which gallery URLs are supported? (hover)", parent=self)
		web_info.setToolTip(app_constants.SUPPORTED_METADATA_URLS)
		web_info.setToolTipDuration(999999999)
		web_main_layout.addWidget(web_info)
		web_layout = QHBoxLayout()
		web_main_layout.addLayout(web_layout)
		f_web.setLayout(web_main_layout)

		f_gallery = QGroupBox("Gallery Info")
		f_gallery.setCheckable(False)
		self.main_layout.addWidget(f_gallery)
		gallery_layout = QFormLayout()
		f_gallery.setLayout(gallery_layout)

		def basic_web(name):
			return QLabel(name), QLineEdit(), QPushButton("Get metadata"), QProgressBar()

		url_lbl, self.url_edit, url_btn, url_prog = basic_web("URL:")
		url_btn.clicked.connect(lambda: self.web_metadata(self.url_edit.text(), url_btn,
											url_prog))
		url_prog.setTextVisible(False)
		url_prog.setMinimum(0)
		url_prog.setMaximum(0)
		web_layout.addWidget(url_lbl, 0, Qt.AlignLeft)
		web_layout.addWidget(self.url_edit, 0)
		web_layout.addWidget(url_btn, 0, Qt.AlignRight)
		web_layout.addWidget(url_prog, 0, Qt.AlignRight)
		self.url_edit.setPlaceholderText("Insert supported gallery URLs or just press the button!")
		url_prog.hide()

		self.title_edit = QLineEdit()
		self.author_edit = QLineEdit()
		author_completer = misc.GCompleter(self, False, True, False)
		author_completer.setCaseSensitivity(Qt.CaseInsensitive)
		self.author_edit.setCompleter(author_completer)
		self.descr_edit = QTextEdit()
		self.descr_edit.setAcceptRichText(True)
		self.lang_box = QComboBox()
		self.lang_box.addItems(app_constants.G_LANGUAGES)
		self.lang_box.addItems(app_constants.G_CUSTOM_LANGUAGES)
		self._find_combobox_match(self.lang_box, app_constants.G_DEF_LANGUAGE, 0)
		tags_l = QVBoxLayout()
		tag_info = misc.ClickedLabel("How do i write namespace & tags? (hover)", parent=self)
		tag_info.setToolTip("Ways to write tags:\n\nNormal tags:\ntag1, tag2, tag3\n\n"+
					  "Namespaced tags:\nns1:tag1, ns1:tag2\n\nNamespaced tags with one or more"+
					  " tags under same namespace:\nns1:[tag1, tag2, tag3], ns2:[tag1, tag2]\n\n"+
					  "Those three ways of writing namespace & tags can be combined freely.\n"+
					  "Tags are seperated by a comma, NOT whitespace.\nNamespaces will be capitalized while tags"+
					  " will be lowercased.")
		tag_info.setToolTipDuration(99999999)
		tags_l.addWidget(tag_info)
		self.tags_edit = misc.CompleterTextEdit()
		self.tags_edit.setCompleter(misc.GCompleter(self, False, False))
		tags_l.addWidget(self.tags_edit, 3)
		self.tags_edit.setPlaceholderText("Press Tab to autocomplete (Ctrl + E to show popup)")
		self.type_box = QComboBox()
		self.type_box.addItems(app_constants.G_TYPES)
		self._find_combobox_match(self.type_box, app_constants.G_DEF_TYPE, 0)
		#self.type_box.currentIndexChanged[int].connect(self.doujin_show)
		#self.doujin_parent = QLineEdit()
		#self.doujin_parent.setVisible(False)
		self.status_box = QComboBox()
		self.status_box.addItems(app_constants.G_STATUS)
		self._find_combobox_match(self.status_box, app_constants.G_DEF_STATUS, 0)
		self.pub_edit = QDateEdit()
		self.pub_edit.setCalendarPopup(True)
		self.pub_edit.setDate(QDate.currentDate())
		self.path_lbl = misc.ClickedLabel("")
		self.path_lbl.setWordWrap(True)
		self.path_lbl.clicked.connect(lambda a: utils.open_path(a, a) if a else None)

		link_layout = QHBoxLayout()
		self.link_lbl = QLabel("")
		self.link_lbl.setWordWrap(True)
		self.link_edit = QLineEdit()
		link_layout.addWidget(self.link_edit)
		link_layout.addWidget(self.link_lbl)
		self.link_edit.hide()
		self.link_btn = QPushButton("Modify")
		self.link_btn.setFixedWidth(50)
		self.link_btn2 = QPushButton("Set")
		self.link_btn2.setFixedWidth(40)
		self.link_btn.clicked.connect(self.link_modify)
		self.link_btn2.clicked.connect(self.link_set)
		link_layout.addWidget(self.link_btn)
		link_layout.addWidget(self.link_btn2)
		self.link_btn2.hide()

		gallery_layout.addRow("Title:", self.title_edit)
		gallery_layout.addRow("Author:", self.author_edit)
		gallery_layout.addRow("Description:", self.descr_edit)
		gallery_layout.addRow("Language:", self.lang_box)
		gallery_layout.addRow("Tags:", tags_l)
		gallery_layout.addRow("Type:", self.type_box)
		gallery_layout.addRow("Status:", self.status_box)
		gallery_layout.addRow("Publication Date:", self.pub_edit)
		gallery_layout.addRow("Path:", self.path_lbl)
		gallery_layout.addRow("Link:", link_layout)

		self.title_edit.setFocus()

	def resizeEvent(self, event):
		self.tags_edit.setFixedHeight(event.size().height()//8)
		self.descr_edit.setFixedHeight(event.size().height()//12.5)
		return super().resizeEvent(event)

	def _find_combobox_match(self, combobox, key, default):
		f_index = combobox.findText(key, Qt.MatchFixedString)
		if f_index != -1:
			combobox.setCurrentIndex(f_index)
		else:
			combobox.setCurrentIndex(default)

	def setGallery(self, gallery):
		"To be used for when editing a gallery"
		self.gallery = gallery

		self.url_edit.setText(gallery.link)

		self.title_edit.setText(gallery.title)
		self.author_edit.setText(gallery.artist)
		self.descr_edit.setText(gallery.info)

		self.tags_edit.setText(utils.tag_to_string(gallery.tags))


		self._find_combobox_match(self.lang_box, gallery.language, 2)
		self._find_combobox_match(self.type_box, gallery.type, 0)
		self._find_combobox_match(self.status_box, gallery.status, 0)

		gallery_pub_date = "{}".format(gallery.pub_date).split(' ')
		try:
			self.gallery_time = datetime.strptime(gallery_pub_date[1], '%H:%M:%S').time()
		except IndexError:
			pass
		qdate_pub_date = QDate.fromString(gallery_pub_date[0], "yyyy-MM-dd")
		self.pub_edit.setDate(qdate_pub_date)

		self.link_lbl.setText(gallery.link)
		self.path_lbl.setText(gallery.path)

	def newUI(self):

		f_local = QGroupBox("Directory/Archive")
		f_local.setCheckable(False)
		self.main_layout.addWidget(f_local)
		local_layout = QHBoxLayout()
		f_local.setLayout(local_layout)

		choose_folder = QPushButton("From Directory")
		choose_folder.clicked.connect(lambda: self.choose_dir('f'))
		local_layout.addWidget(choose_folder)

		choose_archive = QPushButton("From Archive")
		choose_archive.clicked.connect(lambda: self.choose_dir('a'))
		local_layout.addWidget(choose_archive)

		self.file_exists_lbl = QLabel()
		local_layout.addWidget(self.file_exists_lbl)
		self.file_exists_lbl.hide()

	def choose_dir(self, mode):
		"""
		Pass which mode to open the folder explorer in:
		'f': directory
		'a': files
		Or pass a predefined path
		"""
		self.done.show()
		self.file_exists_lbl.hide()
		if mode == 'a':
			name = QFileDialog.getOpenFileName(self, 'Choose archive',
											  filter=utils.FILE_FILTER)
			name = name[0]
		elif mode == 'f':
			name = QFileDialog.getExistingDirectory(self, 'Choose folder')
		elif mode:
			if os.path.exists(mode):
				name = mode
			else:
				return None
		if not name:
			return
		head, tail = os.path.split(name)
		name = os.path.join(head, tail)
		parsed = utils.title_parser(tail)
		self.title_edit.setText(parsed['title'])
		self.author_edit.setText(parsed['artist'])
		self.path_lbl.setText(name)
		if not parsed['language']:
			parsed['language'] = app_constants.G_DEF_LANGUAGE
		l_i = self.lang_box.findText(parsed['language'])
		if l_i != -1:
			self.lang_box.setCurrentIndex(l_i)
		if gallerydb.GalleryDB.check_exists(name):
			self.file_exists_lbl.setText('<font color="red">Gallery already exists.</font>')
			self.file_exists_lbl.show()
		# check galleries
		gs = 1
		if name.endswith(utils.ARCHIVE_FILES):
			gs = len(utils.check_archive(name))
		elif os.path.isdir(name):
			g_dirs, g_archs = utils.recursive_gallery_check(name)
			gs = len(g_dirs) + len(g_archs)
		if gs == 0:
			self.file_exists_lbl.setText('<font color="red">Invalid gallery source.</font>')
			self.file_exists_lbl.show()
			self.done.hide()
		if app_constants.SUBFOLDER_AS_GALLERY:
			if gs > 1:
				self.file_exists_lbl.setText('<font color="red">More than one galleries detected in source! Use other methods to add.</font>')
				self.file_exists_lbl.show()
				self.done.hide()

	def check(self):
		if len(self.title_edit.text()) is 0:
			self.title_edit.setFocus()
			self.title_edit.setStyleSheet("border-style:outset;border-width:2px;border-color:red;")
			return False
		elif len(self.author_edit.text()) is 0:
			self.author_edit.setText("Unknown")

		if len(self.path_lbl.text()) == 0 or self.path_lbl.text() == 'No path specified':
			self.path_lbl.setStyleSheet("color:red")
			self.path_lbl.setText('No path specified')
			return False

		return True

	def set_chapters(self, gallery_object, add_to_model=True):
		path = gallery_object.path
		chap_container = gallerydb.ChaptersContainer(gallery_object)
		metafile = utils.GMetafile()
		try:
			log_d('Listing dir...')
			con = scandir.scandir(path) # list all folders in gallery dir
			log_i('Gallery source is a directory')
			log_d('Sorting')
			chapters = sorted([sub.path for sub in con if sub.is_dir() or sub.name.endswith(utils.ARCHIVE_FILES)]) #subfolders
			# if gallery has chapters divided into sub folders
			if len(chapters) != 0:
				log_d('Chapters divided in folders..')
				for ch in chapters:
					chap = chap_container.create_chapter()
					chap.title = utils.title_parser(ch)['title']
					chap.path = os.path.join(path, ch)
					metafile.update(utils.GMetafile(chap.path))
					chap.pages = len(list(scandir.scandir(chap.path)))

			else: #else assume that all images are in gallery folder
				chap = chap_container.create_chapter()
				chap.title = utils.title_parser(os.path.split(path)[1])['title']
				chap.path = path
				metafile.update(utils.GMetafile(path))
				chap.pages = len(list(scandir.scandir(path)))

		except NotADirectoryError:
			if path.endswith(utils.ARCHIVE_FILES):
				gallery_object.is_archive = 1
				log_i("Gallery source is an archive")
				archive_g = sorted(utils.check_archive(path))
				for g in archive_g:
					chap = chap_container.create_chapter()
					chap.path = g
					chap.in_archive = 1
					metafile.update(utils.GMetafile(g, path))
					arch = utils.ArchiveFile(path)
					chap.pages = len(arch.dir_contents(g))
					arch.close()

		metafile.apply_gallery(gallery_object)
		if add_to_model:
			self.SERIES.emit([gallery_object])
			log_d('Sent gallery to model')
		

	def reject(self):
		if self.check():
			msgbox = QMessageBox()
			msgbox.setText("<font color='red'><b>Noo oniichan! You were about to add a new gallery.</b></font>")
			msgbox.setInformativeText("Do you really want to discard?")
			msgbox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
			msgbox.setDefaultButton(QMessageBox.No)
			if msgbox.exec() == QMessageBox.Yes:
				self.close()
		else:
			self.close()

	def web_metadata(self, url, btn_widget, pgr_widget):
		self.link_lbl.setText(url)
		btn_widget.hide()
		pgr_widget.show()

		def status(stat):
			def do_hide():
				try:
					pgr_widget.hide()
					btn_widget.show()
				except RuntimeError:
					pass

			if stat:
				do_hide()
			else:
				danger = """QProgressBar::chunk {
					background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0,stop: 0 #FF0350,stop: 0.4999 #FF0020,stop: 0.5 #FF0019,stop: 1 #FF0000 );
					border-bottom-right-radius: 5px;
					border-bottom-left-radius: 5px;
					border: .px solid black;}"""
				pgr_widget.setStyleSheet(danger)
				QTimer.singleShot(3000, do_hide)

		def gallery_picker(gallery, title_url_list, q):
			self.parent_widget._web_metadata_picker(gallery, title_url_list, q, self)

		try:
			dummy_gallery = self.make_gallery(self.gallery)
		except AttributeError:
			dummy_gallery = self.make_gallery(gallerydb.Gallery(), False)
		if not dummy_gallery:
			status(False)
			return None

		dummy_gallery._g_dialog_url = url
		self._fetch_inst.galleries = [dummy_gallery]
		self._disconnect()
		self._fetch_inst.GALLERY_PICKER.connect(gallery_picker)
		self._fetch_inst.GALLERY_EMITTER.connect(self.set_web_metadata)
		self._fetch_inst.FINISHED.connect(status)
		self._fetch_thread.start()
			
	def set_web_metadata(self, metadata):
		assert isinstance(metadata, gallerydb.Gallery)
		self.link_lbl.setText(metadata.link)
		self.title_edit.setText(metadata.title)
		self.author_edit.setText(metadata.artist)
		tags = ""
		lang = ['English', 'Japanese']
		self._find_combobox_match(self.lang_box, metadata.language, 2)
		self.tags_edit.setText(utils.tag_to_string(metadata.tags))
		pub_string = "{}".format(metadata.pub_date)
		pub_date = QDate.fromString(pub_string.split()[0], "yyyy-MM-dd")
		self.pub_edit.setDate(pub_date)
		self._find_combobox_match(self.type_box, metadata.type, 0)

	def make_gallery(self, new_gallery, add_to_model=True, new=False):
		if self.check():
			new_gallery.title = self.title_edit.text()
			log_d('Adding gallery title')
			new_gallery.artist = self.author_edit.text()
			log_d('Adding gallery artist')
			log_d('Adding gallery path')
			if new and app_constants.MOVE_IMPORTED_GALLERIES:
				app_constants.OVERRIDE_MONITOR = True
				new_gallery.path = utils.move_files(self.path_lbl.text())
			else:
				new_gallery.path = self.path_lbl.text()
			new_gallery.info = self.descr_edit.toPlainText()
			log_d('Adding gallery descr')
			new_gallery.type = self.type_box.currentText()
			log_d('Adding gallery type')
			new_gallery.language = self.lang_box.currentText()
			log_d('Adding gallery lang')
			new_gallery.status = self.status_box.currentText()
			log_d('Adding gallery status')
			new_gallery.tags = utils.tag_to_dict(self.tags_edit.toPlainText())
			log_d('Adding gallery: tagging to dict')
			qpub_d = self.pub_edit.date().toString("ddMMyyyy")
			dpub_d = datetime.strptime(qpub_d, "%d%m%Y").date()
			try:
				d_t = self.gallery_time
			except AttributeError:
				d_t = datetime.now().time().replace(microsecond=0)
			dpub_d = datetime.combine(dpub_d, d_t)
			new_gallery.pub_date = dpub_d
			log_d('Adding gallery pub date')
			new_gallery.link = self.link_lbl.text()
			log_d('Adding gallery link')
			if not new_gallery.chapters:
				log_d('Starting chapters')
				thread = threading.Thread(target=self.set_chapters, args=(new_gallery,add_to_model), daemon=True)
				thread.start()
				thread.join()
				log_d('Finished chapters')
			return new_gallery


	def link_set(self):
		t = self.link_edit.text()
		self.link_edit.hide()
		self.link_lbl.show()
		self.link_lbl.setText(t)
		self.link_btn2.hide()
		self.link_btn.show() 

	def link_modify(self):
		t = self.link_lbl.text()
		self.link_lbl.hide()
		self.link_edit.show()
		self.link_edit.setText(t)
		self.link_btn.hide()
		self.link_btn2.show()

	def _disconnect(self):
		try:
			self._fetch_inst.GALLERY_PICKER.disconnect()
			self._fetch_inst.GALLERY_EMITTER.disconnect()
			self._fetch_inst.FINISHED.disconnect()
		except TypeError:
			pass

	def delayed_close(self):
		if self._fetch_thread.isRunning():
			self._fetch_thread.finished.connect(self.close)
			self.hide()
		else:
			self.close()

	def accept(self):
		new_gallery = self.make_gallery(gallerydb.Gallery(), new=True)

		if new_gallery:
			self.delayed_close()

	def accept_edit(self):
		new_gallery = self.make_gallery(self.gallery)
		#for ser in self.gallery:
		if new_gallery:
			self.SERIES_EDIT.emit([new_gallery], self.position)
			self.delayed_close()

	def reject_edit(self):
		self.delayed_close()
Example #34
0
    def gallery_populate(self, path, validate=False):
        "Scans the given path for gallery to add into the DB"
        if len(path) is not 0:
            data_thread = QThread(self)
            data_thread.setObjectName("General gallery populate")
            loading = misc.Loading(self)
            if not loading.ON:
                misc.Loading.ON = True
                fetch_instance = fetch.Fetch()
                fetch_instance.series_path = path
                loading.show()

                def finished(status):
                    def hide_loading():
                        loading.hide()

                    if status:
                        if len(status) != 0:

                            def add_gallery(gallery_list):
                                def append_to_model(x):
                                    self.manga_list_view.gallery_model.insertRows(x, None, len(x))

                                class A(QObject):
                                    done = pyqtSignal()
                                    prog = pyqtSignal(int)

                                    def __init__(self, obj, parent=None):
                                        super().__init__(parent)
                                        self.obj = obj
                                        self.galleries = []

                                    def add_to_db(self):
                                        gui_constants.NOTIF_BAR.begin_show()
                                        gui_constants.NOTIF_BAR.add_text("Populating database...")
                                        for y, x in enumerate(self.obj):
                                            gui_constants.NOTIF_BAR.add_text(
                                                "Populating database {}/{}".format(y + 1, len(self.obj))
                                            )
                                            gallerydb.add_method_queue(gallerydb.GalleryDB.add_gallery_return, False, x)
                                            self.galleries.append(x)
                                            y += 1
                                            self.prog.emit(y)
                                        append_to_model(self.galleries)
                                        gui_constants.NOTIF_BAR.end_show()
                                        self.done.emit()

                                loading.progress.setMaximum(len(gallery_list))
                                a_instance = A(gallery_list)
                                thread = QThread(self)
                                thread.setObjectName("Database populate")

                                def loading_show():
                                    loading.setText("Populating database.\nPlease wait...")
                                    loading.show()

                                def loading_hide():
                                    loading.close()
                                    self.manga_list_view.gallery_model.ROWCOUNT_CHANGE.emit()

                                def del_later():
                                    try:
                                        a_instance.deleteLater()
                                    except NameError:
                                        pass

                                a_instance.moveToThread(thread)
                                a_instance.prog.connect(loading.progress.setValue)
                                thread.started.connect(loading_show)
                                thread.started.connect(a_instance.add_to_db)
                                a_instance.done.connect(loading_hide)
                                a_instance.done.connect(del_later)
                                thread.finished.connect(thread.deleteLater)
                                thread.start()

                            data_thread.quit
                            hide_loading()
                            log_i("Populating DB from gallery folder: OK")
                            if validate:
                                gallery_list = misc.GalleryListView(self)
                                gallery_list.SERIES.connect(add_gallery)
                                for ser in status:
                                    gallery_list.add_gallery(ser, os.path.split(ser.path)[1])
                                    # self.manga_list_view.gallery_model.populate_data()
                                gallery_list.show()
                            else:
                                add_gallery(status)
                            misc.Loading.ON = False
                        else:
                            log_d("No new gallery was found")
                            loading.setText("No new gallery found")
                            data_thread.quit
                            misc.Loading.ON = False

                    else:
                        log_e("Populating DB from gallery folder: Nothing was added!")
                        loading.setText("<font color=red>Nothing was added. Check happypanda_log for details..</font>")
                        loading.progress.setStyleSheet("background-color:red;")
                        data_thread.quit
                        QTimer.singleShot(10000, loading.close)

                def fetch_deleteLater():
                    try:
                        fetch_instance.deleteLater
                    except NameError:
                        pass

                def a_progress(prog):
                    loading.progress.setValue(prog)
                    loading.setText("Searching for galleries...")

                fetch_instance.moveToThread(data_thread)
                fetch_instance.DATA_COUNT.connect(loading.progress.setMaximum)
                fetch_instance.PROGRESS.connect(a_progress)
                data_thread.started.connect(fetch_instance.local)
                fetch_instance.FINISHED.connect(finished)
                fetch_instance.FINISHED.connect(fetch_deleteLater)
                data_thread.finished.connect(data_thread.deleteLater)
                data_thread.start()
                log_i("Populating DB from gallery folder")
Example #35
0
	def gallery_populate(self, path, validate=False):
		"Scans the given path for gallery to add into the DB"
		if len(path) is not 0:
			data_thread = QThread(self)
			data_thread.setObjectName('General gallery populate')
			loading = misc.Loading(self)
			self.g_populate_inst = fetch.Fetch()
			self.g_populate_inst.series_path = path
			loading.show()

			def finished(status):
				def hide_loading():
					loading.hide()
				if status:
					if len(status) != 0:
						def add_gallery(gallery_list):
							def append_to_model(x):
								self.manga_list_view.sort_model.insertRows(x, None, len(x))
								self.manga_list_view.sort_model.init_search(
									self.manga_list_view.sort_model.current_term)

							class A(QObject):
								done = pyqtSignal()
								prog = pyqtSignal(int)
								def __init__(self, obj, parent=None):
									super().__init__(parent)
									self.obj = obj
									self.galleries = []

								def add_to_db(self):
									for y, x in enumerate(self.obj):
										gallerydb.add_method_queue(
											gallerydb.GalleryDB.add_gallery_return, False, x)
										self.galleries.append(x)
										y += 1
										self.prog.emit(y)
									append_to_model(self.galleries)
									self.done.emit()
							loading.progress.setMaximum(len(gallery_list))
							self.a_instance = A(gallery_list)
							thread = QThread(self)
							thread.setObjectName('Database populate')
							def loading_show(numb):
								if loading.isHidden():
									loading.show()
								loading.setText('Populating database ({}/{})\nPlease wait...'.format(
									numb, loading.progress.maximum()))
								loading.progress.setValue(numb)
								loading.show()

							def loading_hide():
								loading.close()
								self.manga_list_view.gallery_model.ROWCOUNT_CHANGE.emit()

							self.a_instance.moveToThread(thread)
							self.a_instance.prog.connect(loading_show)
							thread.started.connect(self.a_instance.add_to_db)
							self.a_instance.done.connect(loading_hide)
							self.a_instance.done.connect(self.a_instance.deleteLater)
							#a_instance.add_to_db()
							thread.finished.connect(thread.deleteLater)
							thread.start()
						#data_thread.quit
						hide_loading()
						log_i('Populating DB from gallery folder: OK')
						if validate:
							gallery_list = misc.GalleryListView(self)
							gallery_list.SERIES.connect(add_gallery)
							for ser in status:
								if ser.is_archive and app_constants.SUBFOLDER_AS_GALLERY:
									p = os.path.split(ser.path)[1]
									if ser.chapters[0].path:
										pt_in_arch = os.path.split(ser.path_in_archive)
										pt_in_arch = pt_in_arch[1] or pt_in_arch[0]
										text = '{}: {}'.format(p, pt_in_arch)
									else:
										text = p
									gallery_list.add_gallery(ser, text)
								else:
									gallery_list.add_gallery(ser, os.path.split(ser.path)[1])
							#self.manga_list_view.gallery_model.populate_data()
							gallery_list.update_count()
							gallery_list.show()
						else:
							add_gallery(status)
					else:
						log_d('No new gallery was found')
						loading.setText("No new gallery found")
						#data_thread.quit

				else:
					log_e('Populating DB from gallery folder: Nothing was added!')
					loading.setText("<font color=red>Nothing was added. Check happypanda_log for details..</font>")
					loading.progress.setStyleSheet("background-color:red;")
					data_thread.quit
					QTimer.singleShot(8000, loading.close)

			def skipped_gs(s_list):
				"Skipped galleries"
				msg_box = QMessageBox(self)
				msg_box.setIcon(QMessageBox.Question)
				msg_box.setText('Do you want to view skipped paths?')
				msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
				msg_box.setDefaultButton(QMessageBox.No)
				if msg_box.exec() == QMessageBox.Yes:
					list_wid = QTableWidget(self)
					list_wid.setAttribute(Qt.WA_DeleteOnClose)
					list_wid.setRowCount(len(s_list))
					list_wid.setColumnCount(2)
					list_wid.setAlternatingRowColors(True)
					list_wid.setEditTriggers(list_wid.NoEditTriggers)
					list_wid.setHorizontalHeaderLabels(['Reason', 'Path'])
					list_wid.setSelectionBehavior(list_wid.SelectRows)
					list_wid.setSelectionMode(list_wid.SingleSelection)
					list_wid.setSortingEnabled(True)
					list_wid.verticalHeader().hide()
					list_wid.setAutoScroll(False)
					for x, g in enumerate(s_list):
						list_wid.setItem(x, 0, QTableWidgetItem(g[1]))
						list_wid.setItem(x, 1, QTableWidgetItem(g[0]))
					list_wid.resizeColumnsToContents()
					list_wid.setWindowTitle('{} skipped paths'.format(len(s_list)))
					list_wid.setWindowFlags(Qt.Window)
					list_wid.resize(900,400)

					list_wid.doubleClicked.connect(lambda i: utils.open_path(
						list_wid.item(i.row(), 1).text(), list_wid.item(i.row(), 1).text()))

					list_wid.show()

			def a_progress(prog):
				loading.progress.setValue(prog)
				loading.setText("Preparing galleries...")

			self.g_populate_inst.moveToThread(data_thread)
			self.g_populate_inst.DATA_COUNT.connect(loading.progress.setMaximum)
			self.g_populate_inst.PROGRESS.connect(a_progress)
			self.g_populate_inst.FINISHED.connect(finished)
			self.g_populate_inst.FINISHED.connect(self.g_populate_inst.deleteLater)
			self.g_populate_inst.SKIPPED.connect(skipped_gs)
			data_thread.finished.connect(data_thread.deleteLater)
			data_thread.started.connect(self.g_populate_inst.local)
			data_thread.start()
			#.g_populate_inst.local()
			log_i('Populating DB from directory/archive')
Example #36
0
class ProgressWidget(QWidget):
    '''
    The ProgressWidget initializes the process and displays information about it.

    Args:
        progress_icon (str): the file path of the loading icon gif

    Attributes:
        thread (QThread): the thread used by the widget to delegate the processor to
        file_name_label (QLabel): displays the file name that was selected
        averaging_label (QLabel): displays the averaging duration number and unit selected
        start_label (QLabel): displays the start time selected (or N/A)
        end_label (QLabel): displays the end time selected (or N/A)
        movie (QMovie): runs the loading icon gif

        All labels are defined as attributes of the class so they can be modified at the start of each process.
    '''
    def __init__(self, progress_icon):
        super().__init__()
        # delegation thread used to run the process off of the main event loop
        self.thread = QThread()
        self.thread.setObjectName("Data Processor Thread")

        # define information labels
        title_label = QLabel("Analysis in progress...")
        title_label.setObjectName("title")
        self.file_name_label = QLabel("")
        self.file_name_label.setObjectName("details")
        self.averaging_label = QLabel("")
        self.averaging_label.setObjectName("details")
        self.start_label = QLabel("")
        self.start_label.setObjectName("details")
        self.end_label = QLabel("")
        self.end_label.setObjectName("details")

        # define movie and container label
        self.movie = QMovie(progress_icon)
        loading_label = QLabel()
        loading_label.setAlignment(Qt.AlignCenter)
        loading_label.setMovie(self.movie)

        # define layout
        layout = QVBoxLayout()
        layout.addWidget(title_label)
        layout.addWidget(self.file_name_label)
        layout.addWidget(self.averaging_label)
        layout.addWidget(self.start_label)
        layout.addWidget(self.end_label)
        layout.addWidget(loading_label)
        self.setLayout(layout)

    # takes in all parameters related to the process and spawns a worker to run it
    def begin_progress(self, file_name, file_path, output_path, ad_num, ad_unit, start, end, pdf_header):
        # populate labels with inputted parameters
        self.file_name_label.setText("File Name: " + file_name)
        self.averaging_label.setText("Averaging Duration: " + str(ad_num) + " " + ad_unit)
        if start != None and end != None:
            self.start_label.setText("Start Time: " + start.toString())
            self.end_label.setText("End Time: " + end.toString())
        else:
            self.start_label.setText("Start Time: N/A")
            self.end_label.setText("End Time: N/A")

        # start icon gif and allow main event loop to process it
        self.movie.start()
        qApp.processEvents()

        # define a worker object, move it to a new thread, and begin the processor work
        self.processor = Processor(file_path, output_path, ad_num, ad_unit, start, end, pdf_header)
        self.processor.moveToThread(self.thread)
        self.thread.started.connect(self.processor.work)
        self.processor.result_signal.connect(self.finish)
        self.thread.start()

    # ends the process and makes a callback to the main window with the result
    @pyqtSlot(str, bool)
    def finish(self, output, error):
        # stop and disconnect the thread from the processor object
        self.thread.quit()
        self.thread.wait()
        self.thread.disconnect()
        self.movie.stop()
        self.parentWidget().parentWidget().complete_analysis(output, error)
Example #37
0
class BlenderListView(QListView):
    """ A ListView QWidget used on the animated title window """

    # Our signals
    start_render = pyqtSignal(str, str, int)
    cancel_render = pyqtSignal()

    def currentChanged(self, selected, deselected):
        # Get selected item
        self.selected = selected
        self.deselected = deselected

        # Get translation object
        _ = self.app._tr

        self.win.clear_effect_controls()
        animation = self.get_animation_details()
        self.selected_template = animation.get("service")

        # In newer versions of Qt, setting the model invokes the currentChanged signal,
        # but the selection is -1. So, just do nothing here.
        if not self.selected_template:
            return

        # Assign a new unique id for each template selected
        self.generateUniqueFolder()

        # Loop through params
        for param in animation.get("params", []):
            log.debug('Using parameter %s: %s' %
                      (param["name"], param["title"]))

            # Is Hidden Param?
            if param["name"] in ["start_frame", "end_frame"]:
                # add value to dictionary
                self.params[param["name"]] = int(param["default"])
                # skip to next param without rendering a control
                continue

            widget = None
            label = QLabel()
            label.setText(_(param["title"]))
            label.setToolTip(_(param["title"]))

            if param["type"] == "spinner":
                # add value to dictionary
                self.params[param["name"]] = float(param["default"])

                # create spinner
                widget = QDoubleSpinBox()
                widget.setMinimum(float(param["min"]))
                widget.setMaximum(float(param["max"]))
                widget.setValue(float(param["default"]))
                widget.setSingleStep(0.01)
                widget.setToolTip(param["title"])
                widget.valueChanged.connect(
                    functools.partial(self.spinner_value_changed, param))

            elif param["type"] == "text":
                # add value to dictionary
                self.params[param["name"]] = _(param["default"])

                # create spinner
                widget = QLineEdit()
                widget.setText(_(param["default"]))
                widget.textChanged.connect(
                    functools.partial(self.text_value_changed, widget, param))

            elif param["type"] == "multiline":
                # add value to dictionary
                self.params[param["name"]] = _(param["default"])

                # create spinner
                widget = QPlainTextEdit()
                widget.setPlainText(_(param["default"]).replace("\\n", "\n"))
                widget.textChanged.connect(
                    functools.partial(self.text_value_changed, widget, param))

            elif param["type"] == "dropdown":
                # add value to dictionary
                self.params[param["name"]] = param["default"]

                # create spinner
                widget = QComboBox()
                widget.currentIndexChanged.connect(
                    functools.partial(self.dropdown_index_changed, widget,
                                      param))

                # Add values to dropdown
                if "project_files" in param["name"]:
                    # override files dropdown
                    param["values"] = {}
                    for file in File.filter():
                        if file.data["media_type"] not in ("image", "video"):
                            continue

                        fileName = os.path.basename(file.data["path"])
                        fileExtension = os.path.splitext(fileName)[1]

                        if fileExtension.lower() in (".svg"):
                            continue

                        param["values"][fileName] = "|".join(
                            (file.data["path"], str(file.data["height"]),
                             str(file.data["width"]), file.data["media_type"],
                             str(file.data["fps"]["num"] /
                                 file.data["fps"]["den"])))

                # Add normal values
                for i, (k, v) in enumerate(sorted(param["values"].items())):
                    # add dropdown item
                    widget.addItem(_(k), v)

                    # select dropdown (if default)
                    if v == param["default"]:
                        widget.setCurrentIndex(i)

                if not param["values"]:
                    widget.addItem(_("No Files Found"), "")
                    widget.setEnabled(False)

            elif param["type"] == "color":
                # add value to dictionary
                color = QColor(param["default"])
                self.params[param["name"]] = [
                    color.redF(), color.greenF(),
                    color.blueF()
                ]
                if "diffuse_color" in param.get("name"):
                    self.params[param["name"]].append(color.alphaF())
                widget = QPushButton()
                widget.setText("")
                widget.setStyleSheet("background-color: {}".format(
                    param["default"]))
                widget.clicked.connect(
                    functools.partial(self.color_button_clicked, widget,
                                      param))

            # Add Label and Widget to the form
            if (widget and label):
                self.win.settingsContainer.layout().addRow(label, widget)
            elif (label):
                self.win.settingsContainer.layout().addRow(label)

        self.end_processing()
        self.init_slider_values()

    def spinner_value_changed(self, param, value):
        self.params[param["name"]] = value
        log.info('Animation param %s set to %s' % (param["name"], value))

    def text_value_changed(self, widget, param, value=None):
        try:
            # Attempt to load value from QPlainTextEdit (i.e. multi-line)
            if not value:
                value = widget.toPlainText()
        except Exception:
            log.debug('Failed to read plain text value from widget')
            return
        self.params[param["name"]] = value
        # XXX: This will log every individual KEYPRESS in the text field.
        # log.info('Animation param %s set to %s' % (param["name"], value))

    def dropdown_index_changed(self, widget, param, index):
        value = widget.itemData(index)
        self.params[param["name"]] = value
        log.info('Animation param %s set to %s' % (param["name"], value))
        if param["name"] == "length_multiplier":
            self.init_slider_values()

    def color_button_clicked(self, widget, param, index):
        # Get translation object
        _ = get_app()._tr

        color_value = self.params[param["name"]]
        currentColor = QColor("#FFFFFF")
        if len(color_value) >= 3:
            currentColor.setRgbF(color_value[0], color_value[1],
                                 color_value[2])
        # Store our arguments for the callback to pick up again
        self._color_scratchpad = (widget, param)
        ColorPicker(currentColor,
                    callback=self.color_selected,
                    parent=self.win)

    @pyqtSlot(QColor)
    def color_selected(self, newColor):
        """Callback when the user chooses a color in the dialog"""
        if not self._color_scratchpad:
            log.warning("ColorPicker callback called without parameter to set")
            return
        (widget, param) = self._color_scratchpad
        if not newColor or not newColor.isValid():
            return
        widget.setStyleSheet("background-color: {}".format(newColor.name()))
        self.params[param["name"]] = [
            newColor.redF(),
            newColor.greenF(),
            newColor.blueF()
        ]
        if "diffuse_color" in param.get("name"):
            self.params[param["name"]].append(newColor.alphaF())
        log.info('Animation param %s set to %s', param["name"],
                 newColor.name())

    def generateUniqueFolder(self):
        """ Generate a new, unique folder name to contain Blender frames """

        # Assign a new unique id for each template selected
        self.unique_folder_name = str(self.app.project.generate_id())

        # Create a folder (if it does not exist)
        if not os.path.exists(
                os.path.join(info.BLENDER_PATH, self.unique_folder_name)):
            os.mkdir(os.path.join(info.BLENDER_PATH, self.unique_folder_name))

    def processing_mode(self, cursor=True):
        """ Disable all controls on interface """

        # Store keyboard-focused widget
        self.focus_owner = self.win.focusWidget()

        self.win.btnRefresh.setEnabled(False)
        self.win.sliderPreview.setEnabled(False)
        self.win.btnRender.setEnabled(False)

        # Show 'Wait' cursor
        if cursor:
            QApplication.setOverrideCursor(Qt.WaitCursor)

    @pyqtSlot()
    def end_processing(self):
        """ Enable all controls on interface """
        self.win.btnRefresh.setEnabled(True)
        self.win.sliderPreview.setEnabled(True)
        self.win.btnRender.setEnabled(True)
        self.win.statusContainer.hide()

        # Restore normal cursor and keyboard focus
        QApplication.restoreOverrideCursor()
        if self.focus_owner:
            self.focus_owner.setFocus()

    def init_slider_values(self):
        """ Init the slider and preview frame label to the currently selected animation """

        # Get current preview slider frame
        length = int(self.params.get("end_frame", 1)) * int(
            self.params.get("length_multiplier", 1))

        # Update the preview slider
        middle_frame = int(length / 2)

        self.win.sliderPreview.setMinimum(self.params.get("start_frame", 1))
        self.win.sliderPreview.setMaximum(length)
        self.win.sliderPreview.setValue(middle_frame)

        # Trigger a refresh of the preview
        self.preview_timer.start()

    @pyqtSlot()
    def render_finished(self):
        # Don't try to capture image sequences for preview frames
        if not self.final_render:
            return

        # Compose image sequence data
        seq_params = {
            "folder_path": os.path.join(info.BLENDER_PATH,
                                        self.unique_folder_name),
            "base_name": self.params["file_name"],
            "fixlen": True,
            "digits": 4,
            "extension": "png"
        }

        filename = "{}%04d.png".format(seq_params["base_name"])
        final_path = os.path.join(seq_params["folder_path"], filename)
        log.info(
            'RENDER FINISHED! Adding to project files: {}'.format(filename))

        # Add to project files
        get_app().window.files_model.add_files(final_path, seq_params)

        # We're done here
        self.win.close()

    @pyqtSlot(str)
    def render_stage(self, stage=None):
        _ = get_app()._tr
        self.win.frameProgress.setRange(0, 0)
        self.win.frameStatus.setText(_("Generating"))
        log.debug("Set Blender progress to Generating step")

    @pyqtSlot(int, int)
    def render_progress(self, step_value, step_max):
        _ = get_app()._tr
        self.win.frameProgress.setRange(0, step_max)
        self.win.frameProgress.setValue(step_value)
        self.win.frameStatus.setText(_("Rendering"))
        log.debug("set Blender progress to Rendering step, %d of %d complete",
                  step_value, step_max)

    @pyqtSlot(int)
    def render_saved(self, frame=None):
        _ = get_app()._tr
        self.win.frameProgress.setValue(self.win.frameProgress.maximum() + 1)
        self.win.frameStatus.setText(_("Saved"))
        log.debug("Set Blender progress to Saved step")

    @pyqtSlot()
    def render_initialize(self):
        _ = get_app()._tr
        self.win.frameProgress.setRange(0, 0)
        self.win.frameStatus.setText(_("Initializing"))
        self.win.statusContainer.show()
        log.debug("Set Blender progress to Initializing step")

    @pyqtSlot(int)
    def update_progress_bar(self, current_frame):

        # update label and preview slider
        self.win.sliderPreview.setValue(current_frame)

        length = int(self.params.get("end_frame", 1)) * int(
            self.params.get("length_multiplier", 1))
        self.win.lblFrame.setText("{}/{}".format(current_frame, length))

    @pyqtSlot(int)
    def sliderPreview_valueChanged(self, new_value):
        """Get new value of preview slider, and start timer to Render frame"""
        if self.win.sliderPreview.isEnabled():
            self.preview_timer.start()

        # Update preview label
        length = int(self.params.get("end_frame", 1)) * int(
            self.params.get("length_multiplier", 1))
        self.win.lblFrame.setText("{}/{}".format(new_value, length))

    def preview_timer_onTimeout(self):
        """Timer is ready to Render frame"""
        # Update preview label
        preview_frame_number = self.win.sliderPreview.value()
        log.info('Previewing frame %s' % preview_frame_number)

        # Render current frame
        self.Render(preview_frame_number)

    def get_animation_details(self):
        """ Build a dictionary of all animation settings and properties from XML """

        if not self.selected:
            return {}
        elif self.selected and self.selected.row() == -1:
            return {}

        # Get all selected rows items
        ItemRow = self.blender_model.model.itemFromIndex(self.selected).row()
        animation_title = self.blender_model.model.item(ItemRow, 1).text()
        xml_path = self.blender_model.model.item(ItemRow, 2).text()
        service = self.blender_model.model.item(ItemRow, 3).text()

        # load xml effect file
        xmldoc = xml.parse(xml_path)

        # Get list of params
        animation = {
            "title": animation_title,
            "path": xml_path,
            "service": service,
            "params": []
        }

        # Loop through params
        for param in xmldoc.getElementsByTagName("param"):
            # Set up item dict, "default" key is required
            param_item = {"default": ""}

            # Get details of param
            for att in ["title", "description", "name", "type"]:
                if param.attributes[att]:
                    param_item[att] = param.attributes[att].value

            for tag in ["min", "max", "step", "digits", "default"]:
                for p in param.getElementsByTagName(tag):
                    if p.childNodes:
                        param_item[tag] = p.firstChild.data

            try:
                # Build values dict from list of (name, num) tuples
                param_item["values"] = dict([
                    (p.attributes["name"].value, p.attributes["num"].value)
                    for p in param.getElementsByTagName("value")
                    if ("name" in p.attributes and "num" in p.attributes)
                ])
            except (TypeError, AttributeError) as ex:
                log.warn("XML parser: %s", ex)
                pass

            # Append param object to list
            animation["params"].append(param_item)

        # Free up XML document memory
        xmldoc.unlink()

        # Return animation dictionary
        return animation

    def mousePressEvent(self, event):
        # Ignore event, propagate to parent
        event.ignore()
        super().mousePressEvent(event)

    def refresh_view(self):
        self.blender_model.update_model()

    def get_project_params(self, is_preview=True):
        """ Return a dictionary of project related settings, needed by the Blender python script. """

        project = self.app.project
        project_params = {}

        # Append some project settings
        fps = project.get("fps")
        project_params["fps"] = fps["num"]
        if fps["den"] != 1:
            project_params["fps_base"] = fps["den"]

        project_params["resolution_x"] = project.get("width")
        project_params["resolution_y"] = project.get("height")

        if is_preview:
            project_params["resolution_percentage"] = 50
        else:
            project_params["resolution_percentage"] = 100
        project_params["quality"] = 100
        project_params["file_format"] = "PNG"
        project_params["color_mode"] = "RGBA"
        project_params["alpha_mode"] = 1
        project_params["horizon_color"] = (0.57, 0.57, 0.57)
        project_params["animation"] = True
        project_params["output_path"] = os.path.join(info.BLENDER_PATH,
                                                     self.unique_folder_name,
                                                     self.params["file_name"])

        # return the dictionary
        return project_params

    # Error from blender (with version number)
    @pyqtSlot(str)
    def onBlenderVersionError(self, version):
        self.error_with_blender(version, None)

    # Signal error from blender (with custom message)
    @pyqtSlot()
    @pyqtSlot(str)
    def onBlenderError(self, error=None):
        self.error_with_blender(None, error)

    def error_with_blender(self, version=None, worker_message=None):
        """ Show a friendly error message regarding the blender executable or version. """
        _ = self.app._tr
        s = self.app.get_settings()

        error_message = ""
        if version:
            error_message = _("Version Detected: {}").format(version)
            log.info("Blender version detected: {}".format(version))

        if worker_message:
            error_message = _("Error Output:\n{}").format(worker_message)
            log.error("Blender error: {}".format(worker_message))

        QMessageBox.critical(
            self, error_message,
            _("""
Blender, the free open source 3D content creation suite, is required for this action. (http://www.blender.org)

Please check the preferences in OpenShot and be sure the Blender executable is correct.
This setting should be the path of the 'blender' executable on your computer.
Also, please be sure that it is pointing to Blender version {} or greater.

Blender Path: {}
{}""").format(info.BLENDER_MIN_VERSION, s.get("blender_command"),
              error_message))

        # Close the blender interface
        self.win.close()

    def inject_params(self, source_path, out_path, frame=None):
        # determine if this is 'preview' mode?
        is_preview = False
        if frame:
            # if a frame is passed in, we are in preview mode.
            # This is used to turn the background color to off-white... instead of transparent
            is_preview = True

        # prepare string to inject
        user_params = "\n#BEGIN INJECTING PARAMS\n"

        param_data = copy.deepcopy(self.params)
        param_data.update(self.get_project_params(is_preview))

        param_serialization = json.dumps(param_data)
        user_params += 'params_json = r' + '"""{}"""'.format(
            param_serialization)

        user_params += "\n#END INJECTING PARAMS\n"

        # If GPU rendering is selected, see if GPU enable code is available
        s = self.app.get_settings()
        gpu_code_body = None
        if s.get("blender_gpu_enabled"):
            gpu_enable_py = os.path.join(info.PATH, "blender", "scripts",
                                         "gpu_enable.py.in")
            try:
                with open(gpu_enable_py, 'r') as f:
                    gpu_code_body = f.read()
                if gpu_code_body:
                    log.info("Injecting GPU enable code from {}".format(
                        gpu_enable_py))
                    user_params += "\n#ENABLE GPU RENDERING\n"
                    user_params += gpu_code_body
                    user_params += "\n#END ENABLE GPU RENDERING\n"
            except IOError as e:
                log.error("Could not load GPU enable code! %s", e)

        # Read Python source from script file
        with open(source_path, 'r') as f:
            script_body = f.read()

        # insert our modifications to script source
        script_body = script_body.replace("# INJECT_PARAMS_HERE", user_params)

        # Write final script to output dir
        try:
            with open(out_path, "w", encoding="UTF-8", errors="strict") as f:
                f.write(script_body)
        except Exception:
            log.error("Could not write blender script to %s",
                      out_path,
                      exc_info=1)

    @pyqtSlot(str)
    def update_image(self, image_path):

        # get the pixbuf
        image = QImage(image_path)
        scaled_image = image.scaled(self.win.imgPreview.size(),
                                    Qt.KeepAspectRatio,
                                    Qt.SmoothTransformation)
        pixmap = QPixmap.fromImage(scaled_image)
        self.win.imgPreview.setPixmap(pixmap)

    def Cancel(self):
        """Cancel the current render, if any"""
        #QMetaObject.invokeMethod(self.worker, 'Cancel', Qt.DirectConnection)
        self.cancel_render.emit()

    def Render(self, frame=None):
        """ Render an images sequence of the current template using Blender 2.62+ and the
        Blender Python API. """

        self.processing_mode()

        # Init blender paths
        blend_file_path = os.path.join(info.PATH, "blender", "blend",
                                       self.selected_template)
        source_script = os.path.join(
            info.PATH, "blender", "scripts",
            self.selected_template.replace(".blend", ".py.in"))
        target_script = os.path.join(
            info.BLENDER_PATH, self.unique_folder_name,
            self.selected_template.replace(".blend", ".py"))

        # Background Worker Thread (for Blender process)
        self.background = QThread(self)
        self.background.setObjectName("openshot_renderer")
        self.worker = Worker(blend_file_path, target_script,
                             int(frame or 0))  # no parent!
        self.worker.setObjectName("render_worker")
        # Move Worker to new thread
        self.worker.moveToThread(self.background)

        # Hook up signals to/from Background Worker
        self.background.started.connect(self.worker.Render)

        self.worker.render_complete.connect(self.render_finished)
        self.cancel_render.connect(self.worker.Cancel)

        # State changes
        self.worker.end_processing.connect(self.end_processing)
        self.worker.start_processing.connect(self.render_initialize)

        # Actual communication between the worker and front-end
        self.worker.blender_version_error.connect(self.onBlenderVersionError)
        self.worker.blender_error_nodata.connect(self.onBlenderError)
        self.worker.blender_error_with_data.connect(self.onBlenderError)
        self.worker.progress.connect(self.update_progress_bar)
        self.worker.image_updated.connect(self.update_image)
        self.worker.frame_saved.connect(self.render_saved)
        self.worker.frame_stage.connect(self.render_stage)
        self.worker.frame_render.connect(self.render_progress)

        # Cleanup signals all 'round
        self.worker.finished.connect(self.worker.deleteLater)
        self.worker.finished.connect(self.background.quit, Qt.DirectConnection)
        self.background.finished.connect(self.background.deleteLater)
        self.background.finished.connect(self.worker.deleteLater)

        # Read .py file, inject user parameters, and write to output path
        self.inject_params(source_script, target_script, frame)

        # Note whether we're rendering a preview or an animation
        self.final_render = bool(frame is None)

        # Run worker in background thread
        self.background.start()

    def __init__(self, parent, *args):
        # Invoke base class init
        super().__init__(*args)

        self.win = parent
        self.app = get_app()

        # Get Model data
        self.blender_model = BlenderModel()

        self.selected = None
        self.deselected = None
        self._color_scratchpad = None
        self.selected_template = ""
        self.final_render = False

        # Preview render timer
        self.preview_timer = QTimer(self)
        self.preview_timer.setInterval(300)
        self.preview_timer.setSingleShot(True)
        self.preview_timer.timeout.connect(self.preview_timer_onTimeout)

        # Init dictionary which holds the values to the template parameters
        self.params = {}

        # Assign a new unique id for each template selected
        self.unique_folder_name = None

        # Disable interface
        self.processing_mode(cursor=False)

        # Setup header columns
        self.setModel(self.blender_model.model)
        self.setIconSize(QSize(131, 108))
        self.setGridSize(QSize(102, 92))
        self.setViewMode(QListView.IconMode)
        self.setResizeMode(QListView.Adjust)
        self.setUniformItemSizes(True)
        self.setWordWrap(True)
        self.setTextElideMode(Qt.ElideRight)

        # Hook up controls
        self.win.btnRefresh.clicked.connect(self.preview_timer.start)
        self.win.sliderPreview.valueChanged.connect(
            functools.partial(self.sliderPreview_valueChanged))

        # Refresh view
        self.refresh_view()