class RecipeWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.recipe_title = ""

        self.conn = QSqlDatabase.addDatabase("QSQLITE")
        self.conn.setDatabaseName("recipes.db")

        self.initUI()

    def initUI(self):
        """
        PURPOSE
        -----
        To pass create all QWidgets to be displayed on our window.
        All class instances are changed dynamically, i.e. title, author, etc.
        All non-class instances are static widgets that will not be moved

        OUTPUT
        -----
        Nothing
        """
        self.setWindowTitle("Recipe Application")

        self.title = QLabel(self)
        self.title.setWordWrap(True)  # Enables word wrap for specific label
        self.title.setFont(QFont('Arial', 20, QFont.Bold))
        self.title.setAlignment(Qt.AlignCenter)
        self.title.setFixedWidth(600)
        self.title.move(150, 10)

        author_label = QLabel("Author: ", self)
        author_label.move(45, 120)
        author_label.setFont(QFont('Arial', 10, QFont.Bold))

        self.author = QLabel(self)
        self.author.move(95, 120)
        self.author.setFixedWidth(165)
        self.author.setFont(QFont('Arial', 10))

        cuisine_label = QLabel("Cuisine: ", self)
        cuisine_label.move(265, 120)
        cuisine_label.setFont(QFont('Arial', 10, QFont.Bold))

        self.cuisine = QLabel(self)
        self.cuisine.move(320, 120)
        self.cuisine.setFixedWidth(160)
        self.cuisine.setFont(QFont('Arial', 10))

        tag_label = QLabel("Tags: ", self)
        tag_label.move(485, 120)
        tag_label.setFont(QFont('Arial', 10, QFont.Bold))

        self.tags = QLabel(self)
        self.tags.setFixedWidth(330)
        self.tags.setWordWrap(True)
        self.tags.move(525, 115)
        self.tags.setFont(QFont('Arial', 10))

        ingredients_label = QLabel("Ingredients: ", self)
        ingredients_label.setFont(QFont('Arial', 10, QFont.Bold))
        ingredients_label.move(45, 170)

        self.ingredients = QTextEdit(self)
        self.ingredients.setReadOnly(True)
        self.ingredients.setFixedWidth(200)
        self.ingredients.setFixedHeight(300)
        self.ingredients.move(30, 200)
        self.ingredients.setFont(QFont('Arial', 10))

        recipe_label = QLabel("Recipe Procedure: ", self)
        recipe_label.setFont(QFont('Arial', 10, QFont.Bold))
        recipe_label.move(265, 170)

        self.recipe = QTextEdit(self)
        self.recipe.setReadOnly(True)
        self.recipe.setFixedWidth(610)
        self.recipe.setFixedHeight(300)
        self.recipe.move(250, 200)
        self.recipe.setFont(QFont('Arial', 10))

        website_url_label = QLabel("If you'd like to see full details of this recipe click here: ", self)
        website_url_label.setFont(QFont('Arial', 10, QFont.Bold))
        website_url_label.move(35, 540)

        self.website_url = QLabel(self)
        self.website_url.setOpenExternalLinks(True)  # Allows us to click on hyperlink
        self.website_url.setFixedWidth(500)
        self.website_url.setWordWrap(True)  # Set word wrap
        self.website_url.move(385, 540)
        self.website_url.setFont(QFont('Arial', 10))

        self.title_view_label = QLabel("Below are potentially some related recipes! :", self)
        self.title_view_label.setFont(QFont('Arial', 10))

    def obtain_recipe(self):
        """
        PURPOSE
        -----
        To obtain all information pertaining to the recipe clicked on by user, i.e. title, author, cuisine, etc. and
        create a model to store that information

        OUTPUT
        -----
        The model with the stored information
        """
        recipe_info_cmd = \
            f"""
        SELECT R.`Recipe Title`, R.Author, R.Cuisine, R.Ingredients, R.`Recipe Procedure`, R.Tags, R.'Website URL'
        FROM recipes R
        WHERE R.`Recipe Title` = "{self.recipe_title}"
        """
        # Query to get title, author, ingredients, and recipe procedure from desired recipe title
        recipe_query = QSqlQuery(self.conn)  # Same process as the title_query
        recipe_query.prepare(recipe_info_cmd)
        recipe_query.exec_()

        recipe_model = QSqlQueryModel()
        recipe_model.setQuery(recipe_query)

        return recipe_model

    def view_recipe(self, model):
        """
        PURPOSE
        -----
        To obtain all information pertaining to the recipe clicked on by user, i.e. title, author, cuisine, etc. from
        the model parameter and set the information to our widgets create in the initUI() function declared previously

        Then takes the tags from said recipe to view other recipes with one of its tags in view_related_recipes()

        INPUT
        -----
        model: QSqlQueryModel() containing recipe information

        OUTPUT
        -----
        Calls view_related_recipes() with array of tag values
        """

        # All values taken from QSqlQuery returned in obtain_recipe() function
        recipe_title = model.record(0).value(0)  # Recipe title
        recipe_author = model.record(0).value(1)  # Recipe author
        recipe_cuisine = model.record(0).value(2)  # Recipe cuisine
        recipe_ingredients = model.record(0).value(3)  # Recipe ingredients
        recipe_steps = model.record(0).value(4)  # Recipe steps
        recipe_tags = model.record(0).value(5)  # Recipe tags
        recipe_website = model.record(0).value(6)  # Recipe website

        self.setGeometry(0, 0, 900, 780)  # Set our window size
        self.setFixedSize(self.width(), self.height())

        # Everything done below is pretty self explanatory; create label/textedit, populate, move, etc.
        self.title.setText(recipe_title)
        self.title.repaint()

        self.author.setText(recipe_author)
        self.author.repaint()

        self.cuisine.setText(recipe_cuisine)
        self.cuisine.repaint()

        self.tags.setText(recipe_tags)
        self.tags.repaint()

        self.ingredients.setText(recipe_ingredients)
        self.ingredients.repaint()

        self.recipe.setText(recipe_steps)
        self.recipe.repaint()

        self.website_url.setText('<a href="' + recipe_website + '/">' + recipe_title + '</a>')  # Creates hyperlink
        self.website_url.repaint()

        self.view_related_recipes(recipe_tags.split(","))

    def view_related_recipes(self, tag_list):
        """
        PURPOSE
        -----
        To create a QTableView() with all recipes with like-tags of the already shown recipe

        INPUT
        -----
        tag_list: list containing tag information

        OUTPUT
        -----
        Displays full window containing recipe information + related recipes
        """

        recipe_query = ""  # Empty string to construct our query
        for elements in tag_list:
            # Here we construct our query through a for-loop
            # There shouldn't be many elements in our array in the first place, so no worries of efficiency
            recipe_query += f"Tags LIKE '%" + elements + "%' OR "

        recipe_query = recipe_query[:len(recipe_query) - 4]  # to remove last OR + extra space

        cmd = \
            f"""
            SELECT `Recipe Title` FROM recipes R WHERE {recipe_query}
            """
        title_query = QSqlQuery(self.conn)  # Establish our query, prepare, and execute it
        title_query.prepare(cmd)
        title_query.exec_()

        title_model = QSqlQueryModel()  # Adds our queried information into a read-only model
        title_model.setQuery(title_query)

        # Adds information from out query model to a QTableView to be seen
        self.title_view = QTableView(self)
        self.title_view.setModel(title_model)
        self.title_view.resizeColumnToContents(0)  # Modifying sizing of our QTableView

        new_title_width = self.title_view.columnWidth(0) + 45

        self.title_view_label.move(int((self.width() - new_title_width) / 2), 580)

        self.title_view.resize(new_title_width, 130)
        self.title_view.move(int((self.width() - new_title_width) / 2), 605)

        self.title_view.doubleClicked.connect(lambda: self.pass_info(
            self.title_view.selectionModel().currentIndex()))  # Allow users to go to new related recipe

        self.show()

    def pass_info(self, index):
        """
        PURPOSE
        -----
        To pass related recipe information in next window

        INPUT
        -----
        index: The index of the row+col selected by the user when they double click the recipe view

        OUTPUT
        -----
        calls function within RecipeWindow object we create in __init()__
        """
        # Get current indexed position from click
        cell_val = index.sibling(index.row(), index.column()).data()  # Get cell value from the cell itself
        self.recipe_title = cell_val
        self.title_view.hide()
        self.hide()
        self.view_recipe(self.obtain_recipe())
class RecipeTitleWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.search_text = ""
        self.search_category = ""

        self.title_view = QTableView(self)

        self.recipe_window = RecipeWindow()

        # Create connection with SQlite3 language
        self.conn = QSqlDatabase.addDatabase("QSQLITE")
        self.conn.setDatabaseName("recipes.db")

    def recipe_search(self):
        """
        PURPOSE
        -----
        To search and display the recipes from the desired query from the user using sqlite3 on a table-view

        OUTPUT
        -----
        Displays a table of rows containing recipe titles that the user can double click on to view information about it

        """
        self.conn.open() # Open our connection to database
        self.setWindowTitle("Recipe Application")

        if len(self.search_text.strip(' ')) == 0: # If the query is empty, throw an error message
            error_dialog = QErrorMessage()
            error_dialog.showMessage(
                f"It seems you haven't input anything to search for the {self.search_category.lower()}'s associated recipes! Please try again.")
            error_dialog.exec_()
        else: # Otherwise we proceed as normally

            self.search_list = self.search_text.split(',')  # Create an array of desired ingredients

            recipe_query = ""  # Empty string to construct our query
            for elements in self.search_list:
                # Here we construct our query through a for-loop
                # There shouldn't be many elements in our array in the first place, so no worries of efficiency
                recipe_query += f"`{self.search_category}` LIKE '%" + elements + "%' AND "

            if len(recipe_query) > 5:  # On the off chance that there is nothing input into the text box, throw error
                recipe_query = recipe_query[:len(recipe_query) - 5]  # to remove last AND

            cmd = \
                f"""
                SELECT `Recipe Title` FROM recipes R WHERE {recipe_query}
                """

            title_query = QSqlQuery(self.conn)  # Establish our query, prepare, and execute it
            title_query.prepare(cmd)
            title_query.exec_()

            title_model = QSqlQueryModel()  # Adds our queried information into a read-only model
            title_model.setQuery(title_query)

            if title_model.rowCount() == 0: # If no recipes return, throw error message saying there exists no recipe
                                            # with their desired information
                empty_error_dialog = QErrorMessage()
                empty_error_dialog.showMessage(
                    f"It appears that there are no recipes pertaining to your search. Please try again!")
                empty_error_dialog.exec_()
            else:
                # Adds information from out query model to a QTableView to be seen
                self.title_view.setModel(title_model)
                self.title_view.setWindowTitle("Recipe List")

                self.title_view.doubleClicked.connect(lambda: self.pass_info(self.title_view.selectionModel().currentIndex()))
                # Adds functionality of view_recipe when double clicking cell

                self.title_view.setMaximumHeight(500) # Set max height of table + window
                self.setMaximumHeight(500)

                self.title_view.resizeColumnToContents(0) # Sets size of column to contents, i.e. longest recipe title

                # Each cell is ~ 30 pixels, so we make our window slightly smaller than the rows can fit if it
                # is less than 16 cells (which populates ~ 500 pixel height) to keep width consistent w/ scroll bar
                # and to make sure we have no empty space in our window
                self.title_view.resize(self.title_view.columnWidth(0) + 40, 25*title_model.rowCount())
                self.resize(self.title_view.columnWidth(0) + 40, 25*title_model.rowCount())
                self.title_view.show()
                self.show()

    def pass_info(self, index):  # Get current indexed position from click
        """
        PURPOSE
        -----
        To pass recipe information in next window, 'RecipeWindow'

        INPUT
        -----
        index: The index of the row+col selected by the user when they double click the recipe view

        OUTPUT
        -----
        calls function within RecipeWindow object we create in __init()__
        """

        cell_val = index.sibling(index.row(), index.column()).data()  # Get cell value from the cell itself
        self.recipe_window.recipe_title = cell_val # Sets information in RecipeWindow object

        self.title_view.hide()
        self.hide() # Hides our current window
        self.recipe_window.view_recipe(self.recipe_window.obtain_recipe())
