コード例 #1
0
    def fillOptions(self, frameLayout: QtWidgets.QVBoxLayout,
                    fullState: FullState) -> None:
        frameLayout.addWidget(QtWidgets.QLabel("Options for " + self.name))
        self.formParent = QtWidgets.QWidget(frameLayout.parentWidget())
        self.formLayout = QtWidgets.QFormLayout(self.formParent)

        # One forced widget on all - whether or not to include in analysis.
        self.inclusionCheck = QtWidgets.QCheckBox(self.formParent)
        k = self._includeKey()
        currentState = fullState.projectOptions.analysisOptions
        self.inclusionCheck.setChecked(currentState[k] if k in
                                       currentState else True)
        self._addFormRow("Include in analysis?", self.inclusionCheck)

        self.fillOptionsInner(currentState, fullState, self.formParent)
        frameLayout.addWidget(self.formParent)
コード例 #2
0
class ImageSearchScraperApp(QMainWindow):
    searchCountUpdated = pyqtSignal()
    clientCountUpdated = pyqtSignal()

    def __init__(self):
        super().__init__()
        self.title = __appname__
        self.left = 100
        self.top = 100
        self.width = 1024
        self.height = 768
        self.defaultSaveDir = str(Path.home()).replace('\\', '/')
        self.searchCount = 0
        self.clientCount = 0
        self.initUI()

    def initUI(self):
        """
        Initialize the user interface
        """
        # Main layout
        centralWidget = QWidget(self)
        self.setCentralWidget(centralWidget)
        scrollAreaContent = QWidget()
        listLayout = QVBoxLayout()

        # Search Clients layout
        self.clientsLayout = QVBoxLayout()
        firstSearchClient = SearchClient(SupportedSearchClients.GOOGLE_API,
                                         self.defaultSaveDir)
        self.clientsLayout.addWidget(firstSearchClient)
        self.clientCount = 1  # update client count
        self.searchCount = 1
        listLayout.addLayout(self.clientsLayout)

        # Layout to add additional API search clients
        apiPlusLayout = QHBoxLayout()
        apiPlusLayout.setAlignment(Qt.AlignRight)
        addGoogleAPI = ImageButton('add-google-api', 49, 32)
        addGoogleAPI.setToolTip('Add Google Custom Search Engine API Instance')
        apiPlusLayout.addWidget(addGoogleAPI)
        addBingAPI = ImageButton('add-bing-api', 49, 32)
        addBingAPI.setToolTip('Add Bing Image Search API Instance')
        apiPlusLayout.addWidget(addBingAPI)
        addGoogleScraper = ImageButton('add-google-scraper', 49, 32)
        addGoogleScraper.setToolTip('Add Google Image Scraper Instance')
        apiPlusLayout.addWidget(addGoogleScraper)
        # apiPlusLayout.addStretch(1)
        listLayout.addLayout(apiPlusLayout)

        listLayout.addStretch(1)

        # Scroll area
        scroll = QScrollArea()
        scroll.setWidget(scrollAreaContent)
        scroll.setWidgetResizable(True)
        scroll.setFrameShape(QFrame.NoFrame)
        scroll.setContentsMargins(0, 0, 0, 0)
        scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        scroll.setMinimumWidth(250)
        scrollLayout = QVBoxLayout()
        scrollLayout.addWidget(scroll)
        scrollAreaContent.setLayout(listLayout)
        centralWidget.setLayout(scrollLayout)
        centralWidget.layout().setContentsMargins(0, 0, 0, 0)

        # Toolbar
        self.toolbar = ToolBar()
        self.addToolBar(Qt.TopToolBarArea, self.toolbar)

        # Dock widgets
        self.downloadProgressDock = ProgressDock('Queries Download Progress')
        self.addDockWidget(Qt.RightDockWidgetArea, self.downloadProgressDock)

        # Connect signals and slots
        addGoogleScraper.clicked.connect(lambda state, x=SupportedSearchClients
                                         .GOOGLE: self.addSearchClient(x))
        self.searchCountUpdated.connect(self.updateToolbar)
        self.clientCountUpdated.connect(self.updateToolbar)
        addGoogleAPI.clicked.connect(lambda state, x=SupportedSearchClients.
                                     GOOGLE_API: self.addSearchClient(x))
        addBingAPI.clicked.connect(lambda state, x=SupportedSearchClients.
                                   BING_API: self.addSearchClient(x))
        self.toolbar.setDefaultDirectory.connect(self.setDefaultSaveDirectory)
        self.toolbar.searchAll.connect(self.searchAllQueries)
        self.toolbar.deleteAll.connect(self.removeAllSearchClients)
        self.toolbar.clearCompleted.connect(
            self.downloadProgressDock.removeCompleted)
        self.downloadProgressDock.completed[bool].connect(
            lambda state: self.toolbar.setClearProgressDockEnabled(state))
        firstSearchClient.delete.connect(self.decrementClientCount)
        firstSearchClient.search[str, str, str, str,
                                 int].connect(self.startGoogleAPISearchTask)
        firstSearchClient.searchCountUpdated[int, str].connect(
            self.updateSearchCount)

        # Window settings
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        self.setWindowIcon(newIcon('app'))
        self.center()
        self.show()

    def startSearchTask(self,
                        client: SupportedSearchClients,
                        directory: str,
                        query: str,
                        numImages: int,
                        apiKey: str = None,
                        cseID: str = None):
        """
        Start an image search and download in a worker thread, and start tracking its download progress
        """
        self.downloadProgressDock.addProgressItem(
            reduceString(directory + '/' + query))
        idx = self.downloadProgressDock.getItemCount() - 1
        self.thread = QThread(self)
        self.searchTask = SearchTask(idx,
                                     client,
                                     directory,
                                     query,
                                     numImages,
                                     apiKey=apiKey,
                                     cseID=cseID)
        self.searchTask.moveToThread(self.thread)
        self.searchTask.finished.connect(self.thread.terminate)
        self.searchTask.updateProgress[int, float].connect(
            self.downloadProgressDock.setProgressItemValue)
        self.thread.started.connect(self.searchTask.executeSearchTask)
        self.thread.start()

    def center(self):
        """
        Utility function to center the UI window on a user's screen
        """
        qtRectangle = self.frameGeometry()
        centerPoint = QDesktopWidget().availableGeometry().center()
        qtRectangle.moveCenter(centerPoint)
        self.move(qtRectangle.topLeft())

    def closeEvent(self, event):
        """
        QMainWindow closeEvent handler
        """
        mboxtitle = 'Exit'
        mboxmsg = 'Are you sure you want to quit?'
        reply = QMessageBox.warning(self, mboxtitle, mboxmsg,
                                    QMessageBox.Yes | QMessageBox.No,
                                    QMessageBox.No)
        if reply == QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()
            self.show()

    @pyqtSlot(str, str, int)
    def startGoogleSearchTask(self, directory: str, query: str,
                              numImages: int):
        """
        Start Google Image Scraper search task
        """
        self.startSearchTask(SupportedSearchClients.GOOGLE,
                             directory + '/google', query, numImages)

    @pyqtSlot(str, str, str, int)
    def startBingAPISearchTask(self, apiKey: str, directory: str, query: str,
                               numImages: int):
        """
        Start Bing Image Search API search task
        """
        self.startSearchTask(SupportedSearchClients.BING_API,
                             directory + '/bing-api', query, numImages, apiKey)

    @pyqtSlot(str, str, str, str, int)
    def startGoogleAPISearchTask(self, apiKey: str, cseID: str, directory: str,
                                 query: str, numImages: int):
        """
        Start Google Custom Search JSON API search task
        """
        self.startSearchTask(SupportedSearchClients.GOOGLE_API,
                             directory + '/google-api', query, numImages,
                             apiKey, cseID)

    @pyqtSlot()
    def removeAllSearchClients(self):
        """
        Remove all search client instances in the UI
        """
        mboxtitle = 'Delete All'
        mboxmsg = 'Are you sure you want to delete all search client instances and their corresponding queries?'
        reply = QMessageBox.warning(self, mboxtitle, mboxmsg,
                                    QMessageBox.Yes | QMessageBox.No,
                                    QMessageBox.No)
        if reply == QMessageBox.Yes:
            for s in self.clientsLayout.parentWidget().findChildren(
                    SearchClient):
                s.destroy()

    @pyqtSlot()
    def searchAllQueries(self):
        """
        Start a search and download for all the non-empty queries
        """
        for s in self.clientsLayout.parentWidget().findChildren(SearchBox):
            if s.searchQuery():
                s.searchButton.click()

    @pyqtSlot()
    def setDefaultSaveDirectory(self):
        """
        Set the default save root directory for all queries
        """
        selected = QFileDialog.getExistingDirectory(
            self, 'Save images to the directory', self.defaultSaveDir,
            QFileDialog.ShowDirsOnly)
        if selected:
            self.defaultSaveDir = selected
            for s in self.clientsLayout.parentWidget().findChildren(
                    SearchClient):
                s.setDefaultSaveDirectory(self.defaultSaveDir)

    @pyqtSlot(SupportedSearchClients)
    def addSearchClient(self, client: SupportedSearchClients):
        """
        Add a search client widget to the UI
        """
        self.clientCount += 1
        self.searchCount += 1
        clientWidget = SearchClient(client, self.defaultSaveDir)
        self.clientsLayout.addWidget(clientWidget)
        clientWidget.delete.connect(self.decrementClientCount)
        clientWidget.searchCountUpdated[int,
                                        str].connect(self.updateSearchCount)
        if client == SupportedSearchClients.GOOGLE_API:
            clientWidget.search[str, str, str, str,
                                int].connect(self.startGoogleAPISearchTask)
        elif client == SupportedSearchClients.BING_API:
            clientWidget.search[str, str, str,
                                int].connect(self.startBingAPISearchTask)
        elif client == SupportedSearchClients.GOOGLE:
            clientWidget.search[str, str,
                                int].connect(self.startGoogleSearchTask)
        self.clientCountUpdated.emit()

    @pyqtSlot()
    def decrementClientCount(self):
        """
        Decrement the client count
        """
        self.clientCount -= 1
        self.clientCountUpdated.emit()

    @pyqtSlot(int, str)
    def updateSearchCount(self, count: int, action: str):
        """
        Update the search boxes count based on action (add/remove)
        """
        if action == 'added':
            self.searchCount += count
        elif action == 'removed':
            self.searchCount -= count
        # print(f'Search count: {self.searchCount} ({action} {count})')
        self.searchCountUpdated.emit()

    @pyqtSlot()
    def updateToolbar(self):
        """
        Update the toolbar buttons state (enabled or disabled)
        """
        self.toolbar.setSearchAllEnabled(self.searchCount > 0)
        self.toolbar.setDeleteAllEnabled(self.clientCount > 0)
