def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.setWindowTitle("My Awesome App") label = QLabel("THIS IS AWESOME!!!") label.setAlignment(Qt.AlignCenter) self.setCentralWidget(label) toolbar = QToolBar("My main toolbar") self.addToolBar(toolbar) # Andriod Button button_action = QAction(QIcon("android.png"), "Your button", self) button_action.setStatusTip('This is your button') button_action.triggered.connect(self.onMyToolBarButtonClick) button_action.setCheckable(True) toolbar.addAction(button_action) toolbar.addSeparator() #Second Button button_action2 = QAction(QIcon('android.png'), 'Your button2', self) button_action2.setStatusTip('This is your button 2') button_action2.triggered.connect(self.onMyToolBarButtonClick) button_action2.setCheckable(True) toolbar.addAction(button_action2) #CheckBox toolbar.addWidget(QLabel('Hello')) toolbar.addWidget(QCheckBox()) self.setStatusBar(QStatusBar(self))
def toolbar(self) -> QToolBar: toolbar = QToolBar(self) toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) label = QLabel("Blend:") toolbar.addWidget(label) blend_combo_box = QComboBox() modes_list = [ o for o in getmembers(QPainter) if o[0].startswith('CompositionMode_') ] blend_combo_box.addItems( [f[0].replace('CompositionMode_', '') for f in modes_list]) blend_combo_box.setCurrentText('Screen') blend_combo_box.currentTextChanged.connect( self._blend_current_text_changed) toolbar.addWidget(blend_combo_box) toolbar.addSeparator() show_scale_bar_action = QAction(QIcon(":/icons/icons8-ruler-16.png"), "Scale Bar", self) show_scale_bar_action.triggered.connect(self._show_scale_bar) show_scale_bar_action.setCheckable(True) toolbar.addAction(show_scale_bar_action) show_mask_action = QAction(QIcon(":/icons/icons8-ruler-16.png"), "Mask", self) show_mask_action.triggered.connect(self._show_mask) show_mask_action.setCheckable(True) toolbar.addAction(show_mask_action) return toolbar
def __init__(self, *args, **kwargs): super(MainWindow01, self).__init__(*args, **kwargs) self.setWindowTitle("My Awesome App") label = QLabel("THIS IS AWESOME!!!") label.setAlignment(Qt.AlignCenter) self.setCentralWidget(label) toolbar = QToolBar("My main toolbar") self.addToolBar(toolbar) button_action = QAction(QIcon("rocket.png"), "Your button", self) button_action.setStatusTip("This is your button") button_action.triggered.connect(self.onMyToolBarButtonClick) button_action.setCheckable(True) toolbar.addAction(button_action) toolbar.addSeparator() button_action2 = QAction("Your button2", self) button_action2.setStatusTip("This is your button2") button_action2.triggered.connect(self.onMyToolBarButtonClick) button_action2.setCheckable(True) toolbar.addAction(button_action2) toolbar.addWidget(QLabel("Hello")) toolbar.addWidget(QCheckBox()) self.setStatusBar(QStatusBar(self))
def init_ui(self): """Layout and main functionalities""" path_icon = str( os.path.dirname(os.path.abspath(__file__))) + "\\..\\icon\\" # Canvas self.plot_canvas = FigureCanvas(self.fig) self.plot_canvas.draw() # Matplotlib toolbar plot_toolbar = NavigationToolbar(self.plot_canvas, self) # Custom Toolbar action_toolbar = QToolBar(self) # - Actions - refresh_act = QAction(QIcon(path_icon+"refresh.png"), 'Refresh', self) refresh_act.triggered.connect(self.refresh_plot) close_act = QAction(QIcon(path_icon+"close.png"), 'Close', self) close_act.triggered.connect(self.hide) # - Format - action_toolbar.addAction(refresh_act) action_toolbar.addSeparator() action_toolbar.addAction(close_act) # Layout # - For the Widget v_plot = QVBoxLayout() v_plot.addWidget(self.plot_canvas) v_plot.addWidget(plot_toolbar) v_plot.addWidget(action_toolbar) self.setLayout(v_plot)
def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.setWindowTitle('My Awesome App !!!!!!') label = QLabel('This is a PyQt5 window !') label.setAlignment(Qt.AlignCenter) self.setCentralWidget(label) toolbar = QToolBar('My main toolbar') toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) toolbar.setIconSize(QSize(16, 16)) self.addToolBar(toolbar) button_action = QAction(QIcon('.\\bug.png'), "Your button", self) button_action.setStatusTip('This is your button') button_action.triggered.connect(self.onMyToolbarButtonClick) button_action.setCheckable(True) toolbar.addAction(button_action) toolbar.addSeparator() button_action2 = QAction(QIcon('.\\bug.png'), "Your button 2", self) button_action2.setStatusTip('This is your button 2') button_action2.triggered.connect(self.onMyToolbarButtonClick) button_action2.setCheckable(True) toolbar.addAction(button_action2) toolbar.addWidget(QLabel('Hello')) toolbar.addWidget(QCheckBox()) self.setStatusBar(QStatusBar(self))
class ToolBarAction(QWidgetAction): def __init__(self, *args, **kwargs): super(ToolBarAction, self).__init__(*args, **kwargs) self._toolBar = QToolBar( styleSheet="QToolBar {background: transparent; border: 0;}") self._toolBar.setIconSize(QSize(16, 16)) self.setDefaultWidget(self._toolBar) def toolBar(self): return self._toolBar def addAction(self, action): self._toolBar.addAction(action) if action.shortcut().toString() > "": action.setToolTip(action.text().replace("&", "") + "<br>" + action.shortcut().toString()) def widgetForAction(self, *args, **kwargs): return self._toolBar.widgetForAction(*args, **kwargs) def addWidget(self, *args, **kwargs): self._toolBar.addWidget(*args, **kwargs) def addSeparator(self): self._toolBar.addSeparator()
def setupToolbar(self): tb = QToolBar() self.verticalLayout.insertWidget(0, tb) tb.addAction(self.actionReplyToAll) self.actionReplyToAll.triggered.connect(self._compose) tb.addAction(self.actionReplyToSender) self.actionReplyToSender.triggered.connect(self._compose) tb.addAction(self.actionForward) self.actionForward.triggered.connect(self._compose) tb.addSeparator() tb.addAction(self.actionFlagMessage) self.actionFlagMessage.triggered.connect(self._flagMessages) tb.addAction(self.actionTagMessage) self.actionTagMessage.triggered.connect(self.openTagEditor) tagMenu = QMenu('Tags') tagMenu.aboutToShow.connect(self._prepareTagMenu) self.actionTagMessage.setMenu(tagMenu) tb.addSeparator() tb.addAction(self.actionSelectAll) self.actionSelectAll.triggered.connect(self.messagesTree.selectAll) tb.addAction(self.actionDeleteMessage) self.actionDeleteMessage.triggered.connect( self._deleteSelectedMessages) self.updateToolbarState()
def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_QWFormDoc() self.ui.setupUi(self) self.__curFile = "" locToolBar = QToolBar("文档", self) locToolBar.addAction(self.ui.qAction1) locToolBar.addAction(self.ui.qAction5) locToolBar.addSeparator() locToolBar.addAction(self.ui.qAction2) locToolBar.addAction(self.ui.qAction3) locToolBar.addAction(self.ui.qAction4) locToolBar.addAction(self.ui.qAction7) locToolBar.addAction(self.ui.qAction8) locToolBar.addSeparator() locToolBar.addAction(self.ui.qAction6) locToolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) Layout = QVBoxLayout() Layout.addWidget(locToolBar) Layout.addWidget(self.ui.qPlainTextEdit) Layout.setContentsMargins(2, 2, 2, 2) Layout.setSpacing(2) self.setLayout(Layout) self.setAutoFillBackground(True)
def __init__(self): super().__init__() self.setWindowTitle("My Awesome App") label = QLabel("THIS IS AWESOME!!!") label.setAlignment(Qt.AlignCenter) self.setCentralWidget(label) toolbar = QToolBar("My Main toolbar") toolbar.setIconSize(QSize(16, 16)) self.addToolBar(toolbar) button_action = QAction(QIcon("../../icons/bug.png"), "Your button", self) button_action.setStatusTip("This is your button") button_action.triggered.connect(self.onMyToolBarButtonClick) button_action.setCheckable(True) # True, False Toggle 됨 toolbar.addAction(button_action) toolbar.addSeparator() button_action2 = QAction(QIcon("../../icons/bug.png"), "Your button2", self) button_action2.setStatusTip("This is your button2") button_action2.triggered.connect(self.onMyToolBarButtonClick) button_action2.setCheckable(True) toolbar.addAction(button_action2) toolbar.addWidget(QLabel("Hello")) toolbar.addWidget(QCheckBox()) self.setStatusBar(QStatusBar(self))
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setWindowTitle("My GUI App") label = QLabel("This is a PyQt5 window") label.setAlignment(Qt.AlignCenter) self.setCentralWidget(label) toolbar = QToolBar("My Main Toolbar") toolbar.setIconSize(QSize(16, 16)) self.addToolBar(toolbar) buttonAction = QAction(QIcon(path.join(iconsPath, "bug.png")), "Your button", self) buttonAction.setStatusTip("This is your button") buttonAction.triggered.connect(self.onMyToolBarButtonClick) buttonAction.setCheckable(True) toolbar.addAction(buttonAction) toolbar.addSeparator() buttonAction2 = QAction(QIcon(path.join(iconsPath, "bug.png")), "Your button 2", self) buttonAction2.setStatusTip("This is your second button") buttonAction2.triggered.connect(self.onMyToolBarButtonClick) buttonAction2.setCheckable(True) toolbar.addAction(buttonAction2) toolbar.addWidget(QLabel("Hello")) toolbar.addWidget(QCheckBox()) self.setStatusBar(QStatusBar(self))
def run(self): toolbar = QToolBar() toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) toolbar.setIconSize(QSize(42, 42)) toolbar.addSeparator() self.tb_configure = QAction(icon_get("preferences-system"), wrap_text(_("Configure"), 4), self) toolbar.addAction(self.tb_configure) toolbar.addSeparator() self.play_one = play(self, "fit_ribbon_one", run_text=wrap_text(_("One itteration"), 2)) toolbar.addAction(self.play_one) self.play = play(self, "fit_ribbon_run", play_icon="forward", run_text=wrap_text(_("Run fit"), 2)) toolbar.addAction(self.play) spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) toolbar.addWidget(spacer) self.help = QAction(icon_get("help"), _("Help"), self) toolbar.addAction(self.help) return toolbar
def createGroup(self): self.bottomMenu = QFrame() self.searchText = QLineEdit(self) self.searchText.setStatusTip('Text to search for') self.replaceText = QLineEdit(self) self.replaceText.setStatusTip('Replace search text with this text') toolBar = QToolBar() # Create new action undoAction = QAction(QIcon.fromTheme('edit-undo'), 'Undo', self) undoAction.setStatusTip('Undo') undoAction.triggered.connect(self.undoCall) toolBar.addAction(undoAction) # create redo action redoAction = QAction(QIcon.fromTheme('edit-redo'), 'Redo', self) redoAction.setStatusTip('Redo') redoAction.triggered.connect(self.redoCall) toolBar.addAction(redoAction) toolBar.addSeparator() # create replace action replaceAction = QAction(QIcon.fromTheme('edit-find-replace'), 'Replace', self) replaceAction.setStatusTip('Replace text') replaceAction.triggered.connect(self.replaceCall) toolBar.addAction(replaceAction) # create find action findAction = QAction(QIcon.fromTheme('edit-find'), 'Find', self) findAction.setStatusTip('Find next occurrence of text') findAction.triggered.connect(self.findCall) toolBar.addAction(findAction) # create next action previousAction = QAction(QIcon.fromTheme('go-previous'), 'Find Previous', self) previousAction.setStatusTip('Find previous occurrence of text') previousAction.triggered.connect(self.previousCall) toolBar.addAction(previousAction) toolBar.addSeparator() # create case action caseAction = QAction(QIcon.fromTheme('edit-case'), 'Aa', self) caseAction.setStatusTip('Toggle between any case and match case') caseAction.setCheckable(1) caseAction.triggered.connect(self.caseCall) toolBar.addAction(caseAction) box = QHBoxLayout() box.addWidget(toolBar) box.addWidget(self.searchText) box.addWidget(self.replaceText) box.addStretch(1) self.bottomMenu.setLayout(box) return self.bottomMenu
def simulations(self): toolbar = QToolBar() toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) toolbar.setIconSize(QSize(42, 42)) self.tb_simulate = QAction(icon_get("build_play2"), wrap_text(_("Run scan"), 2), self) toolbar.addAction(self.tb_simulate) self.tb_stop = QAction(icon_get("media-playback-pause"), wrap_text(_("Stop"), 3), self) toolbar.addAction(self.tb_stop) toolbar.addSeparator() self.tb_plot = QAction(icon_get("plot"), wrap_text(_("Plot"), 4), self) toolbar.addAction(self.tb_plot) self.tb_plot_time = QAction(icon_get("plot_time"), wrap_text(_("Time domain plot"), 6), self) toolbar.addAction(self.tb_plot_time) self.box_widget = QWidget() self.box = QVBoxLayout() self.box_widget.setLayout(self.box) self.box_tb0 = QToolBar() self.box_tb0.setIconSize(QSize(32, 32)) self.box.addWidget(self.box_tb0) self.box_tb1 = QToolBar() self.box_tb1.setIconSize(QSize(32, 32)) self.box.addWidget(self.box_tb1) self.tb_build = QAction(icon_get("cog"), wrap_text(_("Build scan"), 2), self) self.box_tb0.addAction(self.tb_build) self.tb_rerun = QAction(icon_get("play-green"), wrap_text(_("Rerun"), 2), self) self.box_tb0.addAction(self.tb_rerun) self.tb_zip = QAction(icon_get("package-x-generic"), wrap_text(_("Archive simulations"), 2), self) self.box_tb0.addAction(self.tb_zip) self.tb_clean = QAction(icon_get("clean"), wrap_text(_("Clean simulation"), 4), self) self.box_tb1.addAction(self.tb_clean) self.tb_run_all = QAction(icon_get("forward2"), wrap_text(_("Run all scans"), 3), self) self.box_tb1.addAction(self.tb_run_all) self.tb_notes = QAction(icon_get("text-x-generic"), wrap_text(_("Notes"), 3), self) toolbar.addAction(self.tb_notes) toolbar.addWidget(self.box_widget) return toolbar
def ToolBar(*action_list): toolbar = QToolBar() for action in action_list: if action is None: toolbar.addSeparator() else: toolbar.addWidget(ToolButton(action)) return toolbar
def createToolbar(self) -> QToolBar: toolbar = QToolBar() import os print(os.system("pwd")) action = toolbar.addAction('load_hg') action.setIcon(QtGui.QIcon(str(Path('Resources/load.png')))) action.setText("Загрузить гиперграф") action.triggered.connect(self.centralWidget().loadHypergraphSlot) action = toolbar.addAction('save_hg') action.setIcon(QtGui.QIcon(str(Path('Resources/save.png')))) action.setText("Сохранить гиперграф") action.triggered.connect(self.centralWidget().saveHypergraphSlot) action = toolbar.addAction('new_scene') action.setIcon(QtGui.QIcon(str(Path('Resources/new.png')))) action.setText("Новый гиперграф") action.triggered.connect(self.centralWidget().resetSceneSlot) toolbar.addSeparator() action = toolbar.addAction('add_vertex') action.setIcon(QtGui.QIcon(str(Path('Resources/new_vertex.png')))) action.setText("Добавить вершину") action.triggered.connect( self.centralWidget().addVertexForCurrentSceneSlot) action = toolbar.addAction('remove_vertex') action.setIcon(QtGui.QIcon(str(Path('Resources/remove_vertex.png')))) action.setText("Удалить вершину") action.triggered.connect( self.centralWidget().removeVertexForCurrentSceneSlot) action = toolbar.addAction('add_edge') action.setIcon(QtGui.QIcon(str(Path('Resources/new_edge.png')))) action.setText("Добавить ребро") action.triggered.connect( self.centralWidget().addEdgeForCurrentSceneSlot) action = toolbar.addAction('remove_edge') action.setIcon(QtGui.QIcon(str(Path('Resources/remove_edge.png')))) action.setText("Удалить ребро ребро") action.triggered.connect( self.centralWidget().removeEdgeForCurrentSceneSlot) toolbar.addSeparator() action = toolbar.addAction('contraction_player') action.setIcon(QtGui.QIcon(str(Path('Resources/contraction.png')))) action.setText("Стягивание гиперграфа") action.triggered.connect( self.centralWidget().startContractionPlayerSlot) return toolbar
def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) dia = QtWidgets.QDialog() layout = QtWidgets.QVBoxLayout() # self.browser = QWebEngineView() # self.setStyleSheet("background-color : white;") # self.showFullScreen() # layout1=QtWidgets.QHBoxLayout() self.setWindowTitle("My Awesome App") self.setWindowIcon(QtGui.QIcon(os.path.join('ges12.jpg'))) self.label4 = QLabel("ok") self.label4.setAlignment(Qt.AlignTop) self.label4.linkActivated.connect(self.link) self.label4.setText('<a href="http://google.com/">Google</a>') self.label = QLabel("Welcome To My New Desktop Application") self.label.setAlignment(Qt.AlignCenter) self.label2 = QLabel("LOGIN / REGISTER") self.label2.setAlignment(Qt.AlignCenter) self.label.setFont(QFont("Arial", 20)) self.label2.setFont(QFont("Arial", 20)) self.line = QLineEdit() self.line1 = QLineEdit() # self.setCentralWidget(self.browser) navtb = QToolBar("Navigation") navtb.setIconSize(QSize(20, 20)) self.addToolBar(navtb) self.urlbar = QLineEdit() navtb.addSeparator() navtb.addWidget(self.urlbar) # self.label.setAlignment(Qt.AlignCenter) self.button = QPushButton("Login") self.button.setStatusTip("Next Page") # self.button.clicked.connect(self.do)# self.b1 = QPushButton("Cancel") layout.addWidget(self.label4) layout.addWidget(self.label) layout.addWidget(self.label2) layout.addWidget(self.line) layout.addWidget(self.line1) layout.addWidget(self.button) layout.addWidget(self.b1) # self.setCentralWidget(label) dia.setLayout(layout) self.setCentralWidget(dia)
def initUI(self): path_icon = str( os.path.dirname(os.path.abspath(__file__))) + \ "\\..\\icon\\" # Canvas self.plotCanvas = FigureCanvas(self.fig) self.plotCanvas.draw() # Matplotlib toolbar plotToolbar = NavigationToolbar(self.plotCanvas, self) # Custom Toolbar actionToolbar = QToolBar(self) # - Labels - rollingLabel = QLabel("Moving window: ") averageLabel = QLabel(" Average time: ") # - Spin Box - self.rolling = QSpinBox(self) self.rolling.setMinimum(0) self.rolling.setMaximum(1000) self.rolling.setValue(0) self.rolling.setToolTip( "Size of the moving window.\n" "This is the number of observations used for calculating" " the statistic.\n0 = Auto") self.average = QComboBox(self) self.average.addItems( ["None", "Minutely", "Hourly", "Daily", "Weekly"]) self.average.setToolTip("") # - Actions - applyAct = QAction(QIcon(path_icon+"apply.png"), 'Apply', self) applyAct.triggered.connect(self.refreshPlot) closeAct = QAction(QIcon(path_icon+"close.png"), 'Close', self) closeAct.triggered.connect(self.hide) # - Format - actionToolbar.addWidget(rollingLabel) actionToolbar.addWidget(self.rolling) actionToolbar.addWidget(averageLabel) actionToolbar.addWidget(self.average) actionToolbar.addAction(applyAct) actionToolbar.addSeparator() actionToolbar.addAction(closeAct) # Layout # - For the Widget vPlot = QVBoxLayout() vPlot.addWidget(self.plotCanvas) vPlot.addWidget(plotToolbar) vPlot.addWidget(actionToolbar) self.setLayout(vPlot)
def _createToolBars(self): # Using a title fileToolBar = self.addToolBar("File") # Using a QToolBar object editToolBar = QToolBar("Edit", self) self.addToolBar(editToolBar) editToolBar.addSeparator() self.fontSizeSpinBox = QSpinBox() self.fontSizeSpinBox.setFocusPolicy(Qt.NoFocus) editToolBar.addWidget(self.fontSizeSpinBox)
def createGroup(self): self.bottomMenu = QFrame() self.searchText = QLineEdit(self) self.replaceText = QLineEdit(self) toolBar = QToolBar() # Create new action undoAction = QAction(QIcon.fromTheme('edit-undo'), 'Undo', self) undoAction.setStatusTip('Undo') undoAction.triggered.connect(self.undoCall) toolBar.addAction(undoAction) # create redo action redoAction = QAction(QIcon.fromTheme('edit-redo'), 'Redo', self) redoAction.setStatusTip('Undo') redoAction.triggered.connect(self.redoCall) toolBar.addAction(redoAction) toolBar.addSeparator() # create replace action replaceAction = QAction(QIcon.fromTheme('edit-find-replace'), 'Replace', self) replaceAction.triggered.connect(self.replaceCall) toolBar.addAction(replaceAction) # create find action findAction = QAction(QIcon.fromTheme('edit-find'), 'Find', self) findAction.triggered.connect(self.findCall) toolBar.addAction(findAction) # create next action nextAction = QAction(QIcon.fromTheme('go-previous'), 'Find Previous', self) nextAction.triggered.connect(self.nextCall) toolBar.addAction(nextAction) toolBar.addSeparator() # create case action caseAction = QAction(QIcon.fromTheme('edit-case'), 'Aa', self) caseAction.setCheckable(1) caseAction.triggered.connect(self.caseCall) toolBar.addAction(caseAction) box = QHBoxLayout() box.addWidget(toolBar) box.addWidget(self.searchText) box.addWidget(self.replaceText) box.addStretch(1) self.bottomMenu.setLayout(box) return self.bottomMenu
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 设置窗口标题 self.setWindowTitle('Rhodes no Workers Desktop') # 设置窗口图标 self.setWindowIcon(QIcon('src/favicon.png')) # 设置浏览器 self.browser = QWebEngineView() # 添加导航栏 navigation_bar = QToolBar('Navigation') # 设定图标的大小 navigation_bar.setIconSize(QSize(20, 20)) self.addToolBar(navigation_bar) # 添加前进、后退、停止加载和刷新的按钮 back_button = QAction(QIcon('src/back.png'), 'Back', self) next_button = QAction(QIcon('src/forward.png'), 'Forward', self) stop_button = QAction(QIcon('src/stop.png'), 'Stop', self) reload_button = QAction(QIcon('src/reload.png'), 'Reload', self) home_button = QPushButton(' HomePage ', self) honor_button = QPushButton(' ArkHonor ', self) honor_button.setStyleSheet("QPushButton{background: lightpink;}") story_button = QPushButton(' ArkStory ', self) story_button.setStyleSheet("QPushButton{background: lightblue;}") back_button.triggered.connect(self.browser.back) next_button.triggered.connect(self.browser.forward) stop_button.triggered.connect(self.browser.stop) reload_button.triggered.connect(self.browser.reload) home_button.clicked.connect(self.homeClick) honor_button.clicked.connect(self.honorClick) story_button.clicked.connect(self.storyClick) # 将按钮添加到导航栏上 navigation_bar.addAction(back_button) navigation_bar.addAction(next_button) navigation_bar.addAction(stop_button) navigation_bar.addAction(reload_button) navigation_bar.addSeparator() navigation_bar.addWidget(home_button) navigation_bar.addWidget(honor_button) navigation_bar.addWidget(story_button) # 指定打开界面的 URL self.browser.setUrl(QUrl(self.homeUrl)) # 添加浏览器到窗口中 self.setCentralWidget(self.browser) self.setStyleSheet(self.button_style)
def __init__(self, parent=None): super().__init__(parent) self.mOpacityLabel = QLabel() self.mOpacitySlider = QSlider(Qt.Horizontal) self.mLayerView = LayerView() self.mMapDocument = None self.mUpdatingSlider = False self.mChangingLayerOpacity = False self.mUpdatingSlider = False self.setObjectName("layerDock") widget = QWidget(self) layout = QVBoxLayout(widget) layout.setContentsMargins(5, 5, 5, 5) opacityLayout = QHBoxLayout() self.mOpacitySlider.setRange(0, 100) self.mOpacitySlider.setEnabled(False) opacityLayout.addWidget(self.mOpacityLabel) opacityLayout.addWidget(self.mOpacitySlider) self.mOpacityLabel.setBuddy(self.mOpacitySlider) handler = MapDocumentActionHandler.instance() newLayerMenu = QMenu(self) newLayerMenu.addAction(handler.actionAddTileLayer()) newLayerMenu.addAction(handler.actionAddObjectGroup()) newLayerMenu.addAction(handler.actionAddImageLayer()) newIcon = QIcon(":/images/16x16/document-new.png") newLayerButton = QToolButton() newLayerButton.setPopupMode(QToolButton.InstantPopup) newLayerButton.setMenu(newLayerMenu) newLayerButton.setIcon(newIcon) Utils.setThemeIcon(newLayerButton, "document-new") buttonContainer = QToolBar() buttonContainer.setFloatable(False) buttonContainer.setMovable(False) buttonContainer.setIconSize(QSize(16, 16)) buttonContainer.addWidget(newLayerButton) buttonContainer.addAction(handler.actionMoveLayerUp()) buttonContainer.addAction(handler.actionMoveLayerDown()) buttonContainer.addAction(handler.actionDuplicateLayer()) buttonContainer.addAction(handler.actionRemoveLayer()) buttonContainer.addSeparator() buttonContainer.addAction(handler.actionToggleOtherLayers()) listAndToolBar = QVBoxLayout() listAndToolBar.setSpacing(0) listAndToolBar.addWidget(self.mLayerView) listAndToolBar.addWidget(buttonContainer) layout.addLayout(opacityLayout) layout.addLayout(listAndToolBar) self.setWidget(widget) self.retranslateUi() self.mOpacitySlider.valueChanged.connect(self.sliderValueChanged) self.updateOpacitySlider()
def __init__(self, parent = None): super().__init__(parent) self.mOpacityLabel = QLabel() self.mOpacitySlider = QSlider(Qt.Horizontal) self.mLayerView = LayerView() self.mMapDocument = None self.mUpdatingSlider = False self.mChangingLayerOpacity = False self.mUpdatingSlider = False self.setObjectName("layerDock") widget = QWidget(self) layout = QVBoxLayout(widget) layout.setContentsMargins(5, 5, 5, 5) opacityLayout = QHBoxLayout() self.mOpacitySlider.setRange(0, 100) self.mOpacitySlider.setEnabled(False) opacityLayout.addWidget(self.mOpacityLabel) opacityLayout.addWidget(self.mOpacitySlider) self.mOpacityLabel.setBuddy(self.mOpacitySlider) handler = MapDocumentActionHandler.instance() newLayerMenu = QMenu(self) newLayerMenu.addAction(handler.actionAddTileLayer()) newLayerMenu.addAction(handler.actionAddObjectGroup()) newLayerMenu.addAction(handler.actionAddImageLayer()) newIcon = QIcon(":/images/16x16/document-new.png") newLayerButton = QToolButton() newLayerButton.setPopupMode(QToolButton.InstantPopup) newLayerButton.setMenu(newLayerMenu) newLayerButton.setIcon(newIcon) Utils.setThemeIcon(newLayerButton, "document-new") buttonContainer = QToolBar() buttonContainer.setFloatable(False) buttonContainer.setMovable(False) buttonContainer.setIconSize(QSize(16, 16)) buttonContainer.addWidget(newLayerButton) buttonContainer.addAction(handler.actionMoveLayerUp()) buttonContainer.addAction(handler.actionMoveLayerDown()) buttonContainer.addAction(handler.actionDuplicateLayer()) buttonContainer.addAction(handler.actionRemoveLayer()) buttonContainer.addSeparator() buttonContainer.addAction(handler.actionToggleOtherLayers()) listAndToolBar = QVBoxLayout() listAndToolBar.setSpacing(0) listAndToolBar.addWidget(self.mLayerView) listAndToolBar.addWidget(buttonContainer) layout.addLayout(opacityLayout) layout.addLayout(listAndToolBar) self.setWidget(widget) self.retranslateUi() self.mOpacitySlider.valueChanged.connect(self.sliderValueChanged) self.updateOpacitySlider()
def toolbar(groups, actions, controls=None): texts = [str(action.text()) for action in actions] if controls: controls = dict([('widget-' + z, v) for z, v in controls.items()]) toolbar = QToolBar('Toolbar') for name, actionlist in groups: for action in actionlist: if action in texts: toolbar.addAction(actions[texts.index(action)]) elif action in controls: toolbar.addWidget(controls[action]) toolbar.addSeparator() return toolbar
def _defineToolBar(self) -> None: toolBar = QToolBar(self) toolBar.setMovable(False) # add actions toolBar.addAction(self.actionAdd) toolBar.addAction(self.actionRemove) toolBar.addSeparator() toolBar.addAction(self.actionRemoveAll) toolBar.addSeparator() toolBar.addAction(self.actionSave) self.addToolBar(toolBar)
def createToolBar(self): tool_bar = QToolBar("Toolbar") # You should set the size of the icons in the toolbar using the setIconSize() method # with QSize() to avoid extra padding # when PyQt tries to figure out the arrangement by itself. tool_bar.setIconSize(QSize(24,24)) self.addToolBar(tool_bar) tool_bar.addAction(self.open_act) tool_bar.addAction(self.save_act) tool_bar.addAction(self.print_act) tool_bar.addSeparator() tool_bar.addAction(self.exit_act)
def __createToolbar(self): self.buttons = {} toolbar = QToolBar("bottom toolbar") toolbar.setOrientation(Qt.Horizontal) toolbar.setAllowedAreas(Qt.BottomToolBarArea) toolbar.setIconSize(QSize(32, 32)) button1 = QPushButton("Show All") toolbar.addWidget(button1) toolbar.addSeparator() button2 = QPushButton("Play") toolbar.addWidget(button2) toolbar.addSeparator() button3 = QPushButton("Stop") toolbar.addWidget(button3) toolbar.addSeparator() button4 = QPushButton("Record") toolbar.addWidget(button4) toolbar.addSeparator() self.addToolBar(Qt.BottomToolBarArea, toolbar)
def init_topmenu(self): topmenu = QToolBar() Sync_Button = QPushButton("Sync") Sync_Button.clicked.connect(self.do_sync) Last_Updated = QLabel("Last Updated:") settings = QPushButton("Settings") settings.clicked.connect(self.popup_settings) topmenu.addWidget(Sync_Button) topmenu.addSeparator() topmenu.addWidget(Last_Updated) topmenu.addWidget(settings) # Do not let the user drag the toolbar around topmenu.setMovable(False) return topmenu
def createToolBar(self): """ Create toolbar for photo editor GUI """ tool_bar = QToolBar("Photo Editor Toolbar") tool_bar.setIconSize(QSize(24, 24)) self.addToolBar(tool_bar) # Add actions to toolbar tool_bar.addAction(self.open_act) tool_bar.addAction(self.save_act) tool_bar.addAction(self.print_act) tool_bar.addAction(self.clear_act) tool_bar.addSeparator() tool_bar.addAction(self.exit_act)
def init_bar(self) -> None: """ Init function bar, set to right side of widget :return: """ toolbar = QToolBar() toolbar.setObjectName('MapToolbar') self.mainWindow.addToolBar(Qt.RightToolBarArea, toolbar) # ------------------------------- Open map ---------------------------------- self.openMap_action = QAction(QIcon('resources/icons/openMap.png'), TR().tr('Open_map'), self.mainWindow, triggered=self.open_map_action, enabled=False) toolbar.addAction(self.openMap_action) # ------------------------------- Zoom in ---------------------------------- self.zoomIn_action = QAction(QIcon('resources/icons/zoomIn.png'), TR().tr('Zoom_in'), self.mainWindow, triggered=self.zoom_in_action, shortcut='Ctrl++', enabled=False) toolbar.addAction(self.zoomIn_action) # ------------------------------- Zoom out ---------------------------------- self.zoomOut_action = QAction(QIcon('resources/icons/zoomOut.png'), TR().tr('Zoom_out'), self.mainWindow, triggered=self.zoom_out_action, shortcut='Ctrl++', enabled=False) toolbar.addAction(self.zoomOut_action) # ------------------------------- Edit info ---------------------------------- self.info_action = QAction(QIcon('resources/icons/scroll.png'), TR().tr('Edit_info'), self.mainWindow, triggered=self.edit_info_action, shortcut='Ctrl+P', enabled=False) toolbar.addAction(self.info_action) toolbar.addSeparator() # ------------------------------- Add monster ---------------------------------- self.addMonster_action = QAction(QIcon('resources/icons/addMonster.png'), TR().tr('Add_monster'), self.mainWindow, triggered=self.add_monster_action, shortcut='Ctrl+M', enabled=False) toolbar.addAction(self.addMonster_action) # ------------------------------- Add item ---------------------------------- self.addItem_action = QAction(QIcon('resources/icons/addItem.png'), TR().tr('Add_item'), self.mainWindow, triggered=self.add_item_action, enabled=False) toolbar.addAction(self.addItem_action) # ------------------------------- Add room ---------------------------------- self.addRoom_action = QAction(QIcon('resources/icons/addRoom.png'), TR().tr('Add_room'), self.mainWindow, triggered=self.add_room_action, enabled=False) toolbar.addAction(self.addRoom_action) # ------------------------------- Add object ---------------------------------- self.addObject_action = QAction(QIcon('resources/icons/addObject.png'), TR().tr('Add_object_map'), self.mainWindow, triggered=self.add_object_action, enabled=False) toolbar.addAction(self.addObject_action)
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 设置浏览器 self.browser = HJWebEngineView() layout = QVBoxLayout() layout.addWidget(self.browser) layout.addStretch(1) # 添加浏览器到窗口中 self.setLayout(layout) # 使用QToolBar创建导航栏,并使用QAction创建按钮 # 添加导航栏 navigation_bar = QToolBar('Navigation') # 设定图标的大小 navigation_bar.setIconSize(QSize(16, 16)) # 添加导航栏到窗口中 layout.addWidget(navigation_bar) # QAction类提供了抽象的用户界面action,这些action可以被放置在窗口部件中 # 添加前进、后退、停止加载和刷新的按钮 back_button = QAction(QIcon('icons/previous.png'), 'Back', self) next_button = QAction(QIcon('icons/next.png'), 'Forward', self) stop_button = QAction(QIcon('icons/stop_cross.png'), 'stop', self) reload_button = QAction(QIcon('icons/reload.png'), 'reload', self) back_button.triggered.connect(self.browser.back) next_button.triggered.connect(self.browser.forward) stop_button.triggered.connect(self.browser.stop) reload_button.triggered.connect(self.browser.reload) # 将按钮添加到导航栏上 navigation_bar.addAction(back_button) navigation_bar.addAction(next_button) navigation_bar.addAction(stop_button) navigation_bar.addAction(reload_button) # 添加URL地址栏 self.urlbar = QLineEdit() # 让地址栏能响应回车按键信号 # self.urlbar.returnPressed.connect(self.navigate_to_url) # 不允许编辑 self.urlbar.setReadOnly(True) navigation_bar.addSeparator() navigation_bar.addWidget(self.urlbar) # 让浏览器相应url地址的变化 self.browser.urlChanged.connect(self.update_urlbar)
def _createToolBar(self): tools = QToolBar() Geffe = tools.addAction(Window.actions[0], self._change_lfsr3_state) Geffe.setToolTip("Geffe Random Number Generator") Geffe.setFont(Window.font_toolbar) tools.addSeparator() StopGo = tools.addAction(Window.actions[1], self._change_lfsr3_state) StopGo.setToolTip("Stop&Go Random Number Generator") StopGo.setFont(Window.font_toolbar) tools.addSeparator() Shrinking = tools.addAction(Window.actions[2], self._change_lfsr3_state) Shrinking.setToolTip("Shrinking Random Number Generator") Shrinking.setFont(Window.font_toolbar) self.addToolBar(tools)
def setup_toolbar(self): toolbar = QToolBar("Main Toolbar") toolbar.setObjectName("main-toolbar") toolbar.addAction(self.start_listening_action) toolbar.addAction(self.stop_listening_action) toolbar.addSeparator() toolbar.addAction(self.stop_debug_action) toolbar.addAction(self.detach_debug_action) toolbar.addSeparator() toolbar.addAction(self.run_debug_action) toolbar.addAction(self.step_over_action) toolbar.addAction(self.step_into_action) toolbar.addAction(self.step_out_action) self.addToolBar(toolbar)
class ToolBarAction(QWidgetAction): def __init__(self, *args, **kwargs): super(ToolBarAction, self).__init__(*args, **kwargs) self._toolBar = QToolBar(styleSheet="QToolBar {background: transparent; border: 0;}") self._toolBar.setIconSize(QSize(16, 16)) self.setDefaultWidget(self._toolBar) def toolBar(self): return self._toolBar def addAction(self, action): self._toolBar.addAction(action) if action.shortcut().toString() > "": action.setToolTip(action.text().replace("&", "") + "<br>" + action.shortcut().toString()) def widgetForAction(self, *args, **kwargs): return self._toolBar.widgetForAction(*args, **kwargs) def addWidget(self, *args, **kwargs): self._toolBar.addWidget(*args, **kwargs) def addSeparator(self): self._toolBar.addSeparator()
def toolbarWidget(self): self.brushSizeWidget = BrushSizeWidget(self.brush_size, max_size=70) self.clearAction = QAction(QIcon("assets/clear.png"), "Clear", self) self.saveAction = QAction(QIcon("assets/save.png"), "Save", self) self.removeAction = QAction(QIcon("assets/remove.png"), "Remove", self) self.previewAction = QAction(QIcon("assets/preview.png"), "Preview", self) toolbar = QToolBar() toolbar.setIconSize(QSize(30, 30)) toolbar.addWidget(self.brushSizeWidget) toolbar.addSeparator() toolbar.addAction(self.clearAction) toolbar.addAction(self.saveAction) toolbar.addAction(self.removeAction) toolbar.addAction(self.removeAction) toolbar.addAction(self.previewAction) toolbar.addSeparator() toolbar.addWidget(self.sizeWidget()) toolbar.addWidget(self.prefixWidget()) toolbar.addWidget(self.toolbarSpacer()) return toolbar
def initToolBar(self): """ Public method to populate a toolbar with our actions. @return the populated toolBar (QToolBar) """ toolBar = QToolBar(self.tr("Graphics"), self) toolBar.setIconSize(UI.Config.ToolBarIconSize) toolBar.addAction(self.deleteShapeAct) toolBar.addSeparator() toolBar.addAction(self.alignLeftAct) toolBar.addAction(self.alignHCenterAct) toolBar.addAction(self.alignRightAct) toolBar.addAction(self.alignTopAct) toolBar.addAction(self.alignVCenterAct) toolBar.addAction(self.alignBottomAct) toolBar.addSeparator() toolBar.addAction(self.incWidthAct) toolBar.addAction(self.incHeightAct) toolBar.addAction(self.decWidthAct) toolBar.addAction(self.decHeightAct) toolBar.addAction(self.setSizeAct) toolBar.addSeparator() toolBar.addAction(self.rescanAct) toolBar.addAction(self.relayoutAct) return toolBar
def __init__(self): super(MainWindow, self).__init__() # remove close & maximize window buttons #self.setWindowFlags(Qt.CustomizeWindowHint|Qt.WindowMinimizeButtonHint) self.setMinimumSize(500, 666) #self.setMaximumSize(1000,666) self.mdiArea = QMdiArea() self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setCentralWidget(self.mdiArea) self.mdiArea.subWindowActivated.connect(self.updateMenus) self.mdiArea.setViewMode(QMdiArea.TabbedView) self.windowMapper = QSignalMapper(self) self.windowMapper.mapped[QWidget].connect(self.setActiveSubWindow) self.child = None self.createActions() self.createMenus() self.createStatusBar() self.updateMenus() self.readSettings() self.setWindowTitle("LEKTURE") mytoolbar = QToolBar() #self.toolbar = self.addToolBar() mytoolbar.addAction(self.newAct) mytoolbar.addAction(self.openAct) mytoolbar.addAction(self.saveAct) mytoolbar.addAction(self.saveAsAct) mytoolbar.addSeparator() mytoolbar.addAction(self.outputsAct) mytoolbar.addAction(self.scenarioAct) self.scenarioAct.setVisible(False) mytoolbar.setMovable(False) mytoolbar.setFixedWidth(60) self.addToolBar(Qt.LeftToolBarArea, mytoolbar)
def initBasicToolbar(self, ui, toolbarManager): """ Public slot to initialize the basic VCS toolbar. @param ui reference to the main window (UserInterface) @param toolbarManager reference to a toolbar manager object (E5ToolBarManager) @return the toolbar generated (QToolBar) """ tb = QToolBar(self.tr("VCS"), ui) tb.setIconSize(UI.Config.ToolBarIconSize) tb.setObjectName("VersionControlToolbar") tb.setToolTip(self.tr('VCS')) tb.addAction(self.vcsNewAct) tb.addAction(self.vcsExportAct) tb.addSeparator() tb.addAction(self.vcsAddAct) toolbarManager.addToolBar(tb, tb.windowTitle()) return tb
def initToolbar(self, toolbarManager): """ Public slot to initialize the multi project toolbar. @param toolbarManager reference to a toolbar manager object (E5ToolBarManager) @return the toolbar generated (QToolBar) """ tb = QToolBar(self.tr("Multiproject"), self.ui) tb.setIconSize(UI.Config.ToolBarIconSize) tb.setObjectName("MultiProjectToolbar") tb.setToolTip(self.tr('Multiproject')) tb.addActions(self.actGrp1.actions()) tb.addAction(self.closeAct) tb.addSeparator() tb.addAction(self.saveAct) tb.addAction(self.saveasAct) toolbarManager.addToolBar(tb, tb.windowTitle()) toolbarManager.addAction(self.addProjectAct, tb.windowTitle()) toolbarManager.addAction(self.propsAct, tb.windowTitle()) return tb
class SimulationGui(QMainWindow): """ class for the graphical user interface """ # TODO enable closing plot docks by right-clicking their name runSimulation = pyqtSignal() stopSimulation = pyqtSignal() playbackTimeChanged = pyqtSignal() regimeFinished = pyqtSignal() finishedRegimeBatch = pyqtSignal(bool) def __init__(self): # constructor of the base class QMainWindow.__init__(self) QCoreApplication.setOrganizationName("RST") QCoreApplication.setOrganizationDomain("https://tu-dresden.de/rst") QCoreApplication.setApplicationVersion( pkg_resources.require("PyMoskito")[0].version) QCoreApplication.setApplicationName(globals()["__package__"]) # load settings self._settings = QSettings() self._read_settings() # initialize logger self._logger = logging.getLogger(self.__class__.__name__) # Create Simulation Backend self.guiProgress = None self.cmdProgress = None self.sim = SimulatorInteractor(self) self.runSimulation.connect(self.sim.run_simulation) self.stopSimulation.connect(self.sim.stop_simulation) self.sim.simulation_finalized.connect(self.new_simulation_data) self.currentDataset = None self.interpolator = None # sim setup viewer self.targetView = SimulatorView(self) self.targetView.setModel(self.sim.target_model) self.targetView.expanded.connect(self.target_view_changed) self.targetView.collapsed.connect(self.target_view_changed) # sim results viewer self.result_view = QTreeView() # the docking area allows to rearrange the user interface at runtime self.area = pg.dockarea.DockArea() # Window properties icon_size = QSize(25, 25) self.setCentralWidget(self.area) self.resize(1000, 700) self.setWindowTitle("PyMoskito") res_path = get_resource("mosquito.png") icon = QIcon(res_path) self.setWindowIcon(icon) # create docks self.propertyDock = pg.dockarea.Dock("Properties") self.animationDock = pg.dockarea.Dock("Animation") self.regimeDock = pg.dockarea.Dock("Regimes") self.dataDock = pg.dockarea.Dock("Data") self.logDock = pg.dockarea.Dock("Log") self.plotDockPlaceholder = pg.dockarea.Dock("Placeholder") # arrange docks self.area.addDock(self.animationDock, "right") self.area.addDock(self.regimeDock, "left", self.animationDock) self.area.addDock(self.propertyDock, "bottom", self.regimeDock) self.area.addDock(self.dataDock, "bottom", self.propertyDock) self.area.addDock(self.plotDockPlaceholder, "bottom", self.animationDock) self.area.addDock(self.logDock, "bottom", self.dataDock) self.non_plotting_docks = list(self.area.findAll()[1].keys()) # add widgets to the docks self.propertyDock.addWidget(self.targetView) if not vtk_available: self._logger.error("loading vtk failed with:{}".format(vtk_error_msg)) # check if there is a registered visualizer available_vis = get_registered_visualizers() self._logger.info("found visualizers: {}".format( [name for cls, name in available_vis])) if available_vis: # instantiate the first visualizer self._logger.info("loading visualizer '{}'".format(available_vis[0][1])) self.animationLayout = QVBoxLayout() if issubclass(available_vis[0][0], MplVisualizer): self.animationWidget = QWidget() self.visualizer = available_vis[0][0](self.animationWidget, self.animationLayout) self.animationDock.addWidget(self.animationWidget) elif issubclass(available_vis[0][0], VtkVisualizer): if vtk_available: # vtk window self.animationFrame = QFrame() self.vtkWidget = QVTKRenderWindowInteractor( self.animationFrame) self.animationLayout.addWidget(self.vtkWidget) self.animationFrame.setLayout(self.animationLayout) self.animationDock.addWidget(self.animationFrame) self.vtk_renderer = vtkRenderer() self.vtkWidget.GetRenderWindow().AddRenderer( self.vtk_renderer) self.visualizer = available_vis[0][0](self.vtk_renderer) self.vtkWidget.Initialize() else: self._logger.warning("visualizer depends on vtk which is " "not available on this system!") elif available_vis: raise NotImplementedError else: self.visualizer = None # regime window self.regime_list = QListWidget(self) self.regime_list.setSelectionMode(QAbstractItemView.ExtendedSelection) self.regimeDock.addWidget(self.regime_list) self.regime_list.itemDoubleClicked.connect(self.regime_dclicked) self._regimes = [] self.regime_file_name = "" self.actDeleteRegimes = QAction(self.regime_list) self.actDeleteRegimes.setText("&Delete Selected Regimes") # TODO shortcut works always, not only with focus on the regime list # self.actDeleteRegimes.setShortcutContext(Qt.WindowShortcut) self.actDeleteRegimes.setShortcut(QKeySequence(Qt.Key_Delete)) self.actDeleteRegimes.triggered.connect(self.remove_regime_items) self.actSave = QAction(self) self.actSave.setText('Save Results As') self.actSave.setIcon(QIcon(get_resource("save.png"))) self.actSave.setDisabled(True) self.actSave.setShortcut(QKeySequence.Save) self.actSave.triggered.connect(self.export_simulation_data) self.actLoadRegimes = QAction(self) self.actLoadRegimes.setText("Load Regimes from File") self.actLoadRegimes.setIcon(QIcon(get_resource("load.png"))) self.actLoadRegimes.setDisabled(False) self.actLoadRegimes.setShortcut(QKeySequence.Open) self.actLoadRegimes.triggered.connect(self.load_regime_dialog) self.actExitOnBatchCompletion = QAction(self) self.actExitOnBatchCompletion.setText("&Exit On Batch Completion") self.actExitOnBatchCompletion.setCheckable(True) self.actExitOnBatchCompletion.setChecked( self._settings.value("control/exit_on_batch_completion") == "True" ) self.actExitOnBatchCompletion.changed.connect( self.update_exit_on_batch_completion_setting) # regime management self.runningBatch = False self._current_regime_index = None self._current_regime_name = None self._regimes = [] self.regimeFinished.connect(self.run_next_regime) self.finishedRegimeBatch.connect(self.regime_batch_finished) # data window self.dataList = QListWidget(self) self.dataDock.addWidget(self.dataList) self.dataList.itemDoubleClicked.connect(self.create_plot) # actions for simulation control self.actSimulateCurrent = QAction(self) self.actSimulateCurrent.setText("&Simulate Current Regime") self.actSimulateCurrent.setIcon(QIcon(get_resource("simulate.png"))) self.actSimulateCurrent.setShortcut(QKeySequence("F5")) self.actSimulateCurrent.triggered.connect(self.start_simulation) self.actSimulateAll = QAction(self) self.actSimulateAll.setText("Simulate &All Regimes") self.actSimulateAll.setIcon(QIcon(get_resource("execute_regimes.png"))) self.actSimulateAll.setShortcut(QKeySequence("F6")) self.actSimulateAll.setDisabled(True) self.actSimulateAll.triggered.connect(self.start_regime_execution) # actions for animation control self.actAutoPlay = QAction(self) self.actAutoPlay.setText("&Autoplay Simulation") self.actAutoPlay.setCheckable(True) self.actAutoPlay.setChecked( self._settings.value("control/autoplay_animation") == "True" ) self.actAutoPlay.changed.connect(self.update_autoplay_setting) self.actPlayPause = QAction(self) self.actPlayPause.setText("Play Animation") self.actPlayPause.setIcon(QIcon(get_resource("play.png"))) self.actPlayPause.setDisabled(True) self.actPlayPause.setShortcut(QKeySequence(Qt.Key_Space)) self.actPlayPause.triggered.connect(self.play_animation) self.actStop = QAction(self) self.actStop.setText("Stop") self.actStop.setIcon(QIcon(get_resource("stop.png"))) self.actStop.setDisabled(True) self.actStop.triggered.connect(self.stop_animation) self.actSlow = QAction(self) self.actSlow.setText("Slowest") self.actSlow.setIcon(QIcon(get_resource("slow.png"))) self.actSlow.setDisabled(False) self.actSlow.triggered.connect(self.set_slowest_playback_speed) self.actFast = QAction(self) self.actFast.setText("Fastest") self.actFast.setIcon(QIcon(get_resource("fast.png"))) self.actFast.setDisabled(False) self.actFast.triggered.connect(self.set_fastest_playback_speed) self.speedControl = QSlider(Qt.Horizontal, self) self.speedControl.setMaximumSize(200, 25) self.speedControl.setTickPosition(QSlider.TicksBothSides) self.speedControl.setDisabled(False) self.speedControl.setMinimum(0) self.speedControl.setMaximum(12) self.speedControl.setValue(6) self.speedControl.setTickInterval(6) self.speedControl.setSingleStep(2) self.speedControl.setPageStep(3) self.speedControl.valueChanged.connect(self.update_playback_speed) self.timeSlider = QSlider(Qt.Horizontal, self) self.timeSlider.setMinimum(0) self.timeSliderRange = 1000 self.timeSlider.setMaximum(self.timeSliderRange) self.timeSlider.setTickInterval(1) self.timeSlider.setTracking(True) self.timeSlider.setDisabled(True) self.timeSlider.valueChanged.connect(self.update_playback_time) self.playbackTime = .0 self.playbackGain = 1 self.currentStepSize = .0 self.currentEndTime = .0 self.playbackTimer = QTimer() self.playbackTimer.timeout.connect(self.increment_playback_time) self.playbackTimeChanged.connect(self.update_gui) self.playbackTimeout = 33 # in [ms] -> 30 fps self.actResetCamera = QAction(self) self.actResetCamera.setText("Reset Camera") self.actResetCamera.setIcon(QIcon(get_resource("reset_camera.png"))) self.actResetCamera.setDisabled(True) if available_vis: self.actResetCamera.setEnabled(self.visualizer.can_reset_view) self.actResetCamera.triggered.connect(self.reset_camera_clicked) # postprocessing self.actPostprocessing = QAction(self) self.actPostprocessing.setText("Launch Postprocessor") self.actPostprocessing.setIcon(QIcon(get_resource("processing.png"))) self.actPostprocessing.setDisabled(False) self.actPostprocessing.triggered.connect(self.postprocessing_clicked) self.actPostprocessing.setShortcut(QKeySequence("F7")) self.postprocessor = None # toolbar self.toolbarSim = QToolBar("Simulation") self.toolbarSim.setContextMenuPolicy(Qt.PreventContextMenu) self.toolbarSim.setMovable(False) self.toolbarSim.setIconSize(icon_size) self.addToolBar(self.toolbarSim) self.toolbarSim.addAction(self.actLoadRegimes) self.toolbarSim.addAction(self.actSave) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actSimulateCurrent) self.toolbarSim.addAction(self.actSimulateAll) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actPlayPause) self.toolbarSim.addAction(self.actStop) self.toolbarSim.addWidget(self.timeSlider) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actSlow) self.toolbarSim.addWidget(self.speedControl) self.toolbarSim.addAction(self.actFast) self.toolbarSim.addSeparator() self.toolbarSim.addAction(self.actPostprocessing) self.toolbarSim.addAction(self.actResetCamera) self.postprocessor = None # log dock self.logBox = QPlainTextEdit(self) self.logBox.setReadOnly(True) self.logDock.addWidget(self.logBox) # init logger for logging box self.textLogger = PlainTextLogger(logging.INFO) self.textLogger.set_target_cb(self.logBox.appendPlainText) logging.getLogger().addHandler(self.textLogger) # menu bar fileMenu = self.menuBar().addMenu("&File") fileMenu.addAction(self.actLoadRegimes) fileMenu.addAction(self.actSave) fileMenu.addAction("&Quit", self.close) editMenu = self.menuBar().addMenu("&Edit") editMenu.addAction(self.actDeleteRegimes) simMenu = self.menuBar().addMenu("&Simulation") simMenu.addAction(self.actSimulateCurrent) simMenu.addAction(self.actSimulateAll) simMenu.addAction(self.actExitOnBatchCompletion) simMenu.addAction(self.actPostprocessing) animMenu = self.menuBar().addMenu("&Animation") animMenu.addAction(self.actPlayPause) animMenu.addAction("&Increase Playback Speed", self.increment_playback_speed, QKeySequence(Qt.CTRL + Qt.Key_Plus)) animMenu.addAction("&Decrease Playback Speed", self.decrement_playback_speed, QKeySequence(Qt.CTRL + Qt.Key_Minus)) animMenu.addAction("&Reset Playback Speed", self.reset_playback_speed, QKeySequence(Qt.CTRL + Qt.Key_0)) animMenu.addAction(self.actAutoPlay) animMenu.addAction(self.actResetCamera) helpMenu = self.menuBar().addMenu("&Help") helpMenu.addAction("&Online Documentation", self.show_online_docs) helpMenu.addAction("&About", self.show_info) # status bar self.status = QStatusBar(self) self.setStatusBar(self.status) self.statusLabel = QLabel("Ready.") self.statusBar().addPermanentWidget(self.statusLabel) self.timeLabel = QLabel("current time: 0.0") self.statusBar().addPermanentWidget(self.timeLabel) self._logger.info("Simulation GUI is up and running.") def _read_settings(self): # add default settings if none are present if not self._settings.contains("path/simulation_results"): self._settings.setValue("path/simulation_results", os.path.join(os.path.curdir, "results", "simulation")) if not self._settings.contains("path/postprocessing_results"): self._settings.setValue("path/postprocessing_results", os.path.join(os.path.curdir, "results", "postprocessing")) if not self._settings.contains("path/metaprocessing_results"): self._settings.setValue("path/metaprocessing_results", os.path.join(os.path.curdir, "results", "metaprocessing")) if not self._settings.contains("control/autoplay_animation"): self._settings.setValue("control/autoplay_animation", "False") if not self._settings.contains("control/exit_on_batch_completion"): self._settings.setValue("control/exit_on_batch_completion", "False") def _write_settings(self): """ Store the application state. """ pass @pyqtSlot() def update_autoplay_setting(self): self._settings.setValue("control/autoplay_animation", str(self.actAutoPlay.isChecked())) @pyqtSlot() def update_exit_on_batch_completion_setting(self, state=None): if state is None: state = self.actExitOnBatchCompletion.isChecked() self._settings.setValue("control/exit_on_batch_completion", str(state)) def set_visualizer(self, vis): self.visualizer = vis self.vtkWidget.Initialize() @pyqtSlot() def play_animation(self): """ play the animation """ self._logger.debug("Starting Playback") # if we are at the end, start from the beginning if self.playbackTime == self.currentEndTime: self.timeSlider.setValue(0) self.actPlayPause.setText("Pause Animation") self.actPlayPause.setIcon(QIcon(get_resource("pause.png"))) self.actPlayPause.triggered.disconnect(self.play_animation) self.actPlayPause.triggered.connect(self.pause_animation) self.playbackTimer.start(self.playbackTimeout) @pyqtSlot() def pause_animation(self): """ pause the animation """ self._logger.debug("Pausing Playback") self.playbackTimer.stop() self.actPlayPause.setText("Play Animation") self.actPlayPause.setIcon(QIcon(get_resource("play.png"))) self.actPlayPause.triggered.disconnect(self.pause_animation) self.actPlayPause.triggered.connect(self.play_animation) def stop_animation(self): """ Stop the animation if it is running and reset the playback time. """ self._logger.debug("Stopping Playback") if self.actPlayPause.text() == "Pause Animation": # animation is playing -> stop it self.playbackTimer.stop() self.actPlayPause.setText("Play Animation") self.actPlayPause.setIcon(QIcon(get_resource("play.png"))) self.actPlayPause.triggered.disconnect(self.pause_animation) self.actPlayPause.triggered.connect(self.play_animation) self.timeSlider.setValue(0) @pyqtSlot() def start_simulation(self): """ start the simulation and disable start button """ if self._current_regime_index is None: regime_name = "" else: regime_name = str(self.regime_list.item( self._current_regime_index).text()) self.statusLabel.setText("simulating {}".format(regime_name)) self._logger.info("Simulating: {}".format(regime_name)) self.actSimulateCurrent.setIcon(QIcon( get_resource("stop_simulation.png"))) self.actSimulateCurrent.setText("Abort &Simulation") self.actSimulateCurrent.triggered.disconnect(self.start_simulation) self.actSimulateCurrent.triggered.connect(self.stop_simulation) if not self.runningBatch: self.actSimulateAll.setDisabled(True) self.guiProgress = QProgressBar(self) self.sim.simulationProgressChanged.connect(self.guiProgress.setValue) self.statusBar().addWidget(self.guiProgress) self.runSimulation.emit() @pyqtSlot() def stop_simulation(self): self.stopSimulation.emit() def export_simulation_data(self, ok): """ Query the user for a custom name and export the current simulation results. :param ok: unused parameter from QAction.triggered() Signal """ self._save_data() def _save_data(self, file_path=None): """ Save the current simulation results. If *fie_name* is given, the result will be saved to the specified location, making automated exporting easier. Args: file_path(str): Absolute path of the target file. If `None` the use will be asked for a storage location. """ regime_name = self._regimes[self._current_regime_index]["Name"] if file_path is None: # get default path path = self._settings.value("path/simulation_results") # create canonic file name suggestion = self._simfile_name(regime_name) else: path = os.path.dirname(file_path) suggestion = os.path.basename(file_path) # check if path exists otherwise create it if not os.path.isdir(path): box = QMessageBox() box.setText("Export Folder does not exist yet.") box.setInformativeText("Do you want to create it? \n" "{}".format(os.path.abspath(path))) box.setStandardButtons(QMessageBox.Ok | QMessageBox.No) box.setDefaultButton(QMessageBox.Ok) ret = box.exec_() if ret == QMessageBox.Ok: os.makedirs(path) else: path = os.path.abspath(os.path.curdir) file_path = None # If no path was given, present the default and let the user choose if file_path is None: dialog = QFileDialog(self) dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setFileMode(QFileDialog.AnyFile) dialog.setDirectory(path) dialog.setNameFilter("PyMoskito Results (*.pmr)") dialog.selectFile(suggestion) if dialog.exec_(): file_path = dialog.selectedFiles()[0] else: self._logger.warning("Export Aborted") return -1 # ask whether this should act as new default path = os.path.abspath(os.path.dirname(file_path)) if path != self._settings.value("path/simulation_results"): box = QMessageBox() box.setText("Use this path as new default?") box.setInformativeText("{}".format(path)) box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) box.setDefaultButton(QMessageBox.Yes) ret = box.exec_() if ret == QMessageBox.Yes: self._settings.setValue("path/simulation_results", path) self.currentDataset.update({"regime name": regime_name}) with open(file_path, "wb") as f: pickle.dump(self.currentDataset, f, protocol=4) self.statusLabel.setText("results saved to {}".format(file_path)) self._logger.info("results saved to {}".format(file_path)) def _simfile_name(self, regime_name): """ Create a canonical name for a simulation result file """ suggestion = (time.strftime("%Y%m%d-%H%M%S") + "_" + regime_name + ".pmr") return suggestion def load_regime_dialog(self): regime_path = os.path.join(os.curdir) dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.ExistingFile) dialog.setDirectory(regime_path) dialog.setNameFilter("Simulation Regime files (*.sreg)") if dialog.exec_(): file = dialog.selectedFiles()[0] self.load_regimes_from_file(file) def load_regimes_from_file(self, file_name): """ load simulation regime from file :param file_name: """ self.regime_file_name = os.path.split(file_name)[-1][:-5] self._logger.info("loading regime file: {0}".format(self.regime_file_name)) with open(file_name.encode(), "r") as f: self._regimes += yaml.load(f) self._update_regime_list() if self._regimes: self.actSimulateAll.setDisabled(False) self._logger.info("loaded {} regimes".format(len(self._regimes))) self.statusBar().showMessage("loaded {} regimes.".format(len(self._regimes)), 1000) return def _update_regime_list(self): self.regime_list.clear() for reg in self._regimes: self._logger.debug("adding '{}' to regime list".format(reg["Name"])) self.regime_list.addItem(reg["Name"]) def remove_regime_items(self): if self.regime_list.currentRow() >= 0: # flag all selected files as invalid items = self.regime_list.selectedItems() for item in items: del self._regimes[self.regime_list.row(item)] self.regime_list.takeItem(self.regime_list.row(item)) @pyqtSlot(QListWidgetItem) def regime_dclicked(self, item): """ Apply the selected regime to the current target. """ self.apply_regime_by_name(str(item.text())) def apply_regime_by_name(self, regime_name): """ Apply the regime given by `regime_name` und update the regime index. Returns: bool: `True` if successful, `False` if errors occurred. """ # get regime idx try: idx = list(map(itemgetter("Name"), self._regimes)).index(regime_name) except ValueError as e: self._logger.error("apply_regime_by_name(): Error no regime called " "'{0}'".format(regime_name)) return False # apply return self._apply_regime_by_idx(idx) def _apply_regime_by_idx(self, index=0): """ Apply the given regime. Args: index(int): Index of the regime in the `RegimeList` . Returns: bool: `True` if successful, `False` if errors occurred. """ if index >= len(self._regimes): self._logger.error("applyRegime: index error! ({})".format(index)) return False reg_name = self._regimes[index]["Name"] self.statusBar().showMessage("regime {} applied.".format(reg_name), 1000) self._logger.info("applying regime '{}'".format(reg_name)) self._current_regime_index = index self._current_regime_name = reg_name return self.sim.set_regime(self._regimes[index]) @pyqtSlot() def start_regime_execution(self): """ Simulate all regimes in the regime list. """ self.actSimulateAll.setText("Stop Simulating &All Regimes") self.actSimulateAll.setIcon(QIcon(get_resource("stop_batch.png"))) self.actSimulateAll.triggered.disconnect(self.start_regime_execution) self.actSimulateAll.triggered.connect(self.stop_regime_excecution) self.runningBatch = True self._current_regime_index = -1 self.regimeFinished.emit() def run_next_regime(self): """ Execute the next regime in the regime batch. """ # are we finished? if self._current_regime_index == len(self._regimes) - 1: self.finishedRegimeBatch.emit(True) return suc = self._apply_regime_by_idx(self._current_regime_index + 1) if not suc: self.finishedRegimeBatch.emit(False) return self.start_simulation() @pyqtSlot() def stop_regime_excecution(self): """ Stop the batch process. """ self.stopSimulation.emit() self.finishedRegimeBatch.emit(False) def regime_batch_finished(self, status): self.runningBatch = False self.actSimulateAll.setDisabled(False) self.actSave.setDisabled(True) self.actSimulateAll.setText("Simulate &All Regimes") self.actSimulateAll.setIcon(QIcon(get_resource("execute_regimes.png"))) self.actSimulateAll.triggered.disconnect(self.stop_regime_excecution) self.actSimulateAll.triggered.connect(self.start_regime_execution) if status: self.statusLabel.setText("All regimes have been simulated") self._logger.info("All Regimes have been simulated") else: self._logger.error("Batch simulation has been aborted") if self._settings.value("control/exit_on_batch_completion") == "True": self._logger.info("Shutting down SimulationGUI") self.close() @pyqtSlot(str, dict, name="new_simulation_data") def new_simulation_data(self, status, data): """ Slot to be called when the simulation interface has completed the current job and new data is available. Args: status (str): Status of the simulation, either - `finished` : Simulation has been finished successfully or - `failed` : Simulation has failed. data (dict): Dictionary, holding the simulation data. """ self._logger.info("Simulation {}".format(status)) self.statusLabel.setText("Simulation {}".format(status)) self.actSimulateCurrent.setText("&Simulate Current Regime") self.actSimulateCurrent.setIcon(QIcon(get_resource("simulate.png"))) self.actSimulateCurrent.triggered.disconnect(self.stop_simulation) self.actSimulateCurrent.triggered.connect(self.start_simulation) self.actPlayPause.setDisabled(False) self.actStop.setDisabled(False) self.actSave.setDisabled(False) self.speedControl.setDisabled(False) self.timeSlider.setDisabled(False) self.sim.simulationProgressChanged.disconnect(self.guiProgress.setValue) self.statusBar().removeWidget(self.guiProgress) self.stop_animation() self.currentDataset = data if data: self._read_results() self._update_data_list() self._update_plots() if self._settings.value("control/autoplay_animation") == "True": self.actPlayPause.trigger() if self.runningBatch: regime_name = self._regimes[self._current_regime_index]["Name"] file_name = self._simfile_name(regime_name) self._save_data(os.path.join( self._settings.value("path/simulation_results"), file_name)) self.regimeFinished.emit() else: self.actSimulateAll.setDisabled(False) def _read_results(self): state = self.currentDataset["results"]["Solver"] self.interpolator = interp1d(self.currentDataset["results"]["time"], state, axis=0, bounds_error=False, fill_value=(state[0], state[-1])) self.currentStepSize = 1.0/self.currentDataset["simulation"][ "measure rate"] self.currentEndTime = self.currentDataset["simulation"]["end time"] self.validData = True def increment_playback_speed(self): self.speedControl.setValue(self.speedControl.value() + self.speedControl.singleStep()) def decrement_playback_speed(self): self.speedControl.setValue(self.speedControl.value() - self.speedControl.singleStep()) def reset_playback_speed(self): self.speedControl.setValue((self.speedControl.maximum() - self.speedControl.minimum())/2) def set_slowest_playback_speed(self): self.speedControl.setValue(self.speedControl.minimum()) def set_fastest_playback_speed(self): self.speedControl.setValue(self.speedControl.maximum()) def update_playback_speed(self, val): """ adjust playback time to slider value :param val: """ maximum = self.speedControl.maximum() self.playbackGain = 10**(3.0 * (val - maximum / 2) / maximum) @pyqtSlot() def increment_playback_time(self): """ go one time step forward in playback """ if self.playbackTime == self.currentEndTime: self.pause_animation() return increment = self.playbackGain * self.playbackTimeout / 1000 self.playbackTime = min(self.currentEndTime, self.playbackTime + increment) pos = int(self.playbackTime / self.currentEndTime * self.timeSliderRange) self.timeSlider.blockSignals(True) self.timeSlider.setValue(pos) self.timeSlider.blockSignals(False) self.playbackTimeChanged.emit() def update_playback_time(self): """ adjust playback time to slider value """ self.playbackTime = self.timeSlider.value()/self.timeSliderRange*self.currentEndTime self.playbackTimeChanged.emit() return def update_gui(self): """ updates the graphical user interface, including: - timestamp - visualisation - time cursor in diagrams """ if not self.validData: return self.timeLabel.setText("current time: %4f" % self.playbackTime) # update time cursor in plots self._update_time_cursors() # update state of rendering if self.visualizer: state = self.interpolator(self.playbackTime) self.visualizer.update_scene(state) if isinstance(self.visualizer, MplVisualizer): pass elif isinstance(self.visualizer, VtkVisualizer): self.vtkWidget.GetRenderWindow().Render() def _update_data_list(self): self.dataList.clear() for module_name, results in self.currentDataset["results"].items(): if not isinstance(results, np.ndarray): continue if len(results.shape) == 1: self.dataList.insertItem(0, module_name) elif len(results.shape) == 2: for col in range(results.shape[1]): self.dataList.insertItem( 0, self._build_entry_name(module_name, (col, )) ) elif len(results.shape) == 3: for col in range(results.shape[1]): for der in range(results.shape[2]): self.dataList.insertItem( 0, self._build_entry_name(module_name, (col, der)) ) def _build_entry_name(self, module_name, idx): """ Construct an identifier for a given entry of a module. Args: module_name (str): name of the module the entry belongs to. idx (tuple): Index of the entry. Returns: str: Identifier to use for display. """ # save the user from defining 1d entries via tuples if len(idx) == 1: m_idx = idx[0] else: m_idx = idx mod_settings = self.currentDataset["modules"] info = mod_settings.get(module_name, {}).get("output_info", None) if info: if m_idx in info: return ".".join([module_name, info[m_idx]["Name"]]) return ".".join([module_name] + [str(i) for i in idx]) def _get_index_from_suffix(self, module_name, suffix): info = self.currentDataset["modules"].get(module_name, {}).get( "output_info", None) idx = next((i for i in info if info[i]["Name"] == suffix), None) return idx def _get_units(self, entry): """ Return the unit that corresponds to a given entry. If no information is available, None is returned. Args: entry (str): Name of the entry. This can either be "Model.a.b" where a and b are numbers or if information is available "Model.Signal" where signal is the name of that part. Returns: """ args = entry.split(".") module_name = args.pop(0) info = self.currentDataset["modules"].get(module_name, {}).get( "output_info", None) if info is None: return None if len(args) == 1: try: idx = int(args[0]) except ValueError: idx = next((i for i in info if info[i]["Name"] == args[0]), None) else: idx = (int(a) for a in args) return info[idx]["Unit"] def create_plot(self, item): """ Creates a plot widget based on the given item. If a plot for this item is already open no new plot is created but the existing one is raised up again. Args: item(Qt.ListItem): Item to plot. """ title = str(item.text()) if title in self.non_plotting_docks: self._logger.error("Title '{}' not allowed for a plot window since" "it would shadow on of the reserved " "names".format(title)) # check if plot has already been opened if title in self.area.findAll()[1]: self.area.docks[title].raiseDock() return # collect data data = self._get_data_by_name(title) t = self.currentDataset["results"]["time"] unit = self._get_units(title) if "." in title: name = title.split(".")[1] else: name = title # create plot widget widget = pg.PlotWidget(title=title) widget.showGrid(True, True) widget.plot(x=t, y=data) widget.getPlotItem().getAxis("bottom").setLabel(text="Time", units="s") widget.getPlotItem().getAxis("left").setLabel(text=name, units=unit) # add a time line time_line = pg.InfiniteLine(self.playbackTime, angle=90, movable=False, pen=pg.mkPen("#FF0000", width=2.0)) widget.getPlotItem().addItem(time_line) # create dock container and add it to dock area dock = pg.dockarea.Dock(title, closable=True) dock.addWidget(widget) self.area.addDock(dock, "above", self.plotDockPlaceholder) def _get_data_by_name(self, name): tmp = name.split(".") module_name = tmp[0] if len(tmp) == 1: data = np.array(self.currentDataset["results"][module_name]) elif len(tmp) == 2: try: idx = int(tmp[1]) except ValueError: idx = self._get_index_from_suffix(module_name, tmp[1]) finally: data = self.currentDataset["results"][module_name][..., idx] elif len(tmp) == 3: idx = int(tmp[1]) der = int(tmp[2]) data = self.currentDataset["results"][module_name][..., idx, der] else: raise ValueError("Format not supported") return data def _update_time_cursors(self): """ Update the time lines of all plot windows """ for title, dock in self.area.findAll()[1].items(): if title in self.non_plotting_docks: continue for widget in dock.widgets: for item in widget.getPlotItem().items: if isinstance(item, pg.InfiniteLine): item.setValue(self.playbackTime) def _update_plots(self): """ Update the data in all plot windows """ for title, dock in self.area.findAll()[1].items(): if title in self.non_plotting_docks: continue if not self.dataList.findItems(dock.name(), Qt.MatchExactly): # no data for this plot -> remove it dock.close() continue for widget in dock.widgets: for item in widget.getPlotItem().items: if isinstance(item, pg.PlotDataItem): x_data = self.currentDataset["results"]["time"] y_data = self._get_data_by_name(dock.name()) item.setData(x=x_data, y=y_data) @pyqtSlot(QModelIndex) def target_view_changed(self, index): self.targetView.resizeColumnToContents(0) def postprocessing_clicked(self): """ starts the post- and metaprocessing application """ self._logger.info("launching postprocessor") self.statusBar().showMessage("launching postprocessor", 1000) if self.postprocessor is None: self.postprocessor = PostProcessor() self.postprocessor.show() def reset_camera_clicked(self): """ reset camera in vtk window """ self.visualizer.reset_camera() self.vtkWidget.GetRenderWindow().Render() def show_info(self): icon_lic = open(get_resource("license.txt"), "r").read() text = "This application was build using PyMoskito ver. {} .<br />" \ "PyMoskito is free software distributed under GPLv3. <br />" \ "It is developed by members of the " \ "<a href=\'https://tu-dresden.de/ing/elektrotechnik/rst'>" \ "Institute of Control Theory</a>" \ " at the <a href=\'https://tu-dresden.de'>" \ "Dresden University of Technology</a>. <br />" \ "".format(pkg_resources.require("PyMoskito")[0].version) \ + "<br />" + icon_lic box = QMessageBox.about(self, "PyMoskito", text) def show_online_docs(self): webbrowser.open("https://pymoskito.readthedocs.org") def closeEvent(self, QCloseEvent): self._logger.info("Close Event received, shutting down.") logging.getLogger().removeHandler(self.textLogger) super().closeEvent(QCloseEvent)
class ReTextWindow(QMainWindow): def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.resize(950, 700) screenRect = QDesktopWidget().screenGeometry() if globalSettings.windowGeometry: self.restoreGeometry(globalSettings.windowGeometry) else: self.move((screenRect.width()-self.width())/2, (screenRect.height()-self.height())/2) if not screenRect.contains(self.geometry()): self.showMaximized() if globalSettings.iconTheme: QIcon.setThemeName(globalSettings.iconTheme) if QIcon.themeName() in ('hicolor', ''): if not QFile.exists(icon_path + 'document-new.png'): QIcon.setThemeName(get_icon_theme()) if QFile.exists(icon_path+'retext.png'): self.setWindowIcon(QIcon(icon_path+'retext.png')) elif QFile.exists('/usr/share/pixmaps/retext.png'): self.setWindowIcon(QIcon('/usr/share/pixmaps/retext.png')) else: self.setWindowIcon(QIcon.fromTheme('retext', QIcon.fromTheme('accessories-text-editor'))) self.tabWidget = QTabWidget(self) self.initTabWidget() self.setCentralWidget(self.tabWidget) self.tabWidget.currentChanged.connect(self.changeIndex) self.tabWidget.tabCloseRequested.connect(self.closeTab) toolBar = QToolBar(self.tr('File toolbar'), self) self.addToolBar(Qt.TopToolBarArea, toolBar) self.editBar = QToolBar(self.tr('Edit toolbar'), self) self.addToolBar(Qt.TopToolBarArea, self.editBar) self.searchBar = QToolBar(self.tr('Search toolbar'), self) self.addToolBar(Qt.BottomToolBarArea, self.searchBar) toolBar.setVisible(not globalSettings.hideToolBar) self.editBar.setVisible(not globalSettings.hideToolBar) self.actionNew = self.act(self.tr('New'), 'document-new', self.createNew, shct=QKeySequence.New) self.actionNew.setPriority(QAction.LowPriority) self.actionOpen = self.act(self.tr('Open'), 'document-open', self.openFile, shct=QKeySequence.Open) self.actionOpen.setPriority(QAction.LowPriority) self.actionSetEncoding = self.act(self.tr('Set encoding'), trig=self.showEncodingDialog) self.actionSetEncoding.setEnabled(False) self.actionReload = self.act(self.tr('Reload'), 'view-refresh', lambda: self.currentTab.readTextFromFile()) self.actionReload.setEnabled(False) self.actionSave = self.act(self.tr('Save'), 'document-save', self.saveFile, shct=QKeySequence.Save) self.actionSave.setEnabled(False) self.actionSave.setPriority(QAction.LowPriority) self.actionSaveAs = self.act(self.tr('Save as'), 'document-save-as', self.saveFileAs, shct=QKeySequence.SaveAs) self.actionNextTab = self.act(self.tr('Next tab'), 'go-next', lambda: self.switchTab(1), shct=Qt.CTRL+Qt.Key_PageDown) self.actionPrevTab = self.act(self.tr('Previous tab'), 'go-previous', lambda: self.switchTab(-1), shct=Qt.CTRL+Qt.Key_PageUp) self.actionPrint = self.act(self.tr('Print'), 'document-print', self.printFile, shct=QKeySequence.Print) self.actionPrint.setPriority(QAction.LowPriority) self.actionPrintPreview = self.act(self.tr('Print preview'), 'document-print-preview', self.printPreview) self.actionViewHtml = self.act(self.tr('View HTML code'), 'text-html', self.viewHtml) self.actionChangeEditorFont = self.act(self.tr('Change editor font'), trig=self.changeEditorFont) self.actionChangePreviewFont = self.act(self.tr('Change preview font'), trig=self.changePreviewFont) self.actionSearch = self.act(self.tr('Find text'), 'edit-find', shct=QKeySequence.Find) self.actionSearch.setCheckable(True) self.actionSearch.triggered[bool].connect(self.searchBar.setVisible) self.searchBar.visibilityChanged.connect(self.searchBarVisibilityChanged) self.actionPreview = self.act(self.tr('Preview'), shct=Qt.CTRL+Qt.Key_E, trigbool=self.preview) if QIcon.hasThemeIcon('document-preview'): self.actionPreview.setIcon(QIcon.fromTheme('document-preview')) elif QIcon.hasThemeIcon('preview-file'): self.actionPreview.setIcon(QIcon.fromTheme('preview-file')) elif QIcon.hasThemeIcon('x-office-document'): self.actionPreview.setIcon(QIcon.fromTheme('x-office-document')) else: self.actionPreview.setIcon(QIcon(icon_path+'document-preview.png')) self.actionLivePreview = self.act(self.tr('Live preview'), shct=Qt.CTRL+Qt.Key_L, trigbool=self.enableLivePreview) menuPreview = QMenu() menuPreview.addAction(self.actionLivePreview) self.actionPreview.setMenu(menuPreview) self.actionTableMode = self.act(self.tr('Table mode'), shct=Qt.CTRL+Qt.Key_T, trigbool=lambda x: self.currentTab.editBox.enableTableMode(x)) if ReTextFakeVimHandler: self.actionFakeVimMode = self.act(self.tr('FakeVim mode'), shct=Qt.CTRL+Qt.ALT+Qt.Key_V, trigbool=self.enableFakeVimMode) if globalSettings.useFakeVim: self.actionFakeVimMode.setChecked(True) self.enableFakeVimMode(True) self.actionFullScreen = self.act(self.tr('Fullscreen mode'), 'view-fullscreen', shct=Qt.Key_F11, trigbool=self.enableFullScreen) self.actionFullScreen.setPriority(QAction.LowPriority) self.actionConfig = self.act(self.tr('Preferences'), icon='preferences-system', trig=self.openConfigDialog) self.actionConfig.setMenuRole(QAction.PreferencesRole) self.actionSaveHtml = self.act('HTML', 'text-html', self.saveFileHtml) self.actionPdf = self.act('PDF', 'application-pdf', self.savePdf) self.actionOdf = self.act('ODT', 'x-office-document', self.saveOdf) self.getExportExtensionsList() self.actionQuit = self.act(self.tr('Quit'), 'application-exit', shct=QKeySequence.Quit) self.actionQuit.setMenuRole(QAction.QuitRole) self.actionQuit.triggered.connect(self.close) self.actionUndo = self.act(self.tr('Undo'), 'edit-undo', lambda: self.currentTab.editBox.undo(), shct=QKeySequence.Undo) self.actionRedo = self.act(self.tr('Redo'), 'edit-redo', lambda: self.currentTab.editBox.redo(), shct=QKeySequence.Redo) self.actionCopy = self.act(self.tr('Copy'), 'edit-copy', lambda: self.currentTab.editBox.copy(), shct=QKeySequence.Copy) self.actionCut = self.act(self.tr('Cut'), 'edit-cut', lambda: self.currentTab.editBox.cut(), shct=QKeySequence.Cut) self.actionPaste = self.act(self.tr('Paste'), 'edit-paste', lambda: self.currentTab.editBox.paste(), shct=QKeySequence.Paste) self.actionUndo.setEnabled(False) self.actionRedo.setEnabled(False) self.actionCopy.setEnabled(False) self.actionCut.setEnabled(False) qApp = QApplication.instance() qApp.clipboard().dataChanged.connect(self.clipboardDataChanged) self.clipboardDataChanged() if enchant_available: self.actionEnableSC = self.act(self.tr('Enable'), trigbool=self.enableSpellCheck) self.actionSetLocale = self.act(self.tr('Set locale'), trig=self.changeLocale) self.actionWebKit = self.act(self.tr('Use WebKit renderer'), trigbool=self.enableWebKit) self.actionWebKit.setChecked(globalSettings.useWebKit) self.actionShow = self.act(self.tr('Show directory'), 'system-file-manager', self.showInDir) self.actionFind = self.act(self.tr('Next'), 'go-next', self.find, shct=QKeySequence.FindNext) self.actionFindPrev = self.act(self.tr('Previous'), 'go-previous', lambda: self.find(back=True), shct=QKeySequence.FindPrevious) self.actionCloseSearch = self.act(self.tr('Close'), 'window-close', lambda: self.searchBar.setVisible(False)) self.actionCloseSearch.setPriority(QAction.LowPriority) self.actionHelp = self.act(self.tr('Get help online'), 'help-contents', self.openHelp) self.aboutWindowTitle = self.tr('About ReText') self.actionAbout = self.act(self.aboutWindowTitle, 'help-about', self.aboutDialog) self.actionAbout.setMenuRole(QAction.AboutRole) self.actionAboutQt = self.act(self.tr('About Qt')) self.actionAboutQt.setMenuRole(QAction.AboutQtRole) self.actionAboutQt.triggered.connect(qApp.aboutQt) availableMarkups = markups.get_available_markups() if not availableMarkups: print('Warning: no markups are available!') self.defaultMarkup = availableMarkups[0] if availableMarkups else None if globalSettings.defaultMarkup: mc = markups.find_markup_class_by_name(globalSettings.defaultMarkup) if mc and mc.available(): self.defaultMarkup = mc if len(availableMarkups) > 1: self.chooseGroup = QActionGroup(self) markupActions = [] for markup in availableMarkups: markupAction = self.act(markup.name, trigbool=self.markupFunction(markup)) if markup == self.defaultMarkup: markupAction.setChecked(True) self.chooseGroup.addAction(markupAction) markupActions.append(markupAction) self.actionBold = self.act(self.tr('Bold'), shct=QKeySequence.Bold, trig=lambda: self.insertFormatting('bold')) self.actionItalic = self.act(self.tr('Italic'), shct=QKeySequence.Italic, trig=lambda: self.insertFormatting('italic')) self.actionUnderline = self.act(self.tr('Underline'), shct=QKeySequence.Underline, trig=lambda: self.insertFormatting('underline')) self.usefulTags = ('header', 'italic', 'bold', 'underline', 'numbering', 'bullets', 'image', 'link', 'inline code', 'code block', 'blockquote') self.usefulChars = ('deg', 'divide', 'dollar', 'hellip', 'laquo', 'larr', 'lsquo', 'mdash', 'middot', 'minus', 'nbsp', 'ndash', 'raquo', 'rarr', 'rsquo', 'times') self.formattingBox = QComboBox(self.editBar) self.formattingBox.addItem(self.tr('Formatting')) self.formattingBox.addItems(self.usefulTags) self.formattingBox.activated[str].connect(self.insertFormatting) self.symbolBox = QComboBox(self.editBar) self.symbolBox.addItem(self.tr('Symbols')) self.symbolBox.addItems(self.usefulChars) self.symbolBox.activated.connect(self.insertSymbol) self.updateStyleSheet() menubar = self.menuBar() menuFile = menubar.addMenu(self.tr('File')) menuEdit = menubar.addMenu(self.tr('Edit')) menuHelp = menubar.addMenu(self.tr('Help')) menuFile.addAction(self.actionNew) menuFile.addAction(self.actionOpen) self.menuRecentFiles = menuFile.addMenu(self.tr('Open recent')) self.menuRecentFiles.aboutToShow.connect(self.updateRecentFiles) menuFile.addAction(self.actionShow) menuFile.addAction(self.actionSetEncoding) menuFile.addAction(self.actionReload) menuFile.addSeparator() menuFile.addAction(self.actionSave) menuFile.addAction(self.actionSaveAs) menuFile.addSeparator() menuFile.addAction(self.actionNextTab) menuFile.addAction(self.actionPrevTab) menuFile.addSeparator() menuExport = menuFile.addMenu(self.tr('Export')) menuExport.addAction(self.actionSaveHtml) menuExport.addAction(self.actionOdf) menuExport.addAction(self.actionPdf) if self.extensionActions: menuExport.addSeparator() for action, mimetype in self.extensionActions: menuExport.addAction(action) menuExport.aboutToShow.connect(self.updateExtensionsVisibility) menuFile.addAction(self.actionPrint) menuFile.addAction(self.actionPrintPreview) menuFile.addSeparator() menuFile.addAction(self.actionQuit) menuEdit.addAction(self.actionUndo) menuEdit.addAction(self.actionRedo) menuEdit.addSeparator() menuEdit.addAction(self.actionCut) menuEdit.addAction(self.actionCopy) menuEdit.addAction(self.actionPaste) menuEdit.addSeparator() if enchant_available: menuSC = menuEdit.addMenu(self.tr('Spell check')) menuSC.addAction(self.actionEnableSC) menuSC.addAction(self.actionSetLocale) menuEdit.addAction(self.actionSearch) menuEdit.addAction(self.actionChangeEditorFont) menuEdit.addAction(self.actionChangePreviewFont) menuEdit.addSeparator() if len(availableMarkups) > 1: self.menuMode = menuEdit.addMenu(self.tr('Default markup')) for markupAction in markupActions: self.menuMode.addAction(markupAction) menuFormat = menuEdit.addMenu(self.tr('Formatting')) menuFormat.addAction(self.actionBold) menuFormat.addAction(self.actionItalic) menuFormat.addAction(self.actionUnderline) menuEdit.addAction(self.actionWebKit) menuEdit.addSeparator() menuEdit.addAction(self.actionViewHtml) menuEdit.addAction(self.actionPreview) menuEdit.addAction(self.actionTableMode) if ReTextFakeVimHandler: menuEdit.addAction(self.actionFakeVimMode) menuEdit.addSeparator() menuEdit.addAction(self.actionFullScreen) menuEdit.addAction(self.actionConfig) menuHelp.addAction(self.actionHelp) menuHelp.addSeparator() menuHelp.addAction(self.actionAbout) menuHelp.addAction(self.actionAboutQt) toolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) toolBar.addAction(self.actionNew) toolBar.addSeparator() toolBar.addAction(self.actionOpen) toolBar.addAction(self.actionSave) toolBar.addAction(self.actionPrint) toolBar.addSeparator() toolBar.addAction(self.actionPreview) toolBar.addAction(self.actionFullScreen) self.editBar.addAction(self.actionUndo) self.editBar.addAction(self.actionRedo) self.editBar.addSeparator() self.editBar.addAction(self.actionCut) self.editBar.addAction(self.actionCopy) self.editBar.addAction(self.actionPaste) self.editBar.addSeparator() self.editBar.addWidget(self.formattingBox) self.editBar.addWidget(self.symbolBox) self.searchEdit = QLineEdit(self.searchBar) self.searchEdit.setPlaceholderText(self.tr('Search')) self.searchEdit.returnPressed.connect(self.find) self.csBox = QCheckBox(self.tr('Case sensitively'), self.searchBar) self.searchBar.addWidget(self.searchEdit) self.searchBar.addSeparator() self.searchBar.addWidget(self.csBox) self.searchBar.addAction(self.actionFindPrev) self.searchBar.addAction(self.actionFind) self.searchBar.addAction(self.actionCloseSearch) self.searchBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.searchBar.setVisible(False) self.autoSaveEnabled = globalSettings.autoSave if self.autoSaveEnabled: timer = QTimer(self) timer.start(60000) timer.timeout.connect(self.saveAll) self.ind = None if enchant_available: self.sl = globalSettings.spellCheckLocale if self.sl: try: enchant.Dict(self.sl) except Exception as e: print(e, file=sys.stderr) self.sl = None if globalSettings.spellCheck: self.actionEnableSC.setChecked(True) self.fileSystemWatcher = QFileSystemWatcher() self.fileSystemWatcher.fileChanged.connect(self.fileChanged) def iterateTabs(self): for i in range(self.tabWidget.count()): yield self.tabWidget.widget(i).tab def updateStyleSheet(self): if globalSettings.styleSheet: sheetfile = QFile(globalSettings.styleSheet) sheetfile.open(QIODevice.ReadOnly) self.ss = QTextStream(sheetfile).readAll() sheetfile.close() else: palette = QApplication.palette() self.ss = 'html { color: %s; }\n' % palette.color(QPalette.WindowText).name() self.ss += 'td, th { border: 1px solid #c3c3c3; padding: 0 3px 0 3px; }\n' self.ss += 'table { border-collapse: collapse; }\n' def initTabWidget(self): def dragEnterEvent(e): e.acceptProposedAction() def dropEvent(e): fn = bytes(e.mimeData().data('text/plain')).decode().rstrip() if fn.startswith('file:'): fn = QUrl(fn).toLocalFile() self.openFileWrapper(fn) self.tabWidget.setTabsClosable(True) self.tabWidget.setAcceptDrops(True) self.tabWidget.setMovable(True) self.tabWidget.dragEnterEvent = dragEnterEvent self.tabWidget.dropEvent = dropEvent def act(self, name, icon=None, trig=None, trigbool=None, shct=None): if not isinstance(shct, QKeySequence): shct = QKeySequence(shct) if icon: action = QAction(self.actIcon(icon), name, self) else: action = QAction(name, self) if trig: action.triggered.connect(trig) elif trigbool: action.setCheckable(True) action.triggered[bool].connect(trigbool) if shct: action.setShortcut(shct) return action def actIcon(self, name): return QIcon.fromTheme(name, QIcon(icon_path+name+'.png')) def printError(self): import traceback print('Exception occured while parsing document:', file=sys.stderr) traceback.print_exc() def createTab(self, fileName): self.currentTab = ReTextTab(self, fileName, previewState=int(globalSettings.livePreviewByDefault)) self.tabWidget.addTab(self.currentTab.getSplitter(), self.tr("New document")) def closeTab(self, ind): if self.maybeSave(ind): if self.tabWidget.count() == 1: self.createTab("") currentWidget = self.tabWidget.widget(ind) if currentWidget.tab.fileName: self.fileSystemWatcher.removePath(currentWidget.tab.fileName) del currentWidget.tab self.tabWidget.removeTab(ind) def docTypeChanged(self): markupClass = self.currentTab.getMarkupClass() if type(self.currentTab.markup) != markupClass: self.currentTab.setMarkupClass(markupClass) self.currentTab.updatePreviewBox() dtMarkdown = (markupClass == markups.MarkdownMarkup) dtMkdOrReST = dtMarkdown or (markupClass == markups.ReStructuredTextMarkup) self.formattingBox.setEnabled(dtMarkdown) self.symbolBox.setEnabled(dtMarkdown) self.actionUnderline.setEnabled(dtMarkdown) self.actionBold.setEnabled(dtMkdOrReST) self.actionItalic.setEnabled(dtMkdOrReST) canReload = bool(self.currentTab.fileName) and not self.autoSaveActive() self.actionSetEncoding.setEnabled(canReload) self.actionReload.setEnabled(canReload) def changeIndex(self, ind): self.currentTab = self.tabWidget.currentWidget().tab editBox = self.currentTab.editBox previewState = self.currentTab.previewState self.actionUndo.setEnabled(editBox.document().isUndoAvailable()) self.actionRedo.setEnabled(editBox.document().isRedoAvailable()) self.actionCopy.setEnabled(editBox.textCursor().hasSelection()) self.actionCut.setEnabled(editBox.textCursor().hasSelection()) self.actionPreview.setChecked(previewState >= PreviewLive) self.actionLivePreview.setChecked(previewState == PreviewLive) self.actionTableMode.setChecked(editBox.tableModeEnabled) self.editBar.setEnabled(previewState < PreviewNormal) self.ind = ind if self.currentTab.fileName: self.setCurrentFile() else: self.setWindowTitle(self.tr('New document') + '[*]') self.docTypeChanged() self.modificationChanged(editBox.document().isModified()) editBox.setFocus(Qt.OtherFocusReason) def changeEditorFont(self): font, ok = QFontDialog.getFont(globalSettings.editorFont, self) if ok: globalSettings.editorFont = font for tab in self.iterateTabs(): tab.editBox.updateFont() def changePreviewFont(self): font, ok = QFontDialog.getFont(globalSettings.font, self) if ok: globalSettings.font = font for tab in self.iterateTabs(): tab.updatePreviewBox() def preview(self, viewmode): self.currentTab.previewState = viewmode * 2 self.actionLivePreview.setChecked(False) self.editBar.setDisabled(viewmode) self.currentTab.updateBoxesVisibility() if viewmode: self.currentTab.updatePreviewBox() def enableLivePreview(self, livemode): self.currentTab.previewState = int(livemode) self.actionPreview.setChecked(livemode) self.editBar.setEnabled(True) self.currentTab.updateBoxesVisibility() if livemode: self.currentTab.updatePreviewBox() def enableWebKit(self, enable): globalSettings.useWebKit = enable for i in range(self.tabWidget.count()): splitter = self.tabWidget.widget(i) tab = splitter.tab tab.previewBox.disconnectExternalSignals() tab.previewBox.setParent(None) tab.previewBox.deleteLater() tab.previewBox = tab.createPreviewBox(tab.editBox) tab.previewBox.setMinimumWidth(125) splitter.addWidget(tab.previewBox) splitter.setSizes((50, 50)) tab.updatePreviewBox() tab.updateBoxesVisibility() def enableCopy(self, copymode): self.actionCopy.setEnabled(copymode) self.actionCut.setEnabled(copymode) def enableFullScreen(self, yes): if yes: self.showFullScreen() else: self.showNormal() def openConfigDialog(self): dlg = ConfigDialog(self) dlg.setWindowTitle(self.tr('Preferences')) dlg.show() def enableFakeVimMode(self, yes): globalSettings.useFakeVim = yes if yes: FakeVimMode.init(self) for tab in self.iterateTabs(): tab.installFakeVimHandler() else: FakeVimMode.exit(self) def enableSpellCheck(self, yes): if yes: self.setAllDictionaries(enchant.Dict(self.sl or None)) else: self.setAllDictionaries(None) globalSettings.spellCheck = yes def setAllDictionaries(self, dictionary): for tab in self.iterateTabs(): hl = tab.highlighter hl.dictionary = dictionary hl.rehighlight() def changeLocale(self): if self.sl: localedlg = LocaleDialog(self, defaultText=self.sl) else: localedlg = LocaleDialog(self) if localedlg.exec() != QDialog.Accepted: return sl = localedlg.localeEdit.text() setdefault = localedlg.checkBox.isChecked() if sl: try: sl = str(sl) enchant.Dict(sl) except Exception as e: QMessageBox.warning(self, '', str(e)) else: self.sl = sl self.enableSpellCheck(self.actionEnableSC.isChecked()) else: self.sl = None self.enableSpellCheck(self.actionEnableSC.isChecked()) if setdefault: globalSettings.spellCheckLocale = sl def searchBarVisibilityChanged(self, visible): self.actionSearch.setChecked(visible) if visible: self.searchEdit.setFocus(Qt.ShortcutFocusReason) def find(self, back=False): flags = QTextDocument.FindFlags() if back: flags |= QTextDocument.FindBackward if self.csBox.isChecked(): flags |= QTextDocument.FindCaseSensitively text = self.searchEdit.text() editBox = self.currentTab.editBox cursor = editBox.textCursor() newCursor = editBox.document().find(text, cursor, flags) if not newCursor.isNull(): editBox.setTextCursor(newCursor) return self.setSearchEditColor(True) cursor.movePosition(QTextCursor.End if back else QTextCursor.Start) newCursor = editBox.document().find(text, cursor, flags) if not newCursor.isNull(): editBox.setTextCursor(newCursor) return self.setSearchEditColor(True) self.setSearchEditColor(False) def setSearchEditColor(self, found): palette = self.searchEdit.palette() palette.setColor(QPalette.Active, QPalette.Base, Qt.white if found else QColor(255, 102, 102)) self.searchEdit.setPalette(palette) def showInDir(self): if self.currentTab.fileName: path = QFileInfo(self.currentTab.fileName).path() QDesktopServices.openUrl(QUrl.fromLocalFile(path)) else: QMessageBox.warning(self, '', self.tr("Please, save the file somewhere.")) def setCurrentFile(self): self.setWindowTitle("") self.tabWidget.setTabText(self.ind, self.currentTab.getDocumentTitle(baseName=True)) self.tabWidget.setTabToolTip(self.ind, self.currentTab.fileName or '') self.setWindowFilePath(self.currentTab.fileName) files = readListFromSettings("recentFileList") while self.currentTab.fileName in files: files.remove(self.currentTab.fileName) files.insert(0, self.currentTab.fileName) if len(files) > 10: del files[10:] writeListToSettings("recentFileList", files) QDir.setCurrent(QFileInfo(self.currentTab.fileName).dir().path()) self.docTypeChanged() def createNew(self, text=None): self.createTab("") self.ind = self.tabWidget.count()-1 self.tabWidget.setCurrentIndex(self.ind) if text: self.currentTab.editBox.textCursor().insertText(text) def switchTab(self, shift=1): self.tabWidget.setCurrentIndex((self.ind + shift) % self.tabWidget.count()) def updateRecentFiles(self): self.menuRecentFiles.clear() self.recentFilesActions = [] filesOld = readListFromSettings("recentFileList") files = [] for f in filesOld: if QFile.exists(f): files.append(f) self.recentFilesActions.append(self.act(f, trig=self.openFunction(f))) writeListToSettings("recentFileList", files) for action in self.recentFilesActions: self.menuRecentFiles.addAction(action) def markupFunction(self, markup): return lambda: self.setDefaultMarkup(markup) def openFunction(self, fileName): return lambda: self.openFileWrapper(fileName) def extensionFunction(self, data): return lambda: \ self.runExtensionCommand(data['Exec'], data['FileFilter'], data['DefaultExtension']) def getExportExtensionsList(self): extensions = [] for extsprefix in datadirs: extsdir = QDir(extsprefix+'/export-extensions/') if extsdir.exists(): for fileInfo in extsdir.entryInfoList(['*.desktop', '*.ini'], QDir.Files | QDir.Readable): extensions.append(self.readExtension(fileInfo.filePath())) locale = QLocale.system().name() self.extensionActions = [] for extension in extensions: try: if ('Name[%s]' % locale) in extension: name = extension['Name[%s]' % locale] elif ('Name[%s]' % locale.split('_')[0]) in extension: name = extension['Name[%s]' % locale.split('_')[0]] else: name = extension['Name'] data = {} for prop in ('FileFilter', 'DefaultExtension', 'Exec'): if 'X-ReText-'+prop in extension: data[prop] = extension['X-ReText-'+prop] elif prop in extension: data[prop] = extension[prop] else: data[prop] = '' action = self.act(name, trig=self.extensionFunction(data)) if 'Icon' in extension: action.setIcon(self.actIcon(extension['Icon'])) mimetype = extension['MimeType'] if 'MimeType' in extension else None except KeyError: print('Failed to parse extension: Name is required', file=sys.stderr) else: self.extensionActions.append((action, mimetype)) def updateExtensionsVisibility(self): markupClass = self.currentTab.getMarkupClass() for action in self.extensionActions: if markupClass is None: action[0].setEnabled(False) continue mimetype = action[1] if mimetype == None: enabled = True elif markupClass == markups.MarkdownMarkup: enabled = (mimetype in ("text/x-retext-markdown", "text/x-markdown")) elif markupClass == markups.ReStructuredTextMarkup: enabled = (mimetype in ("text/x-retext-rst", "text/x-rst")) else: enabled = False action[0].setEnabled(enabled) def readExtension(self, fileName): extFile = QFile(fileName) extFile.open(QIODevice.ReadOnly) extension = {} stream = QTextStream(extFile) while not stream.atEnd(): line = stream.readLine() if '=' in line: index = line.index('=') extension[line[:index].rstrip()] = line[index+1:].lstrip() extFile.close() return extension def openFile(self): supportedExtensions = ['.txt'] for markup in markups.get_all_markups(): supportedExtensions += markup.file_extensions fileFilter = ' (' + str.join(' ', ['*'+ext for ext in supportedExtensions]) + ');;' fileNames = QFileDialog.getOpenFileNames(self, self.tr("Select one or several files to open"), "", self.tr("Supported files") + fileFilter + self.tr("All files (*)")) for fileName in fileNames[0]: self.openFileWrapper(fileName) def openFileWrapper(self, fileName): if not fileName: return fileName = QFileInfo(fileName).canonicalFilePath() exists = False for i, tab in enumerate(self.iterateTabs()): if tab.fileName == fileName: exists = True ex = i if exists: self.tabWidget.setCurrentIndex(ex) elif QFile.exists(fileName): noEmptyTab = ( (self.ind is None) or self.currentTab.fileName or self.currentTab.editBox.toPlainText() or self.currentTab.editBox.document().isModified() ) if noEmptyTab: self.createTab(fileName) self.ind = self.tabWidget.count()-1 self.tabWidget.setCurrentIndex(self.ind) if fileName: self.fileSystemWatcher.addPath(fileName) self.currentTab.fileName = fileName self.currentTab.readTextFromFile() editBox = self.currentTab.editBox self.setCurrentFile() self.setWindowModified(editBox.document().isModified()) def showEncodingDialog(self): if not self.maybeSave(self.ind): return encoding, ok = QInputDialog.getItem(self, '', self.tr('Select file encoding from the list:'), [bytes(b).decode() for b in QTextCodec.availableCodecs()], 0, False) if ok: self.currentTab.readTextFromFile(encoding) def saveFile(self): self.saveFileMain(dlg=False) def saveFileAs(self): self.saveFileMain(dlg=True) def saveAll(self): for tab in self.iterateTabs(): if tab.fileName and QFileInfo(tab.fileName).isWritable(): tab.saveTextToFile() tab.editBox.document().setModified(False) def saveFileMain(self, dlg): if (not self.currentTab.fileName) or dlg: markupClass = self.currentTab.getMarkupClass() if (markupClass is None) or not hasattr(markupClass, 'default_extension'): defaultExt = self.tr("Plain text (*.txt)") ext = ".txt" else: defaultExt = self.tr('%s files', 'Example of final string: Markdown files') \ % markupClass.name + ' (' + str.join(' ', ('*'+extension for extension in markupClass.file_extensions)) + ')' if markupClass == markups.MarkdownMarkup: ext = globalSettings.markdownDefaultFileExtension elif markupClass == markups.ReStructuredTextMarkup: ext = globalSettings.restDefaultFileExtension else: ext = markupClass.default_extension newFileName = QFileDialog.getSaveFileName(self, self.tr("Save file"), "", defaultExt)[0] if newFileName: if not QFileInfo(newFileName).suffix(): newFileName += ext if self.currentTab.fileName: self.fileSystemWatcher.removePath(self.currentTab.fileName) self.currentTab.fileName = newFileName self.actionSetEncoding.setDisabled(self.autoSaveActive()) if self.currentTab.fileName: if self.currentTab.saveTextToFile(): self.setCurrentFile() self.currentTab.editBox.document().setModified(False) self.setWindowModified(False) return True else: QMessageBox.warning(self, '', self.tr("Cannot save to file because it is read-only!")) return False def saveHtml(self, fileName): if not QFileInfo(fileName).suffix(): fileName += ".html" try: htmltext = self.currentTab.getHtml(includeStyleSheet=False, webenv=True) except Exception: return self.printError() htmlFile = QFile(fileName) htmlFile.open(QIODevice.WriteOnly) html = QTextStream(htmlFile) if globalSettings.defaultCodec: html.setCodec(globalSettings.defaultCodec) html << htmltext htmlFile.close() def textDocument(self): td = QTextDocument() td.setMetaInformation(QTextDocument.DocumentTitle, self.currentTab.getDocumentTitle()) if self.ss: td.setDefaultStyleSheet(self.ss) td.setHtml(self.currentTab.getHtml()) td.setDefaultFont(globalSettings.font) return td def saveOdf(self): try: document = self.textDocument() except Exception: return self.printError() fileName = QFileDialog.getSaveFileName(self, self.tr("Export document to ODT"), "", self.tr("OpenDocument text files (*.odt)"))[0] if not QFileInfo(fileName).suffix(): fileName += ".odt" writer = QTextDocumentWriter(fileName) writer.setFormat(b"odf") writer.write(document) def saveFileHtml(self): fileName = QFileDialog.getSaveFileName(self, self.tr("Save file"), "", self.tr("HTML files (*.html *.htm)"))[0] if fileName: self.saveHtml(fileName) def getDocumentForPrint(self): if globalSettings.useWebKit: return self.currentTab.previewBox try: return self.textDocument() except Exception: self.printError() def standardPrinter(self): printer = QPrinter(QPrinter.HighResolution) printer.setDocName(self.currentTab.getDocumentTitle()) printer.setCreator('ReText %s' % app_version) return printer def savePdf(self): self.currentTab.updatePreviewBox() fileName = QFileDialog.getSaveFileName(self, self.tr("Export document to PDF"), "", self.tr("PDF files (*.pdf)"))[0] if fileName: if not QFileInfo(fileName).suffix(): fileName += ".pdf" printer = self.standardPrinter() printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFileName(fileName) document = self.getDocumentForPrint() if document != None: document.print(printer) def printFile(self): self.currentTab.updatePreviewBox() printer = self.standardPrinter() dlg = QPrintDialog(printer, self) dlg.setWindowTitle(self.tr("Print document")) if (dlg.exec() == QDialog.Accepted): document = self.getDocumentForPrint() if document != None: document.print(printer) def printPreview(self): document = self.getDocumentForPrint() if document == None: return printer = self.standardPrinter() preview = QPrintPreviewDialog(printer, self) preview.paintRequested.connect(document.print) preview.exec() def runExtensionCommand(self, command, filefilter, defaultext): of = ('%of' in command) html = ('%html' in command) if of: if defaultext and not filefilter: filefilter = '*'+defaultext fileName = QFileDialog.getSaveFileName(self, self.tr('Export document'), '', filefilter)[0] if not fileName: return if defaultext and not QFileInfo(fileName).suffix(): fileName += defaultext basename = '.%s.retext-temp' % self.currentTab.getDocumentTitle(baseName=True) if html: tmpname = basename+'.html' self.saveHtml(tmpname) else: tmpname = basename + self.currentTab.getMarkupClass().default_extension self.currentTab.saveTextToFile(fileName=tmpname, addToWatcher=False) command = command.replace('%of', '"out'+defaultext+'"') command = command.replace('%html' if html else '%if', '"'+tmpname+'"') try: Popen(str(command), shell=True).wait() except Exception as error: errorstr = str(error) QMessageBox.warning(self, '', self.tr('Failed to execute the command:') + '\n' + errorstr) QFile(tmpname).remove() if of: QFile('out'+defaultext).rename(fileName) def autoSaveActive(self, ind=None): tab = self.currentTab if ind is None else self.tabWidget.widget(ind).tab return (self.autoSaveEnabled and tab.fileName and QFileInfo(tab.fileName).isWritable()) def modificationChanged(self, changed): if self.autoSaveActive(): changed = False self.actionSave.setEnabled(changed) self.setWindowModified(changed) def clipboardDataChanged(self): mimeData = QApplication.instance().clipboard().mimeData() if mimeData is not None: self.actionPaste.setEnabled(mimeData.hasText()) def insertFormatting(self, formatting): cursor = self.currentTab.editBox.textCursor() text = cursor.selectedText() moveCursorTo = None def c(cursor): nonlocal moveCursorTo moveCursorTo = cursor.position() def ensurenl(cursor): if not cursor.atBlockStart(): cursor.insertText('\n\n') toinsert = { 'header': (ensurenl, '# ', text), 'italic': ('*', text, c, '*'), 'bold': ('**', text, c, '**'), 'underline': ('<u>', text, c, '</u>'), 'numbering': (ensurenl, ' 1. ', text), 'bullets': (ensurenl, ' * ', text), 'image': ('![', text or self.tr('Alt text'), c, '](', self.tr('URL'), ')'), 'link': ('[', text or self.tr('Link text'), c, '](', self.tr('URL'), ')'), 'inline code': ('`', text, c, '`'), 'code block': (ensurenl, ' ', text), 'blockquote': (ensurenl, '> ', text), } if formatting not in toinsert: return cursor.beginEditBlock() for token in toinsert[formatting]: if callable(token): token(cursor) else: cursor.insertText(token) cursor.endEditBlock() self.formattingBox.setCurrentIndex(0) # Bring back the focus on the editor self.currentTab.editBox.setFocus(Qt.OtherFocusReason) if moveCursorTo: cursor.setPosition(moveCursorTo) self.currentTab.editBox.setTextCursor(cursor) def insertSymbol(self, num): if num: self.currentTab.editBox.insertPlainText('&'+self.usefulChars[num-1]+';') self.symbolBox.setCurrentIndex(0) def fileChanged(self, fileName): ind = None for testind, tab in enumerate(self.iterateTabs()): if tab.fileName == fileName: ind = testind if ind is None: self.fileSystemWatcher.removePath(fileName) self.tabWidget.setCurrentIndex(ind) if not QFile.exists(fileName): self.currentTab.editBox.document().setModified(True) QMessageBox.warning(self, '', self.tr( 'This file has been deleted by other application.\n' 'Please make sure you save the file before exit.')) elif not self.currentTab.editBox.document().isModified(): # File was not modified in ReText, reload silently self.currentTab.readTextFromFile() self.currentTab.updatePreviewBox() else: text = self.tr( 'This document has been modified by other application.\n' 'Do you want to reload the file (this will discard all ' 'your changes)?\n') if self.autoSaveEnabled: text += self.tr( 'If you choose to not reload the file, auto save mode will ' 'be disabled for this session to prevent data loss.') messageBox = QMessageBox(QMessageBox.Warning, '', text) reloadButton = messageBox.addButton(self.tr('Reload'), QMessageBox.YesRole) messageBox.addButton(QMessageBox.Cancel) messageBox.exec() if messageBox.clickedButton() is reloadButton: self.currentTab.readTextFromFile() self.currentTab.updatePreviewBox() else: self.autoSaveEnabled = False self.currentTab.editBox.document().setModified(True) if fileName not in self.fileSystemWatcher.files(): # https://github.com/retext-project/retext/issues/137 self.fileSystemWatcher.addPath(fileName) def maybeSave(self, ind): tab = self.tabWidget.widget(ind).tab if self.autoSaveActive(ind): tab.saveTextToFile() return True if not tab.editBox.document().isModified(): return True self.tabWidget.setCurrentIndex(ind) ret = QMessageBox.warning(self, '', self.tr("The document has been modified.\nDo you want to save your changes?"), QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) if ret == QMessageBox.Save: return self.saveFileMain(False) elif ret == QMessageBox.Cancel: return False return True def closeEvent(self, closeevent): for ind in range(self.tabWidget.count()): if not self.maybeSave(ind): return closeevent.ignore() if globalSettings.saveWindowGeometry and not self.isMaximized(): globalSettings.windowGeometry = self.saveGeometry() closeevent.accept() def viewHtml(self): htmlDlg = HtmlDialog(self) try: htmltext = self.currentTab.getHtml(includeStyleSheet=False) except Exception: return self.printError() winTitle = self.currentTab.getDocumentTitle(baseName=True) htmlDlg.setWindowTitle(winTitle+" ("+self.tr("HTML code")+")") htmlDlg.textEdit.setPlainText(htmltext.rstrip()) htmlDlg.hl.rehighlight() htmlDlg.show() htmlDlg.raise_() htmlDlg.activateWindow() def openHelp(self): QDesktopServices.openUrl(QUrl('https://github.com/retext-project/retext/wiki')) def aboutDialog(self): QMessageBox.about(self, self.aboutWindowTitle, '<p><b>' + (self.tr('ReText %s (using PyMarkups %s)') % (app_version, markups.__version__)) +'</b></p>' + self.tr('Simple but powerful editor' ' for Markdown and reStructuredText') +'</p><p>'+self.tr('Author: Dmitry Shachnev, 2011').replace('2011', '2011–2016') +'<br><a href="https://github.com/retext-project/retext">'+self.tr('Website') +'</a> | <a href="http://daringfireball.net/projects/markdown/syntax">' +self.tr('Markdown syntax') +'</a> | <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">' +self.tr('reStructuredText syntax')+'</a></p>') def setDefaultMarkup(self, markupClass): self.defaultMarkup = markupClass defaultName = markups.get_available_markups()[0].name writeToSettings('defaultMarkup', markupClass.name, defaultName) for tab in self.iterateTabs(): if not tab.fileName: tab.setMarkupClass(markupClass) tab.updatePreviewBox() self.docTypeChanged()
def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.resize(950, 700) screenRect = QDesktopWidget().screenGeometry() if globalSettings.windowGeometry: self.restoreGeometry(globalSettings.windowGeometry) else: self.move((screenRect.width()-self.width())/2, (screenRect.height()-self.height())/2) if not screenRect.contains(self.geometry()): self.showMaximized() if globalSettings.iconTheme: QIcon.setThemeName(globalSettings.iconTheme) if QIcon.themeName() in ('hicolor', ''): if not QFile.exists(icon_path + 'document-new.png'): QIcon.setThemeName(get_icon_theme()) if QFile.exists(icon_path+'retext.png'): self.setWindowIcon(QIcon(icon_path+'retext.png')) elif QFile.exists('/usr/share/pixmaps/retext.png'): self.setWindowIcon(QIcon('/usr/share/pixmaps/retext.png')) else: self.setWindowIcon(QIcon.fromTheme('retext', QIcon.fromTheme('accessories-text-editor'))) self.tabWidget = QTabWidget(self) self.initTabWidget() self.setCentralWidget(self.tabWidget) self.tabWidget.currentChanged.connect(self.changeIndex) self.tabWidget.tabCloseRequested.connect(self.closeTab) toolBar = QToolBar(self.tr('File toolbar'), self) self.addToolBar(Qt.TopToolBarArea, toolBar) self.editBar = QToolBar(self.tr('Edit toolbar'), self) self.addToolBar(Qt.TopToolBarArea, self.editBar) self.searchBar = QToolBar(self.tr('Search toolbar'), self) self.addToolBar(Qt.BottomToolBarArea, self.searchBar) toolBar.setVisible(not globalSettings.hideToolBar) self.editBar.setVisible(not globalSettings.hideToolBar) self.actionNew = self.act(self.tr('New'), 'document-new', self.createNew, shct=QKeySequence.New) self.actionNew.setPriority(QAction.LowPriority) self.actionOpen = self.act(self.tr('Open'), 'document-open', self.openFile, shct=QKeySequence.Open) self.actionOpen.setPriority(QAction.LowPriority) self.actionSetEncoding = self.act(self.tr('Set encoding'), trig=self.showEncodingDialog) self.actionSetEncoding.setEnabled(False) self.actionReload = self.act(self.tr('Reload'), 'view-refresh', lambda: self.currentTab.readTextFromFile()) self.actionReload.setEnabled(False) self.actionSave = self.act(self.tr('Save'), 'document-save', self.saveFile, shct=QKeySequence.Save) self.actionSave.setEnabled(False) self.actionSave.setPriority(QAction.LowPriority) self.actionSaveAs = self.act(self.tr('Save as'), 'document-save-as', self.saveFileAs, shct=QKeySequence.SaveAs) self.actionNextTab = self.act(self.tr('Next tab'), 'go-next', lambda: self.switchTab(1), shct=Qt.CTRL+Qt.Key_PageDown) self.actionPrevTab = self.act(self.tr('Previous tab'), 'go-previous', lambda: self.switchTab(-1), shct=Qt.CTRL+Qt.Key_PageUp) self.actionPrint = self.act(self.tr('Print'), 'document-print', self.printFile, shct=QKeySequence.Print) self.actionPrint.setPriority(QAction.LowPriority) self.actionPrintPreview = self.act(self.tr('Print preview'), 'document-print-preview', self.printPreview) self.actionViewHtml = self.act(self.tr('View HTML code'), 'text-html', self.viewHtml) self.actionChangeEditorFont = self.act(self.tr('Change editor font'), trig=self.changeEditorFont) self.actionChangePreviewFont = self.act(self.tr('Change preview font'), trig=self.changePreviewFont) self.actionSearch = self.act(self.tr('Find text'), 'edit-find', shct=QKeySequence.Find) self.actionSearch.setCheckable(True) self.actionSearch.triggered[bool].connect(self.searchBar.setVisible) self.searchBar.visibilityChanged.connect(self.searchBarVisibilityChanged) self.actionPreview = self.act(self.tr('Preview'), shct=Qt.CTRL+Qt.Key_E, trigbool=self.preview) if QIcon.hasThemeIcon('document-preview'): self.actionPreview.setIcon(QIcon.fromTheme('document-preview')) elif QIcon.hasThemeIcon('preview-file'): self.actionPreview.setIcon(QIcon.fromTheme('preview-file')) elif QIcon.hasThemeIcon('x-office-document'): self.actionPreview.setIcon(QIcon.fromTheme('x-office-document')) else: self.actionPreview.setIcon(QIcon(icon_path+'document-preview.png')) self.actionLivePreview = self.act(self.tr('Live preview'), shct=Qt.CTRL+Qt.Key_L, trigbool=self.enableLivePreview) menuPreview = QMenu() menuPreview.addAction(self.actionLivePreview) self.actionPreview.setMenu(menuPreview) self.actionTableMode = self.act(self.tr('Table mode'), shct=Qt.CTRL+Qt.Key_T, trigbool=lambda x: self.currentTab.editBox.enableTableMode(x)) if ReTextFakeVimHandler: self.actionFakeVimMode = self.act(self.tr('FakeVim mode'), shct=Qt.CTRL+Qt.ALT+Qt.Key_V, trigbool=self.enableFakeVimMode) if globalSettings.useFakeVim: self.actionFakeVimMode.setChecked(True) self.enableFakeVimMode(True) self.actionFullScreen = self.act(self.tr('Fullscreen mode'), 'view-fullscreen', shct=Qt.Key_F11, trigbool=self.enableFullScreen) self.actionFullScreen.setPriority(QAction.LowPriority) self.actionConfig = self.act(self.tr('Preferences'), icon='preferences-system', trig=self.openConfigDialog) self.actionConfig.setMenuRole(QAction.PreferencesRole) self.actionSaveHtml = self.act('HTML', 'text-html', self.saveFileHtml) self.actionPdf = self.act('PDF', 'application-pdf', self.savePdf) self.actionOdf = self.act('ODT', 'x-office-document', self.saveOdf) self.getExportExtensionsList() self.actionQuit = self.act(self.tr('Quit'), 'application-exit', shct=QKeySequence.Quit) self.actionQuit.setMenuRole(QAction.QuitRole) self.actionQuit.triggered.connect(self.close) self.actionUndo = self.act(self.tr('Undo'), 'edit-undo', lambda: self.currentTab.editBox.undo(), shct=QKeySequence.Undo) self.actionRedo = self.act(self.tr('Redo'), 'edit-redo', lambda: self.currentTab.editBox.redo(), shct=QKeySequence.Redo) self.actionCopy = self.act(self.tr('Copy'), 'edit-copy', lambda: self.currentTab.editBox.copy(), shct=QKeySequence.Copy) self.actionCut = self.act(self.tr('Cut'), 'edit-cut', lambda: self.currentTab.editBox.cut(), shct=QKeySequence.Cut) self.actionPaste = self.act(self.tr('Paste'), 'edit-paste', lambda: self.currentTab.editBox.paste(), shct=QKeySequence.Paste) self.actionUndo.setEnabled(False) self.actionRedo.setEnabled(False) self.actionCopy.setEnabled(False) self.actionCut.setEnabled(False) qApp = QApplication.instance() qApp.clipboard().dataChanged.connect(self.clipboardDataChanged) self.clipboardDataChanged() if enchant_available: self.actionEnableSC = self.act(self.tr('Enable'), trigbool=self.enableSpellCheck) self.actionSetLocale = self.act(self.tr('Set locale'), trig=self.changeLocale) self.actionWebKit = self.act(self.tr('Use WebKit renderer'), trigbool=self.enableWebKit) self.actionWebKit.setChecked(globalSettings.useWebKit) self.actionShow = self.act(self.tr('Show directory'), 'system-file-manager', self.showInDir) self.actionFind = self.act(self.tr('Next'), 'go-next', self.find, shct=QKeySequence.FindNext) self.actionFindPrev = self.act(self.tr('Previous'), 'go-previous', lambda: self.find(back=True), shct=QKeySequence.FindPrevious) self.actionCloseSearch = self.act(self.tr('Close'), 'window-close', lambda: self.searchBar.setVisible(False)) self.actionCloseSearch.setPriority(QAction.LowPriority) self.actionHelp = self.act(self.tr('Get help online'), 'help-contents', self.openHelp) self.aboutWindowTitle = self.tr('About ReText') self.actionAbout = self.act(self.aboutWindowTitle, 'help-about', self.aboutDialog) self.actionAbout.setMenuRole(QAction.AboutRole) self.actionAboutQt = self.act(self.tr('About Qt')) self.actionAboutQt.setMenuRole(QAction.AboutQtRole) self.actionAboutQt.triggered.connect(qApp.aboutQt) availableMarkups = markups.get_available_markups() if not availableMarkups: print('Warning: no markups are available!') self.defaultMarkup = availableMarkups[0] if availableMarkups else None if globalSettings.defaultMarkup: mc = markups.find_markup_class_by_name(globalSettings.defaultMarkup) if mc and mc.available(): self.defaultMarkup = mc if len(availableMarkups) > 1: self.chooseGroup = QActionGroup(self) markupActions = [] for markup in availableMarkups: markupAction = self.act(markup.name, trigbool=self.markupFunction(markup)) if markup == self.defaultMarkup: markupAction.setChecked(True) self.chooseGroup.addAction(markupAction) markupActions.append(markupAction) self.actionBold = self.act(self.tr('Bold'), shct=QKeySequence.Bold, trig=lambda: self.insertFormatting('bold')) self.actionItalic = self.act(self.tr('Italic'), shct=QKeySequence.Italic, trig=lambda: self.insertFormatting('italic')) self.actionUnderline = self.act(self.tr('Underline'), shct=QKeySequence.Underline, trig=lambda: self.insertFormatting('underline')) self.usefulTags = ('header', 'italic', 'bold', 'underline', 'numbering', 'bullets', 'image', 'link', 'inline code', 'code block', 'blockquote') self.usefulChars = ('deg', 'divide', 'dollar', 'hellip', 'laquo', 'larr', 'lsquo', 'mdash', 'middot', 'minus', 'nbsp', 'ndash', 'raquo', 'rarr', 'rsquo', 'times') self.formattingBox = QComboBox(self.editBar) self.formattingBox.addItem(self.tr('Formatting')) self.formattingBox.addItems(self.usefulTags) self.formattingBox.activated[str].connect(self.insertFormatting) self.symbolBox = QComboBox(self.editBar) self.symbolBox.addItem(self.tr('Symbols')) self.symbolBox.addItems(self.usefulChars) self.symbolBox.activated.connect(self.insertSymbol) self.updateStyleSheet() menubar = self.menuBar() menuFile = menubar.addMenu(self.tr('File')) menuEdit = menubar.addMenu(self.tr('Edit')) menuHelp = menubar.addMenu(self.tr('Help')) menuFile.addAction(self.actionNew) menuFile.addAction(self.actionOpen) self.menuRecentFiles = menuFile.addMenu(self.tr('Open recent')) self.menuRecentFiles.aboutToShow.connect(self.updateRecentFiles) menuFile.addAction(self.actionShow) menuFile.addAction(self.actionSetEncoding) menuFile.addAction(self.actionReload) menuFile.addSeparator() menuFile.addAction(self.actionSave) menuFile.addAction(self.actionSaveAs) menuFile.addSeparator() menuFile.addAction(self.actionNextTab) menuFile.addAction(self.actionPrevTab) menuFile.addSeparator() menuExport = menuFile.addMenu(self.tr('Export')) menuExport.addAction(self.actionSaveHtml) menuExport.addAction(self.actionOdf) menuExport.addAction(self.actionPdf) if self.extensionActions: menuExport.addSeparator() for action, mimetype in self.extensionActions: menuExport.addAction(action) menuExport.aboutToShow.connect(self.updateExtensionsVisibility) menuFile.addAction(self.actionPrint) menuFile.addAction(self.actionPrintPreview) menuFile.addSeparator() menuFile.addAction(self.actionQuit) menuEdit.addAction(self.actionUndo) menuEdit.addAction(self.actionRedo) menuEdit.addSeparator() menuEdit.addAction(self.actionCut) menuEdit.addAction(self.actionCopy) menuEdit.addAction(self.actionPaste) menuEdit.addSeparator() if enchant_available: menuSC = menuEdit.addMenu(self.tr('Spell check')) menuSC.addAction(self.actionEnableSC) menuSC.addAction(self.actionSetLocale) menuEdit.addAction(self.actionSearch) menuEdit.addAction(self.actionChangeEditorFont) menuEdit.addAction(self.actionChangePreviewFont) menuEdit.addSeparator() if len(availableMarkups) > 1: self.menuMode = menuEdit.addMenu(self.tr('Default markup')) for markupAction in markupActions: self.menuMode.addAction(markupAction) menuFormat = menuEdit.addMenu(self.tr('Formatting')) menuFormat.addAction(self.actionBold) menuFormat.addAction(self.actionItalic) menuFormat.addAction(self.actionUnderline) menuEdit.addAction(self.actionWebKit) menuEdit.addSeparator() menuEdit.addAction(self.actionViewHtml) menuEdit.addAction(self.actionPreview) menuEdit.addAction(self.actionTableMode) if ReTextFakeVimHandler: menuEdit.addAction(self.actionFakeVimMode) menuEdit.addSeparator() menuEdit.addAction(self.actionFullScreen) menuEdit.addAction(self.actionConfig) menuHelp.addAction(self.actionHelp) menuHelp.addSeparator() menuHelp.addAction(self.actionAbout) menuHelp.addAction(self.actionAboutQt) toolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) toolBar.addAction(self.actionNew) toolBar.addSeparator() toolBar.addAction(self.actionOpen) toolBar.addAction(self.actionSave) toolBar.addAction(self.actionPrint) toolBar.addSeparator() toolBar.addAction(self.actionPreview) toolBar.addAction(self.actionFullScreen) self.editBar.addAction(self.actionUndo) self.editBar.addAction(self.actionRedo) self.editBar.addSeparator() self.editBar.addAction(self.actionCut) self.editBar.addAction(self.actionCopy) self.editBar.addAction(self.actionPaste) self.editBar.addSeparator() self.editBar.addWidget(self.formattingBox) self.editBar.addWidget(self.symbolBox) self.searchEdit = QLineEdit(self.searchBar) self.searchEdit.setPlaceholderText(self.tr('Search')) self.searchEdit.returnPressed.connect(self.find) self.csBox = QCheckBox(self.tr('Case sensitively'), self.searchBar) self.searchBar.addWidget(self.searchEdit) self.searchBar.addSeparator() self.searchBar.addWidget(self.csBox) self.searchBar.addAction(self.actionFindPrev) self.searchBar.addAction(self.actionFind) self.searchBar.addAction(self.actionCloseSearch) self.searchBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.searchBar.setVisible(False) self.autoSaveEnabled = globalSettings.autoSave if self.autoSaveEnabled: timer = QTimer(self) timer.start(60000) timer.timeout.connect(self.saveAll) self.ind = None if enchant_available: self.sl = globalSettings.spellCheckLocale if self.sl: try: enchant.Dict(self.sl) except Exception as e: print(e, file=sys.stderr) self.sl = None if globalSettings.spellCheck: self.actionEnableSC.setChecked(True) self.fileSystemWatcher = QFileSystemWatcher() self.fileSystemWatcher.fileChanged.connect(self.fileChanged)
class PlotsMath(QWidget): F_X = Qt.UserRole + 1 FITS_SPECTRUM = Qt.UserRole + 2 def __init__(self, settings, database, project=None): super(PlotsMath, self).__init__() self.ui = Ui_PlotsMath() self.ui.setupUi(self) self.settings = settings self.project=project self.plot = QtCommons.nestWidget(self.ui.plot, QMathPlotWidget()) self.reference_dialog = ReferenceSpectraDialog(database) self.reference_dialog.fits_picked.connect(self.open_fits) self.toolbar = QToolBar('Instrument Response Toolbar') open_btn = QtCommons.addToolbarPopup(self.toolbar, text="Open...", icon_file=':/new_open_20') open_file_action = open_btn.menu().addAction('FITS file') open_btn.menu().addAction('Reference library', self.reference_dialog.show) self.blackbody_menu = blackbody.BlackBodyAction(self.blackbody, open_btn.menu()) if project: save_result = QtCommons.addToolbarPopup(self.toolbar, text='Save', icon_file=':/save_20') save_result.menu().addAction('As File', lambda: QtCommons.save_file('Save Operation Result...', FITS_EXTS, lambda f: self.save(f[0]), project.path)) save_result.menu().addAction('As Instrument Response', self.save_project_instrument_response) open_file_action.triggered.connect(lambda: QtCommons.open_file('Open FITS Spectrum',FITS_EXTS, lambda f: self.open_fits(f[0]), project.path)) else: open_file_action.triggered.connect(lambda: open_file_sticky('Open FITS Spectrum',FITS_EXTS, lambda f: self.open_fits(f[0]), self.settings, CALIBRATED_PROFILE, [RAW_PROFILE])) self.toolbar.addAction(QIcon(':/save_20'), 'Save', lambda: save_file_sticky('Save Operation Result...', 'FITS file (.fit)', lambda f: self.save(f[0]), self.settings, MATH_OPERATION, [CALIBRATED_PROFILE])) self.toolbar.addAction('Set operand', self.set_operand) self.toolbar.addSeparator() self.toolbar.addAction(self.ui.actionZoom) self.ui.actionZoom.triggered.connect(self.start_zoom) self.toolbar.addAction(self.ui.actionReset_Zoom) self.ui.actionReset_Zoom.triggered.connect(self.reset_zoom) self.toolbar.addSeparator() self.operands_model = QStandardItemModel() self.ui.operands_listview.setModel(self.operands_model) remove_btn = QtCommons.addToolbarPopup(self.toolbar, text='Remove...') remove_btn.menu().addAction(self.ui.actionSelectPointsToRemove) remove_btn.menu().addAction("Before point", lambda: spectrum_trim_dialog(self.spectrum, 'before', self.plot.axes, lambda: self.draw(), self, before_removal=self.undo.save_undo)) remove_btn.menu().addAction("After point", lambda: spectrum_trim_dialog(self.spectrum, 'after', self.plot.axes, lambda: self.draw(), self, before_removal=self.undo.save_undo)) self.ui.clear_operands.clicked.connect(self.operands_model.clear) self.ui.remove_operand.clicked.connect(lambda: self.operands_model.removeRows(self.ui.operands_listview.selectionModel().selectedRows()[0].row(), 1)) self.operands_model.rowsInserted.connect(lambda: self.ui.clear_operands.setEnabled(self.operands_model.rowCount() > 0) ) self.operands_model.rowsRemoved.connect(lambda: self.ui.clear_operands.setEnabled(self.operands_model.rowCount() > 0) ) self.ui.operands_listview.selectionModel().selectionChanged.connect(lambda s, u: self.ui.remove_operand.setEnabled(len(s))) self.ui.actionSelectPointsToRemove.triggered.connect(self.pick_rm_points) self.undo = Undo(None, self.draw) self.undo.add_actions(self.toolbar) self.ui.spline_factor.valueChanged.connect(self.factor_valueChanged) self.ui.spline_degrees.valueChanged.connect(lambda v: self.draw()) self.ui.spline_factor_auto.toggled.connect(lambda v: self.draw()) self.ui.spline_factor_auto.toggled.connect(lambda v: self.ui.spline_factor.setEnabled(not v)) self.ui.execute.clicked.connect(self.execute_operation) self.plot.figure.tight_layout() def blackbody(self, blackbody): self.spectrum = blackbody.spectrum() self.spectrum_name = "Blackbody radiation for {0}".format(blackbody.kelvin) self.undo.set_spectrum(self.spectrum) self.spectrum.normalize_to_max() self.draw() def open_fits(self, filename): fits_file = fits.open(filename) fits_spectrum = FitsSpectrum(fits_file) self.spectrum_name = fits_spectrum.name() self.spectrum = fits_spectrum.spectrum self.undo.set_spectrum(self.spectrum) self.spectrum.normalize_to_max() if self.spectrum.dispersion() <0.4: print("dispersion too high ({}), reducing spectrum resolution".format(self.spectrum.dispersion())) self.spectrum.resample(self.spectrum.dispersion() / 0.4) self.draw() @pyqtSlot(float) def factor_valueChanged(self, f): self.draw() def pick_rm_points(self): self.plot.rm_element('zoom') self.plot.add_span_selector('pick_rm_points', lambda min,max: self.rm_points(min,max+1),direction='horizontal') def start_zoom(self): self.plot.rm_element('pick_rm_points') self.plot.select_zoom() def draw(self): self.ui.spline_degrees_value.setText("{}".format(self.ui.spline_degrees.value())) spline_factor = self.ui.spline_factor.value() if not self.ui.spline_factor_auto.isChecked() else None spline = UnivariateSpline(self.spectrum.wavelengths, self.spectrum.fluxes, k=self.ui.spline_degrees.value(), s=spline_factor) self.f_x = lambda x: spline(x) self.plot.plot(None, self.spectrum.wavelengths, self.spectrum.fluxes, '--', self.spectrum.wavelengths, spline(self.spectrum.wavelengths), '-') self.plot.figure.tight_layout() self.plot.figure.canvas.draw() def rm_points(self, wmin, wmax): self.undo.save_undo() x_min = self.spectrum.wavelength_index(max(self.spectrum.wavelengths[0], wmin)) x_max = self.spectrum.wavelength_index(min(self.spectrum.wavelengths[-1], wmax)) m=(self.spectrum.fluxes[x_max]-self.spectrum.fluxes[x_min])/(x_max-x_min) q = self.spectrum.fluxes[x_min] f = lambda x: x * m + q self.spectrum.fluxes[x_min:x_max] = np.fromfunction(f, self.spectrum.fluxes[x_min:x_max].shape) self.draw() def trim(self, direction): point = QInputDialog.getInt(None, 'Trim curve', 'Enter wavelength for trimming', self.spectrum.wavelengths[0] if direction == 'before' else self.spectrum.wavelengths[-1], self.spectrum.wavelengths[0], self.spectrum.wavelengths[-1]) if not point[1]: return self.undo.save_undo() if direction == 'before': self.spectrum.cut(start=self.spectrum.wavelength_index(point[0])) else: self.spectrum.cut(end=self.spectrum.wavelength_index(point[0])) self.reset_zoom() self.draw() def set_operand(self): item = QStandardItem(self.spectrum_name) item.setData(self.f_x, PlotsMath.F_X) item.setData(self.spectrum, PlotsMath.FITS_SPECTRUM) self.operands_model.appendRow(item) def execute_operation(self): max_wavelengths = lambda operands: np.arange(max([o[0].wavelengths[0] for o in operands]), min([o[0].wavelengths[-1] for o in operands])) datasets = lambda operands, wavelengths: [PlotsMath.__data(o[1], wavelengths) for o in operands] operands = [(self.operands_model.item(a).data(PlotsMath.FITS_SPECTRUM), self.operands_model.item(a).data(PlotsMath.F_X)) for a in np.arange(self.operands_model.rowCount())] def divide(operands): if len(operands) > 2: print("Division supports only 2 operands, truncating") wavelengths = max_wavelengths(operands[0:2]) datas = datasets(operands[0:2], wavelengths) return (wavelengths, datas[0]/datas[1]) def mean(operands): wavelengths = max_wavelengths(operands) mean_data = np.zeros(wavelengths.shape) for data in datasets(operands, wavelengths): mean_data += data return (wavelengths, mean_data/len(wavelengths)) operations = { 0: divide, 1: mean } try: wavelengths, data = operations[self.ui.operation_type.currentIndex()](operands) self.spectrum = Spectrum(data, wavelengths) self.spectrum.normalize_to_max() self.undo.set_spectrum(self.spectrum) self.ui.spline_degrees.setValue(5) self.ui.spline_factor.setValue(0) self.ui.spline_factor_auto.setChecked(False) self.draw() except IndexError: QMessageBox.warning(None, "Error", "Datasets are not compatible. Maybe you need to calibrate better, or use a different reference file") def save_project_instrument_response(self): name = QInputDialog.getText(self, 'Enter Name', 'Enter new instrument response name for saving') if name[1]: self.project.add_file(Project.INSTRUMENT_RESPONSES, lambda f: self.save(f), bare_name=name[0]) def save(self, filename): hdu = fits.PrimaryHDU( PlotsMath.__data(self.f_x, self.spectrum.wavelengths)) #hdu = fits.PrimaryHDU( self.spectrum.fluxes) fits_file = fits.HDUList([hdu]) hdu.header['CRPIX1'] = 1 hdu.header['CRVAL1'] = self.spectrum.wavelengths[0] hdu.header['CDELT1'] = self.spectrum.dispersion() hdu.writeto(filename, clobber=True) def reset_zoom(self): self.plot.reset_zoom(self.spectrum.wavelengths, self.spectrum.fluxes.min(), self.spectrum.fluxes.max()) def __data(f_x, xrange): return np.fromfunction(lambda x: f_x(x+xrange[0]), xrange.shape)
class MainWindow(QMainWindow, ui_window.Ui_Window): emulator_found = QtCore.pyqtSignal(dict) emulators_loaded = QtCore.pyqtSignal() def __init__(self): super(MainWindow, self).__init__() self.setupUi(self) self.emulators = { } self.settings = QSettings('SanderTheDragon', 'Qtendo') self.ui_create() self.ui_connect() self.settings_load() def showEvent(self, ev): QMainWindow.showEvent(self, ev) self.statusBar.showMsg('Searching for emulators', 1000) Thread(target=self.find_emulators, daemon=True).start() def closeEvent(self, ev): QMainWindow.closeEvent(self, ev) self.settings_save() def ui_create(self): #Add toolbar self.toolBar = QToolBar() self.toolBar.addAction(self.actionPageEmulation) self.toolBar.setFloatable(False) self.toolBar.setMovable(False) self.toolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.gridLayout.addWidget(self.toolBar, 1, 0) #Add a second toolbar on emulation page self.toolBarEmulation = QToolBar() self.toolBarEmulation.setFloatable(False) self.toolBarEmulation.setMovable(False) self.toolBarEmulation.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.pageEmulationLayout.addWidget(self.toolBarEmulation, 0, 0) #Add progress bar to status bar self.taskProgress = QProgressBar() self.taskProgress.setVal = lambda x: ( self.taskProgress.setVisible(True), self.taskProgress.setValue(x) ) self.taskProgress.setVal(0) self.taskProgress.setTextVisible(False) self.statusBar.addPermanentWidget(self.taskProgress) #Also print messages to terminal self.statusBar.showMsg = lambda msg, timeout: ( logging.info(msg), self.statusBar.showMessage(msg, timeout) ) #Styling self.setStyleSheet('QToolButton { padding-right: -3px; }') def ui_connect(self): #Menu actions self.actionQuit.triggered.connect(QCoreApplication.quit) self.actionSettings.triggered.connect(lambda: settings.SettingsDialog(parent=self).exec_()) self.actionAbout.triggered.connect(lambda: about.AboutDialog(parent=self).exec_()) #Toolbar actions self.actionPageEmulation.triggered.connect(lambda: self.stackedWidget.setCurrentIndex(0)) #Other signals self.emulator_found.connect(self.add_emulator) self.emulators_loaded.connect(self.reset_status) def settings_load(self): if self.settings.value('qtendo/window/restore', True, type=bool): self.restoreGeometry(self.settings.value('qtendo/window/geometry', type=QByteArray)) def settings_save(self): if self.settings.value('qtendo/window/restore', True, type=bool): self.settings.setValue('qtendo/window/geometry', self.saveGeometry()) def change_emulator(self, index): current = self.stackedWidgetEmulation.currentIndex() if current != index: emulator = self.emulators[list(self.emulators.keys())[current]] emulator['action'].setIcon(QIcon(emulator['action'].icon().pixmap(QSize(24, 24), QIcon.Disabled))) self.stackedWidgetEmulation.setCurrentIndex(index) emulator = self.emulators[list(self.emulators.keys())[index]] emulator['action'].setIcon(QIcon(':' + emulator['icon'])) def add_emulator(self, emulator): if len(emulator['path']) > 0: self.statusBar.showMsg('Found ' + emulator['name'], 1000) else: self.statusBar.showMsg('Failed to find ' + emulator['name'], 1000) self.emulators[emulator['name']] = emulator i = self.stackedWidgetEmulation.count() emulator['action'] = QAction() emulator['action'].setIcon(QIcon(':' + emulator['icon'])) if i > 0: self.toolBarEmulation.addSeparator() emulator['action'].setIcon(QIcon(emulator['action'].icon().pixmap(QSize(24, 24), QIcon.Disabled))) emulator['action'].setIconText(emulator['name']) emulator['action'].triggered.connect(lambda checked, index=i: self.change_emulator(index)) self.toolBarEmulation.addAction(emulator['action']) self.stackedWidgetEmulation.insertWidget(i, emulator['widget'](emulator)) if len(self.settings.value('emulation/emulator/' + emulator['name'].lower().replace(' ', '_') + '/path', emulator['path'], type=str)) == 0: self.toolBarEmulation.widgetForAction(emulator['action']).setStyleSheet('color: ' + QApplication.palette().color(QPalette.Disabled, QPalette.WindowText).name() + ';') emulator['reload_settings'] = self.reload_settings self.taskProgress.setVal(int((100.0 / float(emulator_count)) * float(i + 1))) def reset_status(self): self.statusBar.clearMessage() self.taskProgress.setValue(0) self.taskProgress.setVisible(False) def reload_settings(self): emulator = self.emulators[list(self.emulators.keys())[self.stackedWidgetEmulation.currentIndex()]] if len(self.settings.value('emulation/emulator/' + emulator['name'].lower().replace(' ', '_') + '/path', emulator['path'], type=str)) == 0: self.toolBarEmulation.widgetForAction(emulator['action']).setStyleSheet('color: ' + QApplication.palette().color(QPalette.Disabled, QPalette.WindowText).name() + ';') else: self.toolBarEmulation.widgetForAction(emulator['action']).setStyleSheet('') def find_emulators(self): #Search for FCEUX self.emulator_found.emit(fceux.find(None if not self.settings.contains('emulation/emulator/fceux/path') else self.settings.value('emulation/emulator/fceux/path', type=str))) #Search for ZSNES self.emulator_found.emit(zsnes.find(None if not self.settings.contains('emulation/emulator/zsnes/path') else self.settings.value('emulation/emulator/zsnes/path', type=str))) #Search for Mupen64Plus self.emulator_found.emit(mupen64plus.find(None if not self.settings.contains('emulation/emulator/mupen64plus/path') else self.settings.value('emulation/emulator/mupen64plus/path', type=str))) #Search for Dolphin Emulator self.emulator_found.emit(dolphin.find(None if not self.settings.contains('emulation/emulator/dolphin/path') else self.settings.value('emulation/emulator/dolphin/path', type=str))) #Search for Citra self.emulator_found.emit(citra.find(None if not self.settings.contains('emulation/emulator/citra/path') else self.settings.value('emulation/emulator/citra/path', type=str))) self.emulators_loaded.emit()
class AppWindow(QMainWindow): "The application's main window" move_listener = pyqtSignal() db_activity_checker = pyqtSignal() graphics_blur = QGraphicsBlurEffect() def __init__(self): super().__init__() app_constants.GENERAL_THREAD = QThread(self) app_constants.GENERAL_THREAD.finished.connect(app_constants.GENERAL_THREAD.deleteLater) app_constants.GENERAL_THREAD.start() self.setAcceptDrops(True) self.initUI() self.start_up() QTimer.singleShot(3000, self._check_update) self.setFocusPolicy(Qt.NoFocus) self.set_shortcuts() self.graphics_blur.setParent(self) #ex = settings.ExProperties() #d = pewnet.ExHenManager(ex.ipb_id, ex.ipb_pass) #item = d.from_gallery_url('http://exhentai.org/g/861957/02741dc584/') #def a(): print(item.file) #if not item.file: # item.file_rdy.connect(a) #else: # a() def set_shortcuts(self): quit = QShortcut(QKeySequence('Ctrl+Q'), self, self.close) def init_watchers(self): def remove_gallery(g): index = self.manga_list_view.find_index(g.id, True) if index: self.manga_list_view.remove_gallery([index]) def create_gallery(path): g_dia = gallerydialog.GalleryDialog(self, path) g_dia.SERIES.connect(self.manga_list_view.gallery_model.addRows) g_dia.show() def update_gallery(g): index = self.manga_list_view.find_index(g.id) if index: self.manga_list_view.replace_edit_gallery([g], index.row()) else: log_e('Could not find gallery to update from watcher') def created(path): c_popup = io_misc.CreatedPopup(path, self) c_popup.ADD_SIGNAL.connect(create_gallery) def modified(path, gallery): mod_popup = io_misc.ModifiedPopup(path, gallery, self) def deleted(path, gallery): d_popup = io_misc.DeletedPopup(path, gallery, self) d_popup.UPDATE_SIGNAL.connect(update_gallery) d_popup.REMOVE_SIGNAL.connect(remove_gallery) def moved(new_path, gallery): mov_popup = io_misc.MovedPopup(new_path, gallery, self) mov_popup.UPDATE_SIGNAL.connect(update_gallery) self.watchers = io_misc.Watchers() self.watchers.gallery_handler.CREATE_SIGNAL.connect(created) self.watchers.gallery_handler.MODIFIED_SIGNAL.connect(modified) self.watchers.gallery_handler.MOVED_SIGNAL.connect(moved) self.watchers.gallery_handler.DELETED_SIGNAL.connect(deleted) admin_db_method_invoker = pyqtSignal(str) def start_up(self): hello = ["Hello!", "Hi!", "Onii-chan!", "Senpai!", "Hisashiburi!", "Welcome!", "Okaerinasai!", "Welcome back!", "Hajimemashite!"] self.notification_bar.add_text("{} Please don't hesitate to report any bugs you find.".format(hello[random.randint(0, len(hello)-1)])+ " Go to Settings -> About -> Bug Reporting for more info!") level = 5 def normalize_first_time(): settings.set(level, 'Application', 'first time level') settings.save() def done(status=True): gallerydb.DatabaseEmitter.RUN = True if app_constants.FIRST_TIME_LEVEL != level: normalize_first_time() if app_constants.ENABLE_MONITOR and\ app_constants.MONITOR_PATHS and all(app_constants.MONITOR_PATHS): self.init_watchers() if app_constants.LOOK_NEW_GALLERY_STARTUP: if self.manga_list_view.gallery_model.db_emitter.count == app_constants.GALLERY_DATA: self.scan_for_new_galleries() else: self.manga_list_view.gallery_model.db_emitter.DONE.connect(self.scan_for_new_galleries) self.download_manager = pewnet.Downloader() app_constants.DOWNLOAD_MANAGER = self.download_manager self.download_manager.start_manager(4) if app_constants.FIRST_TIME_LEVEL < 4: log_i('Invoking first time level {}'.format(4)) settings.set([], 'Application', 'monitor paths') settings.set([], 'Application', 'ignore paths') app_constants.MONITOR_PATHS = [] app_constants.IGNORE_PATHS = [] settings.save() done() elif app_constants.FIRST_TIME_LEVEL < 5: log_i('Invoking first time level {}'.format(5)) app_widget = misc.ApplicationPopup(self) app_widget.note_info.setText("<font color='red'>IMPORTANT:</font> Application restart is required when done") app_widget.restart_info.hide() self.admin_db = gallerydb.AdminDB() self.admin_db.moveToThread(app_constants.GENERAL_THREAD) self.admin_db.DONE.connect(done) self.admin_db.DONE.connect(lambda: app_constants.NOTIF_BAR.add_text("Application requires a restart")) self.admin_db.DONE.connect(self.admin_db.deleteLater) self.admin_db.DATA_COUNT.connect(app_widget.prog.setMaximum) self.admin_db.PROGRESS.connect(app_widget.prog.setValue) self.admin_db_method_invoker.connect(self.admin_db.rebuild_db) self.admin_db_method_invoker.connect(app_widget.show) app_widget.adjustSize() db_p = os.path.join(os.path.split(database.db_constants.DB_PATH)[0], 'sadpanda.db') self.admin_db_method_invoker.emit(db_p) else: done() def initUI(self): self.center = QWidget() self.display = QStackedLayout() self._main_layout = QVBoxLayout() self._main_layout.setSpacing(0) self._main_layout.setContentsMargins(0,0,0,0) self._main_layout.addLayout(self.display) self.center.setLayout(self._main_layout) # init the manga view variables self.manga_display() log_d('Create manga display: OK') # init the chapter view variables #self.chapter_display() self.m_l_view_index = self.display.addWidget(self.manga_list_view) self.m_t_view_index = self.display.addWidget(self.manga_table_view) self.download_window = io_misc.GalleryDownloader(self) self.download_window.hide() # init toolbar self.init_toolbar() log_d('Create toolbar: OK') # init status bar self.init_stat_bar() log_d('Create statusbar: OK') self.tags_treeview = None if app_constants.TAGS_TREEVIEW_ON_START: def tags_tree_none(): self.tags_treeview = None self.tags_treeview = misc_db.DBOverview(self, True) self.tags_treeview.about_to_close.connect(tags_tree_none) self.tags_treeview.show() self.system_tray = misc.SystemTray(QIcon(app_constants.APP_ICO_PATH), self) app_constants.SYSTEM_TRAY = self.system_tray tray_menu = QMenu(self) self.system_tray.setContextMenu(tray_menu) self.system_tray.setToolTip('Happypanda {}'.format(app_constants.vs)) tray_quit = QAction('Quit', tray_menu) tray_update = tray_menu.addAction('Check for update') tray_update.triggered.connect(self._check_update) tray_menu.addAction(tray_quit) tray_quit.triggered.connect(self.close) self.system_tray.show() def tray_activate(r=None): if not r or r == QSystemTrayIcon.Trigger: self.showNormal() self.activateWindow() self.system_tray.messageClicked.connect(tray_activate) self.system_tray.activated.connect(tray_activate) log_d('Create system tray: OK') #self.display.addWidget(self.chapter_main) self.setCentralWidget(self.center) self.setWindowIcon(QIcon(app_constants.APP_ICO_PATH)) props = settings.win_read(self, 'AppWindow') if props.resize: x, y = props.resize self.resize(x, y) else: self.resize(app_constants.MAIN_W, app_constants.MAIN_H) posx, posy = props.pos self.move(posx, posy) self.init_spinners() self.show() log_d('Show window: OK') self.notification_bar = misc.NotificationOverlay(self) p = self.toolbar.pos() self.notification_bar.move(p.x(), p.y()+self.toolbar.height()) self.notification_bar.resize(self.width()) app_constants.NOTIF_BAR = self.notification_bar log_d('Create notificationbar: OK') log_d('Window Create: OK') def _check_update(self): class upd_chk(QObject): UPDATE_CHECK = pyqtSignal(str) def __init__(self, **kwargs): super().__init__(**kwargs) def fetch_vs(self): import requests import time log_d('Checking Update') time.sleep(1.5) try: if os.path.exists('cacert.pem'): r = requests.get("https://raw.githubusercontent.com/Pewpews/happypanda/master/VS.txt", verify='cacert.pem') else: r = requests.get("https://raw.githubusercontent.com/Pewpews/happypanda/master/VS.txt") a = r.text vs = a.strip() self.UPDATE_CHECK.emit(vs) except: log.exception('Checking Update: FAIL') self.UPDATE_CHECK.emit('this is a very long text which is sure to be over limit') def check_update(vs): log_i('Received version: {}\nCurrent version: {}'.format(vs, app_constants.vs)) if vs != app_constants.vs: if len(vs) < 10: self.notification_bar.begin_show() self.notification_bar.add_text("Version {} of Happypanda is".format(vs)+ " available. Click here to update!", False) self.notification_bar.clicked.connect(lambda: utils.open_web_link( 'https://github.com/Pewpews/happypanda/releases')) self.notification_bar.set_clickable(True) else: self.notification_bar.add_text("An error occurred while checking for new version") self.update_instance = upd_chk() thread = QThread(self) self.update_instance.moveToThread(thread) thread.started.connect(self.update_instance.fetch_vs) self.update_instance.UPDATE_CHECK.connect(check_update) self.update_instance.UPDATE_CHECK.connect(self.update_instance.deleteLater) thread.finished.connect(thread.deleteLater) thread.start() def _web_metadata_picker(self, gallery, title_url_list, queue, parent=None): if not parent: parent = self text = "Which gallery do you want to extract metadata from?" s_gallery_popup = misc.SingleGalleryChoices(gallery, title_url_list, text, parent) s_gallery_popup.USER_CHOICE.connect(queue.put) def get_metadata(self, gal=None): metadata_spinner = misc.Spinner(self) def move_md_spinner(): metadata_spinner.update_move( QPoint( self.pos().x()+self.width()-65, self.pos().y()+self.toolbar.height()+55)) metadata_spinner.set_text("Metadata") metadata_spinner.set_size(55) metadata_spinner.move(QPoint(self.pos().x()+self.width()-65, self.pos().y()+self.toolbar.height()+55)) self.move_listener.connect(move_md_spinner) thread = QThread(self) thread.setObjectName('App.get_metadata') fetch_instance = fetch.Fetch() if gal: if not isinstance(gal, list): galleries = [gal] else: galleries = gal else: if app_constants.CONTINUE_AUTO_METADATA_FETCHER: galleries = [g for g in self.manga_list_view.gallery_model._data if not g.exed] else: galleries = self.manga_list_view.gallery_model._data if not galleries: self.notification_bar.add_text('Looks like we\'ve already gone through all galleries!') return None fetch_instance.galleries = galleries self.notification_bar.begin_show() fetch_instance.moveToThread(thread) def done(status): self.notification_bar.end_show() fetch_instance.deleteLater() if not isinstance(status, bool): galleries = [] for tup in status: galleries.append(tup[0]) class GalleryContextMenu(QMenu): app_instance = self def __init__(self, parent=None): super().__init__(parent) show_in_library_act = self.addAction('Show in library') show_in_library_act.triggered.connect(self.show_in_library) def show_in_library(self): viewer = self.app_instance.manga_list_view index = viewer.find_index(self.gallery_widget.gallery.id, True) if index: self.app_instance.manga_table_view.scroll_to_index(index) self.app_instance.manga_list_view.scroll_to_index(index) g_popup = io_misc.GalleryPopup(('Fecthing metadata for these galleries failed.'+ ' Check happypanda.log for details.', galleries), self, menu=GalleryContextMenu()) #errors = {g[0].id: g[1] for g in status} #for g_item in g_popup.get_all_items(): # g_item.setToolTip(errors[g_item.gallery.id]) g_popup.graphics_blur.setEnabled(False) close_button = g_popup.add_buttons('Close')[0] close_button.clicked.connect(g_popup.close) fetch_instance.GALLERY_PICKER.connect(self._web_metadata_picker) fetch_instance.GALLERY_EMITTER.connect(self.manga_list_view.replace_edit_gallery) fetch_instance.AUTO_METADATA_PROGRESS.connect(self.notification_bar.add_text) thread.started.connect(fetch_instance.auto_web_metadata) fetch_instance.FINISHED.connect(done) fetch_instance.FINISHED.connect(metadata_spinner.close) fetch_instance.FINISHED.connect(lambda: self.move_listener.disconnect(move_md_spinner)) thread.finished.connect(thread.deleteLater) thread.start() metadata_spinner.show() def init_stat_bar(self): self.status_bar = self.statusBar() self.status_bar.setMaximumHeight(20) self.status_bar.setSizeGripEnabled(False) self.stat_info = QLabel() self.stat_info.setIndent(5) self.sort_main = QAction("Asc", self) sort_menu = QMenu() self.sort_main.setMenu(sort_menu) s_by_title = QAction("Title", sort_menu) s_by_artist = QAction("Artist", sort_menu) sort_menu.addAction(s_by_title) sort_menu.addAction(s_by_artist) self.status_bar.addPermanentWidget(self.stat_info) #self.status_bar.addAction(self.sort_main) self.temp_msg = QLabel() self.temp_timer = QTimer() self.manga_list_view.gallery_model.ROWCOUNT_CHANGE.connect(self.stat_row_info) self.manga_list_view.gallery_model.db_emitter.COUNT_CHANGE.connect(self.stat_row_info) self.manga_list_view.gallery_model.STATUSBAR_MSG.connect(self.stat_temp_msg) self.manga_list_view.STATUS_BAR_MSG.connect(self.stat_temp_msg) self.manga_table_view.STATUS_BAR_MSG.connect(self.stat_temp_msg) self.stat_row_info() app_constants.STAT_MSG_METHOD = self.stat_temp_msg def stat_temp_msg(self, msg): self.temp_timer.stop() self.temp_msg.setText(msg) self.status_bar.addWidget(self.temp_msg) self.temp_timer.timeout.connect(self.temp_msg.clear) self.temp_timer.setSingleShot(True) self.temp_timer.start(5000) def stat_row_info(self): r = self.manga_list_view.model().rowCount() t = self.manga_list_view.gallery_model.db_emitter.count self.stat_info.setText("Loaded {} of {} ".format(r, t)) def manga_display(self): "initiates the manga view and related things" #list view self.manga_list_view = gallery.MangaView(self) #table view self.manga_table_view = gallery.MangaTableView(self) self.manga_table_view.gallery_model = self.manga_list_view.gallery_model self.manga_table_view.sort_model = self.manga_list_view.sort_model self.manga_table_view.setModel(self.manga_table_view.sort_model) self.manga_table_view.sort_model.change_model(self.manga_table_view.gallery_model) self.manga_table_view.setColumnWidth(app_constants.FAV, 20) self.manga_table_view.setColumnWidth(app_constants.ARTIST, 200) self.manga_table_view.setColumnWidth(app_constants.TITLE, 400) self.manga_table_view.setColumnWidth(app_constants.TAGS, 300) self.manga_table_view.setColumnWidth(app_constants.TYPE, 60) self.manga_table_view.setColumnWidth(app_constants.CHAPTERS, 60) self.manga_table_view.setColumnWidth(app_constants.LANGUAGE, 100) self.manga_table_view.setColumnWidth(app_constants.LINK, 400) def init_spinners(self): # fetching spinner self.data_fetch_spinner = misc.Spinner(self) self.data_fetch_spinner.set_size(60) self.move_listener.connect( lambda: self.data_fetch_spinner.update_move( QPoint(self.pos().x()+self.width()//2, self.pos().y()+self.height()//2))) self.manga_list_view.gallery_model.ADD_MORE.connect(self.data_fetch_spinner.show) self.manga_list_view.gallery_model.db_emitter.START.connect(self.data_fetch_spinner.show) self.manga_list_view.gallery_model.ADDED_ROWS.connect(self.data_fetch_spinner.before_hide) self.manga_list_view.gallery_model.db_emitter.CANNOT_FETCH_MORE.connect(self.data_fetch_spinner.before_hide) ## deleting spinner #self.gallery_delete_spinner = misc.Spinner(self) #self.gallery_delete_spinner.set_size(40,40) ##self.gallery_delete_spinner.set_text('Removing...') #self.manga_list_view.gallery_model.rowsAboutToBeRemoved.connect(self.gallery_delete_spinner.show) #self.manga_list_view.gallery_model.rowsRemoved.connect(self.gallery_delete_spinner.before_hide) def search(self, srch_string): self.search_bar.setText(srch_string) self.search_backward.setVisible(True) self.manga_list_view.sort_model.init_search(srch_string) old_cursor_pos = self._search_cursor_pos[0] self.search_bar.end(False) if self.search_bar.cursorPosition() != old_cursor_pos+1: self.search_bar.setCursorPosition(old_cursor_pos) def favourite_display(self): "Switches to favourite display" self.manga_table_view.sort_model.fav_view() self.favourite_btn.selected = True self.library_btn.selected = False def catalog_display(self): "Switches to catalog display" self.manga_table_view.sort_model.catalog_view() self.library_btn.selected = True self.favourite_btn.selected = False def settings(self): sett = settingsdialog.SettingsDialog(self) sett.scroll_speed_changed.connect(self.manga_list_view.updateGeometries) #sett.show() def init_toolbar(self): self.toolbar = QToolBar() self.toolbar.setFixedHeight(25) self.toolbar.setWindowTitle("Show") # text for the contextmenu #self.toolbar.setStyleSheet("QToolBar {border:0px}") # make it user defined? self.toolbar.setMovable(False) self.toolbar.setFloatable(False) #self.toolbar.setIconSize(QSize(20,20)) self.toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toolbar.setIconSize(QSize(20,20)) spacer_start = QWidget() # aligns the first actions properly spacer_start.setFixedSize(QSize(10, 1)) self.toolbar.addWidget(spacer_start) self.favourite_btn = misc.ToolbarButton(self.toolbar, 'Favorites') self.toolbar.addWidget(self.favourite_btn) self.favourite_btn.clicked.connect(self.favourite_display) #need lambda to pass extra args self.library_btn = misc.ToolbarButton(self.toolbar, 'Library') self.toolbar.addWidget(self.library_btn) self.library_btn.clicked.connect(self.catalog_display) #need lambda to pass extra args self.library_btn.selected = True self.toolbar.addSeparator() gallery_menu = QMenu() gallery_action = QToolButton() gallery_action.setText('Gallery ') gallery_action.setPopupMode(QToolButton.InstantPopup) gallery_action.setToolTip('Contains various gallery related features') gallery_action.setMenu(gallery_menu) add_gallery_icon = QIcon(app_constants.PLUS_PATH) gallery_action_add = QAction(add_gallery_icon, "Add single gallery...", self) gallery_action_add.triggered.connect(self.manga_list_view.SERIES_DIALOG.emit) gallery_action_add.setToolTip('Add a single gallery thoroughly') gallery_menu.addAction(gallery_action_add) add_more_action = QAction(add_gallery_icon, "Add galleries...", self) add_more_action.setStatusTip('Add galleries from different folders') add_more_action.triggered.connect(lambda: self.populate(True)) gallery_menu.addAction(add_more_action) populate_action = QAction(add_gallery_icon, "Populate from directory/archive...", self) populate_action.setStatusTip('Populates the DB with galleries from a single folder or archive') populate_action.triggered.connect(self.populate) gallery_menu.addAction(populate_action) gallery_menu.addSeparator() metadata_action = QAction('Get metadata for all galleries', self) metadata_action.triggered.connect(self.get_metadata) gallery_menu.addAction(metadata_action) scan_galleries_action = QAction('Scan for new galleries', self) scan_galleries_action.triggered.connect(self.scan_for_new_galleries) scan_galleries_action.setStatusTip('Scan monitored folders for new galleries') gallery_menu.addAction(scan_galleries_action) gallery_action_random = gallery_menu.addAction("Open random gallery") gallery_action_random.triggered.connect(self.manga_list_view.open_random_gallery) self.toolbar.addWidget(gallery_action) misc_action = QToolButton() misc_action.setText('Tools ') misc_action_menu = QMenu() misc_action.setMenu(misc_action_menu) misc_action.setPopupMode(QToolButton.InstantPopup) misc_action.setToolTip("Contains misc. features") gallery_downloader = QAction("Gallery Downloader", misc_action_menu) gallery_downloader.triggered.connect(self.download_window.show) misc_action_menu.addAction(gallery_downloader) duplicate_check_simple = QAction("Simple Duplicate Finder", misc_action_menu) duplicate_check_simple.triggered.connect(lambda: self.manga_list_view.duplicate_check()) misc_action_menu.addAction(duplicate_check_simple) self.toolbar.addWidget(misc_action) spacer_middle = QWidget() # aligns buttons to the right spacer_middle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.toolbar.addWidget(spacer_middle) sort_action = QToolButton() sort_action.setIcon(QIcon(app_constants.SORT_PATH)) sort_action.setMenu(misc.SortMenu(self.toolbar, self.manga_list_view)) sort_action.setPopupMode(QToolButton.InstantPopup) self.toolbar.addWidget(sort_action) self.grid_toggle_g_icon = QIcon(app_constants.GRID_PATH) self.grid_toggle_l_icon = QIcon(app_constants.LIST_PATH) self.grid_toggle = QToolButton() if self.display.currentIndex() == self.m_l_view_index: self.grid_toggle.setIcon(self.grid_toggle_l_icon) else: self.grid_toggle.setIcon(self.grid_toggle_g_icon) self.grid_toggle.setObjectName('gridtoggle') self.grid_toggle.clicked.connect(self.toggle_view) self.toolbar.addWidget(self.grid_toggle) spacer_mid2 = QWidget() spacer_mid2.setFixedSize(QSize(5, 1)) self.toolbar.addWidget(spacer_mid2) def set_search_case(b): app_constants.GALLERY_SEARCH_CASE = b settings.set(b, 'Application', 'gallery search case') settings.save() def set_search_strict(b): app_constants.GALLERY_SEARCH_STRICT = b settings.set(b, 'Application', 'gallery search strict') settings.save() self.search_bar = misc.LineEdit() search_options = self.search_bar.addAction(QIcon(app_constants.SEARCH_OPTIONS_PATH), QLineEdit.TrailingPosition) search_options_menu = QMenu(self) search_options.triggered.connect(lambda: search_options_menu.popup(QCursor.pos())) search_options.setMenu(search_options_menu) case_search_option = search_options_menu.addAction('Case Sensitive') case_search_option.setCheckable(True) case_search_option.setChecked(app_constants.GALLERY_SEARCH_CASE) case_search_option.toggled.connect(set_search_case) strict_search_option = search_options_menu.addAction('Match whole terms') strict_search_option.setCheckable(True) strict_search_option.setChecked(app_constants.GALLERY_SEARCH_STRICT) strict_search_option.toggled.connect(set_search_strict) self.search_bar.setObjectName('search_bar') self.search_timer = QTimer(self) self.search_timer.setSingleShot(True) self.search_timer.timeout.connect(lambda: self.search(self.search_bar.text())) self._search_cursor_pos = [0, 0] def set_cursor_pos(old, new): self._search_cursor_pos[0] = old self._search_cursor_pos[1] = new self.search_bar.cursorPositionChanged.connect(set_cursor_pos) if app_constants.SEARCH_AUTOCOMPLETE: completer = QCompleter(self) completer_view = misc.CompleterPopupView() completer.setPopup(completer_view) completer_view._setup() completer.setModel(self.manga_list_view.gallery_model) completer.setCaseSensitivity(Qt.CaseInsensitive) completer.setCompletionMode(QCompleter.PopupCompletion) completer.setCompletionRole(Qt.DisplayRole) completer.setCompletionColumn(app_constants.TITLE) completer.setFilterMode(Qt.MatchContains) self.search_bar.setCompleter(completer) self.search_bar.returnPressed.connect(lambda: self.search(self.search_bar.text())) if not app_constants.SEARCH_ON_ENTER: self.search_bar.textEdited.connect(lambda: self.search_timer.start(800)) self.search_bar.setPlaceholderText("Search title, artist, namespace & tags") self.search_bar.setMinimumWidth(150) self.search_bar.setMaximumWidth(500) self.search_bar.setFixedHeight(19) self.manga_list_view.sort_model.HISTORY_SEARCH_TERM.connect(lambda a: self.search_bar.setText(a)) self.toolbar.addWidget(self.search_bar) def search_history(_, back=True): # clicked signal passes a bool sort_model = self.manga_list_view.sort_model nav = sort_model.PREV if back else sort_model.NEXT history_term = sort_model.navigate_history(nav) if back: self.search_forward.setVisible(True) back = QShortcut(QKeySequence(QKeySequence.Back), self, lambda: search_history(None)) forward = QShortcut(QKeySequence(QKeySequence.Forward), self, lambda: search_history(None, False)) search_backbutton = QToolButton(self.toolbar) search_backbutton.setText(u'\u25C0') search_backbutton.setFixedWidth(15) search_backbutton.clicked.connect(search_history) self.search_backward = self.toolbar.addWidget(search_backbutton) self.search_backward.setVisible(False) search_forwardbutton = QToolButton(self.toolbar) search_forwardbutton.setText(u'\u25B6') search_forwardbutton.setFixedWidth(15) search_forwardbutton.clicked.connect(lambda: search_history(None, False)) self.search_forward = self.toolbar.addWidget(search_forwardbutton) self.search_forward.setVisible(False) spacer_end = QWidget() # aligns settings action properly spacer_end.setFixedSize(QSize(10, 1)) self.toolbar.addWidget(spacer_end) settings_act = QToolButton(self.toolbar) settings_act.setIcon(QIcon(app_constants.SETTINGS_PATH)) settings_act.clicked.connect(self.settings) self.toolbar.addWidget(settings_act) spacer_end2 = QWidget() # aligns About action properly spacer_end2.setFixedSize(QSize(5, 1)) self.toolbar.addWidget(spacer_end2) self.addToolBar(self.toolbar) def toggle_view(self): """ Toggles the current display view """ if self.display.currentIndex() == self.m_l_view_index: self.display.setCurrentIndex(self.m_t_view_index) self.grid_toggle.setIcon(self.grid_toggle_g_icon) else: self.display.setCurrentIndex(self.m_l_view_index) self.grid_toggle.setIcon(self.grid_toggle_l_icon) # TODO: Improve this so that it adds to the gallery dialog, # so user can edit data before inserting (make it a choice) def populate(self, mixed=None): "Populates the database with gallery from local drive'" if mixed: gallery_view = misc.GalleryListView(self, True) gallery_view.SERIES.connect(self.gallery_populate) gallery_view.show() else: msg_box = misc.BasePopup(self) l = QVBoxLayout() msg_box.main_widget.setLayout(l) l.addWidget(QLabel('Directory or Archive?')) l.addLayout(msg_box.buttons_layout) def from_dir(): path = QFileDialog.getExistingDirectory(self, "Choose a directory containing your galleries") if not path: return msg_box.close() app_constants.OVERRIDE_SUBFOLDER_AS_GALLERY = True self.gallery_populate(path, True) def from_arch(): path = QFileDialog.getOpenFileName(self, 'Choose an archive containing your galleries', filter=utils.FILE_FILTER) path = [path[0]] if not all(path) or not path: return msg_box.close() app_constants.OVERRIDE_SUBFOLDER_AS_GALLERY = True self.gallery_populate(path, True) buttons = msg_box.add_buttons('Directory', 'Archive', 'Close') buttons[2].clicked.connect(msg_box.close) buttons[0].clicked.connect(from_dir) buttons[1].clicked.connect(from_arch) msg_box.adjustSize() msg_box.show() def gallery_populate(self, path, validate=False): "Scans the given path for gallery to add into the DB" if len(path) is not 0: data_thread = QThread(self) data_thread.setObjectName('General gallery populate') loading = misc.Loading(self) self.g_populate_inst = fetch.Fetch() self.g_populate_inst.series_path = path loading.show() def finished(status): def hide_loading(): loading.hide() if status: if len(status) != 0: def add_gallery(gallery_list): def append_to_model(x): self.manga_list_view.sort_model.insertRows(x, None, len(x)) self.manga_list_view.sort_model.init_search( self.manga_list_view.sort_model.current_term) class A(QObject): done = pyqtSignal() prog = pyqtSignal(int) def __init__(self, obj, parent=None): super().__init__(parent) self.obj = obj self.galleries = [] def add_to_db(self): for y, x in enumerate(self.obj): gallerydb.add_method_queue( gallerydb.GalleryDB.add_gallery_return, False, x) self.galleries.append(x) y += 1 self.prog.emit(y) append_to_model(self.galleries) self.done.emit() loading.progress.setMaximum(len(gallery_list)) self.a_instance = A(gallery_list) thread = QThread(self) thread.setObjectName('Database populate') def loading_show(numb): if loading.isHidden(): loading.show() loading.setText('Populating database ({}/{})\nPlease wait...'.format( numb, loading.progress.maximum())) loading.progress.setValue(numb) loading.show() def loading_hide(): loading.close() self.manga_list_view.gallery_model.ROWCOUNT_CHANGE.emit() self.a_instance.moveToThread(thread) self.a_instance.prog.connect(loading_show) thread.started.connect(self.a_instance.add_to_db) self.a_instance.done.connect(loading_hide) self.a_instance.done.connect(self.a_instance.deleteLater) #a_instance.add_to_db() thread.finished.connect(thread.deleteLater) thread.start() #data_thread.quit hide_loading() log_i('Populating DB from gallery folder: OK') if validate: gallery_list = misc.GalleryListView(self) gallery_list.SERIES.connect(add_gallery) for ser in status: if ser.is_archive and app_constants.SUBFOLDER_AS_GALLERY: p = os.path.split(ser.path)[1] if ser.chapters[0].path: pt_in_arch = os.path.split(ser.path_in_archive) pt_in_arch = pt_in_arch[1] or pt_in_arch[0] text = '{}: {}'.format(p, pt_in_arch) else: text = p gallery_list.add_gallery(ser, text) else: gallery_list.add_gallery(ser, os.path.split(ser.path)[1]) #self.manga_list_view.gallery_model.populate_data() gallery_list.update_count() gallery_list.show() else: add_gallery(status) else: log_d('No new gallery was found') loading.setText("No new gallery found") #data_thread.quit else: log_e('Populating DB from gallery folder: Nothing was added!') loading.setText("<font color=red>Nothing was added. Check happypanda_log for details..</font>") loading.progress.setStyleSheet("background-color:red;") data_thread.quit QTimer.singleShot(8000, loading.close) def skipped_gs(s_list): "Skipped galleries" msg_box = QMessageBox(self) msg_box.setIcon(QMessageBox.Question) msg_box.setText('Do you want to view skipped paths?') msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msg_box.setDefaultButton(QMessageBox.No) if msg_box.exec() == QMessageBox.Yes: list_wid = QTableWidget(self) list_wid.setAttribute(Qt.WA_DeleteOnClose) list_wid.setRowCount(len(s_list)) list_wid.setColumnCount(2) list_wid.setAlternatingRowColors(True) list_wid.setEditTriggers(list_wid.NoEditTriggers) list_wid.setHorizontalHeaderLabels(['Reason', 'Path']) list_wid.setSelectionBehavior(list_wid.SelectRows) list_wid.setSelectionMode(list_wid.SingleSelection) list_wid.setSortingEnabled(True) list_wid.verticalHeader().hide() list_wid.setAutoScroll(False) for x, g in enumerate(s_list): list_wid.setItem(x, 0, QTableWidgetItem(g[1])) list_wid.setItem(x, 1, QTableWidgetItem(g[0])) list_wid.resizeColumnsToContents() list_wid.setWindowTitle('{} skipped paths'.format(len(s_list))) list_wid.setWindowFlags(Qt.Window) list_wid.resize(900,400) list_wid.doubleClicked.connect(lambda i: utils.open_path( list_wid.item(i.row(), 1).text(), list_wid.item(i.row(), 1).text())) list_wid.show() def a_progress(prog): loading.progress.setValue(prog) loading.setText("Preparing galleries...") self.g_populate_inst.moveToThread(data_thread) self.g_populate_inst.DATA_COUNT.connect(loading.progress.setMaximum) self.g_populate_inst.PROGRESS.connect(a_progress) self.g_populate_inst.FINISHED.connect(finished) self.g_populate_inst.FINISHED.connect(self.g_populate_inst.deleteLater) self.g_populate_inst.SKIPPED.connect(skipped_gs) data_thread.finished.connect(data_thread.deleteLater) data_thread.started.connect(self.g_populate_inst.local) data_thread.start() #.g_populate_inst.local() log_i('Populating DB from directory/archive') def scan_for_new_galleries(self): available_folders = app_constants.ENABLE_MONITOR and\ app_constants.MONITOR_PATHS and all(app_constants.MONITOR_PATHS) if available_folders and not app_constants.SCANNING_FOR_GALLERIES: app_constants.SCANNING_FOR_GALLERIES = True self.notification_bar.add_text("Scanning for new galleries...") log_i('Scanning for new galleries...') try: class ScanDir(QObject): final_paths_and_galleries = pyqtSignal(list, list) finished = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self.scanned_data = [] def scan_dirs(self): paths = [] for p in app_constants.MONITOR_PATHS: if os.path.exists(p): dir_content = scandir.scandir(p) for d in dir_content: paths.append(d.path) else: log_e("Monitored path does not exists: {}".format(p.encode(errors='ignore'))) fetch_inst = fetch.Fetch(self) fetch_inst.series_path = paths def set_scanned_d(d): self.scanned_data = d fetch_inst.FINISHED.connect(set_scanned_d) fetch_inst.local() #contents = [] #for g in self.scanned_data: # contents.append(g) #paths = sorted(paths) #new_galleries = [] #for x in contents: # y = utils.b_search(paths, os.path.normcase(x.path)) # if not y: # new_galleries.append(x) galleries = [] final_paths = [] if self.scanned_data: for g in self.scanned_data: try: if g.is_archive: g.profile = utils.get_gallery_img(g.chapters[0].path, g.path) else: g.profile = utils.get_gallery_img(g.chapters[0].path) if not g.profile: raise Exception except: g.profile = app_constants.NO_IMAGE_PATH galleries.append(g) final_paths.append(g.path) self.final_paths_and_galleries.emit(final_paths, galleries) self.finished.emit() self.deleteLater() #if app_constants.LOOK_NEW_GALLERY_AUTOADD: # QTimer.singleShot(10000, self.gallery_populate(final_paths)) # return def show_new_galleries(final_paths, galleries): if final_paths and galleries: app_constants.OVERRIDE_MOVE_IMPORTED_IN_FETCH = True if app_constants.LOOK_NEW_GALLERY_AUTOADD: self.gallery_populate(final_paths) else: class NewGalleryMenu(QMenu): def __init__(self, parent=None): super().__init__(parent) ignore_act = self.addAction('Add to ignore list') ignore_act.triggered.connect(self.add_to_ignore) def add_to_ignore(self): gallery = self.gallery_widget.gallery app_constants.IGNORE_PATHS.append(gallery.path) settings.set(app_constants.IGNORE_PATHS, 'Application', 'ignore paths') if self.gallery_widget.parent_widget.gallery_layout.count() == 1: self.gallery_widget.parent_widget.close() else: self.gallery_widget.close() if len(galleries) == 1: self.notification_bar.add_text("{} new gallery was discovered in one of your monitored directories".format(len(galleries))) else: self.notification_bar.add_text("{} new galleries were discovered in one of your monitored directories".format(len(galleries))) text = "These new galleries were discovered! Do you want to add them?"\ if len(galleries) > 1 else "This new gallery was discovered! Do you want to add it?" g_popup = io_misc.GalleryPopup((text, galleries), self, NewGalleryMenu()) buttons = g_popup.add_buttons('Add', 'Close') def populate_n_close(): g_popup.close() self.gallery_populate(final_paths) buttons[0].clicked.connect(populate_n_close) buttons[1].clicked.connect(g_popup.close) def finished(): app_constants.SCANNING_FOR_GALLERIES = False thread = QThread(self) self.scan_inst = ScanDir() self.scan_inst.moveToThread(thread) self.scan_inst.final_paths_and_galleries.connect(show_new_galleries) self.scan_inst.finished.connect(finished) thread.started.connect(self.scan_inst.scan_dirs) #self.scan_inst.scan_dirs() thread.finished.connect(thread.deleteLater) thread.start() except: self.notification_bar.add_text('An error occured while attempting to scan for new galleries. Check happypanda.log.') log.exception('An error occured while attempting to scan for new galleries.') app_constants.SCANNING_FOR_GALLERIES = False def dragEnterEvent(self, event): if event.mimeData().hasUrls(): event.acceptProposedAction() else: self.notification_bar.add_text('File is not supported') def dropEvent(self, event): acceptable = [] unaccept = [] for u in event.mimeData().urls(): path = u.toLocalFile() if os.path.isdir(path) or path.endswith(utils.ARCHIVE_FILES): acceptable.append(path) else: unaccept.append(path) log_i('Acceptable dropped items: {}'.format(len(acceptable))) log_i('Unacceptable dropped items: {}'.format(len(unaccept))) log_d('Dropped items: {}\n{}'.format(acceptable, unaccept).encode(errors='ignore')) if acceptable: self.notification_bar.add_text('Adding dropped items...') log_i('Adding dropped items') l = len(acceptable) == 1 f_item = acceptable[0] if f_item.endswith(utils.ARCHIVE_FILES): f_item = utils.check_archive(f_item) else: f_item = utils.recursive_gallery_check(f_item) f_item_l = len(f_item) < 2 subfolder_as_c = not app_constants.SUBFOLDER_AS_GALLERY if l and subfolder_as_c or l and f_item_l: g_d = gallerydialog.GalleryDialog(self, acceptable[0]) g_d.SERIES.connect(self.manga_list_view.gallery_model.addRows) g_d.show() else: self.gallery_populate(acceptable, True) else: text = 'File not supported' if len(unaccept) < 2 else 'Files not supported' self.notification_bar.add_text(text) if unaccept: self.notification_bar.add_text('Some unsupported files did not get added') def resizeEvent(self, event): try: self.notification_bar.resize(event.size().width()) except AttributeError: pass self.move_listener.emit() return super().resizeEvent(event) def moveEvent(self, event): self.move_listener.emit() return super().moveEvent(event) def showEvent(self, event): return super().showEvent(event) def cleanup_exit(self): self.system_tray.hide() # watchers try: self.watchers.stop_all() except AttributeError: pass # settings settings.set(self.manga_list_view.current_sort, 'General', 'current sort') settings.set(app_constants.IGNORE_PATHS, 'Application', 'ignore paths') settings.win_save(self, 'AppWindow') # temp dir try: for root, dirs, files in scandir.walk('temp', topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) log_d('Flush temp on exit: OK') except: log.exception('Flush temp on exit: FAIL') if self.tags_treeview: self.tags_treeview.close() self.download_window.close() # check if there is db activity if not gallerydb.method_queue.empty(): class DBActivityChecker(QObject): FINISHED = pyqtSignal() def __init__(self, **kwargs): super().__init__(**kwargs) def check(self): gallerydb.method_queue.join() self.FINISHED.emit() self.deleteLater() db_activity = DBActivityChecker() db_spinner = misc.Spinner(self) self.db_activity_checker.connect(db_activity.check) db_activity.moveToThread(app_constants.GENERAL_THREAD) db_activity.FINISHED.connect(db_spinner.close) db_spinner.set_size(50) db_spinner.set_text('Activity') db_spinner.move(QPoint(self.pos().x()+self.width()-70, self.pos().y()+self.height()-70)) self.move_listener.connect(lambda: db_spinner.update_move(QPoint(self.pos().x()+self.width()-70, self.pos().y()+self.height()-70))) db_spinner.show() self.db_activity_checker.emit() msg_box = QMessageBox(self) msg_box.setText('Database activity detected!') msg_box.setInformativeText("Closing now might result in data loss." + " Do you still want to close?\n(Wait for the activity spinner to hide before closing)") msg_box.setIcon(QMessageBox.Critical) msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msg_box.setDefaultButton(QMessageBox.No) if msg_box.exec() == QMessageBox.Yes: return 1 else: return 2 else: return 0 def closeEvent(self, event): r_code = self.cleanup_exit() if r_code == 1: log_d('Force Exit App: OK') super().closeEvent(event) elif r_code == 2: log_d('Ignore Exit App') event.ignore() else: log_d('Normal Exit App: OK') super().closeEvent(event)
class EditorWidget(QWidget): TOOLBAR_ITEMS = [ 'save_query', '', 'undo_action', 'redo_action', 'cut_action', 'paste_action', ] editorModified = pyqtSignal(bool) def __init__(self, parent=None): QWidget.__init__(self, parent) vbox = QVBoxLayout(self) vbox.setContentsMargins(0, 0, 0, 0) vbox.setSpacing(0) self.setStyleSheet("outline: none") hbox = QHBoxLayout() # Position self._column_str = "Col: {}" self._column_lbl = QLabel(self._column_str.format(0)) # Toolbar self._toolbar = QToolBar(self) self._toolbar.setIconSize(QSize(16, 16)) pireal = Pireal.get_service("pireal") for action in self.TOOLBAR_ITEMS: qaction = pireal.get_action(action) if qaction is not None: self._toolbar.addAction(qaction) else: self._toolbar.addSeparator() # hbox.addWidget(self._toolbar, 1) # hbox.addWidget(self._column_lbl) vbox.addLayout(hbox) # Editor self._editor = editor.Editor() vbox.addWidget(self._editor) # Search widget self._search_widget = SearchWidget(self) self._search_widget.hide() vbox.addWidget(self._search_widget) # Editor connections self._editor.customContextMenuRequested.connect( self.__show_context_menu) self._editor.modificationChanged[bool].connect( lambda modified: self.editorModified.emit(modified)) self._editor.undoAvailable[bool].connect( self.__on_undo_available) self._editor.redoAvailable[bool].connect( self.__on_redo_available) self._editor.copyAvailable[bool].connect( self.__on_copy_available) self._editor.cursorPositionChanged.connect( self._update_column_label) def show_search_widget(self): self._search_widget.show() self._search_widget._line_search.setFocus() self._search_widget._execute_search(self._search_widget.search_text) def hide_search_widget(self): self._search_widget.hide() self._search_widget._line_search.clear() def _update_column_label(self): col = str(self._editor.textCursor().columnNumber() + 1) self._column_lbl.setText(self._column_str.format(col)) def get_editor(self): return self._editor def __show_context_menu(self, point): popup_menu = self._editor.createStandardContextMenu() undock_editor = QAction(self.tr("Undock"), self) popup_menu.insertAction(popup_menu.actions()[0], undock_editor) popup_menu.insertSeparator(popup_menu.actions()[1]) undock_editor.triggered.connect(self.__undock_editor) popup_menu.exec_(self.mapToGlobal(point)) def __undock_editor(self): new_editor = editor.Editor() actual_doc = self._editor.document() new_editor.setDocument(actual_doc) new_editor.resize(900, 400) # Set text cursor tc = self._editor.textCursor() new_editor.setTextCursor(tc) # Set title db = Pireal.get_service("central").get_active_db() qc = db.query_container new_editor.setWindowTitle(qc.tab_text(qc.current_index())) new_editor.show() def __on_undo_available(self, value): """ Change state of undo action """ pireal = Pireal.get_service("pireal") action = pireal.get_action("undo_action") action.setEnabled(value) def __on_redo_available(self, value): """ Change state of redo action """ pireal = Pireal.get_service("pireal") action = pireal.get_action("redo_action") action.setEnabled(value) def __on_copy_available(self, value): """ Change states of cut and copy action """ cut_action = Pireal.get_action("cut_action") cut_action.setEnabled(value) copy_action = Pireal.get_action("copy_action") copy_action.setEnabled(value)
class ImportImage(QWidget): def icon(): return QIcon(':/image_20') ACTION_TEXT = 'Import Image' def pick(on_ok, settings): open_file_sticky('Open FITS Image',FITS_IMG_EXTS, on_ok, settings, IMPORT_IMG ) def __init__(self, fits_file, settings, project = None): super(ImportImage, self).__init__() self.settings = settings self.fits_file = fits_file self.project = project try: image_hdu_index = fits_file.index_of('IMAGE') except KeyError: image_hdu_index = 0 original_image = fits.ImageHDU(data=fits_file[image_hdu_index].data, header=fits_file[image_hdu_index].header, name='IMAGE') for hdu in [h for h in self.fits_file if h.name == 'IMAGE']: self.fits_file.remove(hdu) self.fits_file.append(original_image) self.ui = Ui_ImportImage() self.ui.setupUi(self) self.rotate_dialog = RotateImageDialog(self.fits_file, image_hdu_index, project=project) self.rotate_dialog.rotated.connect(self.rotated) self.image_plot = QtCommons.nestWidget(self.ui.image_widget, QImPlotWidget(self.rotate_dialog.data_rotated, cmap='gray')) self.spatial_plot = QtCommons.nestWidget(self.ui.spatial_plot_widget, QMathPlotWidget()) self.spectrum_plot = QtCommons.nestWidget(self.ui.spectrum_plot_widget, QMathPlotWidget()) self.image_view = self.image_plot.axes_image self.toolbar = QToolBar('Image Toolbar') self.toolbar.addAction(QIcon(':/rotate_20'), "Rotate", lambda: self.rotate_dialog.show()) self.toolbar.addAction(QIcon(':/save_20'), "Save", self.save_profile) self.toolbar.addAction(QIcon(':/select_all_20'), "Select spectrum data", lambda: self.spatial_plot.add_span_selector('select_spectrum', self.spectrum_span_selected,direction='horizontal')) self.toolbar.addAction(QIcon.fromTheme('edit-select-invert'), "Select background data", lambda: self.spatial_plot.add_span_selector('select_background', self.background_span_selected,direction='horizontal', rectprops = dict(facecolor='blue', alpha=0.5))).setEnabled(False) #self.toolbar.addAction('Stack', self.show_stack_images_dialog) self.toolbar.addSeparator() self.object_properties = ObjectProperties(self.fits_file, project=project) self.object_properties_dialog = ObjectPropertiesDialog(settings, self.object_properties) self.toolbar.addAction("Object properties", self.object_properties_dialog.show) self.rotated() def rotated(self): self.image_view.set_data(self.rotate_dialog.data_rotated) self.image_view.axes.relim() self.image_view.axes.autoscale_view() self.image_view.set_extent([self.rotate_dialog.data_rotated.shape[1],0, self.rotate_dialog.data_rotated.shape[0],0]) self.image_view.figure.canvas.draw() self.draw_plot(self.spectrum_plot.axes, self.spectrum_profile()) self.draw_plot(self.spatial_plot.axes, self.spatial_profile()) def background_span_selected(self, min, max): self.background_span_selection = (min, max) self.spatial_plot.add_span('background_window', min, max, 'v', facecolor='gray', alpha=0.5) self.image_plot.add_span('background_window', min, max, 'h', facecolor='red', alpha=0.5, clip_on=True) self.draw_plot(self.spectrum_plot.axes, self.spectrum_profile()) def spectrum_span_selected(self, min, max): self.spectrum_span_selection = (min, max) self.spatial_plot.add_span('spectrum_window', min, max, 'v', facecolor='g', alpha=0.5) self.image_plot.add_span('spectrum_window', min, max, 'h', facecolor='y', alpha=0.25, clip_on=True) self.draw_plot(self.spectrum_plot.axes, self.spectrum_profile()) def draw_plot(self, axes, data): axes.clear() axes.plot(data) axes.figure.tight_layout() axes.figure.canvas.draw() def spatial_profile(self): return self.rotate_dialog.data_rotated.sum(1) def spectrum_profile(self): return self.rotate_dialog.data_rotated[self.spectrum_span_selection[0]:self.spectrum_span_selection[1]+1,:].sum(0) if hasattr(self, 'spectrum_span_selection') else self.rotate_dialog.data_rotated.sum(0) def save(self, save_file): data = self.spectrum_profile() data -= np.amin(data) data /= np.amax(data) hdu = self.fits_file[0] hdu.data = data hdu.header['ORIGIN'] = 'PySpectrum' self.fits_file.writeto(save_file, clobber=True) def save_profile(self): if not self.project: save_file_sticky('Save plot...', 'FITS file (.fit)', lambda f: self.save(f[0]), self.settings, RAW_PROFILE ) return if not self.object_properties.name: QMessageBox.information(self, 'Save FITS', 'Please set file information (name, date, etc) using the Object Properties button before saving') return file_path = self.project.add_file(Project.RAW_PROFILE, object_properties = self.object_properties, on_added=self.save)
class AppWindow(QMainWindow): "The application's main window" def __init__(self): super().__init__() self.center = QWidget() self.display = QStackedLayout() self.center.setLayout(self.display) # init the manga view variables self.manga_display() # init the chapter view variables self.chapter_display() # init toolbar self.init_toolbar() # init status bar self.init_stat_bar() self.display.addWidget(self.manga_main) self.display.addWidget(self.chapter_main) self.setCentralWidget(self.center) self.setWindowTitle("Happypanda") self.resize(1029, 650) self.show() def init_stat_bar(self): self.status_bar = self.statusBar() self.status_bar.setMaximumHeight(20) self.status_bar.setSizeGripEnabled(False) self.stat_info = QLabel() self.stat_info.setIndent(5) self.sort_main = QAction("Asc", self) sort_menu = QMenu() self.sort_main.setMenu(sort_menu) s_by_title = QAction("Title", sort_menu) s_by_artist = QAction("Artist", sort_menu) sort_menu.addAction(s_by_title) sort_menu.addAction(s_by_artist) self.status_bar.addPermanentWidget(self.stat_info) #self.status_bar.addAction(self.sort_main) self.temp_msg = QLabel() self.temp_timer = QTimer() self.manga_list_view.series_model.ROWCOUNT_CHANGE.connect(self.stat_row_info) self.manga_list_view.series_model.STATUSBAR_MSG.connect(self.stat_temp_msg) def stat_temp_msg(self, msg): self.temp_timer.stop() self.temp_msg.setText(msg) self.status_bar.addWidget(self.temp_msg) self.temp_timer.timeout.connect(self.temp_msg.clear) self.temp_timer.setSingleShot(True) self.temp_timer.start(5000) def stat_row_info(self): r = self.manga_list_view.series_model.rowCount() t = len(self.manga_list_view.series_model._data) self.stat_info.setText("<b>Showing {} of {} </b>".format(r, t)) def manga_display(self): "initiates the manga view" self.manga_main = QWidget() self.manga_main.setContentsMargins(-10, -12, -10, -10) self.manga_view = QHBoxLayout() self.manga_main.setLayout(self.manga_view) manga_delegate = series.CustomDelegate() manga_delegate.BUTTON_CLICKED.connect(self.setCurrentIndex) self.manga_list_view = series.MangaView() self.manga_list_view.setItemDelegate(manga_delegate) self.manga_view.addWidget(self.manga_list_view) def favourite_display(self): "initiates favourite display" pass def chapter_display(self): "Initiates chapter view" self.chapter_main = QWidget() self.chapter_main.setObjectName("chapter_main") # to allow styling this object self.chapter_layout = QHBoxLayout() self.chapter_main.setLayout(self.chapter_layout) #self.chapter_info.setContentsMargins(-8,-7,-7,-7) #self.chapter_info.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.chapter_info_view = series.ChapterInfo() self.chapter_layout.addWidget(self.chapter_info_view) chapter_list_view = series.ChapterView() self.chapter_layout.addWidget(chapter_list_view) #self.chapter.setCollapsible(0, True) #self.chapter.setCollapsible(1, False) def init_toolbar(self): self.toolbar = QToolBar() self.toolbar.setFixedHeight(30) self.toolbar.setWindowTitle("Show") # text for the contextmenu #self.toolbar.setStyleSheet("QToolBar {border:0px}") # make it user defined? self.toolbar.setMovable(False) self.toolbar.setFloatable(False) #self.toolbar.setIconSize(QSize(20,20)) self.toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) spacer_start = QWidget() # aligns the first actions properly spacer_start.setFixedSize(QSize(10, 1)) self.toolbar.addWidget(spacer_start) favourite_view_icon = QIcon(gui_constants.STAR_BTN_PATH) favourite_view_action = QAction(favourite_view_icon, "Favourite", self) #favourite_view_action.setText("Manga View") favourite_view_action.triggered.connect(lambda: self.setCurrentIndex(1)) #need lambda to pass extra args self.toolbar.addAction(favourite_view_action) catalog_view_icon = QIcon(gui_constants.HOME_BTN_PATH) catalog_view_action = QAction(catalog_view_icon, "Library", self) #catalog_view_action.setText("Catalog") catalog_view_action.triggered.connect(lambda: self.setCurrentIndex(0)) #need lambda to pass extra args self.toolbar.addAction(catalog_view_action) self.toolbar.addSeparator() series_icon = QIcon(gui_constants.PLUS_PATH) series_action = QAction(series_icon, "Add series...", self) series_action.triggered.connect(self.manga_list_view.SERIES_DIALOG.emit) series_menu = QMenu() series_menu.addSeparator() populate_action = QAction("Populate from folder...", self) populate_action.triggered.connect(self.populate) series_menu.addAction(populate_action) series_action.setMenu(series_menu) self.toolbar.addAction(series_action) spacer_middle = QWidget() # aligns buttons to the right spacer_middle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.toolbar.addWidget(spacer_middle) self.search_bar = QLineEdit() self.search_bar.setPlaceholderText("Search title, artist, genres") self.search_bar.setMaximumWidth(200) self.toolbar.addWidget(self.search_bar) self.toolbar.addSeparator() settings_icon = QIcon(gui_constants.SETTINGS_PATH) settings_action = QAction(settings_icon, "Set&tings", self) self.toolbar.addAction(settings_action) self.addToolBar(self.toolbar) spacer_end = QWidget() # aligns About action properly spacer_end.setFixedSize(QSize(10, 1)) self.toolbar.addWidget(spacer_end) def setCurrentIndex(self, number, index=None): """Changes the current display view. Params: number <- int (0 for manga view, 1 for chapter view Optional: index <- QModelIndex for chapter view Note: 0-based indexing """ if index is not None: self.chapter_info_view.display_manga(index) self.display.setCurrentIndex(number) else: self.display.setCurrentIndex(number) # TODO: Improve this so that it adds to the series dialog, # so user can edit data before inserting (make it a choice) def populate(self): "Populates the database with series from local drive'" msgbox = QMessageBox() msgbox.setText("<font color='red'><b>Use with care.</b></font> Choose a folder containing all your series'.") msgbox.setInformativeText("Oniichan, are you sure you want to do this?") msgbox.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msgbox.setDefaultButton(QMessageBox.No) if msgbox.exec() == QMessageBox.Yes: path = QFileDialog.getExistingDirectory(None, "Choose a folder containing your series'") if len(path) is not 0: data_thread = QThread() loading_thread = QThread() loading = misc.Loading() if not loading.ON: misc.Loading.ON = True fetch_instance = fetch.Fetch() fetch_instance.series_path = path loading.show() def finished(status): if status: self.manga_list_view.series_model.populate_data() # TODO: make it spawn a dialog instead (from utils.py or misc.py) if loading.progress.maximum() == loading.progress.value(): misc.Loading.ON = False loading.hide() data_thread.quit else: loading.setText("<font color=red>An error occured. Try restarting..</font>") loading.progress.setStyleSheet("background-color:red") data_thread.quit def fetch_deleteLater(): try: fetch_instance.deleteLater except NameError: pass def thread_deleteLater(): #NOTE: Isn't this bad? data_thread.deleteLater def a_progress(prog): loading.progress.setValue(prog) loading.setText("Searching on local disk...\n(Will take a while on first time)") fetch_instance.moveToThread(data_thread) fetch_instance.DATA_COUNT.connect(loading.progress.setMaximum) fetch_instance.PROGRESS.connect(a_progress) data_thread.started.connect(fetch_instance.local) fetch_instance.FINISHED.connect(finished) fetch_instance.FINISHED.connect(fetch_deleteLater) fetch_instance.FINISHED.connect(thread_deleteLater) data_thread.start()
class ImageViewer(QMainWindow): def __init__(self, db, *args, **kwargs): super(ImageViewer, self).__init__(*args, **kwargs) self.db = db self.currentIndex = -1 self.files = [] self._init_widgets() def _init_widgets(self): #self.toolbar = AutoHideToolBar() self.toolbar = QToolBar() self.addToolBar(self.toolbar) self.toolbar.hide() act = self.toolbar.addAction(QIcon.fromTheme('go-previous'), 'Previous') act.setShortcut(QKeySequence(Qt.Key_Backspace)) act.triggered.connect(self.showPreviousFile) act = self.toolbar.addAction(QIcon.fromTheme('go-next'), 'Next') act.setShortcut(QKeySequence(Qt.Key_Space)) act.triggered.connect(self.showNextFile) self.toolbar.addSeparator() self.toolbar.addAction(QIcon.fromTheme('zoom-original'), 'Z 1:1').triggered.connect(self.doNormalZoom) self.toolbar.addAction(QIcon.fromTheme('zoom-fit-best'), 'Z Fit').triggered.connect(self.doFitAllZoom) self.toolbar.addAction(QIcon.fromTheme('zoom-fit-best'), 'Z FitExp').triggered.connect(self.doFitCutZoom) self.toolbar.addAction(QIcon.fromTheme('zoom-in'), 'Z x1.5').triggered.connect(self.zoom) self.toolbar.addAction(QIcon.fromTheme('zoom-out'), 'Z /1.5').triggered.connect(self.unzoom) self.fullscreenAction = self.toolbar.addAction(QIcon.fromTheme('view-fullscreen'), 'Fullscreen') self.fullscreenAction.setCheckable(True) self.fullscreenAction.toggled.connect(self.setFullscreen) self.toolbar.addSeparator() self.toolbar.addAction('Copy tags').triggered.connect(self.copyPreviousTags) self.tageditor = TagEditor(self.db) self.docktagger = AutoHideDock() self.docktagger.setWidget(self.tageditor) self.addDockWidget(Qt.LeftDockWidgetArea, self.docktagger) self.docktagger.hide() self.scrollview = ImageViewerCenter() self.scrollview.installEventFilter(self) ### ! self.setCentralWidget(self.scrollview) self.scrollview.topZoneEntered.connect(self.toolbar.show) self.scrollview.topZoneLeft.connect(self.toolbar.hide) self.scrollview.leftZoneEntered.connect(self.docktagger.show) self.scrollview.leftZoneLeft.connect(self.docktagger.hide) #~ self.setWindowState(self.windowState() | Qt.WindowMaximized) ''' self.qtagwl = QListWidget() self.qtagwl.setParent(self) self.qtagwl.hide() #self.qtagwl.setFixedSize(self.qtagwl.minimumSizeHint()) self.qtagwl.setFrameShape(QFrame.NoFrame) self.qtagwl.setStyleSheet('QListWidget{background-color: rgba(255,255,255,200);}\n *{background-color:rgba(0,255,255,255);}') self.qtagwl.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.qthtimer = QTimer() self.connect(self.qthtimer, SIGNAL('timeout()'), self.qtaghide) ''' def eventFilter(self, sview, ev): if ev.type() == QEvent.KeyPress: if ev.key() == Qt.Key_Escape: self.fullscreenAction.setChecked(False) return True elif ev.key() in [Qt.Key_PageUp, Qt.Key_Backspace]: # qactions self.showPreviousFile() return True elif ev.key() in [Qt.Key_PageDown, Qt.Key_Space]: self.showNextFile() return True return super(ImageViewer, self).eventFilter(sview, ev) @Slot() def doNormalZoom(self): self.scrollview.setZoomFactor(1.) @Slot() def doFitAllZoom(self): self.scrollview.setZoomMode(ZOOM_FITALL) @Slot() def doFitCutZoom(self): self.scrollview.setZoomMode(ZOOM_FITCUT) @Slot() def zoom(self): self.scrollview.multiplyZoomFactor(1.5) @Slot() def unzoom(self): self.scrollview.multiplyZoomFactor(1/1.5) def spawn(self, files, currentFile): self.files = files self.currentIndex = files.index(currentFile) self.setFile(currentFile) if self.isHidden(): #~ self.setWindowState(self.windowState() | Qt.WindowMaximized) #~ self.show() self.fullscreenAction.setChecked(False) self.fullscreenAction.setChecked(True) #~ self.showMaximized() else: self.show() def setFile(self, file): self.tageditor.setFile(file) self.scrollview.setFile(file) @Slot() def copyPreviousTags(self): tags = self.db.find_tags_by_file(self.files[self.currentIndex - 1]) with self.db: self.db.tag_file(self.files[self.currentIndex], tags) self.tageditor.setFile(self.files[self.currentIndex]) def setFullscreen(self, full): if full: self.showFullScreen() else: self.showNormal() @Slot() def showPreviousFile(self): if self.currentIndex > 0: self.currentIndex -= 1 self.setFile(self.files[self.currentIndex]) @Slot() def showNextFile(self): if self.currentIndex < len(self.files) - 1: self.currentIndex += 1 self.setFile(self.files[self.currentIndex])
class SvnProjectHelper(VcsProjectHelper): """ Class implementing the VCS project helper for Subversion. """ def __init__(self, vcsObject, projectObject, parent=None, name=None): """ Constructor @param vcsObject reference to the vcs object @param projectObject reference to the project object @param parent parent widget (QWidget) @param name name of this object (string) """ VcsProjectHelper.__init__(self, vcsObject, projectObject, parent, name) def getActions(self): """ Public method to get a list of all actions. @return list of all actions (list of E5Action) """ return self.actions[:] def initActions(self): """ Public method to generate the action objects. """ self.vcsNewAct = E5Action( self.tr('New from repository'), UI.PixmapCache.getIcon("vcsCheckout.png"), self.tr('&New from repository...'), 0, 0, self, 'subversion_new') self.vcsNewAct.setStatusTip(self.tr( 'Create a new project from the VCS repository' )) self.vcsNewAct.setWhatsThis(self.tr( """<b>New from repository</b>""" """<p>This creates a new local project from the VCS""" """ repository.</p>""" )) self.vcsNewAct.triggered.connect(self._vcsCheckout) self.actions.append(self.vcsNewAct) self.vcsUpdateAct = E5Action( self.tr('Update from repository'), UI.PixmapCache.getIcon("vcsUpdate.png"), self.tr('&Update from repository'), 0, 0, self, 'subversion_update') self.vcsUpdateAct.setStatusTip(self.tr( 'Update the local project from the VCS repository' )) self.vcsUpdateAct.setWhatsThis(self.tr( """<b>Update from repository</b>""" """<p>This updates the local project from the VCS""" """ repository.</p>""" )) self.vcsUpdateAct.triggered.connect(self._vcsUpdate) self.actions.append(self.vcsUpdateAct) self.vcsCommitAct = E5Action( self.tr('Commit changes to repository'), UI.PixmapCache.getIcon("vcsCommit.png"), self.tr('&Commit changes to repository...'), 0, 0, self, 'subversion_commit') self.vcsCommitAct.setStatusTip(self.tr( 'Commit changes to the local project to the VCS repository' )) self.vcsCommitAct.setWhatsThis(self.tr( """<b>Commit changes to repository</b>""" """<p>This commits changes to the local project to the VCS""" """ repository.</p>""" )) self.vcsCommitAct.triggered.connect(self._vcsCommit) self.actions.append(self.vcsCommitAct) self.vcsLogAct = E5Action( self.tr('Show log'), UI.PixmapCache.getIcon("vcsLog.png"), self.tr('Show &log'), 0, 0, self, 'subversion_log') self.vcsLogAct.setStatusTip(self.tr( 'Show the log of the local project' )) self.vcsLogAct.setWhatsThis(self.tr( """<b>Show log</b>""" """<p>This shows the log of the local project.</p>""" )) self.vcsLogAct.triggered.connect(self._vcsLog) self.actions.append(self.vcsLogAct) self.svnLogBrowserAct = E5Action( self.tr('Show log browser'), UI.PixmapCache.getIcon("vcsLog.png"), self.tr('Show log browser'), 0, 0, self, 'subversion_log_browser') self.svnLogBrowserAct.setStatusTip(self.tr( 'Show a dialog to browse the log of the local project' )) self.svnLogBrowserAct.setWhatsThis(self.tr( """<b>Show log browser</b>""" """<p>This shows a dialog to browse the log of the local""" """ project. A limited number of entries is shown first. More""" """ can be retrieved later on.</p>""" )) self.svnLogBrowserAct.triggered.connect(self._vcsLogBrowser) self.actions.append(self.svnLogBrowserAct) self.vcsDiffAct = E5Action( self.tr('Show differences'), UI.PixmapCache.getIcon("vcsDiff.png"), self.tr('Show &difference'), 0, 0, self, 'subversion_diff') self.vcsDiffAct.setStatusTip(self.tr( 'Show the difference of the local project to the repository' )) self.vcsDiffAct.setWhatsThis(self.tr( """<b>Show differences</b>""" """<p>This shows differences of the local project to the""" """ repository.</p>""" )) self.vcsDiffAct.triggered.connect(self._vcsDiff) self.actions.append(self.vcsDiffAct) self.svnExtDiffAct = E5Action( self.tr('Show differences (extended)'), UI.PixmapCache.getIcon("vcsDiff.png"), self.tr('Show differences (extended)'), 0, 0, self, 'subversion_extendeddiff') self.svnExtDiffAct.setStatusTip(self.tr( 'Show the difference of revisions of the project to the repository' )) self.svnExtDiffAct.setWhatsThis(self.tr( """<b>Show differences (extended)</b>""" """<p>This shows differences of selectable revisions of""" """ the project.</p>""" )) self.svnExtDiffAct.triggered.connect(self.__svnExtendedDiff) self.actions.append(self.svnExtDiffAct) self.svnUrlDiffAct = E5Action( self.tr('Show differences (URLs)'), UI.PixmapCache.getIcon("vcsDiff.png"), self.tr('Show differences (URLs)'), 0, 0, self, 'subversion_urldiff') self.svnUrlDiffAct.setStatusTip(self.tr( 'Show the difference of the project between two repository URLs' )) self.svnUrlDiffAct.setWhatsThis(self.tr( """<b>Show differences (URLs)</b>""" """<p>This shows differences of the project between""" """ two repository URLs.</p>""" )) self.svnUrlDiffAct.triggered.connect(self.__svnUrlDiff) self.actions.append(self.svnUrlDiffAct) self.vcsStatusAct = E5Action( self.tr('Show status'), UI.PixmapCache.getIcon("vcsStatus.png"), self.tr('Show &status'), 0, 0, self, 'subversion_status') self.vcsStatusAct.setStatusTip(self.tr( 'Show the status of the local project' )) self.vcsStatusAct.setWhatsThis(self.tr( """<b>Show status</b>""" """<p>This shows the status of the local project.</p>""" )) self.vcsStatusAct.triggered.connect(self._vcsStatus) self.actions.append(self.vcsStatusAct) self.svnChangeListsAct = E5Action( self.tr('Show change lists'), UI.PixmapCache.getIcon("vcsChangeLists.png"), self.tr('Show change lists'), 0, 0, self, 'subversion_changelists') self.svnChangeListsAct.setStatusTip(self.tr( 'Show the change lists and associated files of the local project' )) self.svnChangeListsAct.setWhatsThis(self.tr( """<b>Show change lists</b>""" """<p>This shows the change lists and associated files of the""" """ local project.</p>""" )) self.svnChangeListsAct.triggered.connect(self.__svnChangeLists) self.actions.append(self.svnChangeListsAct) self.vcsTagAct = E5Action( self.tr('Tag in repository'), UI.PixmapCache.getIcon("vcsTag.png"), self.tr('&Tag in repository...'), 0, 0, self, 'subversion_tag') self.vcsTagAct.setStatusTip(self.tr( 'Tag the local project in the repository' )) self.vcsTagAct.setWhatsThis(self.tr( """<b>Tag in repository</b>""" """<p>This tags the local project in the repository.</p>""" )) self.vcsTagAct.triggered.connect(self._vcsTag) self.actions.append(self.vcsTagAct) self.vcsExportAct = E5Action( self.tr('Export from repository'), UI.PixmapCache.getIcon("vcsExport.png"), self.tr('&Export from repository...'), 0, 0, self, 'subversion_export') self.vcsExportAct.setStatusTip(self.tr( 'Export a project from the repository' )) self.vcsExportAct.setWhatsThis(self.tr( """<b>Export from repository</b>""" """<p>This exports a project from the repository.</p>""" )) self.vcsExportAct.triggered.connect(self._vcsExport) self.actions.append(self.vcsExportAct) self.vcsPropsAct = E5Action( self.tr('Command options'), self.tr('Command &options...'), 0, 0, self, 'subversion_options') self.vcsPropsAct.setStatusTip(self.tr( 'Show the VCS command options')) self.vcsPropsAct.setWhatsThis(self.tr( """<b>Command options...</b>""" """<p>This shows a dialog to edit the VCS command options.</p>""" )) self.vcsPropsAct.triggered.connect(self._vcsCommandOptions) self.actions.append(self.vcsPropsAct) self.vcsRevertAct = E5Action( self.tr('Revert changes'), UI.PixmapCache.getIcon("vcsRevert.png"), self.tr('Re&vert changes'), 0, 0, self, 'subversion_revert') self.vcsRevertAct.setStatusTip(self.tr( 'Revert all changes made to the local project' )) self.vcsRevertAct.setWhatsThis(self.tr( """<b>Revert changes</b>""" """<p>This reverts all changes made to the local project.</p>""" )) self.vcsRevertAct.triggered.connect(self._vcsRevert) self.actions.append(self.vcsRevertAct) self.vcsMergeAct = E5Action( self.tr('Merge'), UI.PixmapCache.getIcon("vcsMerge.png"), self.tr('Mer&ge changes...'), 0, 0, self, 'subversion_merge') self.vcsMergeAct.setStatusTip(self.tr( 'Merge changes of a tag/revision into the local project' )) self.vcsMergeAct.setWhatsThis(self.tr( """<b>Merge</b>""" """<p>This merges changes of a tag/revision into the local""" """ project.</p>""" )) self.vcsMergeAct.triggered.connect(self._vcsMerge) self.actions.append(self.vcsMergeAct) self.vcsSwitchAct = E5Action( self.tr('Switch'), UI.PixmapCache.getIcon("vcsSwitch.png"), self.tr('S&witch...'), 0, 0, self, 'subversion_switch') self.vcsSwitchAct.setStatusTip(self.tr( 'Switch the local copy to another tag/branch' )) self.vcsSwitchAct.setWhatsThis(self.tr( """<b>Switch</b>""" """<p>This switches the local copy to another tag/branch.</p>""" )) self.vcsSwitchAct.triggered.connect(self._vcsSwitch) self.actions.append(self.vcsSwitchAct) self.vcsResolveAct = E5Action( self.tr('Conflicts resolved'), self.tr('Con&flicts resolved'), 0, 0, self, 'subversion_resolve') self.vcsResolveAct.setStatusTip(self.tr( 'Mark all conflicts of the local project as resolved' )) self.vcsResolveAct.setWhatsThis(self.tr( """<b>Conflicts resolved</b>""" """<p>This marks all conflicts of the local project as""" """ resolved.</p>""" )) self.vcsResolveAct.triggered.connect(self.__svnResolve) self.actions.append(self.vcsResolveAct) self.vcsCleanupAct = E5Action( self.tr('Cleanup'), self.tr('Cleanu&p'), 0, 0, self, 'subversion_cleanup') self.vcsCleanupAct.setStatusTip(self.tr( 'Cleanup the local project' )) self.vcsCleanupAct.setWhatsThis(self.tr( """<b>Cleanup</b>""" """<p>This performs a cleanup of the local project.</p>""" )) self.vcsCleanupAct.triggered.connect(self._vcsCleanup) self.actions.append(self.vcsCleanupAct) self.vcsCommandAct = E5Action( self.tr('Execute command'), self.tr('E&xecute command...'), 0, 0, self, 'subversion_command') self.vcsCommandAct.setStatusTip(self.tr( 'Execute an arbitrary VCS command' )) self.vcsCommandAct.setWhatsThis(self.tr( """<b>Execute command</b>""" """<p>This opens a dialog to enter an arbitrary VCS command.</p>""" )) self.vcsCommandAct.triggered.connect(self._vcsCommand) self.actions.append(self.vcsCommandAct) self.svnTagListAct = E5Action( self.tr('List tags'), self.tr('List tags...'), 0, 0, self, 'subversion_list_tags') self.svnTagListAct.setStatusTip(self.tr( 'List tags of the project' )) self.svnTagListAct.setWhatsThis(self.tr( """<b>List tags</b>""" """<p>This lists the tags of the project.</p>""" )) self.svnTagListAct.triggered.connect(self.__svnTagList) self.actions.append(self.svnTagListAct) self.svnBranchListAct = E5Action( self.tr('List branches'), self.tr('List branches...'), 0, 0, self, 'subversion_list_branches') self.svnBranchListAct.setStatusTip(self.tr( 'List branches of the project' )) self.svnBranchListAct.setWhatsThis(self.tr( """<b>List branches</b>""" """<p>This lists the branches of the project.</p>""" )) self.svnBranchListAct.triggered.connect(self.__svnBranchList) self.actions.append(self.svnBranchListAct) self.svnListAct = E5Action( self.tr('List repository contents'), self.tr('List repository contents...'), 0, 0, self, 'subversion_contents') self.svnListAct.setStatusTip(self.tr( 'Lists the contents of the repository' )) self.svnListAct.setWhatsThis(self.tr( """<b>List repository contents</b>""" """<p>This lists the contents of the repository.</p>""" )) self.svnListAct.triggered.connect(self.__svnTagList) self.actions.append(self.svnListAct) self.svnPropSetAct = E5Action( self.tr('Set Property'), self.tr('Set Property...'), 0, 0, self, 'subversion_property_set') self.svnPropSetAct.setStatusTip(self.tr( 'Set a property for the project files' )) self.svnPropSetAct.setWhatsThis(self.tr( """<b>Set Property</b>""" """<p>This sets a property for the project files.</p>""" )) self.svnPropSetAct.triggered.connect(self.__svnPropSet) self.actions.append(self.svnPropSetAct) self.svnPropListAct = E5Action( self.tr('List Properties'), self.tr('List Properties...'), 0, 0, self, 'subversion_property_list') self.svnPropListAct.setStatusTip(self.tr( 'List properties of the project files' )) self.svnPropListAct.setWhatsThis(self.tr( """<b>List Properties</b>""" """<p>This lists the properties of the project files.</p>""" )) self.svnPropListAct.triggered.connect(self.__svnPropList) self.actions.append(self.svnPropListAct) self.svnPropDelAct = E5Action( self.tr('Delete Property'), self.tr('Delete Property...'), 0, 0, self, 'subversion_property_delete') self.svnPropDelAct.setStatusTip(self.tr( 'Delete a property for the project files' )) self.svnPropDelAct.setWhatsThis(self.tr( """<b>Delete Property</b>""" """<p>This deletes a property for the project files.</p>""" )) self.svnPropDelAct.triggered.connect(self.__svnPropDel) self.actions.append(self.svnPropDelAct) self.svnRelocateAct = E5Action( self.tr('Relocate'), UI.PixmapCache.getIcon("vcsSwitch.png"), self.tr('Relocate...'), 0, 0, self, 'subversion_relocate') self.svnRelocateAct.setStatusTip(self.tr( 'Relocate the working copy to a new repository URL' )) self.svnRelocateAct.setWhatsThis(self.tr( """<b>Relocate</b>""" """<p>This relocates the working copy to a new repository""" """ URL.</p>""" )) self.svnRelocateAct.triggered.connect(self.__svnRelocate) self.actions.append(self.svnRelocateAct) self.svnRepoBrowserAct = E5Action( self.tr('Repository Browser'), UI.PixmapCache.getIcon("vcsRepoBrowser.png"), self.tr('Repository Browser...'), 0, 0, self, 'subversion_repo_browser') self.svnRepoBrowserAct.setStatusTip(self.tr( 'Show the Repository Browser dialog' )) self.svnRepoBrowserAct.setWhatsThis(self.tr( """<b>Repository Browser</b>""" """<p>This shows the Repository Browser dialog.</p>""" )) self.svnRepoBrowserAct.triggered.connect(self.__svnRepoBrowser) self.actions.append(self.svnRepoBrowserAct) self.svnConfigAct = E5Action( self.tr('Configure'), self.tr('Configure...'), 0, 0, self, 'subversion_configure') self.svnConfigAct.setStatusTip(self.tr( 'Show the configuration dialog with the Subversion page selected' )) self.svnConfigAct.setWhatsThis(self.tr( """<b>Configure</b>""" """<p>Show the configuration dialog with the Subversion page""" """ selected.</p>""" )) self.svnConfigAct.triggered.connect(self.__svnConfigure) self.actions.append(self.svnConfigAct) self.svnUpgradeAct = E5Action( self.tr('Upgrade'), self.tr('Upgrade...'), 0, 0, self, 'subversion_upgrade') self.svnUpgradeAct.setStatusTip(self.tr( 'Upgrade the working copy to the current format' )) self.svnUpgradeAct.setWhatsThis(self.tr( """<b>Upgrade</b>""" """<p>Upgrades the working copy to the current format.</p>""" )) self.svnUpgradeAct.triggered.connect(self.__svnUpgrade) self.actions.append(self.svnUpgradeAct) def initMenu(self, menu): """ Public method to generate the VCS menu. @param menu reference to the menu to be populated (QMenu) """ menu.clear() act = menu.addAction( UI.PixmapCache.getIcon( os.path.join("VcsPlugins", "vcsSubversion", "icons", "subversion.png")), self.vcs.vcsName(), self._vcsInfoDisplay) font = act.font() font.setBold(True) act.setFont(font) menu.addSeparator() menu.addAction(self.vcsUpdateAct) menu.addAction(self.vcsCommitAct) menu.addSeparator() menu.addAction(self.vcsTagAct) if self.vcs.otherData["standardLayout"]: menu.addAction(self.svnTagListAct) menu.addAction(self.svnBranchListAct) else: menu.addAction(self.svnListAct) menu.addSeparator() menu.addAction(self.vcsLogAct) menu.addAction(self.svnLogBrowserAct) menu.addSeparator() menu.addAction(self.vcsStatusAct) menu.addAction(self.svnChangeListsAct) menu.addSeparator() menu.addAction(self.vcsDiffAct) menu.addAction(self.svnExtDiffAct) menu.addAction(self.svnUrlDiffAct) menu.addSeparator() menu.addAction(self.vcsRevertAct) menu.addAction(self.vcsMergeAct) menu.addAction(self.vcsResolveAct) menu.addSeparator() menu.addAction(self.svnRelocateAct) menu.addAction(self.vcsSwitchAct) menu.addSeparator() menu.addAction(self.svnPropSetAct) menu.addAction(self.svnPropListAct) menu.addAction(self.svnPropDelAct) menu.addSeparator() menu.addAction(self.vcsCleanupAct) menu.addSeparator() menu.addAction(self.vcsCommandAct) menu.addAction(self.svnRepoBrowserAct) menu.addAction(self.svnUpgradeAct) menu.addSeparator() menu.addAction(self.vcsPropsAct) menu.addSeparator() menu.addAction(self.svnConfigAct) menu.addSeparator() menu.addAction(self.vcsNewAct) menu.addAction(self.vcsExportAct) def initToolbar(self, ui, toolbarManager): """ Public slot to initialize the VCS toolbar. @param ui reference to the main window (UserInterface) @param toolbarManager reference to a toolbar manager object (E5ToolBarManager) """ self.__toolbar = QToolBar(self.tr("Subversion (svn)"), ui) self.__toolbar.setIconSize(UI.Config.ToolBarIconSize) self.__toolbar.setObjectName("SubversionToolbar") self.__toolbar.setToolTip(self.tr('Subversion (svn)')) self.__toolbar.addAction(self.svnLogBrowserAct) self.__toolbar.addAction(self.vcsStatusAct) self.__toolbar.addSeparator() self.__toolbar.addAction(self.vcsDiffAct) self.__toolbar.addSeparator() self.__toolbar.addAction(self.svnRepoBrowserAct) self.__toolbar.addAction(self.vcsNewAct) self.__toolbar.addAction(self.vcsExportAct) self.__toolbar.addSeparator() title = self.__toolbar.windowTitle() toolbarManager.addToolBar(self.__toolbar, title) toolbarManager.addAction(self.vcsUpdateAct, title) toolbarManager.addAction(self.vcsCommitAct, title) toolbarManager.addAction(self.vcsLogAct, title) toolbarManager.addAction(self.svnExtDiffAct, title) toolbarManager.addAction(self.svnUrlDiffAct, title) toolbarManager.addAction(self.svnChangeListsAct, title) toolbarManager.addAction(self.vcsTagAct, title) toolbarManager.addAction(self.vcsRevertAct, title) toolbarManager.addAction(self.vcsMergeAct, title) toolbarManager.addAction(self.vcsSwitchAct, title) toolbarManager.addAction(self.svnRelocateAct, title) self.__toolbar.setEnabled(False) self.__toolbar.setVisible(False) ui.registerToolbar("subversion", self.__toolbar.windowTitle(), self.__toolbar) ui.addToolBar(self.__toolbar) def removeToolbar(self, ui, toolbarManager): """ Public method to remove a toolbar created by initToolbar(). @param ui reference to the main window (UserInterface) @param toolbarManager reference to a toolbar manager object (E5ToolBarManager) """ ui.removeToolBar(self.__toolbar) ui.unregisterToolbar("subversion") title = self.__toolbar.windowTitle() toolbarManager.removeCategoryActions(title) toolbarManager.removeToolBar(self.__toolbar) self.__toolbar.deleteLater() self.__toolbar = None def __svnResolve(self): """ Private slot used to resolve conflicts of the local project. """ self.vcs.svnResolve(self.project.ppath) def __svnPropList(self): """ Private slot used to list the properties of the project files. """ self.vcs.svnListProps(self.project.ppath, True) def __svnPropSet(self): """ Private slot used to set a property for the project files. """ self.vcs.svnSetProp(self.project.ppath, True) def __svnPropDel(self): """ Private slot used to delete a property for the project files. """ self.vcs.svnDelProp(self.project.ppath, True) def __svnTagList(self): """ Private slot used to list the tags of the project. """ self.vcs.svnListTagBranch(self.project.ppath, True) def __svnBranchList(self): """ Private slot used to list the branches of the project. """ self.vcs.svnListTagBranch(self.project.ppath, False) def __svnExtendedDiff(self): """ Private slot used to perform a svn diff with the selection of revisions. """ self.vcs.svnExtendedDiff(self.project.ppath) def __svnUrlDiff(self): """ Private slot used to perform a svn diff with the selection of repository URLs. """ self.vcs.svnUrlDiff(self.project.ppath) def __svnRelocate(self): """ Private slot used to relocate the working copy to a new repository URL. """ self.vcs.svnRelocate(self.project.ppath) def __svnRepoBrowser(self): """ Private slot to open the repository browser. """ self.vcs.svnRepoBrowser(projectPath=self.project.ppath) def __svnConfigure(self): """ Private slot to open the configuration dialog. """ e5App().getObject("UserInterface")\ .showPreferences("zzz_subversionPage") def __svnChangeLists(self): """ Private slot used to show a list of change lists. """ self.vcs.svnShowChangelists(self.project.ppath) def __svnUpgrade(self): """ Private slot used to upgrade the working copy format. """ self.vcs.svnUpgrade(self.project.ppath)
def __init__(self, parent=None): super(GcodeEditor, self).__init__(parent) self.isCaseSensitive = 0 self.setMinimumSize(QSize(300, 200)) self.setWindowTitle("PyQt5 editor test example") lay = QVBoxLayout() lay.setContentsMargins(0,0,0,0) self.setLayout(lay) # make editor self.editor = GcodeDisplay(self) # class patch editor's function to ours # so we get the lines percent update self.editor.emit_percent = self.emit_percent self.editor.setReadOnly(True) ################################ # add menubar actions ################################ # Create new action newAction = QAction(QIcon.fromTheme('document-new'), 'New', self) newAction.setShortcut('Ctrl+N') newAction.setStatusTip('New document') newAction.triggered.connect(self.newCall) # Create open action openAction = QAction(QIcon.fromTheme('document-open'), '&Open', self) openAction.setShortcut('Ctrl+O') openAction.setStatusTip('Open document') openAction.triggered.connect(self.openCall) # Create save action saveAction = QAction(QIcon.fromTheme('document-save'), '&save', self) saveAction.setShortcut('Ctrl+S') saveAction.setStatusTip('save document') saveAction.triggered.connect(self.saveCall) # Create exit action exitAction = QAction(QIcon.fromTheme('application-exit'), '&Exit', self) exitAction.setShortcut('Ctrl+Q') exitAction.setStatusTip('Exit application') exitAction.triggered.connect(self.exitCall) # Create gcode lexer action gCodeLexerAction = QAction(QIcon.fromTheme('lexer.png'), '&Gcode\n lexer', self) gCodeLexerAction.setCheckable(1) gCodeLexerAction.setShortcut('Ctrl+G') gCodeLexerAction.setStatusTip('Set Gcode highlighting') gCodeLexerAction.triggered.connect(self.editor.set_gcode_lexer) # Create gcode lexer action pythonLexerAction = QAction(QIcon.fromTheme('lexer.png'), '&python\n lexer', self) pythonLexerAction.setShortcut('Ctrl+P') pythonLexerAction.setStatusTip('Set Python highlighting') pythonLexerAction.triggered.connect(self.editor.set_python_lexer) # Create toolbar and add action toolBar = QToolBar('File') toolBar.addAction(newAction) toolBar.addAction(openAction) toolBar.addAction(saveAction) toolBar.addAction(exitAction) toolBar.addSeparator() # add lexer actions toolBar.addAction(gCodeLexerAction) toolBar.addAction(pythonLexerAction) toolBar.addSeparator() toolBar.addWidget(QLabel('<html><head/><body><p><span style=" font-size:20pt; font-weight:600;">Edit Mode</span></p></body></html>')) # create a frame for buttons box = QHBoxLayout() box.addWidget(toolBar) self.topMenu = QFrame() self.topMenu.setLayout(box) # add widgets lay.addWidget(self.topMenu) lay.addWidget(self.editor) lay.addWidget(self.createGroup()) self.readOnlyMode()
class CalibrateSpectrum(QWidget): def __init__(self, fits_file, settings, database, project=None): super(CalibrateSpectrum, self).__init__() self.project=project self.settings = settings self.fits_spectrum = FitsSpectrum(fits_file) self.fits_spectrum.spectrum.normalize_to_max() self.fits_file = fits_file self.ui = Ui_CalibrateSpectrum() self.ui.setupUi(self) self.toolbar = QToolBar('Calibration Toolbar') self.toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.ui.x_axis_pick.setMenu(QMenu()) self.ui.x_axis_pick.menu().addAction("Maximum from range").triggered.connect(lambda: self.pick_from_range('maximum')) self.ui.x_axis_pick.menu().addAction("Minimum from range").triggered.connect(lambda: self.pick_from_range('minimum')) self.ui.x_axis_pick.menu().addAction("Central value from range").triggered.connect(lambda: self.pick_from_range('central')) self.ui.wavelength_pick.clicked.connect(lambda: self.lines_dialog.show()) save_action = self.toolbar.addAction(QIcon(':/save_20'), 'Save', self.save_spectrum) self.spectrum_plot = QtCommons.nestWidget(self.ui.spectrum_plot_widget, QMathPlotWidget()) self.reference_spectra_dialog = ReferenceSpectraDialog(database) self.reference_spectra_dialog.setup_menu(self.toolbar, self.spectrum_plot.axes, settings) self.object_properties = ObjectProperties(self.fits_file, project=project) self.object_properties_dialog = ObjectPropertiesDialog(settings, self.object_properties) self.toolbar.addAction("Object properties", self.object_properties_dialog.show) self.calibration_model = QStandardItemModel() self.calibration_model.setHorizontalHeaderLabels(["x-axis", "wavelength", "error"]) self.ui.calibration_points.setModel(self.calibration_model) self.ui.calibration_points.selectionModel().selectionChanged.connect(lambda selected, deselected: self.ui.remove_calibration_point.setEnabled(len(selected.indexes()) > 0) ) self.ui.add_calibration_point.clicked.connect(self.add_calibration_point) self.ui.remove_calibration_point.setEnabled(False) self.ui.remove_calibration_point.clicked.connect(self.remove_calibration_point) if project and project.avg_dispersion(): self.ui.set_dispersion.setMenu(QMenu()) self.ui.set_dispersion.menu().addAction('From input value', self.calculate_calibration) self.ui.set_dispersion.menu().addAction('From Project', lambda: self.calculate_calibration(project.avg_dispersion())) else: self.ui.set_dispersion.clicked.connect(self.calculate_calibration) self.ui.point_is_star.toggled.connect(lambda checked: self.ui.wavelength_pick.setEnabled(not checked)) self.ui.point_is_star.toggled.connect(lambda checked: self.ui.point_wavelength.setEnabled(not checked)) self.fits_spectrum.plot_to(self.spectrum_plot.axes) self.toolbar.addSeparator() self.toolbar.addAction("Zoom", self.spectrum_plot.select_zoom) self.toolbar.addAction("Reset Zoom", lambda: self.spectrum_plot.reset_zoom(self.fits_spectrum.spectrum.wavelengths, self.fits_spectrum.spectrum.fluxes.min(), self.fits_spectrum.spectrum.fluxes.max())) self.toolbar.addSeparator() self.lines_dialog = LinesDialog(database, settings, self.spectrum_plot, enable_picker = False, selection_mode = 'single') self.lines_dialog.lines.connect(self.picked_line) hdu_calibration_points = [h for h in self.fits_file if h.name == FitsSpectrum.CALIBRATION_DATA] if len(hdu_calibration_points) > 0: for point in hdu_calibration_points[-1].data: self.add_calibration_point_data(point[0], point[1]) self.calculate_calibration() def picked_from_range(self, type, min, max): min=(self.fits_spectrum.spectrum.wavelength_index(min)) max=(self.fits_spectrum.spectrum.wavelength_index(max)) add_line = lambda x: self.spectrum_plot.add_line("x_axis_pick", self.fits_spectrum.spectrum.wavelengths[x], color='r') set_x_value = lambda x: self.ui.point_x_axis.setValue(x) if type != 'central': subplot = SelectPlottedPoints(self.fits_spectrum.spectrum.fluxes, min, max+1, self.settings, type) subplot.point.connect(add_line) subplot.point.connect(set_x_value) subplot.show() return point = min+(max-min)/2 self.ui.point_x_axis.setValue(point) set_x_value(point) add_line(point) def pick_from_range(self, type): self.spectrum_plot.add_span_selector('pick_x_axis', lambda min,max: self.picked_from_range(type, min, max),direction='horizontal') def remove_calibration_point(self): self.calibration_model.removeRow(self.ui.calibration_points.selectionModel().selectedIndexes()[0].row()) self.calculate_calibration() def add_calibration_point_data(self, x_value, wavelength): x_axis_item = QStandardItem("star" if x_value == 0 else "{}".format(x_value)) x_axis_item.setData(x_value) wavelength_item = QStandardItem("{:.2f}".format(wavelength)) wavelength_item.setData(wavelength) self.calibration_model.appendRow([x_axis_item, wavelength_item, QStandardItem("n/a")]) self.spectrum_plot.rm_element('x_axis_pick') def picked_line(self, lines): self.ui.point_wavelength.setValue(lines[0]['lambda']) def add_calibration_point(self): self.add_calibration_point_data(self.ui.point_x_axis.value(), 0 if self.ui.point_is_star.isChecked() else self.ui.point_wavelength.value()) self.calculate_calibration() def calibration_points(self): return [{'row': row, 'x': self.calibration_model.item(row, 0).data(), 'wavelength': self.calibration_model.item(row, 1).data()} for row in range(self.calibration_model.rowCount())] def calculate_calibration(self, dispersion = None): points_number = self.calibration_model.rowCount() self.ui.set_dispersion.setEnabled(points_number == 1) self.ui.dispersion.setEnabled(points_number == 1) if points_number == 0: self.fits_spectrum.reset() self.lines_dialog.set_picker_enabled(False) else: self.lines_dialog.set_picker_enabled(True) points = sorted(self.calibration_points(), key=lambda point: point['x']) self.fits_spectrum.calibrate(points, dispersion if dispersion else self.ui.dispersion.value() ) for row, value in [(p['row'], "{:.2f}".format( p['wavelength']-self.fits_spectrum.spectrum.wavelengths[p['x']])) for p in points]: self.calibration_model.item(row, 2).setText(value) self.ui.dispersion.setValue(self.fits_spectrum.spectrum.dispersion()) self.fits_spectrum.plot_to(self.spectrum_plot.axes) def save_spectrum(self): if not self.project: save_file_sticky('Save plot...', 'FITS file (.fit)', lambda f: self.save(f[0]), self.settings, CALIBRATED_PROFILE, [RAW_PROFILE]) return self.project.add_file(Project.CALIBRATED_PROFILE, object_properties = self.object_properties, on_added=self.save) def save(self, filename): self.fits_spectrum.save(filename, self.calibration_points())
class UMLDialog(E5MainWindow): """ Class implementing a dialog showing UML like diagrams. """ NoDiagram = 255 ClassDiagram = 0 PackageDiagram = 1 ImportsDiagram = 2 ApplicationDiagram = 3 FileVersions = ["1.0"] def __init__(self, diagramType, project, path="", parent=None, initBuilder=True, **kwargs): """ Constructor @param diagramType type of the diagram (one of ApplicationDiagram, ClassDiagram, ImportsDiagram, NoDiagram, PackageDiagram) @param project reference to the project object (Project) @param path file or directory path to build the diagram from (string) @param parent parent widget of the dialog (QWidget) @keyparam initBuilder flag indicating to initialize the diagram builder (boolean) @keyparam kwargs diagram specific data """ super(UMLDialog, self).__init__(parent) self.setObjectName("UMLDialog") self.__diagramType = diagramType self.__project = project from .UMLGraphicsView import UMLGraphicsView self.scene = QGraphicsScene(0.0, 0.0, 800.0, 600.0) self.umlView = UMLGraphicsView(self.scene, parent=self) self.builder = self.__diagramBuilder( self.__diagramType, path, **kwargs) if self.builder and initBuilder: self.builder.initialize() self.__fileName = "" self.__initActions() self.__initToolBars() self.setCentralWidget(self.umlView) self.umlView.relayout.connect(self.__relayout) self.setWindowTitle(self.__diagramTypeString()) def __initActions(self): """ Private slot to initialize the actions. """ self.closeAct = \ QAction(UI.PixmapCache.getIcon("close.png"), self.tr("Close"), self) self.closeAct.triggered.connect(self.close) self.openAct = \ QAction(UI.PixmapCache.getIcon("open.png"), self.tr("Load"), self) self.openAct.triggered.connect(self.load) self.saveAct = \ QAction(UI.PixmapCache.getIcon("fileSave.png"), self.tr("Save"), self) self.saveAct.triggered.connect(self.__save) self.saveAsAct = \ QAction(UI.PixmapCache.getIcon("fileSaveAs.png"), self.tr("Save As..."), self) self.saveAsAct.triggered.connect(self.__saveAs) self.saveImageAct = \ QAction(UI.PixmapCache.getIcon("fileSavePixmap.png"), self.tr("Save as Image"), self) self.saveImageAct.triggered.connect(self.umlView.saveImage) self.printAct = \ QAction(UI.PixmapCache.getIcon("print.png"), self.tr("Print"), self) self.printAct.triggered.connect(self.umlView.printDiagram) self.printPreviewAct = \ QAction(UI.PixmapCache.getIcon("printPreview.png"), self.tr("Print Preview"), self) self.printPreviewAct.triggered.connect( self.umlView.printPreviewDiagram) def __initToolBars(self): """ Private slot to initialize the toolbars. """ self.windowToolBar = QToolBar(self.tr("Window"), self) self.windowToolBar.setIconSize(UI.Config.ToolBarIconSize) self.windowToolBar.addAction(self.closeAct) self.fileToolBar = QToolBar(self.tr("File"), self) self.fileToolBar.setIconSize(UI.Config.ToolBarIconSize) self.fileToolBar.addAction(self.openAct) self.fileToolBar.addSeparator() self.fileToolBar.addAction(self.saveAct) self.fileToolBar.addAction(self.saveAsAct) self.fileToolBar.addAction(self.saveImageAct) self.fileToolBar.addSeparator() self.fileToolBar.addAction(self.printPreviewAct) self.fileToolBar.addAction(self.printAct) self.umlToolBar = self.umlView.initToolBar() self.addToolBar(Qt.TopToolBarArea, self.fileToolBar) self.addToolBar(Qt.TopToolBarArea, self.windowToolBar) self.addToolBar(Qt.TopToolBarArea, self.umlToolBar) def show(self, fromFile=False): """ Public method to show the dialog. @keyparam fromFile flag indicating, that the diagram was loaded from file (boolean) """ if not fromFile and self.builder: self.builder.buildDiagram() super(UMLDialog, self).show() def __relayout(self): """ Private method to relayout the diagram. """ if self.builder: self.builder.buildDiagram() def __diagramBuilder(self, diagramType, path, **kwargs): """ Private method to instantiate a diagram builder object. @param diagramType type of the diagram (one of ApplicationDiagram, ClassDiagram, ImportsDiagram, PackageDiagram) @param path file or directory path to build the diagram from (string) @keyparam kwargs diagram specific data @return reference to the instantiated diagram builder @exception ValueError raised to indicate an illegal diagram type """ if diagramType == UMLDialog.ClassDiagram: from .UMLClassDiagramBuilder import UMLClassDiagramBuilder return UMLClassDiagramBuilder( self, self.umlView, self.__project, path, **kwargs) elif diagramType == UMLDialog.PackageDiagram: from .PackageDiagramBuilder import PackageDiagramBuilder return PackageDiagramBuilder( self, self.umlView, self.__project, path, **kwargs) elif diagramType == UMLDialog.ImportsDiagram: from .ImportsDiagramBuilder import ImportsDiagramBuilder return ImportsDiagramBuilder( self, self.umlView, self.__project, path, **kwargs) elif diagramType == UMLDialog.ApplicationDiagram: from .ApplicationDiagramBuilder import ApplicationDiagramBuilder return ApplicationDiagramBuilder( self, self.umlView, self.__project, **kwargs) elif diagramType == UMLDialog.NoDiagram: return None else: raise ValueError(self.tr( "Illegal diagram type '{0}' given.").format(diagramType)) def __diagramTypeString(self): """ Private method to generate a readable string for the diagram type. @return readable type string (string) """ if self.__diagramType == UMLDialog.ClassDiagram: return "Class Diagram" elif self.__diagramType == UMLDialog.PackageDiagram: return "Package Diagram" elif self.__diagramType == UMLDialog.ImportsDiagram: return "Imports Diagram" elif self.__diagramType == UMLDialog.ApplicationDiagram: return "Application Diagram" else: return "Illegal Diagram Type" def __save(self): """ Private slot to save the diagram with the current name. """ self.__saveAs(self.__fileName) @pyqtSlot() def __saveAs(self, filename=""): """ Private slot to save the diagram. @param filename name of the file to write to (string) """ if not filename: fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( self, self.tr("Save Diagram"), "", self.tr("Eric Graphics File (*.e5g);;All Files (*)"), "", E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) if not fname: return ext = QFileInfo(fname).suffix() if not ext: ex = selectedFilter.split("(*")[1].split(")")[0] if ex: fname += ex if QFileInfo(fname).exists(): res = E5MessageBox.yesNo( self, self.tr("Save Diagram"), self.tr("<p>The file <b>{0}</b> already exists." " Overwrite it?</p>").format(fname), icon=E5MessageBox.Warning) if not res: return filename = fname lines = [ "version: 1.0", "diagram_type: {0} ({1})".format( self.__diagramType, self.__diagramTypeString()), "scene_size: {0};{1}".format(self.scene.width(), self.scene.height()), ] persistenceData = self.builder.getPersistenceData() if persistenceData: lines.append("builder_data: {0}".format(persistenceData)) lines.extend(self.umlView.getPersistenceData()) try: f = open(filename, "w", encoding="utf-8") f.write("\n".join(lines)) f.close() except (IOError, OSError) as err: E5MessageBox.critical( self, self.tr("Save Diagram"), self.tr( """<p>The file <b>{0}</b> could not be saved.</p>""" """<p>Reason: {1}</p>""").format(filename, str(err))) return self.__fileName = filename def load(self): """ Public method to load a diagram from a file. @return flag indicating success (boolean) """ filename = E5FileDialog.getOpenFileName( self, self.tr("Load Diagram"), "", self.tr("Eric Graphics File (*.e5g);;All Files (*)")) if not filename: # Cancelled by user return False try: f = open(filename, "r", encoding="utf-8") data = f.read() f.close() except (IOError, OSError) as err: E5MessageBox.critical( self, self.tr("Load Diagram"), self.tr( """<p>The file <b>{0}</b> could not be read.</p>""" """<p>Reason: {1}</p>""").format(filename, str(err))) return False lines = data.splitlines() if len(lines) < 3: self.__showInvalidDataMessage(filename) return False try: # step 1: check version linenum = 0 key, value = lines[linenum].split(": ", 1) if key.strip() != "version" or \ value.strip() not in UMLDialog.FileVersions: self.__showInvalidDataMessage(filename, linenum) return False else: version = value # step 2: extract diagram type linenum += 1 key, value = lines[linenum].split(": ", 1) if key.strip() != "diagram_type": self.__showInvalidDataMessage(filename, linenum) return False try: self.__diagramType = int(value.strip().split(None, 1)[0]) except ValueError: self.__showInvalidDataMessage(filename, linenum) return False self.scene.clear() self.builder = self.__diagramBuilder(self.__diagramType, "") # step 3: extract scene size linenum += 1 key, value = lines[linenum].split(": ", 1) if key.strip() != "scene_size": self.__showInvalidDataMessage(filename, linenum) return False try: width, height = [float(v.strip()) for v in value.split(";")] except ValueError: self.__showInvalidDataMessage(filename, linenum) return False self.umlView.setSceneSize(width, height) # step 4: extract builder data if available linenum += 1 key, value = lines[linenum].split(": ", 1) if key.strip() == "builder_data": ok = self.builder.parsePersistenceData(version, value) if not ok: self.__showInvalidDataMessage(filename, linenum) return False linenum += 1 # step 5: extract the graphics items ok, vlinenum = self.umlView.parsePersistenceData( version, lines[linenum:]) if not ok: self.__showInvalidDataMessage(filename, linenum + vlinenum) return False except IndexError: self.__showInvalidDataMessage(filename) return False # everything worked fine, so remember the file name self.__fileName = filename return True def __showInvalidDataMessage(self, filename, linenum=-1): """ Private slot to show a message dialog indicating an invalid data file. @param filename name of the file containing the invalid data (string) @param linenum number of the invalid line (integer) """ if linenum < 0: msg = self.tr("""<p>The file <b>{0}</b> does not contain""" """ valid data.</p>""").format(filename) else: msg = self.tr("""<p>The file <b>{0}</b> does not contain""" """ valid data.</p><p>Invalid line: {1}</p>""" ).format(filename, linenum + 1) E5MessageBox.critical(self, self.tr("Load Diagram"), msg)
class HgServeDialog(E5MainWindow): """ Class implementing a dialog for the Mercurial server. """ def __init__(self, vcs, path, parent=None): """ Constructor @param vcs reference to the vcs object @param path path of the repository to serve (string) @param parent reference to the parent widget (QWidget) """ super(HgServeDialog, self).__init__(parent) self.vcs = vcs self.__repoPath = path self.__styles = ["paper", "coal", "gitweb", "monoblue", "spartan", ] self.setWindowTitle(self.tr("Mercurial Server")) self.__startAct = QAction( UI.PixmapCache.getIcon( os.path.join("VcsPlugins", "vcsMercurial", "icons", "startServer.png")), self.tr("Start Server"), self) self.__startAct.triggered.connect(self.__startServer) self.__stopAct = QAction( UI.PixmapCache.getIcon( os.path.join("VcsPlugins", "vcsMercurial", "icons", "stopServer.png")), self.tr("Stop Server"), self) self.__stopAct.triggered.connect(self.__stopServer) self.__browserAct = QAction( UI.PixmapCache.getIcon("home.png"), self.tr("Start Browser"), self) self.__browserAct.triggered.connect(self.__startBrowser) self.__portSpin = QSpinBox(self) self.__portSpin.setMinimum(2048) self.__portSpin.setMaximum(65535) self.__portSpin.setToolTip(self.tr("Enter the server port")) self.__portSpin.setValue( self.vcs.getPlugin().getPreferences("ServerPort")) self.__styleCombo = QComboBox(self) self.__styleCombo.addItems(self.__styles) self.__styleCombo.setToolTip(self.tr("Select the style to use")) self.__styleCombo.setCurrentIndex(self.__styleCombo.findText( self.vcs.getPlugin().getPreferences("ServerStyle"))) self.__serverToolbar = QToolBar(self.tr("Server"), self) self.__serverToolbar.addAction(self.__startAct) self.__serverToolbar.addAction(self.__stopAct) self.__serverToolbar.addSeparator() self.__serverToolbar.addWidget(self.__portSpin) self.__serverToolbar.addWidget(self.__styleCombo) self.__browserToolbar = QToolBar(self.tr("Browser"), self) self.__browserToolbar.addAction(self.__browserAct) self.addToolBar(Qt.TopToolBarArea, self.__serverToolbar) self.addToolBar(Qt.TopToolBarArea, self.__browserToolbar) self.__log = QPlainTextEdit(self) self.setCentralWidget(self.__log) # polish up the dialog self.__startAct.setEnabled(True) self.__stopAct.setEnabled(False) self.__browserAct.setEnabled(False) self.__portSpin.setEnabled(True) self.__styleCombo.setEnabled(True) self.resize(QSize(800, 600).expandedTo(self.minimumSizeHint())) self.process = QProcess() self.process.finished.connect(self.__procFinished) self.process.readyReadStandardOutput.connect(self.__readStdout) self.process.readyReadStandardError.connect(self.__readStderr) self.cNormalFormat = self.__log.currentCharFormat() self.cErrorFormat = self.__log.currentCharFormat() self.cErrorFormat.setForeground( QBrush(Preferences.getUI("LogStdErrColour"))) def __startServer(self): """ Private slot to start the Mercurial server. """ port = self.__portSpin.value() style = self.__styleCombo.currentText() args = self.vcs.initCommand("serve") args.append("-v") args.append("--port") args.append(str(port)) args.append("--style") args.append(style) self.process.setWorkingDirectory(self.__repoPath) self.process.start('hg', args) procStarted = self.process.waitForStarted(5000) if procStarted: self.__startAct.setEnabled(False) self.__stopAct.setEnabled(True) self.__browserAct.setEnabled(True) self.__portSpin.setEnabled(False) self.__styleCombo.setEnabled(False) self.vcs.getPlugin().setPreferences("ServerPort", port) self.vcs.getPlugin().setPreferences("ServerStyle", style) else: E5MessageBox.critical( self, self.tr('Process Generation Error'), self.tr( 'The process {0} could not be started. ' 'Ensure, that it is in the search path.' ).format('hg')) def __stopServer(self): """ Private slot to stop the Mercurial server. """ if self.process is not None and \ self.process.state() != QProcess.NotRunning: self.process.terminate() self.process.waitForFinished(5000) if self.process.state() != QProcess.NotRunning: self.process.kill() self.__startAct.setEnabled(True) self.__stopAct.setEnabled(False) self.__browserAct.setEnabled(False) self.__portSpin.setEnabled(True) self.__styleCombo.setEnabled(True) def __startBrowser(self): """ Private slot to start a browser for the served repository. """ ui = e5App().getObject("UserInterface") ui.launchHelpViewer( "http://localhost:{0}".format(self.__portSpin.value())) def closeEvent(self, e): """ Protected slot implementing a close event handler. @param e close event (QCloseEvent) """ self.__stopServer() def __procFinished(self, exitCode, exitStatus): """ Private slot connected to the finished signal. @param exitCode exit code of the process (integer) @param exitStatus exit status of the process (QProcess.ExitStatus) """ self.__stopServer() def __readStdout(self): """ Private slot to handle the readyReadStandardOutput signal. It reads the output of the process and inserts it into the log. """ if self.process is not None: s = str(self.process.readAllStandardOutput(), self.vcs.getEncoding(), 'replace') self.__appendText(s, False) def __readStderr(self): """ Private slot to handle the readyReadStandardError signal. It reads the error output of the process and inserts it into the log. """ if self.process is not None: s = str(self.process.readAllStandardError(), self.vcs.getEncoding(), 'replace') self.__appendText(s, True) def __appendText(self, txt, error=False): """ Private method to append text to the end. @param txt text to insert (string) @param error flag indicating to insert error text (boolean) """ tc = self.__log.textCursor() tc.movePosition(QTextCursor.End) self.__log.setTextCursor(tc) if error: self.__log.setCurrentCharFormat(self.cErrorFormat) else: self.__log.setCurrentCharFormat(self.cNormalFormat) self.__log.insertPlainText(txt) self.__log.ensureCursorVisible()
class ReTextWindow(QMainWindow): def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.resize(950, 700) screenRect = QDesktopWidget().screenGeometry() if globalSettings.windowGeometry: self.restoreGeometry(globalSettings.windowGeometry) else: self.move((screenRect.width()-self.width())/2, (screenRect.height()-self.height())/2) if not screenRect.contains(self.geometry()): self.showMaximized() if globalSettings.iconTheme: QIcon.setThemeName(globalSettings.iconTheme) if QIcon.themeName() in ('hicolor', ''): if not QFile.exists(icon_path + 'document-new.png'): QIcon.setThemeName(get_icon_theme()) if QFile.exists(icon_path+'retext.png'): self.setWindowIcon(QIcon(icon_path+'retext.png')) elif QFile.exists('/usr/share/pixmaps/retext.png'): self.setWindowIcon(QIcon('/usr/share/pixmaps/retext.png')) else: self.setWindowIcon(QIcon.fromTheme('retext', QIcon.fromTheme('accessories-text-editor'))) self.editBoxes = [] self.previewBoxes = [] self.highlighters = [] self.markups = [] self.fileNames = [] self.actionPreviewChecked = [] self.actionLivePreviewChecked = [] self.tabWidget = QTabWidget(self) self.initTabWidget() self.setCentralWidget(self.tabWidget) self.tabWidget.currentChanged.connect(self.changeIndex) self.tabWidget.tabCloseRequested.connect(self.closeTab) toolBar = QToolBar(self.tr('File toolbar'), self) self.addToolBar(Qt.TopToolBarArea, toolBar) self.editBar = QToolBar(self.tr('Edit toolbar'), self) self.addToolBar(Qt.TopToolBarArea, self.editBar) self.searchBar = QToolBar(self.tr('Search toolbar'), self) self.addToolBar(Qt.BottomToolBarArea, self.searchBar) toolBar.setVisible(not globalSettings.hideToolBar) self.editBar.setVisible(not globalSettings.hideToolBar) self.actionNew = self.act(self.tr('New'), 'document-new', self.createNew, shct=QKeySequence.New) self.actionNew.setPriority(QAction.LowPriority) self.actionOpen = self.act(self.tr('Open'), 'document-open', self.openFile, shct=QKeySequence.Open) self.actionOpen.setPriority(QAction.LowPriority) self.actionSetEncoding = self.act(self.tr('Set encoding'), trig=self.showEncodingDialog) self.actionSetEncoding.setEnabled(False) self.actionReload = self.act(self.tr('Reload'), 'view-refresh', trig=self.openFileMain) self.actionReload.setEnabled(False) self.actionSave = self.act(self.tr('Save'), 'document-save', self.saveFile, shct=QKeySequence.Save) self.actionSave.setEnabled(False) self.actionSave.setPriority(QAction.LowPriority) self.actionSaveAs = self.act(self.tr('Save as'), 'document-save-as', self.saveFileAs, shct=QKeySequence.SaveAs) self.actionNextTab = self.act(self.tr('Next tab'), 'go-next', lambda: self.switchTab(1), shct=Qt.CTRL+Qt.Key_PageDown) self.actionPrevTab = self.act(self.tr('Previous tab'), 'go-previous', lambda: self.switchTab(-1), shct=Qt.CTRL+Qt.Key_PageUp) self.actionPrint = self.act(self.tr('Print'), 'document-print', self.printFile, shct=QKeySequence.Print) self.actionPrint.setPriority(QAction.LowPriority) self.actionPrintPreview = self.act(self.tr('Print preview'), 'document-print-preview', self.printPreview) self.actionViewHtml = self.act(self.tr('View HTML code'), 'text-html', self.viewHtml) self.actionChangeEditorFont = self.act(self.tr('Change editor font'), trig=self.changeEditorFont) self.actionChangePreviewFont = self.act(self.tr('Change preview font'), trig=self.changePreviewFont) self.actionSearch = self.act(self.tr('Find text'), 'edit-find', shct=QKeySequence.Find) self.actionSearch.setCheckable(True) self.actionSearch.triggered[bool].connect(self.searchBar.setVisible) self.searchBar.visibilityChanged.connect(self.searchBarVisibilityChanged) self.actionPreview = self.act(self.tr('Preview'), shct=Qt.CTRL+Qt.Key_E, trigbool=self.preview) if QIcon.hasThemeIcon('document-preview'): self.actionPreview.setIcon(QIcon.fromTheme('document-preview')) elif QIcon.hasThemeIcon('preview-file'): self.actionPreview.setIcon(QIcon.fromTheme('preview-file')) elif QIcon.hasThemeIcon('x-office-document'): self.actionPreview.setIcon(QIcon.fromTheme('x-office-document')) else: self.actionPreview.setIcon(QIcon(icon_path+'document-preview.png')) self.actionLivePreview = self.act(self.tr('Live preview'), shct=Qt.CTRL+Qt.Key_L, trigbool=self.enableLivePreview) self.actionTableMode = self.act(self.tr('Table mode'), shct=Qt.CTRL+Qt.Key_T, trigbool=lambda x: self.editBoxes[self.ind].enableTableMode(x)) if ReTextFakeVimHandler: self.actionFakeVimMode = self.act(self.tr('FakeVim mode'), shct=Qt.CTRL+Qt.ALT+Qt.Key_V, trigbool=self.enableFakeVimMode) if globalSettings.useFakeVim: self.actionFakeVimMode.setChecked(True) self.enableFakeVimMode(True) self.actionFullScreen = self.act(self.tr('Fullscreen mode'), 'view-fullscreen', shct=Qt.Key_F11, trigbool=self.enableFullScreen) self.actionFullScreen.setPriority(QAction.LowPriority) self.actionConfig = self.act(self.tr('Preferences'), icon='preferences-system', trig=self.openConfigDialog) self.actionConfig.setMenuRole(QAction.PreferencesRole) self.actionSaveHtml = self.act('HTML', 'text-html', self.saveFileHtml) self.actionPdf = self.act('PDF', 'application-pdf', self.savePdf) self.actionOdf = self.act('ODT', 'x-office-document', self.saveOdf) self.getExportExtensionsList() self.actionQuit = self.act(self.tr('Quit'), 'application-exit', shct=QKeySequence.Quit) self.actionQuit.setMenuRole(QAction.QuitRole) self.actionQuit.triggered.connect(self.close) self.actionUndo = self.act(self.tr('Undo'), 'edit-undo', lambda: self.editBoxes[self.ind].undo(), shct=QKeySequence.Undo) self.actionRedo = self.act(self.tr('Redo'), 'edit-redo', lambda: self.editBoxes[self.ind].redo(), shct=QKeySequence.Redo) self.actionCopy = self.act(self.tr('Copy'), 'edit-copy', lambda: self.editBoxes[self.ind].copy(), shct=QKeySequence.Copy) self.actionCut = self.act(self.tr('Cut'), 'edit-cut', lambda: self.editBoxes[self.ind].cut(), shct=QKeySequence.Cut) self.actionPaste = self.act(self.tr('Paste'), 'edit-paste', lambda: self.editBoxes[self.ind].paste(), shct=QKeySequence.Paste) self.actionUndo.setEnabled(False) self.actionRedo.setEnabled(False) self.actionCopy.setEnabled(False) self.actionCut.setEnabled(False) qApp = QApplication.instance() qApp.clipboard().dataChanged.connect(self.clipboardDataChanged) self.clipboardDataChanged() if enchant_available: self.actionEnableSC = self.act(self.tr('Enable'), trigbool=self.enableSpellCheck) self.actionSetLocale = self.act(self.tr('Set locale'), trig=self.changeLocale) self.actionWebKit = self.act(self.tr('Use WebKit renderer'), trigbool=self.enableWebKit) self.actionWebKit.setChecked(globalSettings.useWebKit) self.actionShow = self.act(self.tr('Show directory'), 'system-file-manager', self.showInDir) self.actionFind = self.act(self.tr('Next'), 'go-next', self.find, shct=QKeySequence.FindNext) self.actionFindPrev = self.act(self.tr('Previous'), 'go-previous', lambda: self.find(back=True), shct=QKeySequence.FindPrevious) self.actionHelp = self.act(self.tr('Get help online'), 'help-contents', self.openHelp) self.aboutWindowTitle = self.tr('About ReText') self.actionAbout = self.act(self.aboutWindowTitle, 'help-about', self.aboutDialog) self.actionAbout.setMenuRole(QAction.AboutRole) self.actionAboutQt = self.act(self.tr('About Qt')) self.actionAboutQt.setMenuRole(QAction.AboutQtRole) self.actionAboutQt.triggered.connect(qApp.aboutQt) availableMarkups = markups.get_available_markups() if not availableMarkups: print('Warning: no markups are available!') self.defaultMarkup = availableMarkups[0] if availableMarkups else None if globalSettings.defaultMarkup: mc = markups.find_markup_class_by_name(globalSettings.defaultMarkup) if mc and mc.available(): self.defaultMarkup = mc if len(availableMarkups) > 1: self.chooseGroup = QActionGroup(self) markupActions = [] for markup in availableMarkups: markupAction = self.act(markup.name, trigbool=self.markupFunction(markup)) if markup == self.defaultMarkup: markupAction.setChecked(True) self.chooseGroup.addAction(markupAction) markupActions.append(markupAction) self.actionBold = self.act(self.tr('Bold'), shct=QKeySequence.Bold, trig=lambda: self.insertChars('**')) self.actionItalic = self.act(self.tr('Italic'), shct=QKeySequence.Italic, trig=lambda: self.insertChars('*')) self.actionUnderline = self.act(self.tr('Underline'), shct=QKeySequence.Underline, trig=lambda: self.insertTag('u')) self.usefulTags = ('a', 'big', 'center', 'img', 's', 'small', 'span', 'table', 'td', 'tr', 'u') self.usefulChars = ('deg', 'divide', 'dollar', 'hellip', 'laquo', 'larr', 'lsquo', 'mdash', 'middot', 'minus', 'nbsp', 'ndash', 'raquo', 'rarr', 'rsquo', 'times') self.tagsBox = QComboBox(self.editBar) self.tagsBox.addItem(self.tr('Tags')) self.tagsBox.addItems(self.usefulTags) self.tagsBox.activated.connect(self.insertTag) self.symbolBox = QComboBox(self.editBar) self.symbolBox.addItem(self.tr('Symbols')) self.symbolBox.addItems(self.usefulChars) self.symbolBox.activated.connect(self.insertSymbol) self.updateStyleSheet() menubar = QMenuBar(self) menubar.setGeometry(QRect(0, 0, 800, 25)) self.setMenuBar(menubar) menuFile = menubar.addMenu(self.tr('File')) menuEdit = menubar.addMenu(self.tr('Edit')) menuHelp = menubar.addMenu(self.tr('Help')) menuFile.addAction(self.actionNew) menuFile.addAction(self.actionOpen) self.menuRecentFiles = menuFile.addMenu(self.tr('Open recent')) self.menuRecentFiles.aboutToShow.connect(self.updateRecentFiles) menuFile.addMenu(self.menuRecentFiles) menuFile.addAction(self.actionShow) menuFile.addAction(self.actionSetEncoding) menuFile.addAction(self.actionReload) menuFile.addSeparator() menuFile.addAction(self.actionSave) menuFile.addAction(self.actionSaveAs) menuFile.addSeparator() menuFile.addAction(self.actionNextTab) menuFile.addAction(self.actionPrevTab) menuFile.addSeparator() menuExport = menuFile.addMenu(self.tr('Export')) menuExport.addAction(self.actionSaveHtml) menuExport.addAction(self.actionOdf) menuExport.addAction(self.actionPdf) if self.extensionActions: menuExport.addSeparator() for action, mimetype in self.extensionActions: menuExport.addAction(action) menuExport.aboutToShow.connect(self.updateExtensionsVisibility) menuFile.addAction(self.actionPrint) menuFile.addAction(self.actionPrintPreview) menuFile.addSeparator() menuFile.addAction(self.actionQuit) menuEdit.addAction(self.actionUndo) menuEdit.addAction(self.actionRedo) menuEdit.addSeparator() menuEdit.addAction(self.actionCut) menuEdit.addAction(self.actionCopy) menuEdit.addAction(self.actionPaste) menuEdit.addSeparator() if enchant_available: menuSC = menuEdit.addMenu(self.tr('Spell check')) menuSC.addAction(self.actionEnableSC) menuSC.addAction(self.actionSetLocale) menuEdit.addAction(self.actionSearch) menuEdit.addAction(self.actionChangeEditorFont) menuEdit.addAction(self.actionChangePreviewFont) menuEdit.addSeparator() if len(availableMarkups) > 1: self.menuMode = menuEdit.addMenu(self.tr('Default markup')) for markupAction in markupActions: self.menuMode.addAction(markupAction) menuFormat = menuEdit.addMenu(self.tr('Formatting')) menuFormat.addAction(self.actionBold) menuFormat.addAction(self.actionItalic) menuFormat.addAction(self.actionUnderline) menuEdit.addAction(self.actionWebKit) menuEdit.addSeparator() menuEdit.addAction(self.actionViewHtml) menuEdit.addAction(self.actionLivePreview) menuEdit.addAction(self.actionPreview) menuEdit.addAction(self.actionTableMode) if ReTextFakeVimHandler: menuEdit.addAction(self.actionFakeVimMode) menuEdit.addSeparator() menuEdit.addAction(self.actionFullScreen) menuEdit.addAction(self.actionConfig) menuHelp.addAction(self.actionHelp) menuHelp.addSeparator() menuHelp.addAction(self.actionAbout) menuHelp.addAction(self.actionAboutQt) menubar.addMenu(menuFile) menubar.addMenu(menuEdit) menubar.addMenu(menuHelp) toolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) toolBar.addAction(self.actionNew) toolBar.addSeparator() toolBar.addAction(self.actionOpen) toolBar.addAction(self.actionSave) toolBar.addAction(self.actionPrint) toolBar.addSeparator() toolBar.addAction(self.actionPreview) toolBar.addAction(self.actionFullScreen) self.editBar.addAction(self.actionUndo) self.editBar.addAction(self.actionRedo) self.editBar.addSeparator() self.editBar.addAction(self.actionCut) self.editBar.addAction(self.actionCopy) self.editBar.addAction(self.actionPaste) self.editBar.addSeparator() self.editBar.addWidget(self.tagsBox) self.editBar.addWidget(self.symbolBox) self.searchEdit = QLineEdit(self.searchBar) self.searchEdit.setPlaceholderText(self.tr('Search')) self.searchEdit.returnPressed.connect(self.find) self.csBox = QCheckBox(self.tr('Case sensitively'), self.searchBar) self.searchBar.addWidget(self.searchEdit) self.searchBar.addSeparator() self.searchBar.addWidget(self.csBox) self.searchBar.addAction(self.actionFindPrev) self.searchBar.addAction(self.actionFind) self.searchBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.searchBar.setVisible(False) self.autoSaveEnabled = globalSettings.autoSave if self.autoSaveEnabled: timer = QTimer(self) timer.start(60000) timer.timeout.connect(self.saveAll) self.ind = None if enchant_available: self.sl = globalSettings.spellCheckLocale if self.sl: try: enchant.Dict(self.sl) except Exception as e: print(e, file=sys.stderr) self.sl = None if globalSettings.spellCheck: self.actionEnableSC.setChecked(True) self.enableSpellCheck(True) self.fileSystemWatcher = QFileSystemWatcher() self.fileSystemWatcher.fileChanged.connect(self.fileChanged) def updateStyleSheet(self): if globalSettings.styleSheet: sheetfile = QFile(globalSettings.styleSheet) sheetfile.open(QIODevice.ReadOnly) self.ss = QTextStream(sheetfile).readAll() sheetfile.close() else: self.ss = '' def initTabWidget(self): def dragEnterEvent(e): e.acceptProposedAction() def dropEvent(e): fn = bytes(e.mimeData().data('text/plain')).decode().rstrip() if fn.startswith('file:'): fn = QUrl(fn).toLocalFile() self.openFileWrapper(fn) self.tabWidget.setTabsClosable(True) self.tabWidget.setAcceptDrops(True) self.tabWidget.dragEnterEvent = dragEnterEvent self.tabWidget.dropEvent = dropEvent def act(self, name, icon=None, trig=None, trigbool=None, shct=None): if not isinstance(shct, QKeySequence): shct = QKeySequence(shct) if icon: action = QAction(self.actIcon(icon), name, self) else: action = QAction(name, self) if trig: action.triggered.connect(trig) elif trigbool: action.setCheckable(True) action.triggered[bool].connect(trigbool) if shct: action.setShortcut(shct) return action def actIcon(self, name): return QIcon.fromTheme(name, QIcon(icon_path+name+'.png')) def printError(self): import traceback print('Exception occured while parsing document:', file=sys.stderr) traceback.print_exc() def getSplitter(self, index): splitter = QSplitter(Qt.Horizontal) # Give both boxes a minimum size so the minimumSizeHint will be # ignored when splitter.setSizes is called below for widget in self.editBoxes[index], self.previewBoxes[index]: widget.setMinimumWidth(125) splitter.addWidget(widget) splitter.setSizes((50, 50)) splitter.setChildrenCollapsible(False) return splitter def getWebView(self): webView = QWebView() if not globalSettings.handleWebLinks: webView.page().setLinkDelegationPolicy(QWebPage.DelegateExternalLinks) webView.page().linkClicked.connect(QDesktopServices.openUrl) settings = webView.settings() settings.setAttribute(QWebSettings.LocalContentCanAccessFileUrls, False) settings.setDefaultTextEncoding('utf-8') return webView def createTab(self, fileName): self.previewBlocked = False self.editBoxes.append(ReTextEdit(self)) self.highlighters.append(ReTextHighlighter(self.editBoxes[-1].document())) if enchant_available and self.actionEnableSC.isChecked(): self.highlighters[-1].dictionary = \ enchant.Dict(self.sl) if self.sl else enchant.Dict() self.highlighters[-1].rehighlight() if globalSettings.useWebKit: self.previewBoxes.append(self.getWebView()) else: self.previewBoxes.append(QTextBrowser()) self.previewBoxes[-1].setOpenExternalLinks(True) self.previewBoxes[-1].setVisible(False) self.fileNames.append(fileName) markupClass = self.getMarkupClass(fileName) self.markups.append(self.getMarkup(fileName)) self.highlighters[-1].docType = (markupClass.name if markupClass else '') liveMode = globalSettings.restorePreviewState and globalSettings.previewState self.actionPreviewChecked.append(liveMode) self.actionLivePreviewChecked.append(liveMode) metrics = QFontMetrics(self.editBoxes[-1].font()) self.editBoxes[-1].setTabStopWidth(globalSettings.tabWidth * metrics.width(' ')) self.editBoxes[-1].textChanged.connect(self.updateLivePreviewBox) self.editBoxes[-1].undoAvailable.connect(self.actionUndo.setEnabled) self.editBoxes[-1].redoAvailable.connect(self.actionRedo.setEnabled) self.editBoxes[-1].copyAvailable.connect(self.enableCopy) self.editBoxes[-1].document().modificationChanged.connect(self.modificationChanged) if globalSettings.useFakeVim: self.installFakeVimHandler(self.editBoxes[-1]) return self.getSplitter(-1) def closeTab(self, ind): if self.maybeSave(ind): if self.tabWidget.count() == 1: self.tabWidget.addTab(self.createTab(""), self.tr("New document")) if self.fileNames[ind]: self.fileSystemWatcher.removePath(self.fileNames[ind]) del self.editBoxes[ind] del self.previewBoxes[ind] del self.highlighters[ind] del self.markups[ind] del self.fileNames[ind] del self.actionPreviewChecked[ind] del self.actionLivePreviewChecked[ind] self.tabWidget.removeTab(ind) def getMarkupClass(self, fileName=None): if fileName is None: fileName = self.fileNames[self.ind] if fileName: markupClass = markups.get_markup_for_file_name( fileName, return_class=True) if markupClass: return markupClass return self.defaultMarkup def getMarkup(self, fileName=None): if fileName is None: fileName = self.fileNames[self.ind] markupClass = self.getMarkupClass(fileName=fileName) if markupClass and markupClass.available(): return markupClass(filename=fileName) def docTypeChanged(self): oldType = self.highlighters[self.ind].docType markupClass = self.getMarkupClass() newType = markupClass.name if markupClass else '' if oldType != newType: self.markups[self.ind] = self.getMarkup() self.updatePreviewBox() self.highlighters[self.ind].docType = newType self.highlighters[self.ind].rehighlight() dtMarkdown = (newType == DOCTYPE_MARKDOWN) dtMkdOrReST = (newType in (DOCTYPE_MARKDOWN, DOCTYPE_REST)) self.tagsBox.setEnabled(dtMarkdown) self.symbolBox.setEnabled(dtMarkdown) self.actionUnderline.setEnabled(dtMarkdown) self.actionBold.setEnabled(dtMkdOrReST) self.actionItalic.setEnabled(dtMkdOrReST) canReload = bool(self.fileNames[self.ind]) and not self.autoSaveActive() self.actionSetEncoding.setEnabled(canReload) self.actionReload.setEnabled(canReload) def changeIndex(self, ind): if ind > -1: self.actionUndo.setEnabled(self.editBoxes[ind].document().isUndoAvailable()) self.actionRedo.setEnabled(self.editBoxes[ind].document().isRedoAvailable()) self.actionCopy.setEnabled(self.editBoxes[ind].textCursor().hasSelection()) self.actionCut.setEnabled(self.editBoxes[ind].textCursor().hasSelection()) self.actionPreview.setChecked(self.actionPreviewChecked[ind]) self.actionLivePreview.setChecked(self.actionLivePreviewChecked[ind]) self.actionTableMode.setChecked(self.editBoxes[ind].tableModeEnabled) self.editBar.setDisabled(self.actionPreviewChecked[ind]) self.ind = ind if self.fileNames[ind]: self.setCurrentFile() else: self.setWindowTitle(self.tr('New document') + '[*]') self.docTypeChanged() self.modificationChanged(self.editBoxes[ind].document().isModified()) if globalSettings.restorePreviewState: globalSettings.previewState = self.actionLivePreviewChecked[ind] if self.actionLivePreviewChecked[ind]: self.enableLivePreview(True) self.editBoxes[self.ind].setFocus(Qt.OtherFocusReason) def changeEditorFont(self): font, ok = QFontDialog.getFont(globalSettings.editorFont, self) if ok: globalSettings.editorFont = font for editor in self.editBoxes: editor.updateFont() def changePreviewFont(self): font, ok = QFontDialog.getFont(globalSettings.font, self) if ok: globalSettings.font = font self.updatePreviewBox() def preview(self, viewmode): self.actionPreviewChecked[self.ind] = viewmode if self.actionLivePreview.isChecked(): self.actionLivePreview.setChecked(False) return self.enableLivePreview(False) self.editBar.setDisabled(viewmode) self.editBoxes[self.ind].setVisible(not viewmode) self.previewBoxes[self.ind].setVisible(viewmode) if viewmode: self.updatePreviewBox() def enableLivePreview(self, livemode): if globalSettings.restorePreviewState: globalSettings.previewState = livemode self.actionLivePreviewChecked[self.ind] = livemode self.actionPreviewChecked[self.ind] = livemode self.actionPreview.setChecked(livemode) self.editBar.setEnabled(True) self.previewBoxes[self.ind].setVisible(livemode) self.editBoxes[self.ind].setVisible(True) if livemode: self.updatePreviewBox() def enableWebKit(self, enable): globalSettings.useWebKit = enable oldind = self.ind self.tabWidget.clear() for self.ind in range(len(self.editBoxes)): if enable: self.previewBoxes[self.ind] = self.getWebView() else: self.previewBoxes[self.ind] = QTextBrowser() self.previewBoxes[self.ind].setOpenExternalLinks(True) splitter = self.getSplitter(self.ind) self.tabWidget.addTab(splitter, self.getDocumentTitle(baseName=True)) self.updatePreviewBox() self.previewBoxes[self.ind].setVisible(self.actionPreviewChecked[self.ind]) self.ind = oldind self.tabWidget.setCurrentIndex(self.ind) def enableCopy(self, copymode): self.actionCopy.setEnabled(copymode) self.actionCut.setEnabled(copymode) def enableFullScreen(self, yes): if yes: self.showFullScreen() else: self.showNormal() def openConfigDialog(self): dlg = ConfigDialog(self) dlg.setWindowTitle(self.tr('Preferences')) dlg.show() def installFakeVimHandler(self, editor): if ReTextFakeVimHandler: fakeVimEditor = ReTextFakeVimHandler(editor, self) fakeVimEditor.setSaveAction(self.actionSave) fakeVimEditor.setQuitAction(self.actionQuit) self.actionFakeVimMode.triggered.connect(fakeVimEditor.remove) def enableFakeVimMode(self, yes): globalSettings.useFakeVim = yes if yes: FakeVimMode.init(self) for editor in self.editBoxes: self.installFakeVimHandler(editor) else: FakeVimMode.exit(self) def enableSpellCheck(self, yes): if yes: if self.sl: self.setAllDictionaries(enchant.Dict(self.sl)) else: self.setAllDictionaries(enchant.Dict()) else: self.setAllDictionaries(None) globalSettings.spellCheck = yes def setAllDictionaries(self, dictionary): for hl in self.highlighters: hl.dictionary = dictionary hl.rehighlight() def changeLocale(self): if self.sl: localedlg = LocaleDialog(self, defaultText=self.sl) else: localedlg = LocaleDialog(self) if localedlg.exec() != QDialog.Accepted: return sl = localedlg.localeEdit.text() setdefault = localedlg.checkBox.isChecked() if sl: try: sl = str(sl) enchant.Dict(sl) except Exception as e: QMessageBox.warning(self, '', str(e)) else: self.sl = sl self.enableSpellCheck(self.actionEnableSC.isChecked()) else: self.sl = None self.enableSpellCheck(self.actionEnableSC.isChecked()) if setdefault: globalSettings.spellCheckLocale = sl def searchBarVisibilityChanged(self, visible): self.actionSearch.setChecked(visible) if visible: self.searchEdit.setFocus(Qt.ShortcutFocusReason) def find(self, back=False): flags = QTextDocument.FindFlags() if back: flags |= QTextDocument.FindBackward if self.csBox.isChecked(): flags |= QTextDocument.FindCaseSensitively text = self.searchEdit.text() editBox = self.editBoxes[self.ind] cursor = editBox.textCursor() newCursor = editBox.document().find(text, cursor, flags) if not newCursor.isNull(): editBox.setTextCursor(newCursor) return self.setSearchEditColor(True) cursor.movePosition(QTextCursor.End if back else QTextCursor.Start) newCursor = editBox.document().find(text, cursor, flags) if not newCursor.isNull(): editBox.setTextCursor(newCursor) return self.setSearchEditColor(True) self.setSearchEditColor(False) def setSearchEditColor(self, found): palette = self.searchEdit.palette() palette.setColor(QPalette.Active, QPalette.Base, Qt.white if found else QColor(255, 102, 102)) self.searchEdit.setPalette(palette) def getHtml(self, includeStyleSheet=True, includeTitle=True, includeMeta=False, webenv=False): if self.markups[self.ind] is None: markupClass = self.getMarkupClass() errMsg = self.tr('Could not parse file contents, check if ' 'you have the <a href="%s">necessary module</a> installed!') try: errMsg %= markupClass.attributes[MODULE_HOME_PAGE] except (AttributeError, KeyError): # Remove the link if markupClass doesn't have the needed attribute errMsg = errMsg.replace('<a href="%s">', '') errMsg = errMsg.replace('</a>', '') return '<p style="color: red">%s</p>' % errMsg text = self.editBoxes[self.ind].toPlainText() headers = '' if includeStyleSheet: headers += '<style type="text/css">\n' + self.ss + '</style>\n' cssFileName = self.getDocumentTitle(baseName=True)+'.css' if QFile(cssFileName).exists(): headers += '<link rel="stylesheet" type="text/css" href="%s">\n' \ % cssFileName if includeMeta: headers += ('<meta name="generator" content="ReText %s">\n' % app_version) fallbackTitle = self.getDocumentTitle() if includeTitle else '' return self.markups[self.ind].get_whole_html(text, custom_headers=headers, include_stylesheet=includeStyleSheet, fallback_title=fallbackTitle, webenv=webenv) def updatePreviewBox(self): self.previewBlocked = False pb = self.previewBoxes[self.ind] textedit = isinstance(pb, QTextEdit) if textedit: scrollbar = pb.verticalScrollBar() disttobottom = scrollbar.maximum() - scrollbar.value() else: frame = pb.page().mainFrame() scrollpos = frame.scrollPosition() try: html = self.getHtml() except Exception: return self.printError() if textedit: pb.setHtml(html) pb.document().setDefaultFont(globalSettings.font) scrollbar.setValue(scrollbar.maximum() - disttobottom) else: pb.settings().setFontFamily(QWebSettings.StandardFont, globalSettings.font.family()) pb.settings().setFontSize(QWebSettings.DefaultFontSize, globalSettings.font.pointSize()) pb.setHtml(html, QUrl.fromLocalFile(self.fileNames[self.ind])) frame.setScrollPosition(scrollpos) def updateLivePreviewBox(self): if self.actionLivePreview.isChecked() and self.previewBlocked == False: self.previewBlocked = True QTimer.singleShot(1000, self.updatePreviewBox) def showInDir(self): if self.fileNames[self.ind]: path = QFileInfo(self.fileNames[self.ind]).path() QDesktopServices.openUrl(QUrl.fromLocalFile(path)) else: QMessageBox.warning(self, '', self.tr("Please, save the file somewhere.")) def setCurrentFile(self): self.setWindowTitle("") self.tabWidget.setTabText(self.ind, self.getDocumentTitle(baseName=True)) self.setWindowFilePath(self.fileNames[self.ind]) files = readListFromSettings("recentFileList") while self.fileNames[self.ind] in files: files.remove(self.fileNames[self.ind]) files.insert(0, self.fileNames[self.ind]) if len(files) > 10: del files[10:] writeListToSettings("recentFileList", files) QDir.setCurrent(QFileInfo(self.fileNames[self.ind]).dir().path()) self.docTypeChanged() def createNew(self, text=None): self.tabWidget.addTab(self.createTab(""), self.tr("New document")) self.ind = self.tabWidget.count()-1 self.tabWidget.setCurrentIndex(self.ind) if text: self.editBoxes[self.ind].textCursor().insertText(text) def switchTab(self, shift=1): self.tabWidget.setCurrentIndex((self.ind + shift) % self.tabWidget.count()) def updateRecentFiles(self): self.menuRecentFiles.clear() self.recentFilesActions = [] filesOld = readListFromSettings("recentFileList") files = [] for f in filesOld: if QFile.exists(f): files.append(f) self.recentFilesActions.append(self.act(f, trig=self.openFunction(f))) writeListToSettings("recentFileList", files) for action in self.recentFilesActions: self.menuRecentFiles.addAction(action) def markupFunction(self, markup): return lambda: self.setDefaultMarkup(markup) def openFunction(self, fileName): return lambda: self.openFileWrapper(fileName) def extensionFuntion(self, data): return lambda: \ self.runExtensionCommand(data['Exec'], data['FileFilter'], data['DefaultExtension']) def getExportExtensionsList(self): extensions = [] for extsprefix in datadirs: extsdir = QDir(extsprefix+'/export-extensions/') if extsdir.exists(): for fileInfo in extsdir.entryInfoList(['*.desktop', '*.ini'], QDir.Files | QDir.Readable): extensions.append(self.readExtension(fileInfo.filePath())) locale = QLocale.system().name() self.extensionActions = [] for extension in extensions: try: if ('Name[%s]' % locale) in extension: name = extension['Name[%s]' % locale] elif ('Name[%s]' % locale.split('_')[0]) in extension: name = extension['Name[%s]' % locale.split('_')[0]] else: name = extension['Name'] data = {} for prop in ('FileFilter', 'DefaultExtension', 'Exec'): if 'X-ReText-'+prop in extension: data[prop] = extension['X-ReText-'+prop] elif prop in extension: data[prop] = extension[prop] else: data[prop] = '' action = self.act(name, trig=self.extensionFuntion(data)) if 'Icon' in extension: action.setIcon(self.actIcon(extension['Icon'])) mimetype = extension['MimeType'] if 'MimeType' in extension else None except KeyError: print('Failed to parse extension: Name is required', file=sys.stderr) else: self.extensionActions.append((action, mimetype)) def updateExtensionsVisibility(self): markupClass = self.getMarkupClass() for action in self.extensionActions: if markupClass is None: action[0].setEnabled(False) continue mimetype = action[1] if mimetype == None: enabled = True elif markupClass == markups.MarkdownMarkup: enabled = (mimetype in ("text/x-retext-markdown", "text/x-markdown")) elif markupClass == markups.ReStructuredTextMarkup: enabled = (mimetype in ("text/x-retext-rst", "text/x-rst")) else: enabled = False action[0].setEnabled(enabled) def readExtension(self, fileName): extFile = QFile(fileName) extFile.open(QIODevice.ReadOnly) extension = {} stream = QTextStream(extFile) while not stream.atEnd(): line = stream.readLine() if '=' in line: index = line.index('=') extension[line[:index].rstrip()] = line[index+1:].lstrip() extFile.close() return extension def openFile(self): supportedExtensions = ['.txt'] for markup in markups.get_all_markups(): supportedExtensions += markup.file_extensions fileFilter = ' (' + str.join(' ', ['*'+ext for ext in supportedExtensions]) + ');;' fileNames = QFileDialog.getOpenFileNames(self, self.tr("Select one or several files to open"), "", self.tr("Supported files") + fileFilter + self.tr("All files (*)")) for fileName in fileNames[0]: self.openFileWrapper(fileName) def openFileWrapper(self, fileName): if not fileName: return fileName = QFileInfo(fileName).canonicalFilePath() exists = False for i in range(self.tabWidget.count()): if self.fileNames[i] == fileName: exists = True ex = i if exists: self.tabWidget.setCurrentIndex(ex) elif QFile.exists(fileName): noEmptyTab = ( (self.ind is None) or self.fileNames[self.ind] or self.editBoxes[self.ind].toPlainText() or self.editBoxes[self.ind].document().isModified() ) if noEmptyTab: self.tabWidget.addTab(self.createTab(fileName), "") self.ind = self.tabWidget.count()-1 self.tabWidget.setCurrentIndex(self.ind) if fileName: self.fileSystemWatcher.addPath(fileName) self.fileNames[self.ind] = fileName self.openFileMain() def openFileMain(self, encoding=None): openfile = QFile(self.fileNames[self.ind]) openfile.open(QIODevice.ReadOnly) stream = QTextStream(openfile) if encoding: stream.setCodec(encoding) elif globalSettings.defaultCodec: stream.setCodec(globalSettings.defaultCodec) text = stream.readAll() openfile.close() markupClass = markups.get_markup_for_file_name( self.fileNames[self.ind], return_class=True) self.highlighters[self.ind].docType = (markupClass.name if markupClass else '') self.markups[self.ind] = self.getMarkup() if self.defaultMarkup: self.highlighters[self.ind].docType = self.defaultMarkup.name editBox = self.editBoxes[self.ind] modified = bool(encoding) and (editBox.toPlainText() != text) editBox.setPlainText(text) self.setCurrentFile() editBox.document().setModified(modified) self.setWindowModified(modified) def showEncodingDialog(self): if not self.maybeSave(self.ind): return encoding, ok = QInputDialog.getItem(self, '', self.tr('Select file encoding from the list:'), [bytes(b).decode() for b in QTextCodec.availableCodecs()], 0, False) if ok: self.openFileMain(encoding) def saveFile(self): self.saveFileMain(dlg=False) def saveFileAs(self): self.saveFileMain(dlg=True) def saveAll(self): oldind = self.ind for self.ind in range(self.tabWidget.count()): if self.fileNames[self.ind] and QFileInfo(self.fileNames[self.ind]).isWritable(): self.saveFileCore(self.fileNames[self.ind]) self.editBoxes[self.ind].document().setModified(False) self.ind = oldind def saveFileMain(self, dlg): if (not self.fileNames[self.ind]) or dlg: markupClass = self.getMarkupClass() if (markupClass is None) or not hasattr(markupClass, 'default_extension'): defaultExt = self.tr("Plain text (*.txt)") ext = ".txt" else: defaultExt = self.tr('%s files', 'Example of final string: Markdown files') \ % markupClass.name + ' (' + str.join(' ', ('*'+extension for extension in markupClass.file_extensions)) + ')' if markupClass == markups.MarkdownMarkup: ext = globalSettings.markdownDefaultFileExtension elif markupClass == markups.ReStructuredTextMarkup: ext = globalSettings.restDefaultFileExtension else: ext = markupClass.default_extension newFileName = QFileDialog.getSaveFileName(self, self.tr("Save file"), "", defaultExt)[0] if newFileName: if not QFileInfo(newFileName).suffix(): newFileName += ext if self.fileNames[self.ind]: self.fileSystemWatcher.removePath(self.fileNames[self.ind]) self.fileNames[self.ind] = newFileName self.actionSetEncoding.setDisabled(self.autoSaveActive()) if self.fileNames[self.ind]: result = self.saveFileCore(self.fileNames[self.ind]) if result: self.setCurrentFile() self.editBoxes[self.ind].document().setModified(False) self.setWindowModified(False) return True else: QMessageBox.warning(self, '', self.tr("Cannot save to file because it is read-only!")) return False def saveFileCore(self, fn, addToWatcher=True): self.fileSystemWatcher.removePath(fn) savefile = QFile(fn) result = savefile.open(QIODevice.WriteOnly) if result: savestream = QTextStream(savefile) if globalSettings.defaultCodec: savestream.setCodec(globalSettings.defaultCodec) savestream << self.editBoxes[self.ind].toPlainText() savefile.close() if result and addToWatcher: self.fileSystemWatcher.addPath(fn) return result def saveHtml(self, fileName): if not QFileInfo(fileName).suffix(): fileName += ".html" try: htmltext = self.getHtml(includeStyleSheet=False, includeMeta=True, webenv=True) except Exception: return self.printError() htmlFile = QFile(fileName) htmlFile.open(QIODevice.WriteOnly) html = QTextStream(htmlFile) if globalSettings.defaultCodec: html.setCodec(globalSettings.defaultCodec) html << htmltext htmlFile.close() def textDocument(self): td = QTextDocument() td.setMetaInformation(QTextDocument.DocumentTitle, self.getDocumentTitle()) if self.ss: td.setDefaultStyleSheet(self.ss) td.setHtml(self.getHtml()) td.setDefaultFont(globalSettings.font) return td def saveOdf(self): try: document = self.textDocument() except Exception: return self.printError() fileName = QFileDialog.getSaveFileName(self, self.tr("Export document to ODT"), "", self.tr("OpenDocument text files (*.odt)"))[0] if not QFileInfo(fileName).suffix(): fileName += ".odt" writer = QTextDocumentWriter(fileName) writer.setFormat("odf") writer.write(document) def saveFileHtml(self): fileName = QFileDialog.getSaveFileName(self, self.tr("Save file"), "", self.tr("HTML files (*.html *.htm)"))[0] if fileName: self.saveHtml(fileName) def getDocumentForPrint(self): if globalSettings.useWebKit: return self.previewBoxes[self.ind] try: return self.textDocument() except Exception: self.printError() def standardPrinter(self): printer = QPrinter(QPrinter.HighResolution) printer.setDocName(self.getDocumentTitle()) printer.setCreator('ReText %s' % app_version) return printer def savePdf(self): self.updatePreviewBox() fileName = QFileDialog.getSaveFileName(self, self.tr("Export document to PDF"), "", self.tr("PDF files (*.pdf)"))[0] if fileName: if not QFileInfo(fileName).suffix(): fileName += ".pdf" printer = self.standardPrinter() printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFileName(fileName) document = self.getDocumentForPrint() if document != None: document.print(printer) def printFile(self): self.updatePreviewBox() printer = self.standardPrinter() dlg = QPrintDialog(printer, self) dlg.setWindowTitle(self.tr("Print document")) if (dlg.exec() == QDialog.Accepted): document = self.getDocumentForPrint() if document != None: document.print(printer) def printPreview(self): document = self.getDocumentForPrint() if document == None: return printer = self.standardPrinter() preview = QPrintPreviewDialog(printer, self) preview.paintRequested.connect(document.print) preview.exec() def runExtensionCommand(self, command, filefilter, defaultext): of = ('%of' in command) html = ('%html' in command) if of: if defaultext and not filefilter: filefilter = '*'+defaultext fileName = QFileDialog.getSaveFileName(self, self.tr('Export document'), '', filefilter)[0] if not fileName: return if defaultext and not QFileInfo(fileName).suffix(): fileName += defaultext basename = '.%s.retext-temp' % self.getDocumentTitle(baseName=True) if html: tmpname = basename+'.html' self.saveHtml(tmpname) else: tmpname = basename+self.getMarkupClass().default_extension self.saveFileCore(tmpname, addToWatcher=False) command = command.replace('%of', '"out'+defaultext+'"') command = command.replace('%html' if html else '%if', '"'+tmpname+'"') try: Popen(str(command), shell=True).wait() except Exception as error: errorstr = str(error) QMessageBox.warning(self, '', self.tr('Failed to execute the command:') + '\n' + errorstr) QFile(tmpname).remove() if of: QFile('out'+defaultext).rename(fileName) def getDocumentTitle(self, baseName=False): markup = self.markups[self.ind] realTitle = '' if markup and not baseName: text = self.editBoxes[self.ind].toPlainText() try: realTitle = markup.get_document_title(text) except Exception: self.printError() if realTitle: return realTitle elif self.fileNames[self.ind]: fileinfo = QFileInfo(self.fileNames[self.ind]) basename = fileinfo.completeBaseName() return (basename if basename else fileinfo.fileName()) return self.tr("New document") def autoSaveActive(self): return self.autoSaveEnabled and self.fileNames[self.ind] and \ QFileInfo(self.fileNames[self.ind]).isWritable() def modificationChanged(self, changed): if self.autoSaveActive(): changed = False self.actionSave.setEnabled(changed) self.setWindowModified(changed) def clipboardDataChanged(self): mimeData = QApplication.instance().clipboard().mimeData() if mimeData is not None: self.actionPaste.setEnabled(mimeData.hasText()) def insertChars(self, chars): tc = self.editBoxes[self.ind].textCursor() if tc.hasSelection(): selection = tc.selectedText() if selection.startswith(chars) and selection.endswith(chars): if len(selection) > 2*len(chars): selection = selection[len(chars):-len(chars)] tc.insertText(selection) else: tc.insertText(chars+tc.selectedText()+chars) else: tc.insertText(chars) def insertTag(self, ut): if not ut: return if isinstance(ut, int): ut = self.usefulTags[ut - 1] arg = ' style=""' if ut == 'span' else '' tc = self.editBoxes[self.ind].textCursor() if ut == 'img': toinsert = ('<a href="' + tc.selectedText() + '" target="_blank"><img src="' + tc.selectedText() + '"/></a>') elif ut == 'a': toinsert = ('<a href="' + tc.selectedText() + '" target="_blank">' + tc.selectedText() + '</a>') else: toinsert = '<'+ut+arg+'>'+tc.selectedText()+'</'+ut+'>' tc.insertText(toinsert) self.tagsBox.setCurrentIndex(0) def insertSymbol(self, num): if num: self.editBoxes[self.ind].insertPlainText('&'+self.usefulChars[num-1]+';') self.symbolBox.setCurrentIndex(0) def fileChanged(self, fileName): ind = self.fileNames.index(fileName) self.tabWidget.setCurrentIndex(ind) if not QFile.exists(fileName): self.editBoxes[ind].document().setModified(True) QMessageBox.warning(self, '', self.tr( 'This file has been deleted by other application.\n' 'Please make sure you save the file before exit.')) elif not self.editBoxes[ind].document().isModified(): # File was not modified in ReText, reload silently self.openFileMain() self.updatePreviewBox() else: text = self.tr( 'This document has been modified by other application.\n' 'Do you want to reload the file (this will discard all ' 'your changes)?\n') if self.autoSaveEnabled: text += self.tr( 'If you choose to not reload the file, auto save mode will ' 'be disabled for this session to prevent data loss.') messageBox = QMessageBox(QMessageBox.Warning, '', text) reloadButton = messageBox.addButton(self.tr('Reload'), QMessageBox.YesRole) messageBox.addButton(QMessageBox.Cancel) messageBox.exec() if messageBox.clickedButton() is reloadButton: self.openFileMain() self.updatePreviewBox() else: self.autoSaveEnabled = False self.editBoxes[ind].document().setModified(True) if fileName not in self.fileSystemWatcher.files(): # https://github.com/retext-project/retext/issues/137 self.fileSystemWatcher.addPath(fileName) def maybeSave(self, ind): if self.autoSaveActive(): self.saveFileCore(self.fileNames[self.ind]) return True if not self.editBoxes[ind].document().isModified(): return True self.tabWidget.setCurrentIndex(ind) ret = QMessageBox.warning(self, '', self.tr("The document has been modified.\nDo you want to save your changes?"), QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) if ret == QMessageBox.Save: return self.saveFileMain(False) elif ret == QMessageBox.Cancel: return False return True def closeEvent(self, closeevent): for self.ind in range(self.tabWidget.count()): if not self.maybeSave(self.ind): return closeevent.ignore() if globalSettings.saveWindowGeometry and not self.isMaximized(): globalSettings.windowGeometry = self.saveGeometry() closeevent.accept() def viewHtml(self): htmlDlg = HtmlDialog(self) try: htmltext = self.getHtml(includeStyleSheet=False, includeTitle=False) except Exception: return self.printError() winTitle = self.getDocumentTitle(baseName=True) htmlDlg.setWindowTitle(winTitle+" ("+self.tr("HTML code")+")") htmlDlg.textEdit.setPlainText(htmltext.rstrip()) htmlDlg.hl.rehighlight() htmlDlg.show() htmlDlg.raise_() htmlDlg.activateWindow() def openHelp(self): QDesktopServices.openUrl(QUrl('https://github.com/retext-project/retext/wiki')) def aboutDialog(self): QMessageBox.about(self, self.aboutWindowTitle, '<p><b>' + (self.tr('ReText %s (using PyMarkups %s)') % (app_version, markups.__version__)) +'</b></p>' + self.tr('Simple but powerful editor' ' for Markdown and reStructuredText') +'</p><p>'+self.tr('Author: Dmitry Shachnev, 2011').replace('2011', '2011\u2013' '2015') +'<br><a href="https://github.com/retext-project/retext">'+self.tr('Website') +'</a> | <a href="http://daringfireball.net/projects/markdown/syntax">' +self.tr('Markdown syntax') +'</a> | <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">' +self.tr('reStructuredText syntax')+'</a></p>') def setDefaultMarkup(self, markup): self.defaultMarkup = markup defaultName = markups.get_available_markups()[0].name writeToSettings('defaultMarkup', markup.name, defaultName) oldind = self.ind for self.ind in range(len(self.previewBoxes)): self.docTypeChanged() self.ind = oldind
class AppWindow(QMainWindow): "The application's main window" def __init__(self): super().__init__() self.initUI() self.start_up() QTimer.singleShot(3000, self._check_update) def init_watchers(self): def remove_gallery(g): index = self.manga_list_view.find_index(g.id) if index: self.manga_list_view.remove_gallery([index]) def create_gallery(path): g_dia = gallerydialog.GalleryDialog(self, path) g_dia.SERIES.connect(self.manga_list_view.gallery_model.addRows) g_dia.show() def update_gallery(g): index = self.manga_list_view.find_index(g.id) if index: self.manga_list_view.replace_edit_gallery([g], index.row()) else: log_e("Could not find gallery to update from Watcher") def created(path): c_popup = file_misc.CreatedPopup(path, self) c_popup.ADD_SIGNAL.connect(create_gallery) def modified(path, gallery): mod_popup = file_misc.ModifiedPopup(path, gallery, self) def deleted(path, gallery): d_popup = file_misc.DeletedPopup(path, gallery, self) d_popup.UPDATE_SIGNAL.connect(update_gallery) d_popup.REMOVE_SIGNAL.connect(remove_gallery) def moved(new_path, gallery): mov_popup = file_misc.MovedPopup(new_path, gallery, self) mov_popup.UPDATE_SIGNAL.connect(update_gallery) self.watchers = file_misc.Watchers() self.watchers.gallery_handler.CREATE_SIGNAL.connect(created) self.watchers.gallery_handler.MODIFIED_SIGNAL.connect(modified) self.watchers.gallery_handler.MOVED_SIGNAL.connect(moved) self.watchers.gallery_handler.DELETED_SIGNAL.connect(deleted) if gui_constants.LOOK_NEW_GALLERY_STARTUP: self.notification_bar.add_text("Looking for new galleries...") try: class ScanDir(QObject): final_paths_and_galleries = pyqtSignal(list, list) def __init__(self, model_data, parent=None): super().__init__(parent) self.model_data = model_data def scan_dirs(self): db_data = self.model_data paths = [] for g in range(len(db_data)): paths.append(os.path.normcase(db_data[g].path)) contents = [] case_path = [] # needed for tile and artist parsing... e.g to avoid lowercase for m_path in gui_constants.MONITOR_PATHS: for p in os.listdir(m_path): abs_p = os.path.join(m_path, p) if os.path.isdir(abs_p) or p.endswith(utils.ARCHIVE_FILES): case_path.append(abs_p) contents.append(os.path.normcase(abs_p)) paths = sorted(paths) new_galleries = [] for c, x in enumerate(contents): y = utils.b_search(paths, x) if not y: # (path, number for case_path) new_galleries.append((x, c)) galleries = [] final_paths = [] if new_galleries: for g in new_galleries: gallery = gallerydb.Gallery() try: gallery.profile = utils.get_gallery_img(g[0]) except: gallery.profile = gui_constants.NO_IMAGE_PATH parser_dict = utils.title_parser(os.path.split(case_path[g[1]])[1]) gallery.title = parser_dict["title"] gallery.artist = parser_dict["artist"] galleries.append(gallery) final_paths.append(case_path[g[1]]) self.final_paths_and_galleries.emit(final_paths, galleries) # if gui_constants.LOOK_NEW_GALLERY_AUTOADD: # QTimer.singleShot(10000, self.gallery_populate(final_paths)) # return def show_new_galleries(final_paths, galleries): if final_paths and galleries: if gui_constants.LOOK_NEW_GALLERY_AUTOADD: self.gallery_populate(final_paths) else: if len(galleries) == 1: self.notification_bar.add_text( "{} new gallery was discovered in one of your monitored directories".format( len(galleries) ) ) else: self.notification_bar.add_text( "{} new galleries were discovered in one of your monitored directories".format( len(galleries) ) ) text = ( "These new galleries were discovered! Do you want to add them?" if len(galleries) > 1 else "This new gallery was discovered! Do you want to add it?" ) g_popup = file_misc.GalleryPopup((text, galleries), self) buttons = g_popup.add_buttons("Add", "Close") def populate_n_close(): self.gallery_populate(final_paths) g_popup.close() buttons[0].clicked.connect(populate_n_close) buttons[1].clicked.connect(g_popup.close) thread = QThread(self) self.scan_inst = ScanDir(self.manga_list_view.gallery_model._data) self.scan_inst.moveToThread(thread) self.scan_inst.final_paths_and_galleries.connect(show_new_galleries) self.scan_inst.final_paths_and_galleries.connect(lambda a: self.scan_inst.deleteLater()) thread.started.connect(self.scan_inst.scan_dirs) thread.finished.connect(thread.deleteLater) thread.start() except: self.notification_bar.add_text( "An error occured while attempting to scan for new galleries. Check happypanda.log." ) log.exception("An error occured while attempting to scan for new galleries.") def start_up(self): def normalize_first_time(): settings.set(2, "Application", "first time level") def done(): self.manga_list_view.gallery_model.init_data() if gui_constants.ENABLE_MONITOR and gui_constants.MONITOR_PATHS and all(gui_constants.MONITOR_PATHS): self.init_watchers() if gui_constants.FIRST_TIME_LEVEL != 2: normalize_first_time() if gui_constants.FIRST_TIME_LEVEL < 2: class FirstTime(file_misc.BasePopup): def __init__(self, parent=None): super().__init__(parent) main_layout = QVBoxLayout() info_lbl = QLabel( "Hi there! Some big changes are about to occur!\n" + "Please wait.. This will take at most a few minutes.\n" + "If not then try restarting the application." ) info_lbl.setAlignment(Qt.AlignCenter) main_layout.addWidget(info_lbl) prog = QProgressBar(self) prog.setMinimum(0) prog.setMaximum(0) prog.setTextVisible(False) main_layout.addWidget(prog) main_layout.addWidget(QLabel("Note: This popup will close itself when everything is ready")) self.main_widget.setLayout(main_layout) ft_widget = FirstTime(self) log_i("Invoking first time level 2") bridge = gallerydb.Bridge() thread = QThread(self) thread.setObjectName("Startup") bridge.moveToThread(thread) thread.started.connect(bridge.rebuild_galleries) bridge.DONE.connect(ft_widget.close) bridge.DONE.connect(self.setEnabled) bridge.DONE.connect(done) bridge.DONE.connect(bridge.deleteLater) thread.finished.connect(thread.deleteLater) thread.start() ft_widget.adjustSize() ft_widget.show() self.setEnabled(False) else: done() def initUI(self): self.center = QWidget() self.display = QStackedLayout() self.center.setLayout(self.display) # init the manga view variables self.manga_display() log_d("Create manga display: OK") # init the chapter view variables # self.chapter_display() self.m_l_view_index = self.display.addWidget(self.manga_list_main) self.m_t_view_index = self.display.addWidget(self.manga_table_view) # init toolbar self.init_toolbar() log_d("Create toolbar: OK") # init status bar self.init_stat_bar() log_d("Create statusbar: OK") self.system_tray = misc.SystemTray(QIcon(gui_constants.APP_ICO_PATH), self) gui_constants.SYSTEM_TRAY = self.system_tray tray_menu = QMenu(self) self.system_tray.setContextMenu(tray_menu) self.system_tray.setToolTip("Happypanda {}".format(gui_constants.vs)) tray_quit = QAction("Quit", tray_menu) tray_menu.addAction(tray_quit) tray_quit.triggered.connect(self.close) self.system_tray.show() log_d("Create system tray: OK") # self.display.addWidget(self.chapter_main) self.setCentralWidget(self.center) self.setWindowTitle("Happypanda") self.setWindowIcon(QIcon(gui_constants.APP_ICO_PATH)) props = settings.win_read(self, "AppWindow") if props.resize: x, y = props.resize self.resize(x, y) else: self.resize(gui_constants.MAIN_W, gui_constants.MAIN_H) posx, posy = props.pos self.move(posx, posy) self.show() log_d("Show window: OK") self.notification_bar = misc.NotificationOverlay(self) p = self.toolbar.pos() self.notification_bar.move(p.x(), p.y() + self.toolbar.height()) self.notification_bar.resize(self.width()) gui_constants.NOTIF_BAR = self.notification_bar log_d("Create notificationbar: OK") log_d("Window Create: OK") def _check_update(self): class upd_chk(QObject): UPDATE_CHECK = pyqtSignal(str) def __init__(self, **kwargs): super().__init__(**kwargs) def fetch_vs(self): import requests import time try: log_d("Checking Update") time.sleep(1.5) r = requests.get( "https://raw.githubusercontent.com/Pewpews/happypanda/master/VS.txt", verify="cacert.pem" ) a = r.text vs = a.strip() self.UPDATE_CHECK.emit(vs) except: log.exception("Checking Update: FAIL") self.UPDATE_CHECK.emit("this is a very long text which is is sure to be over limit") def check_update(vs): log_i("Received version: {}\nCurrent version: {}".format(vs, gui_constants.vs)) if vs != gui_constants.vs: if len(vs) < 10: self.notification_bar.add_text( "Version {} of Happypanda is".format(vs) + " available. Click here to update!", False ) self.notification_bar.clicked.connect( lambda: utils.open_web_link("https://github.com/Pewpews/happypanda/releases") ) self.notification_bar.set_clickable(True) else: self.notification_bar.add_text("An error occurred while checking for new version") self.update_instance = upd_chk() thread = QThread(self) self.update_instance.moveToThread(thread) thread.started.connect(self.update_instance.fetch_vs) self.update_instance.UPDATE_CHECK.connect(check_update) self.update_instance.UPDATE_CHECK.connect(self.update_instance.deleteLater) thread.finished.connect(thread.deleteLater) thread.start() def _web_metadata_picker(self, gallery, title_url_list, queue, parent=None): if not parent: parent = self text = "Which gallery do you want to extract metadata from?" s_gallery_popup = misc.SingleGalleryChoices(gallery, title_url_list, text, parent) s_gallery_popup.USER_CHOICE.connect(queue.put) def get_metadata(self, gal=None): thread = QThread(self) thread.setObjectName("App.get_metadata") fetch_instance = fetch.Fetch() if gal: galleries = [gal] else: if gui_constants.CONTINUE_AUTO_METADATA_FETCHER: galleries = [g for g in self.manga_list_view.gallery_model._data if not g.exed] else: galleries = self.manga_list_view.gallery_model._data if not galleries: self.notification_bar.add_text("Looks like we've already gone through all galleries!") return None fetch_instance.galleries = galleries self.notification_bar.begin_show() fetch_instance.moveToThread(thread) def done(status): self.notification_bar.end_show() fetch_instance.deleteLater() fetch_instance.GALLERY_PICKER.connect(self._web_metadata_picker) fetch_instance.GALLERY_EMITTER.connect(self.manga_list_view.replace_edit_gallery) fetch_instance.AUTO_METADATA_PROGRESS.connect(self.notification_bar.add_text) thread.started.connect(fetch_instance.auto_web_metadata) fetch_instance.FINISHED.connect(done) thread.finished.connect(thread.deleteLater) thread.start() # def style_tooltip(self): # palette = QToolTip.palette() # palette.setColor() def init_stat_bar(self): self.status_bar = self.statusBar() self.status_bar.setMaximumHeight(20) self.status_bar.setSizeGripEnabled(False) self.stat_info = QLabel() self.stat_info.setIndent(5) self.sort_main = QAction("Asc", self) sort_menu = QMenu() self.sort_main.setMenu(sort_menu) s_by_title = QAction("Title", sort_menu) s_by_artist = QAction("Artist", sort_menu) sort_menu.addAction(s_by_title) sort_menu.addAction(s_by_artist) self.status_bar.addPermanentWidget(self.stat_info) # self.status_bar.addAction(self.sort_main) self.temp_msg = QLabel() self.temp_timer = QTimer() self.manga_list_view.gallery_model.ROWCOUNT_CHANGE.connect(self.stat_row_info) self.manga_list_view.gallery_model.STATUSBAR_MSG.connect(self.stat_temp_msg) self.manga_list_view.STATUS_BAR_MSG.connect(self.stat_temp_msg) self.manga_table_view.STATUS_BAR_MSG.connect(self.stat_temp_msg) self.stat_row_info() def stat_temp_msg(self, msg): self.temp_timer.stop() self.temp_msg.setText(msg) self.status_bar.addWidget(self.temp_msg) self.temp_timer.timeout.connect(self.temp_msg.clear) self.temp_timer.setSingleShot(True) self.temp_timer.start(5000) def stat_row_info(self): r = self.manga_list_view.model().rowCount() t = self.manga_list_view.gallery_model._data_count self.stat_info.setText("Loaded {} of {} ".format(r, t)) def manga_display(self): "initiates the manga view" # list view self.manga_list_main = QWidget() # self.manga_list_main.setContentsMargins(-10, -12, -10, -10) self.manga_list_main.setContentsMargins(10, -9, -10, -10) # x, y, inverted_width, inverted_height self.manga_list_layout = QHBoxLayout() self.manga_list_main.setLayout(self.manga_list_layout) self.manga_list_view = gallery.MangaView(self) self.manga_list_view.clicked.connect(self.popup) self.manga_list_view.manga_delegate.POPUP.connect(self.popup) self.popup_window = self.manga_list_view.manga_delegate.popup_window self.manga_list_layout.addWidget(self.manga_list_view) # table view self.manga_table_main = QWidget() self.manga_table_layout = QVBoxLayout() self.manga_table_main.setLayout(self.manga_table_layout) self.manga_table_view = gallery.MangaTableView(self) self.manga_table_view.gallery_model = self.manga_list_view.gallery_model self.manga_table_view.sort_model = self.manga_list_view.sort_model self.manga_table_view.setModel(self.manga_table_view.sort_model) self.manga_table_view.sort_model.change_model(self.manga_table_view.gallery_model) self.manga_table_view.setColumnWidth(gui_constants.FAV, 20) self.manga_table_view.setColumnWidth(gui_constants.ARTIST, 200) self.manga_table_view.setColumnWidth(gui_constants.TITLE, 400) self.manga_table_view.setColumnWidth(gui_constants.TAGS, 300) self.manga_table_view.setColumnWidth(gui_constants.TYPE, 60) self.manga_table_view.setColumnWidth(gui_constants.CHAPTERS, 60) self.manga_table_view.setColumnWidth(gui_constants.LANGUAGE, 100) self.manga_table_view.setColumnWidth(gui_constants.LINK, 400) self.manga_table_layout.addWidget(self.manga_table_view) def search(self, srch_string): case_ins = srch_string.lower() if not gui_constants.ALLOW_SEARCH_REGEX: remove = "^$*+?{}\\|()[]" for x in remove: if x == "[" or x == "]": continue else: case_ins = case_ins.replace(x, ".") else: try: re.compile(case_ins) except re.error: return self.manga_list_view.sort_model.search(case_ins) def popup(self, index): if not self.popup_window.isVisible(): m_x = QCursor.pos().x() m_y = QCursor.pos().y() d_w = QDesktopWidget().width() d_h = QDesktopWidget().height() p_w = gui_constants.POPUP_WIDTH p_h = gui_constants.POPUP_HEIGHT index_rect = self.manga_list_view.visualRect(index) index_point = self.manga_list_view.mapToGlobal(index_rect.topRight()) # adjust so it doesn't go offscreen if d_w - m_x < p_w and d_h - m_y < p_h: # bottom self.popup_window.move(m_x - p_w + 5, m_y - p_h) elif d_w - m_x > p_w and d_h - m_y < p_h: self.popup_window.move(m_x + 5, m_y - p_h) elif d_w - m_x < p_w: self.popup_window.move(m_x - p_w + 5, m_y + 5) else: self.popup_window.move(index_point) self.popup_window.set_gallery(index.data(Qt.UserRole + 1)) self.popup_window.show() def favourite_display(self): "Switches to favourite display" if self.display.currentIndex() == self.m_l_view_index: self.manga_list_view.sort_model.fav_view() else: self.manga_table_view.sort_model.fav_view() def catalog_display(self): "Switches to catalog display" if self.display.currentIndex() == self.m_l_view_index: self.manga_list_view.sort_model.catalog_view() else: self.manga_table_view.sort_model.catalog_view() def settings(self): sett = settingsdialog.SettingsDialog(self) sett.scroll_speed_changed.connect(self.manga_list_view.updateGeometries) # sett.show() def init_toolbar(self): self.toolbar = QToolBar() self.toolbar.setFixedHeight(25) self.toolbar.setWindowTitle("Show") # text for the contextmenu # self.toolbar.setStyleSheet("QToolBar {border:0px}") # make it user defined? self.toolbar.setMovable(False) self.toolbar.setFloatable(False) # self.toolbar.setIconSize(QSize(20,20)) self.toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) spacer_start = QWidget() # aligns the first actions properly spacer_start.setFixedSize(QSize(10, 1)) self.toolbar.addWidget(spacer_start) favourite_view_icon = QIcon(gui_constants.STAR_BTN_PATH) favourite_view_action = QAction(favourite_view_icon, "Favorites", self) favourite_view_action.setToolTip("Show only favourite galleries") favourite_view_action.triggered.connect(self.favourite_display) # need lambda to pass extra args self.toolbar.addAction(favourite_view_action) catalog_view_icon = QIcon(gui_constants.HOME_BTN_PATH) catalog_view_action = QAction(catalog_view_icon, "Library", self) catalog_view_action.setToolTip("Show all your galleries") # catalog_view_action.setText("Catalog") catalog_view_action.triggered.connect(self.catalog_display) # need lambda to pass extra args self.toolbar.addAction(catalog_view_action) self.toolbar.addSeparator() gallery_menu = QMenu() gallery_action = QToolButton() gallery_action.setText("Gallery ") gallery_action.setPopupMode(QToolButton.InstantPopup) gallery_action.setToolTip("Contains various gallery related features") gallery_action.setMenu(gallery_menu) add_gallery_icon = QIcon(gui_constants.PLUS_PATH) gallery_action_add = QAction(add_gallery_icon, "Add gallery", self) gallery_action_add.triggered.connect(self.manga_list_view.SERIES_DIALOG.emit) gallery_action_add.setToolTip("Add a single gallery thoroughly") gallery_menu.addAction(gallery_action_add) add_more_action = QAction(add_gallery_icon, "Add galleries...", self) add_more_action.setStatusTip("Add galleries from different folders") add_more_action.triggered.connect(lambda: self.populate(True)) gallery_menu.addAction(add_more_action) populate_action = QAction(add_gallery_icon, "Populate from folder...", self) populate_action.setStatusTip("Populates the DB with galleries from a single folder") populate_action.triggered.connect(self.populate) gallery_menu.addAction(populate_action) gallery_menu.addSeparator() metadata_action = QAction("Get metadata for all galleries", self) metadata_action.triggered.connect(self.get_metadata) gallery_menu.addAction(metadata_action) self.toolbar.addWidget(gallery_action) self.toolbar.addSeparator() misc_action = QToolButton() misc_action.setText("Misc ") misc_action_menu = QMenu() misc_action.setMenu(misc_action_menu) misc_action.setPopupMode(QToolButton.InstantPopup) misc_action.setToolTip("Contains misc. features") misc_action_random = QAction("Open random gallery", misc_action_menu) misc_action_random.triggered.connect(self.manga_list_view.open_random_gallery) misc_action_menu.addAction(misc_action_random) duplicate_check_simple = QAction("Simple duplicate finder", misc_action_menu) duplicate_check_simple.triggered.connect(lambda: self.manga_list_view.duplicate_check()) misc_action_menu.addAction(duplicate_check_simple) self.toolbar.addWidget(misc_action) spacer_middle = QWidget() # aligns buttons to the right spacer_middle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.toolbar.addWidget(spacer_middle) self.grid_toggle_g_icon = QIcon(gui_constants.GRID_PATH) self.grid_toggle_l_icon = QIcon(gui_constants.LIST_PATH) self.grid_toggle = QToolButton() if self.display.currentIndex() == self.m_l_view_index: self.grid_toggle.setIcon(self.grid_toggle_l_icon) else: self.grid_toggle.setIcon(self.grid_toggle_g_icon) self.grid_toggle.setObjectName("gridtoggle") self.grid_toggle.clicked.connect(self.toggle_view) self.toolbar.addWidget(self.grid_toggle) self.search_bar = misc.LineEdit() if gui_constants.SEARCH_AUTOCOMPLETE: completer = QCompleter(self) completer.setModel(self.manga_list_view.gallery_model) completer.setCaseSensitivity(Qt.CaseInsensitive) completer.setCompletionMode(QCompleter.PopupCompletion) completer.setCompletionRole(Qt.DisplayRole) completer.setCompletionColumn(gui_constants.TITLE) completer.setFilterMode(Qt.MatchContains) self.search_bar.setCompleter(completer) if gui_constants.SEARCH_ON_ENTER: self.search_bar.returnPressed.connect(lambda: self.search(self.search_bar.text())) else: self.search_bar.textChanged[str].connect(self.search) self.search_bar.setPlaceholderText("Search title, artist, namespace & tags") self.search_bar.setMinimumWidth(150) self.search_bar.setMaximumWidth(500) self.toolbar.addWidget(self.search_bar) self.toolbar.addSeparator() settings_icon = QIcon(gui_constants.SETTINGS_PATH) settings_action = QAction("Set&tings", self) settings_action.triggered.connect(self.settings) self.toolbar.addAction(settings_action) spacer_end = QWidget() # aligns About action properly spacer_end.setFixedSize(QSize(10, 1)) self.toolbar.addWidget(spacer_end) self.addToolBar(self.toolbar) def toggle_view(self): """ Toggles the current display view """ if self.display.currentIndex() == self.m_l_view_index: self.display.setCurrentIndex(self.m_t_view_index) self.grid_toggle.setIcon(self.grid_toggle_g_icon) else: self.display.setCurrentIndex(self.m_l_view_index) self.grid_toggle.setIcon(self.grid_toggle_l_icon) # TODO: Improve this so that it adds to the gallery dialog, # so user can edit data before inserting (make it a choice) def populate(self, mixed=None): "Populates the database with gallery from local drive'" if mixed: gallery_view = misc.GalleryListView(self, True) gallery_view.SERIES.connect(self.gallery_populate) gallery_view.show() else: path = QFileDialog.getExistingDirectory(None, "Choose a folder containing your galleries") self.gallery_populate(path, True) def gallery_populate(self, path, validate=False): "Scans the given path for gallery to add into the DB" if len(path) is not 0: data_thread = QThread(self) data_thread.setObjectName("General gallery populate") loading = misc.Loading(self) if not loading.ON: misc.Loading.ON = True fetch_instance = fetch.Fetch() fetch_instance.series_path = path loading.show() def finished(status): def hide_loading(): loading.hide() if status: if len(status) != 0: def add_gallery(gallery_list): def append_to_model(x): self.manga_list_view.gallery_model.insertRows(x, None, len(x)) class A(QObject): done = pyqtSignal() prog = pyqtSignal(int) def __init__(self, obj, parent=None): super().__init__(parent) self.obj = obj self.galleries = [] def add_to_db(self): gui_constants.NOTIF_BAR.begin_show() gui_constants.NOTIF_BAR.add_text("Populating database...") for y, x in enumerate(self.obj): gui_constants.NOTIF_BAR.add_text( "Populating database {}/{}".format(y + 1, len(self.obj)) ) gallerydb.add_method_queue(gallerydb.GalleryDB.add_gallery_return, False, x) self.galleries.append(x) y += 1 self.prog.emit(y) append_to_model(self.galleries) gui_constants.NOTIF_BAR.end_show() self.done.emit() loading.progress.setMaximum(len(gallery_list)) a_instance = A(gallery_list) thread = QThread(self) thread.setObjectName("Database populate") def loading_show(): loading.setText("Populating database.\nPlease wait...") loading.show() def loading_hide(): loading.close() self.manga_list_view.gallery_model.ROWCOUNT_CHANGE.emit() def del_later(): try: a_instance.deleteLater() except NameError: pass a_instance.moveToThread(thread) a_instance.prog.connect(loading.progress.setValue) thread.started.connect(loading_show) thread.started.connect(a_instance.add_to_db) a_instance.done.connect(loading_hide) a_instance.done.connect(del_later) thread.finished.connect(thread.deleteLater) thread.start() data_thread.quit hide_loading() log_i("Populating DB from gallery folder: OK") if validate: gallery_list = misc.GalleryListView(self) gallery_list.SERIES.connect(add_gallery) for ser in status: gallery_list.add_gallery(ser, os.path.split(ser.path)[1]) # self.manga_list_view.gallery_model.populate_data() gallery_list.show() else: add_gallery(status) misc.Loading.ON = False else: log_d("No new gallery was found") loading.setText("No new gallery found") data_thread.quit misc.Loading.ON = False else: log_e("Populating DB from gallery folder: Nothing was added!") loading.setText("<font color=red>Nothing was added. Check happypanda_log for details..</font>") loading.progress.setStyleSheet("background-color:red;") data_thread.quit QTimer.singleShot(10000, loading.close) def fetch_deleteLater(): try: fetch_instance.deleteLater except NameError: pass def a_progress(prog): loading.progress.setValue(prog) loading.setText("Searching for galleries...") fetch_instance.moveToThread(data_thread) fetch_instance.DATA_COUNT.connect(loading.progress.setMaximum) fetch_instance.PROGRESS.connect(a_progress) data_thread.started.connect(fetch_instance.local) fetch_instance.FINISHED.connect(finished) fetch_instance.FINISHED.connect(fetch_deleteLater) data_thread.finished.connect(data_thread.deleteLater) data_thread.start() log_i("Populating DB from gallery folder") def resizeEvent(self, event): try: self.notification_bar.resize(event.size().width()) except AttributeError: pass return super().resizeEvent(event) def closeEvent(self, event): # watchers try: self.watchers.stop_all() except AttributeError: pass # settings settings.set(self.manga_list_view.current_sort, "General", "current sort") settings.win_save(self, "AppWindow") # temp dir try: for root, dirs, files in os.walk("temp", topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) log_d("Empty temp on exit: OK") except: log_d("Empty temp on exit: FAIL") # error err = sys.exc_info() if all(err): log_c("Last error before exit:\n{}\n{}\n{}".format(err[0], err[1], err[2])) else: log_d("Normal Exit App: OK") super().closeEvent(event) app = QApplication.instance() app.quit() sys.exit()
class MainWindow(QMainWindow): """This is the main application window class it defines the GUI window for the browser """ def parse_config(self, file_config, options): self.config = {} options = vars(options) for key, metadata in CONFIG_OPTIONS.items(): options_val = options.get(key) file_val = file_config.get(key) env_val = os.environ.get(metadata.get("env", '')) default_val = metadata.get("default") vals = metadata.get("values") debug("key: {}, default: {}, file: {}, options: {}".format( key, default_val, file_val, options_val )) if vals: options_val = (options_val in vals and options_val) or None file_val = (file_val in vals and file_val) or None env_val = (env_val in vals and env_val) or None if metadata.get("is_file"): filename = options_val or env_val if not filename: self.config[key] = default_val else: try: with open(filename, 'r') as fh: self.config[key] = fh.read() except IOError: debug("Could not open file {} for reading.".format( filename) ) self.config[key] = default_val else: set_values = [ val for val in (options_val, env_val, file_val) if val is not None ] if len(set_values) > 0: self.config[key] = set_values[0] else: self.config[key] = default_val if metadata.get("type") and self.config[key]: debug("{} cast to {}".format(key, metadata.get("type"))) self.config[key] = metadata.get("type")(self.config[key]) debug(repr(self.config)) def createAction(self, text, slot=None, shortcut=None, icon=None, tip=None, checkable=False, signal="triggered"): """Return a QAction given a number of common QAction attributes Just a shortcut function Originally borrowed from 'Rapid GUI Development with PyQT' by Mark Summerset """ action = QAction(text, self) if icon is not None: action.setIcon(QIcon.fromTheme( icon, QIcon(":/{}.png".format(icon)) )) if shortcut is not None and not shortcut.isEmpty(): action.setShortcut(shortcut) tip += " ({})".format(shortcut.toString()) if tip is not None: action.setToolTip(tip) action.setStatusTip(tip) if slot is not None: action.__getattr__(signal).connect(slot) if checkable: action.setCheckable() return action def __init__(self, options, parent=None): """Construct a MainWindow Object.""" super(MainWindow, self).__init__(parent) # Load config file self.setWindowTitle("Browser") debug("loading configuration from '{}'".format(options.config_file)) configfile = {} if options.config_file: configfile = yaml.safe_load(open(options.config_file, 'r')) self.parse_config(configfile, options) # self.popup will hold a reference to the popup window # if it gets opened self.popup = None # Stylesheet support if self.config.get("stylesheet"): try: with open(self.config.get("stylesheet")) as ss: self.setStyleSheet(ss.read()) except: debug( """Problem loading stylesheet file "{}", """ """using default style.""" .format(self.config.get("stylesheet")) ) self.setObjectName("global") # If the whitelist is activated, add the bookmarks and start_url if self.config.get("whitelist"): # we can just specify whitelist = True, # which should whitelist just the start_url and bookmark urls. whitelist = self.config.get("whitelist") if type(whitelist) is not list: whitelist = [] whitelist.append(str(QUrl( self.config.get("start_url") ).host())) bookmarks = self.config.get("bookmarks") if bookmarks: whitelist += [ str(QUrl(b.get("url")).host()) for k, b in bookmarks.items() ] self.config["whitelist"] = set(whitelist) # uniquify and optimize debug("Generated whitelist: " + str(whitelist)) # If diagnostic is enabled, connect CTRL+ALT+? to show some diagnistic info if (self.config.get("enable_diagnostic")): self.diagnostic_action = self.createAction( "Show Diagnostic", self.show_diagnostic, QKeySequence("Ctrl+Alt+/"), tip='' ) self.addAction(self.diagnostic_action) self.build_ui() # ## END OF CONSTRUCTOR ## # def build_ui(self): """Set up the user interface for the main window. Unlike the constructor, this method is re-run whenever the browser is "reset" by the user. """ debug("build_ui") inactivity_timeout = self.config.get("timeout") quit_button_tooltip = ( self.config.get("quit_button_mode") == 'close' and "Click here to quit the browser." or """Click here when you are done. It will clear your browsing history""" """ and return you to the start page.""") qb_mode_callbacks = {'close': self.close, 'reset': self.reset_browser} to_mode_callbacks = {'close': self.close, 'reset': self.reset_browser, 'screensaver': self.screensaver} self.screensaver_active = False # ##Start GUI configuration## # self.browser_window = WcgWebView(self.config) self.browser_window.setObjectName("web_content") if ( self.config.get("icon_theme") is not None and QT_VERSION_STR > '4.6' ): QIcon.setThemeName(self.config.get("icon_theme")) self.setCentralWidget(self.browser_window) debug("loading {}".format(self.config.get("start_url"))) self.browser_window.setUrl(QUrl(self.config.get("start_url"))) if self.config.get("fullscreen"): self.showFullScreen() elif ( self.config.get("window_size") and self.config.get("window_size").lower() == 'max' ): self.showMaximized() elif self.config.get("window_size"): size = re.match(r"(\d+)x(\d+)", self.config.get("window_size")) if size: width, height = size.groups() self.setFixedSize(int(width), int(height)) else: debug('Ignoring invalid window size "{}"'.format( self.config.get("window_size") )) # Set up the top navigation bar if it's configured to exist if self.config.get("navigation"): self.navigation_bar = QToolBar("Navigation") self.navigation_bar.setObjectName("navigation") self.addToolBar(Qt.TopToolBarArea, self.navigation_bar) self.navigation_bar.setMovable(False) self.navigation_bar.setFloatable(False) # Standard navigation tools self.nav_items = {} self.nav_items["back"] = self.browser_window.pageAction(QWebPage.Back) self.nav_items["forward"] = self.browser_window.pageAction(QWebPage.Forward) self.nav_items["refresh"] = self.browser_window.pageAction(QWebPage.Reload) self.nav_items["stop"] = self.browser_window.pageAction(QWebPage.Stop) # The "I'm finished" button. self.nav_items["quit"] = self.createAction( self.config.get("quit_button_text"), qb_mode_callbacks.get(self.config.get("quit_button_mode"), self.reset_browser), QKeySequence("Alt+F"), None, quit_button_tooltip) # Zoom buttons self.nav_items["zoom_in"] = self.createAction( "Zoom In", self.zoom_in, QKeySequence("Alt++"), "zoom-in", "Increase the size of the text and images on the page") self.nav_items["zoom_out"] = self.createAction( "Zoom Out", self.zoom_out, QKeySequence("Alt+-"), "zoom-out", "Decrease the size of text and images on the page") if self.config.get("allow_printing"): self.nav_items["print"] = self.createAction( "Print", self.browser_window.print_webpage, QKeySequence("Ctrl+p"), "document-print", "Print this page") # Add all the actions to the navigation bar. for item in self.config.get("navigation_layout"): if item == "separator": self.navigation_bar.addSeparator() elif item == "spacer": # an expanding spacer. spacer = QWidget() spacer.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Preferred) self.navigation_bar.addWidget(spacer) elif item == "bookmarks": # Insert bookmarks buttons here. self.bookmark_buttons = [] for bookmark in self.config.get("bookmarks", {}).items(): debug("Bookmark:\n" + bookmark.__str__()) # bookmark name will use the "name" attribute, if present # or else just the key: bookmark_name = bookmark[1].get("name") or bookmark[0] # Create a button for the bookmark as a QAction, # which we'll add to the toolbar button = self.createAction( bookmark_name, lambda url=bookmark[1].get("url"): self.browser_window.load(QUrl(url)), QKeySequence.mnemonic(bookmark_name), None, bookmark[1].get("description") ) self.navigation_bar.addAction(button) self.navigation_bar.widgetForAction(button).setObjectName("navigation_button") else: action = self.nav_items.get(item, None) if action: self.navigation_bar.addAction(action) self.navigation_bar.widgetForAction(action).setObjectName("navigation_button") # This removes the ability to toggle off the navigation bar: self.nav_toggle = self.navigation_bar.toggleViewAction() self.nav_toggle.setVisible(False) # End "if show_navigation is True" block # set hidden quit action # For reasons I haven't adequately ascertained, # this shortcut fails now and then claiming # "Ambiguous shortcut overload". # No idea why, as it isn't consistent. self.really_quit = self.createAction( "", self.close, QKeySequence("Ctrl+Alt+Q"), None, "" ) self.addAction(self.really_quit) # Call a reset function after timeout if inactivity_timeout != 0: self.event_filter = InactivityFilter(inactivity_timeout) QCoreApplication.instance().installEventFilter(self.event_filter) self.browser_window.page().installEventFilter(self.event_filter) self.event_filter.timeout.connect( to_mode_callbacks.get(self.config.get("timeout_mode"), self.reset_browser)) else: self.event_filter = None # ##END OF UI SETUP## # def screensaver(self): """Enter "screensaver" mode This method puts the browser in screensaver mode, where a URL is displayed while the browser is idle. Activity causes the browser to return to the home screen. """ debug("screensaver started") self.screensaver_active = True if self.popup: self.popup.close() if self.config.get("navigation"): self.navigation_bar.hide() self.browser_window.setZoomFactor(self.config.get("zoom_factor")) self.browser_window.load(QUrl(self.config.get("screensaver_url"))) self.event_filter.timeout.disconnect() self.event_filter.activity.connect(self.reset_browser) def reset_browser(self): """Clear the history and reset the UI. Called whenever the inactivity filter times out, or when the user clicks the "finished" button in 'reset' mode. """ # Clear out the memory cache QWebSettings.clearMemoryCaches() self.browser_window.history().clear() # self.navigation_bar.clear() doesn't do its job, # so remove the toolbar first, then rebuild the UI. debug("RESET BROWSER") if self.event_filter: self.event_filter.blockSignals(True) if self.screensaver_active is True: self.screensaver_active = False self.event_filter.activity.disconnect() if self.event_filter: self.event_filter.blockSignals(False) if hasattr(self, "navigation_bar"): self.removeToolBar(self.navigation_bar) self.build_ui() def zoom_in(self): """Zoom in action callback. Note that we cap zooming in at a factor of 3x. """ if self.browser_window.zoomFactor() < 3.0: self.browser_window.setZoomFactor( self.browser_window.zoomFactor() + 0.1 ) self.nav_items["zoom_out"].setEnabled(True) else: self.nav_items["zoom_in"].setEnabled(False) def zoom_out(self): """Zoom out action callback. Note that we cap zooming out at 0.1x. """ if self.browser_window.zoomFactor() > 0.1: self.browser_window.setZoomFactor( self.browser_window.zoomFactor() - 0.1 ) self.nav_items["zoom_in"].setEnabled(True) else: self.nav_items["zoom_out"].setEnabled(False) def show_diagnostic(self): "Display a dialog box with some diagnostic info" data = { "OS": os.uname(), "USER": (os.environ.get("USER") or os.environ.get("USERNAME")), "Python": sys.version, "Qt": QT_VERSION_STR, "Script Date": ( datetime.datetime.fromtimestamp( os.stat(__file__).st_mtime).isoformat() ) } html = "\n".join([ "<h1>System Information</h1>", "<h2>Please click "", self.config.get("quit_button_text").replace("&", ''), "" when you are finished.</h2>", "<ul>", "\n".join([ "<li><b>{}</b>: {}</li>".format(k, v) for k, v in data.items() ]), "</ul>" ]) self.browser_window.setHtml(html)
class FinishSpectrum(QWidget): def __init__(self, fits_file, settings, database, project=None): super(FinishSpectrum, self).__init__() self.settings = settings self.ui = Ui_FinishSpectrum() self.ui.setupUi(self) self.profile_line = None self.project = project self.fits_spectrum = FitsSpectrum(fits_file) self.undo = Undo(self.fits_spectrum.spectrum, self.draw) try: fits_file.index_of('ORIGINAL_DATA') except KeyError: hdu = fits.ImageHDU(data = fits_file[0].data, header = fits_file[0].header, name='ORIGINAL_DATA') fits_file.append(hdu) self.fits_spectrum.spectrum.normalize_to_max() self.spectrum = self.fits_spectrum.spectrum self.spectrum_plot = QtCommons.nestWidget(self.ui.plot, QMathPlotWidget()) self.spectrum_plot.mouse_moved.connect(Instances.MainWindow.print_coordinates) self.split_view() self.toolbar = QToolBar('Finish Spectrum Toolbar') if project: instrument_response_action = QtCommons.addToolbarPopup(self.toolbar, "Instrument Response") instrument_response_action.menu().addAction('From FITS file...', lambda: open_file_sticky('Open Instrument Response Profile', FITS_EXTS, lambda f: self.instrument_response(f[0]), settings, MATH_OPERATION, [RAW_PROFILE])) for instrument_response in project.get_instrument_responses(): print("Adding instrument response {}".format(instrument_response)) instrument_response_action.menu().addAction(os.path.basename(instrument_response[1]), lambda: self.instrument_response(instrument_response[1])) else: self.toolbar.addAction('Instrument Response', lambda: open_file_sticky('Open Instrument Response Profile', FITS_EXTS, lambda f: self.instrument_response(f[0]), settings, MATH_OPERATION, [RAW_PROFILE])) self.toolbar.addAction("Zoom", lambda: self.spectrum_plot.select_zoom(self.profile_plot.axes)) self.toolbar.addAction("Reset Zoom", lambda: self.spectrum_plot.reset_zoom(self.spectrum.wavelengths, self.spectrum.fluxes.min(), self.spectrum.fluxes.max(), self.profile_plot.axes)) remove_action = QtCommons.addToolbarPopup(self.toolbar, "Remove") remove_action.menu().addAction("Before point", lambda: spectrum_trim_dialog(self.spectrum, 'before', self.profile_plot.axes, lambda: self.draw(), self, before_removal=self.undo.save_undo)) remove_action.menu().addAction("After point", lambda: spectrum_trim_dialog(self.spectrum, 'after', self.profile_plot.axes, lambda: self.draw(), self, before_removal=self.undo.save_undo)) self.undo.add_actions(self.toolbar) self.toolbar.addSeparator() self.reference_spectra_dialog = ReferenceSpectraDialog(database, self.fits_spectrum.spectrum) self.reference_spectra_dialog.setup_menu(self.toolbar, self.profile_plot.axes, settings) lines_menu = QtCommons.addToolbarPopup(self.toolbar, "Spectral Lines..") lines_menu.menu().addAction('Lines Database', lambda: self.lines_dialog.show()) lines_menu.menu().addAction('Custom line', self.add_custom_line) labels_action = QtCommons.addToolbarPopup(self.toolbar, "Labels..") self.object_properties = ObjectProperties(fits_file, project=project) labels_action.menu().addAction('Title', self.add_title) if self.object_properties: labels_action.menu().addAction('Information from FITS file', self.add_fits_information_label) labels_action.menu().addAction('Custom', self.add_label) self.object_properties_dialog = ObjectPropertiesDialog(settings, self.object_properties) self.toolbar.addAction("Object properties", self.object_properties_dialog.show) self.labels, self.lines = [], [] for label in self.fits_spectrum.labels(): self.add_label(text=label['text'], coords=label['coords'], type=label['type'], fontsize=label['fontsize']) self.toolbar.addSeparator() if project: self.toolbar.addAction(QIcon(':/image_20'), "Export Image...", lambda: QtCommons.save_file('Export plot to image', 'PNG (*.png);;PDF (*.pdf);;PostScript (*.ps);;SVG (*.svg)', lambda f: self.save_image(f[0]), project.directory_path(Project.EXPORTED_IMAGES))) self.toolbar.addAction(QIcon(':/save_20'), 'Save', self.save_finished_in_project) else: self.toolbar.addAction(QIcon(':/image_20'), "Export Image...", lambda: save_file_sticky('Export plot to image', 'PNG (*.png);;PDF (*.pdf);;PostScript (*.ps);;SVG (*.svg)', lambda f: self.save_image(f[0]), self.settings, EXPORT_IMAGES, [CALIBRATED_PROFILE])) self.toolbar.addAction(QIcon(':/save_20'), 'Save', lambda: save_file_sticky('Save plot...', 'FITS file (.fit)', lambda f: self.__save(f[0]), self.settings, CALIBRATED_PROFILE)) self.lines_dialog = LinesDialog(database, settings, self.spectrum_plot, self.profile_plot.axes) self.lines_dialog.lines.connect(self.add_lines) for line in self.fits_spectrum.lines_labels(): self.lines.append(ReferenceLine(line['text'], line['wavelength'], self.profile_plot.axes, lambda line: self.lines.remove(line), show_wavelength=line['display_wavelength'], fontsize=line['fontsize'], position=line['position'])) def add_custom_line(self): wl = QInputDialog.getDouble(self, "Custom Line", "Enter line wavelength in Å", self.fits_spectrum.spectrum.wavelengths[0],self.fits_spectrum.spectrum.wavelengths[0],self.fits_spectrum.spectrum.wavelengths[-1],3) if not wl[1]: return self.add_lines([{'name': 'Custom Line', 'lambda': wl[0]}]) def add_lines(self, lines): for line in lines: self.lines.append(ReferenceLine(line['name'], line['lambda'], self.profile_plot.axes, lambda line: self.lines.remove(line))) def synthetize_img(wavelengths, fluxes): f_fluxes = lambda f: math.pow(f, 3/5) colors = [wavelength_to_rgb(w/10., f_fluxes(fluxes[i])) for i,w in enumerate(wavelengths)] im_height = 150 colors = np.array(colors*im_height).reshape(im_height,len(colors),4) return colors, im_height def split_view(self): figure = self.spectrum_plot.figure figure.clear() self.gs = gridspec.GridSpec(40,1) self.profile_plot = figure.add_subplot(self.gs[0:-6]) self.synthetize = figure.add_subplot(self.gs[-3:-1], sharex = self.profile_plot) self.synthetize.yaxis.set_visible(False) self.synthetize.xaxis.set_visible(False) self.draw() def draw(self): # self.profile_plot.clear() if self.profile_line: self.profile_line.remove() self.profile_line = self.profile_plot.plot(self.spectrum.wavelengths, self.spectrum.fluxes, color='blue')[0] self.synthetize.axes.set_axis_bgcolor('black') with ThreadPoolExecutor(max_workers=1) as executor: future = executor.submit(FinishSpectrum.synthetize_img, self.spectrum.wavelengths, self.spectrum.fluxes) future.add_done_callback(lambda f: self.synthetize.imshow(f.result()[0], extent=[self.spectrum.wavelengths[0], self.spectrum.wavelengths[-1], 0, f.result()[1]]) ) self.profile_plot.axes.set_xlabel('wavelength (Å)') self.profile_plot.axes.set_ylabel('relative flux') self.profile_plot.axes.xaxis.set_major_locator(MaxNLocator(16)) # TODO: settings for customization? self.profile_plot.axes.xaxis.set_minor_locator(MaxNLocator(200)) self.spectrum_plot.figure.canvas.draw() self.gs.tight_layout(self.spectrum_plot.figure) def instrument_response(self, filename): print("Applying instrument response {}".format(filename)) instrument_response_file = fits.open(filename) instrument_response = FitsSpectrum(instrument_response_file) response = instrument_response.spectrum response.normalize_to_max() range = (max(response.wavelengths[0], self.spectrum.wavelengths[0] ), min(response.wavelengths[-1], self.spectrum.wavelengths[-1])) self.spectrum.cut(self.spectrum.wavelength_index(range[0]), self.spectrum.wavelength_index(range[1])) spline = InterpolatedUnivariateSpline(response.wavelengths, response.fluxes) response_data = [spline(x) for x in self.spectrum.wavelengths] self.spectrum.fluxes /= response_data self.spectrum.normalize_to_max() self.draw() def save_image(self, filename): Notification('Image {} saved in {}'.format(os.path.basename(filename), os.path.dirname(filename)), title='File Saved', type='success', timeout=5) self.spectrum_plot.figure.savefig(filename, bbox_inches='tight', dpi=300) def save_finished_in_project(self): self.project.add_file(Project.FINISHED_PROFILES, self.__save, self.object_properties) def __save(self, filename): self.fits_spectrum.save(filename, spectral_lines = self.lines, labels = self.labels) def add_title(self): title = self.object_properties.name if self.object_properties else 'Title - double click to edit' self.add_label(text=title, coords=(self.spectrum.wavelengths[len(self.spectrum.wavelengths)/2-100], 0.95), fontsize=25, type='lineedit') def add_fits_information_label(self): info_text = "Object Name: {}, type: {}, spectral class: {}\nCoordinates: {}\nDate: {}\nObserver: {}\nEquipment: {}\nPosition: {}".format( self.object_properties.name, self.object_properties.type, self.object_properties.sptype, self.object_properties.printable_coordinates(), self.object_properties.date.toString(), self.object_properties.observer, self.object_properties.equipment, self.object_properties.position ) self.add_label(info_text, type='textbox', coords=(self.spectrum.wavelengths[len(self.spectrum.wavelengths)/4*3], 0.80), fontsize=14) self.profile_plot.figure.canvas.draw() def add_label(self, text=None, type='textbox', coords = None, fontsize = 12, color='black'): if not coords: coords = (self.spectrum.wavelengths[len(self.spectrum.wavelengths)/2], 0.5) self.labels.append((type, MoveableLabel(text=text if text else 'Label - double click to edit', on_dblclick=lambda l: self.edit_label(l, type=type), x=coords[0], y=coords[1], fontsize=fontsize, color=color, axes=self.profile_plot.axes))) self.profile_plot.figure.canvas.draw() def edit_label(self, label, type='lineedit'): def remove_label(self, label, dialog): label.remove() self.labels.remove([l for l in self.labels if l[1] == label][0]) self.profile_plot.figure.canvas.draw() dialog.reject() dialog = QDialog() dialog.setWindowTitle("Edit Label") dialog.setLayout(QVBoxLayout()) font_size = QSpinBox() font_size.setValue(label.get_fontsize()) dialog.layout().addWidget(QLabel("Font Size")) dialog.layout().addWidget(font_size) text_edit = None if type == 'lineedit': text_edit = QLineEdit(label.get_text()) else: text_edit = QTextEdit() text_edit.setPlainText(label.get_text()) dialog.layout().addWidget(QLabel("Text")) dialog.layout().addWidget(text_edit) button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box.accepted.connect(dialog.accept) button_box.rejected.connect(dialog.reject) remove_button = QPushButton('Remove') remove_button.clicked.connect(lambda: remove_label(self, label, dialog)) dialog.layout().addWidget(remove_button) dialog.layout().addWidget(button_box) if QDialog.Accepted != dialog.exec(): return label.set_text(text_edit.text() if type=='lineedit' else text_edit.toPlainText()) label.set_fontsize(font_size.value()) label.axes.figure.canvas.draw()
class _s_MiscContainer(QWidget): """From Miscellaneous, contains all the widgets in the bottom area.""" #Miscellaneous was to long and dificult to write :P def __init__(self, parent=None): super(_s_MiscContainer, self).__init__(parent) vbox = QVBoxLayout(self) vbox.setContentsMargins(0, 0, 0, 0) vbox.setSpacing(0) self.__toolbar = QToolBar() self.__toolbar.setObjectName('custom') hbox = QHBoxLayout() vbox.addLayout(hbox) self.stack = StackedWidget() vbox.addWidget(self.stack) self._console = console_widget.ConsoleWidget() self.stack.addWidget(self._console) self._runWidget = run_widget.RunWidget() self.stack.addWidget(self._runWidget) self._web = web_render.WebRender() self.stack.addWidget(self._web) self._findInFilesWidget = find_in_files.FindInFilesWidget( self.parent()) self.stack.addWidget(self._findInFilesWidget) #Last Element in the Stacked widget self._results = results.Results(self) self.stack.addWidget(self._results) self._btnConsole = QPushButton(QIcon(resources.IMAGES['console']), '') self._btnConsole.setToolTip(_translate("_s_MiscContainer", "Console")) self._btnRun = QPushButton(QIcon(resources.IMAGES['play']), '') self._btnRun.setToolTip(_translate("_s_MiscContainer", "Output")) self._btnWeb = QPushButton(QIcon(resources.IMAGES['web']), '') self._btnWeb.setToolTip(_translate("_s_MiscContainer", "Web Preview")) self._btnFind = QPushButton(QIcon(resources.IMAGES['find']), '') self._btnFind.setToolTip(_translate("_s_MiscContainer", "Find in Files")) #Toolbar hbox.addWidget(self.__toolbar) self.__toolbar.addWidget(self._btnConsole) self.__toolbar.addWidget(self._btnRun) self.__toolbar.addWidget(self._btnWeb) self.__toolbar.addWidget(self._btnFind) self.__toolbar.addSeparator() hbox.addSpacerItem(QSpacerItem(1, 0, QSizePolicy.Expanding)) btn_close = QPushButton( self.style().standardIcon(QStyle.SP_DialogCloseButton), '') btn_close.setObjectName('navigation_button') btn_close.setToolTip(_translate("_s_MiscContainer", 'F4: Show/Hide')) hbox.addWidget(btn_close) self._btnConsole.clicked['bool'].connect(lambda: self._item_changed(0)) self._btnRun.clicked['bool'].connect(lambda: self._item_changed(1)) self._btnWeb.clicked['bool'].connect(lambda: self._item_changed(2)) self._btnFind.clicked['bool'].connect(lambda: self._item_changed(3)) btn_close.clicked['bool'].connect(self.hide) def gain_focus(self): self._console.setFocus() def _item_changed(self, val): if not self.isVisible(): self.show() self.stack.show_display(val) def show_find_in_files_widget(self): index_of = self.stack.indexOf(self._findInFilesWidget) self._item_changed(index_of) self._findInFilesWidget.open() def show_find_occurrences(self, word): index_of = self.stack.indexOf(self._findInFilesWidget) self._item_changed(index_of) self._findInFilesWidget.find_occurrences(word) def load_toolbar(self, toolbar): toolbar.addWidget(self._combo) toolbar.addSeparator() def run_application(self, fileName, pythonPath=False, PYTHONPATH=None, programParams='', preExec='', postExec=''): self._item_changed(1) self.show() self._runWidget.start_process(fileName, pythonPath, PYTHONPATH, programParams, preExec, postExec) self._runWidget.input.setFocus() def show_results(self, items): self._item_changed(4) self.show() self._results.update_result(items) self._results._tree.setFocus() def kill_application(self): self._runWidget.kill_process() def render_web_page(self, url): self._item_changed(2) self.show() self._web.render_page(url) if settings.SHOW_WEB_INSPECTOR: explorer_container.ExplorerContainer().set_inspection_page( self._web.webFrame.page()) self._web.webFrame.triggerPageAction( QWebPage.InspectElement, True) explorer_container.ExplorerContainer().refresh_inspector() def add_to_stack(self, widget, icon_path, description): """ Add a widget to the container and an button(with icon))to the toolbar to show the widget """ #add the widget self.stack.addWidget(widget) #create a button in the toolbar to show the widget button = QPushButton(QIcon(icon_path), '') button.setToolTip(description) #index = self.stack.count() - 1 #func = lambda: self._item_changed(index) button.clicked['bool'].connect(lambda: self._item_changed(self.stack.count() - 1))#func self.__toolbar.addWidget(button)