class TaggerDialog(QDialog):
    def __init__(self, args, **kwargs):
        super(TaggerDialog, self).__init__(**kwargs)

        self.reviewing = False
        self.args = args

        self.worker = TaggerWorker()
        self.thread = QThread()
        self.worker.moveToThread(self.thread)

        self.worker.on_error.connect(self.on_error)
        self.worker.on_review_ready.connect(self.on_review_ready)
        self.worker.on_stopped.connect(self.on_stopped)
        self.worker.on_progress.connect(self.on_progress)
        self.worker.on_updates_sent.connect(self.on_updates_sent)
        self.worker.on_mint_mfa.connect(self.on_mint_mfa)

        self.thread.started.connect(
            partial(self.worker.create_updates, args, self))
        self.thread.start()

        self.init_ui()

    def init_ui(self):
        self.setWindowTitle('Tagger is running...')
        self.setModal(True)
        self.v_layout = QVBoxLayout()
        self.setLayout(self.v_layout)

        self.label = QLabel()
        self.v_layout.addWidget(self.label)

        self.progress = 0
        self.progress_bar = QProgressBar()
        self.progress_bar.setRange(0, 0)
        self.v_layout.addWidget(self.progress_bar)

        self.button_bar = QHBoxLayout()
        self.v_layout.addLayout(self.button_bar)

        self.cancel_button = QPushButton('Cancel')
        self.button_bar.addWidget(self.cancel_button)
        self.cancel_button.clicked.connect(self.on_cancel)

    def on_error(self, msg):
        logger.error(msg)
        self.label.setText('Error: {}'.format(msg))
        self.label.setStyleSheet('QLabel { color: red; font-weight: bold; }')
        self.cancel_button.setText('Close')
        self.cancel_button.clicked.connect(self.close)

    def open_amazon_order_id(self, order_id):
        if order_id:
            QDesktopServices.openUrl(QUrl(amazon.get_invoice_url(order_id)))

    def on_activated(self, index):
        # Only handle clicks on the order_id cell.
        if index.column() != 5:
            return
        order_id = self.updates_table_model.data(index, Qt.DisplayRole)
        self.open_amazon_order_id(order_id)

    def on_double_click(self, index):
        if index.column() == 5:
            # Ignore double clicks on the order_id cell.
            return
        order_id_cell = self.updates_table_model.createIndex(index.row(), 5)
        order_id = self.updates_table_model.data(order_id_cell, Qt.DisplayRole)
        self.open_amazon_order_id(order_id)

    def on_review_ready(self, results):
        self.reviewing = True
        self.progress_bar.hide()

        self.label.setText('Select below which updates to send to Mint.')

        self.updates_table_model = MintUpdatesTableModel(results.updates)
        self.updates_table = QTableView()
        self.updates_table.doubleClicked.connect(self.on_double_click)
        self.updates_table.clicked.connect(self.on_activated)

        def resize():
            self.updates_table.resizeColumnsToContents()
            self.updates_table.resizeRowsToContents()
            min_width = sum(
                self.updates_table.columnWidth(i) for i in range(6))
            self.updates_table.setMinimumSize(min_width + 20, 600)

        self.updates_table.setSelectionMode(QAbstractItemView.SingleSelection)
        self.updates_table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.updates_table.setModel(self.updates_table_model)
        self.updates_table.setSortingEnabled(True)
        resize()
        self.updates_table_model.layoutChanged.connect(resize)

        self.v_layout.insertWidget(2, self.updates_table)

        unmatched_button = QPushButton('View Unmatched Amazon orders')
        self.button_bar.addWidget(unmatched_button)
        unmatched_button.clicked.connect(
            partial(self.on_open_unmatched, results.unmatched_orders))

        amazon_stats_button = QPushButton('Amazon Stats')
        self.button_bar.addWidget(amazon_stats_button)
        amazon_stats_button.clicked.connect(
            partial(self.on_open_amazon_stats, results.items, results.orders,
                    results.refunds))

        tagger_stats_button = QPushButton('Tagger Stats')
        self.button_bar.addWidget(tagger_stats_button)
        tagger_stats_button.clicked.connect(
            partial(self.on_open_tagger_stats, results.stats))

        self.confirm_button = QPushButton('Send to Mint')
        self.button_bar.addWidget(self.confirm_button)
        self.confirm_button.clicked.connect(self.on_send)

        self.setGeometry(50, 50, self.width(), self.height())

    def on_updates_sent(self, num_sent):
        self.label.setText(
            'All done! {} newly tagged Mint transactions'.format(num_sent))
        self.cancel_button.setText('Close')

    def on_open_unmatched(self, unmatched):
        self.unmatched_dialog = AmazonUnmatchedTableDialog(unmatched)
        self.unmatched_dialog.show()

    def on_open_amazon_stats(self, items, orders, refunds):
        self.amazon_stats_dialog = AmazonStatsDialog(items, orders, refunds)
        self.amazon_stats_dialog.show()

    def on_open_tagger_stats(self, stats):
        self.tagger_stats_dialog = TaggerStatsDialog(stats)
        self.tagger_stats_dialog.show()

    def on_send(self):
        self.progress_bar.show()
        updates = self.updates_table_model.get_selected_updates()

        self.confirm_button.hide()
        self.updates_table.hide()
        self.confirm_button.deleteLater()
        self.updates_table.deleteLater()
        self.adjustSize()

        QMetaObject.invokeMethod(self.worker, 'send_updates',
                                 Qt.QueuedConnection, Q_ARG(list, updates),
                                 Q_ARG(object, self.args))

    def on_stopped(self):
        self.close()

    def on_progress(self, msg, max, value):
        self.label.setText(msg)
        self.progress_bar.setRange(0, max)
        self.progress_bar.setValue(value)

    def on_cancel(self):
        if not self.reviewing:
            QMetaObject.invokeMethod(self.worker, 'stop', Qt.QueuedConnection)
        else:
            self.close()

    def on_mint_mfa(self):
        mfa_code, ok = QInputDialog().getText(self,
                                              'Please enter your Mint Code.',
                                              'Mint Code:')
        QMetaObject.invokeMethod(self.worker, 'mfa_code', Qt.QueuedConnection,
                                 Q_ARG(int, mfa_code))