コード例 #3
0
class SearchClient(QGroupBox):
    """
    Search Client groupbox widget layout
    """
    delete = pyqtSignal()
    searchCountUpdated = pyqtSignal(int, str)
    search = pyqtSignal([str, str, str, str, int], [str, str, str, int],
                        [str, str, int])
    _titles = {
        SupportedSearchClients.GOOGLE: 'Google Image Scraper',
        SupportedSearchClients.GOOGLE_API: 'Google Custom Search JSON API',
        SupportedSearchClients.BING_API: 'Bing Image Search API v7'
    }

    def __init__(self,
                 client: SupportedSearchClients,
                 saveDirectory: str,
                 parent: QWidget = None):
        super().__init__(self._titles[client], parent)
        self.client = client
        self.searchCount = 1
        self.maxResults = 25000
        self.defaultSaveDirectory = saveDirectory

        self.setContentsMargins(11, 3, 0, 11)

        # Main vertical layout
        self.mainLayout = QVBoxLayout()
        self.mainLayout.setAlignment(Qt.AlignTop)
        self.mainLayout.setContentsMargins(0, 0, 0, 0)

        # Delete button and its layout
        deleteLayout = QHBoxLayout()
        deleteLayout.setAlignment(Qt.AlignRight)
        deleteLayout.setContentsMargins(0, 6, 0, 0)
        self.deleteButton = DeleteButton()
        deleteLayout.addWidget(self.deleteButton)
        self.mainLayout.addLayout(deleteLayout)

        # Client layout
        clientLayout = QVBoxLayout()
        clientLayout.setAlignment(Qt.AlignTop)
        clientLayout.setContentsMargins(0, 0, 11, 0)
        self.mainLayout.addLayout(clientLayout)

        # API keys text boxes
        if self.client == SupportedSearchClients.GOOGLE_API or self.client == SupportedSearchClients.BING_API:
            keysLayout = QHBoxLayout()
            self.apiKey = TextBox('Enter your API key here...')
            self.apiKey.textChanged.connect(self.updateSearchBoxEnabledState)
            keysLayout.addWidget(self.apiKey)
            if self.client == SupportedSearchClients.GOOGLE_API:
                self.cseID = TextBox('Google Custom Search Engine ID...')
                self.cseID.textChanged.connect(
                    self.updateSearchBoxEnabledState)
                keysLayout.addWidget(self.cseID)
                self.maxResults = 100
            keysLayout.addStretch(1)
            clientLayout.addLayout(keysLayout)

        # Initial search related widgets state
        self.initState = self.client != SupportedSearchClients.GOOGLE_API and self.client != SupportedSearchClients.BING_API

        # Search queries layout
        self.queriesLayout = QVBoxLayout()
        # Add search box
        searchBox = SearchBox(self.maxResults, f'Query #{self.searchCount}',
                              self.defaultSaveDirectory)
        searchBox.setEnabled(self.initState)
        self.queriesLayout.addWidget(searchBox)
        clientLayout.addLayout(self.queriesLayout)

        # Add plus icon
        self.addQuery = PlusIcon('Query', size=20)
        self.addQuery.setEnabled(self.initState)
        clientLayout.addWidget(self.addQuery)

        clientLayout.addStretch(1)
        self.setLayout(self.mainLayout)

        # Connect signals and slots
        self.deleteButton.clicked.connect(self.destroy)
        searchBox.delete.connect(self.updateSearchBoxTitles)
        searchBox.search[str, str, int].connect(self.searchRequest)
        self.addQuery.add.connect(self.addSearchBox)

    def addSearchBox(self):
        self.searchCount += 1
        searchBox = SearchBox(self.maxResults, f'Query #{self.searchCount}',
                              self.defaultSaveDirectory)
        self.queriesLayout.addWidget(searchBox)
        searchBox.delete.connect(self.updateSearchBoxTitles)
        searchBox.search[str, str, int].connect(self.searchRequest)
        self.searchCountUpdated.emit(1, 'added')

    def setDefaultSaveDirectory(self, directory: str):
        self.defaultSaveDirectory = directory
        for s in self.queriesLayout.parentWidget().findChildren(SearchBox):
            s.setSaveDirectory(directory)

    def _clearLayout(self, layout):
        if layout is not None:
            while layout.count():
                item = layout.takeAt(0)
                widget = item.widget()
                if widget is not None:
                    widget.deleteLater()
                else:
                    self._clearLayout(item.layout())

    @pyqtSlot(str, str, int)
    def searchRequest(self, directory: str, query: str, numImages: int):
        if self.client == SupportedSearchClients.GOOGLE_API:
            self.search[str, str, str, str,
                        int].emit(self.apiKey.toPlainText(),
                                  self.cseID.toPlainText(), directory, query,
                                  numImages)
        elif self.client == SupportedSearchClients.BING_API:
            self.search[str, str, str, int].emit(self.apiKey.toPlainText(),
                                                 directory, query, numImages)
        elif self.client == SupportedSearchClients.GOOGLE:
            self.search[str, str, int].emit(directory, query, numImages)

    @pyqtSlot()
    def updateSearchBoxEnabledState(self):
        if self.client == SupportedSearchClients.GOOGLE_API:
            # print(len(self.apiKey.toPlainText()), len(self.cseID.toPlainText()))
            state = len(self.apiKey.toPlainText()) == 39 and len(
                self.cseID.toPlainText()) == 33
        elif self.client == SupportedSearchClients.BING_API:
            state = len(self.apiKey.toPlainText()) == 39

        for s in self.queriesLayout.parentWidget().findChildren(SearchBox):
            s.setEnabled(state)

        self.addQuery.setEnabled(state)

    @pyqtSlot()
    def updateSearchBoxTitles(self):
        self.searchCount -= 1
        i = self.searchCount - 1
        for s in self.queriesLayout.parentWidget().findChildren(SearchBox):
            if not s.deleteInProgress:
                s.setProperties(f'Query #{self.searchCount - i}')
                i -= 1
        self.searchCountUpdated.emit(1, 'removed')  # removed one search box

    @pyqtSlot()
    def destroy(self):
        # Delete all items in the main layout
        self._clearLayout(self.mainLayout)
        # Delete self
        self.deleteLater()
        self.delete.emit()
        self.searchCountUpdated.emit(self.searchCount, 'removed')