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