Exemple #4
0
class FreezeTableMixin:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self._frozen_table = QTableView(self)
        self._frozen_table.verticalHeader().hide()
        self._frozen_table.setFocusPolicy(Qt.NoFocus)
        self._frozen_table.setStyleSheet('''
        QTableView {
            border: none;
            background-color: palette(dark);
            alternate-background-color: palette(mid);
            color: palette(base);
        }
        ''')
        self._frozen_table.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self._frozen_table.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self._frozen_table.horizontalHeader().setStretchLastSection(True)
        self._frozen_table.horizontalHeader().setSortIndicator(
            -1, Qt.AscendingOrder)

        self.viewport().stackUnder(self._frozen_table)

        self._frozen_table.verticalHeader().setDefaultSectionSize(
            self.verticalHeader().defaultSectionSize())
        self._frozen_table.hide()

        self.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
        self.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
        self._frozen_table.setVerticalScrollMode(
            QAbstractItemView.ScrollPerPixel)

        # connect the headers and scrollbars of both tableviews together
        self.horizontalHeader().sectionResized.connect(
            self.updateFrozenSectionWidth)
        self._frozen_table.horizontalHeader().sectionResized.connect(
            self.updateTableSectionWidth)
        self.verticalHeader().sectionResized.connect(self.updateSectionHeight)
        self._frozen_table.verticalScrollBar().valueChanged.connect(
            self.verticalScrollBar().setValue)
        self.verticalScrollBar().valueChanged.connect(
            self._frozen_table.verticalScrollBar().setValue)

    def frozenTable(self):
        return self._frozen_table

    def setHorizontalHeader(self, header: QHeaderView) -> None:
        header.sectionResized.connect(self.updateFrozenSectionWidth)
        header.setSortIndicator(-1, Qt.AscendingOrder)
        super().setHorizontalHeader(header)
        header.stackUnder(self._frozen_table)

    def setFrozenTableHorizontalHeader(self, header: QHeaderView) -> None:
        header.sectionResized.connect(self.updateTableSectionWidth)
        header.setSortIndicator(-1, Qt.AscendingOrder)
        self._frozen_table.setHorizontalHeader(header)

    def setVerticalHeader(self, header: QHeaderView) -> None:
        header.sectionResized.connect(self.updateSectionHeight)
        self._frozen_table.verticalHeader().setDefaultSectionSize(
            header.defaultSectionSize())
        super().setVerticalHeader(header)

    def setModel(self, model: QAbstractItemModel) -> None:
        super().setModel(model)

        # Derive a proxy model from the model to limit number of columns
        frozen_model = RearrangeColumnsProxymodel(self)
        frozen_model.setSourceModel(self.model())
        self._frozen_table.setModel(frozen_model)

        link_selection_model = LinkItemSelectionModel(
            frozen_model, QAbstractItemView.selectionModel(self), self)
        self._frozen_table.setSelectionModel(link_selection_model)

    # noinspection PyUnusedLocal
    def updateFrozenSectionWidth(self, logical_index: int, old_size: int,
                                 new_size: int):
        model = self._frozen_table.model()
        if model is None:
            return

        proxy_logical_index = model.proxyColumnForSourceColumn(logical_index)
        if proxy_logical_index > 0:
            self._frozen_table.horizontalHeader().blockSignals(True)
            self._frozen_table.horizontalHeader().resizeSection(
                proxy_logical_index, new_size)
            self._frozen_table.horizontalHeader().blockSignals(False)
            self.updateFrozenTableGeometry()

    # noinspection PyUnusedLocal
    def updateTableSectionWidth(self, logical_index: int, old_size: int,
                                new_size: int):
        model = self._frozen_table.model()
        if model is None:
            return

        source_logical_index = model.sourceColumnForProxyColumn(logical_index)
        if source_logical_index > 0:
            self.setColumnWidth(source_logical_index, new_size)
            self.updateFrozenTableGeometry()

    # noinspection PyUnusedLocal
    def updateSectionHeight(self, logical_index: int, old_size: int,
                            new_size: int):
        self._frozen_table.setRowHeight(logical_index, new_size)

    def resizeEvent(self, event):
        QTableView.resizeEvent(self, event)
        self.updateFrozenTableGeometry()

    def scrollTo(
        self,
        index: QModelIndex,
        hint: QAbstractItemView.ScrollHint = QAbstractItemView.EnsureVisible
    ) -> None:
        if index.column() > 1:
            QTableView.scrollTo(self, index, hint)

    def setFrozenColumns(self, num_columns=None):
        if num_columns is not None:
            model = self._frozen_table.model()
            if model is None:
                return

            mapping = [
                self.horizontalHeader().logicalIndex(col)
                for col in range(num_columns)
            ]
            mapping = [col for col in mapping if not self.isColumnHidden(col)]
            model.setSourceColumns(mapping)

            # Synchronize section sizes between table and frozen table
            hh = self._frozen_table.horizontalHeader()
            for col in range(num_columns):
                logical_index = model.sourceColumnForProxyColumn(col)
                hh.resizeSection(col, self.columnWidth(logical_index))

            self._frozen_table.show()
            self.updateFrozenTableGeometry()
        else:
            self._frozen_table.hide()

    def updateFrozenTableGeometry(self):
        model = self._frozen_table.model()
        if model is None:
            return

        ax = ay = self.frameWidth()
        aw = sum(
            self.columnWidth(model.sourceColumnForProxyColumn(i))
            for i in range(model.columnCount()))
        ah = self.viewport().height() + self.horizontalHeader().height()
        if self.verticalHeader().isVisible():
            ax += self.verticalHeader().width()
        self._frozen_table.setGeometry(ax, ay, aw, ah)

    def moveCursor(self, cursorAction, modifiers):
        current = QTableView.moveCursor(self, cursorAction, modifiers)
        x = self.visualRect(current).topLeft().x()
        frozen_width = self._frozen_table.columnWidth(
            0) + self._frozen_table.columnWidth(1)
        if cursorAction == self.MoveLeft and current.column(
        ) > 1 and x < frozen_width:
            new_value = self.horizontalScrollBar().value() + x - frozen_width
            self.horizontalScrollBar().setValue(new_value)
        return current
