def data(self, index, role=Qt.DisplayRole): if index.parent().isValid() and \ index.parent().column() == Plot.steps and \ index.column() == PlotStep.meta: if role == Qt.TextAlignmentRole: return Qt.AlignRight | Qt.AlignVCenter elif role == Qt.ForegroundRole: return QBrush(Qt.gray) else: return QStandardItemModel.data(self, index, role) else: return QStandardItemModel.data(self, index, role)
def data(self, index, role=Qt.EditRole): level = 0 i = index while i.parent() != QModelIndex(): i = i.parent() level += 1 if role == Qt.BackgroundRole: if level == 0: return QBrush(lightBlue()) if role == Qt.TextAlignmentRole: if level == 0: return Qt.AlignCenter if role == Qt.FontRole: if level in [0, 1]: f = qApp.font() f.setBold(True) return f if role == Qt.ForegroundRole: if level == 0: return QBrush(Qt.darkBlue) if role == Qt.SizeHintRole: fm = QFontMetrics(qApp.font()) h = fm.height() if level == 0: return QSize(0, h + 12) elif level == 1: return QSize(0, h + 6) return QStandardItemModel.data(self, index, role)
class ListCategory(QSortFilterProxyModel): """Expose a list of items as a category for the CompletionModel.""" def __init__(self, name, items, delete_func=None, parent=None): super().__init__(parent) self.name = name self.srcmodel = QStandardItemModel(parent=self) self._pattern = '' # ListCategory filters all columns self.columns_to_filter = [0, 1, 2] self.setFilterKeyColumn(-1) for item in items: self.srcmodel.appendRow([QStandardItem(x) for x in item]) self.setSourceModel(self.srcmodel) self.delete_func = delete_func def set_pattern(self, val): """Setter for pattern. Args: val: The value to set. """ self._pattern = val val = re.sub(r' +', r' ', val) # See #1919 val = re.escape(val) val = val.replace(r'\ ', '.*') rx = QRegExp(val, Qt.CaseInsensitive) self.setFilterRegExp(rx) self.invalidate() sortcol = 0 self.sort(sortcol) def lessThan(self, _lindex, rindex): """Custom sorting implementation. Prefers all items which start with self._pattern. Other than that, keep items in their original order. Args: _lindex: The QModelIndex of the left item (*left* < right) rindex: The QModelIndex of the right item (left < *right*) Return: True if left < right, else False """ qtutils.ensure_valid(rindex) right = self.srcmodel.data(rindex) return not right.startswith(self._pattern)
class comics_project_manager_docker(DockWidget): setupDictionary = {} stringName = i18n("Comics Manager") projecturl = None def __init__(self): super().__init__() self.setWindowTitle(self.stringName) # Setup layout: base = QHBoxLayout() widget = QWidget() widget.setLayout(base) baseLayout = QSplitter() base.addWidget(baseLayout) self.setWidget(widget) buttonLayout = QVBoxLayout() buttonBox = QWidget() buttonBox.setLayout(buttonLayout) baseLayout.addWidget(buttonBox) # Comic page list and pages model self.comicPageList = QTableView() self.comicPageList.verticalHeader().setSectionsMovable(True) self.comicPageList.verticalHeader().setDragEnabled(True) self.comicPageList.verticalHeader().setDragDropMode(QAbstractItemView.InternalMove) self.comicPageList.setAcceptDrops(True) self.pagesModel = QStandardItemModel() self.comicPageList.doubleClicked.connect(self.slot_open_page) self.comicPageList.setIconSize(QSize(128, 128)) # self.comicPageList.itemDelegate().closeEditor.connect(self.slot_write_description) self.pagesModel.layoutChanged.connect(self.slot_write_config) self.pagesModel.rowsInserted.connect(self.slot_write_config) self.pagesModel.rowsRemoved.connect(self.slot_write_config) self.comicPageList.verticalHeader().sectionMoved.connect(self.slot_write_config) self.comicPageList.setModel(self.pagesModel) pageBox = QWidget() pageBox.setLayout(QVBoxLayout()) zoomSlider = QSlider(Qt.Horizontal, None) zoomSlider.setRange(1, 8) zoomSlider.setValue(4) zoomSlider.setTickInterval(1) zoomSlider.setMinimumWidth(10) zoomSlider.valueChanged.connect(self.slot_scale_thumbnails) self.projectName = Elided_Text_Label() pageBox.layout().addWidget(self.projectName) pageBox.layout().addWidget(zoomSlider) pageBox.layout().addWidget(self.comicPageList) baseLayout.addWidget(pageBox) self.btn_project = QToolButton() self.btn_project.setPopupMode(QToolButton.MenuButtonPopup) self.btn_project.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) menu_project = QMenu() self.action_new_project = QAction(i18n("New Project"), None) self.action_new_project.triggered.connect(self.slot_new_project) self.action_load_project = QAction(i18n("Open Project"), None) self.action_load_project.triggered.connect(self.slot_open_config) menu_project.addAction(self.action_new_project) menu_project.addAction(self.action_load_project) self.btn_project.setMenu(menu_project) self.btn_project.setDefaultAction(self.action_load_project) buttonLayout.addWidget(self.btn_project) # Settings dropdown with actions for the different settings menus. self.btn_settings = QToolButton() self.btn_settings.setPopupMode(QToolButton.MenuButtonPopup) self.btn_settings.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.action_edit_project_settings = QAction(i18n("Project Settings"), None) self.action_edit_project_settings.triggered.connect(self.slot_edit_project_settings) self.action_edit_meta_data = QAction(i18n("Meta Data"), None) self.action_edit_meta_data.triggered.connect(self.slot_edit_meta_data) self.action_edit_export_settings = QAction(i18n("Export Settings"), None) self.action_edit_export_settings.triggered.connect(self.slot_edit_export_settings) menu_settings = QMenu() menu_settings.addAction(self.action_edit_project_settings) menu_settings.addAction(self.action_edit_meta_data) menu_settings.addAction(self.action_edit_export_settings) self.btn_settings.setDefaultAction(self.action_edit_project_settings) self.btn_settings.setMenu(menu_settings) buttonLayout.addWidget(self.btn_settings) self.btn_settings.setDisabled(True) # Add page drop down with different page actions. self.btn_add_page = QToolButton() self.btn_add_page.setPopupMode(QToolButton.MenuButtonPopup) self.btn_add_page.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.action_add_page = QAction(i18n("Add Page"), None) self.action_add_page.triggered.connect(self.slot_add_new_page_single) self.action_add_template = QAction(i18n("Add Page From Template"), None) self.action_add_template.triggered.connect(self.slot_add_new_page_from_template) self.action_add_existing = QAction(i18n("Add Existing Pages"), None) self.action_add_existing.triggered.connect(self.slot_add_page_from_url) self.action_remove_selected_page = QAction(i18n("Remove Page"), None) self.action_remove_selected_page.triggered.connect(self.slot_remove_selected_page) self.action_resize_all_pages = QAction(i18n("Batch Resize"), None) self.action_resize_all_pages.triggered.connect(self.slot_batch_resize) self.btn_add_page.setDefaultAction(self.action_add_page) self.action_show_page_viewer = QAction(i18n("View Page In Window"), None) self.action_show_page_viewer.triggered.connect(self.slot_show_page_viewer) self.action_scrape_authors = QAction(i18n("Scrape Author Info"), None) self.action_scrape_authors.setToolTip(i18n("Search for author information in documents and add it to the author list. This doesn't check for duplicates.")) self.action_scrape_authors.triggered.connect(self.slot_scrape_author_list) actionList = [] menu_page = QMenu() actionList.append(self.action_add_page) actionList.append(self.action_add_template) actionList.append(self.action_add_existing) actionList.append(self.action_remove_selected_page) actionList.append(self.action_resize_all_pages) actionList.append(self.action_show_page_viewer) actionList.append(self.action_scrape_authors) menu_page.addActions(actionList) self.btn_add_page.setMenu(menu_page) buttonLayout.addWidget(self.btn_add_page) self.btn_add_page.setDisabled(True) self.comicPageList.setContextMenuPolicy(Qt.ActionsContextMenu) self.comicPageList.addActions(actionList) # Export button that... exports. self.btn_export = QPushButton(i18n("Export Comic")) self.btn_export.clicked.connect(self.slot_export) buttonLayout.addWidget(self.btn_export) self.btn_export.setDisabled(True) self.btn_project_url = QPushButton(i18n("Copy Location")) self.btn_project_url.setToolTip(i18n("Copies the path of the project to the clipboard. Useful for quickly copying to a file manager or the like.")) self.btn_project_url.clicked.connect(self.slot_copy_project_url) self.btn_project_url.setDisabled(True) buttonLayout.addWidget(self.btn_project_url) self.page_viewer_dialog = comics_project_page_viewer.comics_project_page_viewer() Application.notifier().imageSaved.connect(self.slot_check_for_page_update) buttonLayout.addItem(QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.MinimumExpanding)) """ Open the config file and load the json file into a dictionary. """ def slot_open_config(self): self.path_to_config = QFileDialog.getOpenFileName(caption=i18n("Please select the json comic config file."), filter=str(i18n("json files") + "(*.json)"))[0] if os.path.exists(self.path_to_config) is True: configFile = open(self.path_to_config, "r", newline="", encoding="utf-16") self.setupDictionary = json.load(configFile) self.projecturl = os.path.dirname(str(self.path_to_config)) configFile.close() self.load_config() """ Further config loading. """ def load_config(self): self.projectName.setMainText(text=str(self.setupDictionary["projectName"])) self.fill_pages() self.btn_settings.setEnabled(True) self.btn_add_page.setEnabled(True) self.btn_export.setEnabled(True) self.btn_project_url.setEnabled(True) """ Fill the pages model with the pages from the pages list. """ def fill_pages(self): self.loadingPages = True self.pagesModel.clear() self.pagesModel.setHorizontalHeaderLabels([i18n("Page"), i18n("Description")]) pagesList = [] if "pages" in self.setupDictionary.keys(): pagesList = self.setupDictionary["pages"] progress = QProgressDialog() progress.setMinimum(0) progress.setMaximum(len(pagesList)) progress.setWindowTitle(i18n("Loading pages...")) for url in pagesList: absurl = os.path.join(self.projecturl, url) if (os.path.exists(absurl)): #page = Application.openDocument(absurl) page = zipfile.ZipFile(absurl, "r") thumbnail = QImage.fromData(page.read("preview.png")) pageItem = QStandardItem() dataList = self.get_description_and_title(page.read("documentinfo.xml")) if (dataList[0].isspace() or len(dataList[0]) < 1): dataList[0] = os.path.basename(url) pageItem.setText(dataList[0]) pageItem.setDragEnabled(True) pageItem.setDropEnabled(False) pageItem.setEditable(False) pageItem.setIcon(QIcon(QPixmap.fromImage(thumbnail))) pageItem.setToolTip(url) page.close() description = QStandardItem() description.setText(dataList[1]) description.setEditable(False) listItem = [] listItem.append(pageItem) listItem.append(description) self.pagesModel.appendRow(listItem) self.comicPageList.resizeRowsToContents() self.comicPageList.resizeColumnToContents(0) self.comicPageList.setColumnWidth(1, 256) progress.setValue(progress.value() + 1) progress.setValue(len(pagesList)) self.loadingPages = False """ Function that is triggered by the zoomSlider Resizes the thumbnails. """ def slot_scale_thumbnails(self, multiplier=4): self.comicPageList.setIconSize(QSize(multiplier * 32, multiplier * 32)) self.comicPageList.resizeRowsToContents() """ Function that takes the documentinfo.xml and parses it for the title, subject and abstract tags, to get the title and description. @returns a stringlist with the name on 0 and the description on 1. """ def get_description_and_title(self, string): xmlDoc = ET.fromstring(string) calligra = str("{http://www.calligra.org/DTD/document-info}") name = "" if ET.iselement(xmlDoc[0].find(calligra + 'title')): name = xmlDoc[0].find(calligra + 'title').text if name is None: name = " " desc = "" if ET.iselement(xmlDoc[0].find(calligra + 'subject')): desc = xmlDoc[0].find(calligra + 'subject').text if desc is None or desc.isspace() or len(desc) < 1: if ET.iselement(xmlDoc[0].find(calligra + 'abstract')): desc = str(xmlDoc[0].find(calligra + 'abstract').text) if desc.startswith("<![CDATA["): desc = desc[len("<![CDATA["):] if desc.startswith("]]>"): desc = desc[:-len("]]>")] return [name, desc] """ Scrapes authors from the author data in the document info and puts them into the author list. Doesn't check for duplicates. """ def slot_scrape_author_list(self): listOfAuthors = [] if "authorList" in self.setupDictionary.keys(): listOfAuthors = self.setupDictionary["authorList"] if "pages" in self.setupDictionary.keys(): for relurl in self.setupDictionary["pages"]: absurl = os.path.join(self.projecturl, relurl) page = zipfile.ZipFile(absurl, "r") xmlDoc = ET.fromstring(page.read("documentinfo.xml")) calligra = str("{http://www.calligra.org/DTD/document-info}") authorelem = xmlDoc.find(calligra + 'author') author = {} if ET.iselement(authorelem.find(calligra + 'full-name')): author["nickname"] = str(authorelem.find(calligra + 'full-name').text) if ET.iselement(authorelem.find(calligra + 'email')): author["email"] = str(authorelem.find(calligra + 'email').text) if ET.iselement(authorelem.find(calligra + 'position')): author["role"] = str(authorelem.find(calligra + 'position').text) listOfAuthors.append(author) page.close() self.setupDictionary["authorList"] = listOfAuthors """ Edit the general project settings like the project name, concept, pages location, export location, template location, metadata """ def slot_edit_project_settings(self): dialog = comics_project_settings_dialog.comics_project_details_editor(self.projecturl) dialog.setConfig(self.setupDictionary, self.projecturl) if dialog.exec_() == QDialog.Accepted: self.setupDictionary = dialog.getConfig(self.setupDictionary) self.slot_write_config() self.setWindowTitle(self.stringName + ": " + str(self.setupDictionary["projectName"])) """ This allows users to select existing pages and add them to the pages list. The pages are currently not copied to the pages folder. Useful for existing projects. """ def slot_add_page_from_url(self): # get the pages. urlList = QFileDialog.getOpenFileNames(caption=i18n("Which existing pages to add?"), directory=self.projecturl, filter=str(i18n("Krita files") + "(*.kra)"))[0] # get the existing pages list. pagesList = [] if "pages" in self.setupDictionary.keys(): pagesList = self.setupDictionary["pages"] # And add each url in the url list to the pages list and the model. for url in urlList: if self.projecturl not in urlList: newUrl = os.path.join(self.projecturl, self.setupDictionary["pagesLocation"], os.path.basename(url)) shutil.move(url, newUrl) url = newUrl relative = os.path.relpath(url, self.projecturl) if url not in pagesList: page = zipfile.ZipFile(url, "r") thumbnail = QImage.fromData(page.read("preview.png")) dataList = self.get_description_and_title(page.read("documentinfo.xml")) if (dataList[0].isspace() or len(dataList[0]) < 1): dataList[0] = os.path.basename(url) newPageItem = QStandardItem() newPageItem.setIcon(QIcon(QPixmap.fromImage(thumbnail))) newPageItem.setDragEnabled(True) newPageItem.setDropEnabled(False) newPageItem.setEditable(False) newPageItem.setText(dataList[0]) newPageItem.setToolTip(relative) page.close() description = QStandardItem() description.setText(dataList[1]) description.setEditable(False) listItem = [] listItem.append(newPageItem) listItem.append(description) self.pagesModel.appendRow(listItem) self.comicPageList.resizeRowsToContents() self.comicPageList.resizeColumnToContents(0) """ Remove the selected page from the list of pages. This does not remove it from disk(far too dangerous). """ def slot_remove_selected_page(self): index = self.comicPageList.currentIndex() self.pagesModel.removeRow(index.row()) """ This function adds a new page from the default template. If there's no default template, or the file does not exist, it will show the create/import template dialog. It will remember the selected item as the default template. """ def slot_add_new_page_single(self): templateUrl = "templatepage" templateExists = False if "singlePageTemplate" in self.setupDictionary.keys(): templateUrl = self.setupDictionary["singlePageTemplate"] if os.path.exists(os.path.join(self.projecturl, templateUrl)): templateExists = True if templateExists is False: if "templateLocation" not in self.setupDictionary.keys(): self.setupDictionary["templateLocation"] = os.path.relpath(QFileDialog.getExistingDirectory(caption=i18n("Where are the templates located?"), options=QFileDialog.ShowDirsOnly), self.projecturl) templateDir = os.path.join(self.projecturl, self.setupDictionary["templateLocation"]) template = comics_template_dialog.comics_template_dialog(templateDir) if template.exec_() == QDialog.Accepted: templateUrl = os.path.relpath(template.url(), self.projecturl) self.setupDictionary["singlePageTemplate"] = templateUrl if os.path.exists(os.path.join(self.projecturl, templateUrl)): self.add_new_page(templateUrl) """ This function always asks for a template showing the new template window. This allows users to have multiple different templates created for back covers, spreads, other and have them accesible, while still having the convenience of a singular "add page" that adds a default. """ def slot_add_new_page_from_template(self): if "templateLocation" not in self.setupDictionary.keys(): self.setupDictionary["templateLocation"] = os.path.relpath(QFileDialog.getExistingDirectory(caption=i18n("Where are the templates located?"), options=QFileDialog.ShowDirsOnly), self.projecturl) templateDir = os.path.join(self.projecturl, self.setupDictionary["templateLocation"]) template = comics_template_dialog.comics_template_dialog(templateDir) if template.exec_() == QDialog.Accepted: templateUrl = os.path.relpath(template.url(), self.projecturl) self.add_new_page(templateUrl) """ This is the actual function that adds the template using the template url. It will attempt to name the new page projectName+number, and tries to get the first possible number that is not in the pages list. If such a file already exists it will only append the file. """ def add_new_page(self, templateUrl): # check for page list and or location. pagesList = [] if "pages" in self.setupDictionary.keys(): pagesList = self.setupDictionary["pages"] if (str(self.setupDictionary["pagesLocation"]).isspace()): self.setupDictionary["pagesLocation"] = os.path.relpath(QFileDialog.getExistingDirectory(caption=i18n("Where should the pages go?"), options=QFileDialog.ShowDirsOnly), self.projecturl) # Search for the possible name. extraUnderscore = str() if str(self.setupDictionary["projectName"])[-1].isdigit(): extraUnderscore = "_" pageName = str(self.setupDictionary["projectName"]) + extraUnderscore + str(format(len(pagesList), "03d")) url = os.path.join(str(self.setupDictionary["pagesLocation"]), pageName + ".kra") pageNumber = 0 if (url in pagesList): while (url in pagesList): pageNumber += 1 pageName = str(self.setupDictionary["projectName"]) + extraUnderscore + str(format(pageNumber, "03d")) url = os.path.join(str(self.setupDictionary["pagesLocation"]), pageName + ".kra") # open the page by opening the template and resaving it, or just opening it. absoluteUrl = os.path.join(self.projecturl, url) if (os.path.exists(absoluteUrl)): newPage = Application.openDocument(absoluteUrl) else: booltemplateExists = os.path.exists(os.path.join(self.projecturl, templateUrl)) if booltemplateExists is False: templateUrl = os.path.relpath(QFileDialog.getOpenFileName(caption=i18n("Which image should be the basis the new page?"), directory=self.projecturl, filter=str(i18n("Krita files") + "(*.kra)"))[0], self.projecturl) newPage = Application.openDocument(os.path.join(self.projecturl, templateUrl)) newPage.setFileName(absoluteUrl) newPage.setName(pageName) newPage.exportImage(absoluteUrl, InfoObject()) # Get out the extra data for the standard item. newPageItem = QStandardItem() newPageItem.setIcon(QIcon(QPixmap.fromImage(newPage.thumbnail(100, 100)))) newPageItem.setDragEnabled(True) newPageItem.setDropEnabled(False) newPageItem.setEditable(False) newPageItem.setText(pageName) newPageItem.setToolTip(url) # close page document. newPage.waitForDone() if newPage.isIdle(): newPage.close() # add item to page. description = QStandardItem() description.setText(str("")) description.setEditable(False) listItem = [] listItem.append(newPageItem) listItem.append(description) self.pagesModel.appendRow(listItem) self.comicPageList.resizeRowsToContents() self.comicPageList.resizeColumnToContents(0) """ Write to the json configuratin file. This also checks the current state of the pages list. """ def slot_write_config(self): # Don't load when the pages are still being loaded, otherwise we'll be overwriting our own pages list. if (self.loadingPages is False): print("CPMT: writing comic configuration...") # Generate a pages list from the pagesmodel. # Because we made the drag-and-drop use the tableview header, we need to first request the logicalIndex # for the visualIndex, and then request the items for the logical index in the pagesmodel. # After that, we rename the verticalheader to have the appropriate numbers it will have when reloading. pagesList = [] listOfHeaderLabels = [] for i in range(self.pagesModel.rowCount()): listOfHeaderLabels.append(str(i)) for i in range(self.pagesModel.rowCount()): iLogical = self.comicPageList.verticalHeader().logicalIndex(i) index = self.pagesModel.index(iLogical, 0) if index.isValid() is False: index = self.pagesModel.index(i, 0) url = str(self.pagesModel.data(index, role=Qt.ToolTipRole)) if url not in pagesList: pagesList.append(url) listOfHeaderLabels[iLogical] = str(i + 1) self.pagesModel.setVerticalHeaderLabels(listOfHeaderLabels) self.comicPageList.verticalHeader().update() self.setupDictionary["pages"] = pagesList # Save to our json file. configFile = open(self.path_to_config, "w", newline="", encoding="utf-16") json.dump(self.setupDictionary, configFile, indent=4, sort_keys=True, ensure_ascii=False) configFile.close() print("CPMT: done") """ Open a page in the pagesmodel in Krita. """ def slot_open_page(self, index): if index.column() is 0: # Get the absolute url from the relative one in the pages model. absoluteUrl = os.path.join(self.projecturl, str(self.pagesModel.data(index, role=Qt.ToolTipRole))) # Make sure the page exists. if os.path.exists(absoluteUrl): page = Application.openDocument(absoluteUrl) # Set the title to the filename if it was empty. It looks a bit neater. if page.name().isspace or len(page.name()) < 1: page.setName(str(self.pagesModel.data(index, role=Qt.DisplayRole))) # Add views for the document so the user can use it. Application.activeWindow().addView(page) Application.setActiveDocument(page) else: print("CPMT: The page cannot be opened because the file doesn't exist:", absoluteUrl) """ Call up the metadata editor dialog. Only when the dialog is "Accepted" will the metadata be saved. """ def slot_edit_meta_data(self): dialog = comics_metadata_dialog.comic_meta_data_editor() dialog.setConfig(self.setupDictionary) if (dialog.exec_() == QDialog.Accepted): self.setupDictionary = dialog.getConfig(self.setupDictionary) self.slot_write_config() """ An attempt at making the description editable from the comic pages list. It is currently not working because ZipFile has no overwrite mechanism, and I don't have the energy to write one yet. """ def slot_write_description(self, index): for row in range(self.pagesModel.rowCount()): index = self.pagesModel.index(row, 1) indexUrl = self.pagesModel.index(row, 0) absoluteUrl = os.path.join(self.projecturl, str(self.pagesModel.data(indexUrl, role=Qt.ToolTipRole))) page = zipfile.ZipFile(absoluteUrl, "a") xmlDoc = ET.ElementTree() ET.register_namespace("", "http://www.calligra.org/DTD/document-info") location = os.path.join(self.projecturl, "documentinfo.xml") xmlDoc.parse(location) xmlroot = ET.fromstring(page.read("documentinfo.xml")) calligra = "{http://www.calligra.org/DTD/document-info}" aboutelem = xmlroot.find(calligra + 'about') if ET.iselement(aboutelem.find(calligra + 'subject')): desc = aboutelem.find(calligra + 'subject') desc.text = self.pagesModel.data(index, role=Qt.EditRole) xmlstring = ET.tostring(xmlroot, encoding='unicode', method='xml', short_empty_elements=False) page.writestr(zinfo_or_arcname="documentinfo.xml", data=xmlstring) for document in Application.documents(): if str(document.fileName()) == str(absoluteUrl): document.setDocumentInfo(xmlstring) page.close() """ Calls up the export settings dialog. Only when accepted will the configuration be written. """ def slot_edit_export_settings(self): dialog = comics_export_dialog.comic_export_setting_dialog() dialog.setConfig(self.setupDictionary) if (dialog.exec_() == QDialog.Accepted): self.setupDictionary = dialog.getConfig(self.setupDictionary) self.slot_write_config() """ Export the comic. Won't work without export settings set. """ def slot_export(self): exporter = comics_exporter.comicsExporter() exporter.set_config(self.setupDictionary, self.projecturl) exportSuccess = exporter.export() if exportSuccess: print("CPMT: Export success! The files have been written to the export folder!") """ Calls up the comics project setup wizard so users can create a new json file with the basic information. """ def slot_new_project(self): setup = comics_project_setup_wizard.ComicsProjectSetupWizard() setup.showDialog() """ This is triggered by any document save. It checks if the given url in in the pages list, and if so, updates the appropriate page thumbnail. This helps with the management of the pages, because the user will be able to see the thumbnails as a todo for the whole comic, giving a good overview over whether they still need to ink, color or the like for a given page, and it thus also rewards the user whenever they save. """ def slot_check_for_page_update(self, url): if "pages" in self.setupDictionary.keys(): relUrl = os.path.relpath(url, self.projecturl) if relUrl in self.setupDictionary["pages"]: index = self.pagesModel.index(self.setupDictionary["pages"].index(relUrl), 0) index2 = self.pagesModel.index(index.row(), 1) if index.isValid(): pageItem = self.pagesModel.itemFromIndex(index) page = zipfile.ZipFile(url, "r") dataList = self.get_description_and_title(page.read("documentinfo.xml")) if (dataList[0].isspace() or len(dataList[0]) < 1): dataList[0] = os.path.basename(url) thumbnail = QImage.fromData(page.read("preview.png")) pageItem.setIcon(QIcon(QPixmap.fromImage(thumbnail))) pageItem.setText(dataList[0]) self.pagesModel.setItem(index.row(), index.column(), pageItem) self.pagesModel.setData(index2, str(dataList[1]), Qt.DisplayRole) """ Resize all the pages in the pages list. It will show a dialog with the options for resizing. Then, it will try to pop up a progress dialog while resizing. The progress dialog shows the remaining time and pages. """ def slot_batch_resize(self): dialog = QDialog() dialog.setWindowTitle(i18n("Risize all pages.")) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttons.accepted.connect(dialog.accept) buttons.rejected.connect(dialog.reject) sizesBox = comics_export_dialog.comic_export_resize_widget("Scale", batch=True, fileType=False) exporterSizes = comics_exporter.sizesCalculator() dialog.setLayout(QVBoxLayout()) dialog.layout().addWidget(sizesBox) dialog.layout().addWidget(buttons) if dialog.exec_() == QDialog.Accepted: progress = QProgressDialog(i18n("Resizing pages..."), str(), 0, len(self.setupDictionary["pages"])) progress.setWindowTitle(i18n("Resizing pages.")) progress.setCancelButton(None) timer = QElapsedTimer() timer.start() config = {} config = sizesBox.get_config(config) for p in range(len(self.setupDictionary["pages"])): absoluteUrl = os.path.join(self.projecturl, self.setupDictionary["pages"][p]) progress.setValue(p) timePassed = timer.elapsed() if (p > 0): timeEstimated = (len(self.setupDictionary["pages"]) - p) * (timePassed / p) passedString = str(int(timePassed / 60000)) + ":" + format(int(timePassed / 1000), "02d") + ":" + format(timePassed % 1000, "03d") estimatedString = str(int(timeEstimated / 60000)) + ":" + format(int(timeEstimated / 1000), "02d") + ":" + format(int(timeEstimated % 1000), "03d") progress.setLabelText(str(i18n("{pages} of {pagesTotal} done. \nTime passed: {passedString}:\n Estimated:{estimated}")).format(pages=p, pagesTotal=len(self.setupDictionary["pages"]), passedString=passedString, estimated=estimatedString)) if os.path.exists(absoluteUrl): doc = Application.openDocument(absoluteUrl) listScales = exporterSizes.get_scale_from_resize_config(config["Scale"], [doc.width(), doc.height(), doc.resolution(), doc.resolution()]) doc.scaleImage(listScales[0], listScales[1], listScales[2], listScales[3], "bicubic") doc.waitForDone() doc.save() doc.waitForDone() doc.close() def slot_show_page_viewer(self): index = self.comicPageList.currentIndex() if index.column() is not 0: index = self.pagesModel.index(index.row(), 0) # Get the absolute url from the relative one in the pages model. absoluteUrl = os.path.join(self.projecturl, str(self.pagesModel.data(index, role=Qt.ToolTipRole))) # Make sure the page exists. if os.path.exists(absoluteUrl): page = zipfile.ZipFile(absoluteUrl, "r") image = QImage.fromData(page.read("mergedimage.png")) self.page_viewer_dialog.update_image(image) self.page_viewer_dialog.show() page.close() """ Function to copy the current project location into the clipboard. This is useful for users because they'll be able to use that url to quickly move to the project location in outside applications. """ def slot_copy_project_url(self): if self.projecturl is not None: clipboard = qApp.clipboard() clipboard.setText(str(self.projecturl)) """ This is required by the dockwidget class, otherwise unused. """ def canvasChanged(self, canvas): pass
class MainWindow(QWidget): Id, Password = range(2) CONFIG_FILE = 'config' def __init__(self): super().__init__() with open(self.CONFIG_FILE, 'a'): pass self.init() def init(self): # ------ initUI self.resize(555, 245) self.setFixedSize(555, 245) self.center() self.setWindowTitle('Portal Connector') self.setWindowIcon(QIcon('gao.ico')) self.backgroundRole() palette1 = QPalette() palette1.setColor(self.backgroundRole(), QColor(250, 250, 250)) # 设置背景颜色 self.setPalette(palette1) # ------setLeftWidget self.dataGroupBox = QGroupBox("Saved", self) self.dataGroupBox.setGeometry(10, 10, 60, 20) self.dataGroupBox.setStyleSheet(MyGroupBox) self.model = QStandardItemModel(0, 2, self) self.model.setHeaderData(self.Id, Qt.Horizontal, "Id") self.model.setHeaderData(self.Password, Qt.Horizontal, "Pw") self.dataView = QTreeView(self) self.dataView.setGeometry(10, 32, 255, 150) self.dataView.setRootIsDecorated(False) self.dataView.setAlternatingRowColors(True) self.dataView.setModel(self.model) self.dataView.setStyleSheet(MyTreeView) save_btn = QPushButton('Save', self) save_btn.setGeometry(15, 195, 100, 35) save_btn.setStyleSheet(MyPushButton) delete_btn = QPushButton('Delete', self) delete_btn.setGeometry(135, 195, 100, 35) delete_btn.setStyleSheet(MyPushButton) # ------ setRightWidget username = QLabel('Id:', self) username.setGeometry(300, 45, 50, 30) username.setStyleSheet(MyLabel) self.username_edit = QLineEdit(self) self.username_edit.setGeometry(350, 40, 190, 35) self.username_edit.setStyleSheet(MyLineEdit) password = QLabel('Pw:', self) password.setGeometry(300, 100, 50, 30) password.setStyleSheet(MyLabel) self.password_edit = QLineEdit(self) self.password_edit.setGeometry(350, 95, 190, 35) self.password_edit.setStyleSheet(MyLineEdit) status_label = QLabel('Result:', self) status_label.setGeometry(295, 150, 70, 30) status_label.setStyleSheet(UnderLabel) self.status = QLabel('Disconnect', self) self.status.setGeometry(360, 150, 190, 30) self.status.setStyleSheet(UnderLabel) connect_btn = QPushButton('Connect', self) connect_btn.setGeometry(320, 195, 100, 35) connect_btn.setStyleSheet(MyPushButton) test_btn = QPushButton('Test', self) test_btn.setGeometry(440, 195, 100, 35) test_btn.setStyleSheet(MyPushButton) # ------setTabOrder self.setTabOrder(self.username_edit, self.password_edit) self.setTabOrder(self.password_edit, connect_btn) self.setTabOrder(connect_btn, test_btn) # ------setEvent self.dataView.mouseDoubleClickEvent = self.set_text self.dataView.mousePressEvent = self.set_focus delete_btn.clicked.connect(self.removeItem) connect_btn.clicked.connect(self.connect_clicked) save_btn.clicked.connect(self.save_infomation) test_btn.clicked.connect(self.test_network) self.readItem(self.CONFIG_FILE) self.connect_clicked() self.show() def connect_clicked(self): result = connect_portal(self.username_edit.text(), self.password_edit.text()) self.status.setText(result) def save_infomation(self): if self.username_edit.text() and self.password_edit.text(): try: selected = self.dataView.selectedIndexes()[0].row() self.modifyItem(selected) except IndexError: self.addItem(self.username_edit.text(), self.password_edit.text()) def test_network(self): result = test_public() self.status.setText(result) def set_text(self, event=None): try: self.username_edit.setText( self.dataView.selectedIndexes()[0].data()) self.password_edit.setText( self.dataView.selectedIndexes()[1].data()) except IndexError: pass def set_focus(self, event): index = self.dataView.indexAt(event.pos()) if not index.isValid(): self.dataView.clearSelection() else: self.dataView.setCurrentIndex(index) def readItem(self, filename): with open(filename, 'r') as f: for line in f.readlines(): self.addItem(*(line.split())) self.dataView.setCurrentIndex(self.dataView.indexAt(QPoint(1, 1))) self.set_text() def addItem(self, username, password): self.model.insertRow(0) self.model.setData(self.model.index(0, self.Id), username) self.model.setData(self.model.index(0, self.Password), password) self.save_to_file() def modifyItem(self, row): self.model.setData(self.model.index(row, self.Id), self.username_edit.text()) self.model.setData(self.model.index(row, self.Password), self.password_edit.text()) self.save_to_file() def removeItem(self): try: self.model.removeRow(self.dataView.selectedIndexes()[0].row()) self.save_to_file() except IndexError: pass def save_to_file(self): with open(self.CONFIG_FILE, 'w') as f: for x in range(self.model.rowCount()): for y in range(self.model.columnCount()): f.write(self.model.data(self.model.index(x, y)) + " ") f.write("\n") def center(self): qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft())
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() fileMenu = QMenu("&File", self) openAction = fileMenu.addAction("&Open...") openAction.setShortcut("Ctrl+O") saveAction = fileMenu.addAction("&Save As...") saveAction.setShortcut("Ctrl+S") quitAction = fileMenu.addAction("E&xit") quitAction.setShortcut("Ctrl+Q") self.setupModel() self.setupViews() openAction.triggered.connect(self.openFile) saveAction.triggered.connect(self.saveFile) quitAction.triggered.connect(QApplication.instance().quit) self.menuBar().addMenu(fileMenu) self.statusBar() self.openFile(':/Charts/qtdata.cht') self.setWindowTitle("Chart") self.resize(870, 550) def setupModel(self): self.model = QStandardItemModel(8, 2, self) self.model.setHeaderData(0, Qt.Horizontal, "Label") self.model.setHeaderData(1, Qt.Horizontal, "Quantity") def setupViews(self): splitter = QSplitter() table = QTableView() self.pieChart = PieView() splitter.addWidget(table) splitter.addWidget(self.pieChart) splitter.setStretchFactor(0, 0) splitter.setStretchFactor(1, 1) table.setModel(self.model) self.pieChart.setModel(self.model) self.selectionModel = QItemSelectionModel(self.model) table.setSelectionModel(self.selectionModel) self.pieChart.setSelectionModel(self.selectionModel) table.horizontalHeader().setStretchLastSection(True) self.setCentralWidget(splitter) def openFile(self, path=None): if not path: path, _ = QFileDialog.getOpenFileName(self, "Choose a data file", '', '*.cht') if path: f = QFile(path) if f.open(QFile.ReadOnly | QFile.Text): stream = QTextStream(f) self.model.removeRows(0, self.model.rowCount(QModelIndex()), QModelIndex()) row = 0 line = stream.readLine() while line: self.model.insertRows(row, 1, QModelIndex()) pieces = line.split(',') self.model.setData(self.model.index(row, 0, QModelIndex()), pieces[0]) self.model.setData(self.model.index(row, 1, QModelIndex()), float(pieces[1])) self.model.setData(self.model.index(row, 0, QModelIndex()), QColor(pieces[2]), Qt.DecorationRole) row += 1 line = stream.readLine() f.close() self.statusBar().showMessage("Loaded %s" % path, 2000) def saveFile(self): fileName, _ = QFileDialog.getSaveFileName(self, "Save file as", '', '*.cht') if fileName: f = QFile(fileName) if f.open(QFile.WriteOnly | QFile.Text): for row in range(self.model.rowCount(QModelIndex())): pieces = [] pieces.append( self.model.data( self.model.index(row, 0, QModelIndex()), Qt.DisplayRole)) pieces.append('%g' % self.model.data( self.model.index(row, 1, QModelIndex()), Qt.DisplayRole)) pieces.append( self.model.data( self.model.index(row, 0, QModelIndex()), Qt.DecorationRole).name()) f.write(b','.join([p.encode('utf-8') for p in pieces])) f.write(b'\n') f.close() self.statusBar().showMessage("Saved %s" % fileName, 2000)
class gcodeFile(QObject): ''' Gestion d'un fichier GCode dans la QListView de l'interface graphique reservee a cet usage Methodes : - __init__(QListView) -> Initialise et definit les elements de l'UI qui recevront le contenu du fichier - showFileOpen() -> Affiche la boite de dialogue d'ouverture - showFileSave() -> Affiche la boite de dialogue d'enregistrement - readFile(filePath) -> Charge un fichier dans la QListView - saveFile(filePath) -> Enregistre le contenu de la QListView dans un fichier - closeFile() -> Vide la QListView - setGcodeChanged(bool) -> Definit si le contenu de la liste a ete modifie depuis la lecture ou l'enregistrement du fichier - bool = gcodeChanged() -> Renvoi vrai si le contenu de la liste a ete modifie depuis la lecture ou l'enregistrement du fichier ''' sig_log = pyqtSignal(int, str) # Message de fonctionnement du composant def __init__(self, gcodeFileUi: QListView): super().__init__() self.__filePath = "" self.__gcodeFileUi = gcodeFileUi self.__gcodeFileUiModel = QStandardItemModel(self.__gcodeFileUi) self.__gcodeFileUiModel.itemChanged.connect(self.on_gcodeChanged) self.__gcodeCharge = False self.__gcodeChanged = False def showFileOpen(self): ''' Affiche la boite de dialogue d'ouverture ''' ''' if sys.platform == 'linux': # Prépare la boite de dialogue dialog = Gtk.FileChooserDialog( self.tr("Open a GCode file"), self, Gtk.FileChooserAction.OPEN, ( Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK ) ) dialog.set_local_only(False) # Permet l'affichage des fichiers réseaux sous Linux GTK+3 dialog_filter = Gtk.FileFilter() dialog_filter.set_name(self.tr("GCode file")) dialog_filter.add_pattern("*.gcode") dialog_filter.add_pattern("*.ngc") dialog_filter.add_pattern("*.nc") dialog_filter.add_pattern("*.gc") dialog_filter.add_pattern("*.cnc") dialog.add_filter(dialog_filter) # Affiche la boite de dialogue response = dialog.run() # Traite la réponse if response == Gtk.ResponseType.OK: print("Open clicked") print("File selected: " + dialog.get_filename()) elif response == Gtk.ResponseType.CANCEL: print("Cancel clicked") # Libère les ressources dialog.destroy() else: # sys.platform == 'linux' ''' opt = QtWidgets.QFileDialog.Options() ###opt |= QtWidgets.QFileDialog.DontUseNativeDialog fName = QtWidgets.QFileDialog.getOpenFileName( None, self.tr("Open a GCode file"), "", self.tr("GCode file (*.gcode *.ngc *.nc *.gc *.cnc)"), options=opt) return fName def readFile(self, filePath: str): self.sig_log.emit(logSeverity.info.value, self.tr("Reading file: {}").format(filePath)) try: f = open(filePath, 'r') lignes = f.readlines() f.close() self.sig_log.emit( logSeverity.info.value, self.tr("{} lines in the file").format(len(lignes))) # Envoi du contenu dans la liste self.__gcodeFileUiModel.clear() for l in lignes: item = QStandardItem(l.strip()) self.__gcodeFileUiModel.appendRow(item) self.__gcodeFileUi.setModel(self.__gcodeFileUiModel) # Selectionne la premiere ligne du fichier dans la liste self.selectGCodeFileLine(0) # Selectionne l'onglet du fichier except Exception as e: self.sig_log.emit( logSeverity.error.value, self.tr("Reading file error: {}").format(filePath)) self.sig_log.emit(logSeverity.error.value, str(e)) self.__gcodeFileUiModel.clear() self.__filePath = "" self.__gcodeChanged = False return False # Pas d'erreur self.__gcodeCharge = True self.__filePath = filePath self.__gcodeChanged = False return True def isFileLoaded(self): return self.__gcodeCharge def filePath(self): return self.__filePath def fileName(self): return os.path.basename(self.__filePath) def selectGCodeFileLine(self, num: int): ''' Selectionne un element de la liste du fichier GCode ''' idx = self.__gcodeFileUiModel.index(num, 0, QModelIndex()) self.__gcodeFileUi.selectionModel().clearSelection() self.__gcodeFileUi.selectionModel().setCurrentIndex( idx, QItemSelectionModel.SelectCurrent) def getGCodeSelectedLine(self): ''' Renvoie le N° (0 base) de la ligne selectionnee dans la liste GCode et les donnees de cette ligne. ''' idx = self.__gcodeFileUi.selectionModel().selectedIndexes() return [idx[0].row(), self.__gcodeFileUiModel.data(idx[0])] def saveAs(self): fName = self.showFileSave() if fName[0] != "": self.sig_log.emit(logSeverity.info.value, self.tr("saveAs({})").format(fName[0])) self.saveFile(fName[0]) else: self.sig_log.emit(logSeverity.info.value, self.tr("saveAs() canceled!")) def showFileSave(self): ''' Affiche la boite de dialogue "Save as" ''' opt = QtWidgets.QFileDialog.Options() opt |= QtWidgets.QFileDialog.DontUseNativeDialog fName = QtWidgets.QFileDialog.getSaveFileName( None, self.tr("Save GCode file"), "", self.tr("GCode file (*.gcode *.ngc *.nc *.gc *.cnc)"), options=opt) return fName def saveFile(self, filePath: str = ""): if filePath == "": if self.__filePath == "": # Le nom du fichier n'est pas definit, il n'y a pas de fichier charge, donc, rien a sauvegarder ! return else: filePath = self.__filePath self.sig_log.emit(logSeverity.info.value, self.tr("Saving file: {}").format(filePath)) try: f = open(filePath, 'w') for I in range(self.__gcodeFileUiModel.rowCount()): idx = self.__gcodeFileUiModel.index(I, 0, QModelIndex()) if self.__gcodeFileUiModel.data(idx) != "": f.write(self.__gcodeFileUiModel.data(idx) + '\n') f.close() self.__filePath = filePath except Exception as e: self.sig_log.emit(logSeverity.error.value, self.tr("Save file error: {}").format(filePath)) self.sig_log.emit(logSeverity.error.value, str(e)) # Supprime les lignes vides dans la grille d'affichage self.delEmptyRow() # Reinit du flag fichier change self.__gcodeChanged = False def enQueue(self, com: grblCom, startLine: int = 0, endLine: int = -1): """ Envoi des lignes de startLine a endLine dans la file d'attente du grblCom """ if endLine == -1: endLine = self.__gcodeFileUiModel.rowCount() for I in range(startLine, endLine + 1): idx = self.__gcodeFileUiModel.index(I, 0, QModelIndex()) if self.__gcodeFileUiModel.data(idx) != "": com.gcodePush(self.__gcodeFileUiModel.data(idx)) com.gcodePush(CMD_GRBL_GET_GCODE_STATE, COM_FLAG_NO_OK) def delEmptyRow(self): """ Elimination des lignes GCode vides """ for I in reversed(range(self.__gcodeFileUiModel.rowCount())): # On commence par la fin pour pouvoir supprimer sans tout decaler pour la suite idx = self.__gcodeFileUiModel.index(I, 0, QModelIndex()) if self.__gcodeFileUiModel.data(idx) == "": self.__gcodeFileUiModel.removeRow(I) def deleteGCodeFileLine(self, num: int): self.__gcodeFileUiModel.removeRow(num) self.__gcodeChanged = True def insertGCodeFileLine(self, num: int): item = QStandardItem("") self.__gcodeFileUiModel.insertRow(num, item) def addGCodeFileLine(self, num: int): item = QStandardItem("") self.__gcodeFileUiModel.insertRow(num + 1, item) def showConfirmChangeLost(self): m = msgBox( title=self.tr("Save Changes"), text=self.tr("Do you want to save the changes before closing?"), info=self. tr("If you do not save, any changes made since opening or the last save will be lost." ), icon=msgIconList.Question, stdButton=msgButtonList.Save | msgButtonList.Cancel | msgButtonList.Discard, defButton=msgButtonList.Save, escButton=msgButtonList.Cancel) return (m.afficheMsg()) def closeFile(self): if self.__gcodeChanged: # GCode modifie, on demande confirmation Ret = self.showConfirmChangeLost() if Ret == msgButtonList.Save: if self.__filePath == "": filePath = self.showFileSave() if filePath == "": # Annulation de la boite de dialogue return False else: self.__filePath = filePath self.saveFile(self.__filePath) return True elif Ret == msgButtonList.Discard: # Fermer le fichier consiste en vider la fenetre GCode self.__gcodeFileUiModel.clear() self.__gcodeChanged = False self.__gcodeCharge = False return True else: # Ret == msgButtonList.Cancel: return False else: # GCode non modifie, on ferme sans confirmation # Fermer le fichier consiste en vider la fenetre GCode # et a supprimer le status GCode charge. self.__gcodeFileUiModel.clear() self.__gcodeChanged = False self.__gcodeCharge = False return True @pyqtSlot("QStandardItem*") def on_gcodeChanged(self, item): self.__gcodeChanged = True def gcodeChanged(self): return self.__gcodeChanged def setGcodeChanged(self, value: bool): self.__gcodeChanged = value
class DownloadM3U8QtUI(QMainWindow, Ui_MainWindow): def __init__(self, parent=None): super(DownloadM3U8QtUI, self).__init__(parent) self.setupUi(self) self.ts_sum = 0 self.success_sum = 0 self._max_workers = 128 self._m3u8_ts_list = [] self._max_retries = 5 self._m3u8_file_name = str(time.time()) self.front_url = '' self._m3u8_url = '' self._chunk_size = 1024 self.headers = DEFAULT_HEADERS self.model_table_list = QStandardItemModel(0, 3) self.initial_download_table() self.start_download_button.clicked.connect(self.start_download_file) self.button_open_folder.clicked.connect(self.open_folder) self.download_list_table.doubleClicked.connect( self.check_download_list_ts) self.download_file_name_input.textChanged.connect( self.update_download_m3u8_file_name) self.button_merge_ts_to_mp4.clicked.connect(self.merge_m3u8_ts_2_mp4) def get_default_model_table_list(self): model_table_list = QStandardItemModel(0, 3) model_table_list.setHorizontalHeaderLabels(TS_HEADERS) return model_table_list def update_download_m3u8_file_name(self): if self.download_file_name_input.text(): self._m3u8_file_name = self.download_file_name_input.text() self.initial_download_table() def update_download_max_workers(self): if self.max_async_count_input.text(): self._max_workers = int(self.max_async_count_input.text()) def initial_download_table(self): self.model_table_list = self.get_default_model_table_list() self.download_list_table.horizontalHeader().setStretchLastSection(True) self.download_list_table.setEditTriggers( QAbstractItemView.NoEditTriggers) self.download_list_table.setModel(self.model_table_list) self.download_progress.setValue(0) # 下载文件的保存路径 def open_folder(self): directory = QFileDialog.getExistingDirectory(self, '选择文件夹') self.download_file_path_input.setText(directory) self.initial_download_table() def check_download_list_ts(self, model_index): select_data = self.model_table_list.data( self.model_table_list.index(model_index.row(), 1)) QMessageBox.information(self, '提示', '选中: {}'.format(select_data)) def parse_download_ts_list(self): self.fetch_m3u8_ts_info(self._max_retries) self.set_download_table_view() async def _start_download_ts_file(self, ts_url, file_name, max_sem): async with max_sem: await self.m3u8_download_ts(ts_url, file_name, self._max_retries) async def _start_download_file(self): max_sem = asyncio.Semaphore(self._max_workers) tasks = [] for _index_ts, _ts_url in enumerate(self._m3u8_ts_list): tasks.append( asyncio.ensure_future( self._start_download_ts_file(_ts_url, _index_ts, max_sem))) # 开始下载 def start_download_file(self): self.update_download_m3u8_file_name() self.update_download_max_workers() self.parse_download_ts_list() asyncio.ensure_future(self._start_download_file(), loop=loop) # 合并TS 为 MP4, 使用 ffmpeg def merge_m3u8_ts_2_mp4(self): if self.download_progress.text() != '100%': QMessageBox.warning(self, 'Wait a minute...', '稍等片刻,还在下载...') else: self.merge_ts_to_mp4() def merge_ts_to_mp4(self): new_m3u8_file = self.get_new_m3u8_file() merge_mp4_file = self.get_m3u8_ts_merge_mp4_file() file_folder = self.get_save_ts_file_folder() merge_cmd = 'ffmpeg -allowed_extensions ALL -i {new_m3u8_file} ' \ '-acodec copy -vcodec copy -f mp4 {merge_mp4_file}'.format( new_m3u8_file=new_m3u8_file, merge_mp4_file=merge_mp4_file ) os.system(merge_cmd) # os.system(f'rm -rf {file_folder} {new_m3u8_file}') def fetch_m3u8_ts_info(self, max_retries): if self.download_url_input.text(): m3u8_url = self.download_url_input.text() self.fetch_ts_info_from_url(m3u8_url, max_retries) else: self.fetch_ts_info_from_file(max_retries) def fetch_ts_info_from_url(self, m3u8_url, max_retries): try: res = requests.get(m3u8_url, timeout=(3, 30), verify=False, headers=self.headers) self.front_url = res.url.split(res.request.path_url)[0] if "EXT-X-STREAM-INF" in res.text: for line in res.text.split('\n'): if "#" in line or not line: continue elif re.search(r'^http', line) is not None: self._m3u8_url = line elif re.search(r'^/', line) is not None: self._m3u8_url = self.front_url + line else: self._m3u8_url = self._m3u8_url.rsplit( "/", 1)[0] + '/' + line self.fetch_ts_info_from_url(self._m3u8_url, self._max_retries) else: m3u8_text_str = res.text self.fetch_m3u8_ts_url(m3u8_text_str) except Exception as e: if max_retries > 0: self.fetch_ts_info_from_url(m3u8_url, max_retries - 1) def fetch_ts_info_from_file(self, max_retries): try: res_content = open('/home/1di0t/Videos/合并报表2.m3u8', 'r') res_content = res_content.read() self.fetch_m3u8_ts_url(res_content) except Exception as e: if max_retries > 0: self.fetch_ts_info_from_file(max_retries - 1) def get_save_ts_file_folder(self): if not self.download_file_path_input.text(): file_path_name = './{}'.format(self._m3u8_file_name) else: file_path_name = '{}/{}'.format( self.download_file_path_input.text(), self._m3u8_file_name) return file_path_name def get_m3u8_ts_merge_mp4_file(self): if not self.download_file_path_input.text(): file_path_name = './{}.mp4'.format(self._m3u8_file_name) else: file_path_name = '{}/{}.mp4'.format( self.download_file_path_input.text(), self._m3u8_file_name) return file_path_name def get_new_m3u8_file(self): if not self.download_file_path_input.text(): file_path_name = './{}.m3u8'.format(self._m3u8_file_name) else: file_path_name = '{}/{}.m3u8'.format( self.download_file_path_input.text(), self._m3u8_file_name) return file_path_name def fetch_m3u8_ts_url(self, m3u8_text_str): """ fetch ts url """ file_folder = self.get_save_ts_file_folder() new_m3u8_file = self.get_new_m3u8_file() if not os.path.exists(file_folder): os.mkdir(file_folder) new_m3u8_str = '' ts = compute_sum() for line in m3u8_text_str.split('\n'): if "#" in line: if "EXT-X-KEY" in line and "URI=" in line: key = self.download_key(line, 5) if key: new_m3u8_str += f'{key}\n' continue new_m3u8_str += f'{line}\n' if "EXT-X-ENDLIST" in line: break elif re.search(r'^http', line) is not None: new_m3u8_str += f"{file_folder}/{next(ts)}\n" self._m3u8_ts_list.append(line) elif re.search(r'^/', line) is not None: new_m3u8_str += f"{file_folder}/{next(ts)}\n" self._m3u8_ts_list.append(self.front_url + line) else: new_m3u8_str += f"{file_folder}/{next(ts)}\n" self._m3u8_ts_list.append( self._m3u8_url.rsplit("/", 1)[0] + '/' + line) self.ts_sum = next(ts) with open(f"{new_m3u8_file}", "w") as f: f.write(new_m3u8_str) def get_ts_key_file_name_with_path(self): if not self.download_file_path_input.text(): file_path_name = './{}/key'.format(self._m3u8_file_name) else: file_path_name = '{}/{}/key'.format( self.download_file_path_input.text(), self._m3u8_file_name) return file_path_name def download_key(self, key_line, num_retries): """ 下载key文件 """ key_file_path = self.get_ts_key_file_name_with_path() mid_part = re.search(r"URI=[\'|\"].*?[\'|\"]", key_line).group() may_key_url = mid_part[5:-1] if re.search(r'^http', may_key_url) is not None: true_key_url = may_key_url elif re.search(r'^/', may_key_url) is not None: true_key_url = self.front_url + may_key_url else: true_key_url = self._m3u8_url.rsplit("/", 1)[0] + '/' + may_key_url try: res = requests.get(true_key_url, timeout=(5, 60), verify=False, headers=self.headers) with open(key_file_path, 'wb') as f: f.write(res.content) res.close() return f'{key_line.split(mid_part)[0]}URI="{key_file_path}"{key_line.split(mid_part)[-1]}' except Exception as e: print(e) if os.path.exists(key_file_path): os.remove(key_file_path) if num_retries > 0: self.download_key(key_line, num_retries - 1) def set_download_table_view(self): for index_ts, ts_line in enumerate(self._m3u8_ts_list): self.model_table_list.appendRow([ QStandardItem(index_ts), QStandardItem(ts_line), QStandardItem('未下载') ]) def set_download_list_view(self, list_item): list_model = QStringListModel() list_model.setStringList(list_item) self.download_file_list.setModel(list_model) def update_download_process_property(self, value): if not self._m3u8_ts_list: self.download_progress.setValue(0) else: process_value = math.ceil((value / len(self._m3u8_ts_list)) * 100) self.download_progress.setValue(process_value) async def save_resp_content_to_file(self, resp, ts_file_obj): while True: chunk_file = await resp.content.read(self._chunk_size) if not chunk_file: break ts_file_obj.write(chunk_file) async def _m3u8_download_ts(self, ts_url, save_ts_name, max_retry): file_path_name = self.get_ts_file_name_with_path(save_ts_name) async with aiohttp.ClientSession() as session: async with session.get(ts_url) as resp: if resp.status == 200: with open(file_path_name, "wb") as ts: await self.save_resp_content_to_file(resp, ts) self.success_sum += 1 self.update_download_process_property(self.success_sum) else: await self._m3u8_download_ts(ts_url, save_ts_name, max_retry - 1) resp.close() def get_ts_file_name_with_path(self, save_ts_name): if not self.download_file_path_input.text(): file_path_name = './{}/{}'.format(self._m3u8_file_name, save_ts_name) else: file_path_name = '{}/{}/{}'.format( self.download_file_path_input.text(), self._m3u8_file_name, save_ts_name) return file_path_name async def m3u8_download_ts(self, ts_url, save_ts_name, max_retry): try: file_path_name = self.get_ts_file_name_with_path(save_ts_name) ts_url = ts_url.split('\n')[0] if not os.path.exists(file_path_name): await self._m3u8_download_ts(ts_url, save_ts_name, max_retry) else: self.success_sum += 1 except Exception as e: file_path_name = self.get_ts_file_name_with_path(save_ts_name) if os.path.exists(file_path_name): os.remove(file_path_name) if max_retry > 0: await self.m3u8_download_ts(ts_url, save_ts_name, max_retry - 1)
class MergeDialog(QDialog): MAX_FILENUM = 50 MAX_READLINE = 10000 REPEAT_SUFFIX = "_suffix_#$:@%|&*(!@#$:@%|" #设置为现实中很难出现的字符串 def __init__(self, parent=None): super(MergeDialog, self).__init__(parent) self.datetime_formats = { 'yyyy/mm/dd hh:mm:ss': '%Y/%m/%d %H:%M:%S', 'yyyy/mm/dd h:m:s': '%Y/%m/%d %H:%M:%S', 'yyyy/mm/dd hh:mm': '%Y/%m/%d %H:%M', 'yyyy/mm/dd h:m': '%Y/%m/%d %H:%M', 'yy/mm/dd hh:mm:ss': '%y/%m/%d %H:%M:%S', 'yy/mm/dd h:m:s': '%y/%m/%d %H:%M:%S', 'yy/mm/dd hh:mm': '%y/%m/%d %H:%M', 'yy/mm/dd h:m': '%y/%m/%d %H:%M', 'yyyy-mm-dd hh:mm:ss': '%Y-%m-%d %H:%M:%S', 'yyyy-mm-dd h:m:s': '%Y-%m-%d %H:%M:%S', 'yyyy-mm-dd hh:mm': '%Y-%m-%d %H:%M', 'yyyy-mm-dd h:m': '%Y-%m-%d %H:%M', 'yy-mm-dd hh:mm:ss': '%y-%m-%d %H:%M:%S', 'yy-mm-dd h:m:s': '%y-%m-%d %H:%M:%S', 'yy-mm-dd hh:mm': '%y-%m-%d %H:%M', 'yy-mm-dd h:m': '%y-%m-%d %H:%M' } self.m_selected = False self.ui = Ui_MergeDialog() self.ui.setupUi(self) self.ui.retranslateUi(self) self.initUi() self.setWindowTitle("Merging Source Files") self.m_model = QStandardItemModel(1, 1) self.ui.m_fileListView.setModel(self.m_model) self.setupConnect() def initUi(self): coding_list = [ "utf_8", "utf_16", "latin_1", "cp65001", "ascii", "gb2312", "gb18030", "gbk", "big5", "big5hkscs" ] self.ui.m_srcEncodingCBBox.addItems(coding_list) self.ui.m_srcEncodingCBBox.setCurrentIndex(0) self.ui.m_destEncodingCBBox.addItems(coding_list) self.ui.m_destEncodingCBBox.setCurrentIndex(0) self.ui.m_datetimeFormatCBBox.addItems( [key for key in self.datetime_formats]) self.ui.m_datetimeFormatCBBox.setCurrentIndex(0) self.ui.m_offsetDataHeadEdit.setToolTip(u"数据相对字头的列偏移量,1为右偏1,-1为左偏1") def setupConnect(self): self.ui.m_selectButton.clicked.connect(self.onSelectSrcFiles) self.ui.m_saveButton.clicked.connect(self.onMerging) self.ui.m_quitButton.clicked.connect(self.close) self.ui.m_XisDateTimeCheck.stateChanged.connect( lambda state: self.ui.m_datetimeFormatCBBox.setEnabled(state)) self.ui.m_rowSpanCheck.stateChanged.connect( lambda state: (self.ui.m_headrowEdit.setEnabled(not state), self.ui.m_dataStartRowEdit.setEnabled(not state), self.ui.m_XColumnEdit.setEnabled(not state), self.ui.m_YStartColumnEdit.setEnabled(not state), self.ui.m_stepEdit.setEnabled(not state), self.ui.m_offsetDataHeadEdit.setEnabled(not state))) def onSelectSrcFiles(self): (file_list, _) = QFileDialog.getOpenFileNames( self, "open data files", "./", "Data Files (*.txt *.csv *.dat);;All Files (*.*)") if (len(file_list) > 0): self.m_model.setRowCount(len(file_list)) i = 0 for filename in file_list: self.m_model.setData(self.m_model.index(i, 0, QModelIndex()), filename) i = i + 1 self.m_selected = True def onMerging(self): if (self.m_selected): save_file, _ = QFileDialog.getSaveFileName( self, "Open data file", "./", "Data Files (*.csv);;All Files (*.*)") if (save_file != ""): if (self.ui.m_colmunSpanCheck.isChecked()): #column span try: self.mergingByColumn(save_file) except Exception as e: QMessageBox.warning(self, "Error!", format(e)) elif (self.ui.m_rowSpanCheck.isChecked()): self.mergingByRow(save_file) else: QMessageBox.warning(self, "Warning!", "please select merging file!") else: QMessageBox.warning(self, "Warning", "source files have not been selected") def mergingByColumn(self, save_file): head_row = self.ui.m_headrowEdit.value() - 1 data_startrow = self.ui.m_dataStartRowEdit.value() - 1 x_column = self.ui.m_XColumnEdit.value() - 1 ycolumn_step = self.ui.m_stepEdit.value() ystart_column = self.ui.m_YStartColumnEdit.value() - 1 offset_data_head = self.ui.m_offsetDataHeadEdit.value( ) #数据相对字头的列偏移量,1为右偏1,-1为左偏1 src_encoding = self.ui.m_srcEncodingCBBox.currentText() dest_encoding = self.ui.m_destEncodingCBBox.currentText() delimiter = "," if (self.ui.m_semiCheck.isChecked()): delimiter = ";" elif (self.ui.m_spaceCheck.isChecked()): delimiter = " " elif (self.ui.m_tabCheck.isChecked()): delimiter = "\t" elif (self.ui.m_otherCheck.isChecked()): ss = self.ui.m_otherEdit.text() if (len(ss) >= 1): delimiter = ss files_nume = self.m_model.rowCount() total_data = pandas.DataFrame() for i in range(files_nume): filename = self.m_model.data( self.m_model.index(i, 0, QModelIndex())) mycolumns = pandas.read_csv(filename, sep=delimiter, encoding=src_encoding, skipinitialspace=True, skip_blank_lines=True, skiprows=head_row, header=0, nrows=0).columns use_cols = [ i for i in range(ystart_column, len(mycolumns), ycolumn_step) ] use_cols.insert(0, x_column) myindex_col = None if (self.ui.m_XasIndexCheck.isChecked()): myindex_col = 0 data = pandas.read_csv(filename, sep=delimiter, encoding=src_encoding, skipinitialspace=True, skip_blank_lines=True, skiprows=data_startrow, header=None, index_col=myindex_col, usecols=use_cols, nrows=self.MAX_READLINE) if (len(use_cols) > 1): #不大于1列不执行 j = use_cols[0] use_cols = use_cols[1:] if (offset_data_head != 0 #以下:只有加上偏移后索引不超出范围才有限 and use_cols[0] - offset_data_head >= 0 and use_cols[-1] - offset_data_head < len(mycolumns)): use_cols = [i - offset_data_head for i in use_cols] if (not self.ui.m_XasIndexCheck.isChecked()): use_cols.insert(0, j) data.columns = mycolumns[use_cols] data.index.name = "index" if (self.ui.m_XisDateTimeCheck.isChecked()): if (self.ui.m_XasIndexCheck.isChecked()): data.index.name = "time" mydatetime_format = self.datetime_formats[ self.ui.m_datetimeFormatCBBox.currentText()] data.index = pandas.to_datetime(data.index, format=mydatetime_format) if (i == 0): total_data = data else: total_data = total_data.join(data, lsuffix=self.REPEAT_SUFFIX, rsuffix=self.REPEAT_SUFFIX) mycolumns = [ s.replace(self.REPEAT_SUFFIX, "") for s in total_data.columns ] total_data.columns = mycolumns total_data.to_csv(path_or_buf=save_file, sep=delimiter, encoding=dest_encoding) #print(total_data) def mergingByRow(self, save_file): QMessageBox.warning(self, "Warning!", save_file)
class ClientList(QWidget): def __init__(self, data: ()): super().__init__() self.tree_view = QTreeView() self.model = QStandardItemModel() self.rootNode = self.model.invisibleRootItem() self.applyModel() buttons = QHBoxLayout() add = QPushButton("Add") add.clicked.connect(self.addClient) remove = QPushButton("Remove") remove.clicked.connect(self.removeClient) buttons.addWidget(add) buttons.addWidget(remove) layout = QVBoxLayout() layout.addLayout(buttons) layout.addWidget(self.tree_view) self.setLayout(layout) if data: self.setData(data) self.in_progress = False def addClient(self): branch = [QStandardItem(), QStandardItem()] branch[0].setText("/") branch[1].setText("client_") self.model.appendRow(branch) index = self.model.index(self.model.rowCount() - 1, 0) self.tree_view.setCurrentIndex(index) self.applyModel() def removeClient(self): index = self.tree_view.currentIndex() if index.isValid(): self.model.removeRow(index.row()) self.applyModel() def applyModel(self): self.model.setHeaderData(0, Qt.Horizontal, "Path") self.model.setHeaderData(1, Qt.Horizontal, "Client variable") self.tree_view.setModel(self.model) def getData(self): data = {} for row in range(self.model.rowCount()): path = self.model.data(self.model.index(row, 0)) client = self.model.data(self.model.index(row, 1)) if path and client: data[path] = client return data def setData(self, data): for path, client in data.items(): branch = [QStandardItem(), QStandardItem()] branch[0].setText(path) branch[1].setText(client) self.model.appendRow(branch) self.applyModel()
class comic_export_setting_dialog(QDialog): def __init__(self): super().__init__() self.setLayout(QVBoxLayout()) self.setWindowTitle(i18n("Export settings")) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) mainWidget = QTabWidget() self.layout().addWidget(mainWidget) self.layout().addWidget(buttons) # Set basic crop settings # Set which layers to remove before export. mainExportSettings = QWidget() mainExportSettings.setLayout(QVBoxLayout()) groupExportCrop = QGroupBox(i18n("Crop settings")) formCrop = QFormLayout() groupExportCrop.setLayout(formCrop) self.chk_toOutmostGuides = QCheckBox(i18n("Crop to outmost guides")) self.chk_toOutmostGuides.setChecked(True) self.chk_toOutmostGuides.setToolTip(i18n("This will crop to the outmost guides if possible and otherwise use the underlying crop settings.")) formCrop.addRow("", self.chk_toOutmostGuides) btn_fromSelection = QPushButton(i18n("Set margins from active selection")) btn_fromSelection.clicked.connect(self.slot_set_margin_from_selection) # This doesn't work. formCrop.addRow("", btn_fromSelection) self.spn_marginLeft = QSpinBox() self.spn_marginLeft.setMaximum(99999) self.spn_marginLeft.setSuffix(" px") formCrop.addRow(i18n("Left:"), self.spn_marginLeft) self.spn_marginTop = QSpinBox() self.spn_marginTop.setMaximum(99999) self.spn_marginTop.setSuffix(" px") formCrop.addRow(i18n("Top:"), self.spn_marginTop) self.spn_marginRight = QSpinBox() self.spn_marginRight.setMaximum(99999) self.spn_marginRight.setSuffix(" px") formCrop.addRow(i18n("Right:"), self.spn_marginRight) self.spn_marginBottom = QSpinBox() self.spn_marginBottom.setMaximum(99999) self.spn_marginBottom.setSuffix(" px") formCrop.addRow(i18n("Bottom:"), self.spn_marginBottom) groupExportLayers = QGroupBox(i18n("Layers")) formLayers = QFormLayout() groupExportLayers.setLayout(formLayers) self.cmbLabelsRemove = labelSelector() formLayers.addRow(i18n("Label for removal:"), self.cmbLabelsRemove) mainExportSettings.layout().addWidget(groupExportCrop) mainExportSettings.layout().addWidget(groupExportLayers) mainWidget.addTab(mainExportSettings, i18n("General")) # CBZ, crop, resize, which metadata to add. CBZexportSettings = QWidget() CBZexportSettings.setLayout(QVBoxLayout()) self.CBZactive = QCheckBox(i18n("Export to CBZ")) CBZexportSettings.layout().addWidget(self.CBZactive) self.CBZgroupResize = comic_export_resize_widget("CBZ") CBZexportSettings.layout().addWidget(self.CBZgroupResize) self.CBZactive.clicked.connect(self.CBZgroupResize.setEnabled) CBZgroupMeta = QGroupBox(i18n("Metadata to add")) # CBZexportSettings.layout().addWidget(CBZgroupMeta) CBZgroupMeta.setLayout(QFormLayout()) mainWidget.addTab(CBZexportSettings, "CBZ") # ACBF, crop, resize, creator name, version history, panel layer, text layers. ACBFExportSettings = QWidget() ACBFform = QFormLayout() ACBFExportSettings.setLayout(QVBoxLayout()) ACBFdocInfo = QGroupBox() ACBFdocInfo.setTitle(i18n("ACBF Document Info")) ACBFdocInfo.setLayout(ACBFform) self.lnACBFAuthor = QLineEdit() self.lnACBFAuthor.setToolTip(i18n("The person responsible for the generation of the CBZ.")) self.lnACBFSource = QLineEdit() self.lnACBFSource.setToolTip(i18n("Whether the acbf file is an adaption of an existing source, and if so, how to find information about that source. So for example, for an adapted webcomic, the official website url should go here.")) self.lnACBFID = QLabel() self.lnACBFID.setToolTip(i18n("By default this will be filled with a generated universal unique identifier. The ID by itself is merely so that comic book library management programs can figure out if this particular comic is already in their database and whether it has been rated. Of course, the UUID can be changed into something else by manually changing the json, but this is advanced usage.")) self.spnACBFVersion = QSpinBox() self.ACBFhistoryModel = QStandardItemModel() acbfHistoryList = QListView() acbfHistoryList.setModel(self.ACBFhistoryModel) btn_add_history = QPushButton(i18n("Add history entry")) btn_add_history.clicked.connect(self.slot_add_history_item) ACBFform.addRow(i18n("Author-name:"), self.lnACBFAuthor) ACBFform.addRow(i18n("Source:"), self.lnACBFSource) ACBFform.addRow(i18n("ACBF UID:"), self.lnACBFID) ACBFform.addRow(i18n("Version:"), self.spnACBFVersion) ACBFform.addRow(i18n("Version History:"), acbfHistoryList) ACBFform.addRow("", btn_add_history) ACBFExportSettings.layout().addWidget(ACBFdocInfo) mainWidget.addTab(ACBFExportSettings, "ACBF") # Epub export, crop, resize, other questions. EPUBexportSettings = QWidget() EPUBexportSettings.setLayout(QVBoxLayout()) self.EPUBactive = QCheckBox(i18n("Export to EPUB")) EPUBexportSettings.layout().addWidget(self.EPUBactive) self.EPUBgroupResize = comic_export_resize_widget("EPUB") EPUBexportSettings.layout().addWidget(self.EPUBgroupResize) self.EPUBactive.clicked.connect(self.EPUBgroupResize.setEnabled) mainWidget.addTab(EPUBexportSettings, "EPUB") # For Print. Crop, no resize. TIFFExportSettings = QWidget() TIFFExportSettings.setLayout(QVBoxLayout()) self.TIFFactive = QCheckBox(i18n("Export to TIFF")) TIFFExportSettings.layout().addWidget(self.TIFFactive) self.TIFFgroupResize = comic_export_resize_widget("TIFF") TIFFExportSettings.layout().addWidget(self.TIFFgroupResize) self.TIFFactive.clicked.connect(self.TIFFgroupResize.setEnabled) mainWidget.addTab(TIFFExportSettings, "TIFF") # SVG, crop, resize, embed vs link. #SVGExportSettings = QWidget() #mainWidget.addTab(SVGExportSettings, "SVG") """ Add a history item to the acbf version history list. """ def slot_add_history_item(self): newItem = QStandardItem() newItem.setText("v" + str(self.spnACBFVersion.value()) + "-" + i18n("in this version...")) self.ACBFhistoryModel.appendRow(newItem) """ Get the margins by treating the active selection in a document as the trim area. This allows people to snap selections to a vector or something, and then get the margins. """ def slot_set_margin_from_selection(self): doc = Application.activeDocument() if doc is not None: if doc.selection() is not None: self.spn_marginLeft.setValue(doc.selection().x()) self.spn_marginTop.setValue(doc.selection().y()) self.spn_marginRight.setValue(doc.width() - (doc.selection().x() + doc.selection().width())) self.spn_marginBottom.setValue(doc.height() - (doc.selection().y() + doc.selection().height())) """ Load the UI values from the config dictionary given. """ def setConfig(self, config): if "cropToGuides" in config.keys(): self.chk_toOutmostGuides.setChecked(config["cropToGuides"]) if "cropLeft" in config.keys(): self.spn_marginLeft.setValue(config["cropLeft"]) if "cropTop" in config.keys(): self.spn_marginTop.setValue(config["cropTop"]) if "cropRight" in config.keys(): self.spn_marginRight.setValue(config["cropRight"]) if "cropBottom" in config.keys(): self.spn_marginBottom.setValue(config["cropBottom"]) if "labelsToRemove" in config.keys(): self.cmbLabelsRemove.setLabels(config["labelsToRemove"]) self.CBZgroupResize.set_config(config) if "CBZactive" in config.keys(): self.CBZactive.setChecked(config["CBZactive"]) self.EPUBgroupResize.set_config(config) if "EPUBactive" in config.keys(): self.EPUBactive.setChecked(config["EPUBactive"]) self.TIFFgroupResize.set_config(config) if "TIFFactive" in config.keys(): self.TIFFactive.setChecked(config["TIFFactive"]) if "acbfAuthor" in config.keys(): self.lnACBFAuthor.setText(config["acbfAuthor"]) if "acbfSource" in config.keys(): self.lnACBFSource.setText(config["acbfSource"]) if "acbfID" in config.keys(): self.lnACBFID.setText(config["acbfID"]) else: self.lnACBFID.setText(QUuid.createUuid().toString()) if "acbfVersion" in config.keys(): self.spnACBFVersion.setValue(config["acbfVersion"]) if "acbfHistory" in config.keys(): for h in config["acbfHistory"]: item = QStandardItem() item.setText(h) self.ACBFhistoryModel.appendRow(item) self.CBZgroupResize.setEnabled(self.CBZactive.isChecked()) """ Store the GUI values into the config dictionary given. @return the config diactionary filled with new values. """ def getConfig(self, config): config["cropToGuides"] = self.chk_toOutmostGuides.isChecked() config["cropLeft"] = self.spn_marginLeft.value() config["cropTop"] = self.spn_marginTop.value() config["cropBottom"] = self.spn_marginRight.value() config["cropRight"] = self.spn_marginBottom.value() config["labelsToRemove"] = self.cmbLabelsRemove.getLabels() config["CBZactive"] = self.CBZactive.isChecked() config = self.CBZgroupResize.get_config(config) config["EPUBactive"] = self.EPUBactive.isChecked() config = self.EPUBgroupResize.get_config(config) config["TIFFactive"] = self.TIFFactive.isChecked() config = self.TIFFgroupResize.get_config(config) config["acbfAuthor"] = self.lnACBFAuthor.text() config["acbfSource"] = self.lnACBFSource.text() config["acbfID"] = self.lnACBFID.text() config["acbfVersion"] = self.spnACBFVersion.value() versionList = [] for r in range(self.ACBFhistoryModel.rowCount()): index = self.ACBFhistoryModel.index(r, 0) versionList.append(self.ACBFhistoryModel.data(index, Qt.DisplayRole)) config["acbfHistory"] = versionList return config
class AssetList(MyTreeView): class Columns(IntEnum): NAME = 0 BALANCE = 1 IPFS = 2 REISSUABLE = 3 DIVISIONS = 4 OWNER = 5 filter_columns = [ Columns.NAME, Columns.BALANCE, Columns.IPFS, Columns.REISSUABLE, Columns.DIVISIONS ] ROLE_SORT_ORDER = Qt.UserRole + 1000 ROLE_ASSET_STR = Qt.UserRole + 1001 def __init__(self, parent): super().__init__(parent, self.create_menu, stretch_column=None, editable_columns=[]) self.wallet = self.parent.wallet self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSortingEnabled(True) self.std_model = QStandardItemModel(self) self.proxy = MySortModel(self, sort_role=self.ROLE_SORT_ORDER) self.proxy.setSourceModel(self.std_model) self.setModel(self.proxy) self.update() self.sortByColumn(self.Columns.NAME, Qt.AscendingOrder) self.asset_meta = {} def webopen_safe(self, url): show_warn = self.parent.config.get('show_ipfs_warning', True) if show_warn: cb = QCheckBox(_("Don't show this message again.")) cb_checked = False def on_cb(x): nonlocal cb_checked cb_checked = x == Qt.Checked cb.stateChanged.connect(on_cb) goto = self.parent.question(_( 'You are about to visit:\n\n' '{}\n\n' 'IPFS hashes can link to anything. Please follow ' 'safe practices and common sense. If you are unsure ' 'about what\'s on the other end of an IPFS, don\'t ' 'visit it!\n\n' 'Are you sure you want to continue?').format(url), title=_('Warning: External Data'), checkbox=cb) if cb_checked: self.parent.config.set_key('show_ipfs_warning', False) if goto: webopen(url) else: webopen(url) def mouseDoubleClickEvent(self, event: QMouseEvent): idx = self.indexAt(event.pos()) if not idx.isValid(): return # Get the IPFS from 3rd column hm_idx = self.model().mapToSource(self.model().index(idx.row(), 2)) data = self.std_model.data(hm_idx) if data[:2] == 'Qm': # If it starts with Qm, it's an IPFS url = ipfs_explorer_URL(self.parent.config, 'ipfs', data) self.webopen_safe(url) def refresh_headers(self): headers = { self.Columns.NAME: _('Name'), self.Columns.BALANCE: _('Amount'), self.Columns.IPFS: _('Asset Data'), self.Columns.REISSUABLE: _('Reissuable'), self.Columns.DIVISIONS: _('Divisions'), self.Columns.OWNER: _('Owner'), } self.update_headers(headers) @profiler def update(self): if self.maybe_defer_update(): return current_asset = self.get_role_data_for_current_item( col=self.Columns.NAME, role=self.ROLE_ASSET_STR) addr_list = self.wallet.get_addresses() self.proxy.setDynamicSortFilter( False) # temp. disable re-sorting after every change self.std_model.clear() self.asset_meta.clear() self.refresh_headers() set_asset = None assets = {} # type: Dict[str, List[int, Optional[AssetMeta]]] for address in addr_list: c, u, x = self.wallet.get_addr_balance(address) balance = c + u + x # Don't display assets we no longer have if len(balance.assets) == 0: continue for asset, balance in balance.assets.items(): # Don't show hidden assets if not self.parent.config.get('show_spam_assets', False): should_continue = False for regex in self.parent.asset_blacklist: if re.search(regex, asset): should_continue = True break for regex in self.parent.asset_whitelist: if re.search(regex, asset): should_continue = False break if should_continue: continue if asset not in assets: meta = self.wallet.get_asset_meta(asset) assets[asset] = [balance.value, meta] else: assets[asset][0] += balance.value for asset, data in assets.items(): balance = data[0] meta = data[1] # type: AssetMeta balance_text = self.parent.format_amount(balance, whitespaces=True) if self.config.get('advanced_asset_functions', False): if meta and meta.ipfs_str: s = meta.ipfs_str h, a = get_alternate_data(base_decode(s, base=58)) ipfs_str = '\nBASE58: {}\nHEX: {}\nLATIN-1: {}\n'.format( s, h, a) else: ipfs_str = '\nBASE58: None\nHEX: None\nLATIN-1: None\n' else: ipfs_str = str(meta.ipfs_str) if meta else '' # May be none is_reis = str(meta.is_reissuable) if meta else '' divs = str(meta.divisions) if meta else '' ownr = str(meta.is_owner) if meta else '' # create item labels = [asset, balance_text, ipfs_str, is_reis, divs, ownr] asset_item = [QStandardItem(e) for e in labels] # align text and set fonts for i, item in enumerate(asset_item): item.setTextAlignment(Qt.AlignVCenter) if i not in (self.Columns.NAME, self.Columns.IPFS): item.setFont(QFont(MONOSPACE_FONT)) self.set_editability(asset_item) # add item count = self.std_model.rowCount() self.std_model.insertRow(count, asset_item) self.asset_meta = assets self.set_current_idx(set_asset) self.filter() self.proxy.setDynamicSortFilter(True) def add_copy_menu(self, menu, idx): cc = menu.addMenu(_("Copy")) for column in self.Columns: if self.isColumnHidden(column): continue column_title = self.model().headerData(column, Qt.Horizontal) hm_idx = self.model().mapToSource(self.model().index( idx.row(), column)) column_data = self.std_model.data(hm_idx) cc.addAction(column_title, lambda text=column_data, title=column_title: self. place_text_on_clipboard(text, title=title)) return cc def create_menu(self, position): org_idx: QModelIndex = self.indexAt(position) hm_idx = self.model().mapToSource(self.model().index(org_idx.row(), 0)) if not hm_idx.isValid(): return asset = self.std_model.data(hm_idx) hm_idx = self.model().mapToSource(self.model().index(org_idx.row(), 2)) if not hm_idx.isValid(): return ipfs = self.std_model.data(hm_idx) menu = QMenu() self.add_copy_menu(menu, org_idx) def send_asset(asset): self.parent.show_send_tab() self.parent.to_send_combo.setCurrentIndex( self.parent.send_options.index(asset)) menu.addAction(_('Send {}').format(asset), lambda: send_asset(asset)) if ipfs[:2] == 'Qm': url = ipfs_explorer_URL(self.parent.config, 'ipfs', ipfs) menu.addAction(_('View IPFS'), lambda: self.webopen_safe(url)) menu.addAction(_('View History'), lambda: self.parent.show_asset(asset)) menu.addAction(_('Mark as spam'), lambda: self.parent.hide_asset(asset)) menu.exec_(self.viewport().mapToGlobal(position)) def place_text_on_clipboard(self, text: str, *, title: str = None) -> None: if is_address(text): try: self.wallet.check_address_for_corruption(text) except InternalAddressCorruption as e: self.parent.show_error(str(e)) raise super().place_text_on_clipboard(text, title=title) def get_edit_key_from_coordinate(self, row, col): return None # We don't edit anything here def on_edited(self, idx, edit_key, *, text): pass
class comic_export_setting_dialog(QDialog): acbfStylesList = [ "speech", "commentary", "formal", "letter", "code", "heading", "audio", "thought", "sign", "sound", "emphasis", "strong" ] def __init__(self): super().__init__() self.setLayout(QVBoxLayout()) self.setWindowTitle(i18n("Export settings")) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) mainWidget = QTabWidget() self.layout().addWidget(mainWidget) self.layout().addWidget(buttons) # Set basic crop settings # Set which layers to remove before export. mainExportSettings = QWidget() mainExportSettings.setLayout(QVBoxLayout()) groupExportCrop = QGroupBox(i18n("Crop settings")) formCrop = QFormLayout() groupExportCrop.setLayout(formCrop) self.chk_toOutmostGuides = QCheckBox(i18n("Crop to outmost guides")) self.chk_toOutmostGuides.setChecked(True) self.chk_toOutmostGuides.setToolTip( i18n( "This will crop to the outmost guides if possible and otherwise use the underlying crop settings." )) formCrop.addRow("", self.chk_toOutmostGuides) btn_fromSelection = QPushButton( i18n("Set margins from active selection")) btn_fromSelection.clicked.connect(self.slot_set_margin_from_selection) # This doesn't work. formCrop.addRow("", btn_fromSelection) self.spn_marginLeft = QSpinBox() self.spn_marginLeft.setMaximum(99999) self.spn_marginLeft.setSuffix(" px") formCrop.addRow(i18n("Left:"), self.spn_marginLeft) self.spn_marginTop = QSpinBox() self.spn_marginTop.setMaximum(99999) self.spn_marginTop.setSuffix(" px") formCrop.addRow(i18n("Top:"), self.spn_marginTop) self.spn_marginRight = QSpinBox() self.spn_marginRight.setMaximum(99999) self.spn_marginRight.setSuffix(" px") formCrop.addRow(i18n("Right:"), self.spn_marginRight) self.spn_marginBottom = QSpinBox() self.spn_marginBottom.setMaximum(99999) self.spn_marginBottom.setSuffix(" px") formCrop.addRow(i18n("Bottom:"), self.spn_marginBottom) groupExportLayers = QGroupBox(i18n("Layers")) formLayers = QFormLayout() groupExportLayers.setLayout(formLayers) self.cmbLabelsRemove = labelSelector() formLayers.addRow(i18n("Label for removal:"), self.cmbLabelsRemove) self.ln_text_layer_name = QLineEdit() self.ln_text_layer_name.setToolTip( i18n( "These are keywords that can be used to identify text layers. A layer only needs to contain the keyword to be recognised. Keywords should be comma seperated." )) self.ln_panel_layer_name = QLineEdit() self.ln_panel_layer_name.setToolTip( i18n( "These are keywords that can be used to identify panel layers. A layer only needs to contain the keyword to be recognised. Keywords should be comma seperated." )) formLayers.addRow(i18n("Text Layer Key:"), self.ln_text_layer_name) formLayers.addRow(i18n("Panel Layer Key:"), self.ln_panel_layer_name) mainExportSettings.layout().addWidget(groupExportCrop) mainExportSettings.layout().addWidget(groupExportLayers) mainWidget.addTab(mainExportSettings, i18n("General")) # CBZ, crop, resize, which metadata to add. CBZexportSettings = QWidget() CBZexportSettings.setLayout(QVBoxLayout()) self.CBZactive = QCheckBox(i18n("Export to CBZ")) CBZexportSettings.layout().addWidget(self.CBZactive) self.CBZgroupResize = comic_export_resize_widget("CBZ") CBZexportSettings.layout().addWidget(self.CBZgroupResize) self.CBZactive.clicked.connect(self.CBZgroupResize.setEnabled) CBZgroupMeta = QGroupBox(i18n("Metadata to add")) # CBZexportSettings.layout().addWidget(CBZgroupMeta) CBZgroupMeta.setLayout(QFormLayout()) mainWidget.addTab(CBZexportSettings, "CBZ") # ACBF, crop, resize, creator name, version history, panel layer, text layers. ACBFExportSettings = QWidget() ACBFform = QFormLayout() ACBFExportSettings.setLayout(QVBoxLayout()) ACBFdocInfo = QGroupBox() ACBFdocInfo.setTitle(i18n("ACBF Document Info")) ACBFdocInfo.setLayout(ACBFform) self.lnACBFSource = QLineEdit() self.lnACBFSource.setToolTip( i18n( "Whether the acbf file is an adaption of an existing source, and if so, how to find information about that source. So for example, for an adapted webcomic, the official website url should go here." )) self.lnACBFID = QLabel() self.lnACBFID.setToolTip( i18n( "By default this will be filled with a generated universal unique identifier. The ID by itself is merely so that comic book library management programs can figure out if this particular comic is already in their database and whether it has been rated. Of course, the UUID can be changed into something else by manually changing the json, but this is advanced usage." )) self.spnACBFVersion = QSpinBox() self.ACBFhistoryModel = QStandardItemModel() acbfHistoryList = QListView() acbfHistoryList.setModel(self.ACBFhistoryModel) btn_add_history = QPushButton(i18n("Add history entry")) btn_add_history.clicked.connect(self.slot_add_history_item) self.chkIncludeTranslatorComments = QCheckBox() self.chkIncludeTranslatorComments.setText( i18n("Include Translator's Comments")) self.chkIncludeTranslatorComments.setToolTip( i18n( "A PO file can contain translator's comments. If this is checked, the translations comments will be added as references into the ACBF file." )) self.lnTranslatorHeader = QLineEdit() ACBFform.addRow(i18n("Source:"), self.lnACBFSource) ACBFform.addRow(i18n("ACBF UID:"), self.lnACBFID) ACBFform.addRow(i18n("Version:"), self.spnACBFVersion) ACBFform.addRow(i18n("Version History:"), acbfHistoryList) ACBFform.addRow("", btn_add_history) ACBFform.addRow("", self.chkIncludeTranslatorComments) ACBFform.addRow(i18n("Translator Header:"), self.lnTranslatorHeader) ACBFAuthorInfo = QWidget() acbfAVbox = QVBoxLayout(ACBFAuthorInfo) infoLabel = QLabel( i18n( "The people responsible for the generation of the CBZ/ACBF files." )) infoLabel.setWordWrap(True) ACBFAuthorInfo.layout().addWidget(infoLabel) self.ACBFauthorModel = QStandardItemModel(0, 6) labels = [ i18n("Nick Name"), i18n("Given Name"), i18n("Middle Name"), i18n("Family Name"), i18n("Email"), i18n("Homepage") ] self.ACBFauthorModel.setHorizontalHeaderLabels(labels) self.ACBFauthorTable = QTableView() acbfAVbox.addWidget(self.ACBFauthorTable) self.ACBFauthorTable.setModel(self.ACBFauthorModel) self.ACBFauthorTable.verticalHeader().setDragEnabled(True) self.ACBFauthorTable.verticalHeader().setDropIndicatorShown(True) self.ACBFauthorTable.verticalHeader().setSectionsMovable(True) self.ACBFauthorTable.verticalHeader().sectionMoved.connect( self.slot_reset_author_row_visual) AuthorButtons = QHBoxLayout() btn_add_author = QPushButton(i18n("Add author")) btn_add_author.clicked.connect(self.slot_add_author) AuthorButtons.addWidget(btn_add_author) btn_remove_author = QPushButton(i18n("Remove author")) btn_remove_author.clicked.connect(self.slot_remove_author) AuthorButtons.addWidget(btn_remove_author) acbfAVbox.addLayout(AuthorButtons) ACBFStyle = QWidget() ACBFStyle.setLayout(QHBoxLayout()) self.ACBFStylesModel = QStandardItemModel() self.ACBFStyleClass = QListView() self.ACBFStyleClass.setModel(self.ACBFStylesModel) ACBFStyle.layout().addWidget(self.ACBFStyleClass) ACBFStyleEdit = QWidget() ACBFStyleEditVB = QVBoxLayout(ACBFStyleEdit) self.ACBFfontCombo = QFontComboBox() self.ACBFdefaultFont = QComboBox() self.ACBFdefaultFont.addItems( ["sans-serif", "serif", "monospace", "cursive", "fantasy"]) self.ACBFBold = QCheckBox(i18n("Bold")) self.ACBFItal = QCheckBox(i18n("Italic")) self.ACBFStyleClass.clicked.connect(self.slot_set_style) self.ACBFStyleClass.selectionModel().selectionChanged.connect( self.slot_set_style) self.ACBFStylesModel.itemChanged.connect(self.slot_set_style) self.ACBFfontCombo.currentFontChanged.connect( self.slot_font_current_style) self.ACBFfontCombo.setEditable(False) self.ACBFBold.toggled.connect(self.slot_font_current_style) self.ACBFItal.toggled.connect(self.slot_font_current_style) colorWidget = QGroupBox(self) colorWidget.setTitle(i18n("Text Colors")) colorWidget.setLayout(QVBoxLayout()) self.regularColor = QColorDialog() self.invertedColor = QColorDialog() self.btn_acbfRegColor = QPushButton(i18n("Regular Text"), self) self.btn_acbfRegColor.clicked.connect(self.slot_change_regular_color) self.btn_acbfInvColor = QPushButton(i18n("Inverted Text"), self) self.btn_acbfInvColor.clicked.connect(self.slot_change_inverted_color) colorWidget.layout().addWidget(self.btn_acbfRegColor) colorWidget.layout().addWidget(self.btn_acbfInvColor) ACBFStyleEditVB.addWidget(colorWidget) ACBFStyleEditVB.addWidget(self.ACBFfontCombo) ACBFStyleEditVB.addWidget(self.ACBFdefaultFont) ACBFStyleEditVB.addWidget(self.ACBFBold) ACBFStyleEditVB.addWidget(self.ACBFItal) ACBFStyleEditVB.addStretch() ACBFStyle.layout().addWidget(ACBFStyleEdit) ACBFTabwidget = QTabWidget() ACBFTabwidget.addTab(ACBFdocInfo, i18n("Document Info")) ACBFTabwidget.addTab(ACBFAuthorInfo, i18n("Author Info")) ACBFTabwidget.addTab(ACBFStyle, i18n("Style Sheet")) ACBFExportSettings.layout().addWidget(ACBFTabwidget) mainWidget.addTab(ACBFExportSettings, i18n("ACBF")) # Epub export, crop, resize, other questions. EPUBexportSettings = QWidget() EPUBexportSettings.setLayout(QVBoxLayout()) self.EPUBactive = QCheckBox(i18n("Export to EPUB")) EPUBexportSettings.layout().addWidget(self.EPUBactive) self.EPUBgroupResize = comic_export_resize_widget("EPUB") EPUBexportSettings.layout().addWidget(self.EPUBgroupResize) self.EPUBactive.clicked.connect(self.EPUBgroupResize.setEnabled) mainWidget.addTab(EPUBexportSettings, "EPUB") # For Print. Crop, no resize. TIFFExportSettings = QWidget() TIFFExportSettings.setLayout(QVBoxLayout()) self.TIFFactive = QCheckBox(i18n("Export to TIFF")) TIFFExportSettings.layout().addWidget(self.TIFFactive) self.TIFFgroupResize = comic_export_resize_widget("TIFF") TIFFExportSettings.layout().addWidget(self.TIFFgroupResize) self.TIFFactive.clicked.connect(self.TIFFgroupResize.setEnabled) mainWidget.addTab(TIFFExportSettings, "TIFF") # SVG, crop, resize, embed vs link. #SVGExportSettings = QWidget() #mainWidget.addTab(SVGExportSettings, "SVG") """ Add a history item to the acbf version history list. """ def slot_add_history_item(self): newItem = QStandardItem() newItem.setText("v" + str(self.spnACBFVersion.value()) + "-" + i18n("in this version...")) self.ACBFhistoryModel.appendRow(newItem) """ Get the margins by treating the active selection in a document as the trim area. This allows people to snap selections to a vector or something, and then get the margins. """ def slot_set_margin_from_selection(self): doc = Application.activeDocument() if doc is not None: if doc.selection() is not None: self.spn_marginLeft.setValue(doc.selection().x()) self.spn_marginTop.setValue(doc.selection().y()) self.spn_marginRight.setValue(doc.width() - (doc.selection().x() + doc.selection().width())) self.spn_marginBottom.setValue(doc.height() - (doc.selection().y() + doc.selection().height())) """ Add an author with default values initialised. """ def slot_add_author(self): listItems = [] listItems.append(QStandardItem(i18n("Anon"))) # Nick name listItems.append(QStandardItem(i18n("John"))) # First name listItems.append(QStandardItem()) # Middle name listItems.append(QStandardItem(i18n("Doe"))) # Last name listItems.append(QStandardItem()) # email listItems.append(QStandardItem()) # homepage self.ACBFauthorModel.appendRow(listItems) """ Remove the selected author from the author list. """ def slot_remove_author(self): self.ACBFauthorModel.removeRow( self.ACBFauthorTable.currentIndex().row()) """ Ensure that the drag and drop of authors doesn't mess up the labels. """ def slot_reset_author_row_visual(self): headerLabelList = [] for i in range(self.ACBFauthorTable.verticalHeader().count()): headerLabelList.append(str(i)) for i in range(self.ACBFauthorTable.verticalHeader().count()): logicalI = self.ACBFauthorTable.verticalHeader().logicalIndex(i) headerLabelList[logicalI] = str(i + 1) self.ACBFauthorModel.setVerticalHeaderLabels(headerLabelList) def slot_set_style(self): index = self.ACBFStyleClass.currentIndex() if index.isValid(): item = self.ACBFStylesModel.item(index.row()) font = QFont() font.setFamily(str(item.data(role=Qt.UserRole + 1))) self.ACBFfontCombo.setCurrentFont(font) self.ACBFdefaultFont.setCurrentText( str(item.data(role=Qt.UserRole + 2))) bold = item.data(role=Qt.UserRole + 3) if bold is not None: self.ACBFBold.setChecked(bold) else: self.ACBFBold.setChecked(False) italic = item.data(role=Qt.UserRole + 4) if italic is not None: self.ACBFItal.setChecked(italic) else: self.ACBFItal.setChecked(False) def slot_font_current_style(self): index = self.ACBFStyleClass.currentIndex() if index.isValid(): item = self.ACBFStylesModel.item(index.row()) font = QFont(self.ACBFfontCombo.currentFont()) item.setData(font.family(), role=Qt.UserRole + 1) item.setData(self.ACBFdefaultFont.currentText(), role=Qt.UserRole + 2) item.setData(self.ACBFBold.isChecked(), role=Qt.UserRole + 3) item.setData(self.ACBFItal.isChecked(), role=Qt.UserRole + 4) self.ACBFStylesModel.setItem(index.row(), item) def slot_change_regular_color(self): if (self.regularColor.exec_() == QDialog.Accepted): square = QPixmap(32, 32) square.fill(self.regularColor.currentColor()) self.btn_acbfRegColor.setIcon(QIcon(square)) def slot_change_inverted_color(self): if (self.invertedColor.exec_() == QDialog.Accepted): square = QPixmap(32, 32) square.fill(self.invertedColor.currentColor()) self.btn_acbfInvColor.setIcon(QIcon(square)) """ Load the UI values from the config dictionary given. """ def setConfig(self, config): if "cropToGuides" in config.keys(): self.chk_toOutmostGuides.setChecked(config["cropToGuides"]) if "cropLeft" in config.keys(): self.spn_marginLeft.setValue(config["cropLeft"]) if "cropTop" in config.keys(): self.spn_marginTop.setValue(config["cropTop"]) if "cropRight" in config.keys(): self.spn_marginRight.setValue(config["cropRight"]) if "cropBottom" in config.keys(): self.spn_marginBottom.setValue(config["cropBottom"]) if "labelsToRemove" in config.keys(): self.cmbLabelsRemove.setLabels(config["labelsToRemove"]) if "textLayerNames" in config.keys(): self.ln_text_layer_name.setText(", ".join( config["textLayerNames"])) else: self.ln_text_layer_name.setText("text") if "panelLayerNames" in config.keys(): self.ln_panel_layer_name.setText(", ".join( config["panelLayerNames"])) else: self.ln_panel_layer_name.setText("panels") self.CBZgroupResize.set_config(config) if "CBZactive" in config.keys(): self.CBZactive.setChecked(config["CBZactive"]) self.EPUBgroupResize.set_config(config) if "EPUBactive" in config.keys(): self.EPUBactive.setChecked(config["EPUBactive"]) self.TIFFgroupResize.set_config(config) if "TIFFactive" in config.keys(): self.TIFFactive.setChecked(config["TIFFactive"]) if "acbfAuthor" in config.keys(): if isinstance(config["acbfAuthor"], list): for author in config["acbfAuthor"]: listItems = [] listItems.append(QStandardItem(author.get("nickname", ""))) listItems.append( QStandardItem(author.get("first-name", ""))) listItems.append(QStandardItem(author.get("initials", ""))) listItems.append(QStandardItem(author.get("last-name", ""))) listItems.append(QStandardItem(author.get("email", ""))) listItems.append(QStandardItem(author.get("homepage", ""))) self.ACBFauthorModel.appendRow(listItems) pass else: listItems = [] listItems.append(QStandardItem( config["acbfAuthor"])) # Nick name for i in range(0, 5): listItems.append(QStandardItem()) # First name self.ACBFauthorModel.appendRow(listItems) if "acbfSource" in config.keys(): self.lnACBFSource.setText(config["acbfSource"]) if "acbfID" in config.keys(): self.lnACBFID.setText(config["acbfID"]) else: self.lnACBFID.setText(QUuid.createUuid().toString()) if "acbfVersion" in config.keys(): self.spnACBFVersion.setValue(config["acbfVersion"]) if "acbfHistory" in config.keys(): for h in config["acbfHistory"]: item = QStandardItem() item.setText(h) self.ACBFhistoryModel.appendRow(item) if "acbfStyles" in config.keys(): styleDict = config.get("acbfStyles", {}) for key in self.acbfStylesList: keyDict = styleDict.get(key, {}) style = QStandardItem(key.title()) style.setCheckable(True) if key in styleDict.keys(): style.setCheckState(Qt.Checked) else: style.setCheckState(Qt.Unchecked) style.setData(keyDict.get("font", QFont().family()), role=Qt.UserRole + 1) style.setData(keyDict.get("genericfont", "sans-serif"), role=Qt.UserRole + 2) style.setData(keyDict.get("bold", False), role=Qt.UserRole + 3) style.setData(keyDict.get("ital", False), role=Qt.UserRole + 4) self.ACBFStylesModel.appendRow(style) keyDict = styleDict.get("general", {}) self.regularColor.setCurrentColor( QColor(keyDict.get("color", "#000000"))) square = QPixmap(32, 32) square.fill(self.regularColor.currentColor()) self.btn_acbfRegColor.setIcon(QIcon(square)) keyDict = styleDict.get("inverted", {}) self.invertedColor.setCurrentColor( QColor(keyDict.get("color", "#FFFFFF"))) square.fill(self.invertedColor.currentColor()) self.btn_acbfInvColor.setIcon(QIcon(square)) else: for key in self.acbfStylesList: style = QStandardItem(key.title()) style.setCheckable(True) style.setCheckState(Qt.Unchecked) style.setData(QFont().family(), role=Qt.UserRole + 1) style.setData("sans-serif", role=Qt.UserRole + 2) style.setData(False, role=Qt.UserRole + 3) #Bold style.setData(False, role=Qt.UserRole + 4) #Italic self.ACBFStylesModel.appendRow(style) self.CBZgroupResize.setEnabled(self.CBZactive.isChecked()) self.lnTranslatorHeader.setText( config.get("translatorHeader", "Translator's Notes")) self.chkIncludeTranslatorComments.setChecked( config.get("includeTranslComment", False)) """ Store the GUI values into the config dictionary given. @return the config diactionary filled with new values. """ def getConfig(self, config): config["cropToGuides"] = self.chk_toOutmostGuides.isChecked() config["cropLeft"] = self.spn_marginLeft.value() config["cropTop"] = self.spn_marginTop.value() config["cropBottom"] = self.spn_marginRight.value() config["cropRight"] = self.spn_marginBottom.value() config["labelsToRemove"] = self.cmbLabelsRemove.getLabels() config["CBZactive"] = self.CBZactive.isChecked() config = self.CBZgroupResize.get_config(config) config["EPUBactive"] = self.EPUBactive.isChecked() config = self.EPUBgroupResize.get_config(config) config["TIFFactive"] = self.TIFFactive.isChecked() config = self.TIFFgroupResize.get_config(config) authorList = [] for row in range(self.ACBFauthorTable.verticalHeader().count()): logicalIndex = self.ACBFauthorTable.verticalHeader().logicalIndex( row) listEntries = [ "nickname", "first-name", "initials", "last-name", "email", "homepage" ] author = {} for i in range(len(listEntries)): entry = self.ACBFauthorModel.data( self.ACBFauthorModel.index(logicalIndex, i)) if entry is None: entry = " " if entry.isspace() is False and len(entry) > 0: author[listEntries[i]] = entry elif listEntries[i] in author.keys(): author.pop(listEntries[i]) authorList.append(author) config["acbfAuthor"] = authorList config["acbfSource"] = self.lnACBFSource.text() config["acbfID"] = self.lnACBFID.text() config["acbfVersion"] = self.spnACBFVersion.value() versionList = [] for r in range(self.ACBFhistoryModel.rowCount()): index = self.ACBFhistoryModel.index(r, 0) versionList.append( self.ACBFhistoryModel.data(index, Qt.DisplayRole)) config["acbfHistory"] = versionList acbfStylesDict = {} for row in range(0, self.ACBFStylesModel.rowCount()): entry = self.ACBFStylesModel.item(row) if entry.checkState() == Qt.Checked: key = entry.text().lower() style = {} font = entry.data(role=Qt.UserRole + 1) if font is not None: style["font"] = font genericfont = entry.data(role=Qt.UserRole + 2) if font is not None: style["genericfont"] = genericfont bold = entry.data(role=Qt.UserRole + 3) if bold is not None: style["bold"] = bold italic = entry.data(role=Qt.UserRole + 4) if italic is not None: style["ital"] = italic acbfStylesDict[key] = style acbfStylesDict["general"] = { "color": self.regularColor.currentColor().name() } acbfStylesDict["inverted"] = { "color": self.invertedColor.currentColor().name() } config["acbfStyles"] = acbfStylesDict config["translatorHeader"] = self.lnTranslatorHeader.text() config[ "includeTranslComment"] = self.chkIncludeTranslatorComments.isChecked( ) # Turn this into something that retreives from a line-edit when string freeze is over. config["textLayerNames"] = self.ln_text_layer_name.text().split(",") config["panelLayerNames"] = self.ln_panel_layer_name.text().split(",") return config
class UserForm(QtWidgets.QMainWindow, Ui_UserForm): def __init__(self, parent): # Это здесь нужно для доступа к переменным, методам # и т.д. в файле design.py super().__init__() self.parent_form = parent self.setupUi(self) self.user_model = None self.model = QStandardItemModel() self.tvMetadata.setModel(self.model) self.model.setHorizontalHeaderLabels(["Name", "Value", "Type", "Row Type"]) self.tvMetadata.setColumnWidth(0, 200) self.tvMetadata.setColumnWidth(1, 290) self.tvMetadata.setColumnWidth(2, 290) self.tvMetadata.setColumnWidth(3, 100) # this column should be read-only self.btnAddMetodata.clicked.connect(self.add_metadata) self.btnRemoveMetodata.clicked.connect(self.remove_metadata) self.btnSave.clicked.connect(self.save) self.setFixedSize(self.width(), self.height() - 20) def apply_model(self): model = self.user_model self.txbUsername.setText(model.username) self.txbComment.setText(model.comment) self.txbProviderUserKey.setText(model.provider_user_key) self.txbEmail.setText(model.email) self.txbIsAdmin.setText(model.is_admin) self.txbPassword.setText(model.password) self.txbName.setText(model.name) self.txbApplicationName.setText(model.application_name) self.txbUserState.setText(model.user_state) self.txbProviderName.setText(model.provider_name) # Clear while self.model.rowCount() > 0: self.model.removeRow(0) for item in model.metadata: self.model.appendRow( [ QStandardItem(item['name']), QStandardItem(item['value']), QStandardItem(item['type']), QStandardItem('MetaData') ]) for item in model.content_data: self.model.appendRow( [ QStandardItem(item['name']), QStandardItem(item['value']), QStandardItem(item['type']), QStandardItem('ContentData') ]) def remove_metadata(self): indexes = self.tvMetadata.selectedIndexes() for index in indexes: self.model.removeRow(index.row()) def add_metadata(self): try: dialog = MetadataWidget() result = dialog.exec_() if result == 1: data = dialog.return_data() # TODO: Make 'row_type' read only self.model.appendRow( [ QStandardItem(data['name']), QStandardItem(data['value']), QStandardItem(data['type']), QStandardItem(data['row_type']) ]) except BaseException as err: QMessageBox.about(self, 'PyQt5 message', 'An exception occurred: {}'.format(err)) def save(self): try: metadata = [] content_data = [] row_count = self.model.rowCount() for row in range(row_count): name = self.model.data(self.model.index(row, 0)) value = self.model.data(self.model.index(row, 1)) value_type = self.model.data(self.model.index(row, 2)) row_type = self.model.data(self.model.index(row, 3)) if row_type == "ContentData": content_data.append({'name': name, 'value': value, 'type': value_type}) if row_type == "MetaData": metadata.append({'name': name, 'value': value, 'type': value_type}) self.user_model = UserModel( username=self.txbUsername.text(), comment=self.txbComment.text(), provider_user_key=self.txbProviderUserKey.text(), email=self.txbEmail.text(), is_admin=self.txbIsAdmin.text(), password=self.txbPassword.text(), name=self.txbName.text(), application_name=self.txbApplicationName.text(), user_state=self.txbUserState.text(), provider_name=self.txbProviderName.text(), metadata=metadata, content_data=content_data ) self.parent_form.set_user(self.user_model) except ValidationError as e: QMessageBox.about(self, 'Error message', '{}'.format(e)) except BaseException as err: QMessageBox.about(self, 'Error message', 'An exception occurred: {}'.format(err)) self.close()
class IsosViewer(QMainWindow, mageiaSyncUI.Ui_mainWindow): # Display the main window def __init__(self, parent=None): super(IsosViewer, self).__init__(parent) self.setupUi(self) self.connectActions() self.IprogressBar.setMinimum(0) self.IprogressBar.setMaximum(100) self.IprogressBar.setValue(0) self.IprogressBar.setEnabled(False) self.selectAllState=True self.stop.setEnabled(False) self.destination='' self.rsyncThread = mageiaSyncExt.syncThread(self) # create a thread to launch rsync self.rsyncThread.progressSignal.connect(self.setProgress) self.rsyncThread.speedSignal.connect(self.setSpeed) self.rsyncThread.sizeSignal.connect(self.setSize) self.rsyncThread.remainSignal.connect(self.setRemain) self.rsyncThread.endSignal.connect(self.syncEnd) self.rsyncThread.lvM.connect(self.lvMessage) self.rsyncThread.checkSignal.connect(self.checks) self.checkThreads=[] # A list of thread for each iso # Model for list view in a table self.model = QStandardItemModel(0, 6, self) headers=[self.tr("Directory"),self.tr("Name"),self.tr("Size"),self.tr("Date"),"SHA1","MD5"] i=0 for label in headers: self.model.setHeaderData(i, QtCore.Qt.Horizontal,label ) i+=1 # settings for the list view self.localList.setModel(self.model) self.localList.setColumnWidth(0,220) self.localList.setColumnWidth(1,220) self.localList.horizontalHeader().setStretchLastSection(True) # settings for local iso names management self.localListNames=[] def multiSelect(self): # allows to select multiple lines in remote ISOs list self.listIsos.setSelectionMode(2) def add(self, iso): # Add an remote ISO in list self.listIsos.addItem(iso) def localAdd(self, path,iso,isoSize): # Add an entry in local ISOs list, with indications about checking itemPath=QStandardItem(path) itemIso=QStandardItem(iso) if isoSize==0: itemSize=QStandardItem('--') else: formatedSize='{:n}'.format(isoSize).replace(","," ") itemSize=QStandardItem(formatedSize) itemSize.setTextAlignment(QtCore.Qt.AlignVCenter|QtCore.Qt.AlignHCenter) itemDate=QStandardItem("--/--/--") itemDate.setTextAlignment(QtCore.Qt.AlignVCenter|QtCore.Qt.AlignHCenter) itemCheck1=QStandardItem("--") itemCheck1.setTextAlignment(QtCore.Qt.AlignVCenter|QtCore.Qt.AlignHCenter) itemCheck5=QStandardItem("--") itemCheck5.setTextAlignment(QtCore.Qt.AlignVCenter|QtCore.Qt.AlignHCenter) self.model.appendRow([itemPath,itemIso,itemSize,itemDate, itemCheck1, itemCheck5,]) self.localListNames.append([path,iso]) def setProgress(self, value): # Update the progress bar self.IprogressBar.setValue(value) def setSpeed(self, value): # Update the speed field self.speedLCD.display(value) def setSize(self, size): # Update the size field self.Lsize.setText(size+self.tr(" bytes")) def setRemain(self,remainTime): content=QtCore.QTime.fromString(remainTime,"h:mm:ss") self.timeRemaining.setTime(content) def manualChecks(self): for iso in self.listIsos.selectedItems(): path,name=iso.text().split('/') try: # Look for ISO in local list item=self.model.findItems(name,QtCore.Qt.MatchExactly,1)[0] except: # Remote ISO is not yet in local directory. We add it in localList and create the directory self.localAdd(path,name,0) basedir=QtCore.QDir(self.destination) basedir.mkdir(path) item=self.model.findItems(name,QtCore.Qt.MatchExactly,1)[0] row=self.model.indexFromItem(item).row() self.checks(row) def checks(self,isoIndex): # processes a checking for each iso # launches a thread for each iso newThread=mageiaSyncExt.checkThread(self) self.checkThreads.append(newThread) self.checkThreads[-1].setup(self.destination, self.model.data(self.model.index(isoIndex,0)) , self.model.data(self.model.index(isoIndex,1)), isoIndex) self.checkThreads[-1].md5Signal.connect(self.md5Check) self.checkThreads[-1].sha1Signal.connect(self.sha1Check) self.checkThreads[-1].dateSignal.connect(self.dateCheck) self.checkThreads[-1].sizeFinalSignal.connect(self.sizeUpdate) self.checkThreads[-1].checkStartSignal.connect(self.checkStart) self.checkThreads[-1].start() def checkStart(self,isoIndex): # the function indicates that checking is in progress # the hundred contains index of the value to check, the minor value contains the row col=(int)(isoIndex/100) row=isoIndex-col*100 self.model.setData(self.model.index(row, col, QtCore.QModelIndex()), self.tr("Checking")) def md5Check(self,check): if check>=128: val=self.tr("OK") row=check-128 else: val=self.tr("Failed") row=check self.model.setData(self.model.index(row, 5, QtCore.QModelIndex()), val) def sha1Check(self,check): if check>=128: val=self.tr("OK") row=check-128 else: val=self.tr("Failed") row=check self.model.setData(self.model.index(row, 4, QtCore.QModelIndex()), val) def dateCheck(self,check): if check>=128: val=self.tr("OK") row=check-128 else: val=self.tr("Failed") row=check self.model.setData(self.model.index(row, 3, QtCore.QModelIndex()), val) def sizeUpdate(self,signal,isoSize): col=(int)(signal/100) row=signal-col*100 self.model.setData(self.model.index(row, col, QtCore.QModelIndex()), isoSize) def syncEnd(self, rc): if rc==1: self.lvMessage(self.tr("Command rsync not found")) elif rc==2: self.lvMessage(self.tr("Error in rsync parameters")) elif rc==3: self.lvMessage(self.tr("Unknown error in rsync")) self.IprogressBar.setEnabled(False) self.syncGo.setEnabled(True) self.listIsos.setEnabled(True) self.selectAll.setEnabled(True) self.stop.setEnabled(False) def prefsInit(self): # Load the parameters at first params=QtCore.QSettings("Mageia","mageiaSync") paramRelease="" try: paramRelease=params.value("release", type="QString") # the parameters already initialised? except: pass if paramRelease =="": # Values are not yet set self.pd0=prefsDialog0() self.pd0.user.setFocus() answer=self.pd0.exec_() if answer: # Update params self.user=self.pd0.user.text() self.password=self.pd0.password.text() self.location=self.pd0.location.text() params=QtCore.QSettings("Mageia","mageiaSync") params.setValue("user",self.user) params.setValue("password",self.password) params.setValue("location",self.location) else: pass # answer=QDialogButtonBox(QDialogButtonBox.Ok) # the user must set values or default values self.pd0.close() self.pd=prefsDialog() if self.password !="": code,list=mageiaSyncExt.findRelease('rsync://'+self.user+'@bcd.mageia.org/isos/',self.password) if code==0: for item in list: self.pd.release.addItem(item) self.pd.password.setText(self.password) self.pd.user.setText(self.user) self.pd.location.setText(self.location) self.pd.selectDest.setText(QtCore.QDir.currentPath()) self.pd.release.setFocus() answer=self.pd.exec_() if answer: # Update params self.user=self.pd.user.text() self.password=self.pd.password.text() self.location=self.pd.location.text() params=QtCore.QSettings("Mageia","mageiaSync") self.release= self.pd.release.currentText() self.destination=self.pd.selectDest.text() self.bwl=self.pd.bwl.value() params.setValue("release", self.release) params.setValue("user",self.user) params.setValue("password",self.password) params.setValue("location",self.location) params.setValue("destination",self.destination) params.setValue("bwl",str(self.bwl)) else: pass # answer=QDialogButtonBox(QDialogButtonBox.Ok) print(self.tr("the user must set values or default values")) self.pd.close() else: self.release=params.value("release", type="QString") self.user=params.value("user", type="QString") self.location=params.value("location", type="QString") self.password=params.value("password", type="QString") self.destination=params.value("destination", type="QString") self.bwl=params.value("bwl",type=int) self.localDirLabel.setText(self.tr("Local directory: ")+self.destination) if self.location !="": self.remoteDirLabel.setText(self.tr("Remote directory: ")+self.location) def selectDestination(self): # dialog box to select the destination (local directory) directory = QFileDialog.getExistingDirectory(self, self.tr('Select a directory'),'~/') isosSync.destination = directory self.pd.selectDest.setText(isosSync.destination) def selectAllIsos(self): # Select or unselect the ISOs in remote list if self.selectAllState : for i in range(self.listIsos.count()): self.listIsos.item(i).setSelected(True) self.selectAll.setText(self.tr("Unselect &All")) else: for i in range(self.listIsos.count()): self.listIsos.item(i).setSelected(False) self.selectAll.setText(self.tr("Select &All")) self.selectAllState=not self.selectAllState def connectActions(self): self.actionQuit.triggered.connect(app.quit) self.quit.clicked.connect(app.quit) self.actionRename.triggered.connect(self.rename) self.actionUpdate.triggered.connect(self.updateList) self.actionCheck.triggered.connect(self.manualChecks) self.actionPreferences.triggered.connect(self.prefs) self.syncGo.clicked.connect(self.launchSync) self.selectAll.clicked.connect(self.selectAllIsos) def updateList(self): # From the menu entry self.lw = LogWindow() self.lw.show() self.listIsos.clear() self.model.removeRows(0,self.model.rowCount()) if self.location == "" : self.nameWithPath='rsync://'+self.user+'@bcd.mageia.org/isos/'+self.release+'/' # print self.nameWithPath else: self.nameWithPath=self.location+'/' self.lvMessage(self.tr("Source: ")+self.nameWithPath) self.fillList = mageiaSyncExt.findIsos() self.fillList.setup(self.nameWithPath, self.password,self.destination) self.fillList.endSignal.connect(self.closeFill) self.fillList.start() # Reset the button self.selectAll.setText(self.tr("Select &All")) self.selectAllState=True def lvMessage( self,message): # Add a line in the logview self.lvText.append(message) def renameDir(self): # Choose the directory where isos are stored directory = QFileDialog.getExistingDirectory(self, self.tr('Select a directory'),self.destination) self.rd.chooseDir.setText(directory) def rename(self): # rename old isos and directories to a new release self.rd=renameDialog() loc=[] loc=self.location.split('/') self.rd.oldRelease.setText(loc[-1]) self.rd.chooseDir.setText(self.destination) answer=self.rd.exec_() if answer: returnMsg=mageiaSyncExt.rename(self.rd.chooseDir.text(),self.rd.oldRelease.text(),str(self.rd.newRelease.text())) self.lvMessage(returnMsg) self.rd.close() def prefs(self): # From the menu entry self.pd=prefsDialog() self.pd.release.addItem(self.release) self.pd.password.setText(self.password) self.pd.user.setText(self.user) self.pd.location.setText(self.location) self.pd.selectDest.setText(self.destination) self.pd.bwl.setValue(self.bwl) params=QtCore.QSettings("Mageia","mageiaSync") answer=self.pd.exec_() if answer: params.setValue("release", self.pd.release.currentText()) params.setValue("user",self.pd.user.text()) params.setValue("password",self.pd.password.text()) params.setValue("location",self.pd.location.text()) params.setValue("destination",self.pd.selectDest.text()) params.setValue("bwl",str(self.pd.bwl.value())) self.prefsInit() self.pd.close() def launchSync(self): self.IprogressBar.setEnabled(True) self.stop.setEnabled(True) self.syncGo.setEnabled(False) self.listIsos.setEnabled(False) self.selectAll.setEnabled(False) # Connect the button Stop self.stop.clicked.connect(self.stopSync) self.rsyncThread.params(self.password, self.bwl) for iso in self.listIsos.selectedItems(): path,name=iso.text().split('/') try: # Look for ISO in local list item=self.model.findItems(name,QtCore.Qt.MatchExactly,1)[0] except: # Remote ISO is not yet in local directory. We add it in localList and create the directory self.localAdd(path,name,0) basedir=QtCore.QDir(self.destination) basedir.mkdir(path) item=self.model.findItems(name,QtCore.Qt.MatchExactly,1)[0] row=self.model.indexFromItem(item).row() if self.location == "" : self.nameWithPath='rsync://'+self.user+'@bcd.mageia.org/isos/'+self.release+'/'+path else: self.nameWithPath=self.location+path if (not str(path).endswith('/')): self.nameWithPath+='/' self.rsyncThread.setup(self.nameWithPath, self.destination+'/'+path+'/',row) self.rsyncThread.start() # start the thread # Pour les tests uniquement #rsync://[email protected]/isos/$release/ #self.nameWithPath='rsync://ftp5.gwdg.de/pub/linux/mageia/iso/4.1/Mageia-4.1-LiveCD-GNOME-en-i586-CD/' def closeFill(self,code): if code==0: # list returned list=self.fillList.getList() for iso in list: self.add(iso) elif code==1: self.lvMessage(self.tr("Command rsync not found")) elif code==2: self.lvMessage(self.tr("Error in rsync parameters")) elif code==3: self.lvMessage(self.tr("Unknown error in rsync")) list=self.fillList.getList() list=self.fillList.getLocal() for path,iso,isoSize in list: self.localAdd(path,iso, isoSize) self.fillList.quit() self.lw.hide() def stopSync(self): self.rsyncThread.stop() self.IprogressBar.setEnabled(False) self.stop.setEnabled(False) self.syncGo.setEnabled(True) self.listIsos.setEnabled(True) self.selectAll.setEnabled(True) def main(self): self.show() # Load or look for intitial parameters self.prefsInit() # look for Isos list and add it to the isoSync list. Update preferences self.updateList() self.multiSelect() def close(self): self.rsyncThread.stop() exit(0)
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() fileMenu = QMenu("&File", self) openAction = fileMenu.addAction("&Open...") openAction.setShortcut("Ctrl+O") saveAction = fileMenu.addAction("&Save As...") saveAction.setShortcut("Ctrl+S") quitAction = fileMenu.addAction("E&xit") quitAction.setShortcut("Ctrl+Q") self.setupModel() self.setupViews() openAction.triggered.connect(self.openFile) saveAction.triggered.connect(self.saveFile) quitAction.triggered.connect(QApplication.instance().quit) self.menuBar().addMenu(fileMenu) self.statusBar() self.openFile(':/Charts/qtdata.cht') self.setWindowTitle("Chart") self.resize(870, 550) def setupModel(self): self.model = QStandardItemModel(8, 2, self) self.model.setHeaderData(0, Qt.Horizontal, "Label") self.model.setHeaderData(1, Qt.Horizontal, "Quantity") def setupViews(self): splitter = QSplitter() table = QTableView() self.pieChart = PieView() splitter.addWidget(table) splitter.addWidget(self.pieChart) splitter.setStretchFactor(0, 0) splitter.setStretchFactor(1, 1) table.setModel(self.model) self.pieChart.setModel(self.model) self.selectionModel = QItemSelectionModel(self.model) table.setSelectionModel(self.selectionModel) self.pieChart.setSelectionModel(self.selectionModel) table.horizontalHeader().setStretchLastSection(True) self.setCentralWidget(splitter) def openFile(self, path=None): if not path: path, _ = QFileDialog.getOpenFileName(self, "Choose a data file", '', '*.cht') if path: f = QFile(path) if f.open(QFile.ReadOnly | QFile.Text): stream = QTextStream(f) self.model.removeRows(0, self.model.rowCount(QModelIndex()), QModelIndex()) row = 0 line = stream.readLine() while line: self.model.insertRows(row, 1, QModelIndex()) pieces = line.split(',') self.model.setData(self.model.index(row, 0, QModelIndex()), pieces[0]) self.model.setData(self.model.index(row, 1, QModelIndex()), float(pieces[1])) self.model.setData(self.model.index(row, 0, QModelIndex()), QColor(pieces[2]), Qt.DecorationRole) row += 1 line = stream.readLine() f.close() self.statusBar().showMessage("Loaded %s" % path, 2000) def saveFile(self): fileName, _ = QFileDialog.getSaveFileName(self, "Save file as", '', '*.cht') if fileName: f = QFile(fileName) if f.open(QFile.WriteOnly | QFile.Text): for row in range(self.model.rowCount(QModelIndex())): pieces = [] pieces.append(self.model.data(self.model.index(row, 0, QModelIndex()), Qt.DisplayRole)) pieces.append(str(self.model.data(self.model.index(row, 1, QModelIndex()), Qt.DisplayRole))) pieces.append(self.model.data(self.model.index(row, 0, QModelIndex()), Qt.DecorationRole).name()) f.write(QByteArray(','.join(pieces))) f.write('\n') f.close() self.statusBar().showMessage("Saved %s" % fileName, 2000)
class ProcessingView(QWidget): def __init__(self, parent=None): super(QWidget, self).__init__(parent) self.parent = parent self.title = "Log Processing" self.logFileManager = LogFileManager() self.initUI() def initUI(self): self.setWindowTitle(self.title) self.tableView = QTableView() self.model = QStandardItemModel() self.model.setHorizontalHeaderLabels([ 'File Name', 'Source', 'Validation', 'Cleansing', 'Ingestion', 'Selection' ]) #set headers self.tableView.setModel(self.model) self.tableView.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) #resize columns to fit into the widget self.vBoxLayout = QVBoxLayout() self.vBoxLayout.addWidget(self.tableView) self.setLayout(self.vBoxLayout) def addToTable(self, logfile): name = QStandardItem(logfile.getLogName()) source = QStandardItem(logfile.getPathToFile()) # Wrost implementation of all but this will do, probably should # be changed validation = QStandardItem() if logfile.getValidationStatus() == "null": validation.setText("IN-PROGRESS") validation.setBackground(QBrush(QColor("yellow"))) elif not logfile.getValidationStatus(): validation.setText("FAILED") validation.setBackground(QBrush(QColor("red"))) else: validation.setText("PASSED") validation.setBackground(QBrush(QColor("green"))) cleansing = QStandardItem() if logfile.getLogCleansingStatus() == "null": cleansing.setText("IN-PROGRESS") cleansing.setBackground(QBrush(QColor("yellow"))) elif not logfile.getLogCleansingStatus(): cleansing.setText("FAILED") cleansing.setBackground(QBrush(QColor("red"))) else: cleansing.setText("PASSED") cleansing.setBackground(QBrush(QColor("green"))) ingestion = QStandardItem() if logfile.getIngestionStatus() == "null": ingestion.setText("IN-PROGRESS") ingestion.setBackground(QBrush(QColor("yellow"))) elif not logfile.getIngestionStatus(): ingestion.setText("FAILED") ingestion.setBackground(QBrush(QColor("red"))) else: ingestion.setText("PASSED") ingestion.setBackground(QBrush(QColor("green"))) self.model.appendRow( [name, source, validation, cleansing, ingestion, QStandardItem()]) self.tableView.setModel(self.model) def deleteFromTable(self): pass def updateRowStatus(self, logfile, process): red = QStandardItem("FAILED") red.setBackground(QBrush(QColor("red"))) green = QStandardItem("PASSED") green.setBackground(QBrush(QColor("green"))) yellow = QStandardItem("IN-PROGRESS") yellow.setBackground(QBrush(QColor("yellow"))) item = None row = -1 col = -1 for index in range(self.model.rowCount()): qindex = self.model.index(index, 0) name = self.model.data(qindex) if name == logfile.getLogName(): row = index if process == "validation": col = 2 if logfile.getValidationStatus() == "null": item = yellow elif not logfile.getValidationStatus(): item = red else: item = green elif process == "cleansing": col = 3 if logfile.getLogCleansingStatus() == "null": item = yellow elif not logfile.getLogCleansingStatus(): item = red else: item = green else: col = 4 if logfile.getIngestionStatus() == "null": item = yellow elif not logfile.getIngestionStatus(): item = red else: item = green break self.model.setItem(row, col, item) self.tableView.setModel(self.model) def update(self): # self.logFileManager.addLogFile("file.py", "root/", "text") self.parent.updateView(1)
class GuiHandViewer(QSplitter): def __init__(self, config, querylist, mainwin): QSplitter.__init__(self, mainwin) self.config = config self.main_window = mainwin self.sql = querylist self.replayer = None self.db = Database.Database(self.config, sql=self.sql) filters_display = { "Heroes": True, "Sites": True, "Games": True, "Currencies": False, "Limits": True, "LimitSep": True, "LimitType": True, "Positions": True, "Type": True, "Seats": False, "SeatSep": False, "Dates": True, "Cards": True, "Groups": False, "GroupsAll": False, "Button1": True, "Button2": False } self.filters = Filters.Filters(self.db, display=filters_display) self.filters.registerButton1Name(_("Load Hands")) self.filters.registerButton1Callback(self.loadHands) self.filters.registerCardsCallback(self.filter_cards_cb) scroll = QScrollArea() scroll.setWidget(self.filters) self.handsFrame = QFrame() self.handsVBox = QVBoxLayout() self.handsFrame.setLayout(self.handsVBox) self.addWidget(scroll) self.addWidget(self.handsFrame) self.setStretchFactor(0, 0) self.setStretchFactor(1, 1) self.deck_instance = Deck.Deck(self.config, height=42, width=30) self.cardImages = self.init_card_images() # Dict of colnames and their column idx in the model/ListStore self.colnum = { 'Stakes': 0, 'Pos': 1, 'Street0': 2, 'Action0': 3, 'Street1-4': 4, 'Action1-4': 5, 'Won': 6, 'Bet': 7, 'Net': 8, 'Game': 9, 'HandId': 10, } self.view = QTableView() self.view.setSelectionBehavior(QTableView.SelectRows) self.handsVBox.addWidget(self.view) self.model = QStandardItemModel(0, len(self.colnum), self.view) self.filterModel = QSortFilterProxyModel() self.filterModel.setSourceModel(self.model) self.filterModel.setSortRole(Qt.UserRole) self.view.setModel(self.filterModel) self.view.verticalHeader().hide() self.model.setHorizontalHeaderLabels([ 'Stakes', 'Pos', 'Street0', 'Action0', 'Street1-4', 'Action1-4', 'Won', 'Bet', 'Net', 'Game', 'HandId' ]) self.view.doubleClicked.connect(self.row_activated) self.view.contextMenuEvent = self.contextMenu self.filterModel.rowsInserted.connect( lambda index, start, end: [self.view.resizeRowToContents(r) for r in xrange(start, end + 1)]) self.filterModel.filterAcceptsRow = lambda row, sourceParent: self.is_row_in_card_filter( row) self.view.resizeColumnsToContents() self.view.setSortingEnabled(True) def init_card_images(self): suits = ('s', 'h', 'd', 'c') ranks = (14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2) card_images = [0] * 53 for j in range(0, 13): for i in range(0, 4): loc = Card.cardFromValueSuit(ranks[j], suits[i]) card_image = self.deck_instance.card(suits[i], ranks[j]) card_images[loc] = card_image back_image = self.deck_instance.back() card_images[0] = back_image return card_images def loadHands(self, checkState): hand_ids = self.get_hand_ids_from_date_range( self.filters.getDates()[0], self.filters.getDates()[1]) self.reload_hands(hand_ids) def get_hand_ids_from_date_range(self, start, end): q = self.db.sql.query['handsInRange'] q = q.replace('<datetest>', "between '" + start + "' and '" + end + "'") q = self.filters.replace_placeholders_with_filter_values(q) c = self.db.get_cursor() c.execute(q) return [r[0] for r in c.fetchall()] def rankedhand(self, hand, game): ranks = { '0': 0, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, 'T': 10, 'J': 11, 'Q': 12, 'K': 13, 'A': 14 } suits = {'x': 0, 's': 1, 'c': 2, 'd': 3, 'h': 4} if game == 'holdem': card1 = ranks[hand[0]] card2 = ranks[hand[3]] suit1 = suits[hand[1]] suit2 = suits[hand[4]] if card1 < card2: (card1, card2) = (card2, card1) (suit1, suit2) = (suit2, suit1) if suit1 == suit2: suit1 += 4 return card1 * 14 * 14 + card2 * 14 + suit1 else: return 0 def reload_hands(self, handids): self.hands = {} self.model.removeRows(0, self.model.rowCount()) if len(handids) == 0: return progress = QProgressDialog("Loading hands", "Abort", 0, len(handids), self) progress.setValue(0) progress.show() for idx, handid in enumerate(handids): if progress.wasCanceled(): break self.hands[handid] = self.importhand(handid) self.addHandRow(handid, self.hands[handid]) progress.setValue(idx + 1) if idx % 10 == 0: QCoreApplication.processEvents() self.view.resizeColumnsToContents() self.view.resizeColumnsToContents() def addHandRow(self, handid, hand): hero = self.filters.getHeroes()[hand.sitename] won = 0 if hero in hand.collectees.keys(): won = hand.collectees[hero] bet = 0 if hero in hand.pot.committed.keys(): bet = hand.pot.committed[hero] net = won - bet pos = hand.get_player_position(hero) gt = hand.gametype['category'] row = [] if hand.gametype['base'] == 'hold': board = [] board.extend(hand.board['FLOP']) board.extend(hand.board['TURN']) board.extend(hand.board['RIVER']) pre_actions = hand.get_actions_short(hero, 'PREFLOP') post_actions = '' if 'F' not in pre_actions: #if player hasen't folded preflop post_actions = hand.get_actions_short_streets( hero, 'FLOP', 'TURN', 'RIVER') row = [ hand.getStakesAsString(), pos, hand.join_holecards(hero), pre_actions, ' '.join(board), post_actions, str(won), str(bet), str(net), gt, str(handid) ] elif hand.gametype['base'] == 'stud': third = " ".join( hand.holecards['THIRD'][hero][0]) + " " + " ".join( hand.holecards['THIRD'][hero][1]) # ugh - fix the stud join_holecards function so we can retrieve sanely later_streets = [] later_streets.extend(hand.holecards['FOURTH'][hero][0]) later_streets.extend(hand.holecards['FIFTH'][hero][0]) later_streets.extend(hand.holecards['SIXTH'][hero][0]) later_streets.extend(hand.holecards['SEVENTH'][hero][0]) pre_actions = hand.get_actions_short(hero, 'THIRD') post_actions = '' if 'F' not in pre_actions: post_actions = hand.get_actions_short_streets( hero, 'FOURTH', 'FIFTH', 'SIXTH', 'SEVENTH') row = [ hand.getStakesAsString(), pos, third, pre_actions, ' '.join(later_streets), post_actions, str(won), str(bet), str(net), gt, str(handid) ] elif hand.gametype['base'] == 'draw': row = [ hand.getStakesAsString(), pos, hand.join_holecards(hero, street='DEAL'), hand.get_actions_short(hero, 'DEAL'), None, None, str(won), str(bet), str(net), gt, str(handid) ] modelrow = [QStandardItem(r) for r in row] for index, item in enumerate(modelrow): item.setEditable(False) if index in (self.colnum['Street0'], self.colnum['Street1-4']): cards = item.data(Qt.DisplayRole) item.setData(self.render_cards(cards), Qt.DecorationRole) item.setData("", Qt.DisplayRole) item.setData(cards, Qt.UserRole + 1) if index in (self.colnum['Bet'], self.colnum['Net'], self.colnum['Won']): item.setData(float(item.data(Qt.DisplayRole)), Qt.UserRole) self.model.appendRow(modelrow) def copyHandToClipboard(self, checkState, hand): handText = StringIO() hand.writeHand(handText) QApplication.clipboard().setText(handText.getvalue()) def contextMenu(self, event): index = self.view.currentIndex() if index.row() < 0: return hand = self.hands[int( index.sibling(index.row(), self.colnum['HandId']).data())] m = QMenu() copyAction = m.addAction('Copy to clipboard') copyAction.triggered.connect( partial(self.copyHandToClipboard, hand=hand)) m.move(event.globalPos()) m.exec_() def filter_cards_cb(self, card): if hasattr(self, 'hands'): self.filterModel.invalidateFilter() def is_row_in_card_filter(self, rownum): """ Returns true if the cards of the given row are in the card filter """ # Does work but all cards that should NOT be displayed have to be clicked. card_filter = self.filters.getCards() hcs = self.model.data(self.model.index(rownum, self.colnum['Street0']), Qt.UserRole + 1).split(' ') if '0x' in hcs: #if cards are unknown return True return True gt = self.model.data(self.model.index(rownum, self.colnum['Game'])) if gt not in ('holdem', 'omahahi', 'omahahilo'): return True # Holdem: Compare the real start cards to the selected filter (ie. AhKh = AKs) value1 = Card.card_map[hcs[0][0]] value2 = Card.card_map[hcs[1][0]] idx = Card.twoStartCards(value1, hcs[0][1], value2, hcs[1][1]) abbr = Card.twoStartCardString(idx) return card_filter[abbr] def row_activated(self, index): handlist = list(sorted(self.hands.keys())) self.replayer = GuiReplayer.GuiReplayer(self.config, self.sql, self.main_window, handlist) self.replayer.play_hand( handlist.index( int(index.sibling(index.row(), self.colnum['HandId']).data()))) def importhand(self, handid=1): # Fetch hand info # We need at least sitename, gametype, handid # for the Hand.__init__ h = Hand.hand_factory(handid, self.config, self.db) # Set the hero for this hand using the filter for the sitename of this hand h.hero = self.filters.getHeroes()[h.sitename] return h def render_cards(self, cardstring): card_width = 30 card_height = 42 if cardstring is None or cardstring == '': cardstring = "0x" cardstring = cardstring.replace("'", "") cardstring = cardstring.replace("[", "") cardstring = cardstring.replace("]", "") cardstring = cardstring.replace("'", "") cardstring = cardstring.replace(",", "") cards = [Card.encodeCard(c) for c in cardstring.split(' ')] n_cards = len(cards) pixbuf = QPixmap(card_width * n_cards, card_height) painter = QPainter(pixbuf) x = 0 # x coord where the next card starts in pixbuf for card in cards: painter.drawPixmap(x, 0, self.cardImages[card]) x += card_width return pixbuf
class KeysTab(GalacteekTab): def __init__(self, *args, **kw): super().__init__(*args, **kw) self.ctx.tabIdent = 'ipfs-keys-manager' self.resolveTimeout = 60 * 5 self.keysW = QWidget() self.addToLayout(self.keysW) self.ui = ui_keys.Ui_KeysForm() self.ui.setupUi(self.keysW) self.ui.addKeyButton.clicked.connect(self.onAddKeyClicked) self.ui.deleteKeyButton.clicked.connect( lambda *args: ensure(self.onDelKeyClicked())) self.model = QStandardItemModel(parent=self) self.ui.treeKeys = KeysView() self.ui.treeKeys.doubleClicked.connect(self.onItemDoubleClicked) self.ui.treeKeys.setModel(self.model) self.ui.verticalLayout.addWidget(self.ui.treeKeys) self.setupModel() self.app.task(self.listKeys) def setupModel(self): self.model.clear() self.model.setColumnCount(3) self.model.setHorizontalHeaderLabels( [iKeyName(), iP2PKey(), iKeyResolve()]) self.ui.treeKeys.header().setSectionResizeMode( 0, QHeaderView.ResizeToContents) self.ui.treeKeys.header().setSectionResizeMode( 1, QHeaderView.ResizeToContents) async def onDelKeyClicked(self): idx = self.ui.treeKeys.currentIndex() if not idx.isValid(): return messageBox('Invalid key') idxName = self.model.index(idx.row(), 0, idx.parent()) keyName = self.model.data(idxName) if not keyName: return reply = await questionBoxAsync( 'Delete key', 'Delete IPNS key <b>{key}</b> ?'.format(key=keyName)) if reply is True: self.app.task(self.delKey, keyName) def onAddKeyClicked(self): runDialog(AddKeyDialog, self.app, parent=self) @ipfsOp async def delKey(self, ipfsop, name): if await ipfsop.keysRemove(name): modelDelete(self.model, name) self.updateKeysList() @ipfsOp async def listKeys(self, ipfsop): keys = await ipfsop.keys() for key in keys: found = modelSearch(self.model, search=key['Name']) if len(found) > 0: continue nameItem = UneditableItem(key['Name']) nameItem.setToolTip(key['Name']) resolveItem = KeyResolvedItem('') self.model.appendRow([nameItem, KeyItem(key['Id']), resolveItem]) self.app.task(self.keyResolve, key, resolveItem) @ipfsOp async def keyResolve(self, ipfsop, key, item): if not isinstance(item, KeyResolvedItem): return now = datetime.now() update = False if item.resolvedLast is None: update = True if isinstance(item.resolvedLast, datetime): delta = now - item.resolvedLast if delta.seconds > self.resolveTimeout: update = True if update is True: resolved = await ipfsop.nameResolve(key['Id']) if isinstance(resolved, dict): rPath = resolved.get('Path') if not rPath: item.setBackground(QBrush(QColor('red'))) elif item.resolvesTo and rPath != item.resolvesTo: color = QColor('#c1f0c1') item.setBackground(QBrush(color)) else: item.setBackground(QBrush(Qt.NoBrush)) if rPath and IPFSPath(rPath).valid: item.resolvesTo = rPath item.setText(rPath) item.setToolTip("{path}\n\nResolved date: {date}".format( path=rPath, date=now.isoformat(sep=' ', timespec='seconds'))) else: item.setText(iUnknown()) item.resolvedLast = now # Ensure another one self.app.loop.call_later(self.resolveTimeout, self.app.task, self.keyResolve, key, item) def updateKeysList(self): self.app.task(self.listKeys) def onItemDoubleClicked(self, index): # Browse IPNS key associated with current item on double-click keyHash = self.model.data(self.model.index(index.row(), 1)) self.gWindow.addBrowserTab().browseIpnsKey(keyHash)
class HistoryMatchesWidget(QTreeView): historyItemSelected = pyqtSignal(str) collapsed = pyqtSignal() def __init__(self, parent=None): super(HistoryMatchesWidget, self).__init__(parent) self.setWindowFlag(Qt.Popup, True) self.setWindowFlag(Qt.FramelessWindowHint, True) self.setWindowFlag(Qt.WindowStaysOnTopHint, True) self.setWindowFlag(Qt.Tool, True) self.setAttribute(Qt.WA_ShowWithoutActivating) self.setWindowModality(Qt.NonModal) # self.setWindowModality(Qt.ApplicationModal) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.app = QApplication.instance() self.setObjectName('historySearchResults') self.clicked.connect(self.onItemActivated) self.hModel = QStandardItemModel() self.fontCategory = QFont('Times', 16, QFont.Bold) self.fontItems = QFont('Inter UI', 14) self.fontItemsTitle = QFont('Inter UI', 14, italic=True) self.setModel(self.hModel) self.setHeaderHidden(True) self.idxSelCount = 0 self.selectionModel().currentChanged.connect(self.onIndexChanged) @property def itemRoot(self): return self.hModel.invisibleRootItem() def onIndexChanged(self, current, previous): item = self.hModel.itemFromIndex(current) if isinstance(item, ResultCategoryItem) and self.idxSelCount == 0: # Automatically jump to first item in the category self.selectionModel().clearSelection() idx = self.hModel.index(0, 0, current) if idx.isValid(): self.setCurrentIndex(idx) self.idxSelCount += 1 def onItemActivated(self, idx): idxUrl = self.hModel.index(idx.row(), 1, idx.parent()) data = self.hModel.data(idxUrl) if isinstance(data, str) and data: self.historyItemSelected.emit(data) async def showMatches(self, marks, hMatches): self.hModel.clear() brush = QBrush(QColor('#508cac')) mItem = ResultCategoryItem(iHashmarks()) mItem.setFont(self.fontCategory) mItem.setBackground(brush) mItemE = UneditableItem('') mItemE.setBackground(brush) if len(marks) > 0: for match in marks: title = match.title[0:64] if match.title else iUnknown() url = match.preferredUrl() itemT = UneditableItem(title) itemT.setFont(self.fontItemsTitle) item = UneditableItem(url) item.setToolTip(url) item.setData(url, Qt.EditRole) item.setFont(self.fontItems) mItem.appendRow([itemT, item]) self.hModel.invisibleRootItem().appendRow([mItem, mItemE]) hItem = ResultCategoryItem('History') hItemE = UneditableItem('') hItem.setFont(self.fontCategory) hItem.setBackground(brush) hItemE.setBackground(brush) if len(hMatches) > 0: for match in hMatches: title = match['title'][0:64] if match['title'] else iUnknown() itemT = UneditableItem(title) itemT.setFont(self.fontItemsTitle) item = UneditableItem(match['url']) item.setToolTip(match['url']) item.setData(match['url'], Qt.EditRole) item.setFont(self.fontItems) hItem.appendRow([itemT, item]) self.hModel.invisibleRootItem().appendRow([hItem, hItemE]) self.expandAll() self.resizeColumnToContents(0) def hideEvent(self, event): self.idxSelCount = 0 super().hideEvent(event) def keyPressEvent(self, event): if event.key() == Qt.Key_Return: # The 'activated' signal does not seem to have the same # behavior across platforms so we handle Return manually here curIdx = self.currentIndex() if curIdx.isValid(): self.onItemActivated(curIdx) elif event.key() == Qt.Key_Escape: self.hide() self.collapsed.emit() else: super(HistoryMatchesWidget, self).keyPressEvent(event)
class MainWindow(QtWidgets.QMainWindow, ui): def __init__(self, parent=None): QtWidgets.QMainWindow.__init__(self, parent) self.setupUi(self) self.setWindowTitle("{} - {}".format(__title__, __version__)) self._settingsFile = os.path.join(ROOT, "data", "settings.ini") self._threadPool = [] self.sitesModel = QStandardItemModel() self.sitesModel.setHorizontalHeaderLabels( ["URL", "Result", "Code", "Status"]) self.sitesTableView.setModel(self.sitesModel) self.importUrlsAction.triggered.connect(self.importUrls) self.exportResultsAction.triggered.connect(self.exportResults) self.quitAction.triggered.connect( lambda: QtWidgets.QApplication.quit()) self.clearTableAction.triggered.connect(self.clearTable) self.aboutAction.triggered.connect(self.about) self.startButton.clicked.connect(self.start) self.stopButton.clicked.connect(self.stop) self.buttonTest.clicked.connect(self.test) self.sitesTableView.doubleClicked.connect( self.sitesTableView_doubleClicked) self.labelActiveThreads = QtWidgets.QLabel("Active threads: 0") self.statusbar.addPermanentWidget(self.labelActiveThreads) self.actionRemove_selected.triggered.connect(self.removeSelected) self.actionInvert_selection.triggered.connect(self.invertSelection) self.actionRemove_duplicates.triggered.connect(self.removeDuplicates) self.actionSelect_all.triggered.connect(self.sitesTableView.selectAll) # Events self.resizeEvent = self.onResize self.closeEvent = self.onClose self.showEvent = self.onShow self._tableViewWidth = 0 self._threads = [] self._activeThreads = 0 self._workers = [] self._progressDone = 0 self._progressTotal = 0 self._boldFont = QFont() self._boldFont.setBold(True) self._recentFIles = [] self.loadSettings() self.centerWindow() self.timerPulse = QTimer(self) self.timerPulse.timeout.connect(self.pulse) self.timerPulse.start(1000) # text = readTextFile("data/sites2.txt") # for url in text.strip().splitlines(): # resultCell = QStandardItem("") # # resultCell.setTextAlignment(Qt.AlignCeter) # codeCell = QStandardItem("") # # codeCell.setTextAlignment(Qt.AlignCenter) # self.sitesModel.appendRow([QStandardItem(url), resultCell, codeCell]) self.stopButton.setEnabled(False) self.buttonTest.setVisible(False) def centerWindow(self): fg = self.frameGeometry() c = QtWidgets.QDesktopWidget().availableGeometry().center() fg.moveCenter(c) self.move(fg.topLeft()) def loadSettings(self): if os.path.isfile(self._settingsFile): settings = QSettings(self._settingsFile, QSettings.IniFormat) self.restoreGeometry(settings.value("geometry", '')) self.restoreState(settings.value("windowState", '')) self._tableViewWidth = int(settings.value("tableViewWidth", '')) self.threadsSpin.setValue( settings.value("threadsCount", THREADS, type=int)) self.timeoutSpin.setValue( settings.value("timeoutSpin", TIMEOUT, type=int)) def saveSettings(self): settings = QSettings(self._settingsFile, QSettings.IniFormat) settings.setValue("geometry", self.saveGeometry()) settings.setValue("windowState", self.saveState()) settings.setValue("tableViewWidth", self.sitesTableView.frameGeometry().width()) settings.setValue("threadsCount", self.threadsSpin.value()) settings.setValue("timeout", self.timeoutSpin.value()) def onResize(self, event): self.resizeTableColumns() QtWidgets.QMainWindow.resizeEvent(self, event) def onClose(self, event): self.saveSettings() QtWidgets.QMainWindow.closeEvent(self, event) def onShow(self, event): self.resizeTableColumns() QtWidgets.QMainWindow.showEvent(self, event) def resizeTableColumns(self): self.sitesTableView.setColumnWidth( 0, int(self.sitesTableView.frameGeometry().width() * 0.6)) self.sitesTableView.setColumnWidth( 1, int(self.sitesTableView.frameGeometry().width() * 0.1)) def start(self): self.resetTable() model = self.sitesModel queues = split_list(range(self.sitesModel.rowCount()), self.threadsSpin.value()) self._progressTotal = self.sitesModel.rowCount() self._progressDone = 0 self._threads = [] self._workers = [] for i, rows in enumerate(queues): self._threads.append(MyThread()) queue = Queue() for row in rows: url = model.data(model.index(row, 0)) queue.put((row, url)) self._workers.append( CheckAliveWorker(check_alive, timeout=self.timeoutSpin.value(), queue=queue)) self._workers[i].moveToThread(self._threads[i]) self._threads[i].started.connect(self._workers[i].start) self._threads[i].finished.connect(self._threads[i].deleteLater) self._workers[i].status.connect(self.onStatus) self._workers[i].result.connect(self.onResult) self._workers[i].finished.connect(self._threads[i].quit) self._workers[i].finished.connect(self._workers[i].deleteLater) for i in range(self.threadsSpin.value()): self._threads[i].start() self.startButton.setEnabled(False) self.stopButton.setEnabled(True) def setActiveThreadsCount(self, i): self._activeThreads = i def pulse(self): self.labelActiveThreads.setText("Active threads: {}".format( MyThread.activeCount)) if MyThread.activeCount == 0: if not self.sitesTableView.isSortingEnabled(): self.sitesTableView.setSortingEnabled(True) if not self.startButton.isEnabled(): self.startButton.setEnabled(True) if self.stopButton.isEnabled(): self.stopButton.setEnabled(False) else: if self.sitesTableView.isSortingEnabled(): self.sitesTableView.setSortingEnabled(False) def stop(self): for i, _ in enumerate(self._workers): self._workers[i]._running = False @pyqtSlot(tuple) def onStatus(self, tuple_): i, status = tuple_ self.sitesModel.setData(self.sitesModel.index(i, 3), status) @pyqtSlot(object) def onResult(self, result): self.sitesModel.item(result["row"], 1).setFont(self._boldFont) # self.sitesModel.item(result["row"], 1).setForeground(Qt.white) self.sitesModel.setData(self.sitesModel.index(result["row"], 2), result["status_code"]) if result["result"]: self.sitesModel.setData(self.sitesModel.index(result["row"], 1), "OK") self.sitesModel.item(result["row"], 1).setForeground(Qt.green) logger.info("{} {}".format(result["url"], result["status_code"])) else: self.sitesModel.setData(self.sitesModel.index(result["row"], 1), "Fail") self.sitesModel.item(result["row"], 1).setForeground(Qt.red) logger.info("{} {}".format(result["url"], result["msg"])) self._progressDone += 1 self.progressBar.setValue( int(float(self._progressDone) / self._progressTotal * 100)) def importUrls(self): filePath, fileType = QtWidgets.QFileDialog.getOpenFileName( self, "Import URLs", filter="Text files (*.txt)") if filePath: text = readTextFile(filePath) for url in text.strip().splitlines(): resultCell = QStandardItem("") resultCell.setTextAlignment(Qt.AlignCenter) codeCell = QStandardItem("") codeCell.setTextAlignment(Qt.AlignCenter) self.sitesModel.appendRow( [QStandardItem(url), resultCell, codeCell]) def sitesTableView_doubleClicked(self, modelIndex): model = self.sitesModel row = modelIndex.row() url = model.data(model.index(row, 0)) webbrowser.open(url) def resetTable(self): model = self.sitesModel for i in range(model.rowCount()): model.setData(model.index(i, 1), "") model.setData(model.index(i, 2), "") def clearTable(self): self.tableRemoveAllRows(self.sitesModel) def tableRemoveAllRows(self, model): for i in reversed(range(model.rowCount())): model.removeRow(i) def exportResults(self): filePath, fileType = QtWidgets.QFileDialog.getSaveFileName( self, "Export URLs", filter="Text file (*.txt);;CSV file (*.csv);;JSON file (*.json)") model = self.sitesModel data = [] for i in range(model.rowCount()): data.append({ "URL": model.data(model.index(i, 0)), "Result": model.data(model.index(i, 1)), "Code": model.data(model.index(i, 2)), }) if "*.txt" in fileType: with open(filePath, "w") as f: f.write("\n".join([i["URL"] for i in data])) elif "*.csv" in fileType: with open(filePath, "w") as f: w = csv.DictWriter(f, data[0].keys()) w.writeheader() w.writerows(data) elif "*.json" in fileType: with open(filePath, "w") as f: f.write(json.dumps(data)) def about(self): QtWidgets.QMessageBox.about( self, "About {}".format(__title__), """<b>{} v{}</b> <p>© 2017. <p>{} <p>Python {} - Qt {} - PyQt {} on {}""".format( __title__, __version__, __description__, platform.python_version(), QT_VERSION_STR, PYQT_VERSION_STR, platform.system())) def test(self): pass def selectedRows(self): rows = set() for index in self.sitesTableView.selectionModel().selectedIndexes(): rows.add(index.row()) return list(rows) def removeSelected(self): model = self.sitesModel for i in reversed(self.selectedRows()): model.removeRow(i) def removeDuplicates(self): items = [] foundDuplicates = False for i in range(self.sitesModel.rowCount()): item = self.sitesModel.data(self.sitesModel.index(i, 0)) if item not in items: items.append(item) else: foundDuplicates = True if foundDuplicates: self.clearTable() for i, item in enumerate(items): self.sitesModel.setData(self.sitesModel.index(i, 0), item) def invertSelection(self): # self.sitesTableView.selectAll() selectedRows = self.selectedRows() self.sitesTableView.clearSelection() for i in range(self.sitesModel.rowCount()): if i not in selectedRows: self.sitesTableView.selectRow(i)
class comic_meta_data_editor(QDialog): configGroup = "ComicsProjectManagementTools" # Translatable genre dictionary that has it's translated entries added to the genrelist and from which the untranslated items are taken. acbfGenreList = {"science_fiction": str(i18n("Science Fiction")), "fantasy": str(i18n("Fantasy")), "adventure": str(i18n("Adventure")), "horror": str(i18n("Horror")), "mystery": str(i18n("Mystery")), "crime": str(i18n("Crime")), "military": str(i18n("Military")), "real_life": str(i18n("Real Life")), "superhero": str(i18n("Superhero")), "humor": str(i18n("Humor")), "western": str(i18n("Western")), "manga": str(i18n("Manga")), "politics": str(i18n("Politics")), "caricature": str(i18n("Caricature")), "sports": str(i18n("Sports")), "history": str(i18n("History")), "biography": str(i18n("Biography")), "education": str(i18n("Education")), "computer": str(i18n("Computer")), "religion": str(i18n("Religion")), "romance": str(i18n("Romance")), "children": str(i18n("Children")), "non-fiction": str(i18n("Non Fiction")), "adult": str(i18n("Adult")), "alternative": str(i18n("Alternative")), "artbook": str(i18n("Artbook")), "other": str(i18n("Other"))} acbfAuthorRolesList = {"Writer": str(i18n("Writer")), "Adapter": str(i18n("Adapter")), "Artist": str(i18n("Artist")), "Penciller": str(i18n("Penciller")), "Inker": str(i18n("Inker")), "Colorist": str(i18n("Colorist")), "Letterer": str(i18n("Letterer")), "Cover Artist": str(i18n("Cover Artist")), "Photographer": str(i18n("Photographer")), "Editor": str(i18n("Editor")), "Assistant Editor": str(i18n("Assistant Editor")), "Designer": str(i18n("Designer")), "Translator": str(i18n("Translator")), "Other": str(i18n("Other"))} def __init__(self): super().__init__() # Get the keys for the autocompletion. self.genreKeysList = [] self.characterKeysList = [] self.ratingKeysList = {} self.formatKeysList = [] self.otherKeysList = [] self.authorRoleList = [] for g in self.acbfGenreList.values(): self.genreKeysList.append(g) for r in self.acbfAuthorRolesList.values(): self.authorRoleList.append(r) mainP = Path(os.path.abspath(__file__)).parent self.get_auto_completion_keys(mainP) extraKeyP = Path(QDir.homePath()) / Application.readSetting(self.configGroup, "extraKeysLocation", str()) self.get_auto_completion_keys(extraKeyP) # Setup the dialog. self.setLayout(QVBoxLayout()) mainWidget = QTabWidget() self.layout().addWidget(mainWidget) self.setWindowTitle(i18n("Comic Metadata")) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.layout().addWidget(buttons) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) # Title, concept, summary, genre, characters, format, rating, language, series, other keywords metadataPage = QWidget() mformLayout = QFormLayout() metadataPage.setLayout(mformLayout) self.lnTitle = QLineEdit() self.lnTitle.setToolTip(i18n("The proper title of the comic.")) self.teSummary = QPlainTextEdit() self.teSummary.setToolTip(i18n("What will you tell others to entice them to read your comic?")) self.lnGenre = QLineEdit() genreCompletion = multi_entry_completer() genreCompletion.setModel(QStringListModel(self.genreKeysList)) self.lnGenre.setCompleter(genreCompletion) genreCompletion.setCaseSensitivity(False) self.lnGenre.setToolTip(i18n("The genre of the work. Prefilled values are from the ACBF, but you can fill in your own. Separate genres with commas. Try to limit the amount to about two or three")) self.lnCharacters = QLineEdit() characterCompletion = multi_entry_completer() characterCompletion.setModel(QStringListModel(self.characterKeysList)) characterCompletion.setCaseSensitivity(False) characterCompletion.setFilterMode(Qt.MatchContains) # So that if there is a list of names with last names, people can type in a last name. self.lnCharacters.setCompleter(characterCompletion) self.lnCharacters.setToolTip(i18n("The names of the characters that this comic revolves around. Comma-separated.")) self.lnFormat = QLineEdit() formatCompletion = multi_entry_completer() formatCompletion.setModel(QStringListModel(self.formatKeysList)) formatCompletion.setCaseSensitivity(False) self.lnFormat.setCompleter(formatCompletion) ratingLayout = QHBoxLayout() self.cmbRatingSystem = QComboBox() self.cmbRatingSystem.addItems(self.ratingKeysList.keys()) self.cmbRatingSystem.setEditable(True) self.cmbRating = QComboBox() self.cmbRating.setEditable(True) self.cmbRatingSystem.currentIndexChanged.connect(self.slot_refill_ratings) ratingLayout.addWidget(self.cmbRatingSystem) ratingLayout.addWidget(self.cmbRating) self.lnSeriesName = QLineEdit() self.lnSeriesName.setToolTip(i18n("If this is part of a series, enter the name of the series and the number.")) self.spnSeriesNumber = QSpinBox() self.spnSeriesNumber.setPrefix("No. ") self.spnSeriesVol = QSpinBox() self.spnSeriesVol.setPrefix("Vol. ") seriesLayout = QHBoxLayout() seriesLayout.addWidget(self.lnSeriesName) seriesLayout.addWidget(self.spnSeriesVol) seriesLayout.addWidget(self.spnSeriesNumber) otherCompletion = multi_entry_completer() otherCompletion.setModel(QStringListModel(self.otherKeysList)) otherCompletion.setCaseSensitivity(False) otherCompletion.setFilterMode(Qt.MatchContains) self.lnOtherKeywords = QLineEdit() self.lnOtherKeywords.setCompleter(otherCompletion) self.lnOtherKeywords.setToolTip(i18n("Other keywords that don't fit in the previously mentioned sets. As always, comma-separated")) self.cmbLanguage = language_combo_box() self.cmbCountry = country_combo_box() self.cmbLanguage.currentIndexChanged.connect(self.slot_update_countries) self.cmbReadingMode = QComboBox() self.cmbReadingMode.addItem(i18n("Left to Right")) self.cmbReadingMode.addItem(i18n("Right to Left")) self.cmbCoverPage = QComboBox() self.cmbCoverPage.setToolTip(i18n("Which page is the cover page? This will be empty if there's no pages.")) mformLayout.addRow(i18n("Title:"), self.lnTitle) mformLayout.addRow(i18n("Cover Page:"), self.cmbCoverPage) mformLayout.addRow(i18n("Summary:"), self.teSummary) mformLayout.addRow(i18n("Language:"), self.cmbLanguage) mformLayout.addRow("", self.cmbCountry) mformLayout.addRow(i18n("Reading Direction:"), self.cmbReadingMode) mformLayout.addRow(i18n("Genre:"), self.lnGenre) mformLayout.addRow(i18n("Characters:"), self.lnCharacters) mformLayout.addRow(i18n("Format:"), self.lnFormat) mformLayout.addRow(i18n("Rating:"), ratingLayout) mformLayout.addRow(i18n("Series:"), seriesLayout) mformLayout.addRow(i18n("Other:"), self.lnOtherKeywords) mainWidget.addTab(metadataPage, i18n("Work")) # The page for the authors. authorPage = QWidget() authorPage.setLayout(QVBoxLayout()) explanation = QLabel(i18n("The following is a table of the authors that contributed to this comic. You can set their nickname, proper names (first, middle, last), Role (Penciller, Inker, etc), email and homepage.")) explanation.setWordWrap(True) self.authorModel = QStandardItemModel(0, 8) labels = [i18n("Nick Name"), i18n("Given Name"), i18n("Middle Name"), i18n("Family Name"), i18n("Role"), i18n("Email"), i18n("Homepage"), i18n("Language")] self.authorModel.setHorizontalHeaderLabels(labels) self.authorTable = QTableView() self.authorTable.setModel(self.authorModel) self.authorTable.verticalHeader().setDragEnabled(True) self.authorTable.verticalHeader().setDropIndicatorShown(True) self.authorTable.verticalHeader().setSectionsMovable(True) self.authorTable.verticalHeader().sectionMoved.connect(self.slot_reset_author_row_visual) delegate = author_delegate() delegate.setCompleterData(self.authorRoleList, 4) delegate.setLanguageData(len(labels) - 1) self.authorTable.setItemDelegate(delegate) author_button_layout = QWidget() author_button_layout.setLayout(QHBoxLayout()) btn_add_author = QPushButton(i18n("Add Author")) btn_add_author.clicked.connect(self.slot_add_author) btn_remove_author = QPushButton(i18n("Remove Author")) btn_remove_author.clicked.connect(self.slot_remove_author) author_button_layout.layout().addWidget(btn_add_author) author_button_layout.layout().addWidget(btn_remove_author) authorPage.layout().addWidget(explanation) authorPage.layout().addWidget(self.authorTable) authorPage.layout().addWidget(author_button_layout) mainWidget.addTab(authorPage, i18n("Authors")) # The page with publisher information. publisherPage = QWidget() publisherLayout = QFormLayout() publisherPage.setLayout(publisherLayout) self.publisherName = QLineEdit() self.publisherName.setToolTip(i18n("The name of the company, group or person who is responsible for the final version the reader gets.")) publishDateLayout = QHBoxLayout() self.publishDate = QDateEdit() self.publishDate.setDisplayFormat(QLocale().system().dateFormat()) currentDate = QPushButton(i18n("Set Today")) currentDate.setToolTip(i18n("Sets the publish date to the current date.")) currentDate.clicked.connect(self.slot_set_date) publishDateLayout.addWidget(self.publishDate) publishDateLayout.addWidget(currentDate) self.publishCity = QLineEdit() self.publishCity.setToolTip(i18n("Traditional publishers are always mentioned in source with the city they are located.")) self.isbn = QLineEdit() self.license = license_combo_box() # Maybe ought to make this a QLineEdit... self.license.setEditable(True) self.license.completer().setCompletionMode(QCompleter.PopupCompletion) dataBaseReference = QVBoxLayout() self.ln_database_name = QLineEdit() self.ln_database_name.setToolTip(i18n("If there's an entry in a comics data base, that should be added here. It is unlikely to be a factor for comics from scratch, but useful when doing a conversion.")) self.cmb_entry_type = QComboBox() self.cmb_entry_type.addItems(["IssueID", "SeriesID", "URL"]) self.cmb_entry_type.setEditable(True) self.ln_database_entry = QLineEdit() dbHorizontal = QHBoxLayout() dbHorizontal.addWidget(self.ln_database_name) dbHorizontal.addWidget(self.cmb_entry_type) dataBaseReference.addLayout(dbHorizontal) dataBaseReference.addWidget(self.ln_database_entry) publisherLayout.addRow(i18n("Name:"), self.publisherName) publisherLayout.addRow(i18n("City:"), self.publishCity) publisherLayout.addRow(i18n("Date:"), publishDateLayout) publisherLayout.addRow(i18n("ISBN:"), self.isbn) publisherLayout.addRow(i18n("License:"), self.license) publisherLayout.addRow(i18n("Database:"), dataBaseReference) mainWidget.addTab(publisherPage, i18n("Publisher")) """ Ensure that the drag and drop of authors doesn't mess up the labels. """ def slot_reset_author_row_visual(self): headerLabelList = [] for i in range(self.authorTable.verticalHeader().count()): headerLabelList.append(str(i)) for i in range(self.authorTable.verticalHeader().count()): logicalI = self.authorTable.verticalHeader().logicalIndex(i) headerLabelList[logicalI] = str(i + 1) self.authorModel.setVerticalHeaderLabels(headerLabelList) """ Set the publish date to the current date. """ def slot_set_date(self): self.publishDate.setDate(QDate().currentDate()) def slot_update_countries(self): code = self.cmbLanguage.codeForCurrentEntry() self.cmbCountry.set_country_for_locale(code) """ Append keys to autocompletion lists from the directory mainP. """ def get_auto_completion_keys(self, mainP=Path()): genre = Path(mainP / "key_genre") characters = Path(mainP / "key_characters") rating = Path(mainP / "key_rating") format = Path(mainP / "key_format") keywords = Path(mainP / "key_other") authorRole = Path(mainP / "key_author_roles") if genre.exists(): for t in list(genre.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: if str(l).strip("\n") not in self.genreKeysList: self.genreKeysList.append(str(l).strip("\n")) file.close() if characters.exists(): for t in list(characters.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: if str(l).strip("\n") not in self.characterKeysList: self.characterKeysList.append(str(l).strip("\n")) file.close() if format.exists(): for t in list(format.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: if str(l).strip("\n") not in self.formatKeysList: self.formatKeysList.append(str(l).strip("\n")) file.close() if rating.exists(): for t in list(rating.glob('**/*.csv')): file = open(str(t), "r", newline="", encoding="utf-8") ratings = csv.reader(file) title = os.path.basename(str(t)) r = 0 for row in ratings: listItem = [] if r is 0: title = row[1] else: listItem = self.ratingKeysList[title] item = [] item.append(row[0]) item.append(row[1]) listItem.append(item) self.ratingKeysList[title] = listItem r += 1 file.close() if keywords.exists(): for t in list(keywords.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: if str(l).strip("\n") not in self.otherKeysList: self.otherKeysList.append(str(l).strip("\n")) file.close() if authorRole.exists(): for t in list(authorRole.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: if str(l).strip("\n") not in self.authorRoleList: self.authorRoleList.append(str(l).strip("\n")) file.close() """ Refill the ratings box. This is called whenever the rating system changes. """ def slot_refill_ratings(self): if self.cmbRatingSystem.currentText() in self.ratingKeysList.keys(): self.cmbRating.clear() model = QStandardItemModel() for i in self.ratingKeysList[self.cmbRatingSystem.currentText()]: item = QStandardItem() item.setText(i[0]) item.setToolTip(i[1]) model.appendRow(item) self.cmbRating.setModel(model) """ Add an author with default values initialised. """ def slot_add_author(self): listItems = [] listItems.append(QStandardItem(i18n("Anon"))) # Nick name listItems.append(QStandardItem(i18n("John"))) # First name listItems.append(QStandardItem()) # Middle name listItems.append(QStandardItem(i18n("Doe"))) # Last name listItems.append(QStandardItem()) # role listItems.append(QStandardItem()) # email listItems.append(QStandardItem()) # homepage language = QLocale.system().name().split("_")[0] if language == "C": language = "en" listItems.append(QStandardItem(language)) # Language self.authorModel.appendRow(listItems) """ Remove the selected author from the author list. """ def slot_remove_author(self): self.authorModel.removeRow(self.authorTable.currentIndex().row()) """ Load the UI values from the config dictionary given. """ def setConfig(self, config): if "title" in config.keys(): self.lnTitle.setText(config["title"]) self.teSummary.clear() if "pages" in config.keys(): self.cmbCoverPage.clear() for page in config["pages"]: self.cmbCoverPage.addItem(page) if "cover" in config.keys(): if config["cover"] in config["pages"]: self.cmbCoverPage.setCurrentText(config["cover"]) if "summary" in config.keys(): self.teSummary.appendPlainText(config["summary"]) if "genre" in config.keys(): genreList = [] genreListConf = config["genre"] totalMatch = 100 if isinstance(config["genre"], dict): genreListConf = config["genre"].keys() totalMatch = 0 for genre in genreListConf: genreKey = genre if genre in self.acbfGenreList: genreKey = self.acbfGenreList[genre] if isinstance(config["genre"], dict): genreValue = config["genre"][genre] if genreValue > 0: genreKey = str(genreKey + "(" + str(genreValue) + ")") genreList.append(genreKey) self.lnGenre.setText(", ".join(genreList)) if "characters" in config.keys(): self.lnCharacters.setText(", ".join(config["characters"])) if "format" in config.keys(): self.lnFormat.setText(", ".join(config["format"])) if "rating" in config.keys(): self.cmbRating.setCurrentText(config["rating"]) else: self.cmbRating.setCurrentText("") if "ratingSystem" in config.keys(): self.cmbRatingSystem.setCurrentText(config["ratingSystem"]) else: self.cmbRatingSystem.setCurrentText("") if "otherKeywords" in config.keys(): self.lnOtherKeywords.setText(", ".join(config["otherKeywords"])) if "seriesName" in config.keys(): self.lnSeriesName.setText(config["seriesName"]) if "seriesVolume" in config.keys(): self.spnSeriesVol.setValue(config["seriesVolume"]) if "seriesNumber" in config.keys(): self.spnSeriesNumber.setValue(config["seriesNumber"]) if "language" in config.keys(): code = config["language"] if "_" in code: self.cmbLanguage.setEntryToCode(code.split("_")[0]) self.cmbCountry.setEntryToCode(code.split("_")[-1]) else: self.cmbLanguage.setEntryToCode(code) if "readingDirection" in config.keys(): if config["readingDirection"] is "leftToRight": self.cmbReadingMode.setCurrentIndex(int(Qt.LeftToRight)) else: self.cmbReadingMode.setCurrentIndex(int(Qt.RightToLeft)) else: self.cmbReadingMode.setCurrentIndex(QLocale(self.cmbLanguage.codeForCurrentEntry()).textDirection()) if "publisherName" in config.keys(): self.publisherName.setText(config["publisherName"]) if "publisherCity" in config.keys(): self.publishCity.setText(config["publisherCity"]) if "publishingDate" in config.keys(): self.publishDate.setDate(QDate.fromString(config["publishingDate"], Qt.ISODate)) if "isbn-number" in config.keys(): self.isbn.setText(config["isbn-number"]) if "license" in config.keys(): self.license.setCurrentText(config["license"]) else: self.license.setCurrentText("") # I would like to keep it ambiguous whether the artist has thought about the license or not. if "authorList" in config.keys(): authorList = config["authorList"] for i in range(len(authorList)): author = authorList[i] if len(author.keys()) > 0: listItems = [] listItems = [] listItems.append(QStandardItem(author.get("nickname", ""))) listItems.append(QStandardItem(author.get("first-name", ""))) listItems.append(QStandardItem(author.get("initials", ""))) listItems.append(QStandardItem(author.get("last-name", ""))) role = author.get("role", "") if role in self.acbfAuthorRolesList.keys(): role = self.acbfAuthorRolesList[role] listItems.append(QStandardItem(role)) listItems.append(QStandardItem(author.get("email", ""))) listItems.append(QStandardItem(author.get("homepage", ""))) listItems.append(QStandardItem(author.get("language", ""))) self.authorModel.appendRow(listItems) else: self.slot_add_author() dbRef = config.get("databaseReference", {}) self.ln_database_name.setText(dbRef.get("name", "")) self.ln_database_entry.setText(dbRef.get("entry", "")) stringCmbEntryType = self.cmb_entry_type.itemText(0) self.cmb_entry_type.setCurrentText(dbRef.get("type", stringCmbEntryType)) """ Store the GUI values into the config dictionary given. @return the config diactionary filled with new values. """ def getConfig(self, config): text = self.lnTitle.text() if len(text) > 0 and text.isspace() is False: config["title"] = text elif "title" in config.keys(): config.pop("title") config["cover"] = self.cmbCoverPage.currentText() listkeys = self.lnGenre.text() if len(listkeys) > 0 and listkeys.isspace() is False: preSplit = self.lnGenre.text().split(",") genreMatcher = re.compile(r'\((\d+)\)') genreList = {} totalValue = 0 for key in preSplit: m = genreMatcher.search(key) if m: genre = str(genreMatcher.sub("", key)).strip() match = int(m.group()[:-1][1:]) else: genre = key.strip() match = 0 if genre in self.acbfGenreList.values(): i = list(self.acbfGenreList.values()).index(genre) genreList[list(self.acbfGenreList.keys())[i]] = match else: genreList[genre] = match totalValue += match # Normalize the values: for key in genreList.keys(): if genreList[key] > 0: genreList[key] = round(genreList[key] / totalValue * 100) config["genre"] = genreList elif "genre" in config.keys(): config.pop("genre") listkeys = self.lnCharacters.text() if len(listkeys) > 0 and listkeys.isspace() is False: config["characters"] = self.lnCharacters.text().split(", ") elif "characters" in config.keys(): config.pop("characters") listkeys = self.lnFormat.text() if len(listkeys) > 0 and listkeys.isspace() is False: config["format"] = self.lnFormat.text().split(", ") elif "format" in config.keys(): config.pop("format") config["ratingSystem"] = self.cmbRatingSystem.currentText() config["rating"] = self.cmbRating.currentText() listkeys = self.lnOtherKeywords.text() if len(listkeys) > 0 and listkeys.isspace() is False: config["otherKeywords"] = self.lnOtherKeywords.text().split(", ") elif "otherKeywords" in config.keys(): config.pop("otherKeywords") text = self.teSummary.toPlainText() if len(text) > 0 and text.isspace() is False: config["summary"] = text elif "summary" in config.keys(): config.pop("summary") if len(self.lnSeriesName.text()) > 0: config["seriesName"] = self.lnSeriesName.text() config["seriesNumber"] = self.spnSeriesNumber.value() if self.spnSeriesVol.value() > 0: config["seriesVolume"] = self.spnSeriesVol.value() config["language"] = str(self.cmbLanguage.codeForCurrentEntry()+"_"+self.cmbCountry.codeForCurrentEntry()) if self.cmbReadingMode.currentIndex() is int(Qt.LeftToRight): config["readingDirection"] = "leftToRight" else: config["readingDirection"] = "rightToLeft" authorList = [] for row in range(self.authorTable.verticalHeader().count()): logicalIndex = self.authorTable.verticalHeader().logicalIndex(row) listEntries = ["nickname", "first-name", "initials", "last-name", "role", "email", "homepage", "language"] author = {} for i in range(len(listEntries)): entry = self.authorModel.data(self.authorModel.index(logicalIndex, i)) if entry is None: entry = " " if entry.isspace() is False and len(entry) > 0: if listEntries[i] == "role": if entry in self.acbfAuthorRolesList.values(): entryI = list(self.acbfAuthorRolesList.values()).index(entry) entry = list(self.acbfAuthorRolesList.keys())[entryI] author[listEntries[i]] = entry elif listEntries[i] in author.keys(): author.pop(listEntries[i]) authorList.append(author) config["authorList"] = authorList config["publisherName"] = self.publisherName.text() config["publisherCity"] = self.publishCity.text() config["publishingDate"] = self.publishDate.date().toString(Qt.ISODate) config["isbn-number"] = self.isbn.text() config["license"] = self.license.currentText() if self.ln_database_name.text().isalnum() and self.ln_database_entry.text().isalnum(): dbRef = {} dbRef["name"] = self.ln_database_name.text() dbRef["entry"] = self.ln_database_entry.text() dbRef["type"] = self.cmb_entry_type.currentText() config["databaseReference"] = dbRef return config
class MyWindow(QMainWindow): def __init__(self): super(MyWindow, self).__init__() self.lz = [] self.ts = 0 self.zahlenListe = [] dir = os.path.dirname(sys.argv[0]) self.settingsfile = "%s/%s" % (dir, "Lotto.conf") self.zahlen = "%s/%s" % (dir, "zahlen.txt") print(self.settingsfile) self.settings = QSettings(self.settingsfile, QSettings.IniFormat) self.setStyleSheet(stylesheet(self)) self.lottolink = 'https://www.dielottozahlende.net/lotto-6-aus-49' self.mysuper = 5 self.model = QStandardItemModel(self) self.model.setRowCount(7) self.tableview = QTableView(self) self.tableview.setSortingEnabled(False) self.tableview.setGridStyle(1) if int(sys.version[0]) > 2: self.tableview.setFixedHeight(149) else: self.tableview.setFixedHeight(171) self.tableview.setSizeAdjustPolicy(QAbstractScrollArea.AdjustIgnored) self.tableview.horizontalHeader().setStretchLastSection(True) self.tableview.verticalHeader().setStretchLastSection(False) self.tableview.setCornerButtonEnabled(True) self.tableview.setShowGrid(True) self.tableview.setModel(self.model) self.tableview.hideRow(6) self.tableview.hideColumn(13) self.pushButtonLoad = QPushButton() self.pushButtonLoad.setText("Zahlen holen") self.pushButtonLoad.setIcon(QIcon.fromTheme("view-refresh")) self.pushButtonLoad.clicked.connect(self.getLotto) self.pushButtonLoad.setFixedWidth(110) self.pushButtonLoad.setFixedHeight(24) self.zahlenAction = QAction(QIcon.fromTheme("edit"), "Editor", self, triggered=self.edit_Tipps, shortcut="F5") self.addAction(self.zahlenAction) self.superAction = QAction(QIcon.fromTheme("edit"), "Superzahl", self, triggered=self.setMySuper, shortcut="F6") self.addAction(self.superAction) self.infoAction = QAction(QIcon.fromTheme("help-info"), "Information", self, triggered=self.showInfo, shortcut="F1") self.addAction(self.infoAction) self.lbl = QLabel() self.hbox = QHBoxLayout() self.hbox.addWidget(self.pushButtonLoad) grid = QVBoxLayout() grid.setSpacing(10) grid.addLayout(self.hbox) grid.addWidget(self.tableview) grid.addWidget(self.lbl) mywidget = QWidget() mywidget.setLayout(grid) self.setCentralWidget(mywidget) self.readSettings() self.tableview.resizeColumnsToContents() self.tableview.resizeRowsToContents() self.setHeaders() print("Wilkommen bei Lotto") self.statusBar().showMessage( "%s %s" % ("Willkommen bei LottoCheck", " *** F5 Lottozahlen ändern *** F6 Superzahl ändern"), 0) def findTableItems(self): model = self.tableview.model() self.tableview.selectionModel().clearSelection() print(self.zahlenListe) for column in range(12): start = model.index(0, column) for zahl in self.zahlenListe: matches = model.match(start, Qt.DisplayRole, str(zahl), 1, Qt.MatchExactly) if matches: index = matches[0] self.tableview.selectionModel().select( index, QItemSelectionModel.Select) def saveNumbers(self): print(self.model.columnCount(), "Columns") textData = "" fileName, _ = QFileDialog.getSaveFileName(self, "Open File", "~/lottozahlen.csv", "CSV Files (*.csv)") if fileName: print("%s %s" % (fileName, "saved")) f = open(fileName, 'w') for col in range(self.model.columnCount()): textData += str(self.model.horizontalHeaderItem(col).text()) textData += "\t" textData += "\n" for row in range(self.model.rowCount() - 1): for col in range(self.model.columnCount()): textData += str(self.model.data(self.model.index(row, col))) textData += "\t" textData += "\n" f.write(textData) f.close() def edit_Tipps(self): self.edWin = Editor() with open(self.zahlen, 'r') as f: text = f.read() self.edWin.tipp_editor.setPlainText(text) f.close() self.edWin.isModified = False def showInfo(self): link = "<p><a title='Axel Schneider' href='http://goodoldsongs.jimdo.com' target='_blank'> \ Axel Schneider</a></p>" message = "<h2>LottoCheck 1.0</h2><h4>6 aus 49</h4>created by " + link + " with PyQt5<br>©September 2019<br>" \ + "<br>Copyright © 2017 The Qt Company Ltd and other contributors." \ + "<br>Qt and the Qt logo are trademarks of The Qt Company Ltd." \ + "<br><br>F5 = Tipps ändern" \ + "<br>F6 = Superzahl ändern" self.msgbox(message) def msgbox(self, message): msg = QMessageBox(1, "Information", message, QMessageBox.Ok) msg.exec_() def getLotto(self): if not self.lz == []: print("values already here", self.lz) self.compare() else: self.lottolink = "https://www.lotto.de/lotto-6aus49/lottozahlen" print("getting Values") source = requests.get(self.lottolink).text soup = bsoup(source, 'lxml') lottoliste = [] for td in soup.find_all(class_='LottoBall__circle'): lottoliste.append(td.text) result = lottoliste self.zahlenListe = result[:6] self.lz = result theSuper = lottoliste[6] print("Gewinnzahlen:\n", self.zahlenListe) self.ts = theSuper print("theSuper", self.ts) for i in range(6): self.model.setData(self.model.index(i, 12), result[i]) self.model.item(i, 12).setTextAlignment(Qt.AlignCenter) self.compare() def getSpiel77(self): self.lottolink = "https://www.lotto.de/lotto-6aus49/lottozahlen" source = requests.get(self.lottolink).text soup = bsoup(source, 'lxml') result = soup.find(class_='WinningNumbersAdditionalGame__text').text print("Spiel 77: ", result) super6 = soup.find(class_='WinningNumbersAdditionalGame__text' ).parent.find_next_siblings()[0].text print("Super 6: ", super6) date = soup.find(class_="WinningNumbers__date").text self.lbl.setText(self.lbl.text() + "\n" + date + "\n\n" + result.replace("Spiel 77", "Spiel 77: ") + "\n" + super6.replace("Super 6", "Super 6: ")) def compare(self): ### compare all tipps print("self.lz: ", self.lz) self.lz = [int(x) for x in self.lz[:6]] print(self.mysuper, self.lz) self.lbl.clear() tipp = [] for x in range(len(self.tliste)): t = [] tipp = [int(x) for x in self.tliste[x]] # print(tipp) for a in self.lz: if int(a) in tipp: print(a, "in tipp", str(x + 1)) t.append(a) rtext = "" print("len(t) ", len(t)) if len(t) == 2 and self.mysuper == self.ts: rtext += self.lbl.text() rtext += '\ngewonnen in Tipp ' rtext += str(int(x) + 1) rtext += " : " rtext += str(t) rtext += " *** " rtext += str(len(t)) rtext += "er ***" rtext += ' + Superzahl' self.lbl.setText(rtext) elif len(t) > 2: if self.mysuper == self.ts: rtext += self.lbl.text() rtext += '\ngewonnen in Tipp ' rtext += str(int(x) + 1) rtext += " : " rtext += str(t) rtext += " *** " rtext += str(len(t)) rtext += "er ***" rtext += ' + Superzahl' self.lbl.setText(rtext) else: rtext += self.lbl.text() rtext += '\ngewonnen in Tipp ' rtext += str(int(x) + 1) rtext += " : " rtext += str(t) rtext += " *** " rtext += str(len(t)) rtext += "er ***" self.lbl.setText(rtext) if self.lbl.text() == "": self.lbl.setText("leider nichts gewonnen ...\n") self.statusBar().showMessage( "%s %s %s %s" % ("Gewinnzahlen: ", (', '.join(str(x) for x in self.lz)), " *** Superzahl: ", str(self.ts)), 0) self.getSpiel77() self.findTableItems() def setHeaders(self): self.tableview.horizontalHeader().setVisible(True) self.tableview.verticalHeader().setVisible(False) for x in range(self.model.columnCount() - 1): self.model.setHeaderData(x, Qt.Horizontal, "%s %s" % ("Tipp", (x + 1))) self.model.setHeaderData(self.model.columnCount() - 1, Qt.Horizontal, "Gewinnzahlen") self.tableview.setAlternatingRowColors(True) self.tableview.resizeColumnsToContents() self.tableview.resizeRowsToContents() def closeEvent(self, event): print("Goodbye ...") self.writeSettings() def setMySuper(self): s = int(self.mysuper) dlg = QInputDialog() ms, ok = dlg.getInt(self, 'Superzahl:', "", s, 0, 9, 1, Qt.Dialog) if ok: self.mysuper = ms print("Superzahl =", self.mysuper) def readSettings(self): if self.settings.contains("mysuper"): self.mysuper = self.settings.value("mysuper") else: self.setMySuper() print("Superzahl:", self.mysuper) self.tliste = [] with open(self.zahlen, 'r') as f: text = f.read() f.close() for line in text.splitlines(): self.tliste.append(line.split(",")) self.model.setColumnCount(len(self.tliste) + 1) for x in range(0, len(self.tliste)): tipp = self.tliste[x] for i in range(len(tipp)): self.model.setData(self.model.index(i, x), tipp[i]) self.model.item(i, x).setTextAlignment(Qt.AlignCenter) def writeSettings(self): self.settings.setValue("mysuper", self.mysuper)
class Emotion(QWidget): EMOTION_DIR = "./resource/expression" WIDTH = 460 HEIGHT = 300 selectChanged = pyqtSignal(str) def __init__(self, parent=None): # super(Emotion, self).__init__(parent) super(Emotion, self).setWindowFlags(QtCore.Qt.Popup) self.resize(QSize(Emotion.WIDTH, Emotion.HEIGHT)) self.setWindowTitle("表情選擇") #self.setModal(True) #self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint) self.emotion_initial() def emotion_initial(self): self.emotion_table = QTableView() self.emotion_table.horizontalHeader().setVisible(False) self.emotion_table.verticalHeader().setVisible(False) self.emotion_table.setMouseTracking(True) self.emotion_table.setEditTriggers(QAbstractItemView.NoEditTriggers) self.emotion_table.verticalHeader().setDefaultSectionSize(30) self.emotion_table.horizontalHeader().setDefaultSectionSize(30) self.emotion_table.setIconSize(QSize(30, 30)) self.emotion_table.entered.connect(self.showEmotionTips) self.emotion_model = QStandardItemModel() emotions = os.listdir(Emotion.EMOTION_DIR) emotions_size = len(emotions) #TODO FIX SORTED MISSING CMP BUG #emotions = sorted(emotions,key=lambda e:(int(e.split(".")))) #emotions = sorted(emotions,key=functools.cmp_to_key(emotionCmp)) i = 0 while i < emotions_size: self.add_emotion(emotions[i:i + 14]) i = i + 14 self.emotion_table.setModel(self.emotion_model) self.emotion_table.clicked.connect(self.emotion_click) # mainLayout = QVBoxLayout() mainLayout.addWidget(self.emotion_table) self.setLayout(mainLayout) #self. def showEmotionTips(self, index): if index.isValid(): QToolTip.showText(QCursor.pos(), "Hello", None) def add_emotion(self, emotions): ''' :param emotions a list of emotion will be adding to emotion table ''' cells = [] for emotion in emotions: item = QtGui.QStandardItem( QIcon(os.path.join(Emotion.EMOTION_DIR, emotion)), emotion) cells.append(item) self.emotion_model.appendRow(cells) #def eventFilter(self, event): #p = QCursor.pos() - self.pos() #item = self.emotion_table def emotion_click(self): row = self.emotion_table.currentIndex().row() column = self.emotion_table.currentIndex().column() #self.accept() self.close() self.selectChanged.emit(self.get_selected_emotion(row, column)) def get_selected_emotion(self, row, column): emotion = self.emotion_model.data(self.emotion_model.index( row, column)) if emotion: return (emotion) else: return "N/A"
class DataBlock(QWidget): def __init__(self, parent=None): super(DataBlock, self).__init__(parent) self.parent = parent loadButton = QPushButton("&Load new data") #self.connect(loadButton, SIGNAL('clicked()'), self.on_load) #pyqt4 loadButton.clicked.connect(self.on_load) dataListView = QListView() self.dataListModel = QStandardItemModel() dataListView.setModel(self.dataListModel) plotButton = QPushButton("&Plot data") #self.connect(plotButton, SIGNAL('clicked()'), self.on_plot) plotButton.clicked.connect(self.on_plot) layout = QVBoxLayout(self) layout.addWidget(loadButton) layout.addWidget(dataListView) layout.addWidget(plotButton) layout.addStretch(1) def update_data(self): self.parent.data = [] for row in range(self.dataListModel.rowCount()): i = self.dataListModel.index(row, 0) checked = (self.dataListModel.data( i, Qt.CheckStateRole) == Qt.Checked) if checked: self.parent.data.append(self.allsets[row]) def on_plot(self): self.update_data() #self.parent.plotblk.on_show() # plots.plot(self.parent.data, axes=self.parent.canvas.axes, # legend=self.parent.plotblk.legendChB.isChecked()) plots.cvfit_plot(self.parent.data, fig=self.parent.figure, logX=self.parent.plotblk.logXChB.isChecked(), logY=self.parent.plotblk.logYChB.isChecked(), legend=self.parent.plotblk.legendChB.isChecked()) self.parent.canvas.draw() def on_load(self): try: self.__on_load() except: pass def __on_load(self): filename, filt = QFileDialog.getOpenFileName( self.parent, "Open a data file...", self.parent.workdir, "Excel files (*.xls *.xlsx);;CSV files (*.csv);;TXT files (*.txt);;All files (*.*)" ) self.parent.log.write('\nLoading file: ' + filename) type = filename.split('.')[-1] self.parent.workdir = os.path.dirname(filename) xlssheet = None if type == 'xls' or type == 'xlsx': book = xlrd.open_workbook(filename) sheets = book.sheet_names() dialog = dialogs.ExcelSheetDlg(sheets, self) if dialog.exec_(): xlssheet = dialog.returnSheet() dialog = dialogs.LoadDataDlg(filename, sheet=xlssheet) if dialog.exec_(): self.allsets = dialog.return_data() #self.allsets = cfio.read_sets_from_csv(filename, col=2) self.parent.log.write("Loaded: " + os.path.split(str(filename))[1]) self.dataListModel.clear() for set in self.allsets: item = QStandardItem(set.title) item.setCheckState(Qt.Checked) item.setCheckable(True) self.dataListModel.appendRow(item) self.parent.log.write('\n' + set.title) self.parent.log.write(str(set)) self.parent.data = self.allsets self.parent.fname = filename
class ListCategory(QSortFilterProxyModel): """Expose a list of items as a category for the CompletionModel.""" def __init__(self, name: str, items: Iterable[Tuple[str, ...]], sort: bool = True, delete_func: util.DeleteFuncType = None, parent: QWidget = None): super().__init__(parent) self.name = name self.srcmodel = QStandardItemModel(parent=self) self._pattern = '' # ListCategory filters all columns self.columns_to_filter = [0, 1, 2] self.setFilterKeyColumn(-1) for item in items: self.srcmodel.appendRow([QStandardItem(x) for x in item]) self.setSourceModel(self.srcmodel) self.delete_func = delete_func self._sort = sort def set_pattern(self, val): """Setter for pattern. Args: val: The value to set. """ if len(val) > 5000: # avoid crash on huge search terms (#5973) log.completion.warning(f"Trimming {len(val)}-char pattern to 5000") val = val[:5000] self._pattern = val val = re.sub(r' +', r' ', val) # See #1919 val = re.escape(val) val = val.replace(r'\ ', '.*') rx = QRegularExpression(val, QRegularExpression.CaseInsensitiveOption) qtutils.ensure_valid(rx) self.setFilterRegularExpression(rx) self.invalidate() sortcol = 0 self.sort(sortcol) def lessThan(self, lindex, rindex): """Custom sorting implementation. Prefers all items which start with self._pattern. Other than that, uses normal Python string sorting. Args: lindex: The QModelIndex of the left item (*left* < right) rindex: The QModelIndex of the right item (left < *right*) Return: True if left < right, else False """ qtutils.ensure_valid(lindex) qtutils.ensure_valid(rindex) left = self.srcmodel.data(lindex) right = self.srcmodel.data(rindex) if left is None or right is None: # pragma: no cover log.completion.warning("Got unexpected None value, " "left={!r} right={!r} " "lindex={!r} rindex={!r}".format( left, right, lindex, rindex)) return False leftstart = left.startswith(self._pattern) rightstart = right.startswith(self._pattern) if leftstart and not rightstart: return True elif rightstart and not leftstart: return False elif self._sort: return left < right else: return False
class MainWindow(QtWidgets.QMainWindow): connection_status = 0 file_path = '' ftp = ftplib.FTP('') def __init__(self): super(MainWindow, self).__init__() self.ui = uic.loadUi('ftp.ui', self) self.model = QFileSystemModel() self.setFixedSize(810, 563) self.init_ui() self.bind_event() self.set_validation_type() def init_ui(self): self.populate_local_tree_view() self.show() def populate_local_tree_view(self): self.fsm = QFileSystemModel(self) self.fsm.setRootPath('') self.fsm.setReadOnly(True) # self.fsm.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot) self.ui.local_tree_view.setModel(self.fsm) self.local_tree_view.setRootIndex(self.fsm.index(self.fsm.rootPath())) self.ui.local_tree_view.setAnimated(False) self.ui.local_tree_view.setIndentation(20) self.ui.local_tree_view.setSortingEnabled(True) def bind_event(self): self.ui.btn_login.clicked.connect(self.on_login_click) self.ui.local_tree_view.clicked.connect(self.on_tree_view_clicked) # self.ui.tableView.doubleClicked.connect(self.on_click) def on_tree_view_clicked(self, index): index_item = self.fsm.index(index.row(), 0, index.parent()) path = str(self.fsm.filePath(index_item)) self.selected_file_path = path def set_validation_type(self): only_int = QIntValidator() self.ui.input_port_number.setValidator( only_int) # allowing only integer input self.ui.input_password.setEchoMode(QLineEdit.Password) @pyqtSlot() def on_login_click(self): if self.connection_status == 0: host_name = self.ui.input_host_name.text().strip() usr = self.ui.input_username.text().strip() password = self.ui.input_password.text().strip() port_number = self.ui.input_port_number.text().strip() if is_not_blank(host_name) and is_not_blank(usr) and is_not_blank( password) and is_not_blank(port_number): port_number = int(port_number) try: self.ftp.connect(host_name, port_number) self.ftp.login(usr, password) self.add_item_to_log_list_widget("login successful") self.ui.btn_login.setText("Disconnect") self.connection_status = 1 self.get_remote_file() except ftplib.all_errors: self.add_item_to_log_list_widget( "logged in incorrect credentials") show_error_dialog("Please check your credentials") else: show_error_dialog("Please enter all credentials") else: self.logout() def get_remote_file(self): self.model = QStandardItemModel() self.model.setColumnCount(3) self.model.setHorizontalHeaderLabels(['Name', 'Size', 'Last Modified']) files = self.ftp.nlst() i = 0 for filename in files: self.add_row_item(i, 0, self.model, filename) try: self.ftp.voidcmd('TYPE I') size = str(self.ftp.size(filename)) size = size + " KB" self.add_row_item(i, 1, self.model, size) modified_time = self.ftp.sendcmd('MDTM ' + filename) formatted_time = datetime.strptime( modified_time[4:], "%Y%m%d%H%M%S").strftime("%d %B %Y %H:%M:%S") self.add_row_item(i, 2, self.model, formatted_time) self.add_item_to_log_list_widget( "remote directory listing successful") except: item = QStandardItem(filename) item.setTextAlignment(Qt.AlignVCenter) item.setEditable(False) self.model.setItem(i, 0, item) item.setIcon(QIcon("static/ic_folder.png")) i += 1 self.ui.tableView.setModel(self.model) self.ui.tableView.setSelectionBehavior(QTableView.SelectRows) self.set_property() @staticmethod def add_row_item(row, column, model, text): item = QStandardItem(text) item.setTextAlignment(Qt.AlignVCenter) item.setEditable(False) model.setItem(row, column, item) def set_property(self): self.ui.tableView.setColumnWidth(0, 120) self.ui.tableView.setColumnWidth(1, 120) self.ui.tableView.setColumnWidth(2, 160) self.ui.tableView.setShowGrid(False) header = self.ui.tableView.horizontalHeader() header.setDefaultAlignment(Qt.AlignVCenter) def contextMenuEvent(self, event): self.menu = QMenu(self) rename_action = QAction('Rename', self) rename_action.triggered.connect(lambda: self.rename_slot(event)) self.menu.addAction(rename_action) delete_action = QAction('Delete', self) delete_action.triggered.connect(lambda: self.delete_slot(event)) self.menu.addAction(delete_action) download_action = QAction('Download', self) download_action.triggered.connect(lambda: self.download_slot(event)) self.menu.addAction(download_action) create_dir_action = QAction('New Dir', self) create_dir_action.triggered.connect( lambda: self.create_dir_slot(event)) self.menu.addAction(create_dir_action) upload_file_action = QAction('Upload', self) upload_file_action.triggered.connect( lambda: self.upload_file_slot(event)) self.menu.addAction(upload_file_action) self.menu.popup(QCursor.pos()) @pyqtSlot() def rename_slot(self, event): # indexes = self.ui.tableView.selectionModel().selectedRows() # for index in sorted(indexes): # print('Row %d is selected' % index.row()) for index in sorted(self.ui.tableView.selectionModel().selectedRows()): selected_filename = self.model.data( self.model.index(index.row(), 0)) new_name = show_input_dialog(self, "Rename", "New Name") if new_name != '' and len(new_name) != 0: try: self.ftp.rename(selected_filename, new_name) self.get_remote_file() self.add_item_to_log_list_widget(selected_filename + " change with " + new_name + " successfully on remote") except: show_error_dialog("Unexpected error occurred") else: show_error_dialog("Invalid file name") break # just rename one file @pyqtSlot() def delete_slot(self, event): for index in sorted(self.ui.tableView.selectionModel().selectedRows()): try: selected_filename = self.model.data( self.model.index(index.row(), 0)) self.ftp.rmd(selected_filename) self.add_item_to_log_list_widget(selected_filename + " deleted on remote") except: print("") @pyqtSlot() def download_slot(self, event): for index in sorted(self.ui.tableView.selectionModel().selectedRows()): selected_filename = str( self.model.data(self.model.index(index.row(), 0))) try: local_file = open(selected_filename, 'wb') self.ftp.retrbinary('RETR ' + selected_filename, local_file.write, 1024) local_file.close() self.add_item_to_log_list_widget( selected_filename + "file is downloaded successfully") except Exception as e: print(e) # show_error_dialog("Unexpected error occurred") @pyqtSlot() def create_dir_slot(self, event): self.add_item_to_log_list_widget( "test file is downloaded successfully") file_name = show_input_dialog(self, "New Directory", "Directory Name") try: if file_name != '' and len(file_name) != 0: self.ftp.mkd(file_name) self.get_remote_file() self.add_item_to_log_list_widget(file_name + " created successfully") else: show_error_dialog("Invalid directory name") except: print("") @pyqtSlot() def upload_file_slot(self, event): try: if os.path.isfile(self.file_path): file_name = os.path.basename(self.file_path) self.ftp.storbinary('STOR ' + file_name, open(self.file_path, 'rb')) self.get_remote_file() self.add_item_to_log_list_widget(file_name + " uploaded successfully") except: show_error_dialog("Please choose only one file") @pyqtSlot() def on_delete_file_click(self): files = list(self.ftp.nlst()) for f in files: self.ftp.delete(f) def get_size(self, directory): size = 0 for file in self.ftp.nlst(directory): size += self.ftp.size(file) return size def add_item_to_log_list_widget(self, log): self.ui.log_list_widget.addItem("status\t" + log) def logout(self): self.ftp.close() self.add_item_to_log_list_widget("logout successfully") self.connection_status = 0 self.ui.btn_login.setText("Connect")
class MessageList(MyTreeView): class Columns(IntEnum): HEIGHT = 0 FROM = 1 DATA = 2 filter_columns = [Columns.HEIGHT, Columns.FROM, Columns.DATA] ROLE_SORT_ORDER = Qt.UserRole + 1000 ROLE_ASSET_STR = Qt.UserRole + 1001 def __init__(self, parent): super().__init__(parent, self.create_menu, stretch_column=self.Columns.DATA, editable_columns=[]) self.wallet = self.parent.wallet self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSortingEnabled(True) self.std_model = QStandardItemModel(self) self.proxy = MySortModel(self, sort_role=self.ROLE_SORT_ORDER) self.proxy.setSourceModel(self.std_model) self.setModel(self.proxy) self.update() self.sortByColumn(self.Columns.HEIGHT, Qt.AscendingOrder) self.messages = {} def webopen_safe(self, url): show_warn = self.parent.config.get('show_ipfs_warning', True) if show_warn: cb = QCheckBox(_("Don't show this message again.")) cb_checked = False def on_cb(x): nonlocal cb_checked cb_checked = x == Qt.Checked cb.stateChanged.connect(on_cb) goto = self.parent.question(_( 'You are about to visit:\n\n' '{}\n\n' 'IPFS hashes can link to anything. Please follow ' 'safe practices and common sense. If you are unsure ' 'about what\'s on the other end of an IPFS, don\'t ' 'visit it!\n\n' 'Are you sure you want to continue?').format(url), title=_('Warning: External Data'), checkbox=cb) if cb_checked: self.parent.config.set_key('show_ipfs_warning', False) if goto: webopen(url) else: webopen(url) def mouseDoubleClickEvent(self, event: QMouseEvent): idx = self.indexAt(event.pos()) if not idx.isValid(): return # Get the IPFS from 3rd column hm_idx = self.model().mapToSource(self.model().index(idx.row(), 2)) data = self.std_model.data(hm_idx) if data[:2] == 'Qm': # If it starts with Qm, it's an IPFS url = ipfs_explorer_URL(self.parent.config, 'ipfs', data) self.webopen_safe(url) def refresh_headers(self): headers = { self.Columns.HEIGHT: _('Height'), self.Columns.FROM: _('From'), self.Columns.DATA: _('Data'), } self.update_headers(headers) @profiler def update(self): if self.maybe_defer_update(): return self.messages = self.wallet.get_messages() self.proxy.setDynamicSortFilter( False) # temp. disable re-sorting after every change self.std_model.clear() self.refresh_headers() set_message = None for height, data in self.messages.items(): for d in data: is_from = d[0] message_txt = d[1] if not self.parent.config.get('get_dev_notifications', True) and d[2] is None: # The source will only be null if this is a dev notification continue # create item labels = [height, is_from, message_txt] asset_item = [QStandardItem(e) for e in labels] # align text and set fonts for i, item in enumerate(asset_item): item.setTextAlignment(Qt.AlignVCenter) # add item count = self.std_model.rowCount() self.std_model.insertRow(count, asset_item) self.set_current_idx(set_message) self.filter() self.proxy.setDynamicSortFilter(True) def add_copy_menu(self, menu, idx): cc = menu.addMenu(_("Copy")) for column in self.Columns: if self.isColumnHidden(column): continue column_title = self.model().headerData(column, Qt.Horizontal) hm_idx = self.model().mapToSource(self.model().index( idx.row(), column)) column_data = self.std_model.data(hm_idx) cc.addAction(column_title, lambda text=column_data, title=column_title: self. place_text_on_clipboard(text, title=title)) return cc def create_menu(self, position): org_idx: QModelIndex = self.indexAt(position) menu = QMenu() self.add_copy_menu(menu, org_idx) menu.exec_(self.viewport().mapToGlobal(position)) def place_text_on_clipboard(self, text: str, *, title: str = None) -> None: if is_address(text): try: self.wallet.check_address_for_corruption(text) except InternalAddressCorruption as e: self.parent.show_error(str(e)) raise super().place_text_on_clipboard(text, title=title) def get_edit_key_from_coordinate(self, row, col): return None # We don't edit anything here def on_edited(self, idx, edit_key, *, text): pass
class ListCategory(QSortFilterProxyModel): """Expose a list of items as a category for the CompletionModel.""" def __init__(self, name, items, sort=True, delete_func=None, parent=None): super().__init__(parent) self.name = name self.srcmodel = QStandardItemModel(parent=self) self._pattern = '' # ListCategory filters all columns self.columns_to_filter = [0, 1, 2] self.setFilterKeyColumn(-1) for item in items: self.srcmodel.appendRow([QStandardItem(x) for x in item]) self.setSourceModel(self.srcmodel) self.delete_func = delete_func self._sort = sort def set_pattern(self, val): """Setter for pattern. Args: val: The value to set. """ self._pattern = val val = re.sub(r' +', r' ', val) # See #1919 val = re.escape(val) val = val.replace(r'\ ', '.*') rx = QRegExp(val, Qt.CaseInsensitive) self.setFilterRegExp(rx) self.invalidate() sortcol = 0 self.sort(sortcol) def lessThan(self, lindex, rindex): """Custom sorting implementation. Prefers all items which start with self._pattern. Other than that, uses normal Python string sorting. Args: lindex: The QModelIndex of the left item (*left* < right) rindex: The QModelIndex of the right item (left < *right*) Return: True if left < right, else False """ qtutils.ensure_valid(lindex) qtutils.ensure_valid(rindex) left = self.srcmodel.data(lindex) right = self.srcmodel.data(rindex) leftstart = left.startswith(self._pattern) rightstart = right.startswith(self._pattern) if leftstart and not rightstart: return True elif rightstart and not leftstart: return False elif self._sort: return left < right else: return False
class MassTabSelectorGUI(QDockWidget): """ classdocs """ masstabViewRaisedSignal = pyqtSignal(object) """ constructor """ def __init__(self, parent=None): super(MassTabSelectorGUI, self).__init__(parent) self.ui = Ui_DockWidget_MassTabSelector() self.ui.setupUi(self) def setup(self, analysis): self.ana = analysis self.__connect_events() def __connect_events(self): self.model = QStandardItemModel() self.mass_list = [] for i in range(10): mass = 184 + i self.mass_list.append(str(mass)) for i in range(10): mass = 209 + i self.mass_list.append(str(mass)) for i in range(10): mass = 273.3 + i self.mass_list.append(str(mass)) for i in range(10): mass = 294 + i self.mass_list.append(str(mass)) for mass in self.mass_list: item = QStandardItem(mass) item.setCheckable(True) item.setEditable(True) self.model.appendRow(item) self.view = self.ui.listView_Mass self.view.setModel(self.model) # changes in one item, don't know which one self.model.itemChanged.connect(self.change_list) # changes in button self.ui.pushButton_ChangeList.clicked.connect(self.emit_list_signal) # get peaks found and update automatically the mass list self.ana.masstabSelectorRaisedSignal.connect(self.update_list_view) def update_list_view(self, xind): self.mass_list = [] for i in range(len(xind)): m = "{:.1f}".format(float(xind[i])) self.mass_list.append(str(m)) self.model.clear() self.model = QStandardItemModel() for mass in self.mass_list: item = QStandardItem(mass) item.setCheckable(True) item.setEditable(True) item.setCheckState(Qt.Checked) self.model.appendRow(item) self.view = self.ui.listView_Mass self.view.setModel(self.model) # changes in one item, don't know which one self.model.itemChanged.connect(self.change_list) def change_list(self): log.debug("event from %s", self.sender()) self.oneIsChecked = False self.mass_list = [] count = self.model.rowCount() for i in range(count): checked = self.model.item(i).checkState() if checked: mass_name = self.model.data(self.model.index(i, 0)) self.mass_list.append(mass_name) self.oneIsChecked = True def emit_list_signal(self): log.debug("event from %s", self.sender()) self.change_list() if self.oneIsChecked: self.masstabViewRaisedSignal.emit(self.mass_list)
def data(self, index, role=None): if role == Qt.TextAlignmentRole: return Qt.AlignCenter return QStandardItemModel.data(self, index, role)
class ListCategory(QSortFilterProxyModel): """Expose a list of items as a category for the CompletionModel.""" def __init__(self, name, items, sort=True, delete_func=None, parent=None): super().__init__(parent) self.name = name self.srcmodel = QStandardItemModel(parent=self) self._pattern = '' # ListCategory filters all columns self.columns_to_filter = [0, 1, 2] self.setFilterKeyColumn(-1) for item in items: self.srcmodel.appendRow([QStandardItem(x) for x in item]) self.setSourceModel(self.srcmodel) self.delete_func = delete_func self._sort = sort def set_pattern(self, val): """Setter for pattern. Args: val: The value to set. """ self._pattern = val val = re.sub(r' +', r' ', val) # See #1919 val = re.escape(val) val = val.replace(r'\ ', '.*') rx = QRegExp(val, Qt.CaseInsensitive) self.setFilterRegExp(rx) self.invalidate() sortcol = 0 self.sort(sortcol) def lessThan(self, lindex, rindex): """Custom sorting implementation. Prefers all items which start with self._pattern. Other than that, uses normal Python string sorting. Args: lindex: The QModelIndex of the left item (*left* < right) rindex: The QModelIndex of the right item (left < *right*) Return: True if left < right, else False """ qtutils.ensure_valid(lindex) qtutils.ensure_valid(rindex) left = self.srcmodel.data(lindex) right = self.srcmodel.data(rindex) if left is None or right is None: # pragma: no cover log.completion.warning("Got unexpected None value, " "left={!r} right={!r} " "lindex={!r} rindex={!r}" .format(left, right, lindex, rindex)) return False leftstart = left.startswith(self._pattern) rightstart = right.startswith(self._pattern) if leftstart and not rightstart: return True elif rightstart and not leftstart: return False elif self._sort: return left < right else: return False
class MyPartitionWidget(QWidget): unassignedStr="NaN / Outside Range" def __init__(self,model,setFunc,comboInit=None,checkable=True,colors=False,parent=None): QWidget.__init__(self,parent) self.ui=Ui_PartitionWidget() self.ui.setupUi(self) self.model = model self.checkable = checkable self.colors = colors self.setFunction = setFunc initial=None search=model.datafield(comboInit) i=0 for col in sorted(set(self.model.cols) - self.model.cfg.internalCols,key=self.model.prettyname): if (self.model.fieldmax(col) - self.model.trueFieldMin(col)) > 0: self.ui.comboBox.addItem(self.model.prettyname(col),col) if col == search: initial=i i += 1 self.parts=None self.ui.groupBox.setChecked(initial is not None) self.listModel=QStandardItemModel() self.ui.listView.setModel(self.listModel) if self.colors: self.ui.listView.doubleClicked.connect(self.selectColor) if initial is not None: self.ui.comboBox.setCurrentIndex(initial) self.ui.comboBox.currentIndexChanged.connect(self.onComboChange) self.onComboChange() self.ui.groupBox.toggled.connect(self.rebuild) self.ui.binSpinBox.editingFinished.connect(self.rebuild) self.ui.maxSpinBox.editingFinished.connect(self.rebuild) self.ui.minSpinBox.editingFinished.connect(self.rebuild) self.listModel.dataChanged.connect(self.update) def onComboChange(self): field=self.ui.comboBox.itemData(self.ui.comboBox.currentIndex()) self.ui.minSpinBox.setMinimum(self.model.fieldmin(field)) self.ui.maxSpinBox.setMinimum(self.model.fieldmin(field)) self.ui.minSpinBox.setMaximum(self.model.fieldmax(field)) self.ui.maxSpinBox.setMaximum(self.model.fieldmax(field)) self.ui.minSpinBox.setValue(self.model.fieldmin(field)) self.ui.maxSpinBox.setValue(self.model.fieldmax(field)) self.rebuild() def rebuild(self): self.listModel.clear() if not self.ui.groupBox.isChecked() and self.ui.groupBox.isCheckable(): self.parts=None self.listModel.clear() self.update() return field=self.ui.comboBox.itemData(self.ui.comboBox.currentIndex()) minimum=None if self.ui.minCheckBox.isChecked(): minimum=self.ui.minSpinBox.value() maximum=None if self.ui.maxCheckBox.isChecked(): maximum=self.ui.maxSpinBox.value() bins=None if self.ui.binCheckBox.isChecked(): bins=self.ui.binSpinBox.value() self.parts=self.model.partition(field,minimum,maximum,bins) lst=list(sorted(self.parts.keys())) keep=np.zeros(self.model.data.shape,dtype=bool) for i,s in enumerate(lst): c=self.model.cfg.color(field,s,i) item=QStandardItem(s) if self.colors: item.setData(QColor(c),Qt.DecorationRole) if self.checkable: item.setCheckable(True) item.setCheckState(Qt.Checked) item.setData(s) self.listModel.appendRow(item) keep |= self.parts[s] keep = np.logical_not(keep) if np.count_nonzero(keep): self.parts[MyPartitionWidget.unassignedStr] = keep item=QStandardItem(MyPartitionWidget.unassignedStr) if self.colors: item.setData(QColor("white"),Qt.DecorationRole) if self.checkable: item.setCheckable(True) item.setCheckState(Qt.Unchecked) item.setData("NaN / Outside Range") self.listModel.appendRow(item) self.update() def selectColor(self,index): c=QColorDialog.getColor(self.listModel.data(index,Qt.DecorationRole),self) if c.isValid(): self.listModel.setData(index,c,Qt.DecorationRole) def update(self): if self.setFunction is not None: if self.colors: self.setFunction(self.currentStacks()) else: self.setFunction(self.currentKeep()) def current(self): parts=None if self.parts is not None: parts={} for i in range(self.listModel.rowCount()): it=self.listModel.item(i) if not self.checkable or it.checkState() == Qt.Checked: parts[it.text()]=self.parts[it.data()] return parts def currentStacks(self): stack=None if self.parts is not None: stack=[[],[],[]] for i in range(self.listModel.rowCount()): it=self.listModel.item(i) if not self.checkable or it.checkState() == Qt.Checked: if self.colors: stack[0].append(self.parts[it.data()]) stack[1].append(it.data(Qt.DecorationRole).name()) stack[2].append(it.text()) return stack def currentKeep(self): keep=np.ones(self.model.data.shape,dtype=bool) if self.parts is not None: keep=np.zeros(self.model.data.shape,dtype=bool) for i in range(self.listModel.rowCount()): it=self.listModel.item(i) if not self.checkable or it.checkState() == Qt.Checked: keep |= self.parts[it.data()] return keep
class MyControlPanel(QWidget): flagselected = pyqtSignal(int) def __init__(self, model, parent=None): QWidget.__init__(self, parent) self.ui = Ui_ControlPanel() self.ui.setupUi(self) self.model = model sortmenu = QMenu() lIinitial = None searchLegend = model.datafield(model.cfg.legendInitial) for i, col in enumerate( sorted(set(self.model.cols) - self.model.cfg.internalCols, key=self.model.prettyname)): # Sort Menu a = sortmenu.addAction(self.model.prettyname(col)) a.setData(col) a.triggered.connect(self.onAddSortField) # Combo boxes self.ui.partComboBox.addItem(self.model.prettyname(col), col) self.ui.legendComboBox.addItem(self.model.prettyname(col), col) if col == searchLegend: lIinitial = i # Sort self.ui.addSortField.setMenu(sortmenu) self.ui.sortByListWidget.itemSelectionChanged.connect( self.onSelectionChange) self.onSelectionChange() self.ui.removeSortField.clicked.connect(self.onRemoveSortField) self.ui.moveSortField.clicked.connect(self.onMoveSortFieldUp) self.ui.sortAscendingCheckBox.clicked.connect( self.onSortAscendingChange) # Legend self.legend = None self.ui.legendGroupBox.setChecked(lIinitial is not None) self.legendModel = QStandardItemModel() self.ui.legendListView.setModel(self.legendModel) self.ui.legendListView.doubleClicked.connect(self.selectLegendColor) if lIinitial is not None: self.ui.legendComboBox.setCurrentIndex(lIinitial) self.ui.legendComboBox.currentIndexChanged.connect( self.onLegendComboChange) self.onLegendComboChange() self.ui.legendGroupBox.toggled.connect(self.calcLegend) self.ui.legendBinSpinBox.editingFinished.connect(self.calcLegend) self.ui.legendMaxSpinBox.editingFinished.connect(self.calcLegend) self.ui.legendMinSpinBox.editingFinished.connect(self.calcLegend) self.legendModel.dataChanged.connect(self.updateModelLegend) # Partition self.partitions = None self.partitionModel = QStringListModel() self.ui.partitionList.setModel(self.partitionModel) self.ui.partitionList.selectionModel().currentChanged.connect( self.onSelectPartition) self.ui.partComboBox.currentIndexChanged.connect( self.onPartitionComboChange) self.onPartitionComboChange() self.ui.partitionBox.toggled.connect(self.calcPartitions) self.ui.partBinSpinBox.valueChanged.connect(self.calcPartitions) self.ui.partMaxSpinBox.valueChanged.connect(self.calcPartitions) self.ui.partMinSpinBox.valueChanged.connect(self.calcPartitions) # Flags if self.model.flagFilter.isActive(): self.ui.flagIgnore.setChecked(True) if self.model.flagFilter.invert: self.ui.flagExclude.setChecked(True) else: self.ui.flagKeep.setChecked(True) else: self.ui.flagIgnore.setChecked(True) self.model.flagFilter.filterchange.connect(self.onFlagChange) self.flagModel = QStringListModel() self.ui.flaggedList.setModel(self.flagModel) self.ui.flagIgnore.clicked.connect(self.onIgnoreFlags) self.ui.flagExclude.clicked.connect(self.onExcludeFlags) self.ui.flagKeep.clicked.connect(self.onOnlyFlags) self.ui.flaggedList.selectionModel().currentChanged.connect( self.onSelectFlag) # Limits self.ui.limitCheckBox.clicked.connect(self.onLimitChange) self.ui.limTopButton.clicked.connect(self.onLimitChange) self.ui.limRandomButton.clicked.connect(self.onLimitChange) self.ui.limitSpinBox.editingFinished.connect(self.onLimitChange) self.onLimitChange() # Filters self.filtermodel = FilterModel(model.topfilter, model) self.ui.filterTreeView.setEditTriggers( QAbstractItemView.AllEditTriggers) self.ui.filterTreeView.setModel(self.filtermodel) if qt5: self.ui.filterTreeView.header().setSectionResizeMode( 0, QHeaderView.ResizeToContents) self.ui.filterTreeView.header().setSectionResizeMode( 1, QHeaderView.ResizeToContents) else: self.ui.filterTreeView.header().setResizeMode( 0, QHeaderView.ResizeToContents) self.ui.filterTreeView.header().setResizeMode( 1, QHeaderView.ResizeToContents) self.ui.filterTreeView.setItemDelegate( filterEditDelegate.FilterItemDelegate()) self.filtermodel.rowsInserted.connect(self.ui.filterTreeView.expandAll) self.ui.filterTreeView.expandAll() # Filters Save/Load self.ui.saveFiltersButton.clicked.connect(self.onSaveFilters) self.ui.loadFiltersButton.clicked.connect(self.onLoadFilters) # Sorting def onAddSortField(self): field = self.sender().data() self.ui.sortByListWidget.addItem(self.model.prettyname(field)) self.model.sortlst.append(field) self.model.onSortChange() def onSelectionChange(self): hasSelection = bool(len(self.ui.sortByListWidget.selectedItems())) self.ui.removeSortField.setEnabled(hasSelection) canMove = hasSelection for item in self.ui.sortByListWidget.selectedItems(): canMove &= self.ui.sortByListWidget.row(item) != 0 self.ui.moveSortField.setEnabled(canMove) def onRemoveSortField(self): rows = [] for item in self.ui.sortByListWidget.selectedItems(): rows.append(self.ui.sortByListWidget.row(item)) for r in sorted(rows, reverse=True): del self.model.sortlst[r] self.ui.sortByListWidget.takeItem(r) self.model.onSortChange() self.onSelectionChange() def onMoveSortFieldUp(self): rows = [] for item in self.ui.sortByListWidget.selectedItems(): rows.append(self.ui.sortByListWidget.row(item)) for r in sorted(rows): self.model.sortlst.insert(r - 1, self.model.sortlst.pop(r)) self.ui.sortByListWidget.insertItem( r - 1, self.ui.sortByListWidget.takeItem(r)) self.ui.sortByListWidget.setCurrentItem( self.ui.sortByListWidget.item(r - 1)) self.model.onSortChange() def setSort(self, lst): for field in lst: self.ui.sortByListWidget.addItem(self.model.prettyname(field)) self.model.sortlst.append(field) self.model.onSortChange() def onSortAscendingChange(self): self.model.reverseSort = not self.ui.sortAscendingCheckBox.isChecked() self.model.onSortChange() # Legends def onLegendComboChange(self): field = self.ui.legendComboBox.itemData( self.ui.legendComboBox.currentIndex()) enable = not self.model.isCategorical(field) if self.ui.legendGroupBox.isChecked() or not enable: self.ui.legendBinSpinBox.setEnabled(enable) self.ui.legendMinSpinBox.setEnabled(enable) self.ui.legendMaxSpinBox.setEnabled(enable) self.ui.legendMinSpinBox.setMinimum(self.model.fieldmin(field)) self.ui.legendMaxSpinBox.setMinimum(self.model.fieldmin(field)) self.ui.legendMinSpinBox.setMaximum(self.model.fieldmax(field)) self.ui.legendMaxSpinBox.setMaximum(self.model.fieldmax(field)) self.ui.legendMinSpinBox.setValue(self.model.fieldmin(field)) self.ui.legendMaxSpinBox.setValue(self.model.fieldmax(field)) self.calcLegend() def calcLegend(self): self.legendModel.clear() if not self.ui.legendGroupBox.isChecked(): self.legend = None self.legendModel.clear() self.updateModelLegend() return field = self.ui.legendComboBox.itemData( self.ui.legendComboBox.currentIndex()) num = None minimum = None maximum = None if self.ui.legendBinSpinBox.isEnabled(): num = self.ui.legendBinSpinBox.value() minimum = self.ui.legendMinSpinBox.value() maximum = self.ui.legendMaxSpinBox.value() self.legend = self.model.partition(field, minimum, maximum, num) lst = list(sorted(self.legend.keys())) for i, s in enumerate(lst): c = self.model.cfg.color(field, s, i) item = QStandardItem(s) item.setData(QColor(c), Qt.DecorationRole) item.setData(s) self.legendModel.appendRow(item) self.updateModelLegend() def selectLegendColor(self, index): c = QColorDialog.getColor( self.legendModel.data(index, Qt.DecorationRole), self) if c.isValid(): self.legendModel.setData(index, c, Qt.DecorationRole) def updateModelLegend(self): stack = None if self.legend is not None: stack = [[], [], []] for i in range(self.legendModel.rowCount()): it = self.legendModel.item(i) stack[0].append(self.legend[it.data()]) stack[1].append(it.data(Qt.DecorationRole).name()) stack[2].append(it.text()) self.model.setStacks(stack) # Partitions def onPartitionComboChange(self): field = self.ui.partComboBox.itemData( self.ui.partComboBox.currentIndex()) enable = not self.model.isCategorical(field) if self.ui.partitionBox.isChecked() or not enable: self.ui.partBinSpinBox.setEnabled(enable) self.ui.partMinSpinBox.setEnabled(enable) self.ui.partMaxSpinBox.setEnabled(enable) self.ui.partMinSpinBox.setMinimum(self.model.fieldmin(field)) self.ui.partMaxSpinBox.setMinimum(self.model.fieldmin(field)) self.ui.partMinSpinBox.setMaximum(self.model.fieldmax(field)) self.ui.partMaxSpinBox.setMaximum(self.model.fieldmax(field)) self.ui.partMinSpinBox.setValue(self.model.fieldmin(field)) self.ui.partMaxSpinBox.setValue(self.model.fieldmax(field)) self.calcPartitions() def calcPartitions(self): self.model.clearPartition() if not self.ui.partitionBox.isChecked(): self.partitions = None self.partitionModel.setStringList([]) return field = self.ui.partComboBox.itemData( self.ui.partComboBox.currentIndex()) num = None minimum = None maximum = None if self.ui.partBinSpinBox.isEnabled(): num = self.ui.partBinSpinBox.value() minimum = self.ui.partMinSpinBox.value() maximum = self.ui.partMaxSpinBox.value() self.partitions = self.model.partition(field, minimum, maximum, num) self.partitionModel.setStringList(sorted(self.partitions.keys())) def onSelectPartition(self): part = self.partitionModel.data( self.ui.partitionList.selectionModel().currentIndex(), Qt.DisplayRole) self.model.setPartition(self.partitions[part]) # Flags def onFlagChange(self): slist = [] for i in np.where(self.model.flagFilter.keep)[0]: slist.append(str(i)) self.flagModel.setStringList(slist) def onIgnoreFlags(self, checked): if checked: self.model.flagFilter.setActive(False) def onExcludeFlags(self, checked): if checked: self.model.flagFilter.setInvert(True) self.model.flagFilter.setActive(True) def onOnlyFlags(self, checked): if checked: self.model.flagFilter.setInvert(False) self.model.flagFilter.setActive(True) def onSelectFlag(self): row = int( self.flagModel.data( self.ui.flaggedList.selectionModel().currentIndex(), Qt.DisplayRole)) self.flagselected.emit(row) # Limits def onLimitChange(self): if self.ui.limitCheckBox.isChecked(): self.model.limit = self.ui.limitSpinBox.value() else: self.model.limit = None self.model.limitModeRandom = self.ui.limRandomButton.isChecked() def setLimit(self, l): self.ui.limitSpinBox.setValue(l) self.ui.limitCheckBox.setChecked(True) self.onLimitChange() # Filters def onSaveFilters(self): name = QFileDialog.getSaveFileName(self, 'Save Filters As', filter='*.xml') if qt5: if name: self.model.saveFilters(name[0]) elif name is not None and len(name): self.model.saveFilters(name) def onLoadFilters(self): name = QFileDialog.getOpenFileName(self, 'Load Filter File', filter='*.xml') if qt5: if name: self.model.loadFilters(name[0]) elif name is not None and len(name): self.model.loadFilters(name)
class logicGoods(Ui_sysGoods, QDialog): def __init__(self, MySQL): super().__init__() self.setupUi(self) self.MySQL = MySQL self.listFunc.currentRowChanged.connect( self.stackedWidget.setCurrentIndex) # list和右边窗口index绑定 self.listFunc.currentRowChanged.connect(self.left_row_changed) # 货物增删查 self.headers = ['货物名', '货物简介', '数目'] self.data_model = QStandardItemModel() self.data_model_2 = QStandardItemModel() self.data = [] self.tv_goods.setModel(self.data_model) self.tv_goods_2.setModel(self.data_model_2) self.tv_goods.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.tv_goods.setEditTriggers(QTableView.NoEditTriggers) # 不可编辑 self.tv_goods.setSelectionBehavior(QTableView.SelectRows) # 选中行 self.tv_goods_2.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.tv_goods_2.setEditTriggers(QTableView.NoEditTriggers) # 不可编辑 self.tv_goods_2.setSelectionBehavior(QTableView.SelectRows) # 选中行 self.onlyInt = QIntValidator() self.le_inout_price.setValidator(self.onlyInt) # 限制只能为int self.le_inout_num.setValidator(self.onlyInt) self.listFunc.setCurrentRow(0) #self.myclear() self.le_filter.textChanged.connect(self.on_le_filter_textChange) self.le_inout_name.textChanged.connect(self.on_le_filter_textChange_2) self.cb_inout_year.currentIndexChanged.connect(self.inout_day_parser) self.cb_inout_month.currentIndexChanged.connect(self.inout_day_parser) ################################# 按钮事件 ############################################# self.btn_confirm_add.clicked.connect(self.on_btn_add_goods) self.btn_remove_select.clicked.connect(self.on_btn_remove_goods) self.btn_inout_confirm.clicked.connect(self.on_btn_inout_confirm) def row_sel_change(self): pass def inout_day_parser(self): self.cb_inout_day.clear() year = self.cb_inout_year.currentText() month = self.cb_inout_month.currentText() if year != '' and month != '': year = int(year) month = int(month) days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] # 闰年逻辑 if year % 400 == 0 or year % 100 != 0 and year % 4 == 0: days[1] = 29 self.cb_inout_day.addItems( [str(i) for i in range(1, days[month - 1] + 1)]) def inout_date_clear(self): self.cb_inout_year.clear() self.cb_inout_month.clear() self.cb_inout_day.clear() year = dt.now().year month = dt.now().month day = dt.now().day self.cb_inout_year.addItems([str(year - 1), str(year), str(year + 1)]) self.cb_inout_year.setCurrentIndex(1) self.cb_inout_month.addItems([str(i) for i in range(1, 13)]) self.cb_inout_month.setCurrentIndex(month - 1) self.inout_day_parser() self.cb_inout_day.setCurrentIndex(day - 1) def myclear(self): self.btn_remove_select.setEnabled(False) self.le_filter.setText('') # 表刷新/后台缓存数据刷新 self.data_model.clear() self.data_model.setHorizontalHeaderLabels(self.headers) self.data = [] # 添加货物刷新 self.le_goods_name.setText('') self.te_goods_info.setText('') self.btn_confirm_add.setEnabled(False) # 进/出库登记刷新 self.le_inout_name.clear() self.le_inout_num.clear() self.le_inout_price.clear() self.inout_date_clear() # 后台数据刷新 self.btn_remove_select.setEnabled(True) self.btn_confirm_add.setEnabled(True) self.le_filter.clear() self.btn_inout_confirm.setEnabled(True) self.connect_db() self.listFunc.setCurrentRow(0) self.data_model_2.clear() self.data_model_2.setHorizontalHeaderLabels(self.headers) for i, (goods_name, goods_info, goods_num) in enumerate(self.data): self.data_model_2.appendRow([ QStandardItem(goods_name), QStandardItem(goods_info), QStandardItem(str(goods_num)) ]) #### 按钮/输入事件 def on_btn_inout_confirm(self): # 校验 if self.le_inout_name.text() == '': QMessageBox.critical(self, '错误', '货物名不能为空!', QMessageBox.Ok, QMessageBox.Ok) elif self.le_inout_price.text() == '': QMessageBox.critical(self, '错误', '货物价格不能为空!', QMessageBox.Ok, QMessageBox.Ok) elif self.le_inout_num.text() == '': QMessageBox.critical(self, '错误', '货物数目不能为空!', QMessageBox.Ok, QMessageBox.Ok) else: goods_name = self.le_inout_name.text() goods_price = int(self.le_inout_price.text()) goods_num = int(self.le_inout_num.text()) year = int(self.cb_inout_year.currentText()) month = int(self.cb_inout_month.currentText()) day = int(self.cb_inout_day.currentText()) inout = 1 - self.cb_inout.currentIndex() try: sql = '''INSERT INTO goods_inout VALUES( '{}',{},{},'{}-{}-{}',{} )'''.format(goods_name, goods_price, inout, year, month, day, goods_num) print(sql) str_confirm = ''' 操作类型:\t{}\t\n 货物名:\t{}\t\n 货物价格:\t{}\t\n 货物数目:\t{}\t\n 操作日期:\t{}-{}-{}\t '''.format(self.cb_inout.currentText(), goods_name, goods_price, goods_num, year, month, day) reply = QMessageBox.question(self, '操作确认', str_confirm, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) # 插入数据库 if reply == QMessageBox.Yes: find = False left = 0 changed_idx = -1 for i, (cache_goods_name, cache_goods_info, cache_goods_num) in enumerate(self.data): if goods_name == cache_goods_name: left = cache_goods_num find = True changed_idx = i break if find == True: # 进出库登记,正确出库或者入库后才执行 def inout_mark(MySQL, sql): flag = MySQL.InsertFromDataBse(sql) if (flag == True): QMessageBox.information( self, '提示', '{}成功'.format(self.cb_inout.currentText()), QMessageBox.Ok, QMessageBox.Ok) return flag # 入库 if inout == 1: flag = inout_mark(self.MySQL, sql) if flag == True: # 刷新缓存数据 self.data[changed_idx][2] += goods_num self.flush_data_model_2() else: QMessageBox.critical( self, '错误', '连接数据库失败!\n考虑是否是以下原因引起:\n (1)网络连接出现问题', QMessageBox.Ok, QMessageBox.Ok) # 出库 elif inout == 0: if left < goods_num: QMessageBox.critical( self, '错误', '当前仓库的“{}”的库存不足!\n当前库存:{}'.format( goods_name, left), QMessageBox.Ok, QMessageBox.Ok) else: flag = inout_mark(self.MySQL, sql) if flag == True: # 刷新缓存数据 self.data[changed_idx][2] -= goods_num self.flush_data_model_2() else: QMessageBox.critical( self, '错误', '连接数据库失败!\n考虑是否是以下原因引起:\n (1)网络连接出现问题', QMessageBox.Ok, QMessageBox.Ok) else: QMessageBox.critical( self, '错误', '仓库中未存在名为“{}”的货物!'.format(goods_name), QMessageBox.Ok, QMessageBox.Ok) except Exception as e: print(e) def on_btn_remove_goods(self): row = self.tv_goods.currentIndex().row() if row == -1: QMessageBox.critical(self, '错误', '未选中任何货物', QMessageBox.Ok, QMessageBox.Ok) else: name = self.data_model.index(row, 0) info = self.data_model.index(row, 1) num = self.data_model.index(row, 2) dl_goods_name = str(self.data_model.data(name)) dl_goods_info = str(self.data_model.data(info)) dl_goods_num = str(self.data_model.data(num)) try: str_confirm = '''是否删除下列货物:\n 货物名:\t{}\n 货物简介:\t{}\n 货物余量:\t{}'''.format( dl_goods_name, dl_goods_info, dl_goods_num) reply = QMessageBox.question(self, '确认信息', str_confirm, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) # 插入数据库 if reply == QMessageBox.Yes: sql = '''DELETE FROM goods WHERE goods_name='{}' '''.format( dl_goods_name) flag = self.MySQL.DeleteFromDataBse(sql) if (flag == True): # 从缓存数据中删除数据 for i, (goods_name, goods_info, goods_num) in enumerate(self.data): if dl_goods_name == goods_name: self.data.pop(i) break # 从数据模型中删除 self.data_model.removeRow(row) QMessageBox.information(self, '提示', '删除货物成功', QMessageBox.Ok, QMessageBox.Ok) else: QMessageBox.critical( self, '错误', '删除货物失败!\n考虑是否是以下原因引起:\n (1)网络连接出现问题', QMessageBox.Ok, QMessageBox.Ok) except Exception as e: print(e) def on_le_filter_textChange(self): term = self.le_filter.text().strip() # 清空数据模型 self.data_model.clear() self.data_model.setHorizontalHeaderLabels(self.headers) # 备份缓存数据 if term == '': for i, (goods_name, goods_info, goods_num) in enumerate(self.data): self.data_model.appendRow([ QStandardItem(goods_name), QStandardItem(goods_info), QStandardItem(str(goods_num)) ]) else: for i, (goods_name, goods_info, goods_num) in enumerate(self.data): if term in goods_name: self.data_model.appendRow([ QStandardItem(goods_name), QStandardItem(goods_info), QStandardItem(str(goods_num)) ]) def on_le_filter_textChange_2(self): term = self.le_inout_name.text().strip() # 清空数据模型 self.data_model_2.clear() self.data_model_2.setHorizontalHeaderLabels(self.headers) # 备份缓存数据 if term == '': for i, (goods_name, goods_info, goods_num) in enumerate(self.data): self.data_model_2.appendRow([ QStandardItem(goods_name), QStandardItem(goods_info), QStandardItem(str(goods_num)) ]) else: for i, (goods_name, goods_info, goods_num) in enumerate(self.data): if term in goods_name: self.data_model_2.appendRow([ QStandardItem(goods_name), QStandardItem(goods_info), QStandardItem(str(goods_num)) ]) def connect_db(self): self.data_model.clear() self.data_model.setHorizontalHeaderLabels(self.headers) try: sql = '''SELECT * FROM goods''' flag, result = self.MySQL.SelectFromDataBse(sql) if (flag == True): for d in result: self.data.append(list(d)) else: QMessageBox.critical(self, '错误', '连接货物数据库失败', QMessageBox.Ok, QMessageBox.Ok) self.btn_remove_select.setEnabled(False) self.btn_confirm_add.setEnabled(False) self.btn_inout_confirm.setEnabled(False) except Exception as e: print(e) def on_btn_add_goods(self): # 有效性检查 if self.le_goods_name.text() == '': QMessageBox.critical(self, '错误', '货物名不能为空!', QMessageBox.Ok, QMessageBox.Ok) else: goods_name = self.le_goods_name.text() goods_info = self.te_goods_info.toPlainText() sql = ''' INSERT INTO goods (goods_name, goods_info) VALUES ('{}','{}') '''.format(goods_name, goods_info) try: str_confirm = '''插入信息如下:\n 货物名:\t{}\n 货物简介:\t{}'''.format( goods_name, goods_info) reply = QMessageBox.question(self, '确认信息', str_confirm, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) # 插入数据库 if reply == QMessageBox.Yes: flag = self.MySQL.InsertFromDataBse(sql) if not flag: QMessageBox.critical( self, '错误', '插入数据库失败!\n考虑是否是以下原因引起:\n (1)数据库存在同名的货物\n (2)网络连接出现问题', QMessageBox.Ok, QMessageBox.Ok) else: QMessageBox.information(self, '提示', '添加货物成功', QMessageBox.Ok, QMessageBox.Ok) # 更新缓存 self.data.append([goods_name, goods_info, 0]) self.data_model.appendRow([ QStandardItem(goods_name), QStandardItem(goods_info), QStandardItem(str(0)) ]) # 清空输入 self.le_goods_name.clear() self.te_goods_info.clear() except Exception as e: print(e) def left_row_changed(self): row = self.listFunc.currentIndex().row() print(row) # 进出库登记 if row == 0: self.flush_data_model_2() # 货物增删查 elif row == 1: self.data_model.clear() self.data_model.setHorizontalHeaderLabels(self.headers) for i, (goods_name, goods_info, goods_num) in enumerate(self.data): self.data_model.appendRow([ QStandardItem(goods_name), QStandardItem(goods_info), QStandardItem(str(goods_num)) ]) def flush_data_model_2(self): self.le_inout_name.clear() self.le_inout_num.clear() self.le_inout_price.clear() self.cb_inout.setCurrentIndex(0) self.data_model_2.clear() self.data_model_2.setHorizontalHeaderLabels(self.headers) for i, (goods_name, goods_info, goods_num) in enumerate(self.data): self.data_model_2.appendRow([ QStandardItem(goods_name), QStandardItem(goods_info), QStandardItem(str(goods_num)) ])