class App(QWidget): # 初期化 def __init__(self): super().__init__() self.browser = QWebEngineView() self.model = QStandardItemModel() self.listWidget = QListView() self.init_ui() # ユーザーインターフェイスを初期化する def init_ui(self): self.listWidget.setModel(self.model) self.listWidget.setAlternatingRowColors(True) self.listWidget.setEditTriggers(QAbstractItemView.NoEditTriggers) self.listWidget.setSelectionMode( QAbstractItemView.SingleSelection ) self.listWidget.selectionModel().selectionChanged.connect(self.load_html) self.listWidget.setItemDelegate(ItemDelegate(self.listWidget)) grid = QGridLayout() grid.setSpacing(10) splitter = QSplitter(Qt.Horizontal) splitter.addWidget(self.listWidget) splitter.addWidget(self.browser) grid.addWidget(splitter,1,0) self.init_list_items() self.setLayout(grid) self.resize(1200, 800) self.listWidget.setMinimumWidth(600) self.browser.setMinimumWidth(600) self.show() # 記事本文をデータベースから取り出す(URLの) @classmethod #これはあってもなくても良い def fetch_contents(self, url): with closing(sqlite3.connect('articles.db')) as conn: c = conn.cursor() c.execute("SELECT contents FROM articles WHERE url=?;", [url]) rows = c.fetchall() for row in rows: return row[0] # ブラウザに記事をセットする def load_html(self): for i in self.listWidget.selectedIndexes(): html = '<h1>' + i.data()[1] + '</h1>' + self.fetch_contents(i.data()[0]) self.browser.setHtml(html) # リストを初期化する def init_list_items(self): with closing(sqlite3.connect('articles.db')) as conn: c = conn.cursor() c.execute("SELECT url,title FROM articles;") rows = c.fetchall() for row in rows: item = QStandardItem() item.setData(row, Qt.DisplayRole) item.setSizeHint(QSize(20, 30)) self.model.appendRow(item)
class MdEditor(QSplitter): def __init__(self): super(MdEditor, self).__init__() self.writePart = QPlainTextEdit() self.showPart = QWebEngineView() self.showPart.setZoomFactor(1.5) self.writePart.setFont(QFont("", 13)) self.showPart.setMinimumWidth(20) self.writePart.textChanged.connect(self.updateShowPart) self.addWidget(self.writePart) self.addWidget(self.showPart) def updateShowPart(self): text = self.writePart.toPlainText() html = md2html(text) self.showPart.setHtml(html)
class App(QWidget): def __init__(self, parent=None): super().__init__(parent) self.actions = [] self.project = Project() self.create_window() def create_window(self): # labels nameLabel = QLabel("Nazwa akcji", self) nameLabel.setFixedWidth(100) durationLabel = QLabel("Czas trwania akcji", self) durationLabel.setFixedWidth(100) predLabel = QLabel("Poprzednicy [;]", self) predLabel.setFixedWidth(100) self.criticalPathLabel = QLabel("Ścieżka krytyczna: ", self) self.timeLabel = QLabel("Czas trwania: ", self) self.messageLabel = QLabel("", self) self.graphLabel = QLabel(self) pixmap2 = QPixmap('images/graph.png') self.graphLabel.setPixmap(pixmap2) # entries self.nameEntry = QLineEdit() self.nameEntry.setFixedWidth(150) self.durationEntry = QLineEdit() self.durationEntry.setFixedWidth(150) self.predEntry = QLineEdit() self.predEntry.setFixedWidth(150) # buttons addButton = QPushButton("Dodaj czynność", self) addButton.setFixedWidth(100) computeButton = QPushButton("Oblicz", self) computeButton.setFixedWidth(100) # buttons action addButton.clicked.connect(self.add_action) computeButton.clicked.connect(self.compute_task) # web view self.view = QWebEngineView(self) self.view.setMinimumWidth(800) self.view.setMinimumHeight(600) url = QtCore.QUrl.fromLocalFile(r"/temp-plot.html") self.view.load(url) # grid hboxRow1 = QHBoxLayout() hboxRow1.addWidget(nameLabel) hboxRow1.addWidget(self.nameEntry) hboxRow1.addWidget(addButton) hboxRow1.addStretch(1) hboxRow2 = QHBoxLayout() hboxRow2.addWidget(durationLabel) hboxRow2.addWidget(self.durationEntry) hboxRow2.addWidget(computeButton) hboxRow2.addStretch(1) hboxRow3 = QHBoxLayout() hboxRow3.addWidget(predLabel) hboxRow3.addWidget(self.predEntry) hboxRow3.addStretch(1) vboxRowLast = QVBoxLayout() vboxRowLast.addWidget(self.criticalPathLabel) vboxRowLast.addWidget(self.timeLabel) vboxEntryData = QVBoxLayout() vboxEntryData.addLayout(hboxRow1) vboxEntryData.addLayout(hboxRow2) vboxEntryData.addLayout(hboxRow3) vboxEntryData.addLayout(vboxRowLast) vboxGraphInfo = QVBoxLayout() vboxGraphInfo.addWidget(self.graphLabel) vboxGraphInfo.addStretch(1) vboxGraphInfo.addWidget(self.messageLabel) vboxLeftSide = QVBoxLayout() vboxLeftSide.addLayout(vboxEntryData) vboxLeftSide.addStretch(1) vboxLeftSide.addLayout(vboxGraphInfo) hboxFinal = QHBoxLayout() hboxFinal.addLayout(vboxLeftSide) hboxFinal.addStretch(1) hboxFinal.addWidget(self.view) self.setLayout(hboxFinal) self.resize(500, 500) self.setWindowTitle("App") self.show() def add_action(self): name = self.nameEntry.text() duration = self.durationEntry.text() if self.predEntry.text() == '': pred = [] else: pred = self.predEntry.text().split(";") try: #s self.actions.append(Action(name, duration, pred)) self.messageLabel.setText("Dodano czynność: " + name) except Exception as error: self.messageLabel.setText(error.args[0]) self.actions = [] for task in self.actions: print(task.name + " " + str(task.duration) + " " + str(task.predecessors)) def compute_task(self): if self.actions != []: self.project.create_network(self.actions) cp = self.project.get_critical_path() dr = self.project.get_duration() create_graph_image(self.actions, cp) self.graphLabel.setPixmap(QPixmap('images/graph.png')) create_gantt_chart(self.actions, cp) url = QtCore.QUrl.fromLocalFile(r"/temp-plot.html") self.view.load(url) # self.ganttLabel.setPixmap(QPixmap('images/gantt.png')) self.timeLabel.setText("Czas trwania " + str(dr) + "h") string_cp = str(cp[0]) for node in cp[1:]: string_cp += " -> " + str(node) self.criticalPathLabel.setText(string_cp) self.actions = [] self.project = Project() else: self.messageLabel.setText("Brak danych!") self.actions = [] self.project = Project()
class OWPlotlyViewer(SparkEnvironment, widget.OWWidget): # --------------- Widget metadata protocol --------------- priority = 2 name = "Plotly" description = "A plotly canvas" icon = "../assets/DataFrameViewer.svg" input_figure: go.Figure = None class Inputs: figure = widget.Input("Figure", go.Figure) class Outputs: pass #data_frame = widget.Output("DataFrame", pyspark.sql.DataFrame) want_control_area = True def __init__(self): super().__init__() self.controlArea.setMinimumWidth(250) self.v_info_box = gui.vBox(self.controlArea, 'Info') self.v_info = gui.label(self.v_info_box, self, '') self.v_info.setAlignment(QtCore.Qt.AlignTop) self.mainArea.setMinimumWidth(800) self.mainArea.setMinimumHeight(600) self.v_webview = QWebEngineView(self.mainArea) self.v_webview.setMinimumWidth(800) self.v_webview.setMinimumHeight(600) @Inputs.figure def set_input_figure(self, figure): self.input_figure = figure # called after received all inputs def handleNewSignals(self): self.apply() def _check_input(self): if self.input_figure is None: self.warning('Input figure does not exist') return False else: return True # this is the logic: computation, update UI, send outputs. .. def apply(self): if not self._check_input(): return self.clear_messages() filename = tempfile.NamedTemporaryFile(suffix='.html').name print('Plot file path: %s' % filename) plotly.offline.plot(self.input_figure, output_type='file', filename=filename, auto_open=False, show_link=False) # , include_plotlyjs=True) self.v_webview.load(QUrl.fromLocalFile(filename))
class MainWindow(QMainWindow): htmlReady = pyqtSignal(str) def __init__(self, app): QMainWindow.__init__(self) self.install_directory = os.getcwd() self.app = app self.book = None self.last_book = "" self.filename = "" self._part_is_new = False self.tread_running = False self.initTheme() self.createUi() self.loadPlugins() self.createMenus() self.createStatusBar() self.readSettings() self.text_edit.textChanged.connect(self.textChanged) def initTheme(self): settings = QSettings(QSettings.IniFormat, QSettings.UserScope, QCoreApplication.organizationName(), QCoreApplication.applicationName()) self.theme = settings.value("theme", "DarkFusion") hilite_color = settings.value( "hiliteColor", self.palette().highlight().color().name()) self.changeStyle(self.theme, hilite_color) def showEvent(self, event): if self.last_book: self.loadBook(self.last_book) def changeStyle(self, theme, hilite_color): self.theme = theme if theme == "DarkFusion": QApplication.setStyle(DarkFusion(hilite_color)) else: QApplication.setStyle(QStyleFactory.create(theme)) pal = self.app.palette() pal.setColor(QPalette.Highlight, QColor(hilite_color)) self.app.setPalette(pal) def createUi(self): self.content = Expander("Content", ":/images/parts.svg") self.images = Expander("Images", ":/images/images.svg") self.settings = Expander("Settings", ":/images/settings.svg") self.setWindowTitle(QCoreApplication.applicationName() + " " + QCoreApplication.applicationVersion()) vbox = QVBoxLayout() vbox.addWidget(self.content) vbox.addWidget(self.images) vbox.addWidget(self.settings) vbox.addStretch() self.content_list = QListWidget() self.content_list.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) content_box = QVBoxLayout() content_box.addWidget(self.content_list) self.item_edit = QLineEdit() self.item_edit.setMaximumHeight(0) self.item_edit.editingFinished.connect(self.editItemFinished) self.item_anim = QPropertyAnimation(self.item_edit, "maximumHeight".encode("utf-8")) content_box.addWidget(self.item_edit) button_layout = QHBoxLayout() plus_button = FlatButton(":/images/plus.svg") self.edit_button = FlatButton(":/images/edit.svg") self.trash_button = FlatButton(":/images/trash.svg") self.up_button = FlatButton(":/images/up.svg") self.down_button = FlatButton(":/images/down.svg") self.trash_button.enabled = False self.up_button.enabled = False self.down_button.enabled = False button_layout.addWidget(plus_button) button_layout.addWidget(self.up_button) button_layout.addWidget(self.down_button) button_layout.addWidget(self.edit_button) button_layout.addWidget(self.trash_button) content_box.addLayout(button_layout) self.content.addLayout(content_box) plus_button.clicked.connect(self.addPart) self.trash_button.clicked.connect(self.dropPart) self.up_button.clicked.connect(self.partUp) self.down_button.clicked.connect(self.partDown) self.edit_button.clicked.connect(self.editPart) self.image_list = QListWidget() self.image_list.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) image_box = QVBoxLayout() image_box.addWidget(self.image_list) image_button_layout = QHBoxLayout() image_plus_button = FlatButton(":/images/plus.svg") self.image_trash_button = FlatButton(":/images/trash.svg") self.image_trash_button.enabled = False image_button_layout.addWidget(image_plus_button) image_button_layout.addWidget(self.image_trash_button) image_box.addLayout(image_button_layout) self.images.addLayout(image_box) image_plus_button.clicked.connect(self.addImage) self.image_trash_button.clicked.connect(self.dropImage) scroll_content = QWidget() scroll_content.setLayout(vbox) scroll = QScrollArea() scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll.setWidget(scroll_content) scroll.setWidgetResizable(True) scroll.setMaximumWidth(200) scroll.setMinimumWidth(200) self.navigationdock = QDockWidget("Navigation", self) self.navigationdock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.navigationdock.setWidget(scroll) self.navigationdock.setObjectName("Navigation") self.addDockWidget(Qt.LeftDockWidgetArea, self.navigationdock) self.splitter = QSplitter() self.text_edit = MarkdownEdit() self.text_edit.setFont(QFont("Courier", 15)) # 11 on Linux self.preview = QWebEngineView() self.preview.setMinimumWidth(300) self.setWindowTitle(QCoreApplication.applicationName()) self.splitter.addWidget(self.text_edit) self.splitter.addWidget(self.preview) self.setCentralWidget(self.splitter) self.content.expanded.connect(self.contentExpanded) self.images.expanded.connect(self.imagesExpanded) self.settings.expanded.connect(self.settingsExpanded) self.settings.clicked.connect(self.openSettings) self.content_list.currentItemChanged.connect(self.partSelectionChanged) self.image_list.currentItemChanged.connect(self.imageSelectionChanged) self.image_list.itemDoubleClicked.connect(self.insertImage) self.text_edit.undoAvailable.connect(self.undoAvailable) self.text_edit.redoAvailable.connect(self.redoAvailable) self.text_edit.copyAvailable.connect(self.copyAvailable) QApplication.clipboard().dataChanged.connect(self.clipboardDataChanged) def undoAvailable(self, value): self.undo_act.setEnabled(value) def redoAvailable(self, value): self.redo_act.setEnabled(value) def copyAvailable(self, value): self.copy_act.setEnabled(value) self.cut_act.setEnabled(value) def clipboardDataChanged(self): md = QApplication.clipboard().mimeData() self.paste_act.setEnabled(md.hasText()) def openSettings(self): dlg = Settings(self.book, self.install_directory) dlg.exec() if dlg.saved: self.setWindowTitle(QCoreApplication.applicationName() + " - " + self.book.name) def addPart(self): self.item_edit.setText("") self.item_edit.setFocus() self.item_anim.setStartValue(0) self.item_anim.setEndValue(23) self.item_anim.start() self._part_is_new = True def addItem(self): text = self.item_edit.text() if text: if not self.book.getPart(text): self.book.addPart(text) self.loadBook(self.last_book) def updateItem(self): text = self.item_edit.text() if text: if not self.book.getPart(text): self.book.updatePart( self.content_list.currentItem().data(1).name, text) self.loadBook(self.last_book) def editItemFinished(self): if self._part_is_new: self.addItem() else: self.updateItem() self.item_anim.setStartValue(23) self.item_anim.setEndValue(0) self.item_anim.start() def editPart(self): item = self.content_list.currentItem().data(1).name self.item_edit.setText(item) self.item_edit.setFocus() self.item_anim.setStartValue(0) self.item_anim.setEndValue(23) self.item_anim.start() self._part_is_new = False def dropPart(self): item = self.content_list.currentItem().data(1).name msgBox = QMessageBox() msgBox.setText("You are about to delete the part <i>" + item + "</i>") msgBox.setInformativeText("Do you really want to delete the item?") msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel) msgBox.setDefaultButton(QMessageBox.Cancel) ret = msgBox.exec() if ret == QMessageBox.Yes: self.book.dropPart(item) self.loadBook(self.last_book) def addImage(self): fileName = "" dialog = QFileDialog() dialog.setFileMode(QFileDialog.AnyFile) dialog.setNameFilter("Image Files(*.png *.jpg *.bmp *.gif);;All (*)") dialog.setWindowTitle("Load Image") dialog.setOption(QFileDialog.DontUseNativeDialog, True) dialog.setAcceptMode(QFileDialog.AcceptOpen) if dialog.exec_(): fileName = dialog.selectedFiles()[0] del dialog if not fileName: return base = os.path.basename(fileName) if not os.path.exists( os.path.join(self.book.source_path, "images", base)): copy(fileName, os.path.join(self.book.source_path, "images")) item = QListWidgetItem() item.setText(Path(fileName).name) item.setData( 1, os.path.join(self.book.source_path, "images", Path(fileName).name)) self.image_list.addItem(item) def dropImage(self): item = self.image_list.currentItem() image = item.data(1) filename = os.path.join(self.book.source_path, "parts", image) os.remove(filename) self.loadImages() def loadImages(self): self.image_list.clear() for root, dir, files in os.walk( os.path.join(self.book.source_path, "images")): for file in files: filename = os.path.join(self.book.source_path, "images", Path(file).name) item = QListWidgetItem() item.setToolTip("Doubleclick image to insert into text") item.setText(Path(file).name) item.setData(1, filename) self.image_list.addItem(item) def partUp(self): pos = self.content_list.currentRow() item = self.content_list.takeItem(pos) self.content_list.insertItem(pos - 1, item) self.content_list.setCurrentRow(pos - 1) self.book.partUp(item.data(1).name) def partDown(self): pos = self.content_list.currentRow() item = self.content_list.takeItem(pos) self.content_list.insertItem(pos + 1, item) self.content_list.setCurrentRow(pos + 1) self.book.partDown(item.data(1).name) def partSelectionChanged(self, item): if item: part = item.data(1) self.filename = os.path.join(self.book.source_path, "parts", part.src) with open(self.filename, "r") as f: t = f.read() self.text_edit.setPlainText(t) self.trash_button.enabled = True self.up_button.enabled = self.content_list.currentRow() > 0 self.down_button.enabled = self.content_list.currentRow( ) < self.content_list.count() - 1 self.edit_button.enabled = True else: self.text_edit.setText("") self.trash_button.enabled = False self.up_button.enabled = False self.down_button.enabled = False self.edit_button.enabled = False def imageSelectionChanged(self, item): if item: self.image_trash_button.enabled = True else: self.image_trash_button.enabled = False def contentExpanded(self, value): if value: self.images.setExpanded(False) self.settings.setExpanded(False) def imagesExpanded(self, value): if value: self.content.setExpanded(False) self.settings.setExpanded(False) def appearanceExpanded(self, value): if value: self.content.setExpanded(False) self.images.setExpanded(False) self.settings.setExpanded(False) def settingsExpanded(self, value): if value: self.content.setExpanded(False) self.images.setExpanded(False) def closeEvent(self, event): self.writeSettings() event.accept() def createMenus(self): new_icon = QIcon(QPixmap(":/images/new.svg")) open_icon = QIcon(QPixmap(":/images/open.svg")) book_icon = QIcon(QPixmap(":/images/book.svg")) bold_icon = QIcon(QPixmap(":/images/bold.svg")) italic_icon = QIcon(QPixmap(":/images/italic.svg")) image_icon = QIcon(QPixmap(":/images/image.svg")) table_icon = QIcon(QPixmap(":/images/table.svg")) á_icon = QIcon(QPixmap(":/images/á.svg")) ã_icon = QIcon(QPixmap(":/images/ã.svg")) é_icon = QIcon(QPixmap(":/images/é.svg")) ê_icon = QIcon(QPixmap(":/images/ê.svg")) ó_icon = QIcon(QPixmap(":/images/ó.svg")) new_act = QAction(new_icon, "&New", self) new_act.setShortcuts(QKeySequence.New) new_act.setStatusTip("Create a new ebook project") new_act.triggered.connect(self.newFile) new_act.setToolTip("Create new ebook project") open_act = QAction(open_icon, "&Open", self) open_act.setShortcuts(QKeySequence.Open) open_act.setStatusTip("Open an existing ebook project") open_act.triggered.connect(self.open) open_act.setToolTip("Open an existing ebook project") book_act = QAction(book_icon, "&Create Book", self) book_act.setShortcuts(QKeySequence.SaveAs) book_act.setStatusTip("Create an ebook") book_act.triggered.connect(self.create) book_act.setToolTip("Create an ebook") pdf_act = QAction("Create &PDF", self) pdf_act.setStatusTip("Create PDF") pdf_act.setToolTip("Create PDF") pdf_act.triggered.connect(self.pdfExport) settings_act = QAction("&Settings", self) settings_act.setStatusTip("Open settings dialog") settings_act.triggered.connect(self.settingsDialog) settings_act.setToolTip("Open settings dialog") exit_act = QAction("E&xit", self) exit_act.setShortcuts(QKeySequence.Quit) exit_act.setStatusTip("Exit the application") exit_act.triggered.connect(self.close) self.undo_act = QAction("Undo", self) self.undo_act.setShortcut(QKeySequence.Undo) self.undo_act.setEnabled(False) self.undo_act.triggered.connect(self.doUndo) self.redo_act = QAction("Redo", self) self.redo_act.setShortcut(QKeySequence.Redo) self.redo_act.setEnabled(False) self.undo_act.triggered.connect(self.doRedo) self.cut_act = QAction("Cu&t", self) self.cut_act.setShortcut(QKeySequence.Cut) self.cut_act.triggered.connect(self.doCut) self.cut_act.setEnabled(False) self.copy_act = QAction("&Copy", self) self.copy_act.setShortcut(QKeySequence.Copy) self.copy_act.triggered.connect(self.doCopy) self.copy_act.setEnabled(False) self.paste_act = QAction("&Paste", self) self.paste_act.setShortcut(QKeySequence.Paste) self.paste_act.triggered.connect(self.doPaste) self.paste_act.setEnabled(False) bold_act = QAction(bold_icon, "Bold", self) bold_act.setShortcut(Qt.CTRL + Qt.Key_B) bold_act.triggered.connect(self.bold) italic_act = QAction(italic_icon, "Italic", self) italic_act.setShortcut(Qt.CTRL + Qt.Key_I) italic_act.triggered.connect(self.italic) image_act = QAction(image_icon, "Image", self) image_act.setShortcut(Qt.CTRL + Qt.Key_G) image_act.triggered.connect(self.insertImage) image_act.setToolTip("Insert an image") table_act = QAction(table_icon, "Table", self) table_act.setShortcut(Qt.CTRL + Qt.Key_T) table_act.triggered.connect(self.insertTable) table_act.setToolTip("Insert a table") á_act = QAction(á_icon, "á", self) á_act.triggered.connect(self.insertLetterA1) á_act.setToolTip("Insert letter á") ã_act = QAction(ã_icon, "ã", self) ã_act.triggered.connect(self.insertLetterA2) ã_act.setToolTip("Insert letter ã") é_act = QAction(é_icon, "é", self) é_act.triggered.connect(self.insertLetterE1) é_act.setToolTip("Insert letter é") ê_act = QAction(ê_icon, "ê", self) ê_act.triggered.connect(self.insertLetterE2) ê_act.setToolTip("Insert letter ê") ó_act = QAction(ó_icon, "ó", self) ó_act.triggered.connect(self.insertLetterO1) ó_act.setToolTip("Insert letter ó") about_act = QAction("&About", self) about_act.triggered.connect(self.about) about_act.setStatusTip("Show the application's About box") spell_act = QAction("&Spellcheck", self) spell_act.setShortcut(Qt.CTRL + Qt.Key_P) spell_act.triggered.connect(self.spellCheck) spell_act.setStatusTip("Spellcheck") file_menu = self.menuBar().addMenu("&File") file_menu.addAction(new_act) file_menu.addAction(open_act) file_menu.addAction(book_act) file_menu.addAction(pdf_act) file_menu.addSeparator() file_menu.addAction(settings_act) file_menu.addSeparator() file_menu.addAction(exit_act) edit_menu = self.menuBar().addMenu("&Edit") edit_menu.addAction(self.undo_act) edit_menu.addAction(self.redo_act) edit_menu.addSeparator() edit_menu.addAction(self.cut_act) edit_menu.addAction(self.copy_act) edit_menu.addAction(self.paste_act) format_menu = self.menuBar().addMenu("&Format") format_menu.addAction(bold_act) format_menu.addAction(italic_act) insert_menu = self.menuBar().addMenu("&Insert") insert_menu.addAction(image_act) insert_menu.addAction(table_act) for key in Plugins.generatorPluginNames(): gen = Plugins.getGeneratorPlugin(key) if gen: act = QAction(gen.display_name, self) #act.triggered.connect(self.insertTable) #act.setToolTip("Insert a table") insert_menu.addAction(act) act.triggered.connect(gen.menu_action) help_menu = self.menuBar().addMenu("&Help") help_menu.addAction(about_act) help_menu.addAction(spell_act) file_tool_bar = self.addToolBar("File") file_tool_bar.addAction(new_act) file_tool_bar.addAction(open_act) file_tool_bar.addAction(book_act) format_tool_bar = self.addToolBar("Format") format_tool_bar.addAction(bold_act) format_tool_bar.addAction(italic_act) insert_toolbar = self.addToolBar("Insert") insert_toolbar.addAction(image_act) insert_toolbar.addAction(table_act) insert_toolbar.addAction(á_act) insert_toolbar.addAction(ã_act) insert_toolbar.addAction(é_act) insert_toolbar.addAction(ê_act) insert_toolbar.addAction(ó_act) def doUndo(self): self.text_edit.undo() def doRedo(self): self.text_edit.redo() def doCut(self): self.text_edit.cut() def doCopy(self): self.text_edit.copy() def doPaste(self): self.text_edit.paste() def insertImage(self): if not self.book: QMessageBox.warning(self, QCoreApplication.applicationName(), "You have to load or create a book first!") return if not self.filename: QMessageBox.warning( self, QCoreApplication.applicationName(), "You have to select part from the book content first!") return if self.image_list.count() == 0: QMessageBox.warning( self, QCoreApplication.applicationName(), "You have to add an image to the image list first!") return if not self.image_list.currentItem(): QMessageBox.warning( self, QCoreApplication.applicationName(), "You have to select an image from the image list first!") return item = self.image_list.currentItem() filename = item.text() cursor = self.text_edit.textCursor() pos = cursor.position() base = filename.split(".")[0].replace("_", "-") cursor.insertText("![" + base + "](../images/" + filename + " \"" + base + "\")") cursor.setPosition(pos) self.text_edit.setTextCursor(cursor) def insertTable(self): cursor = self.text_edit.textCursor() pos = cursor.position() cursor.insertText( "| alignLeft | alignCenter | unAligned | alignRight |\n" "| :--- | :---: | --- | ---: |\n" "| cell a | cell b | cell c | cell d |\n" "| cell e | cell f | cell g | cell h |\n") cursor.setPosition(pos) self.text_edit.setTextCursor(cursor) def insertLetterA1(self): cursor = self.text_edit.textCursor() pos = cursor.position() cursor.insertText("á") cursor.setPosition(pos + 1) self.text_edit.setTextCursor(cursor) def insertLetterA2(self): cursor = self.text_edit.textCursor() pos = cursor.position() cursor.insertText("ã") cursor.setPosition(pos + 1) self.text_edit.setTextCursor(cursor) def insertLetterE1(self): cursor = self.text_edit.textCursor() pos = cursor.position() cursor.insertText("é") cursor.setPosition(pos + 1) self.text_edit.setTextCursor(cursor) def insertLetterE2(self): cursor = self.text_edit.textCursor() pos = cursor.position() cursor.insertText("ê") cursor.setPosition(pos + 1) self.text_edit.setTextCursor(cursor) def insertLetterO1(self): cursor = self.text_edit.textCursor() pos = cursor.position() cursor.insertText("ó") cursor.setPosition(pos + 1) self.text_edit.setTextCursor(cursor) def createStatusBar(self): self.statusBar().showMessage("Ready") def about(self): QMessageBox.about( self, "About " + QCoreApplication.applicationName(), "EbookCreator\nVersion: " + QCoreApplication.applicationVersion() + "\n(C) Copyright 2020 CrowdWare. All rights reserved.\n\nThis program is provided AS IS with NO\nWARRANTY OF ANY KIND, INCLUDING THE\nWARRANTY OF DESIGN, MERCHANTABILITY AND\nFITNESS FOR A PATICULAR PURPOSE." ) def newFile(self): dlg = ProjectWizard(self.install_directory, parent=self) dlg.loadBook.connect(self.loadBook) dlg.show() def open(self): fileName = "" dialog = QFileDialog() dialog.setFileMode(QFileDialog.AnyFile) dialog.setNameFilter("EbookCreator (book.qml);;All (*)") dialog.setWindowTitle("Load Ebook") dialog.setOption(QFileDialog.DontUseNativeDialog, True) dialog.setAcceptMode(QFileDialog.AcceptOpen) dialog.setDirectory(os.path.join(self.install_directory, "sources")) if dialog.exec_(): fileName = dialog.selectedFiles()[0] del dialog if not fileName: return self.loadBook(fileName) def writeSettings(self): settings = QSettings(QSettings.IniFormat, QSettings.UserScope, QCoreApplication.organizationName(), QCoreApplication.applicationName()) settings.setValue("geometry", self.saveGeometry()) settings.setValue("lastBook", self.last_book) def readSettings(self): settings = QSettings(QSettings.IniFormat, QSettings.UserScope, QCoreApplication.organizationName(), QCoreApplication.applicationName()) geometry = settings.value("geometry", QByteArray()) self.last_book = settings.value("lastBook") if not geometry: availableGeometry = QApplication.desktop().availableGeometry(self) self.resize(availableGeometry.width() / 3, availableGeometry.height() / 2) self.move((availableGeometry.width() - self.width()) / 2, (availableGeometry.height() - self.height()) / 2) else: self.restoreGeometry(geometry) def bold(self): if not self.filename: QMessageBox.warning( self, QCoreApplication.applicationName(), "You have to select part from the book content first!") return cursor = self.text_edit.textCursor() pos = cursor.position() if not cursor.hasSelection(): cursor.select(QTextCursor.WordUnderCursor) cursor.insertText("**" + cursor.selectedText() + "**") cursor.setPosition(pos + 2) self.text_edit.setTextCursor(cursor) def italic(self): if not self.filename: QMessageBox.warning( self, QCoreApplication.applicationName(), "You have to select part from the book content first!") return cursor = self.text_edit.textCursor() pos = cursor.position() if not cursor.hasSelection(): cursor.select(QTextCursor.WordUnderCursor) cursor.insertText("*" + cursor.selectedText() + "*") cursor.setPosition(pos + 1) self.text_edit.setTextCursor(cursor) def create(self): filename = "" dialog = QFileDialog() dialog.setFileMode(QFileDialog.AnyFile) dialog.setNameFilter("ePub3 (*.epub);;All (*)") dialog.setWindowTitle("Create Ebook") dialog.setOption(QFileDialog.DontUseNativeDialog, True) dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setDirectory(self.book.source_path) dialog.setDefaultSuffix("epub") if dialog.exec_(): filename = dialog.selectedFiles()[0] del dialog if not filename: return QApplication.setOverrideCursor(Qt.WaitCursor) createEpub(filename, self.book, self) QApplication.restoreOverrideCursor() def loadStatusChanged(self, status): if status == 1: self.book = self.component.create() if self.book is not None: self.book.setFilename(self.last_book) self.book.setWindow(self) else: for error in self.component.errors(): print(error.toString()) return self.content_list.clear() for part in self.book.parts: item = QListWidgetItem() item.setText(part.name) item.setData(1, part) self.content_list.addItem(item) self.loadImages() self.setWindowTitle(QCoreApplication.applicationName() + " - " + self.book.name) self.content.setExpanded(True) self.content_list.setCurrentRow(0) elif status == 3: for error in self.component.errors(): print(error.toString()) return def loadBook(self, filename): self.last_book = filename self.filename = "" engine = QQmlEngine() self.component = QQmlComponent(engine) self.component.statusChanged.connect(self.loadStatusChanged) self.component.loadUrl(QUrl.fromLocalFile(filename)) def settingsDialog(self): dlg = SettingsDialog(self.theme, self.palette().highlight().color().name(), parent=self) dlg.exec() if dlg.theme != self.theme or dlg.hilite_color != self.palette( ).highlight().color().name(): settings = QSettings(QSettings.IniFormat, QSettings.UserScope, QCoreApplication.organizationName(), QCoreApplication.applicationName()) settings.setValue("theme", dlg.theme) settings.setValue("hiliteColor", dlg.hilite_color) msgBox = QMessageBox() msgBox.setText("Please restart the app to change the theme!") msgBox.exec() def textChanged(self): text = self.text_edit.toPlainText() if self.filename: with open(self.filename, "w") as f: f.write(text) self.lock = Lock() with self.lock: if not self.tread_running: self.tread_running = True self.htmlReady.connect(self.previewReady) thread = Thread(target=self.createHtml, args=(text, )) thread.daemon = True thread.start() def previewReady(self, html): self.preview.setHtml( html, baseUrl=QUrl( Path(os.path.join(self.book.source_path, "parts", "index.html")).as_uri())) self.htmlReady.disconnect() with self.lock: self.tread_running = False def createHtml(self, text): html = '<html>\n<head>\n' html += '<link href="../css/pastie.css" rel="stylesheet" type="text/css"/>\n' html += '<link href="../css/stylesheet.css" rel="stylesheet" type="text/css"/>\n' html += '</head>\n<body>\n' html += markdown(text, html4tags=False, extras=[ "fenced-code-blocks", "wiki-tables", "tables", "header-ids" ]) html += '\n</body>\n</html>' html = addLineNumbers(html) self.htmlReady.emit(html) def pdfExport(self): p = PdfExport(self.book, self.statusBar()) def spellCheck(self): if not self.filename: QMessageBox.warning( self, QCoreApplication.applicationName(), "You have to select part from the book content first!") return cursor = self.text_edit.textCursor() pos = cursor.position() if not cursor.hasSelection(): cursor.select(QTextCursor.WordUnderCursor) spell = Speller(lang='en') changed = spell(cursor.selectedText()) if changed != cursor.selectedText(): cursor.insertText(changed) self.text_edit.setTextCursor(cursor) def loadPlugins(self): # check if we are running in a frozen environment (pyinstaller --onefile) if getattr(sys, "frozen", False): bundle_dir = sys._MEIPASS # if we are running in a onefile environment, then copy all plugin to /tmp/... if bundle_dir != os.getcwd(): os.mkdir(os.path.join(bundle_dir, "plugins")) for root, dirs, files in os.walk( os.path.join(os.getcwd(), "plugins")): for file in files: shutil.copy(os.path.join(root, file), os.path.join(bundle_dir, "plugins")) print("copy", file) break # do not copy __pycache__ else: bundle_dir = os.getcwd() plugins_dir = os.path.join(bundle_dir, "plugins") for root, dirs, files in os.walk(plugins_dir): for file in files: modulename, ext = os.path.splitext(file) if ext == ".py": module = import_module("plugins." + modulename) for name, klass in inspect.getmembers( module, inspect.isclass): if klass.__module__ == "plugins." + modulename: instance = klass() if isinstance(instance, GeneratorInterface): Plugins.addGeneratorPlugin(name, instance) instance.setTextEdit(self.text_edit) #instance.registerContenType() break # not to list __pycache__
# loop.exec_() # html = webview.page().mainFrame().toHtml() # tree = lxml.html.fromstring(html) # content = tree.cssselect('#result')[0].text # print(content) ## 新版本使用 # pip install PySide2==5.12.0 # pip install PyQt5==5.11.3 # try: # from PySide2.QtCore import QUrl # from PySide2.QtWidgets import QApplication # from PySide2.QtWebEngineWidgets import QWebEnginePage, QWebEngineView # except: from PyQt5.QtCore import QUrl from PyQt5.QtWidgets import QApplication from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineView # 弹窗显示 url = 'http://example.webscraping.com/places/default/search' app = QApplication([]) view = QWebEngineView() view.setWindowTitle("阿尔法科技有限公司") view.setMinimumWidth(1000) view.setMinimumHeight(800) view.load(QUrl(url)) view.show() app.exec() # 基于webkit的解决方案 # Ghost是Selenium 与PhantomJS 的结合体,安装时会携带PySide,PyQt4 # pip install Ghost==0.5.0
class POSM(QMainWindow): def __init__(self): super().__init__() self.setLocale(QLocale(QLocale.English)) self.initUI() self.setAttribute(Qt.WA_AlwaysShowToolTips) sizegrip = QtWidgets.QSizeGrip(self) self.layout.addWidget(sizegrip, 0, QtCore.Qt.AlignBottom | QtCore.Qt.AlignRight) self.record = [None] recordAction = QAction(datetime.datetime.now().strftime('%H:%M:%S'), self) recordAction.triggered.connect(lambda: self.changeMap(0)) self.recordMenu.addAction(recordAction) def initUI(self): self.layout = QHBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.console = InformationalConsole(app) self.horSplitter = QSplitter(Qt.Horizontal) self.horSplitter.setChildrenCollapsible(False) self.editionSplitter = QSplitter(Qt.Vertical) self.editionSplitter.setChildrenCollapsible(False) self.queryUI = QueryUI() self.queryUI.setOnRequestChanged(self.changeCurrentMap) self.editionSplitter.addWidget(self.queryUI) self.queryWidget = QWidget() self.queryWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.queryWidget.setLayout(QVBoxLayout()) self.queryWidget.layout().setContentsMargins(0, 0, 0, 0) self.queryWidget.layout().setSpacing(0) self.queryHeader = QLabel("Query") self.queryHeader.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.queryHeader.setFixedHeight(self.queryHeader.sizeHint().height() + 10) self.queryHeader.setContentsMargins(5, 5, 0, 5) self.queryWidget.layout().addWidget(self.queryHeader) self.queryText = CodeEditor() self.qlHighlighter = OverpassQLHighlighter(self.queryText.document()) self.queryText.setReadOnly(True) self.queryWidget.layout().addWidget(self.queryText) self.editionSplitter.addWidget(self.queryWidget) self.horSplitter.addWidget(self.editionSplitter) self.emptyMapPage = QWebEnginePage() self.emptyMapPage.setHtml(EMPTY_HTML) self.manualModePage = QWebEnginePage() soup = bs4.BeautifulSoup(EMPTY_HTML, features="html.parser") js = soup.new_tag("script") js.string = (MANUAL_MODE_JS_SCRIPT % (str([]))) soup.append(js) self.manualModePage.setHtml(str(soup)) self.mapRenderer = QWebEngineView() self.mapRenderer.setMinimumWidth(500) self.mapRenderer.setPage(self.emptyMapPage) self.consoleSplitter = QSplitter(Qt.Vertical) self.consoleSplitter.setChildrenCollapsible(False) self.consoleSplitter.addWidget(self.mapRenderer) self.consoleWidget = QWidget() self.consoleWidget.setLayout(QVBoxLayout()) self.consoleWidget.layout().setContentsMargins(0, 0, 0, 0) self.consoleWidget.layout().setSpacing(0) self.consoleHeader = QLabel("Console") self.consoleHeader.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.consoleHeader.setContentsMargins(5, 5, 0, 5) self.consoleWidget.layout().addWidget(self.consoleHeader) self.consoleWidget.layout().addWidget(self.console) self.consoleSplitter.addWidget(self.consoleWidget) self.horSplitter.addWidget(self.consoleSplitter) self.layout.addWidget(self.horSplitter) self.initMenuBar() centralWidget = QWidget(self) centralWidget.setLayout(self.layout) self.setCentralWidget(centralWidget) self.setWindowTitle('Python Open Street Map') def initMenuBar(self): menubar = self.menuBar() fileMenu = menubar.addMenu('File') openAct = QAction('Open netedit', self) openAct.triggered.connect(self.openNet) fileMenu.addAction(openAct) saveMenu = fileMenu.addMenu("Save") saveOutputAct = QAction('output', self) saveOutputAct.triggered.connect(self.saveNet) saveOutputAct.setShortcut('Ctrl+S') saveMenu.addAction(saveOutputAct) saveQueryAct = QAction('query', self) saveQueryAct.triggered.connect(self.saveQuery) saveQueryAct.setShortcut('Ctrl+Shift+S') saveMenu.addAction(saveQueryAct) saveInteractiveModeAct = QAction('interactive mode', self) saveInteractiveModeAct.triggered.connect(self.saveInteractiveQuery) saveMenu.addAction(saveInteractiveModeAct) openMenu = fileMenu.addMenu("Open") openQuery = QAction('query', self) openQuery.triggered.connect(self.openQuery) openQuery.setShortcut('Ctrl+O') openMenu.addAction(openQuery) openInteractiveMode = QAction('interactive mode', self) openInteractiveMode.triggered.connect(self.openInteractiveQuery) openMenu.addAction(openInteractiveMode) self.recordMenu = openMenu.addMenu("record") runMenu = menubar.addMenu('Run') playAct = QAction('Play', self) playAct.triggered.connect(self.playQuery) playAct.setShortcut('Ctrl+P') runMenu.addAction(playAct) playTableRowAct = QAction('Play row selection', self) playTableRowAct.triggered.connect(self.playTableRow) playTableRowAct.setShortcut('Ctrl+T') runMenu.addAction(playTableRowAct) self.requestMenu = menubar.addMenu('Request') addRequestAct = QAction('Add request', self) addRequestAct.triggered.connect(lambda b: self.addRequest()) addRequestAct.setShortcut('Ctrl+A') self.requestMenu.addAction(addRequestAct) templatesMenu = self.requestMenu.addMenu("Add template") addRoadAct = QAction('Roads', self) addRoadAct.triggered.connect(lambda: self.addTemplate([ OverpassFilter("highway", TagComparison.EQUAL, "", False, True), OverpassFilter("name", TagComparison.EQUAL, "", False, True), OverpassFilter("ref", TagComparison.EQUAL, "", False, True), OverpassFilter("maxspeed", TagComparison.AT_MOST, "120", False, False), OverpassFilter("lanes", TagComparison.EQUAL, "", False, True), OverpassFilter("oneway", TagComparison.EQUAL, "", False, True) ])) templatesMenu.addAction(addRoadAct) addMainRoadAct = QAction('Main roads', self) mainHighways = "^(motorway|trunk|primary|secondary|residential)(_link)?$" everythingButYes = "^(y(e([^s]|$|s.)|[^e]|$)|[^y]|$).*" addMainRoadAct.triggered.connect(lambda: self.addTemplate([ OverpassFilter("highway", TagComparison.EQUAL, mainHighways, False, False), OverpassFilter("construction", TagComparison.HAS_NOT_KEY, "", False, False), OverpassFilter("noexit", TagComparison.EQUAL, "yes", True, True), OverpassFilter("access", TagComparison.EQUAL, everythingButYes, True, False) ])) templatesMenu.addAction(addMainRoadAct) addParkingAct = QAction('Parking', self) addParkingAct.triggered.connect(lambda: self.addTemplate([ OverpassFilter("service", TagComparison.EQUAL, "parking", False, False), OverpassFilter("highway", TagComparison.HAS_KEY, "", False, True) ])) templatesMenu.addAction(addParkingAct) addPedestriansAct = QAction('Pedestrians', self) pedestrianHighway = [ "pedestrian", "footway", "path", "cycleway", "bridleway", "steps", "crossing" ] addPedestriansAct.triggered.connect(lambda: self.addTemplate([ OverpassFilter("highway", TagComparison.IS_ONE_OF, pedestrianHighway, False, True) ])) templatesMenu.addAction(addPedestriansAct) removeRequestAct = QAction('Remove current request', self) removeRequestAct.triggered.connect(self.removeRequest) removeRequestAct.setShortcut('Ctrl+R') self.requestMenu.addAction(removeRequestAct) self.manualModeAct = QAction( 'Switch between interactive and manual mode', self) self.manualModeAct.triggered.connect(self.switchManualMode) self.requestMenu.addAction(self.manualModeAct) self.manualModeMenu = menubar.addMenu('Manual mode') self.manualModeMenu.setEnabled(False) manualModeCleanPolygonAct = QAction('Clean polygon', self) manualModeCleanPolygonAct.triggered.connect( self.cleanManualModePolygon) self.manualModeMenu.addAction(manualModeCleanPolygonAct) manualModeGetPolygonAct = QAction('Polygon coordinates', self) manualModeGetPolygonAct.triggered.connect( lambda: self.manualModePage.runJavaScript( "getPolygons();", self.logManualModePolygonCoords)) self.manualModeMenu.addAction(manualModeGetPolygonAct) windowsMenu = menubar.addMenu('Windows') cleanMapAct = QAction('Clean map', self) cleanMapAct.triggered.connect(self.cleanMap) windowsMenu.addAction(cleanMapAct) self.showHideInteractiveModeAct = QAction('Interactive mode', self) self.showHideInteractiveModeAct.triggered.connect( self.showHideInteractiveMode) windowsMenu.addAction(self.showHideInteractiveModeAct) showHideConsole = QAction('Console', self) showHideConsole.triggered.connect(self.showHideConsole) windowsMenu.addAction(showHideConsole) showHideQuery = QAction('Query', self) showHideQuery.triggered.connect(self.showHideQuery) windowsMenu.addAction(showHideQuery) # ACTIONS def cleanMap(self): if self.queryText.isReadOnly(): if self.queryUI.getCurrentMap() is not None: self.mapRenderer.setPage(self.queryUI.updateMaps(EMPTY_HTML)) else: soup = bs4.BeautifulSoup(EMPTY_HTML, features="html.parser") js = soup.new_tag("script") js.string = (MANUAL_MODE_JS_SCRIPT % (str([]))) soup.append(js) self.manualModePage.setHtml(str(soup)) logging.info("Cleaning map") def changeMap(self, i): if i == 0: if not self.queryText.isReadOnly(): self.switchManualMode() if self.queryText.isReadOnly(): self.queryUI.reset() self.queryText.clear() self.mapRenderer.setPage(self.emptyMapPage) self.queryUI.updateMaps(EMPTY_HTML) elif self.record[i]["interactiveMode"]: if not self.queryText.isReadOnly(): self.switchManualMode() if self.queryText.isReadOnly(): self.queryUI.setQuery(self.record[i]["query"]) self.queryText.setPlainText(self.record[i]["query"].getQL()) self.mapRenderer.setPage( self.queryUI.updateMaps(self.record[i]["html"])) else: if self.queryText.isReadOnly(): self.switchManualMode() if not self.queryText.isReadOnly(): self.queryUI.reset() self.queryText.setPlainText(self.record[i]["query"]) self.manualModePage.setHtml(self.record[i]["html"]) self.mapRenderer.setPage(self.manualModePage) def logManualModePolygonCoords(self, coords): coordsString = " ".join([str(c) for point in coords for c in point]) logging.info("Polygon coordinates:\"{}\"".format(coordsString)) pyperclip.copy(coordsString) logging.debug("LINE") def cleanManualModePolygon(self): logging.info("Cleaning polygon.") self.manualModePage.runJavaScript( "cleanPolygon();", lambda returnValue: logging.debug("LINE")) def showHideInteractiveMode(self): if self.queryUI.isHidden(): if self.editionSplitter.isHidden(): self.editionSplitter.show() self.queryText.hide() self.queryUI.show() logging.info("Showing 'Interactive mode' window.") else: if self.queryText.isHidden(): self.editionSplitter.hide() self.queryUI.hide() logging.info("Hiding 'Interactive mode' window.") logging.debug("LINE") def showHideConsole(self): if self.console.isHidden(): self.console.show() logging.info("Showing 'Console' window.") self.consoleWidget.setMaximumHeight(QWIDGETSIZE_MAX) else: self.console.hide() self.consoleWidget.setMaximumHeight( self.queryHeader.sizeHint().height()) logging.info("Hiding 'Console' window.") logging.debug("LINE") def showHideQuery(self): if self.queryText.isHidden(): if self.editionSplitter.isHidden(): self.editionSplitter.show() self.queryUI.hide() self.queryText.show() logging.info("Showing 'Query' window.") self.queryWidget.setMaximumHeight(QWIDGETSIZE_MAX) else: if self.queryUI.isHidden(): self.editionSplitter.hide() self.queryText.hide() self.queryWidget.setMaximumHeight( self.queryHeader.sizeHint().height()) logging.info("Hiding 'Query' window.") logging.debug("LINE") def switchManualMode(self): if self.queryText.isReadOnly(): reply = QMessageBox.question( self, "Manual mode", "Are you sure?\nThe interactive mode will remain as it is now." ) if reply == QMessageBox.Yes: self.queryText.setReadOnly(False) self.queryUI.hide() for action in self.requestMenu.actions(): action.setEnabled(False) self.manualModeAct.setEnabled(True) self.manualModeMenu.setEnabled(True) self.showHideInteractiveModeAct.setEnabled(False) self.mapRenderer.setPage(self.manualModePage) logging.info("Switching to manual mode.") else: logging.info( "'Switch between interactive and manual mode' cancelled.") else: reply = QMessageBox.question( self, "Interactive mode", "Are you sure?\nThe current query will be removed.") if reply == QMessageBox.Yes: try: self.queryText.clear() self.queryText.setPlainText( self.queryUI.getQuery().getQL()) except BadFilterAttributes as e: logging.error(str(e)) except RuntimeError: logging.warning("Failed to write query.") self.queryText.clear() self.queryText.setPlainText("") self.queryText.setReadOnly(True) self.queryUI.show() for action in self.requestMenu.actions(): action.setEnabled(True) self.manualModeMenu.setEnabled(False) self.showHideInteractiveModeAct.setEnabled(True) self.changeCurrentMap(0) logging.info("Switching to interactive mode.") else: logging.info( "'Switch between interactive and manual mode' cancelled.") logging.info("Showing 'manual mode' polygon.") def addRequest(self, filters=None): self.queryUI.addRequestByFilters(filters) logging.info("Request added.") logging.debug("LINE") def addTemplate(self, filters): logging.info("Template applied.") self.queryUI.addRequestByFilters(filters) def removeRequest(self): reply = QMessageBox.question( self, "Remove current request", "Are you sure? This option is not undoable.") if reply == QMessageBox.Yes: self.queryUI.removeRequest() logging.info("'Remove request' successfully executed.") else: logging.info("'Remove request' cancelled.") logging.debug("LINE") def saveQuery(self): filename, selectedFilter = QFileDialog.getSaveFileName( self, 'Save query', expanduser("~/filename.txt"), "Text files (*.txt)") if filename != "": if self.queryText.isReadOnly(): try: query = self.queryUI.getQuery().getQL() f = open(filename, "w+") f.seek(0) f.truncate() f.write(query) f.close() logging.info("Query saved successfully.") except (RuntimeError, BadFilterAttributes) as e: logging.error(str(e)) except OSError: logging.error( "There was a problem creating the file with the query." ) else: try: f = open(filename, "w+") f.seek(0) f.truncate() f.write(self.queryText.toPlainText()) f.close() logging.info("Query saved successfully.") except OSError: logging.error( "There was a problem creating the file with the query." ) else: logging.info("\"Save query\" canceled.") logging.debug("LINE") def openQuery(self): filename, selectedFilter = QFileDialog.getOpenFileName( self, 'Open query', expanduser("~/filename.txt")) if filename != "": try: if self.queryText.isReadOnly(): self.switchManualMode() f = open(filename, "r") self.queryText.clear() self.queryText.setPlainText(f.read()) f.close() logging.info("File read successfully.") except UnicodeDecodeError: logging.error("The given file is not readable as text.") except OSError: logging.error("There was a problem opening the query file.") else: logging.info("\"Open query\" canceled.") logging.debug("LINE") def saveInteractiveQuery(self): filename, selectedFilter = QFileDialog.getSaveFileName( self, 'Save query', expanduser("~/filename.json"), "JSON files (*.json)") if filename != "": try: query = self.queryUI.getQuery() query.saveToFile(filename) logging.info("Query saved successfully.") except (RuntimeError, BadFilterAttributes) as e: logging.error(str(e)) except OSError: logging.error( "There was a problem creating the file with the query.") else: logging.info("\"Save query\" canceled.") logging.debug("LINE") def openInteractiveQuery(self): filename, selectedFilter = QFileDialog.getOpenFileName( self, 'Open query', expanduser("~/filename.json")) if filename != "": try: self.queryUI.setQuery(OverpassQuery.getFromFile(filename)) if not self.queryText.isReadOnly(): self.switchManualMode() except json.decoder.JSONDecodeError: logging.error( "The given file has not the right format (json). The file could not be opened." ) except UnicodeDecodeError: logging.error( "The given file is not readable as text. The file could not be opened." ) except (TypeError, KeyError): logging.error( "Fields are missing from the file or there are fields with the wrong data type. " "The file could not be opened.") except OSError: logging.error( "There was a problem opening the query file. The file could not be opened." ) else: logging.info("\"Open query\" canceled.") logging.debug("LINE") def saveNet(self): filename, selectedFilter = QFileDialog.getSaveFileName( self, 'Save File', expanduser("~/filenameWithoutExtension")) if filename != "": buildNet(filename) else: logging.info("\"Save File\" canceled.") logging.debug("LINE") return filename def openNet(self): try: filename = self.saveNet() if filename == "": logging.error("Can't open NETEDIT without a file.") else: openNetedit(filename + ".net.xml") logging.info("Opening NETEDIT.") logging.warning( "If NETEDIT is not open in ten seconds, there was an unhandled problem." ) logging.debug("LINE") except OSError: logging.error("Can't find NETEDIT.") except Exception: logging.error(traceback.format_exc()) # POLYGONS def changeCurrentMap(self, i): if self.queryUI.getCurrentMap() is None: self.mapRenderer.setPage(self.emptyMapPage) else: self.mapRenderer.setPage(self.queryUI.getCurrentMap()) def playQuery(self): newRecord = { "interactiveMode": self.queryText.isReadOnly(), "query": self.queryText.toPlainText(), "html": "" } if self.queryText.isReadOnly(): try: query = self.queryUI.getQuery() newRecord["query"] = query self.queryText.setPlainText(query.getQL()) except (RuntimeError, BadFilterAttributes) as e: logging.error(str(e)) return try: html = buildHTMLWithQuery(self.queryText.toPlainText()) if self.queryText.isReadOnly(): self.mapRenderer.setPage(self.queryUI.updateMaps(html)) newRecord["html"] = html else: soup = bs4.BeautifulSoup(html, features="html.parser") js = soup.new_tag("script") js.string = (MANUAL_MODE_JS_SCRIPT % (str([]))) soup.append(js) self.manualModePage.setHtml(str(soup)) newRecord["html"] = str(soup) self.mapRenderer.setPage(self.manualModePage) logging.info("Query drawn.") logging.debug("LINE") self.addRecord(newRecord) except (OverpassRequestException, OsmnxException) as e: logging.error(str(e)) except ox.EmptyOverpassResponse: logging.error("There are no elements with the given query.") except OSError: logging.error( "There was a problem creating the file with the request response." ) except Exception: logging.error(traceback.format_exc()) def addRecord(self, newRecord): self.record.append(newRecord) index = len(self.record) - 1 recordAction = QAction(datetime.datetime.now().strftime('%H:%M:%S'), self) recordAction.triggered.connect(lambda: self.changeMap(index)) self.recordMenu.addAction(recordAction) def playTableRow(self): try: self.mapRenderer.setPage(self.queryUI.updateMapFromRow()) except (OverpassRequestException, OsmnxException) as e: logging.error(str(e)) logging.warning( "Before open NETEDIT you must run a query with the row filters applied." ) except ox.EmptyOverpassResponse: logging.error("There are no elements with the given row.") except OSError: logging.error( "There was a problem creating the file with the row selection." ) except RuntimeError as e: logging.error(str(e)) except Exception: logging.error(traceback.format_exc()) logging.debug("LINE") # EVENTS def closeEvent(self, event): for f in os.listdir(tempDir): os.remove(os.path.join(tempDir, f)) QMainWindow.closeEvent(self, event)
class MainWindow(QWidget): def __init__(self): super().__init__() # Init a state dict self.state = { "internet": { "connected": False, }, "serial": { "client": serial.Serial(), "connected": False, "device": "", "baud": 0, }, "mqtt": { "client": mqtt.Client(), "connected": False, "hostname": "", "port": "", "username": "", } } # Grab config self.config = configparser.ConfigParser() self.config.read('config.ini') # Setup MQTT Callbacks self.state["mqtt"]["client"].on_connect = self.mqtt_on_connect self.state["mqtt"]["client"].on_disconnect = self.mqtt_on_disconnect self.state["mqtt"]["client"].on_subscribe = self.mqtt_on_subscribe self.state["mqtt"]["client"].on_unsubscribe = self.mqtt_on_unsubscribe self.state["mqtt"]["client"].on_publish = self.mqtt_on_publish self.state["mqtt"]["client"].on_message = self.mqtt_on_message self.state["mqtt"]["client"].on_log = self.mqtt_on_log # Setup support things self.create_icons() # Setup main window self.layout_main_window = QGridLayout() self.setWindowTitle("Icarus GCS") self.setWindowIcon(QIcon('assets/balloon_map_icon.png')) self.create_toolbar_interface() self.create_map_interface() self.create_serial_interface() self.create_mqtt_interface() self.layout_main_window.addWidget(self.toolbar_status, 0, 1, 1, -1) self.layout_main_window.addWidget(self.webengine_map, 1, 1, -1, -1) self.layout_main_window.addWidget(self.groupbox_serial_interface, 0, 0, 2, 1) self.layout_main_window.addWidget(self.groupbox_mqtt_interface, 2, 0) self.setLayout(self.layout_main_window) # Setup timers for events self.timer_internet_status = QTimer(self) self.timer_internet_status.timeout.connect( self.on_timer_internet_status) self.timer_internet_status.start(5 * 1000) # Check timed events once at the start # self.on_timer_internet_status() # Let's do this! self.show() def create_toolbar_interface(self): self.toolbar_status = QToolBar() self.toolbar_status.setFloatable(False) self.toolbar_status.setMovable(False) self.toolbar_status.setIconSize(QSize(24, 24)) self.toolbutton_internet_status = QToolButton() self.toolbutton_internet_status.setIcon(self.icons["cloud-line"]) self.toolbutton_internet_status.setEnabled(False) self.toolbutton_mqtt_status = QToolButton() self.toolbutton_mqtt_status.setIcon(self.icons["server-line"]) self.toolbutton_mqtt_status.setEnabled(False) self.toolbutton_radio_status = QToolButton() self.toolbutton_radio_status.setIcon(self.icons["wifi-line"]) self.toolbutton_radio_status.setEnabled(False) self.toolbutton_cellular_status = QToolButton() self.toolbutton_cellular_status.setIcon(self.icons["sim-card-line"]) self.toolbutton_cellular_status.setEnabled(False) self.toolbutton_heartbeat_status = QToolButton() self.toolbutton_heartbeat_status.setIcon(self.icons["heart-line"]) self.toolbutton_heartbeat_status.setEnabled(False) self.toolbar_status.addWidget(self.toolbutton_internet_status) self.toolbar_status.addWidget(self.toolbutton_mqtt_status) self.toolbar_status.addWidget(self.toolbutton_radio_status) self.toolbar_status.addWidget(self.toolbutton_cellular_status) self.toolbar_status.addWidget(self.toolbutton_heartbeat_status) def create_map_interface(self): self.webengine_map = QWebEngineView() self.map_wrapper = MapWrapper( self.webengine_map, { "home_latitude": self.config["Map"]["Home Latitude"], "home_longitude": self.config["Map"]["Home Longitude"], "home_zoom": self.config["Map"]["Zoom Level"], "max_zoom": self.config["Map"]["Max Zoom Level"], "min_zoom": self.config["Map"]["Min Zoom Level"], }) self.map_view_webchannel = QWebChannel() self.map_view_webchannel.registerObject("python_link", self.map_wrapper) self.webengine_map.page().setWebChannel(self.map_view_webchannel) self.webengine_map.load( QtCore.QUrl.fromLocalFile( QtCore.QDir.current().filePath("static/map.html"))) self.webengine_map.setMinimumWidth(1024) self.webengine_map.setMinimumHeight(768) def create_serial_interface(self): self.groupbox_serial_interface = QGroupBox("Serial") self.groupbox_serial_interface.setMinimumWidth(320) self.groupbox_serial_interface.setMaximumWidth(320) self.layout_serial_interface = QGridLayout() self.groupbox_serial_interface.setLayout(self.layout_serial_interface) self.label_serial_port = QLabel(self) self.label_serial_port.setText("Device:") self.label_serial_port.setMinimumWidth(100) self.label_serial_port.setMaximumWidth(100) self.combo_serial_port = QComboBox(self) devices = self.serial_list_ports() for device in devices: self.combo_serial_port.addItem(device) self.label_serial_baud_rate = QLabel(self) self.label_serial_baud_rate.setText("Baud Rate:") self.line_edit_serial_baud_rate = QLineEdit(self) self.line_edit_serial_baud_rate.setText("115200") self.button_serial_connect = QPushButton(self) self.button_serial_connect.setText("Connect") self.button_serial_connect.clicked.connect( self.button_serial_connect_clicked) self.layout_serial_interface.addWidget(self.label_serial_port, 0, 0) self.layout_serial_interface.addWidget(self.combo_serial_port, 0, 1) self.layout_serial_interface.addWidget(self.label_serial_baud_rate, 1, 0) self.layout_serial_interface.addWidget(self.line_edit_serial_baud_rate, 1, 1) self.layout_serial_interface.addWidget(self.button_serial_connect, 2, 0, 1, 2) def lock_serial_interface(self): self.combo_serial_port.setReadOnly(True) self.line_edit_serial_baud_rate.setReadOnly(True) self.button_serial_connect.setText("Disconnect") def unlock_serial_interface(self): self.combo_serial_port.setReadOnly(False) self.line_edit_serial_baud_rate.setReadOnly(False) self.button_serial_connect.setText("Connect") def serial_list_ports(self): ports = list(serial.tools.list_ports.comports(False)) devices = [] for port in ports: devices.append(str(port.description) + " (" + port.device + ")") return devices def button_serial_connect_clicked(self): if not self.state["serial"]["connected"]: selected_serial_port = self.combo_serial_port.currentText().split( "(")[1].replace(")", "") try: self.state["serial"]["client"].port = selected_serial_port self.state["serial"]["client"].baudrate = int( self.line_edit_serial_baud_rate.text()) self.state["serial"]["client"].open() self.state["serial"]["connected"] = True self.state["serial"]["device"] = selected_serial_port self.state["serial"][ "baud"] = self.line_edit_serial_baud_rate.text() self.lock_serial_interface() print("Connected to: " + self.combo_serial_port.currentText()) except serial.SerialException as e: print("Failed to connect to Serial Port: " + self.combo_serial_port.currentText()) print(e) else: try: self.serial_port.close() self.state["serial"]["connected"] = False self.state["serial"]["device"] = "" self.state["serial"]["baud"] = 0 self.unlock_serial_interface() print("Disconnected from: " + self.state["serial"]["device"]) except serial.SerialException as e: print("Failed to disconnect from: " + self.state["serial"]["device"]) print(e) def create_mqtt_interface(self): self.groupbox_mqtt_interface = QGroupBox("MQTT") self.groupbox_mqtt_interface.setMinimumWidth(320) self.groupbox_mqtt_interface.setMaximumWidth(320) self.layout_mqtt_interface = QGridLayout() self.groupbox_mqtt_interface.setLayout(self.layout_mqtt_interface) self.label_internet_status = QLabel(self) self.label_internet_status.setText("Internet:") self.label_internet_status.setMinimumWidth(100) self.label_internet_status.setMaximumWidth(100) self.line_edit_internet_status = QLineEdit(self) self.line_edit_internet_status.setText("Disconnected") self.line_edit_internet_status.setReadOnly(True) self.label_mqtt_hostname = QLabel(self) self.label_mqtt_hostname.setText("Hostname:") self.line_edit_mqtt_hostname = QLineEdit(self) self.line_edit_mqtt_hostname.setText(self.config["MQTT"]["Hostname"]) self.label_mqtt_port = QLabel(self) self.label_mqtt_port.setText("Port: ") self.line_edit_mqtt_port = QLineEdit(self) self.line_edit_mqtt_port.setText(self.config["MQTT"]["Port"]) self.label_mqtt_username = QLabel(self) self.label_mqtt_username.setText("Username: "******"MQTT"]["Username"]) self.label_mqtt_password = QLabel(self) self.label_mqtt_password.setText("Password: "******"MQTT"]["Password"]) self.line_edit_mqtt_password.setEchoMode(QLineEdit.Password) self.button_mqtt_connect = QPushButton(self) self.button_mqtt_connect.setText("Connect") self.button_mqtt_connect.clicked.connect( self.button_mqtt_connect_clicked) self.label_mqtt_topic = QLabel(self) self.label_mqtt_topic.setText("Topic:") self.line_edit_mqtt_topic = QLineEdit(self) self.button_mqtt_subscribe = QPushButton(self) self.button_mqtt_subscribe.setText("Subscribe") #self.button_mqtt_subscribe.clicked.connect(self.button_mqtt_subscribe_clicked) self.list_box_mqtt_subscriptions = QListWidget(self) self.list_box_mqtt_subscriptions.setMaximumHeight(100) self.button_mqtt_unsubscribe = QPushButton(self) self.button_mqtt_unsubscribe.setText("Unsubscribe") #self.button_mqtt_unsubscribe.clicked.connect(self.button_mqtt_unsubscribe_clicked) self.layout_mqtt_interface.addWidget(self.label_internet_status, 0, 0) self.layout_mqtt_interface.addWidget(self.line_edit_internet_status, 0, 1) self.layout_mqtt_interface.addWidget(self.label_mqtt_hostname, 1, 0) self.layout_mqtt_interface.addWidget(self.line_edit_mqtt_hostname, 1, 1) self.layout_mqtt_interface.addWidget(self.label_mqtt_port, 2, 0) self.layout_mqtt_interface.addWidget(self.line_edit_mqtt_port, 2, 1) self.layout_mqtt_interface.addWidget(self.label_mqtt_username, 3, 0) self.layout_mqtt_interface.addWidget(self.line_edit_mqtt_username, 3, 1) self.layout_mqtt_interface.addWidget(self.label_mqtt_password, 4, 0) self.layout_mqtt_interface.addWidget(self.line_edit_mqtt_password, 4, 1) self.layout_mqtt_interface.addWidget(self.button_mqtt_connect, 5, 0, 1, 2) self.layout_mqtt_interface.addWidget(self.label_mqtt_topic, 6, 0) self.layout_mqtt_interface.addWidget(self.line_edit_mqtt_topic, 6, 1) self.layout_mqtt_interface.addWidget(self.button_mqtt_subscribe, 7, 0, 1, 2) self.layout_mqtt_interface.addWidget(self.list_box_mqtt_subscriptions, 8, 0, 1, 2) self.layout_mqtt_interface.addWidget(self.button_mqtt_unsubscribe, 9, 0, 1, 2) def lock_mqtt_interface(self): self.line_edit_mqtt_hostname.setReadOnly(True) self.line_edit_mqtt_port.setReadOnly(True) self.line_edit_mqtt_username.setReadOnly(True) self.line_edit_mqtt_password.setReadOnly(True) self.button_mqtt_connect.setText("Disconnect") def unlock_mqtt_interface(self): self.line_edit_mqtt_hostname.setReadOnly(False) self.line_edit_mqtt_port.setReadOnly(False) self.line_edit_mqtt_username.setReadOnly(False) self.line_edit_mqtt_password.setReadOnly(False) self.button_mqtt_connect.setText("Connect") def button_mqtt_connect_clicked(self): if not self.state["mqtt"]["connected"]: self.state["mqtt"]["client"].username_pw_set( self.line_edit_mqtt_username.text(), self.line_edit_mqtt_password.text()) self.state["mqtt"]["client"].connect( self.line_edit_mqtt_hostname.text(), int(self.line_edit_mqtt_port.text()), 30) self.state["mqtt"]["client"].loop_start() else: self.state["mqtt"]["client"].loop_stop() self.state["mqtt"]["client"].disconnect() def create_chart_interface(self): fig = Figure(figsize=(100, 100), dpi=100) self.axes = fig.add_subplot(111) fig.axes.plot([0, 1, 2, 3, 4], [10, 1, 20, 3, 40]) self.wid self.layout_chart_interface = QGridLayout() def on_timer_internet_status(self): try: socket.create_connection(("1.1.1.1", 53)) self.state["internet"]["connected"] = True self.line_edit_internet_status.setText("Connected") self.toolbutton_internet_status.setIcon(self.icons["cloud"]) return True except OSError: pass self.state["internet"]["connected"] = False self.line_edit_internet_status.setText("Disconnected") self.toolbutton_internet_status.setIcon(self.icons["cloud-line"]) return False def mqtt_on_connect(self, client, userdata, flags, return_code): if return_code == 0: print("Connected to MQTT broker at: " + self.line_edit_mqtt_hostname.text()) self.state["mqtt"]["connected"] = True self.state["mqtt"]["hostname"] = self.line_edit_mqtt_hostname.text( ) self.state["mqtt"]["port"] = self.line_edit_mqtt_port.text() self.state["mqtt"]["username"] = self.line_edit_mqtt_username.text( ) self.toolbutton_mqtt_status.setIcon(self.icons["server"]) self.lock_mqtt_interface() else: print("Failed to connect to MQTT broker at: " + self.line_edit_mqtt_hostname.text()) def mqtt_on_disconnect(self, client, userdata, return_code): if return_code == 0: print("Disconnected from MQTT broker") else: print( "Failed to cleanly disconnect from MQTT broker, already disconnected" ) self.state["mqtt"]["connected"] = False self.state["mqtt"]["hostname"] = "" self.state["mqtt"]["port"] = "" self.state["mqtt"]["username"] = "" self.toolbutton_mqtt_status.setIcon(self.icons["server-line"]) self.unlock_mqtt_interface() def mqtt_on_subscribe(self, client, userdata, message_id, granted_qos): print("test") def mqtt_on_unsubscribe(self, client, userdata, message_id): print("test") def mqtt_on_publish(self, client, userdata, message_id): print("test") def mqtt_on_message(self, client, userdata, message): print("test") def mqtt_on_log(self, client, userdata, level, string): print(level + ", " + string) def create_icons(self): self.icons = {} icon_path = "assets/remix/icons" self.icons["cloud"] = QIcon(icon_path + "/Business/cloud-fill.svg") self.icons["cloud-line"] = QIcon(icon_path + "/Business/cloud-line.svg") self.icons["cloud-off"] = QIcon(icon_path + "/Business/cloud-off-fill.svg") self.icons["wifi"] = QIcon(icon_path + "/Device/signal-wifi-fill.svg") self.icons["wifi-line"] = QIcon(icon_path + "/Device/signal-wifi-line.svg") self.icons["server"] = QIcon(icon_path + "/Device/server-fill.svg") self.icons["server-line"] = QIcon(icon_path + "/Device/server-line.svg") self.icons["wifi-off"] = QIcon(icon_path + "/Device/signal-wifi-off-fill.svg") self.icons["radio"] = QIcon(icon_path + "/Device/wifi-fill.svg") self.icons["radio-off"] = QIcon(icon_path + "/Device/wifi-off-fill.svg") self.icons["sim-card"] = QIcon(icon_path + "/Device/sim-card-2-fill.svg") self.icons["sim-card-line"] = QIcon(icon_path + "/Device/sim-card-2-line.svg") self.icons["takeoff"] = QIcon(icon_path + "/Map/flight-takeoff-fill.svg") self.icons["land"] = QIcon(icon_path + "/Map/flight-land-fill.svg") self.icons["heart"] = QIcon(icon_path + "/Health/heart-3-fill.svg") self.icons["heart-line"] = QIcon(icon_path + "/Health/heart-3-line.svg") self.icons["heart-off"] = QIcon(icon_path + "/Health/dislike-fill.svg") self.icons["heart-pulse"] = QIcon(icon_path + "/Health/heart-pulse-fill.svg")
class EmailViewerPageView(QWidget): def __init__(self, parent=None): super().__init__(parent) self.c = EmailViewerPageController() self.c.on_viewemail.connect(self.update_content) self.c.on_clearview.connect(self.clear_content) layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) self.splitter = QSplitter() self._web_engine = QWebEngineView(self) self._web_engine.setMinimumWidth(330) self._web_engine.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Expanding) self.email_page = self._web_engine.page() self.splitter.addWidget(self._web_engine) attachment_model = AttachmentListModel() self.attachments = AttachmentsView(self) self.attachments.setMaximumWidth(300) self.attachments.set_model(attachment_model) self._attachments_collapsed = False self.splitter.addWidget(self.attachments) layout.addWidget(self.splitter) self.setLayout(layout) def resizeEvent(self, event): super().resizeEvent(event) # TODO: Fix the splitter not resizing when _web_engine gets smaller than minimumSize. # Inspect sizes() for that, check if _web_engines size is less than minimumSize and # somehow update the splitter's handle, it just has to be touched in order for it to resize # to the proper size. # !!! NOTE: First call to sizes returns [0, 0]. page_size, attachments_size = self.splitter.sizes() if page_size == 0 and attachments_size == 0: return elif page_size < self._web_engine.minimumWidth( ) and not self._attachments_collapsed: self.splitter.setSizes((self.splitter.width(), 0)) self._attachments_collapsed = True elif self._attachments_collapsed: splitter_width = self.splitter.width() size1 = self._web_engine.minimumWidth() size2 = self.attachments.sizeHint().width() if splitter_width > size1 + size2: self.splitter.setSizes((splitter_width - size2, size2)) self._attachments_collapsed = False def update_content(self, body, attachments, error=None): if error: err_dialog = ErrorReportDialog(error) err_dialog.exec_() return self.attachments.clear_attachments() self.email_page.runJavaScript( f'document.open(); document.write(""); document.write(`{body}`); document.close();' ) self.attachments.append_attachments(attachments) def clear_content(self, flag): self.attachments.clear_attachments() self.email_page.runJavaScript( 'document.open(); document.write(""); document.close();')
class MainWindow(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.cur_file = "" self.splitter = QSplitter() self.text_edit = QTextEdit("") self.preview = QWebEngineView() self.preview.setMinimumWidth(300) self.setWindowTitle("Markdown [*]") self.splitter.addWidget(self.text_edit) self.splitter.addWidget(self.preview) self.setCentralWidget(self.splitter) self.createMenus() self.createStatusBar() self.readSettings() self.text_edit.document().contentsChanged.connect( self.documentWasModified) self.text_edit.textChanged.connect(self.textChanged) def closeEvent(self, event): if self.maybeSave(): self.writeSettings() event.accept() else: event.ignore() def documentWasModified(self): self.setWindowModified(self.text_edit.document().isModified()) def createMenus(self): new_icon = QIcon("./assets/new.png") open_icon = QIcon("./assets/open.png") save_icon = QIcon("./assets/save.png") save_as_icon = QIcon("./assets/save_as.png") exit_icon = QIcon("./assets/exit.png") new_act = QAction(new_icon, "&New", self) new_act.setShortcuts(QKeySequence.New) new_act.setStatusTip("Create a new file") new_act.triggered.connect(self.newFile) open_act = QAction(open_icon, "&Open", self) open_act.setShortcuts(QKeySequence.Open) open_act.setStatusTip("Open an existing file") open_act.triggered.connect(self.open) save_act = QAction(save_icon, "&Save", self) save_act.setShortcuts(QKeySequence.Save) save_act.setStatusTip("Save the document to disk") save_act.triggered.connect(self.save) save_as_act = QAction(save_as_icon, "Save &As...", self) save_as_act.setShortcuts(QKeySequence.SaveAs) save_as_act.setStatusTip("Save the document under a new name") save_as_act.triggered.connect(self.saveAs) exit_act = QAction(exit_icon, "E&xit", self) exit_act.setShortcuts(QKeySequence.Quit) exit_act.setStatusTip("Exit the application") exit_act.triggered.connect(self.close) about_act = QAction("&About", self) about_act.triggered.connect(self.about) about_act.setStatusTip("Show the application's About box") file_menu = self.menuBar().addMenu("&File") file_menu.addAction(new_act) file_menu.addAction(open_act) file_menu.addAction(save_act) file_menu.addAction(save_as_act) file_menu.addSeparator() file_menu.addAction(exit_act) help_menu = self.menuBar().addMenu("&Help") help_menu.addAction(about_act) file_tool_bar = self.addToolBar("File") file_tool_bar.addAction(new_act) file_tool_bar.addAction(open_act) file_tool_bar.addAction(save_act) def createStatusBar(self): self.statusBar().showMessage("Ready") def about(self): QMessageBox.about( self, "About Markdown", "This app demonstrates how to " "write modern GUI applications using Qt, with a menu bar, " "toolbars, and a status bar.") def newFile(self): if self.maybeSave(): self.text_edit.clear() self.setCurrentFile("") def open(self): if self.maybeSave(): fileName = QFileDialog.getOpenFileName(self)[0] if fileName: self.loadFile(fileName) def save(self): if not self.cur_file: return self.saveAs() else: return self.saveFile(self.cur_file) def saveAs(self): dialog = QFileDialog(self) dialog.setWindowModality(Qt.WindowModal) dialog.setAcceptMode(QFileDialog.AcceptSave) if dialog.exec() != QDialog.Accepted: return False return self.saveFile(dialog.selectedFiles()[0]) def maybeSave(self): if not self.text_edit.document().isModified(): return True ret = QMessageBox.warning( self, "Qt Demo", "The document has been modified.\n" "Do you want to save your changes?", QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) if ret == QMessageBox.Save: return self.save() elif ret == QMessageBox.Cancel: return False return True def loadFile(self, fileName): with open(fileName, mode="r") as f: text = f.read() QApplication.setOverrideCursor(Qt.WaitCursor) self.setCurrentFile(fileName) self.text_edit.setPlainText(text) self.text_edit.document().setModified(False) self.setWindowModified(False) QApplication.restoreOverrideCursor() self.statusBar().showMessage("File loaded", 2000) def saveFile(self, fileName): QApplication.setOverrideCursor(Qt.WaitCursor) with open(fileName, "w") as f: f.write(self.text_edit.toPlainText()) QApplication.restoreOverrideCursor() self.setCurrentFile(fileName) self.text_edit.document().setModified(False) self.setWindowModified(False) self.statusBar().showMessage("File saved", 2000) def setCurrentFile(self, fileName): self.cur_file = fileName shown_name = self.cur_file if not self.cur_file: shown_name = "untitled.txt" self.setWindowFilePath(shown_name) def writeSettings(self): settings = QSettings(QCoreApplication.organizationName(), QCoreApplication.applicationName()) settings.setValue("geometry", self.saveGeometry()) def readSettings(self): settings = QSettings(QCoreApplication.organizationName(), QCoreApplication.applicationName()) geometry = settings.value("geometry", QByteArray()) if not geometry: availableGeometry = QApplication.desktop().availableGeometry(self) self.resize(availableGeometry.width() / 3, availableGeometry.height() / 2) self.move((availableGeometry.width() - self.width()) / 2, (availableGeometry.height() - self.height()) / 2) else: self.restoreGeometry(geometry) def textChanged(self): path = os.getcwd() html = "<html><head><link href=\"assets/pastie.css\" rel=\"stylesheet\" type=\"text/css\"/></head><body>" html += markdown2.markdown(self.text_edit.toPlainText(), ..., extras=["fenced-code-blocks"]) html += "</body></html>" self.preview.setHtml(html, baseUrl=QUrl("file://" + path + "/"))
class App(QWidget): # 初期化 def __init__(self): self.connection_pool = mysql.connector.pooling.MySQLConnectionPool( pool_name="pynative_pool", pool_size=1, pool_reset_session=True, host='news-recorder.cnfoeuugi3ju.ap-northeast-1.rds.amazonaws.com', database='newsrecorder', user='******', password=os.environ['NEWS_PASS'], ssl_ca='rds-combined-ca-bundle.pem', charset='utf8') super().__init__() self.browser = QWebEngineView() self.model = QStandardItemModel() self.listWidget = QListView() self.init_ui() # ユーザーインターフェイスを初期化する def init_ui(self): self.listWidget.setModel(self.model) self.listWidget.setAlternatingRowColors(True) self.listWidget.setEditTriggers(QAbstractItemView.NoEditTriggers) self.listWidget.setSelectionMode(QAbstractItemView.SingleSelection) self.listWidget.selectionModel().selectionChanged.connect( self.load_html) self.listWidget.setItemDelegate(ItemDelegate(self.listWidget)) grid = QGridLayout() grid.setSpacing(10) splitter = QSplitter(Qt.Horizontal) splitter.addWidget(self.listWidget) splitter.addWidget(self.browser) grid.addWidget(splitter, 1, 0) self.init_list_items() self.setLayout(grid) self.resize(1200, 800) self.listWidget.setMinimumWidth(600) self.browser.setMinimumWidth(600) self.show() # 記事本文をデータベースから取り出す(URLの) def fetch_contents(self, url): with closing(self.connection_pool.get_connection()) as conn: c = conn.cursor() c.execute("SELECT contents FROM articles WHERE url=%s;", [url]) rows = c.fetchall() for row in rows: return row[0] # ブラウザに記事をセットする def load_html(self): for i in self.listWidget.selectedIndexes(): html = '<h1>' + i.data()[1] + '</h1>' + self.fetch_contents( i.data()[0]) self.browser.setHtml(html) # リストを初期化する def init_list_items(self): with closing(self.connection_pool.get_connection()) as conn: c = conn.cursor() c.execute("SELECT url,title FROM articles;") rows = c.fetchall() for row in rows: item = QStandardItem() item.setData(row, Qt.DisplayRole) item.setSizeHint(QSize(20, 30)) self.model.appendRow(item)