Exemple #5
0
class FileManager(QWidget, _HalWidgetBase):
    def __init__(self, parent=None):
        super(FileManager, self).__init__(parent)
        self.title = 'Qtvcp File System View'
        self.left = 10
        self.top = 10
        self.width = 640
        self.height = 480
        self._last = 0

        if INFO.PROGRAM_PREFIX is not None:
            self.user_path = os.path.expanduser(INFO.PROGRAM_PREFIX)
        else:
            self.user_path = (os.path.join(os.path.expanduser('~'),
                                           'linuxcnc/nc_files'))
        user = os.path.split(os.path.expanduser('~'))[-1]
        self.media_path = (os.path.join('/media', user))
        temp = [('User', self.user_path), ('Media', self.media_path)]
        self._jumpList = OrderedDict(temp)
        self.currentPath = None
        self.currentFolder = None
        self.PREFS_ = None
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        pasteBox = QHBoxLayout()
        self.textLine = QLineEdit()
        self.textLine.setToolTip('Current Director/selected File')
        self.pasteButton = QToolButton()
        self.pasteButton.setEnabled(False)
        self.pasteButton.setText('Paste')
        self.pasteButton.setToolTip(
            'Copy file from copy path to current directory/file')
        self.pasteButton.clicked.connect(self.paste)
        self.pasteButton.hide()
        pasteBox.addWidget(self.textLine)
        pasteBox.addWidget(self.pasteButton)

        self.copyBox = QFrame()
        hbox = QHBoxLayout()
        hbox.setContentsMargins(0, 0, 0, 0)
        self.copyLine = QLineEdit()
        self.copyLine.setToolTip('File path to copy from, when pasting')
        self.copyButton = QToolButton()
        self.copyButton.setText('Copy')
        self.copyButton.setToolTip('Record current file as copy path')
        self.copyButton.clicked.connect(self.recordCopyPath)
        hbox.addWidget(self.copyButton)
        hbox.addWidget(self.copyLine)
        self.copyBox.setLayout(hbox)
        self.copyBox.hide()

        self.model = QFileSystemModel()
        self.model.setRootPath(QDir.currentPath())
        self.model.setFilter(QDir.AllDirs | QDir.NoDot | QDir.Files)
        self.model.setNameFilterDisables(False)
        self.model.rootPathChanged.connect(self.folderChanged)

        self.list = QListView()
        self.list.setModel(self.model)
        self.list.resize(640, 480)
        self.list.clicked[QModelIndex].connect(self.listClicked)
        self.list.activated.connect(self._getPathActivated)
        self.list.setAlternatingRowColors(True)
        self.list.hide()

        self.table = QTableView()
        self.table.setModel(self.model)
        self.table.resize(640, 480)
        self.table.clicked[QModelIndex].connect(self.listClicked)
        self.table.activated.connect(self._getPathActivated)
        self.table.setAlternatingRowColors(True)

        header = self.table.horizontalHeader()
        header.setSectionResizeMode(0, QHeaderView.Stretch)
        header.setSectionResizeMode(1, QHeaderView.ResizeToContents)
        header.setSectionResizeMode(3, QHeaderView.ResizeToContents)
        header.swapSections(1, 3)
        header.setSortIndicator(1, Qt.AscendingOrder)

        self.table.setSortingEnabled(True)
        self.table.setColumnHidden(2, True)  # type
        self.table.verticalHeader().setVisible(False)  # row count header

        self.cb = QComboBox()
        self.cb.currentIndexChanged.connect(self.filterChanged)
        self.fillCombobox(INFO.PROGRAM_FILTERS_EXTENSIONS)
        self.cb.setMinimumHeight(30)
        self.cb.setSizePolicy(QSizePolicy(QSizePolicy.Fixed,
                                          QSizePolicy.Fixed))

        self.button2 = QToolButton()
        self.button2.setText('User')
        self.button2.setSizePolicy(
            QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        self.button2.setMinimumSize(60, 30)
        self.button2.setToolTip(
            'Jump to User directory.\nLong press for Options.')
        self.button2.clicked.connect(self.onJumpClicked)

        self.button3 = QToolButton()
        self.button3.setText('Add Jump')
        self.button3.setSizePolicy(
            QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        self.button3.setMinimumSize(60, 30)
        self.button3.setToolTip('Add current directory to jump button list')
        self.button3.clicked.connect(self.onActionClicked)

        self.settingMenu = QMenu(self)
        self.button2.setMenu(self.settingMenu)

        hbox = QHBoxLayout()
        hbox.addWidget(self.button2)
        hbox.addWidget(self.button3)
        hbox.insertStretch(2, stretch=0)
        hbox.addWidget(self.cb)

        windowLayout = QVBoxLayout()
        windowLayout.addLayout(pasteBox)
        windowLayout.addWidget(self.copyBox)
        windowLayout.addWidget(self.list)
        windowLayout.addWidget(self.table)
        windowLayout.addLayout(hbox)
        self.setLayout(windowLayout)
        self.show()

    def _hal_init(self):
        if self.PREFS_:
            last_path = self.PREFS_.getpref('last_loaded_directory',
                                            self.user_path, str,
                                            'BOOK_KEEPING')
            LOG.debug("lAST FILE PATH: {}".format(last_path))
            if not last_path == '':
                self.updateDirectoryView(last_path)
            else:
                self.updateDirectoryView(self.user_path)

            # get all the saved jumplist paths
            temp = self.PREFS_.getall('FILEMANAGER_JUMPLIST')
            self._jumpList.update(temp)

        else:
            LOG.debug("lAST FILE PATH: {}".format(self.user_path))
            self.updateDirectoryView(self.user_path)

        # install jump paths into toolbutton menu
        for i in self._jumpList:
            self.addAction(i)

        # set recorded columns sort settings
        self.SETTINGS_.beginGroup("FileManager-{}".format(self.objectName()))
        sect = self.SETTINGS_.value('sortIndicatorSection', type=int)
        order = self.SETTINGS_.value('sortIndicatorOrder', type=int)
        self.SETTINGS_.endGroup()
        if not None in (sect, order):
            self.table.horizontalHeader().setSortIndicator(sect, order)

    # when qtvcp closes this gets called
    # record jump list paths
    def _hal_cleanup(self):
        if self.PREFS_:
            for i, key in enumerate(self._jumpList):
                if i in (0, 1):
                    continue
                self.PREFS_.putpref(key, self._jumpList.get(key), str,
                                    'FILEMANAGER_JUMPLIST')

        # record sorted columns
        h = self.table.horizontalHeader()
        self.SETTINGS_.beginGroup("FileManager-{}".format(self.objectName()))
        self.SETTINGS_.setValue('sortIndicatorSection',
                                h.sortIndicatorSection())
        self.SETTINGS_.setValue('sortIndicatorOrder', h.sortIndicatorOrder())
        self.SETTINGS_.endGroup()

    #########################
    # callbacks
    #########################

    # add shown text and hidden filter data from the INI
    def fillCombobox(self, data):
        for i in data:
            self.cb.addItem(i[0], i[1])

    def folderChanged(self, data):
        data = os.path.normpath(data)
        self.currentFolder = data
        self.textLine.setText(data)

    def updateDirectoryView(self, path, quiet=False):
        if os.path.exists(path):
            self.list.setRootIndex(self.model.setRootPath(path))
            self.table.setRootIndex(self.model.setRootPath(path))
        else:
            LOG.debug(
                "Set directory view error - no such path {}".format(path))
            if not quiet:
                STATUS.emit(
                    'error', LOW_ERROR,
                    "File Manager error - No such path: {}".format(path))

    # retrieve selected filter (it's held as QT.userData)
    def filterChanged(self, index):
        userdata = self.cb.itemData(index)
        self.model.setNameFilters(userdata)

    def listClicked(self, index):
        # the signal passes the index of the clicked item
        dir_path = os.path.normpath(self.model.filePath(index))
        if self.model.fileInfo(index).isFile():
            self.currentPath = dir_path
            self.textLine.setText(self.currentPath)
            return
        root_index = self.model.setRootPath(dir_path)
        self.list.setRootIndex(root_index)
        self.table.setRootIndex(root_index)

    def onUserClicked(self):
        self.showUserDir()

    def onMediaClicked(self):
        self.showMediaDir()

    # jump directly to a saved path shown on the button
    def onJumpClicked(self):
        data = self.button2.text()
        if data.upper() == 'MEDIA':
            self.showMediaDir()
        elif data.upper() == 'USER':
            self.showUserDir()
        else:
            temp = self._jumpList.get(data)
            if temp is not None:
                self.updateDirectoryView(temp)
            else:
                STATUS.emit('error', linuxcnc.OPERATOR_ERROR,
                            'file jumopath: {} not valid'.format(data))
                log.debug('file jumopath: {} not valid'.format(data))

    # jump directly to a saved path from the menu
    def jumpTriggered(self, data):
        if data.upper() == 'MEDIA':
            self.button2.setText('{}'.format(data))
            self.button2.setToolTip(
                'Jump to Media directory.\nLong press for Options.')
            self.showMediaDir()
        elif data.upper() == 'USER':
            self.button2.setText('{}'.format(data))
            self.button2.setToolTip(
                'Jump to User directory.\nLong press for Options.')
            self.showUserDir()
        else:
            self.button2.setText('{}'.format(data))
            self.button2.setToolTip('Jump to directory:\n{}'.format(
                self._jumpList.get(data)))
            self.updateDirectoryView(self._jumpList.get(data))

    # add a jump list path
    def onActionClicked(self):
        i = self.currentFolder
        try:
            self._jumpList[i] = i
        except Exception as e:
            print(e)
        button = QAction(QIcon.fromTheme('user-home'), i, self)
        # weird lambda i=i to work around 'function closure'
        button.triggered.connect(lambda state, i=i: self.jumpTriggered(i))
        self.settingMenu.addAction(button)

    # get current selection and update the path
    # then if the path is good load it into linuxcnc
    # record it in the preference file if available
    def _getPathActivated(self):
        if self.list.isVisible():
            row = self.list.selectionModel().currentIndex()
        else:
            row = self.table.selectionModel().currentIndex()
            self.listClicked(row)

        fname = self.currentPath
        if fname is None:
            return
        if fname:
            self.load(fname)

    def recordCopyPath(self):
        data, isFile = self.getCurrentSelected()
        if isFile:
            self.copyLine.setText(os.path.normpath(data))
            self.pasteButton.setEnabled(True)
        else:
            self.copyLine.setText('')
            self.pasteButton.setEnabled(False)
            STATUS.emit('error', OPERATOR_ERROR,
                        'Can only copy a file, not a folder')

    def paste(self):
        res = self.copyFile(self.copyLine.text(), self.textLine.text())
        if res:
            self.copyLine.setText('')
            self.pasteButton.setEnabled(False)

    ########################
    # helper functions
    ########################

    def addAction(self, i):
        axisButton = QAction(QIcon.fromTheme('user-home'), i, self)
        # weird lambda i=i to work around 'function closure'
        axisButton.triggered.connect(lambda state, i=i: self.jumpTriggered(i))
        self.settingMenu.addAction(axisButton)

    def showList(self, state=True):
        if state:
            self.table.hide()
            self.list.show()
        else:
            self.table.show()
            self.list.hide()

    def showTable(self, state=True):
        self.showList(not state)

    def showCopyControls(self, state):
        if state:
            self.copyBox.show()
            self.pasteButton.show()
        else:
            self.copyBox.hide()
            self.pasteButton.hide()

    def showMediaDir(self, quiet=False):
        self.updateDirectoryView(self.media_path, quiet)

    def showUserDir(self, quiet=False):
        self.updateDirectoryView(self.user_path, quiet)

    def copyFile(self, s, d):
        try:
            shutil.copy(s, d)
            return True
        except Exception as e:
            LOG.error("Copy file error: {}".format(e))
            STATUS.emit('error', OPERATOR_ERROR,
                        "Copy file error: {}".format(e))
            return False

    @pyqtSlot(float)
    @pyqtSlot(int)
    def scroll(self, data):
        if data > self._last:
            self.up()
        elif data < self._last:
            self.down()
        self._last = data

    # moves the selection up
    # used with MPG scrolling
    def up(self):
        self.select_row('up')

    # moves the selection down
    # used with MPG scrolling
    def down(self):
        self.select_row('down')

    def select_row(self, style='down'):
        style = style.lower()
        if self.list.isVisible():
            i = self.list.rootIndex()
            selectionModel = self.list.selectionModel()
        else:
            i = self.table.rootIndex()
            selectionModel = self.table.selectionModel()

        row = selectionModel.currentIndex().row()
        self.rows = self.model.rowCount(i)

        if style == 'last':
            row = self.rows
        elif style == 'up':
            if row > 0:
                row -= 1
            else:
                row = 0
        elif style == 'down':
            if row < self.rows - 1:
                row += 1
            else:
                row = self.rows - 1
        else:
            return
        top = self.model.index(row, 0, i)
        selectionModel.setCurrentIndex(
            top, QItemSelectionModel.Select | QItemSelectionModel.Rows)
        selection = QItemSelection(top, top)
        selectionModel.clearSelection()
        selectionModel.select(selection, QItemSelectionModel.Select)

    # returns the current highlighted (selected) path as well as
    # whether it's a file or not.
    def getCurrentSelected(self):
        if self.list.isVisible():
            selectionModel = self.list.selectionModel()
        else:
            selectionModel = self.table.selectionModel()
        index = selectionModel.currentIndex()
        dir_path = os.path.normpath(self.model.filePath(index))
        if self.model.fileInfo(index).isFile():
            return (dir_path, True)
        else:
            return (dir_path, False)

    # This can be class patched to do something else
    def load(self, fname=None):
        try:
            if fname is None:
                self._getPathActivated()
                return
            self.recordBookKeeping()
            ACTION.OPEN_PROGRAM(fname)
            STATUS.emit('update-machine-log', 'Loaded: ' + fname, 'TIME')
        except Exception as e:
            LOG.error("Load file error: {}".format(e))
            STATUS.emit('error', NML_ERROR, "Load file error: {}".format(e))

    # This can be class patched to do something else
    def recordBookKeeping(self):
        fname = self.currentPath
        if fname is None:
            return
        if self.PREFS_:
            self.PREFS_.putpref('last_loaded_directory', self.model.rootPath(),
                                str, 'BOOK_KEEPING')
            self.PREFS_.putpref('RecentPath_0', fname, str, 'BOOK_KEEPING')
Exemple #6
0
class mainwindow(QWidget):
    def __init__(self, data_path, parent=None):
        super(mainwindow, self).__init__(parent)

        folders = os.listdir(data_path)

        self.mainLayout = QGridLayout(self)
        self.btn_layout = QGridLayout()
        self.buttons = []
        for num, folder in enumerate(folders):
            i = num % 4
            j = num // 4
            folder_button = QPushButton(folder)
            self.buttons.append(folder_button)
            self.btn_layout.addWidget(folder_button, j, i)
            folder_button.setFixedSize(200, 30)
            folder_button.clicked.connect(partial(self.dispatcher, folder))

        self.main_minue = QPushButton("back")
        self.btn_layout.addWidget(self.main_minue, 0, 0)
        self.main_minue.setFixedSize(200, 30)
        self.main_minue.hide()
        self.main_minue.clicked.connect(partial(self.main_layout, folder))

        self.projectView = QTableView()
        self.mainLayout.addWidget(self.projectView, 1, 0)
        self.projectView.showMaximized()
        self.projectView.hide()

        self.listWidget = QListWidget(self)
        self.mainLayout.addWidget(self.listWidget, 1, 0)
        self.listWidget.hide()

        self.status_push = QPushButton("Status")
        self.btn_layout.addWidget(self.status_push, 0, 2)
        self.status_push.setFixedSize(200, 30)
        self.status_push.hide()

        self.search_push = QPushButton("Search")
        self.btn_layout.addWidget(self.search_push, 0, 1)
        self.search_push.setFixedSize(200, 30)
        self.search_push.hide()
        self.search_push.clicked.connect(partial(self.search_fn, folder))

        self.search_counter = 0
        txt = "please entre search text"
        self.search_lable = QLabel(txt)
        self.btn_layout.setContentsMargins(0, 0, 0, 0)
        self.btn_layout.addWidget(self.search_lable, 1, 0)
        self.search_lable.setFixedSize(200, 30)
        self.search_lable.hide()

        self.comboBox = QComboBox()
        self.comboBox.setObjectName(("comboBox"))
        self.btn_layout.addWidget(self.comboBox, 1, 2)

        self.comboBox.hide()

        self.search_line_edit = QLineEdit(self)
        self.btn_layout.addWidget(self.search_line_edit, 1, 1)
        self.search_line_edit.hide()

        self.search_line_edit.setFixedSize(200, 30)

        self.status_push.clicked.connect(self.show_status)

        self.back_push = QPushButton("Back")
        self.back_push.clicked.connect(partial(self.close_all, folder))
        self.back_push.setFixedSize(200, 30)

        self.show_report = QPushButton("Show Report")
        self.show_report.clicked.connect(self.get_report)
        self.show_report.setFixedSize(200, 30)

        self.btn_layout.addWidget(self.back_push, 0, 0)
        self.btn_layout.addWidget(self.show_report, 0, 1)

        self.mainLayout.addLayout(self.btn_layout, 0, 0)

        self.back_push.hide()
        self.show_report.hide()

        self.setGeometry(0, 0, 1000, 1000)

        self.imageLabel = QLabel(self)
        self.imageLabel.hide()
        self.setWindowTitle("Stations")

        self.mainLayout.addWidget(self.imageLabel, 1, 0)

    def search_fn(self, folder):
        filename = main_path + "/" + folder + "/" + folder + ".xls"
        df = pd.read_excel(filename)
        header = list(df)
        for h in header:
            self.comboBox.addItem(h)
        if self.search_counter == 0:
            self.search_line_edit.show()
            self.comboBox.show()
            self.search_counter += 1
        else:
            txt = self.search_line_edit.text()
            self.search_lable.hide()
            if txt == "":
                self.search_lable.show()

            else:
                self.search_counter = 2
                df = df.replace(np.nan, '', regex=True)
                df = df.applymap(str)
                h = str(self.comboBox.currentText())
                df1 = df[df[h].str.contains(txt)]
                tmp_filename = "tmp/tmp.csv"
                df1.to_csv(tmp_filename, index=False)
                self.show_table(tmp_filename)

    def main_layout(self, folder):
        if self.search_counter == 1:
            self.search_counter = 0
            self.search_line_edit.hide()
            self.search_line_edit.setText("")
            self.search_lable.hide()
            self.comboBox.hide()
        elif self.search_counter == 2:
            self.dispatcher(folder)
            self.search_counter = 1
            self.search_line_edit.setText("")
        else:

            self.setWindowTitle("Stations")
            self.projectView.hide()
            self.status_push.hide()
            self.search_push.hide()

            for folder in self.buttons:
                folder.show()
            self.main_minue.hide()

    def dispatcher(self, folder):
        filename = main_path + "/" + folder + "/" + folder + ".xls"
        # self.filename = filename
        self.setWindowTitle(folder + "  Stations")
        self.show_table(filename)

    def show_status(self):

        labels = 'Done', 'half year target'
        sizes = [35, 65]
        explode = (0.1, 0.0)

        fig1, ax1 = plt.subplots()
        ax1.pie(sizes,
                explode=explode,
                labels=labels,
                autopct='%1.1f%%',
                shadow=True,
                startangle=90)
        ax1.axis('equal')

        plt.show()

    def get_report(self):
        image_name = str(self.record[-1]) + ".jpg"

        if image_name in os.listdir("reports"):
            image_path = "reports" + "/" + image_name
            image = QtGui.QImage(image_path)

            self.qpixmap = QtGui.QPixmap.fromImage(image)
            self.imageLabel.setPixmap(self.qpixmap)

        else:
            img = Image.new('RGB', (700, 600), (0, 0, 0))

            draw = ImageDraw.Draw(img)
            draw.text((60, 250),
                      "No Photo To Display for station number " +
                      str(self.record[-1]),
                      fill='rgb(255, 255, 255)',
                      font=ImageFont.truetype(
                          "/usr/share/fonts/dejavu/DejaVuSans.ttf", 25))

            img = img.convert("RGBA")
            data = img.tobytes("raw", "RGBA")

            qim = QtGui.QImage(data, img.size[0], img.size[1],
                               QtGui.QImage.Format_ARGB32)
            pix = QtGui.QPixmap.fromImage(qim)

            self.imageLabel.setPixmap(pix)

        self.listWidget.clear()
        self.listWidget.close()
        self.show_report.hide()
        self.imageLabel.show()
        self.main_minue.hide()

    def show_table(self, filename):
        self.projectModel = PandasModel(filename)

        self.projectView.setModel(self.projectModel)

        self.projectView.doubleClicked.connect(self.show_details)
        self.projectView.show()
        self.status_push.show()
        self.search_push.show()
        self.main_minue.show()
        for folder in self.buttons:
            folder.hide()

    def show_details(self, clickedIndex):

        self.projectView.hide()
        self.status_push.hide()
        self.search_push.hide()
        self.search_line_edit.hide()
        self.search_lable.hide()
        self.comboBox.hide()
        self.main_minue.hide()
        df = self.projectModel._data
        header = list(df)
        self.record = df.values.tolist()[clickedIndex.row()]

        self.listWidget.clear()
        self.listWidget.show()
        self.back_push.show()
        self.show_report.show()

        txt = ""
        for h, d in zip(header, self.record):
            txt += str(h) + ": " + str(d) + "\n\n"

        it = QListWidgetItem(txt)

        font = QtGui.QFont()
        font.setBold(True)
        font.setWeight(200)
        font.setPointSize(12)
        it.setFont(font)
        it.setBackground(QtGui.QBrush(QtGui.QColor("lightblue")))
        it.setTextAlignment(Qt.AlignRight)

        self.listWidget.addItem(it)

    def close_all(self, folder):
        self.search_line_edit.clear()
        self.search_counter = 0
        self.listWidget.clear()
        self.listWidget.close()
        self.back_push.hide()
        self.show_report.hide()
        self.imageLabel.hide()
        filename = main_path + "/" + folder + "/" + folder + ".xls"
        self.show_table(filename)