class ViewSourceDialogTabber(QMainWindow): def __init__(self, parent=None, title="Source"): super(ViewSourceDialogTabber, self).__init__(parent) self.setWindowTitle(title) self.tabs = QTabWidget(self) self.tabs.setElideMode(Qt.ElideRight) self.tabs.setTabsClosable(True) self.tabs.tabCloseRequested.connect(self.removeTab) self.setCentralWidget(self.tabs) self.tabs.currentChanged.connect(self.updateWindowTitle) def sizeHint(self): return QSize(640, 480) def removeTab(self): self.tabs.removeTab(self.tabs.currentIndex()) def addTab(self, title="New Tab", data=""): vsd = ViewSourceDialog(self, str(title)) vsd.setPlainText(data) self.tabs.addTab(vsd, tr("Source of %s") % (title,)) self.tabs.setCurrentIndex(self.tabs.count()-1) self.raise_() self.activateWindow() def updateWindowTitle(self): try: self.setWindowTitle(tr("Source of %s") % (self.tabs.currentWidget().windowTitle(),)) except: pass if self.tabs.count() == 0: self.hide()
class MainWidget(QWidget): def __init__(self, flags, *args, **kwargs): super().__init__(flags, *args, **kwargs) self.layout_ = QVBoxLayout() self.tab = QTabWidget() self.file_loader = FileLoader(self) self.file_loader.new_result.connect(self.show_result) self.tab.addTab(self.file_loader, "Выбор файла") self.tab.setTabsClosable(True) self.tab.tabCloseRequested.connect(self.tab_close_request) self.layout_.addWidget(self.tab) self.setLayout(self.layout_) self.results = [] def show_result(self, result, name, in_tabs): if in_tabs: self.tab.addTab(result, name.split('/')[-1]) if self.tab.currentIndex() == 0: self.tab.setCurrentWidget(result) else: self.results.append(result) result.show() def tab_close_request(self, closing_tab_index): if closing_tab_index == 0: return self.tab.removeTab(closing_tab_index)
class MainWidget(QWidget): def __init__(self, width, height, manager, perm_dict): super().__init__() self.manager = manager self.perm_dict = perm_dict self.translation = Translation(perm_dict) self.setWindowTitle(Constants.WINDOW_NAME) self.resize(width, height) layout = QGridLayout() layout.setContentsMargins(0, 0, 0, 0) self.tab_widget = QTabWidget() layout.addWidget(self.tab_widget) self.setLayout(layout) self.show() self.restart() def restart(self): while self.tab_widget.count() > 0: self.tab_widget.removeTab(0) # TODO uncomment once finished self.tab_widget.addTab(OverviewPage(self.manager, self.perm_dict), self.translation.get_text("overview"))
class ViewSourceDialogTabber(QMainWindow): def __init__(self, parent=None, title="Source"): super(ViewSourceDialogTabber, self).__init__(parent) self.setWindowTitle(title) self.tabs = QTabWidget(self) self.tabs.setElideMode(Qt.ElideRight) self.tabs.setTabsClosable(True) self.tabs.tabCloseRequested.connect(self.removeTab) self.setCentralWidget(self.tabs) self.tabs.currentChanged.connect(self.updateWindowTitle) def sizeHint(self): return QSize(640, 480) def removeTab(self): self.tabs.removeTab(self.tabs.currentIndex()) def addTab(self, title="New Tab", data=""): vsd = ViewSourceDialog(self, str(title)) vsd.setPlainText(data) self.tabs.addTab(vsd, tr("Source of %s") % (title, )) self.tabs.setCurrentIndex(self.tabs.count() - 1) self.raise_() self.activateWindow() def updateWindowTitle(self): try: self.setWindowTitle( tr("Source of %s") % (self.tabs.currentWidget().windowTitle(), )) except: pass if self.tabs.count() == 0: self.hide()
class MainScreen(AsyncWidget): def __init__(self, interface, parent=None): super().__init__(parent) self.interface = interface self.setWindowTitle('Main screen') self.tab_widget = QTabWidget() layout = QHBoxLayout(self) layout.addWidget(self.tab_widget) @property def tabs(self): n_tabs = self.tab_widget.count() tabs = [self.tab_widget.widget(i) for i in range(n_tabs)] return tabs def add_tab(self, tab, name): self.tab_widget.addTab(tab, name) def remove_tab(self, tab): try: index = self.tabs.index(tab) self.tab_widget.removeTab(index) except ValueError: raise ValueError(f'{tab} is not in the list of tabs')
class MyTableWidget(QWidget): def __init__(self, parent=None): super(QWidget, self).__init__(parent) self.layout = QVBoxLayout(self) # Initialize tab screen self.tabs = QTabWidget() self.tabs.resize(300, 200) # Add tabs self.tabs.setTabsClosable(True) self.tabs.tabCloseRequested.connect(self.closeTab) self.tabs.currentChanged['int'].connect(self.tabfun) # Add tabs to widget self.layout.addWidget(self.tabs) #self.tabs.toolTip(self.tabs.tipsText) self.setLayout(self.layout) def closeTab(self, index): tab = self.tabs.widget(index) tab.deleteLater() self.tabs.removeTab(index) def addtab(self, content, fileName): self.tabs.addTab(Content(str(content)), str(fileName)) def tabfun(selfself,index): # test the current index number print("The current page is : " +str(index))
class CustomWidget(QWidget): def __init__(self, parent=None): super(CustomWidget, self).__init__(parent) self.button = QPushButton("Add tab") self.button.clicked.connect(self.buttonClicked) self.tabs = QTabWidget() self.tabs.setTabBar(TabBar()) self.tabs.setTabsClosable(True) self.tabs.setMovable(True) self.tabs.setDocumentMode(True) self.tabs.setElideMode(Qt.ElideRight) self.tabs.setUsesScrollButtons(True) self.tabs.tabCloseRequested.connect(self.closeTab) self.tabs.addTab(Container("Very big titleeeeeeeeee"), "Very big titleeeeeeeeeeee") self.tabs.addTab(Container("smalltext"), "smalltext") self.tabs.addTab(Container("smalltext2"), "smalltext2") vbox = QVBoxLayout() vbox.addWidget(self.button) vbox.addWidget(self.tabs) self.setLayout(vbox) self.resize(600, 600) def closeTab(self, index): tab = self.tabs.widget(index) tab.deleteLater() self.tabs.removeTab(index) def buttonClicked(self): self.tabs.addTab(Container("smalltext2"), "smalltext2")
class kstImagePanel(QWidget): def __init__(self): """ Class constructor. """ QWidget.__init__(self) # Prepare the left tab widget for several image viewers: self.tabImageViewers = QTabWidget() self.tabImageViewers.setTabsClosable(True) self.tabImageViewers.tabCloseRequested.connect(self.removeTab) # Compose layout of the whole widget: layout = QHBoxLayout() layout.addWidget(self.tabImageViewers) layout.setContentsMargins(0,0,0,0) #left,top,right,bottom self.setLayout(layout) def addTab(self, data, sourceFile, tabname, type, mode): """ Add a new tab with the specified input. NOTE: input could be a: - 'raw' image (i.e. a 2D raster image ready to be displayed) - 'pre-processed' object (i.e. a 4D data structure) - 'reconstructed' (i.e. a stack of 2D images) - 'post-processed' (i.e. a color RGB image?) """ # Get the central image by default: if data.ndim == 4: im = data[:,:,round(data.shape[2] / 2),round(data.shape[3] / 2)] else: im = data[:,:,round(data.shape[2] / 2)] # Create the image viewer: imageViewer = kstImageViewer(sourceFile, data, type, im, mode) # Set value of the refocusing slider (even if it might be hidden): imageViewer.sldDataset.setMinimum(0) imageViewer.sldDataset.setMaximum(data.shape[2]-1) imageViewer.sldDataset.setValue(round(data.shape[2]/2)) if data.ndim == 4: imageViewer.sldRepetition.setMinimum(0) imageViewer.sldRepetition.setMaximum(data.shape[3]-1) imageViewer.sldRepetition.setValue(round(data.shape[3]/2)) # Add a new tab: self.tabImageViewers.addTab(imageViewer, tabname) # To have it active uncomment this line: #self.tabLeftImageViewers.setCurrentIndex(self.tabLeftImageViewers.count() - 1) def removeTab(self, idx): """ Called when users want to remove an image viewer. """ self.tabImageViewers.removeTab(idx)
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.setWindowTitle('SEASide') self.resize(1000, 560) # Connection self._config = Configuration() server_names = tuple(sorted(SERVERS.keys())) # Interface self._widg = QWidget(self) self.setCentralWidget(self._widg) # Server selection dropdown self._dropdown = QComboBox() self._dropdown.addItems(server_names) default_server_name = self._config.get_default_server() self._dropdown.setCurrentIndex( self._dropdown.findText(default_server_name)) # Connect button self._button = QPushButton('Connect') self._button.clicked.connect(self._new_tab) # Tab view self._tab_view = QTabWidget() self._tab_view.setTabsClosable(True) self._tab_view.tabCloseRequested.connect(self._close_tab) # Default tab default_tab = MasterTab(SERVERS[self._config.get_default_server()], self._config) self._tab_view.addTab(default_tab, default_server_name) # Layout setup conn_layout = QHBoxLayout() top_layout = QVBoxLayout() # conn_layout.addStretch(1) conn_layout.addWidget(self._button) conn_layout.addWidget(self._dropdown) conn_widg = QWidget() conn_widg.setLayout(conn_layout) top_layout.addWidget(conn_widg) top_layout.addWidget(self._tab_view) self._widg.setLayout(top_layout) @pyqtSlot() def _new_tab(self): server_name = str(self._dropdown.currentText()) server = SERVERS[server_name] self._config.set_default_server(server_name) tab = MasterTab(server, self._config) self._tab_view.addTab(tab, server_name) self._tab_view.setCurrentIndex(self._tab_view.count() - 1) @pyqtSlot(int) def _close_tab(self, index): conn = self._tab_view.widget(index).get_connection() conn.close_connection() self._tab_view.removeTab(index) def get_username(self): return self._config.get_username()
class OuterWidget(QWidget): def __init__(self, callerObject): super(QWidget, self).__init__(callerObject) self.caller = callerObject self.layout = QVBoxLayout(self) self.tabs = QTabWidget() self.tab1 = Form(self) self.tabs.setTabsClosable(True) self.tabs.tabCloseRequested.connect(self.tabs.removeTab) self.tab1.refreshRead() self.tabs.addTab(self.tab1, "Home") self.layout.addWidget(self.tabs) self.setLayout(self.layout) #This function is to load a tab for Average of Temperature and Humidity def aver(self): tempAvg = np.mean(listTemperature) humidAvg = np.mean(listHumidity) dateTime = datetime.today() print("listTemperature: ", listTemperature[0:100], "\n") print('Average Temperature: {0:0.1f}'.format(tempAvg)) self.tab2 = Average(self) temper = '{0:0.2f}'.format(tempAvg) + ' C' humid = '{0:0.2f}'.format(humidAvg) + ' %' date = '{:%Y-%m-%d %H:%M}'.format(dateTime) self.tab2.textBrowser.setText(temper) self.tab2.textBrowser_2.setText(humid) self.tab2.textBrowser_3.setText(date) if self.tabs.count() < 2: averageIndex = self.tabs.addTab(self.tab2, "Mean Values") self.tabs.setCurrentIndex(averageIndex) else: self.tabs.setCurrentIndex(1) #This function is used to close this rudimentary thermostat application def closeApp(self): self.tabs.removeTab(1) self.tabs.removeTab(0) self.layout.removeWidget(self.tabs) self.caller.close() #This function is used to close the tab that was opened for Mean Data of temperature and Humidity def closeAvgTab(self): index = self.tabs.currentIndex() self.tabs.removeTab(index)
class BrowserWindow(QMainWindow): name = "PyQt5-WebBrowser" version = "2.0 Beta 3" date = "2018.12.03" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setWindowTitle(self.name + " " + self.version) self.setWindowIcon(QIcon('Assets/main.png')) self.resize(1200, 900) self.tabs = QTabWidget() self.tabs.setTabsClosable(True) self.tabs.setMovable(True) self.tabs.setTabShape(0) self.setCentralWidget(self.tabs) self.tabs.tabCloseRequested.connect(self.close_current_tab) # self.tabs.currentChanged.connect(lambda i: self.setWindowTitle(self.tabs.tabText(i) + " - " + self.name)) self.init_tab = BrowserTab(self) self.init_tab.browser.load(QUrl("http://www.hao123.com/")) self.add_new_tab(self.init_tab) def add_blank_tab(self): blank_tab = BrowserTab(self) self.add_new_tab(blank_tab) def add_new_tab(self, tab): i = self.tabs.addTab(tab, "") self.tabs.setCurrentIndex(i) tab.back_button.triggered.connect(tab.browser.back) tab.next_button.triggered.connect(tab.browser.forward) tab.stop_button.triggered.connect(tab.browser.stop) tab.refresh_button.triggered.connect(tab.browser.reload) tab.home_button.triggered.connect(tab.navigate_to_home) tab.enter_button.triggered.connect(tab.navigate_to_url) tab.add_button.triggered.connect(self.add_blank_tab) tab.set_button.triggered.connect(tab.create_about_window) tab.url_text_bar.returnPressed.connect(tab.navigate_to_url) tab.browser.urlChanged.connect(tab.renew_urlbar) tab.browser.loadProgress.connect(tab.renew_progress_bar) tab.browser.titleChanged.connect(lambda title: (self.tabs.setTabText( i, title), self.tabs.setTabToolTip(i, title))) tab.browser.iconChanged.connect( lambda icon: self.tabs.setTabIcon(i, icon)) def close_current_tab(self, i): if self.tabs.count() > 1: self.tabs.removeTab(i) else: self.close()
class TAOutputTab(QWidget): def __init__(self, ctx): super(TAOutputTab, self).__init__() self.ctx = ctx stacked_widget = QWidget() self.stacked_layout = QStackedLayout() self.stacked_layout.addWidget(self.create_empty_widget()) self.stacked_layout.addWidget(self.create_data_widget()) stacked_widget.setLayout(self.stacked_layout) layout = QHBoxLayout() layout.setContentsMargins(5, 5, 5, 5) layout.addWidget(stacked_widget) self.setLayout(layout) def display_empty(self): self.stacked_layout.setCurrentIndex(0) def display_data(self): self.stacked_layout.setCurrentIndex(1) def create_empty_widget(self): label = QLabel("You first have to successfully run the allocation.") label.setAlignment(Qt.AlignCenter) return label def create_data_widget(self): self.tabs = QTabWidget() self.tabs.setTabPosition(QTabWidget.South) return self.tabs def not_empty(self): m_tab = self.tableWidget.rowCount() n_tab = self.tableWidget.columnCount() return any( self.tableWidget.item(i, j).text() for j in range(n_tab) for i in range(m_tab)) def update_tables_from_data(self): for i in range(self.tabs.count()): self.tabs.removeTab(0) for i in range(len(self.ctx.app_data.results)): self.tabs.addTab(TAAllocationOutputTab(self.ctx, i), "Allocation {}".format(i + 1)) return
class BisitorMPLWidget(BisitorWidget): def __init__(self, user=None): super().__init__() main_layout = QVBoxLayout() self.tabs = QTabWidget() self.tabs.setTabsClosable(True) self.tabs.tabCloseRequested.connect(lambda x: self.tabs.removeTab(x) if x != 0 else None) main_layout.addWidget(self.tabs) setting_widget = SettingWidget(user) self.tabs.addTab(setting_widget, "Настройки") def on_accept(items, group_by, plot_type, semester): plot = MyMplCanvas( items, group_by, plot_type, semester=semester) self.tabs.addTab(plot, 'График') self.tabs.setCurrentWidget(plot) setting_widget.accept.connect(on_accept) self.setLayout(main_layout)
class VectorFrame(GenericFrame): def __init__(self, parent=None): super().__init__(QHBoxLayout(), 'Vector Frame', parent=parent) self.tabs = QTabWidget() self.vectors = None self.init_frame() def init_frame(self): self.setFrameShape(QFrame.StyledPanel) self.populate_tabs() self.layout.addWidget(self.tabs) def populate_tabs(self): self.tabs.clear() self.vectors = get_vector_list() if self.vectors: for vector in self.vectors: print(vector) self.initialize_tab(vector) # TODO: Program to open vector from vector db # self.tabs.addTab(QWidget(), '+') self.tabs.setTabsClosable(True) self.tabs.tabCloseRequested.connect(lambda i: self.tabs.removeTab(i)) def initialize_tab(self, vector): frame = GenericFrame(QHBoxLayout(), 'vector tab', self) splitter = QSplitter(Qt.Horizontal) # TODO: Make generic table class, populate with vector nodes & significant log entries splitter.addWidget(NodeTableFrame()) splitter.addWidget(GraphFrame(vector, frame)) splitter.setSizes([600, 600]) frame.layout.addWidget(splitter) self.tabs.addTab(frame, vector.get("Name")) def delete_tab(self, vector): for i in range(0, self.tabs.count()): if vector.get("Name") == self.tabs.tabText(i): self.tabs.removeTab(i)
class AccountsTabsWidget(QWidget): def __init__(self, accounts_thread): super().__init__() self.accounts_thread = accounts_thread layout = QGridLayout() layout.setContentsMargins(0, 0, 0, 0) self.tab_widget = QTabWidget() layout.addWidget(self.tab_widget) self.setLayout(layout) self.show() self.restart() def restart(self): while self.tab_widget.count() > 0: self.tab_widget.removeTab(0) self.tab_widget.addTab(Overview(self.accounts_thread), Translation.get_text("overview")) self.tab_widget.addTab(AddAccount(self.accounts_thread), Translation.get_text("add_accounts"))
class EvalWindow(QMainWindow): def __init__(self): super(EvalWindow, self).__init__() self.tab_widget = QTabWidget() self.tab_widget.setTabsClosable(True) self.tab_widget.setTabShape(1) self.tab_widget.tabCloseRequested.connect(self.on_close_tab) self.tab_widget.currentChanged.connect(self.tab_changed) self.setCentralWidget(self.tab_widget) self.data_editor_dock = QDockWidget("Data Inspection", self) self.data_editor_dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.BottomDockWidgetArea) self.addDockWidget(Qt.BottomDockWidgetArea, self.data_editor_dock) self.data_editor = DataEditor() self.data_editor_dock.setWidget(self.data_editor) self.navigation_dock = QDockWidget("Navigation Stack", self) self.navigation_dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.BottomDockWidgetArea) self.addDockWidget(Qt.BottomDockWidgetArea, self.navigation_dock) self.last_tab_widget = None self.resize(1000, 700) def add_eval(self, gui_eval): self.show() self.activateWindow() self.tab_widget.addTab(gui_eval, gui_eval.view.scene.workflow.name) def tab_changed(self, index): curr_widget = self.tab_widget.currentWidget() curr_widget.view.scene.on_selection_changed() self.navigation_dock.setWidget(curr_widget.navigation) self.last_tab_widget = curr_widget def on_close_tab(self, index): self.tab_widget.removeTab(index)
class SesionTab(QWidget): def __init__(self, conn, confName, holder=None, parent=None): super(SesionTab, self).__init__(parent) self.conn = conn self.confName = confName self.holder = holder self.tabQueries = QTabWidget() self.tabQueries.setTabShape(QTabWidget.Triangular) meatLayout = QVBoxLayout() meatLayout.addWidget(self.tabQueries) self.setLayout(meatLayout) self.addQuery() def addQuery(self): #self.tabQueries.addTab(QueryTab(self.conn,self.confName,holder=self.holder),"{}:{}".format(self.confName, self.tabQueries.count() +1)) self.tabQueries.addTab( myQueryTab(self.conn, holder=self.holder), "{}:{}".format(self.confName, self.tabQueries.count() + 1)) self.tabQueries.setCurrentIndex(self.tabQueries.count() - 1) def openQuery(self): #self.tabQueries.addTab(QueryTab(self.conn,self.confName,holder=self.holder),"{}:{}".format(self.confName, #self.tabQueries.count() +1)) #self.tabQueries.setCurrentIndex(self.tabQueries.count() -1) self.tabQueries.currentWidget().readQuery() def saveQuery(self): self.tabQueries.currentWidget().writeQuery() def saveAsQuery(self): self.tabQueries.currentWidget().writeQuery(True) def closeQuery(self): tabId = self.tabQueries.currentIndex() self.tabQueries.removeTab(tabId) def execute(self): self.tabQueries.currentWidget().execute() def reformat(self): self.tabQueries.currentWidget().reformat()
class MyTextEditor(QWidget): def __init__(self, parent=None): super().__init__(parent=parent) self.resize(400, 400) self.nb_tabs = 0 self.tab_dict = {} self.init_ui() def init_ui(self): self.main_layout = QVBoxLayout() self.tabs = QTabWidget() # self.tabs.setTabsClosable(True) # self.tabs.tabCloseRequested.connect(self.close_tab) self.open_note("now") self.open_note("done") self.open_note("log") self.open_note("tips") # self.tabs.resize(600, 600) # self.t1.layout = QVBoxLayout() # qte = QTextEdit() # self.t1.layout.addWidget(qte) # self.t1.setLayout(self.t1.layout) # button = QPushButton("Add tab") # button.clicked.connect(self.add_tab) # self.main_layout.addWidget(button) self.main_layout.addWidget(self.tabs) self.setLayout(self.main_layout) def open_note(self, fname): tab = MyQTextEdit(fname) self.tab_dict[fname] = tab self.tabs.addTab(tab, fname) def close_tab(self, index): self.tabs.removeTab(index)
class RadioControlTelemetryWindow(QWidget): def __init__(self, parent=None): super().__init__(parent) self.isAnyRCChannelsUpdate = False self.__defaultWidget = None self.setWindowTitle('Radio Control Telemetry') self.__createDefaultWidget() self.tabs = QTabWidget() self.tabs.addTab(self.__defaultWidget, 'RC Telemetry') self.ports = {} l = QVBoxLayout() l.addWidget(self.tabs) self.setLayout(l) def updateRCChannelValues(self, msg): if msg.port not in self.ports: if self.isAnyRCChannelsUpdate == False: self.isAnyRCChannelsUpdate = True self.tabs.removeTab(0) self.ports[msg.port] = RadioControlTelemetryPanel() self.tabs.addTab(self.ports[msg.port], 'Receiver {}'.format(msg.port)) channels = [] channels.append(msg.chan1_raw) channels.append(msg.chan2_raw) channels.append(msg.chan3_raw) channels.append(msg.chan4_raw) channels.append(msg.chan5_raw) channels.append(msg.chan6_raw) channels.append(msg.chan7_raw) channels.append(msg.chan8_raw) self.ports[msg.port].updateValues(channels) def __createDefaultWidget(self): self.__defaultWidget = QWidget() l = QVBoxLayout() l.addWidget(QLabel('No RC channel value message has been received.')) self.__defaultWidget.setLayout(l)
class DataFileWidget(QWidget): def __init__(self, parent): QWidget.__init__(self) self.parent = parent layout = QVBoxLayout(self) self.tabs = QTabWidget() self.tabs.setTabsClosable(True) self.tabs.tabCloseRequested.connect(self.closeFile) layout.addWidget(self.tabs) def openFile(self, filename): var_list = VarListWidget(self, filename) # Create a new tab and add the varListWidget to it. self.tabs.addTab(var_list, filename) self.tabs.setCurrentWidget(var_list) def closeFile(self, index): # Add function for closing the tab here. self.tabs.widget(index).close() self.tabs.widget(index).deleteLater() self.tabs.removeTab(index)
class MyTableWidget(QWidget): def __init__(self, parent=None): super(QWidget, self).__init__(parent) self.layout = QVBoxLayout(self) # Initialize tab screen self.tabs = QTabWidget() self.tabs.resize(300, 200) # Add tabs self.tabs.setTabsClosable(True) self.tabs.tabCloseRequested.connect(self.closeTab) # Add tabs to widget self.layout.addWidget(self.tabs) self.setLayout(self.layout) def closeTab(self, index): tab = self.tabs.widget(index) tab.deleteLater() self.tabs.removeTab(index) def addtab(self, content, fileName): self.tabs.addTab(Content(str(content)), str(fileName))
class MyTableWidget(QWidget): tab_list = [] def __init__(self, parent): super(QWidget, self).__init__(parent) self.layout = QVBoxLayout(self) self.tb = QToolButton() self.tb.setText("+") self.tabs = QTabWidget() self.tb.clicked.connect(self.addTab) self.tabs.addTab(QLabel("Add tabs by pressing \"+\""), "") self.tabs.setTabEnabled(0, False) self.tabs.tabBar().setTabButton(0, QTabBar().RightSide, self.tb) self.addTab() self.tabs.resize(1028, 720) self.tabs.setTabsClosable(True) self.tabs.tabCloseRequested.connect(self.removeTab) self.layout.addWidget(self.tabs) self.setLayout(self.layout) def addTab(self): self.tab_list.append(QWidget()) self.index = len(self.tab_list) - 1 self.tabs.insertTab(self.index, self.tab_list[-1], "Option") self.tab_list[-1].layout = QGridLayout.QGridLayout() self.tab_list[-1].setLayout(self.tab_list[-1].layout) self.tabs.setCurrentIndex(self.index) def removeTab(self, index): self.tabs.removeTab(index) self.tab_list.pop(index) self.tabs.setCurrentIndex(index - 1)
class StudentGroupsTabs(QWidget): def __init__(self, parent, student_listings=None): super().__init__(parent) if student_listings is None: self.student_listings = students.StudentListings() inserted_group = students.StudentGroup(0, 'INSERTED') self.student_listings.create_listing(inserted_group) else: self.student_listings = student_listings layout = QVBoxLayout(self) self.setLayout(layout) self.tabs = QTabWidget(self) # Special tab for creating a new group: self.tabs.addTab(QWidget(), ' + ') # Group 0 is special: don't show it for listing in self.student_listings[1:]: self._add_group_tab(listing) # At least one normal group needs to be present: if len(self.student_listings) == 1: self._create_default_group() button_load = QPushButton(_('Add students from file'), parent=self) button_new_student = QPushButton( QIcon(utils.resource_path('new_id.svg')), _('New student'), parent=self) button_remove = QPushButton( QIcon(utils.resource_path('discard.svg')), _('Remove group'), parent=self) layout.addWidget(self.tabs) layout.addWidget(button_load) layout.addWidget(button_new_student) layout.addWidget(button_remove) layout.setAlignment(button_load, Qt.AlignHCenter) layout.setAlignment(button_new_student, Qt.AlignHCenter) layout.setAlignment(button_remove, Qt.AlignHCenter) self.tabs.setCurrentIndex(0) self._active_tab = 0 self.tabs.currentChanged.connect(self._tab_changed) self.tabs.tabBarDoubleClicked.connect(self._rename_group) button_load.clicked.connect(self._load_students) button_new_student.clicked.connect(self._new_student) button_remove.clicked.connect(self._remove_group) def _load_students(self): index = self.tabs.currentIndex() file_name, __ = QFileDialog.getOpenFileName( self, _('Select the student list file'), '', FileNameFilters.student_list, None, QFileDialog.DontUseNativeDialog) try: if file_name: with students.StudentReader.create(file_name) as reader: student_list = list(reader.students()) # Flag duplicate student ids: warn_duplicates = False for s in self.student_listings.find_duplicates(student_list): s.is_duplicate = True warn_duplicates = True if warn_duplicates: QMessageBox.warning( self, _('Importing a student list'), _('Some student ids are already in the list. ' 'Remove them or cancel the import operation.')) column_map = reader.column_map.normalize() preview_dialog = DialogPreviewStudents( self, student_list, column_map) result = preview_dialog.exec_() if result == QMessageBox.Accepted: self.tabs.widget(index).add_students(student_list) except Exception as e: QMessageBox.critical( self, _('Error in student list'), file_name + '\n\n' + str(e)) def _new_student(self): index = self.tabs.currentIndex() dialog = NewStudentDialog( self.student_listings, group_index=index, parent=self) student = dialog.exec_() if student is not None: self.tabs.widget(index).listing_updated() def _remove_group(self): index = self.tabs.currentIndex() if len(self.student_listings[index + 1]) > 0: result = QMessageBox.warning( self, _('Warning'), _('This group and its students will be removed. ' 'Are you sure you want to continue?'), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) remove = (result == QMessageBox.Yes) else: remove = True if remove: try: self.student_listings.remove_at(index + 1) if len(self.student_listings) > 1: if index == self.tabs.count() - 2: self.tabs.setCurrentIndex(index - 1) else: self.tabs.setCurrentIndex(index + 1) else: self._create_default_group() self.tabs.setCurrentIndex(1) self.tabs.removeTab(index) except students.CantRemoveGroupException: QMessageBox.critical( self, _('Error'), _('This group cannot be removed because ' 'exams have been graded for some of its students.')) def _add_group_tab(self, listing, show=False): self.tabs.insertTab( self.tabs.count() - 1, GroupWidget(listing, self), listing.group.name ) if show: self.tabs.setCurrentIndex(self.tabs.count() - 2) def _new_group(self): group_name = GroupNameDialog(parent=self).exec_() if group_name is not None: group = students.StudentGroup(None, group_name) listing = self.student_listings.create_listing(group) self._add_group_tab(listing, show=True) else: self.tabs.setCurrentIndex(self._active_tab) def _create_default_group(self): group = students.StudentGroup(None, _('Students')) listing = self.student_listings.create_listing(group) self._add_group_tab(listing, show=True) def _rename_group(self, index): name = self.student_listings[index + 1].group.name new_name = GroupNameDialog(group_name=name, parent=self).exec_() if new_name is not None and new_name != name: self.student_listings[index + 1].rename(new_name) self.tabs.setTabText(index, new_name) def _tab_changed(self, index): if index == self.tabs.count() - 1: # The last (special) tab has been activated: create a new group self._new_group() self._active_tab = self.tabs.currentIndex()
class SimulatorWindow(QMainWindow): def __init__(self, argv): QMainWindow.__init__(self) self.setWindowTitle("SimSo: Real-Time Scheduling Simulator") # Possible actions: style = QApplication.style() # New self._newAction = QAction( style.standardIcon(QStyle.SP_FileDialogNewFolder), '&New', None) self._newAction.setShortcut(Qt.CTRL + Qt.Key_N) self._newAction.triggered.connect(self.fileNew) # Open self._openAction = QAction( style.standardIcon(QStyle.SP_DialogOpenButton), '&Open', None) self._openAction.setShortcut(Qt.CTRL + Qt.Key_O) self._openAction.triggered.connect(self.fileOpen) # Save self._saveAction = QAction( style.standardIcon(QStyle.SP_DialogSaveButton), '&Save', None) self._saveAction.setShortcut(Qt.CTRL + Qt.Key_S) self._saveAction.triggered.connect(self.fileSave) # Save As self._saveAsAction = QAction( style.standardIcon(QStyle.SP_DialogSaveButton), 'Save &As', None) self._saveAsAction.setShortcut(Qt.CTRL + Qt.SHIFT + Qt.Key_S) self._saveAsAction.triggered.connect(self.fileSaveAs) # Run self._runAction = QAction( style.standardIcon(QStyle.SP_MediaPlay), '&Run', None) self._runAction.setShortcut(Qt.CTRL + Qt.Key_R) self._runAction.triggered.connect(self.fileRun) # Show Model data self._modelAction = QAction('&Model data', None) self._modelAction.setShortcut(Qt.CTRL + Qt.Key_M) #self._ganttAction.setCheckable(True) self._modelAction.triggered.connect(self.showModelWindow) # Show Gantt self._ganttAction = QAction('&Gantt', None) self._ganttAction.setShortcut(Qt.CTRL + Qt.Key_G) self._ganttAction.setEnabled(False) #self._ganttAction.setCheckable(True) self._ganttAction.triggered.connect(self.showGantt) # Show Results self._metricsAction = QAction('&Results', None) self._metricsAction.setShortcut(Qt.CTRL + Qt.Key_I) self._metricsAction.setEnabled(False) #self._metricsAction.setCheckable(True) self._metricsAction.triggered.connect(self.showResults) # Show Doc self._docAction = QAction('&Documentation', None) self._docAction.triggered.connect(self.showDocumentation) self._aboutAction = QAction('&About SimSo', None) self._aboutAction.triggered.connect(self.showAbout) # Recent files self._recentFileActions = [] for i in range(5): act = QAction(self) act.setVisible(False) act.triggered.connect(self.openRecentFile) self._recentFileActions.append(act) # File Menu: file_menu = QMenu('&File', self) file_menu.addAction(self._newAction) file_menu.addAction(self._openAction) file_menu.addAction(self._saveAction) file_menu.addAction(self._saveAsAction) file_menu.addAction(self._runAction) file_menu.addSeparator() for act in self._recentFileActions: file_menu.addAction(act) file_menu.addSeparator() file_menu.addAction('&Quit', self.fileQuit, Qt.CTRL + Qt.Key_Q) self.updateRecentFileActions() # View Menu: view_menu = QMenu('&View', self) view_menu.addAction(self._modelAction) view_menu.addAction(self._ganttAction) view_menu.addAction(self._metricsAction) # Help Menu: help_menu = QMenu('&Help', self) help_menu.addAction(self._docAction) help_menu.addAction(self._aboutAction) # Add menus to menuBar: self.menuBar().addMenu(file_menu) self.menuBar().addMenu(view_menu) self.menuBar().addMenu(help_menu) # ToolBar: self.toolBar = QToolBar("Main ToolBar") self.addToolBar(self.toolBar) self.toolBar.addAction(self._newAction) self.toolBar.addAction(self._openAction) self.toolBar.addAction(self._saveAction) self.toolBar.addAction(self._runAction) self.toolBar.addAction(self._ganttAction) self.toolBar.addAction(self._metricsAction) # Tab: self.main_tab = QTabWidget() self.main_tab.setTabsClosable(True) self.main_tab.setMovable(True) self.main_tab.tabCloseRequested.connect(self.tabCloseRequested) self.main_tab.currentChanged.connect(self.tabChanged) self.setCentralWidget(self.main_tab) # Init statusBar: self.statusBar().showMessage("", 2000) self._documentation = None if argv: for arg in argv: try: self.open_file(arg) except Exception as e: print(e) else: self.fileNew() def openRecentFile(self): try: self.open_file(self.sender().data().toString()) except AttributeError: self.open_file(self.sender().data()) def updateRecentFileActions(self): settings = QSettings() files = settings.value("recentFileList", defaultValue=[], type='QString') for i in range(5): if i < len(files): text = "&{} {}".format(i + 1, QFileInfo(files[i]).fileName()) self._recentFileActions[i].setText(text) self._recentFileActions[i].setData(files[i]) self._recentFileActions[i].setVisible(True) else: self._recentFileActions[i].setVisible(False) def setCurrentFile(self, filename): filename = QFileInfo(filename).absoluteFilePath() settings = QSettings() files = settings.value("recentFileList", defaultValue=[], type='QString') if filename in files: files.remove(filename) files.insert(0, filename) while len(files) > 5: del files[-1] settings.setValue("recentFileList", files) self.updateRecentFileActions() def showAbout(self): QMessageBox.about( self, "About SimSo", "<b>SimSo - Simulation of Multiprocessor Scheduling with Overheads</b><br/><br/>" "Version: SimSo {}, Graphical User Interface {}<br/><br/>" "SimSo is a free software developed by Maxime Cheramy (LAAS-CNRS).<br/>" "This software is distributed under the <a href='http://www.cecill.info'>CECILL license</a>, " "compatible with the GNU GPL.<br/><br/>" "Contact: <a href='mailto:[email protected]'>[email protected]</a>".format(simso.__version__, simsogui.__version__) ) def showDocumentation(self): if self._documentation is None: doc = QWebView(self) doc.load(QUrl("doc/html/index.html")) self._documentation = QDockWidget("Documentation", self) self._documentation.setWidget(doc) self._documentation.closeEvent = lambda _: self.hide_documentation() self.addDockWidget(Qt.LeftDockWidgetArea, self._documentation) def showGantt(self): self.main_tab.currentWidget().showGantt() def showModelWindow(self): self.main_tab.currentWidget().showModelWindow() def showResults(self): self.main_tab.currentWidget().showResults() def hide_documentation(self): self._documentation = None def fileNew(self): self.main_tab.addTab(SimulationTab(self), 'Unsaved') def fileOpen(self): simulation_file = QFileDialog.getOpenFileName( filter="*.xml", caption="Open XML simulation file.")[0] if simulation_file: self.open_file(simulation_file) def open_file(self, simulation_file): try: simulation_file = unicode(simulation_file) except NameError: pass try: self.setCurrentFile(simulation_file) sim = SimulationTab(self, simulation_file) if (self.main_tab.currentWidget() and not self.main_tab.currentWidget().simulation_file and self.main_tab.currentWidget().configuration.is_saved() and self.main_tab.count() == 1): self.main_tab.removeTab(0) self.main_tab.addTab(sim, os.path.split(simulation_file)[1]) self.main_tab.setCurrentWidget(sim) self.updateMenus() except Exception: QMessageBox.critical( self, "Could not open file", "The file {} could not be opened.".format(simulation_file)) print(traceback.format_exc()) def fileSave(self): try: self.main_tab.currentWidget().save() except: self.fileSaveAs() def fileSaveAs(self): simulation_file = QFileDialog.getSaveFileName( filter="*.xml", caption="Save XML simulation file.")[0] try: simulation_file = unicode(simulation_file) except NameError: pass if simulation_file: if simulation_file[-4:] != '.xml': simulation_file += '.xml' self.main_tab.currentWidget().save_as(simulation_file) self.setCurrentFile(simulation_file) def fileRun(self): self._runAction.setEnabled(False) self.main_tab.currentWidget().run() def fileQuit(self): self.close() def setTabText(self, tab, text): self.main_tab.setTabText(self.main_tab.indexOf(tab), text) def tabChanged(self, index): self.updateMenus() def tabCloseRequested(self, index): if self.main_tab.widget(index).close(): self.main_tab.removeTab(index) self.updateMenus() def closeEvent(self, event): while self.main_tab.count() > 0: if self.main_tab.widget(0).close(): self.main_tab.removeTab(0) else: event.ignore() return def updateMenus(self): if self.main_tab.count() > 0: widget = self.main_tab.currentWidget() self._runAction.setEnabled(True) self._modelAction.setEnabled(True) self._ganttAction.setEnabled(widget._model is not None) self._metricsAction.setEnabled(widget._model is not None) else: self._runAction.setEnabled(False) self._modelAction.setEnabled(False) self._ganttAction.setEnabled(False) self._metricsAction.setEnabled(False)
class EditorMainWindow(QMainWindow): def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.ui = Ui_ScriptEditor() self.ui.setupUi(self) #self.ui.actionExit.triggered.connect(self.exit) self.splitter = QSplitter(Qt.Vertical, self) self.setCentralWidget(self.splitter) self.edit_tab = QTabWidget(self.splitter) self.console_tab = QTabWidget(self.splitter) self.py_console = PythonConsole(self.console_tab) self.console_tab.addTab(self.py_console, "&Python console") self.js_console = QtQmlConsole(self.console_tab) self.console_tab.addTab(self.js_console, "&QtQml console") self.editors = [] self.on_actionNewPython_triggered() @pyqtSlot() def closeEvent(self, event): while(self.editors.__len__()): edit = self.edit_tab.currentWidget() if edit: if(edit.isModified()): saveBox = SaveDialog("You have unsaved script. Save it now?") prompt = saveBox.exec_() if(prompt == QMessageBox.Save): event.ignore() self.save(True) elif(prompt == QMessageBox.Cancel): event.ignore() return elif(prompt == QMessageBox.Discard): event.accept() i = self.edit_tab.indexOf(edit) self.edit_tab.removeTab(i) self.editors.remove(edit) event.accept() @pyqtSlot() def on_actionExit_triggered(self): while(self.editors.__len__()): edit = self.edit_tab.currentWidget() if edit: if(edit.isModified()): saveBox = SaveDialog("You have unsaved script. Save it now?") prompt = saveBox.exec_() if(prompt == QMessageBox.Save): self.save(True) elif(prompt == QMessageBox.Cancel): return elif(prompt == QMessageBox.Discard): pass i = self.edit_tab.indexOf(edit) self.edit_tab.removeTab(i) self.editors.remove(edit) self.close() @pyqtSlot() def on_actionNewPython_triggered(self): pyedit = PythonEditorWidget(self.edit_tab) pyedit.setPlainText(template_py) self.edit_tab.addTab(pyedit, "Python") self.edit_tab.setCurrentWidget(pyedit) self.editors.append(pyedit) self.py_console.attach() self.console_tab.setCurrentIndex(0) pyedit.setFocus() pyedit.view.setFocus() @pyqtSlot() def on_actionNewQtQml_triggered(self): jsedit = QtQmlEditorWidget(self.edit_tab) self.edit_tab.addTab(jsedit, "QtQml") self.edit_tab.setCurrentWidget(jsedit) self.editors.append(jsedit) self.js_console.attach() self.console_tab.setCurrentIndex(1) @pyqtSlot() def on_actionClose_triggered(self): edit = self.edit_tab.currentWidget() if edit: if(edit.isModified()): saveBox = SaveDialog("Do you want to save this Script?") prompt = saveBox.exec_() if(prompt == QMessageBox.Save): self.save(True) elif(prompt == QMessageBox.Cancel): return elif(prompt == QMessageBox.Discard): pass i = self.edit_tab.indexOf(edit) self.edit_tab.removeTab(i) self.editors.remove(edit) @pyqtSlot() def on_actionClear_triggered(self): #edit = self.edit_tab.currentWidget() #edit.setPlainText(template_py) self.py_console.clear() @pyqtSlot() def on_actionSave_As_triggered(self): self.save() @pyqtSlot() def on_actionSave_triggered(self): self.save(True) #Path of the script file in each tab will be stored in tabToolTip def save(self, Update = False): edit = self.edit_tab.currentWidget() contents = str(edit.toPlainText()) if((Update == False) or (self.edit_tab.tabText(self.edit_tab.currentIndex()) == "Python") ): #Save in its first invocation and Save As will enter filename = QFileDialog.getSaveFileName(self, "Save File", "", "*.spy") fil = open(filename , 'w') if(filename and self.edit_tab.tabText(self.edit_tab.currentIndex()) == "Python"): #Script hasn't been saved before and user specifies a valid filename self.edit_tab.setTabToolTip(self.edit_tab.currentIndex(), filename+'.spy') self.edit_tab.setTabText(self.edit_tab.currentIndex(), os.path.basename(str(filename+'.spy'))) else: #filename = self.edit_tab.tabText(self.edit_tab.currentIndex()) filename = self.edit_tab.tabToolTip(self.edit_tab.currentIndex()) fil = open( filename , 'w') fil.write(contents) fil.close() edit.setModified(False) @pyqtSlot() def on_actionOpen_triggered(self): filename = QFileDialog.getOpenFileName(self,"Open File","","*.spy") try: fil = open(filename , 'r') except IOError: return code = fil.read() edit = self.edit_tab.currentWidget() self.edit_tab.setTabText(self.edit_tab.currentIndex(), os.path.basename(str(filename))) self.edit_tab.setTabToolTip(self.edit_tab.currentIndex(), filename) edit.setPlainText(code) fil.close() @pyqtSlot() def on_actionRun_triggered(self): self.run() @pyqtSlot() def on_actionRunConsole_triggered(self): self.run(True) def run(self, console=False): edit = self.edit_tab.currentWidget() code = str(edit.toPlainText()) if isinstance(edit, PythonEditorWidget): self.py_console.attach() self.console_tab.setCurrentIndex(0) if console: namespace = self.py_console.namespace else: namespace = {} try: exec code in namespace except Exception as e: traceback.print_exc() try: Scripter.activeWindow.redraw = True Scripter.activeWindow.update() except: pass else: self.js_console.attach() self.console_tab.setCurrentIndex(1) if console: self.js_console.inter.execute(code) else: self.js_console.inter.execute_code(code)
class DyStockTradeStrategyMarketMonitorWidget(QWidget): """ 股票策略实时监控窗口,动态创建 """ signal = QtCore.pyqtSignal(type(DyEvent())) def __init__(self, eventEngine, strategyCls, strategyState): super().__init__() self._eventEngine = eventEngine self._strategyCls = strategyCls self._registerEvent() self._initUi(strategyState) def _initUi(self, strategyState): self._dataWidget = DyStockTradeStrategyMarketMonitorDataWidget(self._strategyCls) self._indWidget = DyStockTradeStrategyMarketMonitorIndWidget(self._eventEngine, self._strategyCls, strategyState) self._dataLabel = QLabel('数据') self._indLabel = QLabel('指示') grid = QGridLayout() grid.setSpacing(0) grid.addWidget(self._dataLabel, 0, 0) grid.addWidget(self._dataWidget, 1, 0) grid.addWidget(self._indLabel, 2, 0) grid.addWidget(self._indWidget, 3, 0) grid.setRowStretch(0, 1) grid.setRowStretch(1, 30) grid.setRowStretch(2, 1) grid.setRowStretch(3, 30) self.setLayout(grid) # set menu for labels self._dataLabel.setContextMenuPolicy(Qt.CustomContextMenu) self._dataLabel.customContextMenuRequested.connect(self._showLabelContextMenu) self._indLabel.setContextMenuPolicy(Qt.CustomContextMenu) self._indLabel.customContextMenuRequested.connect(self._showLabelContextMenu) self._labelMenu = QMenu(self) action = QAction('叠加', self) action.triggered.connect(self._overlapAct) self._labelMenu.addAction(action) def _stockMarketMonitorUiHandler(self, event): if 'data' in event.data: data = event.data['data']['data'] new = event.data['data']['new'] strategyCls = event.data['class'] if strategyCls.maxUiDataRowNbr is not None: data = data[:strategyCls.maxUiDataRowNbr] self._dataWidget.update(data, new) if 'ind' in event.data: self._indWidget.update(event.data['ind']) def _signalEmitWrapper(self, event): """ !!!Note: The value of signal.emit will always be changed each time you getting. """ self.signal.emit(event) def _registerEvent(self): self.signal.connect(self._stockMarketMonitorUiHandler) self._eventEngine.register(DyEventType.stockMarketMonitorUi + self._strategyCls.name, self._signalEmitWrapper) def _unregisterEvent(self): self.signal.disconnect(self._stockMarketMonitorUiHandler) self._eventEngine.unregister(DyEventType.stockMarketMonitorUi + self._strategyCls.name, self._signalEmitWrapper) def closeEvent(self, event): self._dataWidget.close() self._indWidget.close() self._unregisterEvent() return super().closeEvent(event) def _showLabelContextMenu(self, position): self._labelMenu.popup(QCursor.pos()) def _overlapAct(self): grid = self.layout() # remove self._dataLabel.setText('') self._indLabel.setText('') grid.removeWidget(self._dataLabel) grid.removeWidget(self._dataWidget) grid.removeWidget(self._indLabel) grid.removeWidget(self._indWidget) # add self._tabWidget = QTabWidget() self._tabWidget.addTab(self._dataWidget, '数据') self._tabWidget.addTab(self._indWidget, '指示') grid.addWidget(self._tabWidget, 0, 0) grid.setRowStretch(0, 1) grid.setRowStretch(1, 0) grid.setRowStretch(2, 0) grid.setRowStretch(3, 0) # 设置Tab右键菜单事件 tabBar = self._tabWidget.tabBar() tabBar.setContextMenuPolicy(Qt.CustomContextMenu) tabBar.customContextMenuRequested.connect(self._showTabContextMenu) # 创建TabBar菜单 self._tabBarMenu = QMenu(self) action = QAction('平铺', self) action.triggered.connect(self._flatAct) self._tabBarMenu.addAction(action) def _showTabContextMenu(self, position): self._tabBarMenu.popup(QCursor.pos()) def _flatAct(self): grid = self.layout() # remove self._tabWidget.removeTab(0) self._tabWidget.removeTab(0) grid.removeWidget(self._tabWidget) self._tabWidget.hide() # add self._dataLabel.setText('数据') self._indLabel.setText('指示') grid.addWidget(self._dataLabel, 0, 0) grid.addWidget(self._dataWidget, 1, 0) grid.addWidget(self._indLabel, 2, 0) grid.addWidget(self._indWidget, 3, 0) self._dataWidget.show() self._indWidget.show() grid.setRowStretch(0, 1) grid.setRowStretch(1, 30) grid.setRowStretch(2, 1) grid.setRowStretch(3, 30)
class ObserverWindow(QMainWindow): messageReceived = pyqtSignal(str, str, str, str, str) newFilterSettingsApplied = pyqtSignal(str) resetDisplays = pyqtSignal() #__________________________________________________________________ def __init__(self, client, logger): super(ObserverWindow, self).__init__() self._connectionState = None self._countUntitledDisplay = 0 self._displays = [] self._session = 'Default session' self._host = 'localhost' self._port = 1883 self._rootTopic = '' self._logger = logger self._logger.info(self.tr("Started")) reg = QSettings() if "current session" in reg.childKeys() and reg.value( "current session", '').strip() and reg.value( "current session", '').strip() in reg.childGroups(): self._session = reg.value("current session") self._logger.info(self.tr("Current session : ") + self._session) if self._session not in reg.childGroups(): reg.beginGroup(self._session) reg.setValue("host", self._host) reg.setValue("port", self._port) reg.setValue("root topic", self._rootTopic) reg.endGroup() else: reg.beginGroup(self._session) self._host = reg.value("host", 'localhost') try: self._port = reg.value("port", 1883, type=int) except: pass self._rootTopic = reg.value("root topic", '') reg.endGroup() if "current session" in reg.childKeys() and not reg.value( "current session", '') in reg.childGroups(): reg.remove("current session") self._mqttSwitchingConnection = False self._mqttClient = client self._mqttServerHost = self._host self._mqttServerPort = self._port self._mqttRootTopic = self._rootTopic self._mqttSubTopic = '#' if self._rootTopic: self._mqttSubTopic = self._rootTopic + '/#' QApplication.desktop().screenCountChanged.connect(self.restoreWindow) QApplication.desktop().resized.connect(self.restoreWindow) self.setWindowTitle(self._session) self.setWindowIcon(QIcon(':/view-eye.svg')) self._tabWidget = QTabWidget() self._cloudLabel = QLabel() self._connectionStateLabel = QLabel() self.builUi() reg.beginGroup(self._session) inbox = reg.value("param inbox", 'inbox') outbox = reg.value("param outbox", 'outbox') regexInbox = reg.value("regex inbox", r'^%ROOT%/(?P<correspondent>.+)/%INBOX%$') regexOutbox = reg.value("regex outbox", r'^%ROOT%/(?P<correspondent>.+)/%OUTBOX%$') regexDefault = reg.value("regex default", r'.*/(?P<correspondent>[^/]+)/[^/]+$') reg.endGroup() regexInbox = regexInbox.replace("%ROOT%", self._rootTopic).replace( "%INBOX%", inbox) regexOutbox = regexOutbox.replace("%ROOT%", self._rootTopic).replace( "%OUTBOX%", outbox) self._topicRegexInbox = None try: self._topicRegexInbox = re.compile(regexInbox) except Exception as e: self._logger.error(self.tr("Failed to compile inbox regex")) self._logger.debug(e) self._topicRegexOutbox = None try: self._topicRegexOutbox = re.compile(regexOutbox) except Exception as e: self._logger.error(self.tr("Failed to compile outbox regex")) self._logger.debug(e) self._topicRegexDefault = None try: self._topicRegexDefault = re.compile(regexDefault) except Exception as e: self._logger.error( self.tr("Failed to compile topic default regex")) self._logger.debug(e) self.addDisplay(self.tr("All messsages")) reg.beginGroup(self._session) for i in reg.childGroups(): self.addDisplay(i) reg.endGroup() self.changeConnectionState(ConnectionState.DISCONNECTED) self._logger.info("{0} : {1}".format(self.tr("MQTT server host"), self._mqttServerHost)) self._logger.info("{0} : {1}".format(self.tr("MQTT server port"), self._mqttServerPort)) self._logger.info("{0} : {1}".format( self.tr("MQTT clientid"), self._mqttClient._client_id.decode("latin1"))) Timer(0, self.layoutLoadSettings).start() Timer(0, self.start).start() #__________________________________________________________________ @pyqtSlot() def addDisplay(self, title=None): if not title: if self._countUntitledDisplay: title = "{0} ({1})".format(self.tr("New observation"), self._countUntitledDisplay) else: title = self.tr("New observation") self._countUntitledDisplay = self._countUntitledDisplay + 1 reg = QSettings() reg.beginGroup(self._session) if title not in reg.childGroups(): reg.beginGroup(title) reg.setValue("display", int(TopicDisplay.EMPHASIZEDCORRESPONDENT)) reg.endGroup() reg.endGroup() reg.sync() root_observation = not len(self._displays) new_display = ObserverDisplayWidget(title, self._logger, self._session, self._mqttServerHost, self._mqttServerPort, self._mqttRootTopic, remove=not root_observation, filter=not root_observation) new_display.addObservation.connect(self.addDisplay) new_display.delObservation.connect(self.removeCurrentDisplay) self.newFilterSettingsApplied.connect(new_display.applyFilterSettings) self._displays.append(new_display) self._tabWidget.addTab(new_display, title) self.resetDisplays.connect(new_display.reset) self.messageReceived.connect(new_display.processMessage) new_display.newFilterSettings.connect(self.applyFilterSettings) new_display.resetWills.connect(self.applyResetWills) self._tabWidget.setCurrentWidget(new_display) self._logger.info("{0} : {1}".format(self.tr("Added observation"), title)) dspmsg = '' for d in self._displays: if dspmsg: dspmsg += ' | ' else: dspmsg = 'Displays : ' dspmsg += d.title() self._logger.debug(dspmsg) #__________________________________________________________________ @pyqtSlot(dict) def applyFilterSettings(self, filter): try: display = None for d in self._displays: if d.title() == filter["observation"]: display = d if not display: self._logger.error( self.tr("Observation not found in displays : ") + filter["observation"]) else: if filter["name"] != filter["observation"]: for d in self._displays: if d.title() == filter["name"]: self._logger.warning( self.tr("Can't rename observation '") + filter["observation"] + self.tr("' : '") + filter["name"] + self.tr("' already exists")) msgbox = QMessageBox() msgbox.setWindowTitle(self.tr("Observer")) msgbox.setWindowIcon( QIcon(':/magnifier-black.svg')) msgbox.setText( self.tr("Ignore apply filter !") + "<br><br><i>" + self. tr("Can't rename observation (name already in use)." ) + "</i><br>") msgbox.setStandardButtons(QMessageBox.Close) msgbox.setAttribute(Qt.WA_DeleteOnClose) msgbox.setWindowFlags( msgbox.windowFlags() & ~Qt.WindowContextHelpButtonHint) msgbox.button(QMessageBox.Close).setText( self.tr("Close")) msgbox.move(self.pos() + QPoint(40, 40)) msgbox.exec() return display.setTitle(filter["name"]) self._tabWidget.setTabText(self._tabWidget.currentIndex(), filter["name"]) reg = QSettings() reg.beginGroup(self._session) reg.remove(filter["observation"]) reg.endGroup() reg = QSettings() reg.beginGroup(self._session) reg.beginGroup(filter["name"]) reg.setValue("display", int(filter["display"])) reg.setValue("filter 1", filter["filter 1"]) reg.setValue("filter 2", filter["filter 2"]) reg.setValue("filter 3", filter["filter 3"]) reg.setValue("filter 4", filter["filter 4"]) reg.setValue("buffer size", filter["buffer size"]) reg.remove("Hidden correspondents") reg.beginGroup("Hidden correspondents") for p in filter["hidden correspondents"]: reg.setValue(p.replace('/', '\\'), '') reg.endGroup() reg.remove("Hidden topics") reg.beginGroup("Hidden topics") for t in filter["hidden topics"]: reg.setValue(t.replace('/', '\\'), '') reg.endGroup() reg.endGroup() reg.endGroup() reg.sync() self.newFilterSettingsApplied.emit(filter["name"]) except Exception as e: self._logger.error("Failed to apply filter settings") self._logger.debug(e) #__________________________________________________________________ @pyqtSlot(list) def applyResetWills(self, topics): for topic in topics: if self._connectionState == ConnectionState.CONNECTED: try: (result, mid) = self._mqttClient.publish(topic, '', qos=0, retain=True) self._logger.info("{0} {1} (mid={2})".format( self.tr("MQTT sending '' to clear will for "), topic, mid)) except Exception as e: self._logger.info("{0} {1} (mid={2})".format( self.tr("MQTT failed to send '' to clear will for "), topic, mid)) self._logger.debug(e) else: self._logger.info("{0} {1}".format( self.tr("MQTT failed to reset will (disconnected) for "), topic)) #__________________________________________________________________ def builUi(self): mw = QWidget() self.setCentralWidget(mw) main_layout = QVBoxLayout(mw) main_layout.setSpacing(12) cloud_image = QLabel() cloud_image.setPixmap(QIcon(":/cloud-data.svg").pixmap(QSize(36, 36))) cloud_image.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) cloud = self._mqttServerHost + ':' + str(self._mqttServerPort) if self._mqttRootTopic: cloud += '/' + self._mqttRootTopic + '/#' else: cloud += '/#' self._cloudLabel.setText(cloud) font = self._cloudLabel.font() font.setPixelSize(12) font.setBold(True) self._cloudLabel.setFont(font) settings_button = QPushButton() settings_button.setIcon(QIcon(":/settings.svg")) settings_button.setFlat(True) settings_button.setToolTip(self.tr("Settings")) settings_button.setIconSize(QSize(16, 16)) settings_button.setFixedSize(QSize(24, 24)) settings_button.setStyleSheet("QPushButton { padding-bottom: 4px }") self._connectionStateLabel = QLabel() self._connectionStateLabel.setPixmap( QIcon(":/led-circle-grey.svg").pixmap(QSize(24, 24))) self._connectionStateLabel.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) header_layout = QHBoxLayout() header_layout.addWidget(cloud_image) header_layout.addWidget(self._cloudLabel) header_layout.addStretch() header_layout.addWidget(settings_button) header_layout.addWidget(self._connectionStateLabel) main_layout.addLayout(header_layout) main_layout.addWidget(self._tabWidget) settings_button.pressed.connect(self.settings) #__________________________________________________________________ def changeConnectionState(self, state): self._connectionState = state if state == ConnectionState.CONNECTED: self._connectionStateLabel.setPixmap( QIcon(":/led-circle-green.svg").pixmap(QSize(24, 24))) self._connectionStateLabel.setToolTip(self.tr("Connected")) elif state == ConnectionState.CONNECTING: self._connectionStateLabel.setPixmap( QIcon(":/led-circle-yellow.svg").pixmap(QSize(24, 24))) self._connectionStateLabel.setToolTip(self.tr("Connecting")) elif state == ConnectionState.DISCONNECTED: self._connectionStateLabel.setPixmap( QIcon(":/led-circle-red.svg").pixmap(QSize(24, 24))) self._connectionStateLabel.setToolTip(self.tr("Disconnected")) else: self._connectionStateLabel.setPixmap( QIcon(":/led-circle-grey.svg").pixmap(QSize(24, 24))) self._connectionStateLabel.setToolTip("") #__________________________________________________________________ def closeEvent(self, event): self._logger.info(self.tr("Done")) #__________________________________________________________________ def layoutLoadSettings(self): reg = QSettings() pos = reg.value("position", QPoint(200, 200)) size = reg.value("size", QSize(400, 400)) self.move(pos) self.resize(size) #__________________________________________________________________ def layoutSaveSettings(self): reg = QSettings() reg.setValue("position", self.pos()) reg.setValue("size", self.size()) reg.sync() #__________________________________________________________________ def moveEvent(self, event): if self.isVisible(): Timer(0, self.layoutSaveSettings).start() #__________________________________________________________________ def mqttConnect(self): self._logger.info(self.tr("May connect to MQTT server...")) if self._mqttSwitchingConnection and self._connectionState == ConnectionState.DISCONNECTED: self._mqttServerHost = self._host self._mqttServerPort = self._port self._mqttRootTopic = self._rootTopic if self._rootTopic: self._mqttSubTopic = self._rootTopic + '/#' else: self._mqttSubTopic = '#' cloud = self._mqttServerHost + ':' + str(self._mqttServerPort) if self._mqttRootTopic: cloud += '/' + self._mqttRootTopic + '/#' else: cloud += '/#' self._cloudLabel.setText(cloud) clientid = "Observer/" + QUuid.createUuid().toString() self._logger.debug("MQTT clientid %s", clientid) self._mqttClient.reinitialise(client_id=clientid, clean_session=True, userdata=None) self._logger.info(self.tr("MQTT client reinitialised")) self._logger.info("{0} : {1}".format(self.tr("MQTT server host"), self._mqttServerHost)) self._logger.info("{0} : {1}".format(self.tr("MQTT server port"), self._mqttServerPort)) self._logger.info("{0} : {1}".format( self.tr("MQTT clientid"), self._mqttClient._client_id.decode("latin1"))) self._mqttClient.on_connect = self.mqttOnConnect self._mqttClient.on_disconnect = self.mqttOnDisconnect self._mqttClient.on_log = self.mqttOnLog self._mqttClient.on_message = self.mqttOnMessage self._mqttClient.on_publish = self.mqttOnPublish self._mqttClient.on_subscribe = self.mqttOnSubscribe self._mqttClient.on_unsubscribe = self.mqttOnUnsubscribe self._mqttSwitchingConnection = False self._mqttClient.loop_start() if self._connectionState == ConnectionState.CONNECTED or self._connectionState == ConnectionState.CONNECTING: self._logger.info( self.tr("MQTT connect ignored (already ongoing)")) else: self._logger.info(self.tr("Connect to MQTT server")) try: self._mqttClient.connect(self._mqttServerHost, port=self._mqttServerPort, keepalive=MQTT_KEEPALIVE) self.changeConnectionState(ConnectionState.CONNECTING) self._logger.info(self.tr("MQTT connecting")) except Exception as e: self.changeConnectionState(ConnectionState.DISCONNECTED) self._logger.warning( self.tr("Failed to connect to MQTT server")) self._logger.debug(e) Timer(15.000, self.mqttConnect).start() self._logger.debug("Connection state = " + str(self._connectionState)) #__________________________________________________________________ def mqttDisconnect(self): try: self._mqttClient.disconnect() except Exception as e: self._logger.error(self.tr("MQTT disconnection call failed")) self._logger.debug(e) #__________________________________________________________________ def mqttOnConnect(self, client, userdata, flags, rc): self._logger.debug("Connected to MQTT server with result code: " + str(rc) + " and flags: ", flags) # flags is dict if rc == 0: self._logger.info(self.tr("MQTT connected")) self.changeConnectionState(ConnectionState.CONNECTED) mydata = { 'host': self._mqttServerHost, 'port': self._mqttServerPort } self._mqttClient.user_data_set(str(mydata)) try: (result, mid) = self._mqttClient.subscribe(self._mqttSubTopic) self._logger.info("{0} {1} : {2}".format( self.tr("MQTT subscribing to"), mid, self._mqttSubTopic)) except Exception as e: self._logger.error(self.tr("MQTT subscribe call failed")) self._logger.debug(e) elif rc == 1: self._logger.warning( self. tr("MQTT failed to connect : connection refused - incorrect protocol version" )) elif rc == 2: self._logger.warning( self. tr("MQTT failed to connect : connection refused - invalid client identifier" )) elif rc == 3: self._logger.warning( self. tr("MQTT failed to connect : connection refused - server unavailable" )) elif rc == 4: self._logger.warning( self. tr("MQTT failed to connect : connection refused - bad username or password" )) elif rc == 5: self._logger.warning( self. tr("MQTT failed to connect : connection refused - not authorised" )) else: self._logger.warning("{0} : {1}".format( self.tr("MQTT failed to connect : return code"), rc)) self._logger.debug("Connection state = " + str(self._connectionState)) #__________________________________________________________________ def mqttOnDisconnect(self, client, userdata, rc): self.changeConnectionState(ConnectionState.DISCONNECTED) if self._mqttSwitchingConnection: self._logger.info( self.tr( "Disconnected from MQTT server (switching connection)")) Timer(0, self.mqttConnect).start() return else: self._logger.info(self.tr("Disconnected from MQTT server")) serv = '' if isinstance(userdata, str): try: mydata = eval(userdata) if isinstance(mydata, dict) and 'host' in mydata and 'port' in mydata: serv = mydata['host'] + ':' + str(mydata['port']) except Exception as e: self._logger.info( self.tr("MQTT client userdata not as expected")) self._logger.debug(e) if rc == 0: if serv: self._logger.info("{0} {1}".format( self.tr("MQTT disconnected on request from"), serv)) else: self._logger.info(self.tr("MQTT disconnected on request")) else: Timer(15.000, self.mqttConnect).start() if serv: self._logger.warning("{0}{1} {2} {3}".format( self.tr("MQTT disconnected with rc="), rc, self.tr("from"), serv)) else: self._logger.warning("{0}{1}".format( self.tr("MQTT disconnected with rc="), rc)) self._logger.debug("Connection state = " + str(self._connectionState)) #__________________________________________________________________ def mqttOnLog(self, client, userdata, level, buf): self._logger.debug("MQTT log level {0} : {1}".format(level, buf)) #__________________________________________________________________ def mqttOnMessage(self, client, userdata, msg): if self._mqttSwitchingConnection: self._logger.info( self.tr("Ignore MQTT message (switching connection)")) return message = None try: message = msg.payload.decode(encoding="utf-8", errors="strict") except: pass if not message: self._logger.warning("{0} {1}".format( self.tr("MQTT message decoding failed on"), msg.topic)) return self._logger.debug('Message: ' + message + ' in ' + msg.topic) direction = "—" correspondent = None if self._topicRegexInbox: if "correspondent" in self._topicRegexInbox.pattern: try: m = re.match(self._topicRegexInbox, msg.topic) if m: match = m.groupdict() if match["correspondent"]: correspondent = match["correspondent"] direction = "→" except Exception as e: self._logger.debug(e) else: self._logger.warning( self.tr("No 'correspondent' field in inbox regex : ") + self._topicRegexInbox.pattern) if not correspondent and self._topicRegexOutbox: if "correspondent" in self._topicRegexOutbox.pattern: try: m = re.match(self._topicRegexOutbox, msg.topic) if m: match = m.groupdict() if match["correspondent"]: correspondent = match["correspondent"] direction = "←" except Exception as e: self._logger.debug(e) else: self._logger.warning( self.tr("No 'correspondent' field in outbox regex : ") + self._topicRegexInbox.pattern) if not correspondent and self._topicRegexDefault: if "correspondent" in self._topicRegexDefault.pattern: try: m = re.match(self._topicRegexDefault, msg.topic) if m: match = m.groupdict() if match["correspondent"]: correspondent = match["correspondent"] except Exception as e: self._logger.debug(e) else: self._logger.warning( self.tr("No 'correspondent' field in default regex : ") + self._topicRegexInbox.pattern) if not correspondent: self._logger.warning( self.tr("No correspondent defined for topic : ") + msg.topic) now = time.time() msec = repr(now).split('.')[1][:3] timestamp = time.strftime("[%d/%m/%Y %H:%M:%S.{}]".format(msec), time.localtime(now)) self.messageReceived.emit(correspondent, msg.topic, message, timestamp, direction) #__________________________________________________________________ def mqttOnPublish(self, client, userdata, mid): self._logger.debug("userdata=%s mid=%s", userdata, mid) self._logger.info("{0} : mid={1}".format( self.tr("MQTT published"), mid)) # mid is a number (message id) #__________________________________________________________________ def mqttOnSubscribe(self, client, userdata, mid, granted_qos): self._logger.debug("mid=%s granted_qos=%s", mid, granted_qos) # granted_qos is (2,) self._logger.info("{0} : {1} {2} {3}".format( self.tr("MQTT susbcribed to"), mid, self.tr("with QoS"), granted_qos)) # mid is a number (count) #__________________________________________________________________ def mqttOnUnsubscribe(self, client, userdata, mid): self._logger.debug("mid=%s", mid) self._logger.info( "{0} : {1}".format(self.tr("MQTT unsusbcribed from"), mid)) # mid is a number (message id) if self._mqttSwitchingConnection: Timer(0, self.mqttDisconnect).start() #__________________________________________________________________ def mqttReconnect(self): self._logger.info(self.tr("MQTT reconnecting")) if self._mqttSwitchingConnection: self._logger.info( self.tr("Ignore MQTT reconnecting (switching connection)")) return try: self._mqttClient.reconnect() except Exception as e: self._logger.error(self.tr("MQTT reconnection call failed")) Timer(15.000, self.mqttConnect).start() self._logger.debug(e) #__________________________________________________________________ @pyqtSlot() def reload(self): reg = QSettings() if "current session" in reg.childKeys() and reg.value( "current session", '').strip() and reg.value( "current session", '').strip() in reg.childGroups(): self._session = reg.value("current session") self._logger.info(self.tr("Current session : ") + self._session) self.setWindowTitle(self._session) if self._session not in reg.childGroups(): reg.beginGroup(self._session) reg.setValue("host", self._host) reg.setValue("port", self._port) reg.setValue("root topic", self._rootTopic) reg.endGroup() else: reg.beginGroup(self._session) self._host = reg.value("host", 'localhost') try: self._port = reg.value("port", 1883, type=int) except: pass self._rootTopic = reg.value("root topic", '') reg.endGroup() if "current session" in reg.childKeys() and not reg.value( "current session", '') in reg.childGroups(): reg.remove("current session") self._mqttSwitchingConnection = False self._mqttSwitchingSubscription = False if self._host != self._mqttServerHost or self._port != self._mqttServerPort: self._mqttSwitchingConnection = True elif self._rootTopic != self._mqttRootTopic: self._mqttSwitchingSubscription = True self._mqttServerHost = self._host self._mqttServerPort = self._port self._mqttRootTopic = self._rootTopic self._mqttSubTopic = '#' if self._rootTopic: self._mqttSubTopic = self._rootTopic + '/#' reg.beginGroup(self._session) inbox = reg.value("param inbox", 'inbox') outbox = reg.value("param outbox", 'outbox') regexInbox = reg.value("regex inbox", r'^%ROOT%/(?P<correspondent>.+)/%INBOX%$') regexOutbox = reg.value("regex outbox", r'^%ROOT%/(?P<correspondent>.+)/%OUTBOX%$') regexDefault = reg.value("regex default", r'.*/(?P<correspondent>[^/]+)/[^/]+$') reg.endGroup() regexInbox = regexInbox.replace("%ROOT%", self._rootTopic).replace( "%INBOX%", inbox) regexOutbox = regexOutbox.replace("%ROOT%", self._rootTopic).replace( "%OUTBOX%", outbox) self._topicRegexInbox = None try: self._topicRegexInbox = re.compile(regexInbox) except Exception as e: self._logger.error(self.tr("Failed to compile inbox regex")) self._logger.debug(e) self._topicRegexOutbox = None try: self._topicRegexOutbox = re.compile(regexOutbox) except Exception as e: self._logger.error(self.tr("Failed to compile outbox regex")) self._logger.debug(e) self._topicRegexDefault = None try: self._topicRegexDefault = re.compile(regexDefault) except Exception as e: self._logger.error( self.tr("Failed to compile topic default regex")) self._logger.debug(e) index = self._tabWidget.currentIndex() current = self._tabWidget.currentWidget() for index in (1, self._tabWidget.count()): try: self._displays.remove(self._tabWidget.widget(index)) self._tabWidget.widget(index).deleteLater() self._tabWidget.removeTab(index) except Exception as e: self._logger.error( self. tr("Failed to remove observation : not in display list (index=" ) + str(index) + self.tr(")")) self._logger.debug(e) dspmsg = '' for d in self._displays: if dspmsg: dspmsg += ' | ' else: dspmsg = 'Displays : ' dspmsg += d.title() self._logger.debug(dspmsg) reg.beginGroup(self._session) for i in reg.childGroups(): self.addDisplay(i) reg.endGroup() QCoreApplication.processEvents() if self._mqttSwitchingConnection: self.switchConnection() elif self._mqttSwitchingSubscription: self.switchSubscription() #__________________________________________________________________ @pyqtSlot() def removeCurrentDisplay(self): index = self._tabWidget.currentIndex() current = self._tabWidget.currentWidget() if index > 0 and current == self.sender(): try: title = self._tabWidget.tabText(index) self._tabWidget.removeTab(index) if current in self._displays: self._displays.remove(self.sender()) self.sender().deleteLater() self._logger.info("{0} : {1}".format( self.tr("Remove observation"), title)) reg = QSettings() reg.beginGroup(self._session) reg.remove(title) reg.endGroup() reg.sync() else: self._logger.warning( self. tr("Failed to remove observation : not in display list (index=" ) + str(index) + self.tr(")")) except Exception as e: self._logger.error( self. tr("Failed to remove observation : not in display list (index=" ) + str(index) + self.tr(")")) self._logger.debug(e) dspmsg = '' for d in self._displays: if dspmsg: dspmsg += ' | ' else: dspmsg = 'Displays : ' dspmsg += d.title() self._logger.debug(dspmsg) #__________________________________________________________________ @pyqtSlot() def restoreWindow(self): self.resize(QSize(400, 400)) self.move(QPoint(200, 200)) #__________________________________________________________________ def resizeEvent(self, event): if self.isVisible(): Timer(0, self.layoutSaveSettings).start() #__________________________________________________________________ @pyqtSlot() def settings(self): dlg = ObserverSettingsDialog(self._logger, self._session) dlg.move(self.pos() + QPoint(20, 20)) dlg.correspondentRegex.connect(self.settingsRegex) dlg.reloadSession.connect(self.reload) dlg.exec() reg = QSettings() reg.beginGroup(self._session) self._host = reg.value("host", 'localhost') try: self._port = reg.value("port", 1883, type=int) except: self._port = 1883 self._rootTopic = reg.value("root topic", '') reg.endGroup() if self._host != self._mqttServerHost or self._port != self._mqttServerPort: self.switchConnection() elif self._rootTopic != self._mqttRootTopic: self.switchSubscription() #__________________________________________________________________ @pyqtSlot() def settingsRegex(self): dlg = ObserverRegexDialog(self._logger, self._session) dlg.move(self.pos() + QPoint(20, 20)) dlg.exec() reg = QSettings() reg.beginGroup(self._session) inbox = reg.value("param inbox", 'inbox') outbox = reg.value("param outbox", 'outbox') regexInbox = reg.value("regex inbox", r'^%ROOT%/(?P<correspondent>.+)/%INBOX%$') regexOutbox = reg.value("regex outbox", r'^%ROOT%/(?P<correspondent>.+)/%OUTBOX%$') regexDefault = reg.value("regex default", r'.*/(?P<correspondent>[^/]+)/[^/]+$') reg.endGroup() regexInbox = regexInbox.replace("%ROOT%", self._rootTopic).replace( "%INBOX%", inbox) regexOutbox = regexOutbox.replace("%ROOT%", self._rootTopic).replace( "%OUTBOX%", outbox) self._topicRegexInbox = None try: self._topicRegexInbox = re.compile(regexInbox) except Exception as e: self._logger.error( self.tr("Failed to compile inbox regex :") + regexInbox) self._logger.debug(e) self._topicRegexOutbox = None try: self._topicRegexOutbox = re.compile(regexOutbox) except Exception as e: self._logger.error( self.tr("Failed to compile outbox regex :") + regexOutbox) self._logger.debug(e) self._topicRegexDefault = None try: self._topicRegexDefault = re.compile(regexDefault) except Exception as e: self._logger.error( self.tr("Failed to compile topic default regex :") + regexDefault) self._logger.debug(e) #__________________________________________________________________ def start(self): try: self._mqttClient.on_connect = self.mqttOnConnect self._mqttClient.on_disconnect = self.mqttOnDisconnect self._mqttClient.on_log = self.mqttOnLog self._mqttClient.on_message = self.mqttOnMessage self._mqttClient.on_publish = self.mqttOnPublish self._mqttClient.on_subscribe = self.mqttOnSubscribe self._mqttClient.on_unsubscribe = self.mqttOnUnsubscribe Timer(0, self.mqttConnect).start() except: self._logger.error( self.tr("Can't start MQTT (check definitions in .INI)")) msgbox = QMessageBox() msgbox.setWindowTitle(self.tr("Observer")) msgbox.setWindowIcon(QIcon(':/view-eye.svg')) msgbox.setText( self.tr("Failed to set MQTT client !") + "<br><br><i>" + self.tr("Application will be closed.") + "</i><br>") msgbox.setStandardButtons(QMessageBox.Close) msgbox.setAttribute(Qt.WA_DeleteOnClose) msgbox.setWindowFlags(msgbox.windowFlags() & ~Qt.WindowContextHelpButtonHint) msgbox.button(QMessageBox.Close).setText(self.tr("Close")) msgbox.resize(QSize(400, 300)) msgbox.exec() self._logger.info(self.tr("Done")) Timer(0, QCoreApplication.quit).start() self._logger.debug("Connection state = " + str(self._connectionState)) #__________________________________________________________________ def switchConnection(self): self._mqttSwitchingConnection = True self.resetDisplays.emit() current_topic = self._mqttSubTopic if self._connectionState == ConnectionState.CONNECTED or self._connectionState == ConnectionState.CONNECTING: try: (result, mid) = self._mqttClient.unsubscribe(current_topic) self._logger.info("{0} {1} : {2}".format( self.tr("MQTT unsubscribing from"), mid, current_topic)) except Exception as e: self._logger.debug(e) Timer(0, self.mqttDisconnect).start() else: Timer(0, self.mqttConnect).start() self._logger.debug("Connection state = " + str(self._connectionState)) #__________________________________________________________________ def switchSubscription(self): self.resetDisplays.emit() current_topic = self._mqttSubTopic self._mqttRootTopic = self._rootTopic if self._rootTopic: self._mqttSubTopic = self._rootTopic + '/#' else: self._mqttSubTopic = '#' cloud = self._mqttServerHost + ':' + str(self._mqttServerPort) if self._mqttRootTopic: cloud += '/' + self._mqttRootTopic + '/#' else: cloud += '/#' self._cloudLabel.setText(cloud) self.resetDisplays.emit() if self._connectionState == ConnectionState.CONNECTED: try: (result, mid) = self._mqttClient.unsubscribe(current_topic) self._logger.info("{0} {1} : {2}".format( self.tr("MQTT unsubscribing from"), mid, current_topic)) (result, mid) = self._mqttClient.subscribe(self._mqttSubTopic) self._logger.info("{0} {1} : {2}".format( self.tr("MQTT subscribing to"), mid, self._mqttSubTopic)) except Exception as e: self._logger.debug(e) else: Timer(0, self.mqttReconnect).start() self._logger.debug("Connection state = " + str(self._connectionState))
class NoteBook(QWidget): def __init__(self, parent, program, filename, spreadsheet, debug=False, progressbar=False, scripting=False): super(QWidget, self).__init__(parent) global debugger debugger = Debug(debug, 'NoteBook:') self.reader = None self.progressbar = progressbar self.spreadsheet = None self.plottingCalculationRequired = True self.analysisCalculationRequired = True self.visualerCalculationRequired = True self.fittingCalculationRequired = True self.debug = debug self.old_tab_index = None self.scripting = scripting self.layout = QVBoxLayout() # The number of tabs before we have scenarios self.tabOffSet = 2 # # Initialize tab screen # self.tabs = QTabWidget(self) self.tabs.currentChanged.connect(self.on_tabs_currentChanged) self.mainTab = MainTab(self, program, filename, spreadsheet, debug=debug) self.settingsTab = SettingsTab(self, debug=debug) if filename != '' and not self.scripting: debugger.print( 'Refreshing settingsTab in notebook initialisation - filename', filename) self.settingsTab.refresh() # # Open more windows # self.scenarios = [] self.scenarios.append(ScenarioTab(self, debug=debug)) self.scenarios[0].setScenarioIndex(0) # # Open the plotting tab # self.plottingTab = PlottingTab(self, debug=debug) if filename != '' and not self.scripting: debugger.print('Refreshing plotting because filename is set') self.plottingTab.refresh() # # Open the Analysis tab # self.analysisTab = AnalysisTab(self, debug=debug) if filename != '' and not self.scripting: debugger.print('Refreshing analysis because filename is set') self.analysisTab.refresh() # # Open the Viewer tab # self.viewerTab = ViewerTab(self, debug=debug) # # Open the Fitter tab # self.fitterTab = FitterTab(self, debug=debug) # # Add tabs # self.tabs.addTab(self.mainTab, 'Main') self.tabs.addTab(self.settingsTab, 'Settings') for i, tab in enumerate(self.scenarios): self.tabs.addTab(tab, 'Scenario ' + str(i + 1)) self.tabs.addTab(self.plottingTab, 'Plotting') self.tabs.addTab(self.analysisTab, 'Analysis') self.tabs.addTab(self.viewerTab, '3D Viewer') self.tabs.addTab(self.fitterTab, 'Fitter') # Add the tab widget self.layout.addWidget(self.tabs) self.setLayout(self.layout) return def addScenario(self, copyFromIndex=-2): debugger.print('Settings for scenario', copyFromIndex) self.plottingCalculationRequired = True self.scenarios.append(ScenarioTab(self, self.debug)) self.scenarios[-1].settings = copy.deepcopy( self.scenarios[copyFromIndex].settings) # debugger.print('Settings for new scenario') self.scenarios[-1].refresh(force=True) for i, scenario in enumerate(self.scenarios): scenario.setScenarioIndex(i) n = len(self.scenarios) self.tabs.insertTab(self.tabOffSet + n - 1, self.scenarios[-1], 'Scenario ' + str(n)) self.tabs.setCurrentIndex(self.tabOffSet + n - 1) return def print_settings(self): # Print the settings of all the settings that have been used to a file settings.py qf = QFileDialog() qf.setWindowTitle('Save the program settings to a file') filename, selection = qf.getSaveFileName() if filename == '': return print('Current settings will be saved to ' + filename) fd = open(filename, 'w') ntabs = 2 + len(self.scenarios) + 4 self.print_tab_settings(self.mainTab, 'mainTab', fd) print('tab.refresh(force=True)', file=fd) self.print_tab_settings(self.settingsTab, 'settingsTab', fd) print('tab.sigmas_cm1 =', self.settingsTab.sigmas_cm1, file=fd) print('tab.refresh(force=True)', file=fd) requireNewScenario = False for i, tab in enumerate(self.scenarios): self.print_tab_settings(tab, 'scenarios[{}]'.format(i), fd, new_scenario=requireNewScenario) print('tab.refresh(force=True)', file=fd) requireNewScenario = True self.print_tab_settings(self.plottingTab, 'plottingTab', fd) print('tab.refresh(force=True)', file=fd) self.print_tab_settings(self.analysisTab, 'analysisTab', fd) print('tab.refresh(force=True)', file=fd) self.print_tab_settings(self.viewerTab, 'viewerTab', fd) print('tab.refresh(force=True)', file=fd) self.print_tab_settings(self.fitterTab, 'fitterTab', fd) print('tab.refresh(force=True)', file=fd) fd.close() return def print_tab_settings(self, tab, title, fd, new_scenario=False): print('#', file=fd) print('#', file=fd) if new_scenario: print('self.notebook.addScenario()', file=fd) print('tab = self.notebook.' + title, file=fd) for item in tab.settings: value = tab.settings[item] if 'str' in str(type(value)): print('tab.settings[\'' + item + '\'] = \'{}\''.format(tab.settings[item]), file=fd) else: print('tab.settings[\'' + item + '\'] = ', tab.settings[item], file=fd) def deleteScenario(self, index): # Don't delete the last scenario if len(self.scenarios) > 1: self.plottingCalculationRequired = True self.tabs.removeTab(self.tabOffSet + index) del self.scenarios[index] for i, scenario in enumerate(self.scenarios): scenario.setScenarioIndex(i) self.tabs.setTabText(self.tabOffSet + i, 'Scenario ' + str(i + 1)) return def refresh(self, force=False): if self.scripting: debugger.print('Notebook aborting refresh because of scripting') return debugger.print('Notebook refresh changed', force) ntabs = 2 + len(self.scenarios) + 4 self.mainTab.refresh(force=force) self.settingsTab.refresh(force=force) for tab in self.scenarios: tab.refresh(force=force) self.tabs.setCurrentIndex(ntabs - 5) self.plottingTab.refresh(force=force) self.tabs.setCurrentIndex(ntabs - 4) self.analysisTab.refresh(force=force) self.tabs.setCurrentIndex(ntabs - 3) self.viewerTab.refresh(force=force) self.tabs.setCurrentIndex(ntabs - 2) self.fitterTab.refresh(force=force) self.tabs.setCurrentIndex(ntabs - 1) def write_spreadsheet(self): debugger.print('Write spreadsheet') self.mainTab.write_spreadsheet() self.settingsTab.write_spreadsheet() self.analysisTab.write_spreadsheet() self.plottingTab.write_spreadsheet() def on_tabs_currentChanged(self, tabindex): debugger.print('Tab index changed', tabindex) if self.scripting: return # See if we have to up date a tab we have left if self.old_tab_index is not None: if self.old_tab_index == 0: self.mainTab.refresh() elif self.old_tab_index == 1: self.settingsTab.refresh() # end if # Number of tabs ntabs = 2 + len(self.scenarios) + 4 if tabindex == ntabs - 1: # fitter tab self.fitterTab.refresh() elif tabindex == ntabs - 2: # viewer tab self.viewerTab.refresh() elif tabindex == ntabs - 3: # analysis tab self.analysisTab.refresh() elif tabindex == ntabs - 4: # plottings tab self.plottingTab.refresh() self.old_tab_index = tabindex def keyPressEvent(self, e): if (e.key() == Qt.Key_S ) and QApplication.keyboardModifiers() and Qt.ControlModifier: print('Control S has been pressed') self.print_settings() elif (e.key() == Qt.Key_C ) and QApplication.keyboardModifiers() and Qt.ControlModifier: print('Control C has been pressed') print('The program will close down') sys.exit()
class Main(QMainWindow): def __init__(self): global downloads_list_file QMainWindow.__init__(self) self.setWindowIcon(QIcon(":/quartz.png")) self.setWindowTitle("Quartz Browser - "+__version__) # Window Properties self.history = [] self.downloads = [] self.confirm_before_quit = True # Create required directories for folder in [configdir, icon_dir, thumbnails_dir]: if not os.path.exists(folder): os.mkdir(folder) # Import and Apply Settings self.setAttribute(Qt.WA_DeleteOnClose) self.settings = QSettings(1, 0, "quartz-browser","Quartz", self) self.opensettings() self.websettings = QWebSettings.globalSettings() self.websettings.setAttribute(QWebSettings.DnsPrefetchEnabled, True) self.websettings.setMaximumPagesInCache(10) self.websettings.setIconDatabasePath(icon_dir) self.websettings.setAttribute(QWebSettings.JavascriptCanOpenWindows, True) self.websettings.setAttribute(QWebSettings.JavascriptCanCloseWindows, True) if webkit.enable_adblock: self.websettings.setUserStyleSheetUrl(QUrl.fromLocalFile(program_dir + 'userContent.css')) # Import Downloads and Bookmarks self.dwnldsmodel = DownloadsModel(self.downloads, QApplication.instance()) self.dwnldsmodel.deleteDownloadsRequested.connect(self.deleteDownloads) imported_downloads = importDownloads(downloads_list_file) for [filepath, url, totalsize, timestamp] in imported_downloads: try : # Check if downloads.txt is valid tymstamp = float(timestamp) except : self.downloads = [] exportDownloads(downloads_list_file, []) print("Error in importing Downloads.") break old_download = Download(networkmanager) old_download.loadDownload(filepath, url, totalsize, timestamp) old_download.datachanged.connect(self.dwnldsmodel.datachanged) self.downloads.append(old_download) self.bookmarks = importBookmarks(configdir+"bookmarks.txt") self.favourites = importFavourites(configdir + 'favourites.txt') # Find and set icon theme name for theme_name in ['Adwaita', 'Gnome', 'Tango']: if os.path.exists('/usr/share/icons/' + theme_name): QIcon.setThemeName(theme_name) break self.initUI() self.resize(1024,714) def initUI(self): ############################### Create Actions ############################## self.loadimagesaction = QAction("Load Images",self) self.loadimagesaction.setCheckable(True) self.loadimagesaction.triggered.connect(self.loadimages) self.javascriptmode = QAction("Enable Javascript",self) self.javascriptmode.setCheckable(True) self.javascriptmode.triggered.connect(self.setjavascript) self.useragent_mode_desktop = QAction("Desktop",self) self.useragent_mode_desktop.setCheckable(True) self.useragent_mode_desktop.triggered.connect(self.setUserAgentDesktop) self.useragent_mode_mobile = QAction("Mobile",self) self.useragent_mode_mobile.setCheckable(True) self.useragent_mode_mobile.triggered.connect(self.setUserAgentMobile) self.useragent_mode_custom = QAction("Custom",self) self.useragent_mode_custom.setCheckable(True) self.useragent_mode_custom.triggered.connect(self.setUserAgentCustom) ################ Add Actions to Menu #################### # This sub-menu sets useragent mode to desktop/mobile/custom self.useragentMenu = QMenu('UserAgent', self) self.useragentMenu.setIcon(QIcon(":/computer.png")) self.useragentMenu.addAction(self.useragent_mode_desktop) self.useragentMenu.addAction(self.useragent_mode_mobile) self.useragentMenu.addAction(self.useragent_mode_custom) # This is main menu self.menu = QMenu(self) self.menu.addAction(QIcon(":/edit-find.png"), "Find Text", self.findmode, "Ctrl+F") self.menu.addAction(QIcon(":/list-add.png"), "Zoom In", self.zoomin, "Ctrl++") self.menu.addAction(QIcon(":/list-remove.png"), "Zoom Out", self.zoomout, "Ctrl+-") self.menu.addAction(QIcon(":/view-fullscreen.png"), "Toggle Fullscreen", self.fullscreenmode, "F11") self.menu.addSeparator() self.menu.addAction(self.loadimagesaction) self.menu.addAction(self.javascriptmode) self.menu.addMenu(self.useragentMenu) self.menu.addAction(QIcon(":/applications-system.png"), "Settings", self.settingseditor, "Ctrl+,") self.menu.addSeparator() self.menu.addAction(QIcon(":/image-x-generic.png"), "Save as Image", self.saveAsImage, "Shift+Ctrl+S") self.menu.addAction(QIcon(":/text-html.png"), "Save as HTML", self.saveashtml, "Ctrl+S") self.menu.addAction(QIcon(":/document-print.png"), "Print to PDF", self.printpage, "Ctrl+P") self.menu.addSeparator() self.menu.addAction(QIcon(":/process-stop.png"), "Quit", self.forceClose, "Ctrl+Q") self.bmk_menu = QMenu(self) self.bmk_menu.addAction(QIcon(':/add-bookmark.png'), 'Add Bookmark', self.addbookmark) self.bmk_menu.addAction(QIcon(':/favourites.png'), 'Add to Home', self.addToFavourites) ############################### Create Gui Parts ############################## self.centralwidget = QWidget(self) self.setCentralWidget(self.centralwidget) grid = QGridLayout(self.centralwidget) grid.setSpacing(1) grid.setContentsMargins(0,2,0,0) self.toolBar = QWidget(self) horLayout = QHBoxLayout(self.toolBar) horLayout.setSpacing(1) horLayout.setContentsMargins(0,2,0,0) self.addtabBtn = QPushButton(QIcon(":/add-tab.png"), "",self) self.addtabBtn.setToolTip("New Tab\n[Ctrl+Tab]") self.addtabBtn.setShortcut("Ctrl+Tab") self.addtabBtn.clicked.connect(self.addTab) self.reload = QPushButton(QIcon(":/refresh.png"), "",self) self.reload.setMinimumSize(35,26) self.reload.setToolTip("Reload/Stop\n [Space]") self.reload.setShortcut("Space") self.reload.clicked.connect(self.Reload) self.back = QPushButton(QIcon(":/prev.png"), "", self) self.back.setToolTip("Previous Page") self.back.setMinimumSize(35,26) self.back.clicked.connect(self.Back) self.forw = QPushButton(QIcon(":/next.png"), "",self) self.forw.setToolTip("Next Page") self.forw.setMinimumSize(35,26) self.forw.clicked.connect(self.Forward) self.homeBtn = QPushButton(QIcon(":/home.png"), "",self) self.homeBtn.setToolTip("Go Home") self.homeBtn.clicked.connect(self.goToHome) self.videoDownloadButton = QPushButton(QIcon(":/video-dwnld.png"), "", self) self.videoDownloadButton.setToolTip("Download this Video") self.videoDownloadButton.clicked.connect(self.downloadVideo) self.videoDownloadButton.hide() self.addbookmarkBtn = QToolButton(self) self.addbookmarkBtn.setIcon(QIcon(":/add-bookmark.png")) self.addbookmarkBtn.setToolTip("Add Bookmark") self.addbookmarkBtn.setMenu(self.bmk_menu) self.addbookmarkBtn.setPopupMode(QToolButton.InstantPopup) self.menuBtn = QToolButton(self) self.menuBtn.setIcon(QIcon(":/menu.png")) self.menuBtn.setMenu(self.menu) self.menuBtn.setPopupMode(QToolButton.InstantPopup) self.bookmarkBtn = QPushButton(QIcon(":/bookmarks.png"), "", self) self.bookmarkBtn.setToolTip("Manage Bookmarks\n [Alt+B]") self.bookmarkBtn.setShortcut("Alt+B") self.bookmarkBtn.clicked.connect(self.managebookmarks) self.historyBtn = QPushButton(QIcon(":/history.png"), "", self) self.historyBtn.setShortcut("Alt+H") self.historyBtn.setToolTip("View History\n [Alt+H]") self.historyBtn.clicked.connect(self.viewhistory) self.downloadsBtn = QPushButton(QIcon(":/download.png"), "", self) self.downloadsBtn.setToolTip("Download Manager") self.downloadsBtn.clicked.connect(self.download_manager) self.find = QPushButton(self) self.find.setText("Find/Next") self.find.clicked.connect(self.findnext) self.find.hide() self.findprev = QPushButton(self) self.findprev.setText("Backward") self.findprev.clicked.connect(self.findback) self.findprev.hide() self.cancelfind = QPushButton(self) self.cancelfind.setText("Cancel") self.cancelfind.clicked.connect(self.cancelfindmode) self.cancelfind.hide() self.pbar = QProgressBar(self) self.pbar.setTextVisible(False) self.pbar.setStyleSheet("QProgressBar::chunk { background-color: #bad8ff; }") pbarLayout = QGridLayout(self.pbar) pbarLayout.setContentsMargins(0,0,0,0) self.line = webkit.UrlEdit(self.pbar) self.line.openUrlRequested.connect(self.Enter) self.line.textEdited.connect(self.urlsuggestions) self.line.downloadRequested.connect(self.download_requested_file) pbarLayout.addWidget(self.line) self.listmodel = QStringListModel(self) self.completer = QCompleter(self.listmodel, self.line) self.completer.setCompletionMode(1) self.completer.setMaxVisibleItems(10) self.line.setCompleter(self.completer) self.statusbar = QLabel(self) self.statusbar.setStyleSheet("QLabel { font-size: 12px; border-radius: 2px; padding: 2px; background: palette(highlight); color: palette(highlighted-text); }") self.statusbar.setMaximumHeight(16) self.statusbar.hide() self.tabWidget = QTabWidget(self) self.tabWidget.setTabsClosable(True) self.tabWidget.setDocumentMode(True) self.tabWidget.tabBar().setExpanding(True) self.tabWidget.tabBar().setElideMode(Qt.ElideMiddle) self.tabWidget.currentChanged.connect(self.onTabSwitch) self.tabWidget.tabCloseRequested.connect(self.closeTab) self.addTab() self.applysettings() # grid.addWidget(self.toolBar, 0,0, 1,1) for widget in [self.addtabBtn, self.back, self.forw, self.reload, self.homeBtn, self.videoDownloadButton, self.pbar, self.find, self.findprev, self.cancelfind, self.addbookmarkBtn, self.menuBtn, self.bookmarkBtn, self.historyBtn, self.downloadsBtn]: horLayout.addWidget(widget) grid.addWidget(self.tabWidget, 1, 0, 1, 1) #------------------------------------------------------------------------------------------ # Must be at the end, otherwise cause segmentation fault # self.status = self.statusBar() def addTab(self, webview_tab=None): """ Creates a new tab and add to QTabWidget applysettings() must be called after adding each tab""" if not webview_tab: webview_tab = webkit.MyWebView(self.tabWidget, networkmanager) webview_tab.windowCreated.connect(self.addTab) webview_tab.loadStarted.connect(self.onLoadStart) webview_tab.loadFinished.connect(self.onLoadFinish) webview_tab.loadProgress.connect(self.onProgress) webview_tab.urlChanged.connect(self.onUrlChange) webview_tab.titleChanged.connect(self.onTitleChange) webview_tab.iconChanged.connect(self.onIconChange) webview_tab.videoListRequested.connect(self.getVideos) webview_tab.page().printRequested.connect(self.printpage) webview_tab.page().downloadRequested.connect(self.download_requested_file) webview_tab.page().unsupportedContent.connect(self.handleUnsupportedContent) webview_tab.page().linkHovered.connect(self.onLinkHover) webview_tab.page().windowCloseRequested.connect(self.closeRequestedTab) self.tabWidget.addTab(webview_tab, "( Untitled )") if self.tabWidget.count()==1: self.tabWidget.tabBar().hide() else: self.tabWidget.tabBar().show() self.tabWidget.setCurrentIndex(self.tabWidget.count()-1) def closeTab(self, index=None): """ Closes tab, hides tabbar if only one tab remains""" if index==None: index = self.tabWidget.currentIndex() widget = self.tabWidget.widget(index) self.tabWidget.removeTab(index) widget.deleteLater() # Auto hide tab bar, when no. of tab widget is one if self.tabWidget.count()==1: self.tabWidget.tabBar().hide() def closeRequestedTab(self): """ Close tab requested by the page """ webview = self.sender().view() index = self.tabWidget.indexOf(webview) self.closeTab(index) def Enter(self): url = self.line.text() if url == 'about:home': self.goToHome() else: self.GoTo(url) def GoTo(self, url): URL = QUrl.fromUserInput(url) self.tabWidget.currentWidget().openLink(URL) self.line.setText(url) self.tabWidget.currentWidget().setFocus() def goToHome(self): self.GoTo(homepage) loop = QEventLoop() QTimer.singleShot(10, loop.quit) loop.exec_() document = self.tabWidget.currentWidget().page().mainFrame().documentElement() gallery = document.findFirst('div') for i, fav in enumerate(self.favourites): title, url, img = fav[0], fav[1], thumbnails_dir+fav[2] child = '<div class="photo"> <a href="{}"><img src="{}"></a><div class="desc">{}</div></div>'.format(url, img, title) gallery.appendInside(child) def onLoadStart(self): webview = self.sender() if webview is self.tabWidget.currentWidget(): self.reload.setIcon(QIcon(":/stop.png")) def onProgress(self, progress): webview = self.sender() if webview is self.tabWidget.currentWidget() and webview.loading: self.pbar.setValue(progress) def onLoadFinish(self, ok): webview = self.sender() if webview is self.tabWidget.currentWidget(): self.reload.setIcon(QIcon(":/refresh.png")) self.pbar.reset() url = self.line.text() self.handleVideoButton(url) def onTabSwitch(self, index): """ Updates urlbox, refresh icon, progress bar on switching tab""" webview = self.tabWidget.currentWidget() if webview.loading == True: self.reload.setIcon(QIcon(":/stop.png")) self.pbar.setValue(webview.progressVal) else: self.reload.setIcon(QIcon(":/refresh.png")) self.pbar.reset() url = webview.url().toString() if url == homepage : url = 'about:home' self.line.setText(url) self.statusbar.hide() self.onIconChange(webview) self.handleVideoButton(url) def onUrlChange(self,url): url = url.toString() if url == homepage : url = 'about:home' webview = self.sender() if webview is self.tabWidget.currentWidget(): self.line.setText(url) self.onIconChange(webview) self.handleVideoButton(url) def onTitleChange(self, title): webview = self.sender() index = self.tabWidget.indexOf(webview) if not title == '': self.tabWidget.tabBar().setTabText(index, title) url = webview.url().toString() for item in self.history: # Removes the old item, inserts new same item on the top if url == item[1]: self.history.remove(item) self.history.insert(0, [title, url]) def onIconChange(self, webview=None): if not webview: webview = self.sender() icon = webview.icon() if icon.isNull(): icon = QIcon(':/quartz.png') if webview is self.tabWidget.currentWidget(): self.line.setIcon(icon) index = self.tabWidget.indexOf(webview) self.tabWidget.setTabIcon(index, icon) def onLinkHover(self, url): if url=="": self.statusbar.hide() return self.statusbar.setText(url) self.statusbar.adjustSize() self.statusbar.show() self.statusbar.move(QPoint(0, self.height()-self.statusbar.height())) def Back(self): self.tabWidget.currentWidget().back() def Forward(self): self.tabWidget.currentWidget().forward() def Reload(self): if self.tabWidget.currentWidget().loading: self.tabWidget.currentWidget().stop() else: if self.line.text() == 'about:home': self.goToHome() else: self.tabWidget.currentWidget().reload() def urlsuggestions(self, text): """ Creates the list of url suggestions for URL box """ suggestions = [] if not webkit.find_mode_on: for [title, url] in self.history: if text in url: suggestions.insert(0, url) for [title, address] in self.bookmarks: if text in address: suggestions.insert(0, address) self.listmodel.setStringList( suggestions ) def handleVideoButton(self, url): if youtube.validYoutubeUrl(url): self.videoDownloadButton.show() return frames = [self.tabWidget.currentWidget().page().mainFrame()] frames += frames[0].childFrames() for frame in frames: video = frame.findFirstElement('video') if not video.isNull(): self.videoDownloadButton.show() return self.videoDownloadButton.hide() ##################### Downloading and Printing ######################## def download_requested_file(self, networkrequest): """ Gets called when the page requests a file to be downloaded """ reply = networkmanager.get(networkrequest) self.handleUnsupportedContent(reply) def handleUnsupportedContent(self, reply, preset_filename=None, page_url=None): """ This is called when url content is a downloadable file. e.g- pdf,mp3,mp4 """ if reply.rawHeaderList() == []: loop = QEventLoop() reply.metaDataChanged.connect(loop.quit) QTimer.singleShot(5000, loop.quit) loop.exec_() if reply.hasRawHeader(b'Location'): URL = QUrl.fromUserInput(str_(reply.rawHeader(b'Location'))) reply.abort() reply = networkmanager.get(QNetworkRequest(URL)) self.handleUnsupportedContent(reply, preset_filename) return for (title, header) in reply.rawHeaderPairs(): print( str_(title) + "-> " + str_(header) ) # copy url to clipboard QApplication.clipboard().setText(reply.url().toString()) # Get filename and mimetype mimetype = None if reply.hasRawHeader(b'Content-Type'): mimetype = str_(reply.rawHeader(b'Content-Type')).split(';')[0] # eg - audio/mpeg; name="" content_name = str_(reply.rawHeader(b'Content-Disposition')) if preset_filename: filename = preset_filename else: filename = filenameFromHeader(content_name) if filename == '': filename = filenameFromUrl(reply.url().toString()) filename = validateFileName(filename, mimetype) # Create downld Confirmation dialog dlDialog = DownloadDialog(self) dlDialog.filenameEdit.setText(filename) # Get filesize if reply.hasRawHeader(b'Content-Length'): filesize = reply.header(1) if filesize >= 1048576 : file_size = "{} M".format(round(float(filesize)/1048576, 2)) elif 1023 < filesize < 1048576 : file_size = "{} k".format(round(float(filesize)/1024, 1)) else: file_size = "{} B".format(filesize) dlDialog.labelFileSize.setText(file_size) # Get filetype and resume support info if mimetype: dlDialog.labelFileType.setText(mimetype) if reply.hasRawHeader(b'Accept-Ranges') or reply.hasRawHeader(b'Content-Range'): dlDialog.labelResume.setText("True") # Execute dialog and show confirmation if dlDialog.exec_()== QDialog.Accepted: filepath = dlDialog.folder + dlDialog.filenameEdit.text() url = reply.url().toString() if self.useexternaldownloader: download_externally(url, self.externaldownloader) reply.abort() reply.deleteLater() return global downloads_list_file newdownload = Download(networkmanager, page_url) newdownload.startDownload(reply, filepath) newdownload.datachanged.connect(self.dwnldsmodel.datachanged) self.downloads.insert(0, newdownload) imported_downloads = importDownloads(downloads_list_file) imported_downloads.insert(0, [filepath, url, str(newdownload.totalsize), newdownload.timestamp]) exportDownloads(downloads_list_file, imported_downloads) else: reply.abort() reply.deleteLater() def download_manager(self): """ Opens download manager dialog """ dialog = QDialog(self) downloads_dialog = Downloads_Dialog() downloads_dialog.setupUi(dialog, self.dwnldsmodel) dialog.exec_() def deleteDownloads(self, timestamps): global downloads_list_file imported_downloads = importDownloads(downloads_list_file) exported_downloads = [] for download in imported_downloads: if download[-1] not in timestamps: exported_downloads.append(download) exportDownloads(downloads_list_file, exported_downloads) def downloadVideo(self): url = self.tabWidget.currentWidget().url().toString() # For youtube videos, parse youtube links in separate thread if youtube.validYoutubeUrl(url): vid_id = parse_qs(urlparse(url).query)['v'][0] ytThread = youtube.YoutubeThread(self) ytThread.ytParseFailed.connect(self.onYtParseFail) ytThread.ytVideoParsed.connect(self.onYtVideoParse) ytThread.finished.connect(ytThread.deleteLater) ytThread.vid_id = vid_id ytThread.start() return # For embeded HTML5 videos self.getVideos() def onYtVideoParse(self, videos): dialog = youtube.YoutubeDialog(videos, self) if dialog.exec_() == 1 : index = abs(dialog.buttonGroup.checkedId())-2 vid = videos[index] reply = networkmanager.get( QNetworkRequest(QUrl.fromUserInput(vid.url)) ) self.handleUnsupportedContent(reply, vid.filename + '.' + vid.extension) def onYtParseFail(self): # Show error on fail to parse youtube QMessageBox.warning(self, "Download Failed !","This Video can not be downloaded") def getVideos(self): dialog = youtube.Media_Dialog(self, self.tabWidget.currentWidget().page(), networkmanager) dialog.downloadRequested.connect(self.handleUnsupportedContent) dialog.exec_() def saveAsImage(self): """ Saves the whole page as PNG/JPG image""" title = self.tabWidget.currentWidget().page().mainFrame().title() title == validateFileName(title) filename = QFileDialog.getSaveFileName(self, "Select Image to Save", downloaddir + title +".jpg", "JPEG Image (*.jpg);;PNG Image (*.png)" )[0] if filename == '': return viewportsize = self.tabWidget.currentWidget().page().viewportSize() contentsize = self.tabWidget.currentWidget().page().mainFrame().contentsSize() self.tabWidget.currentWidget().page().setViewportSize(contentsize) img = QPixmap(contentsize) painter = QPainter(img) self.tabWidget.currentWidget().page().mainFrame().render(painter) painter.end() if img.save(filename): QMessageBox.information(self, "Successful !","Page has been successfully saved as\n"+filename) else: QMessageBox.warning(self, "Saving Failed !","Exporting page to Image hasbeen failed") self.tabWidget.currentWidget().page().setViewportSize(viewportsize) def saveashtml(self): """ Saves current page as HTML , bt does not saves any content (e.g images)""" title = self.tabWidget.currentWidget().page().mainFrame().title() title = validateFileName(title) filename = QFileDialog.getSaveFileName(self, "Enter HTML File Name", downloaddir + title +".html", "HTML Document (*.html)" )[0] if filename == '': return #html = self.tabWidget.currentWidget().page().mainFrame().toHtml() page_URL = self.tabWidget.currentWidget().url() useragent = self.tabWidget.currentWidget().page().userAgentForUrl(page_URL) doc = self.tabWidget.currentWidget().page().mainFrame().documentElement().clone() #doc.setInnerXml(html) SaveAsHtml(networkmanager, doc, filename, page_URL, useragent) def printpage(self, page=None): """ Prints current/requested page """ if not page: page = self.tabWidget.currentWidget().page().currentFrame() printer = QPrinter(QPrinter.HighResolution) printer.setPaperSize(QPrinter.A4) printer.setPageSize(QPrinter.A4) printer.setColorMode(QPrinter.Color) printer.setCreator("Quartz Browser") title = self.tabWidget.currentWidget().page().mainFrame().title() title = validateFileName(title) printer.setDocName(title) printer.setOutputFileName(docdir + title + ".pdf") #printer.setOutputFormat(QPrinter.PdfFormat) print_dialog = QPrintPreviewDialog(printer, self) print_dialog.paintRequested.connect(page.print_) print_dialog.exec_() ################################################################################################## def addToFavourites(self): dialog = QDialog(self) addbmkdialog = Add_Bookmark_Dialog() addbmkdialog.setupUi(dialog) dialog.setWindowTitle('Add to HomePage') addbmkdialog.titleEdit.setMaxLength(31) addbmkdialog.titleEdit.setText(self.tabWidget.currentWidget().page().mainFrame().title()) addbmkdialog.addressEdit.setText(self.line.text()) if (dialog.exec_() == QDialog.Accepted): title = addbmkdialog.titleEdit.text() addr = addbmkdialog.addressEdit.text() imgfile = str(time.time()) + '.jpg' viewportsize = self.tabWidget.currentWidget().page().viewportSize() contentsize = QSize(640, 640) self.tabWidget.currentWidget().page().setViewportSize(contentsize) img = QPixmap(contentsize) painter = QPainter(img) self.tabWidget.currentWidget().page().mainFrame().render(painter, QWebFrame.AllLayers) painter.end() self.tabWidget.currentWidget().page().setViewportSize(viewportsize) icon = img.scaledToWidth(184, 1).copy(0,0, 180, 120) icon.save(thumbnails_dir + imgfile) self.favourites = importFavourites(configdir + 'favourites.txt') self.favourites.append([title, addr, imgfile]) exportFavourites(configdir + 'favourites.txt', self.favourites) def addbookmark(self): """ Opens add bookmark dialog and gets url from url box""" dialog = QDialog(self) addbmkdialog = Add_Bookmark_Dialog() addbmkdialog.setupUi(dialog) addbmkdialog.titleEdit.setText(self.tabWidget.currentWidget().page().mainFrame().title()) addbmkdialog.addressEdit.setText(self.line.text()) if (dialog.exec_() == QDialog.Accepted): url = addbmkdialog.addressEdit.text() bmk = [addbmkdialog.titleEdit.text(), url] self.bookmarks = importBookmarks(configdir+"bookmarks.txt") self.bookmarks.insert(0, bmk) exportBookmarks(configdir+"bookmarks.txt", self.bookmarks) icon = self.tabWidget.currentWidget().icon() if not icon.isNull(): icon.pixmap(16, 16).save(icon_dir + url.split('/')[2] + '.png') def managebookmarks(self): """ Opens Bookmarks dialog """ dialog = QDialog(self) bmk_dialog = Bookmarks_Dialog() bmk_dialog.setupUi(dialog, self.bookmarks, self.favourites) bmk_dialog.bookmarks_table.doubleclicked.connect(self.GoTo) bmk_dialog.favs_table.doubleclicked.connect(self.GoTo) dialog.exec_() if bmk_dialog.bookmarks_table.data_changed: self.bookmarks = bmk_dialog.bookmarks_table.data exportBookmarks(configdir+"bookmarks.txt", self.bookmarks) if bmk_dialog.favs_table.data_changed: self.favourites = bmk_dialog.favs_table.data exportFavourites(configdir+"favourites.txt", self.favourites) def viewhistory(self): """ Open history dialog """ dialog = QDialog(self) history_dialog = History_Dialog() history_dialog.setupUi(dialog, self.history) history_dialog.tableView.doubleclicked.connect(self.GoTo) dialog.exec_() def findmode(self): """ Starts find mode and unhides find buttons""" webkit.find_mode_on = True self.line.clear() self.find.show() self.findprev.show() self.cancelfind.show() self.line.setFocus() def cancelfindmode(self): """ Hides the find buttons, updates urlbox""" webkit.find_mode_on = False self.tabWidget.currentWidget().findText("") self.find.hide() self.findprev.hide() self.cancelfind.hide() self.line.setText(self.tabWidget.currentWidget().url().toString()) def findnext(self): text = self.line.text() self.tabWidget.currentWidget().findText(text) def findback(self): text = self.line.text() self.tabWidget.currentWidget().findText(text, QWebPage.FindBackward) ##################### View Settings ################### def zoomin(self): zoomlevel = self.tabWidget.currentWidget().zoomFactor() self.tabWidget.currentWidget().setZoomFactor(zoomlevel+0.1) # Use setZoomFactor() to zoom text and images def zoomout(self): zoomlevel = self.tabWidget.currentWidget().zoomFactor() self.tabWidget.currentWidget().setZoomFactor(zoomlevel-0.1) def fullscreenmode(self): if self.isFullScreen(): self.showNormal() else: self.showFullScreen() def loadimages(self, state): """ TOggles image loading on/off""" self.websettings.setAttribute(QWebSettings.AutoLoadImages, state) self.loadimagesval = bool(state) def setjavascript(self, state): """ Toggles js on/off """ self.websettings.setAttribute(QWebSettings.JavascriptEnabled, state) self.javascriptenabledval = bool(state) def setUserAgentDesktop(self, checked): if bool(checked): webkit.useragent_mode = 'Desktop' self.useragent_mode_mobile.setChecked(False) self.useragent_mode_custom.setChecked(False) def setUserAgentMobile(self, checked): if bool(checked): webkit.useragent_mode = 'Mobile' self.useragent_mode_desktop.setChecked(False) self.useragent_mode_custom.setChecked(False) def setUserAgentCustom(self, checked): if bool(checked): webkit.useragent_mode = 'Custom' self.useragent_mode_mobile.setChecked(False) self.useragent_mode_desktop.setChecked(False) ########################## Settings Portion ######################### def settingseditor(self): """ Opens the settings manager dialog, then applies the change""" dialog = QDialog(self) websettingsdialog = Ui_SettingsDialog() websettingsdialog.setupUi(dialog) # Enable AdBlock websettingsdialog.checkAdBlock.setChecked(webkit.enable_adblock) # Fonts blocking websettingsdialog.checkFontLoad.setChecked(webkit.block_fonts) # Popups blocking websettingsdialog.checkBlockPopups.setChecked(webkit.block_popups) # Custom user agent websettingsdialog.useragentEdit.setText(webkit.useragent_custom) # External download manager websettingsdialog.checkDownMan.setChecked(self.useexternaldownloader) websettingsdialog.downManEdit.setText(self.externaldownloader) # RTSP media player command websettingsdialog.mediaPlayerEdit.setText(webkit.video_player_command) websettingsdialog.mediaPlayerEdit.setCursorPosition(0) # Font settings websettingsdialog.spinFontSize.setValue(self.minfontsizeval) websettingsdialog.standardfontCombo.setCurrentFont(QFont(self.standardfontval)) websettingsdialog.sansfontCombo.setCurrentFont(QFont(self.sansfontval)) websettingsdialog.seriffontCombo.setCurrentFont(QFont(self.seriffontval)) websettingsdialog.fixedfontCombo.setCurrentFont(QFont(self.fixedfontval)) # Clear Data buttons websettingsdialog.clearCacheButton.clicked.connect(self.websettings.clearMemoryCaches) websettingsdialog.cookiesButton.clicked.connect(cookiejar.clearCookies) websettingsdialog.iconDBButton.clicked.connect(self.websettings.clearIconDatabase) if dialog.exec_() == QDialog.Accepted: # Enable AdBlock webkit.enable_adblock = websettingsdialog.checkAdBlock.isChecked() # Block Fonts webkit.block_fonts = websettingsdialog.checkFontLoad.isChecked() # Block Popups webkit.block_popups = websettingsdialog.checkBlockPopups.isChecked() # User Agent webkit.useragent_custom = websettingsdialog.useragentEdit.text() # Download Manager self.useexternaldownloader = websettingsdialog.checkDownMan.isChecked() self.externaldownloader = websettingsdialog.downManEdit.text() # Media Player Command webkit.video_player_command = websettingsdialog.mediaPlayerEdit.text() self.minfontsizeval = websettingsdialog.spinFontSize.value() self.standardfontval = websettingsdialog.standardfontCombo.currentText() self.sansfontval = websettingsdialog.sansfontCombo.currentText() self.seriffontval = websettingsdialog.seriffontCombo.currentText() self.fixedfontval = websettingsdialog.fixedfontCombo.currentText() self.applysettings() self.savesettings() def opensettings(self): """ Reads settings file in ~/.config/quartz-browser/ directory and saves values in settings variables""" webkit.enable_adblock = _bool(self.settings.value('EnableAdblock', True)) self.loadimagesval = _bool(self.settings.value('LoadImages', True)) self.javascriptenabledval = _bool(self.settings.value('JavaScriptEnabled', True)) webkit.block_fonts = _bool(self.settings.value('BlockFontLoading', False)) webkit.block_popups = _bool(self.settings.value('BlockPopups', False)) webkit.useragent_mode = self.settings.value('UserAgentMode', webkit.useragent_mode) webkit.useragent_custom = self.settings.value('UserAgent', webkit.useragent_custom) self.useexternaldownloader = _bool(self.settings.value('UseExternalDownloader', False)) self.externaldownloader = self.settings.value('ExternalDownloader', "x-terminal-emulator wget -c %u") webkit.video_player_command = self.settings.value('MediaPlayerCommand', webkit.video_player_command) self.maximize_window = _bool(self.settings.value('MaximizeWindow', False)) self.minfontsizeval = int(self.settings.value('MinFontSize', 11)) self.standardfontval = self.settings.value('StandardFont', 'Sans') self.sansfontval = self.settings.value('SansFont', 'Sans') self.seriffontval = self.settings.value('SerifFont', 'Serif') self.fixedfontval = self.settings.value('FixedFont', 'Monospace') def savesettings(self): """ Writes setings to disk in ~/.config/quartz-browser/ directory""" self.settings.setValue('EnableAdblock', webkit.enable_adblock) self.settings.setValue('LoadImages', self.loadimagesval) self.settings.setValue('JavaScriptEnabled', self.javascriptenabledval) self.settings.setValue('BlockFontLoading', webkit.block_fonts) self.settings.setValue('BlockPopups', webkit.block_popups) self.settings.setValue('UserAgent', webkit.useragent_custom) self.settings.setValue('UserAgentMode', webkit.useragent_mode) self.settings.setValue('UseExternalDownloader', self.useexternaldownloader) self.settings.setValue('ExternalDownloader', self.externaldownloader) self.settings.setValue('MediaPlayerCommand', webkit.video_player_command) self.settings.setValue('MaximizeWindow', self.isMaximized()) self.settings.setValue('MinFontSize', self.minfontsizeval) self.settings.setValue('StandardFont', self.standardfontval) self.settings.setValue('SansFont', self.sansfontval) self.settings.setValue('SerifFont', self.seriffontval) self.settings.setValue('FixedFont', self.fixedfontval) def applysettings(self): """ Reads settings variables, and changes browser settings.This is run after changing settings by Settings Dialog""" if webkit.enable_adblock: self.websettings.setUserStyleSheetUrl(QUrl.fromLocalFile(program_dir + 'userContent.css')) else: self.websettings.setUserStyleSheetUrl(QUrl('')) self.websettings.setAttribute(QWebSettings.AutoLoadImages, self.loadimagesval) self.loadimagesaction.setChecked(self.loadimagesval) self.websettings.setAttribute(QWebSettings.JavascriptEnabled, self.javascriptenabledval) self.javascriptmode.setChecked(self.javascriptenabledval) if webkit.useragent_mode == 'Mobile': self.useragent_mode_mobile.setChecked(True) elif webkit.useragent_mode == 'Custom': self.useragent_mode_custom.setChecked(True) else: self.useragent_mode_desktop.setChecked(True) self.websettings.setFontSize(QWebSettings.MinimumFontSize, self.minfontsizeval) self.websettings.setFontFamily(QWebSettings.StandardFont, self.standardfontval) self.websettings.setFontFamily(QWebSettings.SansSerifFont, self.sansfontval) self.websettings.setFontFamily(QWebSettings.SerifFont, self.seriffontval) self.websettings.setFontFamily(QWebSettings.FixedFont, self.fixedfontval) # self.websettings.setFontSize(QWebSettings.DefaultFontSize, 14) def enableKiosk(self): webkit.KIOSK_MODE = True self.menu.clear() self.toolBar.hide() self.showFullScreen() def forceClose(self): self.confirm_before_quit = False self.close() def closeEvent(self, event): """This saves all settings, bookmarks, cookies etc. during window close""" if self.confirm_before_quit: confirm = QMessageBox.warning(self, 'Quit Browser ?', 'Are you sure to close the Browser', QMessageBox.Yes|QMessageBox.No, QMessageBox.Yes) if confirm == QMessageBox.No : event.ignore() return self.savesettings() cookiejar.exportCookies() # Delete excess thumbnails thumbnails = [ x for x in os.listdir(thumbnails_dir) ] for fav in self.favourites: if fav[2] in thumbnails: thumbnails.remove(fav[2]) for f in thumbnails: os.remove(thumbnails_dir + f) # Delete excess icons icons = [ x for x in os.listdir(icon_dir) if x.endswith('.png') ] for bmk in self.bookmarks: host = QUrl(bmk[1]).host() if host + '.png' in icons: icons.remove(host + '.png') for f in icons: os.remove( icon_dir + f ) super(Main, self).closeEvent(event)
class BrowserWindow(QMainWindow): def __init__(self, *args, **kwargs): # Strip out arguments we handle that are different from QMainWindow: if 'width' in kwargs: self.width = kwargs['width'] del kwargs['width'] else: self.width = 1024 if 'height' in kwargs: self.height = kwargs['height'] del kwargs['height'] else: # Short enough to fit in XGA, 768 height: self.height = 735 # Then run the default constructor. super(BrowserWindow, self).__init__(*args, **kwargs) self.browserviews = [] self.profile = QWebEngineProfile() # print("Profile initially off the record?", # self.profile.isOffTheRecord()) # self.interceptor = BrowserRequestInterceptor() # self.profile.setRequestInterceptor(self.interceptor) self.init_tab_name_len = 40 self.init_chrome() # Resize to fit on an XGA screen, allowing for window chrome. # XXX Should check the screen size and see if it can be bigger. self.resize(self.width, self.height) def init_chrome(self): # Set up the browser window chrome: self.setWindowTitle("Quickbrowse") toolbar = QToolBar("Toolbar") self.addToolBar(toolbar) btn_act = QAction("Back", self) # for an icon: QAction(QIcon("bug.png"), "Your button", self) btn_act.setStatusTip("Go back") btn_act.triggered.connect(self.go_back) toolbar.addAction(btn_act) btn_act = QAction("Forward", self) btn_act.setStatusTip("Go forward") btn_act.triggered.connect(self.go_forward) toolbar.addAction(btn_act) btn_act = QAction("Reload", self) btn_act.setStatusTip("Reload") btn_act.triggered.connect(self.reload) toolbar.addAction(btn_act) self.urlbar = ReadlineEdit() self.urlbar.setPlaceholderText("URL goes here") self.urlbar.returnPressed.connect(self.urlbar_load) toolbar.addWidget(self.urlbar) self.tabwidget = QTabWidget() self.tabwidget.setTabBarAutoHide(True) self.setCentralWidget(self.tabwidget) self.tabwidget.tabBar().installEventFilter(self) self.prev_middle = -1 self.active_tab = 0 self.setStatusBar(QStatusBar(self)) self.progress = QProgressBar() self.statusBar().addPermanentWidget(self.progress) # Key bindings # For keys like function keys, use QtGui.QKeySequence("F12") QShortcut("Ctrl+Q", self, activated=self.close) QShortcut("Ctrl+L", self, activated=self.select_urlbar) QShortcut("Ctrl+T", self, activated=self.new_tab) QShortcut("Ctrl+R", self, activated=self.reload) QShortcut("Ctrl++", self, activated=self.zoom) QShortcut("Ctrl+=", self, activated=self.zoom) QShortcut("Ctrl+-", self, activated=self.unzoom) QShortcut("Alt+Left", self, activated=self.go_back) QShortcut("Alt+Right", self, activated=self.go_forward) def eventFilter(self, object, event): '''Handle button presses in the tab bar''' if object != self.tabwidget.tabBar(): print("eventFilter Not in tab bar") return super().eventFilter(object, event) # return False if event.type() not in [QEvent.MouseButtonPress, QEvent.MouseButtonRelease]: # print("Not a button press or release", event) return super().eventFilter(object, event) # return False tabindex = object.tabAt(event.pos()) if event.button() == Qt.LeftButton: if event.type() == QEvent.MouseButtonPress: self.active_tab = tabindex self.urlbar.setText(self.browserviews[tabindex].url().toDisplayString()) return super().eventFilter(object, event) # return False # So we'll still switch to that tab if event.button() == Qt.MidButton: if event.type() == QEvent.MouseButtonPress: self.prev_middle = tabindex else: if tabindex != -1 and tabindex == self.prev_middle: self.close_tab(tabindex) self.prev_middle = -1 return True print("Unknown button", event) return super().eventFilter(object, event) def new_tab(self, url=None): if url: init_name = url[:self.init_tab_name_len] else: init_name = "New tab" if is_pdf(url): webview = PDFBrowserView(self, url) self.browserviews.append(webview) self.tabwidget.addTab(webview, init_name) else: webview = BrowserView(self) # We need a QWebEnginePage in order to get linkHovered events, # and to set an anonymous profile. # print("New tab, profile still off the record?", # self.profile.isOffTheRecord()) webpage = BrowserPage(self.profile, webview, self) # print("New Webpage off the record?", # webpage.profile().isOffTheRecord()) webview.setPage(webpage) # print("New view's page off the record?", # webview.page().profile().isOffTheRecord()) # print("In new tab, view is", webview, "and page is", webpage) self.browserviews.append(webview) self.tabwidget.addTab(webview, init_name) if url: self.load_url(url, len(self.browserviews)-1) # Set up the signals: webview.urlChanged.connect(webview.url_changed) webview.loadStarted.connect(webview.load_started) webview.loadFinished.connect(webview.load_finished) webview.loadProgress.connect(webview.load_progress) webpage.linkHovered.connect(webview.link_hover) return webview def close_tab(self, tabindex): self.tabwidget.removeTab(tabindex) def load_url(self, url, tab=None): '''Load the given URL in the specified tab, or current tab if tab=None. url is a str, not a QUrl. PDF URLs will be loaded in a new tab, because there doesn't seem to be a way of replacing the BrowserView with a BrowserPDFView. ''' # Note that tab=0 is a valid argument here. # When testing whether tab is set, be sure to test for None. if is_pdf(url): self.new_tab(url) return qurl = QUrl(url) if not qurl.scheme(): if os.path.exists(url): qurl.setScheme('file') if not os.path.isabs(url): # Is it better to use posixpath.join or os.path.join? # Both work on Linux. qurl.setPath(os.path.normpath(os.path.join(os.getcwd(), url))) else: qurl.setScheme('http') if len(self.browserviews) == 0: self.new_tab() tab = 0 elif tab == None: tab = self.active_tab self.set_tab_text(url[:self.init_tab_name_len], self.browserviews[tab]) if tab == self.active_tab: self.urlbar.setText(url) self.browserviews[tab].load(qurl) def load_html(self, html, base=None): '''Load a string containing HTML. The base is the file: URL the HTML should be considered to have come from, to avoid "Not allowed to load local resource" errors when clicking on links. ''' if not self.browserviews: self.new_tab() tab = 0 else: tab = self.active_tab self.set_tab_text("---", # XXX Replace with html title if possible self.browserviews[tab]) self.browserviews[tab].setHtml(html, QUrl(base)) def select_urlbar(self): self.urlbar.selectAll() self.urlbar.setFocus() def find_view(self, view): for i, v in enumerate(self.browserviews): if v == view: return i return None def set_tab_text(self, title, view): '''Set tab and, perhaps, window title after a page load. view is the requesting BrowserView, and will be compared to our browserviews[] to figure out which tab to set. ''' if self.tabwidget == None: return whichtab = None whichtab = self.find_view(view) if whichtab == None: print("Warning: set_tab_text for unknown view") return self.tabwidget.setTabText(whichtab, title) def zoom(self): if 'zoom' in dir(self.browserviews[self.active_tab]): self.browserviews[self.active_tab].zoom() def unzoom(self): if 'unzoom' in dir(self.browserviews[self.active_tab]): self.browserviews[self.active_tab].unzoom() def update_buttons(self): # TODO: To enable/disable buttons, check e.g. # self.webview.page().action(QWebEnginePage.Back).isEnabled()) pass def signal_handler(self, signal, frame): with open(URL_FILE % os.getpid()) as url_fp: for url in url_fp: self.new_tab(url.strip()) # # Slots # def urlbar_load(self): url = self.urlbar.text() self.load_url(url) def go_back(self): self.browserviews[self.active_tab].back() def go_forward(self): self.browserviews[self.active_tab].forward() def reload(self): self.browserviews[self.active_tab].reload()
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() fileMenu = QMenu("&File", self) newAction = fileMenu.addAction("&New...") newAction.setShortcut("Ctrl+N") self.printAction = fileMenu.addAction("&Print...", self.printFile) self.printAction.setShortcut("Ctrl+P") self.printAction.setEnabled(False) quitAction = fileMenu.addAction("&Exit") quitAction.setShortcut("Ctrl+Q") helpMenu = QMenu("&Help", self) aboutAction = helpMenu.addAction("&About") self.menuBar().addMenu(fileMenu) self.menuBar().addMenu(helpMenu) self.solvers = QTabWidget() self.solvers.setTabsClosable(True) self.solvers.tabCloseRequested.connect(self.closeTab) newAction.triggered.connect(self.openDialog) quitAction.triggered.connect(self.close) aboutAction.triggered.connect(self.openAbout) self.setCentralWidget(self.solvers) self.setWindowTitle("Pulppy Software") def printFile(self): editor = self.solvers.currentWidget() if type(editor) == QTextEdit: printer = QPrinter() dialog = QPrintDialog(printer, self) dialog.setWindowTitle("Print Document") if dialog.exec_() != QDialog.Accepted: return editor.print_(printer) else: answer = QMessageBox.warning(self, "Not Print Image", "Dont print graph.", QMessageBox.Ok) def closeTab (self, currentIndex): currentQWidget = self.solvers.widget(currentIndex) currentQWidget.deleteLater() self.solvers.removeTab(currentIndex) def openDialog(self): inputProblemDialog = InputProblem(self) if inputProblemDialog.exec_() == QDialog.Accepted: inputTable = InputTableModel(inputProblemDialog.title , inputProblemDialog.numVar, inputProblemDialog.numCons , inputProblemDialog.typeVar, inputProblemDialog.objCrit, self) if inputTable.exec_() == QDialog.Accepted: self.createTabSolver(inputTable.problem) if inputTable.numVariables == 2: self.createTabGraph(inputTable.canvas) def createTabGraph(self, canvas): self.main_widget = QWidget(self) vbox = QVBoxLayout(self.main_widget) vbox.addWidget(canvas.mpl_toolbar) vbox.addWidget(canvas) tabIndex = self.solvers.addTab(self.main_widget, "Graph - "+canvas.title) self.solvers.setCurrentIndex(tabIndex) def createTabSolver(self, problem): editor = QTextEdit() tabIndex = self.solvers.addTab(editor, problem.name) self.solvers.setCurrentIndex(tabIndex) cursor = editor.textCursor() cursor.movePosition(QTextCursor.Start) topFrame = cursor.currentFrame() topFrameFormat = topFrame.frameFormat() topFrameFormat.setPadding(16) topFrame.setFrameFormat(topFrameFormat) textFormat = QTextCharFormat() boldFormat = QTextCharFormat() boldFormat.setFontWeight(QFont.Bold) referenceFrameFormat = QTextFrameFormat() referenceFrameFormat.setBorder(1) referenceFrameFormat.setPadding(8) referenceFrameFormat.setPosition(QTextFrameFormat.FloatLeft) referenceFrameFormat.setWidth(QTextLength(QTextLength.PercentageLength, 100)) cursor.insertFrame(referenceFrameFormat) cursor.insertText("Title Problem: ", boldFormat) cursor.insertText(problem.name+"\n", textFormat) cursor.insertText("Criterion: ", boldFormat) if problem.sense == 1: cursor.insertText("Minimize\n",textFormat) else: cursor.insertText("Maximize\n",textFormat) WasNone, dummyVar = problem.fixObjective() cursor.insertText("Status: ", boldFormat) cursor.insertText(str(LpStatus[problem.status])+"\n", textFormat) cursor.insertText("Value Function Objetive: ", boldFormat) cursor.insertText(str(value(problem.objective))+"\n", textFormat) cursor.insertBlock() cursor.insertText("Objective\n", boldFormat) cursor.insertText(str(problem.objective)+"\n", textFormat) cursor.insertBlock() cursor.insertText("Subject To\n", boldFormat) ks = list(problem.constraints.keys()) ks.sort() for k in ks: constraint = problem.constraints[k] print constraint if not list(constraint.keys()): #empty constraint add the dummyVar dummyVar = problem.get_dummyVar() constraint += dummyVar #set this dummyvar to zero so infeasible problems are not made #feasible cursor.insertText((dummyVar == 0.0).asCplexLpConstraint("_dummy") , textFormat) cursor.insertBlock() cursor.insertText(str(k)+" : ", boldFormat) cursor.insertText(str(constraint), textFormat) cursor.insertBlock() vs = problem.variables() cursor.insertBlock() # Bounds on non-"positive" variables # Note: XPRESS and CPLEX do not interpret integer variables without # explicit bounds mip=1 if mip: vg=[v for v in vs if not (v.isPositive() and v.cat==LpContinuous) \ and not v.isBinary()] else: vg = [v for v in vs if not v.isPositive()] if vg: cursor.insertText("Bounds\n", boldFormat) for v in vg: cursor.insertText("%s, " % v.asCplexLpVariable(), textFormat) # Integer non-binary variables if mip: vg = [v for v in vs if v.cat == LpInteger and not v.isBinary()] if vg: cursor.insertText("Generals\n", boldFormat) for v in vg: cursor.insertText("%s, " % v.name, textFormat) # Binary variables vg = [v for v in vs if v.isBinary()] if vg: cursor.insertText("Binaries\n",boldFormat) for v in vg: cursor.insertText("%s, " % v.name, textFormat) cursor.setPosition(topFrame.lastPosition()) bodyFrameFormat = QTextFrameFormat() bodyFrameFormat.setWidth(QTextLength(QTextLength.PercentageLength, 100)) cursor.insertBlock() cursor.insertFrame(bodyFrameFormat) cursor.insertBlock() orderTableFormat = QTextTableFormat() orderTableFormat.setAlignment(Qt.AlignHCenter) orderTable = cursor.insertTable(1, 3, orderTableFormat) orderFrameFormat = cursor.currentFrame().frameFormat() orderFrameFormat.setBorder(1) cursor.currentFrame().setFrameFormat(orderFrameFormat) cursor = orderTable.cellAt(0, 0).firstCursorPosition() cursor.insertText("Variable", boldFormat) cursor = orderTable.cellAt(0, 1).firstCursorPosition() cursor.insertText("Value", boldFormat) cursor = orderTable.cellAt(0, 2).firstCursorPosition() cursor.insertText("Reduced Cost", boldFormat) for v in problem.variables(): row = orderTable.rows() orderTable.insertRows(row, 1) #Name variable cursor = orderTable.cellAt(row, 0).firstCursorPosition() cursor.insertText(v.name, textFormat) #Value variable cursor = orderTable.cellAt(row, 1).firstCursorPosition() cursor.insertText(str(v.varValue), textFormat) #Cost Reduced variable cursor = orderTable.cellAt(row, 2).firstCursorPosition() cursor.insertText(str(v.dj), textFormat) cursor.setPosition(topFrame.lastPosition()) cursor.insertBlock() orderTableFormat = QTextTableFormat() orderTableFormat.setAlignment(Qt.AlignHCenter) orderTable = cursor.insertTable(1, 3, orderTableFormat) orderFrameFormat = cursor.currentFrame().frameFormat() orderFrameFormat.setBorder(1) cursor.currentFrame().setFrameFormat(orderFrameFormat) cursor = orderTable.cellAt(0, 0).firstCursorPosition() cursor.insertText("Constraint", boldFormat) cursor = orderTable.cellAt(0, 1).firstCursorPosition() cursor.insertText("Slack", boldFormat) cursor = orderTable.cellAt(0, 2).firstCursorPosition() cursor.insertText("Shadow Price", boldFormat) for m in range(problem.numConstraints()): row = orderTable.rows() orderTable.insertRows(row, 1) #Name Constraint cursor = orderTable.cellAt(row, 0).firstCursorPosition() cursor.insertText("C"+ str(m+1), textFormat) #Slack Constraint cursor = orderTable.cellAt(row, 1).firstCursorPosition() cursor.insertText(str(problem.constraints.get("_C"+str(m+1)).slack) , textFormat) cursor = orderTable.cellAt(row, 2).firstCursorPosition() cursor.insertText(str(problem.constraints.get("_C"+str(m+1)).pi) , textFormat) self.printAction.setEnabled(True) def openAbout(self): about = AboutDialog(self) about.show()
class RunWidget(QWidget): allTabsClosed = pyqtSignal() projectExecuted = pyqtSignal(str) fileExecuted = pyqtSignal(str) def __init__(self): QWidget.__init__(self) self.__programs = [] vbox = QVBoxLayout(self) vbox.setContentsMargins(0, 0, 0, 0) vbox.setSpacing(0) connections = ( { "target": "tools_dock", "signal_name": "executeFile", "slot": self.execute_file }, { "target": "tools_dock", "signal_name": "executeProject", "slot": self.execute_project }, { "target": "tools_dock", "signal_name": "executeSelection", "slot": self.execute_selection }, { "target": "tools_dock", "signal_name": "stopApplication", "slot": self.kill_application } ) IDE.register_signals("tools_dock", connections) self._tabs = QTabWidget() self._tabs.setTabsClosable(True) self._tabs.setMovable(True) self._tabs.setDocumentMode(True) vbox.addWidget(self._tabs) # Menu for tab self._tabs.tabBar().setContextMenuPolicy(Qt.CustomContextMenu) self._tabs.tabBar().customContextMenuRequested.connect( self._menu_for_tabbar) self._tabs.tabCloseRequested.connect(self.close_tab) IDE.register_service("run_widget", self) _ToolsDock.register_widget(translations.TR_OUTPUT, self) def install(self): ninjaide = IDE.get_service("ide") ninjaide.goingDown.connect(self._kill_processes) def _kill_processes(self): """Stop all applications""" for program in self.__programs: program.kill() def kill_application(self): """Stop application by current tab index""" index = self._tabs.currentIndex() if index == -1: return program = self.__programs[index] program.kill() def _menu_for_tabbar(self, position): menu = QMenu() close_action = menu.addAction(translations.TR_CLOSE_TAB) close_all_action = menu.addAction(translations.TR_CLOSE_ALL_TABS) close_other_action = menu.addAction(translations.TR_CLOSE_OTHER_TABS) qaction = menu.exec_(self.mapToGlobal(position)) if qaction == close_action: index = self._tabs.tabBar().tabAt(position) self.close_tab(index) elif qaction == close_all_action: self.close_all_tabs() elif qaction == close_other_action: self.close_all_tabs_except_this() def close_tab(self, tab_index): program = self.__programs[tab_index] self.__programs.remove(program) self._tabs.removeTab(tab_index) # Close process and delete OutputWidget program.main_process.close() program.outputw.deleteLater() del program.outputw if self._tabs.count() == 0: # Hide widget tools = IDE.get_service("tools_dock") tools.hide_widget(self) def close_all_tabs(self): for _ in range(self._tabs.count()): self.close_tab(0) def close_all_tabs_except_this(self): self._tabs.tabBar().moveTab(self._tabs.currentIndex(), 0) for _ in range(self._tabs.count()): if self._tabs.count() > 1: self.close_tab(1) def execute_file(self): """Execute the current file""" main_container = IDE.get_service("main_container") editor_widget = main_container.get_current_editor() if editor_widget is not None and (editor_widget.is_modified or editor_widget.file_path): main_container.save_file(editor_widget) file_path = editor_widget.file_path if file_path is None: return # Emit signal for plugin! self.fileExecuted.emit(editor_widget.file_path) extension = file_manager.get_file_extension(file_path) # TODO: Remove the IF statment and use Handlers if extension == "py": self.start_process(filename=file_path) def execute_selection(self): """Execute selected text or current line if not have a selection""" main_container = IDE.get_service("main_container") editor_widget = main_container.get_current_editor() if editor_widget is not None: text = editor_widget.selected_text().splitlines() if not text: # Execute current line text = [editor_widget.line_text()] code = [] for line_text in text: # Get part before firs '#' code.append(line_text.split("#", 1)[0]) # Join to execute with python -c command final_code = ";".join([line.strip() for line in code if line]) # Highlight code to be executed editor_widget.show_run_cursor() # Ok run! self.start_process( filename=editor_widget.file_path, code=final_code) def execute_project(self): """Execute the project marked as Main Project.""" projects_explorer = IDE.get_service("projects_explorer") if projects_explorer is None: return nproject = projects_explorer.current_project if nproject: main_file = nproject.main_file if not main_file: # Open project properties to specify the main file projects_explorer.current_tree.open_project_properties() else: # Save project files projects_explorer.save_project() # Emit a signal for plugin! self.projectExecuted.emit(nproject.path) main_file = file_manager.create_path( nproject.path, nproject.main_file) self.start_process( filename=main_file, python_exec=nproject.python_exec, pre_exec_script=nproject.pre_exec_script, post_exec_script=nproject.post_exec_script, program_params=nproject.program_params ) def start_process(self, **kwargs): # First look if we can reuse a tab fname = kwargs.get("filename") program = None for prog in self.__programs: if prog.filename == fname: if not prog.is_running(): program = prog break if program is not None: index = self.__programs.index(program) program.update(**kwargs) self._tabs.setCurrentIndex(index) program.outputw.gray_out_old_text() else: program = Program(**kwargs) # Create new output widget outputw = OutputWidget(self) program.set_output_widget(outputw) self.add_tab(outputw, program.display_name()) self.__programs.append(program) program.start() def add_tab(self, outputw, tab_text): inserted_index = self._tabs.addTab(outputw, tab_text) self._tabs.setCurrentIndex(inserted_index)
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 QueryUI(QWidget): def __init__(self): super().__init__() try: self.keyValues = getOfficialKeys() except RequestException: logging.warning( "There was a problem with the internet connection. You will not be able to see the existing keys." ) self.onClearPolygonF = lambda: None self.onPolygonEnabledF = lambda: None self.onPolygonDisabledF = lambda: None self.currentHtml = EMPTY_HTML self.initUI() def initUI(self): self.layout = QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.requestAreaWidget = QToolBox() self.requestAreaWidget.layout().setSpacing(1) self.requestTabs = QTabWidget() self.requestTabs.setUsesScrollButtons(True) self.requestTabs.currentChanged.connect(self.__updateTabSizes__) self.requestAreaWidget.addItem(self.requestTabs, "Requests") self.requestOps = RequestsOperations(self) self.requestAreaWidget.addItem(self.requestOps, "Operations") self.generalConfig = GlobalOverpassSettingUI(self) self.requestAreaWidget.addItem(self.generalConfig, "General") self.disambiguationWidget = DisambiguationWidget( self.__getRequestByName__, self.__applyTableRow__, self) self.requestAreaWidget.addItem(self.disambiguationWidget, "Disambiguation") self.headers = self.requestAreaWidget.findChildren( QAbstractButton, "qt_toolbox_toolboxbutton") self.requestAreaWidget.currentChanged.connect( self.__onToolTabChanged__) self.headers[0].setIcon(QIcon(os.path.join(picturesDir, "arrowUp.png"))) for i in range(1, len(self.headers)): self.headers[i].setIcon( QIcon(os.path.join(picturesDir, "arrowDown.png"))) self.layout.addWidget(self.requestAreaWidget) self.setLayout(self.layout) def __getRequestByName__(self, requestName): for requestWidget in self.findChildren(RequestWidget): if requestWidget.getName() == requestName: return requestWidget.getRequest() return None def __applyTableRow__(self, name, data): filters, ids = data for requestWidget in self.findChildren(RequestWidget): if requestWidget.getName() == name: for newFilter in filters: requestWidget.addFilter(newFilter) break if len(ids) > 0: idsRequestName = SetNameManagement.getUniqueSetName() request = OverpassRequest(OsmType.WAYS, Surround.NONE, idsRequestName) request.setIds(ids) self.addRequest(request) differenceOpName = SetNameManagement.getUniqueSetName() self.requestOps.addOp(OverpassDiff(name, differenceOpName), [idsRequestName]) self.requestOps.setOutputSet(differenceOpName) logging.info("Configuration from the table row has been applied.") def __onToolTabChanged__(self, i): for h in range(len(self.headers)): if h == i: self.headers[h].setIcon( QIcon(os.path.join(picturesDir, "arrowUp.png"))) else: self.headers[h].setIcon( QIcon(os.path.join(picturesDir, "arrowDown.png"))) def __updateTabSizes__(self, index): for i in range(self.requestTabs.count()): if i != index: self.requestTabs.widget(i).setSizePolicy( QSizePolicy.Ignored, QSizePolicy.Ignored) if index >= 0: self.requestTabs.widget(index).setSizePolicy( QSizePolicy.Preferred, QSizePolicy.Preferred) self.requestTabs.widget(index).resize( self.requestTabs.widget(index).minimumSizeHint()) self.requestTabs.widget(index).adjustSize() def setOnRequestChanged(self, f): self.requestTabs.currentChanged.connect(f) def addRequestByFilters(self, filters=None): requestWidget = RequestWidget(self, self.keyValues) setName = requestWidget.requestName requestWidget.changePage(self.currentHtml) self.requestTabs.addTab(requestWidget, setName) self.requestOps.addRequest(setName) self.disambiguationWidget.addSet(setName) if filters is not None: for filter in filters: requestWidget.addFilter(filter) def addRequest(self, request): if not SetNameManagement.isAvailable(request.name): raise ValueError("There is another request with the same name.") else: SetNameManagement.assign(request.name) requestWidget = RequestWidget(self, self.keyValues, request) requestWidget.changePage(self.currentHtml) self.requestTabs.addTab(requestWidget, request.name) self.requestOps.addRequest(request.name) self.disambiguationWidget.addSet(request.name) def removeRequest(self): requestName = self.requestTabs.currentWidget().getName() self.requestOps.removeSetAndDependencies(requestName) self.disambiguationWidget.removeSet(requestName) currentRequestWidget = self.requestTabs.currentWidget() SetNameManagement.releaseName(currentRequestWidget.requestName) self.requestTabs.removeTab(self.requestTabs.currentIndex()) currentRequestWidget.deleteLater() def requestsCount(self): return self.requestTabs.count() def getQuery(self): query = OverpassQuery(self.requestOps.outputSet()) query.addDate(self.generalConfig.getDate()) for i in range(self.requestTabs.count()): query.addRequest(self.requestTabs.widget(i).getRequest()) for op in self.requestOps.ops: query.addSetsOp(op) return query def setQuery(self, query): self.reset() for request in query.requests: self.addRequest(request) for op in query.ops: self.requestOps.addOp(op) if query.config.get("date") is not None: self.generalConfig.setDate( datetime.strptime(query.config["date"], "%Y-%m-%dT00:00:00Z")) else: self.generalConfig.setDate() self.requestOps.setOutputSet(query.outputSet) def reset(self): while self.requestTabs.count() > 0: self.removeRequest() self.generalConfig.setDate() def updateMaps(self, html): self.currentHtml = html for requestWidget in self.findChildren(RequestWidget): requestWidget.changePage(html) return self.getCurrentMap() def updateMapFromRow(self): return self.updateMaps( self.disambiguationWidget.getHtmlFromSelectedRow()) def getCurrentMap(self): if self.requestTabs.currentWidget() is None: return None else: return self.requestTabs.currentWidget().getMap()
class MainWindow(QMainWindow): def __init__(self, filename): super().__init__() self.setGeometry(20, 20, 1324, 1068) self.setWindowTitle('qt-Notepad') self.setStyleSheet('font-size: 14pt; font-family: Courier;') self.show() self.init_ui() centralWidget = QWidget() self.tabs = QTabWidget(centralWidget) self.setCentralWidget(self.tabs) self.tabs.setTabsClosable(True) self.tabs.setMovable(True) self.tabs.tabCloseRequested.connect(self.closeTab) if filename: f = open(filename, 'r') filedata = f.read() f.close() newfile = QTextEdit() newfile.setText(filedata) i = self.tabs.addTab(newfile, filename) self.tabs.setCurrentIndex(i) else: self.open_file() def init_ui(self): new_action = QAction('New File', self) new_action.setShortcut('Ctrl+N') new_action.setStatusTip('Create new file') new_action.triggered.connect(self.new_file) open_action = QAction('Open...', self) open_action.setShortcut('Ctrl+O') open_action.setStatusTip('Open a file') open_action.triggered.connect(self.open_file) save_action = QAction('Save File', self) save_action.setShortcut('Ctrl+S') save_action.setStatusTip('Save current file') save_action.triggered.connect(self.save_file) new_save_action = QAction('Save File As...', self) new_save_action.setShortcut('Shift+Ctrl+S') new_save_action.setStatusTip('Save current file') new_save_action.triggered.connect(self.save_file_as) close_action = QAction('Close File', self) close_action.setShortcut('Ctrl+W') close_action.setStatusTip('Close file and exit tab') close_action.triggered.connect(self.close_file) exit_action = QAction('Exit qt-Notepad', self) exit_action.setShortcut('Ctrl+Q') exit_action.setStatusTip('Close Notepad') exit_action.triggered.connect(self.close) undo_action = QAction('Undo', self) undo_action.setShortcut('Ctrl+Z') copy_action = QAction('Copy', self) copy_action.setShortcut('Ctrl+C') cut_action = QAction('Cut', self) cut_action.setShortcut('Ctrl+X') paste_action = QAction('Paste', self) paste_action.setShortcut('Ctrl+V') minimize_action = QAction('Minimize', self) minimize_action.setShortcut('Ctrl+M') view_action = QAction('Show', self) view_action.setShortcut('Ctrl+/') menubar = self.menuBar() file_menu = menubar.addMenu('&File') edit_menu = menubar.addMenu('&Edit') view_menu = menubar.addMenu('&View') window_menu = menubar.addMenu('&Window') file_menu.addAction(new_action) file_menu.addAction(open_action) file_menu.addAction(save_action) file_menu.addAction(new_save_action) file_menu.addAction(close_action) file_menu.addAction(exit_action) edit_menu.addAction(undo_action) edit_menu.addAction(copy_action) edit_menu.addAction(cut_action) edit_menu.addAction(paste_action) view_menu.addAction(view_action) window_menu.addAction(minimize_action) def closeTab(self, currentIndex): currentQWidget = self.tabs.currentWidget() currentQWidget.deleteLater() self.tabs.removeTab(currentIndex) def new_file(self): newfile = QTextEdit() i = self.tabs.addTab(newfile, 'New Document') self.tabs.setCurrentIndex(i) def save_file(self): editor = self.tabs.currentWidget() filename = self.tabs.tabText(self.tabs.currentIndex()) if filename != 'New Document': f = open(filename, 'w') filedata = editor.toPlainText() f.write(filedata) f.close() else: self.save_file_as() def save_file_as(self): filename = QFileDialog.getSaveFileName( self, 'Save File', os.getenv('HOME'))[0] print(filename) if filename != ('', ''): f = open(filename, 'w') filedata = self.text.toPlainText() f.write(filedata) f.close() def open_file(self): filename = QFileDialog.getOpenFileName( self, 'Open File', os.getenv('HOME'))[0] f = open(filename, 'r') filedata = f.read() f.close() newfile = QTextEdit() newfile.setText(filedata) i = self.tabs.addTab(newfile, filename) self.tabs.setCurrentIndex(i) def close_file(self): self.save_file() currentIndex = self.tabs.currentIndex() self.tabs.removeTab(currentIndex)
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()
class PlotWindow(QWidget): def __init__(self, tab): super(PlotWindow, self).__init__() self.tab = tab self.allAxes = {} self.curveList = [] self.extraLines = [] self.layout = QHBoxLayout() self.graph_layout = QVBoxLayout() self.gbox_layout = QVBoxLayout() self.tabGBActor = QTabWidget() self.dateplot = DatePlot(self) self.customize = Customize(self) self.button_arrow = self.ButtonArrow() self.button_del_graph = self.ButtonDelete() self.gbox_layout.addWidget(self.dateplot) self.gbox_layout.addWidget(self.customize) self.gbox_layout.addWidget(self.tabGBActor) self.gbox_layout.addWidget(self.button_del_graph) self.layout.addLayout(self.graph_layout) self.layout.addWidget(self.button_arrow) self.layout.addLayout(self.gbox_layout) for widget in [self.dateplot, self.customize, self.tabGBActor]: widget.setMaximumWidth(400) self.setLayout(self.layout) @property def mainwindow(self): return self.tab.mainwindow @property def config(self): return self.dateplot.config @property def axes2curves(self): d = {ax: [] for ax in [None] + list(self.allAxes.values())} for curve in self.curveList: d[curve.getAxes()].append(curve) return d @property def line2Curve(self): return {curve.line: curve for curve in self.curveList} @property def axes2id(self): d = {ax: id for id, ax in self.allAxes.items()} d[None] = None return d def createGraph(self, custom): try: self.graph.close() self.graph_layout.removeWidget(self.graph) self.graph.deleteLater() except AttributeError: pass self.graph = Graph(self, custom) self.graph_layout.insertWidget(0, self.graph) def getAxes(self, newType, i=-1): for i, ax in self.allAxes.items(): try: curve = self.axes2curves[ax][0] if curve.type == newType: return ax except IndexError: return ax if i == 3: raise ValueError('No Axe available') return i + 1 def setAxes(self, allAxes): for idAxes, oldAxes in list(self.allAxes.items()): self.unsetLines(oldAxes, allAxes) self.allAxes.pop(idAxes, None) self.allAxes = allAxes def unsetLines(self, axes, newAxes): while axes.lines: line = axes.lines[0] axes.lines.remove(line) try: curve = self.line2Curve[line] curve.line = False if curve.getAxes() not in newAxes.values(): curve.setAxes(None) except KeyError: pass del line def addCurve(self, curveConf): new_curve = Curve(self, curveConf) axes = self.getAxes(new_curve.type) if isinstance(axes, int): idAxes = axes self.customize.allAxes[idAxes].checkbox.setChecked(2) axes = self.allAxes[idAxes] new_curve.setAxes(axes) self.appendCurve(new_curve) self.graph.plotCurves(new_curve) return new_curve def appendCurve(self, new_curve): self.curveList.append(new_curve) self.customize.appendRow(new_curve) def switchCurve(self, axeId, curve): ax = self.allAxes[axeId] if axeId is not None else None self.graph.switchCurve(ax, curve) def removeCurve(self, curve): self.curveList.remove(curve) self.graph.removeCurve(curve) try: checkbox = curve.checkbox checkbox.setCheckable(True) checkbox.setChecked(0) except RuntimeError: pass # Checkbox could have been already deleted def constructGroupbox(self, config): while self.tabGBActor.count(): widget = self.tabGBActor.widget(0) self.clearLayout(widget.layout()) self.tabGBActor.removeTab(0) widget.close() widget.deleteLater() sortedModule = self.sortCfg(config) for actorname in sorted(sortedModule): config = sortedModule[actorname] if config: t = TabActor(self, config) self.tabGBActor.addTab(t, actorname) def showhideConfig(self, button_arrow): if not self.tabGBActor.isHidden(): self.tabGBActor.hide() self.dateplot.hide() self.customize.hide() self.button_del_graph.hide() button_arrow.setIcon(self.mainwindow.icon_arrow_left) else: self.tabGBActor.show() self.button_del_graph.show() self.dateplot.show() self.customize.show() button_arrow.setIcon(self.mainwindow.icon_arrow_right) def ButtonDelete(self): button = QPushButton('Remove Graph') button.clicked.connect(partial(self.removeGraph, self.layout)) return button def ButtonArrow(self): button_arrow = QPushButton() button_arrow.setIcon(self.mainwindow.icon_arrow_right) button_arrow.clicked.connect(partial(self.showhideConfig, button_arrow)) button_arrow.setStyleSheet('border: 0px') return button_arrow def removeGraph(self, layout): self.clearLayout(layout) self.tab.removeGraph(self) def clearLayout(self, layout): if layout is not None: while layout.count(): item = layout.takeAt(0) widget = item.widget() if widget is not None: widget.deleteLater() else: self.clearLayout(item.layout()) def table2label(self, tablename, mergeAIT=False): for key, label in self.mainwindow.cuArms.items(): if key in tablename: return label if mergeAIT: return 'AIT' else: return tablename.split('__')[0].upper() def sortCfg(self, config): sortedDict = {} for dev in config: label = self.table2label(tablename=dev.tablename, mergeAIT=False) try: sortedDict[label].append(dev) except KeyError: sortedDict[label] = [dev] return sortedDict
class MainWindow(QMainWindow): def __init__(self, filename): super().__init__() self.setGeometry(20, 20, 1324, 1068) self.setWindowTitle('qt-Notepad') self.setStyleSheet('font-size: 14pt; font-family: Courier;') self.show() self.init_ui() centralWidget = QWidget() self.tabs = QTabWidget(centralWidget) self.setCentralWidget(self.tabs) self.tabs.setTabsClosable(True) self.tabs.setMovable(True) self.tabs.tabCloseRequested.connect(self.closeTab) if filename: f = open(filename, 'r') filedata = f.read() f.close() newfile = QTextEdit() newfile.setText(filedata) i = self.tabs.addTab(newfile, filename) self.tabs.setCurrentIndex(i) else: self.open_file() def init_ui(self): new_action = QAction('New File', self) new_action.setShortcut('Ctrl+N') new_action.setStatusTip('Create new file') new_action.triggered.connect(self.new_file) open_action = QAction('Open...', self) open_action.setShortcut('Ctrl+O') open_action.setStatusTip('Open a file') open_action.triggered.connect(self.open_file) save_action = QAction('Save File', self) save_action.setShortcut('Ctrl+S') save_action.setStatusTip('Save current file') save_action.triggered.connect(self.save_file) new_save_action = QAction('Save File As...', self) new_save_action.setShortcut('Shift+Ctrl+S') new_save_action.setStatusTip('Save current file') new_save_action.triggered.connect(self.save_file_as) close_action = QAction('Close File', self) close_action.setShortcut('Ctrl+W') close_action.setStatusTip('Close file and exit tab') close_action.triggered.connect(self.close_file) exit_action = QAction('Exit qt-Notepad', self) exit_action.setShortcut('Ctrl+Q') exit_action.setStatusTip('Close Notepad') exit_action.triggered.connect(self.close) undo_action = QAction('Undo', self) undo_action.setShortcut('Ctrl+Z') copy_action = QAction('Copy', self) copy_action.setShortcut('Ctrl+C') cut_action = QAction('Cut', self) cut_action.setShortcut('Ctrl+X') paste_action = QAction('Paste', self) paste_action.setShortcut('Ctrl+V') minimize_action = QAction('Minimize', self) minimize_action.setShortcut('Ctrl+M') view_action = QAction('Show', self) view_action.setShortcut('Ctrl+/') menubar = self.menuBar() file_menu = menubar.addMenu('&File') edit_menu = menubar.addMenu('&Edit') view_menu = menubar.addMenu('&View') window_menu = menubar.addMenu('&Window') file_menu.addAction(new_action) file_menu.addAction(open_action) file_menu.addAction(save_action) file_menu.addAction(new_save_action) file_menu.addAction(close_action) file_menu.addAction(exit_action) edit_menu.addAction(undo_action) edit_menu.addAction(copy_action) edit_menu.addAction(cut_action) edit_menu.addAction(paste_action) view_menu.addAction(view_action) window_menu.addAction(minimize_action) def closeTab(self, currentIndex): currentQWidget = self.tabs.currentWidget() currentQWidget.deleteLater() self.tabs.removeTab(currentIndex) def new_file(self): newfile = QTextEdit() i = self.tabs.addTab(newfile, 'New Document') self.tabs.setCurrentIndex(i) def save_file(self): editor = self.tabs.currentWidget() filename = self.tabs.tabText(self.tabs.currentIndex()) if filename != 'New Document': f = open(filename, 'w') filedata = editor.toPlainText() f.write(filedata) f.close() else: self.save_file_as() def save_file_as(self): filename = QFileDialog.getSaveFileName(self, 'Save File', os.getenv('HOME'))[0] print(filename) if filename != ('', ''): f = open(filename, 'w') filedata = self.text.toPlainText() f.write(filedata) f.close() def open_file(self): filename = QFileDialog.getOpenFileName(self, 'Open File', os.getenv('HOME'))[0] f = open(filename, 'r') filedata = f.read() f.close() newfile = QTextEdit() newfile.setText(filedata) i = self.tabs.addTab(newfile, filename) self.tabs.setCurrentIndex(i) def close_file(self): self.save_file() currentIndex = self.tabs.currentIndex() self.tabs.removeTab(currentIndex)
class MainApp(QMainWindow, Ui_MainWindow): _translate = QCoreApplication.translate tab_list = [] # TODO - add dutch translation files def __init__(self, isolated, *args): super(MainApp, self).__init__(*args) Lumberjack.info('spawning the <<< MainApp >>> hey says: I am the Main man here see!') self.load_settings() self.setup_tray(isolated) self.dbhelper = DbHelper() self.setupUi(self) self.iconize_controls() self.load_styling() self.tabWidget = QTabWidget(self.centralwidget) self.tabWidget.setTabsClosable(True) self.tabWidget.setMovable(True) self.tabWidget.setTabBarAutoHide(True) self.tabWidget.setObjectName("tabWidget") self.verticalLayout.addWidget(self.tabWidget) builderLabel = QLabel('made by: MazeFX Solutions') self.statusbar.addPermanentWidget(builderLabel) self.menuPAT.triggered.connect(self.handle_menu_event) self.menuLists.triggered.connect(self.handle_menu_event) self.menuHelp.triggered.connect(self.handle_menu_event) self.tabWidget.tabCloseRequested.connect(self.close_tab) self.actionHome.trigger() self._retranslateUi(self) def iconize_controls(self): Lumberjack.info('< MainApp > - -> (iconize_controls)') homeIcon = qta.icon('fa.home', color='white') self.actionHome.setIcon(homeIcon) wrenchIcon = qta.icon('fa.wrench', color='white') self.actionSettings.setIcon(wrenchIcon) bankIcon = qta.icon('fa.bank', color='white') self.actionListBankAccounts.setIcon(bankIcon) contractIcon = QIcon(':/app_icons/rc/handshake_icon.svg') self.actionListContracts.setIcon(contractIcon) atIcon = qta.icon('fa.at', color='white') self.actionListEmailAddresses.setIcon(atIcon) envelopeIcon = qta.icon('fa.envelope', color='white') self.actionListLetters.setIcon(envelopeIcon) relationIcon = qta.icon('fa.group', color='white') self.actionListRelations.setIcon(relationIcon) transactionIcon = qta.icon('fa.money', color='white') self.actionListTransactions.setIcon(transactionIcon) userIcon = qta.icon('fa.user', color='white') self.actionListUsers.setIcon(userIcon) helpIcon = qta.icon('fa.question', color='white') self.actionHelp.setIcon(helpIcon) aboutIcon = qta.icon('fa.info', color='white') self.actionAbout.setIcon(aboutIcon) def setup_tray(self, isolated): Lumberjack.info('< MainApp > - -> (setup_tray)') self.trayIcon = QSystemTrayIcon(QIcon(':/app_icons/rc/PAT_icon.png'), self) self.trayMenu = QMenu(self) showAction = self.trayMenu.addAction("Open PAT") self.trayMenu.addSeparator() exitAction = self.trayMenu.addAction("Exit") self.trayIcon.setContextMenu(self.trayMenu) self.trayMenu.triggered.connect(self.handle_tray_event) self.trayIcon.activated.connect(self.handle_tray_event) self.trayIcon.show() if isolated: self.trayIcon.showMessage('PAT Service', 'PAT service is now running..') def handle_tray_event(self, *args): Lumberjack.info('< MainApp > - -> (handle_tray_event)') print(Fore.MAGENTA + '$! Received a tray action with args: ', args) if args[0] == 3: self.show() return elif hasattr(args[0], 'text'): print(Fore.MAGENTA + '$! Tray event has text!!') if args[0].text() == 'Open PAT': self.show() elif args[0].text() == 'Exit': self.close() def _retranslateUi(self, MainWindow): pass def handle_menu_event(self, *args): Lumberjack.info('< MainApp > - -> (handle_menu_event)') Lumberjack.debug('(handle_menu_event) - args = {}'.format(args)) action_text = args[0].text() icon = args[0].icon() Lumberjack.debug('(handle_menu_event) - Action text selector = {}'.format(action_text)) print(Fore.MAGENTA + '$! Action text received: ', action_text) if action_text == 'Home': Lumberjack.info('(handle_menu_event) >User action> : Adding Home tab to self') self.add_tab(HomeTab, 'Home', icon) if action_text == 'Settings': Lumberjack.info('(handle_menu_event) >User action> : Showing settings dialog') self.show_settings() elif action_text == 'Bank accounts': Lumberjack.info('(handle_menu_event) >User action> : Adding Bank account List tab to self') self.add_tab(BankAccountListTab, 'Bank accounts', icon) elif action_text == 'Contracts': Lumberjack.info('(handle_menu_event) >User action> : Adding Contracts List tab to self') self.add_tab(ContractListTab, 'Contracts', icon) elif action_text == 'Email addresses': Lumberjack.info('(handle_menu_event) >User action> : Adding EmailAddress List tab to self') self.add_tab(EmailAddressListTab, 'Email addresses', icon) elif action_text == 'Letters': Lumberjack.info('(handle_menu_event) >User action> : Adding Letter List tab to self') self.add_tab(LetterListTab, 'Letters', icon) elif action_text == 'Users': Lumberjack.info('(handle_menu_event) >User action> : Adding User List tab to self') self.add_tab(UserListTab, 'Users', icon) elif action_text == 'Relations': Lumberjack.info('(handle_menu_event) >User action> : Adding Relation List tab to self') self.add_tab(RelationListTab, 'Relations', icon) elif action_text == 'Transactions': Lumberjack.info('(handle_menu_event) >User action> : Adding Transaction List tab to self') self.add_tab(TransactionListTab, 'Transactions', icon) elif action_text == 'Help': Lumberjack.info('(handle_menu_event) >User action> : Showing help dialog') # TODO - build help dialog and help files elif action_text == 'About': Lumberjack.info('(handle_menu_event) >User action> : Showing about dialog') # TODO build About dialog. def show_settings(self): Lumberjack.info('< MainApp > - -> (show_settings)') settings_dialog = SettingsDialog() settings_dialog.exec_() def add_tab(self, tab_cls, tab_name, icon): Lumberjack.info('< MainApp > - -> (add_tab)') new_tab = tab_cls(self.dbhelper) print(Fore.MAGENTA + 'Adding a tab with class: ', str(tab_cls)) new_tab.setObjectName(str(tab_cls)) self.tabWidget.addTab(new_tab, icon, self._translate("MainWindow", tab_name)) print(Fore.MAGENTA + 'New tab added to tab list.') self.tabWidget.setCurrentIndex(self.tabWidget.indexOf(new_tab)) self.tab_list.append(new_tab) def close_tab(self, index): # TODO - Check if index stays correct when moving tabs around requesting_tab = self.tab_list[index] print(Fore.MAGENTA + 'requesting tab is: ', requesting_tab) if hasattr(requesting_tab, 'form'): if requesting_tab.form.edit_mode: print(Fore.MAGENTA + 'Tab is in edit mode.') requesting_tab.form.toggle_edit_mode(False, None, None) if requesting_tab.form.edit_mode is None: print(Fore.MAGENTA + 'Tab is now in equil.') self.tabWidget.removeTab(index) del self.tab_list[index] else: self.tabWidget.removeTab(index) del self.tab_list[index] def load_settings(self): self.settings = QSettings() db_path = self.settings.value('db_base_path') db_name = self.settings.value('db_name') if db_path is not None and db_name is not None: db_file = os.path.join(db_path, db_name) Lumberjack.debug('__init__ - db_file = {}'.format(db_file)) if os.path.exists(db_file): return Lumberjack.warning('(load_settings) - database not found') settings_dialog = SettingsDialog() settings_dialog.exec_() int_value = self.settings.value('db_type', type=int) print(Fore.MAGENTA + "load choosen database setting: %s" % repr(int_value)) def load_styling(self): style.set_window_style(self) def closeEvent(self, event): print(Fore.MAGENTA + "User has clicked the red x on the main window") for tab in self.tab_list: if hasattr(tab, 'form'): if tab.form.edit_mode: print(Fore.MAGENTA + 'Tab is in edit mode.') tab.form.toggle_edit_mode(False, None, None) close_dialog = CloseDialog() result = close_dialog.exec_() if result == close_dialog.Minimize: self.hide() event.ignore() elif result == close_dialog.Rejected: event.ignore() elif result == close_dialog.Exit: print(Fore.MAGENTA + "Exiting via save dialog, result = ", result) self.trayIcon.hide() event.accept()
class ScadaGui(QWidget): def __init__(self, redirect_sys_out=True): self.redirect_sys_out = redirect_sys_out super(ScadaGui, self).__init__() self.initUI() def initUI(self): # creating widgets self._dgridPage = QWidget() self._networkPage = QWidget() # self._plotPage[0] = QWidget() self._plotPage = {} # create tabs self._tabs = QTabWidget(self) self._tabs.addTab(self._dgridPage, "dgrid") self._tabs.addTab(self._networkPage, "network") self._tabs.setTabsClosable(True) self._tabs.tabCloseRequested[int].connect(self.tabClosedRequested) # self._tabs.addTab(self._plotPage, "plots") # create the network image display self._networkDisp = QtSvg.QSvgWidget('IEEE_30BusDC.svg') self._networkDisp.setMaximumSize(600, 500) # create the table for the measurements self._dgridValues = QTableWidget(10, 31, self._dgridPage) self._configure_table() # create the plot # self._plotCanvas = MyMplCanvas(self._plotPage, width=lamda10, height=4, dpi=100) # self._plotToolbar = NavigationToolbar(self._plotCanvas, self._plotPage) self._plotCanvas = {} self._plotToolbar = {} # create the shell self._scadaShell = ScadaShell(self) # setting the layout of the first tab, i.e., dgrid dgridLayout = QVBoxLayout(self._dgridPage) dgridLayout.addWidget(self._dgridValues) # setting the layout of the second tab, i.e., network networkLayout = QVBoxLayout(self._networkPage) networkLayout.addWidget(self._networkDisp) # setting the layout of the third tab, i.e., plots # plotLayout = QVBoxLayout(self._plotPage) # plotLayout.addWidget(self._plotCanvas) # plotLayout.addWidget(self._plotToolbar) # setting the layout of the main page self._mainLayout = QVBoxLayout() self._mainLayout.addWidget(self._tabs) self._mainLayout.addWidget(self._scadaShell) self.setLayout(self._mainLayout) self.setWindowTitle("Distributed Optimization SCADA") self.setMinimumSize(1100, 600) self.setWindowIcon(QtGui.QIcon('DOscada.png')) self._scadaShell.setFocus() def tabClosedRequested(self, tab_index): if tab_index >= 2: self._tabs.removeTab(tab_index) self._current_plot = -1 def _configure_table(self): # configure the row header for the table self._dgridValues.setVerticalHeaderItem(0, QTableWidgetItem("ON")) self._dgridValues.setVerticalHeaderItem(1, QTableWidgetItem("A2O[ms]")) self._dgridValues.setVerticalHeaderItem(2, QTableWidgetItem("V[pu]")) self._dgridValues.setVerticalHeaderItem(3, QTableWidgetItem("P[pu]")) self._dgridValues.setVerticalHeaderItem(4, QTableWidgetItem("Trip")) self._dgridValues.setVerticalHeaderItem(5, QTableWidgetItem("ADMM")) self._dgridValues.setVerticalHeaderItem(6, QTableWidgetItem("Opt [ms]")) self._dgridValues.setVerticalHeaderItem(7, QTableWidgetItem("RPC [ms]")) self._dgridValues.setVerticalHeaderItem(8, QTableWidgetItem("V* [pu]")) self._dgridValues.setVerticalHeaderItem(9, QTableWidgetItem("P* [pu]")) # configure the column header of the table for i in range(0, 31): self._dgridValues.setHorizontalHeaderItem( i, QTableWidgetItem(str(i + 1))) self._dgridValues.horizontalHeaderItem(i).setForeground( QtGui.QColor(255, 0, 0, 255)) # set the font-color for the generators columns in green self._dgridValues.horizontalHeaderItem(0).setForeground( QtGui.QColor(13, 148, 22, 255)) self._dgridValues.horizontalHeaderItem(1).setForeground( QtGui.QColor(13, 148, 22, 255)) self._dgridValues.horizontalHeaderItem(12).setForeground( QtGui.QColor(13, 148, 22, 255)) self._dgridValues.horizontalHeaderItem(21).setForeground( QtGui.QColor(13, 148, 22, 255)) self._dgridValues.horizontalHeaderItem(22).setForeground( QtGui.QColor(13, 148, 22, 255)) self._dgridValues.horizontalHeaderItem(26).setForeground( QtGui.QColor(13, 148, 22, 255)) font = self._dgridValues.horizontalHeader().font() font.setBold(True) self._dgridValues.horizontalHeader().setFont(font) # hide columns that don't have a coresponding network node self._dgridValues.setColumnHidden(4, True) self._dgridValues.setColumnHidden(8, True) self._dgridValues.setColumnHidden(10, True) # populate the table with empty cells for col in range(0, 31): for row in range(0, 10): self._dgridValues.setItem(row, col, QTableWidgetItem(" ")) font = self._dgridValues.item(row, col).font() font.setPixelSize(8) self._dgridValues.item(row, col).setFont(font) self._dgridValues.resizeColumnsToContents() self._dgridValues.resizeRowsToContents() def set_current_plot(self, plot_no): self._current_plot = plot_no def add_plot_tab(self, plot_no, plot_name): self._current_plot = plot_no self._plotPage[plot_no] = QWidget() self._tabs.addTab(self._plotPage[plot_no], str(plot_no) + " - " + plot_name) # create the plot self._plotCanvas[plot_no] = MyMplCanvas(self._plotPage[plot_no], width=5, height=4, dpi=100) self._plotToolbar[plot_no] = NavigationToolbar( self._plotCanvas[plot_no], self._plotPage[plot_no]) # setting the layout of the third tab, i.e., plots plotLayout = QVBoxLayout(self._plotPage[plot_no]) plotLayout.addWidget(self._plotCanvas[plot_no]) plotLayout.addWidget(self._plotToolbar[plot_no]) def agent_connected(self, ano): self._dgridValues.item(0, ano - 1).setText("ON") self._dgridValues.resizeRowsToContents() self._dgridValues.viewport().update() def agent_running_admm(self, ano): self._dgridValues.item(5, ano - 1).setText("ON") self._dgridValues.resizeRowsToContents() self._dgridValues.viewport().update() def agent_disconnected(self, ano): self._dgridValues.item(0, ano - 1).setText("OFF") self._dgridValues.resizeRowsToContents() self._dgridValues.viewport().update() def agent_update_values(self, ano, dt_opal, v_meas, p_meas, trip): self._dgridValues.item(1, ano - 1).setText('{:4.3f}'.format(dt_opal)) self._dgridValues.item(2, ano - 1).setText('{:4.3f}'.format(v_meas)) self._dgridValues.item(3, ano - 1).setText('{:4.3f}'.format(p_meas)) self._dgridValues.item(4, ano - 1).setText('{:4.3f}'.format(trip)) self._dgridValues.resizeColumnsToContents() self._dgridValues.viewport().update() def agent_finished_admm(self, ano, dt_opt, dt_rpc, v_ref, p_ref): self._dgridValues.item(5, ano - 1).setText('OFF') self._dgridValues.item(6, ano - 1).setText('{:4.3f}'.format(dt_opt)) self._dgridValues.item(7, ano - 1).setText('{:4.3f}'.format(dt_rpc)) self._dgridValues.item(8, ano - 1).setText('{:4.3f}'.format(v_ref)) self._dgridValues.item(9, ano - 1).setText('{:4.3f}'.format(p_ref)) self._dgridValues.resizeColumnsToContents() self._dgridValues.viewport().update() def plot_data(self, x, y, marker, label): if self._current_plot == -1: self._scadaShell.log_text("No plot selected!") else: self._plotCanvas[self._current_plot].update_figure( x, y, marker, label) def plot_hold(self, holdon): if self._current_plot == -1: self._scadaShell.log_text("No plot selected!") else: self._plotCanvas[self._current_plot].plot_hold(holdon)
class BrowserWindow(QMainWindow): def __init__(self, *args, **kwargs): # Strip out arguments we handle that are different from QMainWindow: if 'width' in kwargs: self.width = kwargs['width'] del kwargs['width'] else: self.width = 1024 if 'height' in kwargs: self.height = kwargs['height'] del kwargs['height'] else: # Short enough to fit in XGA, 768 height: self.height = 735 # Then run the default constructor. super(BrowserWindow, self).__init__(*args, **kwargs) self.browserviews = [] self.profile = QWebEngineProfile() # print("Profile initially off the record?", # self.profile.isOffTheRecord()) # "Off the record" doesn't mean much: QtWebEngine still # stores cache and maybe persistent cookies. # Here are some other attempts at privacy that might help a little: # self.cachedir = tempfile.mkdtemp() # self.profile.setCachePath(self.cachedir) # self.profile.setPersistentStoragePath(self.cachedir) # self.profile.setPersistentCookiesPolicy(self.profile.NoPersistentCookies); # but even with all those, QtWebEngine still stores a bunch of crap in # .local/share/quickbrowse/QtWebEngine/Default/ # But we can prevent that by lying about $HOME: os.environ["HOME"] = tempfile.mkdtemp() self.init_tab_name_len = 40 self.init_chrome() # Resize to fit on an XGA screen, allowing for window chrome. # XXX Should check the screen size and see if it can be bigger. self.resize(self.width, self.height) # Set up the listener for remote commands, the filename # and the buffer where we'll store those commands: self.cmdsockname = None self.cmdread = b'' self.set_up_listener() # Each process has one BrowserWindow, and each BrowserWindow has one # command pipe (a Unix-domain socket) where it can accept commands. def set_up_listener(self): # Make sure the socket does not already exist self.cmdsockname = CMD_PIPE % os.getpid() try: os.unlink(self.cmdsockname) except OSError: if os.path.exists(self.cmdsockname): raise # Create a UDS socket self.cmdsock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) # Bind the socket to the port self.cmdsock.bind(self.cmdsockname) # Listen for incoming connections self.cmdsock.listen(1) self.notifier = QSocketNotifier(self.cmdsock.fileno(), QSocketNotifier.Read) self.notifier.activated.connect(self.pipe_ready) def pipe_ready(self): connection, client_address = self.cmdsock.accept() with connection: try: while True: data = connection.recv(1024) if not data: break self.cmdread += data finally: # Clean up the connection connection.close() # Commands end with newlines. # Figure out how many commands we've read, if any. # It's possible to read more than one command at once, # or that we don't yet have all the data even for a single command. cmdlines = self.cmdread.split(b'\n') if self.cmdread.endswith(b'\n'): self.cmdread = b'' else: # Our last command is incomplete; we'll have to wait # for the rest of the line to come through. self.cmdread = cmdlines[-1] cmdlines = cmdlines[:-1] for cmd in cmdlines: if not cmd: continue # Change it from bytes to string: cmd = cmd.decode('utf-8') if cmd.startswith('new-tab ') and len(cmd) > 8: self.new_tab(cmd[8:]) @staticmethod def send_command(cmd, url): '''Send a command to a running quickbrowse process. This will usually be called from a separate, new, process. ''' # Start by finding the available CMD_PIPEs. # We'll use the one with the most recent ctime. pipedir, sockbase = os.path.split(CMD_PIPE) # Split off any %d in sockbase if '%' in sockbase: sockbase = sockbase[:sockbase.find('%')] flist = os.listdir(pipedir) cmdsockname = "" # This tests as less than any real string last_ctime = 0 for f in flist: if f.startswith(sockbase): sockname = os.path.join(pipedir, f) this_ctime = os.path.getctime(sockname) if this_ctime > last_ctime: cmdsockname = sockname last_ctime = this_ctime if not cmdsockname: raise IOError("No running quickbrowse process") sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) # Next line can raise socket.err, especially if there's no listener, # but rather than catching them, we'll raise it # and the caller can use that to decide to open a new window. sock.connect(cmdsockname) try: sock.sendall(b"%s %s\n" % (cmd.encode('utf-8'), url.encode('utf-8'))) finally: sock.close() def init_chrome(self): # Set up the browser window chrome: self.setWindowTitle("Quickbrowse") toolbar = QToolBar("Toolbar") self.addToolBar(toolbar) btn_act = QAction("Back", self) # for an icon: QAction(QIcon("bug.png"), "Your button", self) btn_act.setStatusTip("Go back") btn_act.triggered.connect(self.go_back) toolbar.addAction(btn_act) btn_act = QAction("Forward", self) btn_act.setStatusTip("Go forward") btn_act.triggered.connect(self.go_forward) toolbar.addAction(btn_act) btn_act = QAction("Reload", self) btn_act.setStatusTip("Reload") btn_act.triggered.connect(self.reload) toolbar.addAction(btn_act) self.urlbar = ReadlineEdit() self.urlbar.setPlaceholderText("URL goes here") self.urlbar.returnPressed.connect(self.urlbar_load) toolbar.addWidget(self.urlbar) self.tabwidget = QTabWidget() self.tabwidget.setTabBarAutoHide(True) self.setCentralWidget(self.tabwidget) self.tabwidget.tabBar().installEventFilter(self) self.prev_middle = -1 self.active_tab = 0 self.setStatusBar(QStatusBar(self)) self.progress = QProgressBar() self.statusBar().addPermanentWidget(self.progress) # Key bindings # For keys like function keys, use QtGui.QKeySequence("F12") QShortcut("Ctrl+Q", self, activated=self.close) QShortcut("Ctrl+L", self, activated=self.select_urlbar) QShortcut("Ctrl+T", self, activated=self.new_tab) QShortcut("Ctrl+R", self, activated=self.reload) QShortcut("Ctrl++", self, activated=self.zoom) QShortcut("Ctrl+=", self, activated=self.zoom) QShortcut("Ctrl+-", self, activated=self.unzoom) QShortcut("Alt+Left", self, activated=self.go_back) QShortcut("Alt+Right", self, activated=self.go_forward) QShortcut("Esc", self, activated=self.unfullscreen) def eventFilter(self, object, event): '''Handle button presses in the tab bar''' if object != self.tabwidget.tabBar(): print("eventFilter Not in tab bar") return super().eventFilter(object, event) # return False if event.type() not in [QEvent.MouseButtonPress, QEvent.MouseButtonRelease]: # print("Not a button press or release", event) return super().eventFilter(object, event) # return False tabindex = object.tabAt(event.pos()) if event.button() == Qt.LeftButton: if event.type() == QEvent.MouseButtonPress: self.active_tab = tabindex self.urlbar.setText(self.browserviews[tabindex].url().toDisplayString()) return super().eventFilter(object, event) # return False # So we'll still switch to that tab if event.button() == Qt.MidButton: if event.type() == QEvent.MouseButtonPress: self.prev_middle = tabindex else: if tabindex != -1 and tabindex == self.prev_middle: self.close_tab(tabindex) self.prev_middle = -1 return True print("Unknown button", event) return super().eventFilter(object, event) def new_tab(self, url=None): if url: init_name = url[:self.init_tab_name_len] else: init_name = "New tab" if is_pdf(url): webview = PDFBrowserView(self, url) self.browserviews.append(webview) self.tabwidget.addTab(webview, init_name) else: # The normal case, an HTML page webview = BrowserView(self) # We need a QWebEnginePage in order to get linkHovered events, # and to set an anonymous profile. # print("New tab, profile still off the record?", # self.profile.isOffTheRecord()) webpage = BrowserPage(self.profile, webview, self) # print("New Webpage off the record?", # webpage.profile().isOffTheRecord()) webview.setPage(webpage) # print("New view's page off the record?", # webview.page().profile().isOffTheRecord()) # print("In new tab, view is", webview, "and page is", webpage) self.browserviews.append(webview) self.tabwidget.addTab(webview, init_name) if url: self.load_url(url, len(self.browserviews)-1) # Set up the signals: webview.urlChanged.connect(webview.url_changed) webview.loadStarted.connect(webview.load_started) webview.loadFinished.connect(webview.load_finished) webview.loadProgress.connect(webview.load_progress) webpage.linkHovered.connect(webview.link_hover) return webview def focus_content(self): self.browserviews[self.active_tab].setFocus() def closeEvent(self, event): # Clean up if self.cmdsockname: os.unlink(self.cmdsockname) if os.environ["HOME"].startswith('/tmp/') and \ os.getenv('USER') not in os.environ["HOME"]: print("Cleaning up: removing %s" % os.environ["HOME"]) shutil.rmtree(os.environ["HOME"]) def close_tab(self, tabindex): self.tabwidget.removeTab(tabindex) def load_url(self, url, tab=None): '''Load the given URL in the specified tab, or current tab if tab=None. url is a str, not a QUrl. PDF URLs will be loaded in a new tab, because there doesn't seem to be a way of replacing the BrowserView with a BrowserPDFView. ''' # If there are newlines, remove newlines plus all adjacent whitespace. if '\n' in url: lines = url.split('\n') url = ''.join([ l.strip() for l in lines ]) # Note that tab=0 is a valid argument here. # When testing whether tab is set, be sure to test for None. if is_pdf(url): self.new_tab(url) return qurl = QUrl(url) if not qurl.scheme(): if os.path.exists(url): qurl.setScheme('file') if not os.path.isabs(url): # Is it better to use posixpath.join or os.path.join? # Both work on Linux. qurl.setPath(os.path.normpath(os.path.join(os.getcwd(), url))) else: qurl.setScheme('http') if len(self.browserviews) == 0: self.new_tab() tab = 0 elif tab == None: tab = self.active_tab self.set_tab_text(url[:self.init_tab_name_len], self.browserviews[tab]) if tab == self.active_tab: self.urlbar.setText(url) self.browserviews[tab].load(qurl) def load_html(self, html, base=None): '''Load a string containing HTML. The base is the file: URL the HTML should be considered to have come from, to avoid "Not allowed to load local resource" errors when clicking on links. ''' if not self.browserviews: self.new_tab() tab = 0 else: tab = self.active_tab self.set_tab_text("---", # XXX Replace with html title if possible self.browserviews[tab]) self.browserviews[tab].setHtml(html, QUrl(base)) def select_urlbar(self): self.urlbar.selectAll() self.urlbar.setFocus() def find_view(self, view): for i, v in enumerate(self.browserviews): if v == view: return i return None def set_tab_text(self, title, view): '''Set tab and, perhaps, window title after a page load. view is the requesting BrowserView, and will be compared to our browserviews[] to figure out which tab to set. ''' if self.tabwidget == None: return whichtab = None whichtab = self.find_view(view) if whichtab == None: print("Warning: set_tab_text for unknown view") return self.tabwidget.setTabText(whichtab, title) def zoom(self): if 'zoom' in dir(self.browserviews[self.active_tab]): self.browserviews[self.active_tab].zoom() def unzoom(self): if 'unzoom' in dir(self.browserviews[self.active_tab]): self.browserviews[self.active_tab].unzoom() def unfullscreen(self): if self.isFullScreen(): self.showNormal() # Some pages, like YouTube, want to know when the browser comes # out of fullscreen mode so it can adjust its chrome. self.browserviews[self.active_tab].page().triggerAction(QWebEnginePage.ExitFullScreen) def update_buttons(self): # TODO: To enable/disable buttons, check e.g. # self.webview.page().action(QWebEnginePage.Back).isEnabled()) pass # # Slots # def urlbar_load(self): url = self.urlbar.text() self.load_url(url) def go_back(self): self.browserviews[self.active_tab].back() def go_forward(self): self.browserviews[self.active_tab].forward() def reload(self): self.browserviews[self.active_tab].reload()
class AppWindow(QMainWindow): onRestart = pyqtSignal(name='onRestart') onSystemUIElementCreated = pyqtSignal(str, QWidget, name='onSystemUIElementCreated') onSystemUIElementRemoved = pyqtSignal(str, name='onSystemUIElementRemoved') def __init__(self, dwarf_args, flags=None): super(AppWindow, self).__init__(flags) self.dwarf_args = dwarf_args self.session_manager = SessionManager(self) self.session_manager.sessionCreated.connect(self.session_created) self.session_manager.sessionStopped.connect(self.session_stopped) self.session_manager.sessionClosed.connect(self.session_closed) self._tab_order = [ 'debug', 'modules', 'ranges', 'jvm-inspector', 'jvm-debugger' ] self._is_newer_dwarf = False self.q_settings = QSettings("dwarf_window_pos.ini", QSettings.IniFormat) self.menu = self.menuBar() self.view_menu = None self._initialize_ui_elements() self.setWindowTitle( 'Dwarf - A debugger for reverse engineers, crackers and security analyst' ) # load external assets _app = QApplication.instance() self.remove_tmp_dir() # themes self.prefs = Prefs() utils.set_theme(self.prefs.get('dwarf_ui_theme', 'black'), self.prefs) # load font if os.path.exists(utils.resource_path('assets/Anton.ttf')): QFontDatabase.addApplicationFont( utils.resource_path('assets/Anton.ttf')) if os.path.exists(utils.resource_path('assets/OpenSans-Regular.ttf')): QFontDatabase.addApplicationFont( utils.resource_path('assets/OpenSans-Regular.ttf')) font = QFont("OpenSans", 9, QFont.Normal) # TODO: add settingsdlg font_size = self.prefs.get('dwarf_ui_font_size', 12) font.setPixelSize(font_size) _app.setFont(font) if os.path.exists(utils.resource_path('assets/OpenSans-Bold.ttf')): QFontDatabase.addApplicationFont( utils.resource_path('assets/OpenSans-Bold.ttf')) # mainwindow statusbar self.progressbar = QProgressBar() self.progressbar.setRange(0, 0) self.progressbar.setVisible(False) self.progressbar.setFixedHeight(15) self.progressbar.setFixedWidth(100) self.progressbar.setTextVisible(False) self.progressbar.setValue(30) self.statusbar = QStatusBar(self) self.statusbar.setAutoFillBackground(False) self.statusbar.addPermanentWidget(self.progressbar) self.statusbar.setObjectName("statusbar") self.setStatusBar(self.statusbar) self.main_tabs = QTabWidget(self) self.main_tabs.setMovable(False) self.main_tabs.setTabsClosable(True) self.main_tabs.setAutoFillBackground(True) self.main_tabs.tabCloseRequested.connect(self._on_close_tab) self.setCentralWidget(self.main_tabs) # pluginmanager self.plugin_manager = PluginManager(self) self.plugin_manager.reload_plugins() if dwarf_args.any == '': self.welcome_window = WelcomeDialog(self) self.welcome_window.setModal(True) self.welcome_window.onIsNewerVersion.connect( self._enable_update_menu) self.welcome_window.onUpdateComplete.connect( self._on_dwarf_updated) self.welcome_window.setWindowTitle( 'Welcome to Dwarf - A debugger for reverse engineers, crackers and security analyst' ) self.welcome_window.onSessionSelected.connect(self._start_session) # wait for welcome screen self.hide() self.welcome_window.show() else: print('* Starting new Session') self._start_session(dwarf_args.target) def _initialize_ui_elements(self): # dockwidgets self.watchers_dwidget = None self.hooks_dwiget = None self.bookmarks_dwiget = None self.registers_dock = None self.console_dock = None self.backtrace_dock = None self.threads_dock = None # panels self.asm_panel = None self.backtrace_panel = None self.bookmarks_panel = None self.console_panel = None self.context_panel = None self.debug_panel = None self.contexts_list_panel = None self.data_panel = None self.ftrace_panel = None self.hooks_panel = None self.java_inspector_panel = None self.java_explorer_panel = None self.java_trace_panel = None self.modules_panel = None self.ranges_panel = None self.search_panel = None self.smali_panel = None self.watchers_panel = None self.welcome_window = None self._ui_elems = [] def _setup_main_menu(self): self.menu = self.menuBar() dwarf_menu = QMenu('Dwarf', self) theme = QMenu('Theme', dwarf_menu) theme.addAction('Black') theme.addAction('Dark') theme.addAction('Light') theme.triggered.connect(self._set_theme) dwarf_menu.addMenu(theme) dwarf_menu.addSeparator() if sys.platform == 'linux' or sys.platform == 'darwin': dwarf_bin_path = os.path.join( '/'.join(os.path.realpath(__file__).split('/')[:-2]), 'bin/dwarf') if not os.path.exists(dwarf_bin_path): dwarf_menu.addAction('Create launcher', utils.create_launcher) dwarf_menu.addSeparator() if self._is_newer_dwarf: dwarf_menu.addAction('Update', self._update_dwarf) dwarf_menu.addAction('Close', self.session_manager.session.stop) self.menu.addMenu(dwarf_menu) session = self.session_manager.session if session is not None: session_menu = session.main_menu if isinstance(session_menu, list): for menu in session_menu: self.menu.addMenu(menu) else: self.menu.addMenu(session_menu) # plugins if self.plugin_manager.plugins: self.plugin_menu = QMenu('Plugins', self) for plugin in self.plugin_manager.plugins: plugin_instance = self.plugin_manager.plugins[plugin] plugin_sub_menu = self.plugin_menu.addMenu( plugin_instance.name) try: actions = plugin_instance.__get_top_menu_actions__() for action in actions: plugin_sub_menu.addAction(action) except: pass if not plugin_sub_menu.isEmpty(): plugin_sub_menu.addSeparator() about = plugin_sub_menu.addAction('About') about.triggered.connect( lambda x, item=plugin: self._show_plugin_about(item)) if not self.plugin_menu.isEmpty(): self.menu.addMenu(self.plugin_menu) self.view_menu = QMenu('View', self) self.panels_menu = QMenu('Panels', self.view_menu) self.panels_menu.addAction('Search', lambda: self.show_main_tab('search'), shortcut=QKeySequence(Qt.CTRL + Qt.Key_F3)) self.view_menu.addMenu(self.panels_menu) self.debug_view_menu = self.view_menu.addMenu('Debug') self.view_menu.addSeparator() self.view_menu.addAction('Hide all', self._hide_all_widgets, shortcut=QKeySequence(Qt.CTRL + Qt.Key_F1)) self.view_menu.addAction('Show all', self._show_all_widgets, shortcut=QKeySequence(Qt.CTRL + Qt.Key_F2)) self.view_menu.addSeparator() self.menu.addMenu(self.view_menu) if self.dwarf_args.debug_script: debug_menu = QMenu('Debug', self) debug_menu.addAction('Reload core', self._menu_reload_core) debug_menu.addAction('Debug dwarf js core', self._menu_debug_dwarf_js) self.menu.addMenu(debug_menu) # tools _tools = self.prefs.get('tools') if _tools: tools_menu = QMenu('Tools', self) for _tool in _tools: if _tool and _tool['name']: if _tool['name'] == 'sep': tools_menu.addSeparator() continue _cmd = _tool['cmd'] tools_menu.addAction(_tool['name']) if not tools_menu.isEmpty(): tools_menu.triggered.connect(self._execute_tool) self.menu.addMenu(tools_menu) about_menu = QMenu('About', self) about_menu.addAction('Dwarf on GitHub', self._menu_github) about_menu.addAction('Documention', self._menu_documentation) about_menu.addAction('Api', self._menu_api) about_menu.addAction('Slack', self._menu_slack) about_menu.addSeparator() about_menu.addAction('Info', self._show_about_dlg) self.menu.addMenu(about_menu) def _show_plugin_about(self, plugin): plugin = self.plugin_manager.plugins[plugin] if plugin: info = plugin.__get_plugin_info__() version = utils.safe_read_map(info, 'version', '') description = utils.safe_read_map(info, 'description', '') author = utils.safe_read_map(info, 'author', '') homepage = utils.safe_read_map(info, 'homepage', '') license_ = utils.safe_read_map(info, 'license', '') utils.show_message_box( 'Name: {0}\nVersion: {1}\nDescription: {2}\nAuthor: {3}\nHomepage: {4}\nLicense: {5}' .format(plugin.name, version, description, author, homepage, license_)) def _enable_update_menu(self): self._is_newer_dwarf = True def _update_dwarf(self): if self.welcome_window: self.welcome_window._update_dwarf() def _on_close_tab(self, index): tab_text = self.main_tabs.tabText(index) if tab_text: tab_text = tab_text.lower().replace(' ', '-') if tab_text in self.session_manager.session.non_closable: return try: self._ui_elems.remove(tab_text) except ValueError: # recheck ValueError: list.remove(x): x not in list pass self.main_tabs.removeTab(index) self.onSystemUIElementRemoved.emit(tab_text) def _handle_tab_change(self): for index in range(self.main_tabs.count()): tab_name = self.main_tabs.tabText(index).lower().replace(' ', '-') if tab_name in self.session_manager.session.non_closable: self.main_tabs.tabBar().setTabButton(index, QTabBar.RightSide, None) if tab_name in self._tab_order: should_index = self._tab_order.index(tab_name) if index != should_index: self.main_tabs.tabBar().moveTab(index, should_index) def _on_dwarf_updated(self): self.onRestart.emit() def remove_tmp_dir(self): if os.path.exists('.tmp'): shutil.rmtree('.tmp', ignore_errors=True) def _execute_tool(self, qaction): if qaction: _tools = self.prefs.get('tools') if _tools: for _tool in _tools: if _tool and _tool['name'] and _tool['name'] != 'sep': if qaction.text() == _tool['name']: try: import subprocess subprocess.Popen(_tool['cmd'], creationflags=subprocess. CREATE_NEW_CONSOLE) except: pass break def _set_theme(self, qaction): if qaction: utils.set_theme(qaction.text(), self.prefs) def _hide_all_widgets(self): self.watchers_dwidget.hide() self.hooks_dwiget.hide() self.bookmarks_dwiget.hide() self.registers_dock.hide() self.console_dock.hide() self.backtrace_dock.hide() self.threads_dock.hide() def _show_all_widgets(self): self.watchers_dwidget.show() self.hooks_dwiget.show() self.bookmarks_dwiget.show() self.registers_dock.show() self.console_dock.show() self.backtrace_dock.show() self.threads_dock.show() def _menu_reload_core(self): self.dwarf.script.exports.reload() def _menu_debug_dwarf_js(self): you_know_what_to_do = json.loads( self.dwarf.script.exports.debugdwarfjs()) return you_know_what_to_do def show_main_tab(self, name): name = name.lower() # elem doesnt exists? create it if name not in self._ui_elems: self._create_ui_elem(name) index = 0 name = name.join(name.split()).lower() if name == 'ranges': index = self.main_tabs.indexOf(self.ranges_panel) elif name == 'search': index = self.main_tabs.indexOf(self.search_panel) elif name == 'modules': index = self.main_tabs.indexOf(self.modules_panel) elif name == 'data': index = self.main_tabs.indexOf(self.data_panel) elif name == 'jvm-tracer': index = self.main_tabs.indexOf(self.java_trace_panel) elif name == 'jvm-inspector': index = self.main_tabs.indexOf(self.java_inspector_panel) elif name == 'jvm-debugger': index = self.main_tabs.indexOf(self.java_explorer_panel) elif name == 'smali': index = self.main_tabs.indexOf(self.smali_panel) self.main_tabs.setCurrentIndex(index) def jump_to_address(self, ptr, view=0, show_panel=True): if show_panel: self.show_main_tab('debug') self.debug_panel.jump_to_address(ptr, view=view) @pyqtSlot(name='mainMenuGitHub') def _menu_github(self): QDesktopServices.openUrl(QUrl('https://github.com/iGio90/Dwarf')) @pyqtSlot(name='mainMenuApi') def _menu_api(self): QDesktopServices.openUrl( QUrl('http://www.giovanni-rocca.com/dwarf/javascript/')) @pyqtSlot(name='mainMenuDocumentation') def _menu_documentation(self): QDesktopServices.openUrl(QUrl('http://www.giovanni-rocca.com/dwarf/')) @pyqtSlot(name='mainMenuSlack') def _menu_slack(self): QDesktopServices.openUrl( QUrl('https://join.slack.com/t/resecret/shared_invite' '/enQtMzc1NTg4MzE3NjA1LTlkNzYxNTIwYTc2ZTYyOWY1MT' 'Q1NzBiN2ZhYjQwYmY0ZmRhODQ0NDE3NmRmZjFiMmE1MDYwN' 'WJlNDVjZDcwNGE')) def _show_about_dlg(self): about_dlg = AboutDialog(self) about_dlg.show() def _create_ui_elem(self, elem): elem = elem.lower() if not isinstance(elem, str): return if elem not in self._ui_elems: self._ui_elems.append(elem) elem_wiget = None if elem == 'watchers': from ui.session_widgets.watchers import WatchersWidget self.watchers_dwidget = QDockWidget('Watchers', self) self.watchers_panel = WatchersWidget(self) # dont respond to dblclick mem cant be shown # self.watchers_panel.onItemDoubleClicked.connect( # self._on_watcher_clicked) self.watchers_panel.onItemRemoved.connect( self._on_watcher_removeditem) self.watchers_panel.onItemAdded.connect(self._on_watcher_added) self.watchers_dwidget.setWidget(self.watchers_panel) self.watchers_dwidget.setObjectName('WatchersWidget') self.addDockWidget(Qt.LeftDockWidgetArea, self.watchers_dwidget) self.view_menu.addAction(self.watchers_dwidget.toggleViewAction()) elem_wiget = self.watchers_panel elif elem == 'hooks': from ui.session_widgets.hooks import HooksWidget self.hooks_dwiget = QDockWidget('Breakpoints', self) self.hooks_panel = HooksWidget(self) self.hooks_panel.onHookRemoved.connect(self._on_hook_removed) self.hooks_dwiget.setWidget(self.hooks_panel) self.hooks_dwiget.setObjectName('HooksWidget') self.addDockWidget(Qt.LeftDockWidgetArea, self.hooks_dwiget) self.view_menu.addAction(self.hooks_dwiget.toggleViewAction()) elem_wiget = self.hooks_panel elif elem == 'bookmarks': from ui.session_widgets.bookmarks import BookmarksWidget self.bookmarks_dwiget = QDockWidget('Boomarks', self) self.bookmarks_panel = BookmarksWidget(self) self.bookmarks_dwiget.setWidget(self.bookmarks_panel) self.bookmarks_dwiget.setObjectName('BookmarksWidget') self.addDockWidget(Qt.LeftDockWidgetArea, self.bookmarks_dwiget) self.view_menu.addAction(self.bookmarks_dwiget.toggleViewAction()) elem_wiget = self.bookmarks_panel elif elem == 'registers': from ui.session_widgets.context import ContextWidget self.registers_dock = QDockWidget('Context', self) self.context_panel = ContextWidget(self) self.registers_dock.setWidget(self.context_panel) self.registers_dock.setObjectName('ContextWidget') self.addDockWidget(Qt.RightDockWidgetArea, self.registers_dock) self.view_menu.addAction(self.registers_dock.toggleViewAction()) elem_wiget = self.context_panel elif elem == 'debug': from ui.panels.panel_debug import QDebugPanel self.debug_panel = QDebugPanel(self) self.main_tabs.addTab(self.debug_panel, 'Debug') elem_wiget = self.debug_panel elif elem == 'jvm-debugger': from ui.panels.panel_java_explorer import JavaExplorerPanel self.java_explorer_panel = JavaExplorerPanel(self) self.main_tabs.addTab(self.java_explorer_panel, 'JVM debugger') self.main_tabs.tabBar().moveTab( self.main_tabs.indexOf(self.java_explorer_panel), 1) elem_wiget = self.java_explorer_panel elif elem == 'jvm-inspector': from ui.panels.panel_java_inspector import JavaInspector self.java_inspector_panel = JavaInspector(self) self.main_tabs.addTab(self.java_inspector_panel, 'JVM inspector') elem_wiget = self.java_inspector_panel elif elem == 'console': from ui.session_widgets.console import ConsoleWidget self.console_dock = QDockWidget('Console', self) self.console_panel = ConsoleWidget(self) if self.dwarf_args.script and len( self.dwarf_args.script) > 0 and os.path.exists( self.dwarf_args.script): with open(self.dwarf_args.script, 'r') as f: self.console_panel.get_js_console( ).script_file = self.dwarf_args.script self.console_panel.get_js_console( ).function_content = f.read() self.dwarf.onLogToConsole.connect(self._log_js_output) self.dwarf.onLogEvent.connect(self._log_event) self.console_dock.setWidget(self.console_panel) self.console_dock.setObjectName('ConsoleWidget') self.addDockWidget(Qt.BottomDockWidgetArea, self.console_dock) self.view_menu.addAction(self.console_dock.toggleViewAction()) elem_wiget = self.console_panel elif elem == 'backtrace': from ui.session_widgets.backtrace import BacktraceWidget self.backtrace_dock = QDockWidget('Backtrace', self) self.backtrace_panel = BacktraceWidget(self) self.backtrace_dock.setWidget(self.backtrace_panel) self.backtrace_dock.setObjectName('BacktraceWidget') self.backtrace_panel.onShowMemoryRequest.connect( self._on_showmemory_request) self.addDockWidget(Qt.RightDockWidgetArea, self.backtrace_dock) self.view_menu.addAction(self.backtrace_dock.toggleViewAction()) elem_wiget = self.backtrace_panel elif elem == 'threads': from ui.session_widgets.threads import ThreadsWidget self.threads_dock = QDockWidget('Threads', self) self.contexts_list_panel = ThreadsWidget(self) self.dwarf.onThreadResumed.connect( self.contexts_list_panel.resume_tid) self.contexts_list_panel.onItemDoubleClicked.connect( self._manually_apply_context) self.threads_dock.setWidget(self.contexts_list_panel) self.threads_dock.setObjectName('ThreadPanel') self.addDockWidget(Qt.RightDockWidgetArea, self.threads_dock) self.view_menu.addAction(self.threads_dock.toggleViewAction()) elem_wiget = self.contexts_list_panel elif elem == 'modules': from ui.panels.panel_modules import ModulesPanel self.modules_panel = ModulesPanel(self) self.modules_panel.onModuleSelected.connect( self._on_module_dblclicked) self.modules_panel.onModuleFuncSelected.connect( self._on_modulefunc_dblclicked) self.modules_panel.onAddHook.connect(self._on_addmodule_hook) self.modules_panel.onDumpBinary.connect(self._on_dumpmodule) self.main_tabs.addTab(self.modules_panel, 'Modules') elem_wiget = self.modules_panel elif elem == 'ranges': from ui.panels.panel_ranges import RangesPanel self.ranges_panel = RangesPanel(self) self.ranges_panel.onItemDoubleClicked.connect( self._range_dblclicked) self.ranges_panel.onDumpBinary.connect(self._on_dumpmodule) # connect to watcherpanel func self.ranges_panel.onAddWatcher.connect( self.watchers_panel.do_addwatcher_dlg) self.main_tabs.addTab(self.ranges_panel, 'Ranges') elem_wiget = self.ranges_panel elif elem == 'search': from ui.panels.panel_search import SearchPanel self.search_panel = SearchPanel(self) self.main_tabs.addTab(self.search_panel, 'Search') elem_wiget = self.search_panel elif elem == 'data': from ui.panels.panel_data import DataPanel self.data_panel = DataPanel(self) self.main_tabs.addTab(self.data_panel, 'Data') elem_wiget = self.data_panel elif elem == 'jvm-tracer': from ui.panels.panel_java_trace import JavaTracePanel self.java_trace_panel = JavaTracePanel(self) self.main_tabs.addTab(self.java_trace_panel, 'JVM tracer') elem_wiget = self.java_trace_panel elif elem == 'smali': from ui.panels.panel_smali import SmaliPanel self.smali_panel = SmaliPanel() self.main_tabs.addTab(self.smali_panel, 'Smali') elem_wiget = self.smali_panel else: print('no handler for elem: ' + elem) # make tabs unclosable and sort self._handle_tab_change() if elem_wiget is not None: self.onSystemUIElementCreated.emit(elem, elem_wiget) # TODO: remove add @2x for item in self.findChildren(QDockWidget): if item: if 'darwin' in sys.platform: item.setStyleSheet( 'QDockWidget::title { padding-left:-30px; }') def set_status_text(self, txt): self.statusbar.showMessage(txt) # ************************************************************************ # **************************** Properties ******************************** # ************************************************************************ @property def disassembly(self): return self.asm_panel @property def backtrace(self): return self.backtrace_panel @property def console(self): return self.console_panel @property def context(self): return self.context_panel @property def threads(self): return self.contexts_list_panel @property def ftrace(self): return self.ftrace_panel @property def hooks(self): return self.hooks_panel @property def java_inspector(self): return self.java_inspector_panel @property def java_explorer(self): return self.java_explorer_panel @property def modules(self): return self.modules_panel @property def ranges(self): return self.ranges_panel @property def watchers(self): return self.watchers_panel @property def dwarf(self): if self.session_manager.session is not None: return self.session_manager.session.dwarf else: return None @property def ui_elements(self): return self._ui_elems # ************************************************************************ # **************************** Handlers ********************************** # ************************************************************************ # session handlers def _start_session(self, session_type, session_data=None): if self.welcome_window is not None: self.welcome_window.close() self.session_manager.create_session(session_type, session_data=session_data) def _restore_session(self, session_data): if 'session' in session_data: session_type = session_data['session'] self.dwarf_args.any = session_data['package'] self._start_session(session_type, session_data=session_data) def session_created(self): # session init done create ui for it session = self.session_manager.session self._setup_main_menu() for ui_elem in session.session_ui_sections: ui_elem = ui_elem.join(ui_elem.split()).lower() self._create_ui_elem(ui_elem) self.dwarf.onProcessAttached.connect(self._on_attached) self.dwarf.onProcessDetached.connect(self._on_detached) self.dwarf.onScriptLoaded.connect(self._on_script_loaded) # hookup self.dwarf.onSetRanges.connect(self._on_setranges) self.dwarf.onSetModules.connect(self._on_setmodules) self.dwarf.onAddNativeHook.connect(self._on_add_hook) self.dwarf.onApplyContext.connect(self._apply_context) self.dwarf.onThreadResumed.connect(self.on_tid_resumed) self.dwarf.onSetData.connect(self._on_set_data) self.session_manager.start_session(self.dwarf_args) ui_state = self.q_settings.value('dwarf_ui_state') if ui_state: self.restoreGeometry(ui_state) window_state = self.q_settings.value('dwarf_ui_window', self.saveState()) if window_state: self.restoreState(window_state) self.showMaximized() def session_stopped(self): self.remove_tmp_dir() self.menu.clear() self.main_tabs.clear() # actually we need to kill this. needs a refactor if self.java_trace_panel is not None: self.java_trace_panel = None for elem in self._ui_elems: if elem == 'watchers': self.watchers_panel.clear_list() self.watchers_panel.close() self.watchers_panel = None self.removeDockWidget(self.watchers_dwidget) self.watchers_dwidget = None elif elem == 'hooks': self.hooks_panel.close() self.hooks_panel = None self.removeDockWidget(self.hooks_dwiget) self.hooks_dwiget = None elif elem == 'registers': self.context_panel.close() self.context_panel = None self.removeDockWidget(self.registers_dock) self.registers_dock = None elif elem == 'debug': self.debug_panel.close() self.debug_panel = None self.main_tabs.removeTab(0) # self.main_tabs elif elem == 'jvm-debugger': self.java_explorer_panel.close() self.java_explorer_panel = None self.removeDockWidget(self.watchers_dwidget) elif elem == 'console': self.console_panel.close() self.console_panel = None self.removeDockWidget(self.console_dock) self.console_dock = None elif elem == 'backtrace': self.backtrace_panel.close() self.backtrace_panel = None self.removeDockWidget(self.backtrace_dock) elif elem == 'threads': self.contexts_list_panel.close() self.contexts_list_panel = None self.removeDockWidget(self.threads_dock) self.threads_dock = None elif elem == 'bookmarks': self.bookmarks_panel.close() self.bookmarks_panel = None self.removeDockWidget(self.bookmarks_dwiget) self.bookmarks_dwiget = None self._initialize_ui_elements() def session_closed(self): self._initialize_ui_elements() self.hide() if self.welcome_window is not None: self.welcome_window.exec() # close if it was a commandline session if self.welcome_window is None: if self.dwarf_args.any != '': self.close() # ui handler def closeEvent(self, event): """ Window closed save stuff or whatever at exit detaches dwarf """ # save windowstuff self.q_settings.setValue('dwarf_ui_state', self.saveGeometry()) self.q_settings.setValue('dwarf_ui_window', self.saveState()) if self.dwarf: try: self.dwarf.detach() except: pass super().closeEvent(event) def _on_watcher_clicked(self, ptr): """ Address in Watcher/Hookpanel was clicked show Memory """ if '.' in ptr: # java_hook file_path = ptr.replace('.', os.path.sep) if os.path.exists('.tmp/smali/' + file_path + '.smali'): if self.smali_panel is None: self._create_ui_elem('smali') self.smali_panel.set_file('.tmp/smali/' + file_path + '.smali') self.show_main_tab('smali') else: self.jump_to_address(ptr) def _on_watcher_added(self, ptr): """ Watcher Entry was added """ try: # set highlight self.debug_panel.memory_panel.add_highlight( HighLight('watcher', ptr, self.dwarf.pointer_size)) except HighlightExistsError: pass def _on_watcher_removeditem(self, ptr): """ Watcher Entry was removed remove highlight too """ self.debug_panel.memory_panel.remove_highlight(ptr) def _on_module_dblclicked(self, data): """ Module in ModulePanel was doubleclicked """ addr, size = data addr = utils.parse_ptr(addr) self.jump_to_address(addr) def _on_modulefunc_dblclicked(self, ptr): """ Function in ModulePanel was doubleclicked """ ptr = utils.parse_ptr(ptr) self.jump_to_address(ptr) def _on_dumpmodule(self, data): """ DumpBinary MenuItem in ModulePanel was selected """ ptr, size = data ptr = utils.parse_ptr(ptr) size = int(size, 10) self.dwarf.dump_memory(ptr=ptr, length=size) def _disassemble_range(self, dwarf_range): """ Disassemble MenuItem in Hexview was selected """ if dwarf_range: if self.asm_panel is None: self._create_ui_elem('disassembly') self.asm_panel.disassemble(dwarf_range) self.show_main_tab('disassembly') def _range_dblclicked(self, ptr): """ Range in RangesPanel was doubleclicked """ ptr = utils.parse_ptr(ptr) self.jump_to_address(ptr) # dwarf handlers def _log_js_output(self, output): if self.console_panel is not None: time_prefix = True if len(output.split('\n')) > 1 or len(output.split('<br />')) > 1: time_prefix = False self.console_panel.get_js_console().log(output, time_prefix=time_prefix) def _log_event(self, output): if self.console_panel is not None: self.console_panel.get_events_console().log(output) def _on_setranges(self, ranges): """ Dwarf wants to set Ranges only hooked up to switch tab or create ui its connected in panel after creation """ if self.ranges_panel is None: self.show_main_tab('ranges') # forward only now to panel it connects after creation self.ranges_panel.set_ranges(ranges) def _on_setmodules(self, modules): """ Dwarf wants to set Modules only hooked up to switch tab or create ui its connected in panel after creation """ if self.modules_panel is None: self._create_ui_elem('modules') self.modules_panel.set_modules(modules) if self.modules_panel is not None: self.show_main_tab('modules') def _manually_apply_context(self, context): """ perform additional operation if the context has been manually applied from the context list """ self._apply_context(context, manual=True) def _apply_context(self, context, manual=False): # update current context tid # this should be on top as any further api from js needs to be executed on that thread reason = context['reason'] is_initial_setup = reason == -1 if manual or (self.dwarf.context_tid and not is_initial_setup): self.dwarf.context_tid = context['tid'] if is_initial_setup: self.debug_panel.on_context_setup() if 'context' in context: if not manual: self.threads.add_context(context) is_java = context['is_java'] if is_java: if self.java_explorer_panel is None: self._create_ui_elem('jvm-debugger') self.context_panel.set_context(context['ptr'], 1, context['context']) self.java_explorer_panel._set_handle_arg(-1) self.show_main_tab('jvm-debugger') else: self.context_panel.set_context(context['ptr'], 0, context['context']) if reason == 2: # native on load if self.debug_panel.memory_panel_range is None: base = context['moduleBase'] self.jump_to_address(base) else: if 'pc' in context['context']: if self.debug_panel.disassembly_panel_range is None or manual: self.jump_to_address( context['context']['pc']['value'], view=1) if 'backtrace' in context: self.backtrace_panel.set_backtrace(context['backtrace']) def _on_add_hook(self, hook): try: # set highlight ptr = hook.get_ptr() ptr = utils.parse_ptr(ptr) self.debug_panel.memory_panel.add_highlight( HighLight('hook', ptr, self.dwarf.pointer_size)) except HighlightExistsError: pass def _on_hook_removed(self, ptr): ptr = utils.parse_ptr(ptr) self.debug_panel.memory_panel.remove_highlight(ptr) def _on_addmodule_hook(self, data): ptr, name = data self.dwarf.hook_native(ptr, own_input=name) def on_tid_resumed(self, tid): if self.dwarf: if self.dwarf.context_tid == tid: # clear backtrace if 'backtrace' in self._ui_elems: if self.backtrace_panel is not None: self.backtrace_panel.clear() # remove thread if 'threads' in self._ui_elems: if self.contexts_list_panel is not None: self.contexts_list_panel.resume_tid(tid) # clear registers if 'registers' in self._ui_elems: if self.context_panel is not None: self.context_panel.clear() # clear jvm explorer if 'jvm-debugger' in self._ui_elems: if self.java_explorer_panel is not None: self.java_explorer_panel.clear_panel() # invalidate dwarf context tid self.dwarf.context_tid = 0 def _on_set_data(self, data): if not isinstance(data, list): return if self.data_panel is None: self.show_main_tab('data') if self.data_panel is not None: self.data_panel.append_data(data[0], data[1], data[2]) def show_progress(self, text): self.progressbar.setVisible(True) self.set_status_text(text) def hide_progress(self): self.progressbar.setVisible(False) self.set_status_text('') def _on_attached(self, data): self.setWindowTitle('Dwarf - Attached to %s (%s)' % (data[1], data[0])) def _on_detached(self, data): reason = data[1] if reason == 'application-requested': self.session_manager.session.stop() return 0 if self.dwarf is not None: ret = QDialogDetached.show_dialog(self.dwarf, data[0], data[1], data[2]) if ret == 0: self.dwarf.restart_proc() elif ret == 1: self.session_manager.session.stop() return 0 def _on_script_loaded(self): # restore the loaded session if any self.session_manager.restore_session() def on_add_bookmark(self, ptr): """ provide ptr as int """ if self.bookmarks_panel is not None: self.bookmarks_panel._create_bookmark(ptr=hex(ptr)) def _on_showmemory_request(self, ptr): # its simple ptr show in memorypanel if isinstance(ptr, str): ptr = utils.parse_ptr(ptr) self.jump_to_address(ptr, 0) elif isinstance(ptr, list): # TODO: extend caller, ptr = ptr ptr = utils.parse_ptr(ptr) if caller == 'backtrace' or caller == 'bt': # jumpto in disasm self.jump_to_address(ptr, 1)
class SettingsWidget(QWidget, MooseWidget): """ A very simple widget to modify settings. This widget actually doesn't do anything, it just holds various tabs. Other widgets are responsible for creating their own settings widget and adding it here. """ def __init__(self, **kwds): """ Constructor. """ super(SettingsWidget, self).__init__(**kwds) self.top_layout = WidgetUtils.addLayout(vertical=True) self.setLayout(self.top_layout) self.tabs = QTabWidget(parent=self) self.top_layout.addWidget(self.tabs) self.button_layout = WidgetUtils.addLayout() self.top_layout.addLayout(self.button_layout) self.save_button = WidgetUtils.addButton(self.button_layout, self, "&Save", self._save) self.cancel_button = WidgetUtils.addButton(self.button_layout, self, "&Cancel", self._cancel) self.setup() def addTab(self, name, widget, index=-1): """ Add a new tab for settings. Input: name: name of the tab widget: widget of settings index: index of where to put the tab. Default is at the beginning. """ self.removeTab(name) self.tabs.insertTab(index, widget, name) def removeTab(self, name): """ Remove a tab with a given name. Input: name: name of the tab. If it doesn't exist, nothing happens. """ for i in range(self.tabs.count()): if self.tabs.tabText(i) == name: self.tabs.removeTab(i) break def load(self): """ Loads all the settings from the different widgets. """ settings = QSettings() for i in range(self.tabs.count()): w = self.tabs.widget(i) w.load(settings) def _save(self): """ Saves all the settings from the different widgets. """ settings = QSettings() for i in range(self.tabs.count()): w = self.tabs.widget(i) w.save(settings) settings.sync() self.close() def _cancel(self): """ They didn't want to save. """ self.close()
class MainLayout: def __init__(self): self.parameters = {} self.main = QVBoxLayout() container = QHBoxLayout() self.opf = QFormLayout() title = QLabel("Set Operational Parameters") title.setFont(QFont('Helvetica', 14)) title.setAlignment(Qt.AlignCenter) self.opf.addRow(title) self.opf.setVerticalSpacing(20) ops = [('Nm', "Speed of Motor", '1450'), ('Nd', "Speed of Digester Shaft", '109'), ('Nc', "Speed of Cake Breaker Shaft", '109'), ('Na', "Speed of Auger Shaft", '218'), ('Nsp', "Speed of Screw Press Shaft", '60')] self.defaults = QPushButton('Clear') self.defaults.setToolTip('Toggle this button to use optimized values for Operational Parameters') self.defaults.setCheckable(1) self.defaults.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.defaults.toggled.connect(self._defaults) dl = QLabel('Reset') dl.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) for op in ops: opp = QLineEdit() opp.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) opp.setText(op[2]) lb = QLabel(op[1]) lb.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) opp.setPlaceholderText(op[1]) opp.setObjectName(op[1]) opp.setAccessibleName(op[0]) opp.setInputMask('>0000;_') self.opf.addRow(lb, opp) self.opf.addRow(dl, self.defaults) frame = QFrame() frame.setLayout(self.opf) frame.setObjectName('opf') # Layout for Throughput input tpf = QVBoxLayout() self.capacity = QLineEdit() self.capacity.setAlignment(Qt.AlignLeft | Qt.AlignCenter) self.capacity.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) dv = DV(280, 5650, 3, self.capacity) dv.setNotation(DV.StandardNotation) self.capacity.setValidator(dv) self.capacity.setPlaceholderText('Enter value between 280 and 5650') title = QLabel("Set Throughput Capacity") title.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) title.setFont(QFont('Helvetica', 14)) title.setAlignment(Qt.AlignLeft | Qt.AlignCenter) tpf.addWidget(title) tpf.addSpacing(15) tpf.addWidget(self.capacity) tpf.setAlignment(Qt.AlignLeft | Qt.AlignCenter) frame1 = QFrame() frame1.setLayout(tpf) frame1.setObjectName('tpf') container.addWidget(frame1) container.addSpacing(50) container.addWidget(frame) self.bs = QHBoxLayout() self.compute = QPushButton('Compute') self.compute.setCheckable(True) self.compute.clicked.connect(self.run) self.reset = QPushButton('Reset') self.reset.setCheckable(True) self.reset.clicked.connect(self._reset) self.reset.setEnabled(False) self.report = QPushButton('Generate Report') self.report.setCheckable(True) self.report.clicked.connect(self._generate) self.report.setEnabled(False) self.bs.addWidget(self.compute) self.bs.setSpacing(15) self.bs.addWidget(self.report) self.bs.addWidget(self.reset) self.bs.setAlignment(Qt.AlignCenter | Qt.AlignBottom) self.main.addLayout(container) self.main.addSpacing(20) self.main.addLayout(self.bs) mframe = QFrame() mframe.setLayout(self.main) mframe.setObjectName('main') mframe.setFrameShape(QFrame.StyledPanel) mframe.setFrameStyle(QFrame.Raised | QFrame.Panel) mframe.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) ml = QVBoxLayout() ml.addWidget(mframe) ml.setAlignment(Qt.AlignCenter | Qt.AlignTop) self.tabs = QTabWidget() self.tabs.setTabPosition(QTabWidget.North) self.tabs.setMovable(True) self.widget = QWidget() self.widget.setObjectName('mainf') self.widget.setContentsMargins(0, 50, 0, 0) self.widget.setLayout(ml) self.tabs.insertTab(0, self.widget, 'Results') def _defaults(self, s): dp = {'Nm': '1450', 'Nd': '109', 'Nc': '109', 'Na': '218', 'Nsp': '60'} num = self.opf.count() if s: self.defaults.setText('Defaults') else: self.defaults.setText('Clear') for i in range(num): child = self.opf.itemAt(i).widget() if isinstance(child, QLineEdit): if s: child.setText('') else: child.setText(dp[child.accessibleName()]) def _generate(self): path = homedir('m') path = str(path / self.parameters['filename']) file, _ = QFileDialog.getSaveFileName(self.report, 'Save Manual', path, "PDF Format (*.pdf)") if file: BuildDoc(self.parameters, file) def run(self, s): res = self.validate() if res['status']: tpd, op = res['results'][0], res['results'][1:] output = model(tpd, op) self.parameters = format_results(output, tpd) self.buiildTables() self.showImages() self.compute.setDisabled(True) self.reset.setEnabled(True) self.report.setEnabled(True) else: self.msgBox(res['err']) def _reset(self, r): self.compute.setDisabled(False) while self.tabs.count() > 1: self.tabs.removeTab(1) self.reset.setEnabled(False) self.report.setEnabled(False) def validate(self): err = '' inputs = [] try: if (i := self.capacity.text()) and (280 <= int(i) <= 5650): inputs.append(int(i)) else:
class MainWindow(QMainWindow): def __init__(self, os_path, ip, port): super(MainWindow, self).__init__() self.os_path = os_path self.config_path = os_path.split('ics_sps_engineering_plotData')[ 0] + 'ics_sps_engineering_Lib_dataQuery/config/' self.path_img = self.os_path + "/img/" self.readCfg(self.config_path) self.db = DatabaseManager(ip, port) no_err = self.db.initDatabase() if no_err != -1: self.getIcons() self.getWidgets() else: self.showError(no_err) self.width = 1024 self.height = 768 self.center = [300, 300] self.title = "AIT-PFS Monitoring CU" self.resize(self.width, self.height) self.move(self.center[0], self.center[1]) self.setWindowTitle(self.title) self.showMaximized() self.show() def getIcons(self): arrow_left = QPixmap() arrow_right = QPixmap() arrow_left.load(self.path_img + 'arrow_left.png') arrow_right.load(self.path_img + 'arrow_right.png') self.icon_arrow_left = QIcon(arrow_left) self.icon_arrow_right = QIcon(arrow_right) icon_math = QPixmap() icon_math.load(self.path_img + 'xy2.png') icon_math_on = QPixmap() icon_math_on.load(self.path_img + 'xy2_on.png') self.icon_vcursor = QIcon(icon_math) self.icon_vcursor_on = QIcon(icon_math_on) icon_fit = QPixmap() icon_fit.load(self.path_img + 'infini.png') self.icon_fit = QIcon(icon_fit) def getWidgets(self): self.tab_widget = QTabWidget() self.tab_widget.tabCloseRequested.connect(self.delTab) self.tab_widget.setTabsClosable(True) self.getdockCalendar() self.getdockAlarm() self.getMenu() self.addDockWidget(Qt.TopDockWidgetArea, self.qdockalarm) self.setCentralWidget(self.tab_widget) def getMenu(self): self.menubar = self.menuBar() self.database_action = QAction('Database', self) self.curves_action = QAction('Update Configuration', self) self.new_tab_action = QAction('Open a new tab', self) self.about_action = QAction('About', self) self.curves_action.triggered.connect(self.setNewConfig) self.new_tab_action.triggered.connect(self.addTab) self.database_action.triggered.connect(self.calendar.show) self.about_action.triggered.connect( partial(self.showInformation, "PlotData v0.6 working with lib_DataQuery v0.6\n\r made for PFS by ALF")) self.WindowsMenu = self.menubar.addMenu('&Windows') self.WindowsMenu.addAction(self.new_tab_action) self.configurationMenu = self.menubar.addMenu('&Configuration') self.configurationMenu.addAction(self.database_action) self.configurationMenu.addAction(self.curves_action) self.helpMenu = self.menubar.addMenu('&?') self.helpMenu.addAction(self.about_action) def getdockCalendar(self): self.calendar = Calendar(self) def getdockAlarm(self): self.qdockalarm_widget = alarmChecker(parent=self) self.qdockalarm = myQDockWidget() self.qdockalarm.setWidget(self.qdockalarm_widget) def readCfg(self, path, last=True): res = [] all_file = next(os.walk(path))[-1] for f in all_file: config = ConfigParser.ConfigParser() config.readfp(open(path + f)) try: date = config.get('config_date', 'date') res.append((f, dt.datetime.strptime(date, "%d/%m/%Y"))) except ConfigParser.NoSectionError: pass config = ConfigParser.ConfigParser() if last: res.sort(key=lambda tup: tup[1]) config.readfp(open(path + res[-1][0])) else: res2 = [] for f, datetime in res: if self.calendar.mydatetime > datetime: res2.append((f, self.calendar.mydatetime - datetime)) if res2: res2.sort(key=lambda tup: tup[1]) config.readfp(open(path + res2[0][0])) else: res.sort(key=lambda tup: tup[1]) config.readfp(open(path + res[0][0])) self.device_dict = {} for a in config.sections(): if a != 'config_date': inter = {} for b in config.options(a): if b == "label_device": self.device_dict[a] = {"label_device": config.get(a, b)} else: inter[b] = config.get(a, b).split(',') inter[b] = self.cleanSpace(inter[b]) for keys, types, labels, units, ylabels in zip(inter["key"], inter["type"], inter["label"], inter["unit"], inter["ylabel"]): self.device_dict[a][keys] = {} self.device_dict[a][keys]["type"] = types self.device_dict[a][keys]["label"] = labels self.device_dict[a][keys]["unit"] = units self.device_dict[a][keys]["ylabel"] = ylabels def setNewConfig(self): self.readCfg(self.config_path) self.qdockalarm_widget.getTimeout() self.showInformation("New configuration loaded") def addTab(self): text, ok = QInputDialog.getText(self, 'Name your tab', 'Name') if ok: name = str(text) widget = Tab(self) self.tab_widget.addTab(widget, name) self.tab_widget.setCurrentWidget(widget) def delTab(self, k): reply = QMessageBox.question(self, 'Message', "Are you sure to close this window?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: self.tab_widget.removeTab(k) def showError(self, nb_error): error_code = {-1: "The database is unreachable, check your network and your configuration", -2: "They're not such columns / rows in your database", -3: "Bad format date", -4: "No data to display", -5: "network lost"} reply = QMessageBox.critical(self, 'Message', error_code[nb_error], QMessageBox.Ok) def showInformation(self, information): reply = QMessageBox.information(self, 'Message', information, QMessageBox.Ok) def getNumdate(self): return self.calendar.mydate_num def cleanSpace(self, tab): for i, s in enumerate(tab): if tab[i][0] == ' ': tab[i] = tab[i][1:] if tab[i][-1] == ' ': tab[i] = tab[i][:-1] return tab
class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): url = "http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D/" if "url" in kwargs: url = kwargs["url"] del kwargs["url"] if "zeronet_path" in kwargs: self.zeronet_path = kwargs["zeronet_path"] del kwargs["zeronet_path"] super(MainWindow, self).__init__(*args, **kwargs) # Tabs self.tabs = QTabWidget() self.tabs.setTabsClosable(True) self.tabs.tabCloseRequested.connect(self.close_tab) # New tab button #self.tab_add_button_index = self.tabs.addTab(QWidget(), '+') self.add_tab_button = QToolButton() self.add_tab_button.setText('+') self.add_tab_button.setStyleSheet( 'QToolButton {border: none; margin: 4px 20px 4px 0px; height: 480px; border-left: 1px solid lightgrey; padding: 0px 4px 0px 4px; font-weight: bold; color: #5d5b59}' 'QToolButton:hover { background-color: lightgrey }' 'QToolButton:pressed { background-color: grey }') self.add_tab_button.clicked.connect(self.new_tab_clicked) self.tabs.setCornerWidget(self.add_tab_button) # Navigation bar self.navigation = NavigationBar() self.navigation.url_bar.returnPressed.connect(self.navigate_to_url) # Back self.navigation.back_btn.triggered.connect( lambda: self.tabs.currentWidget().back()) # Next self.navigation.next_btn.triggered.connect( lambda: self.tabs.currentWidget().forward()) # Reload self.navigation.reload_btn.triggered.connect( lambda: self.tabs.currentWidget().reload()) self.navigation.shortcut_reload.activated.connect( lambda: self.tabs.currentWidget().reload()) self.navigation.shortcut_reload_f5.activated.connect( lambda: self.tabs.currentWidget().reload()) # Home self.navigation.home_btn.triggered.connect(self.go_home) # Menu: Edit config action self.navigation.edit_config_action.triggered.connect( self.edit_zeronet_config_file) # Add new tab self.add_new_tab(url, "Home") # Get everything fitting in the main window self.addToolBar(self.navigation) self.setCentralWidget(self.tabs) self.show() self.setWindowTitle("ZeroNet Browser") self.setWindowIcon(QIcon("icons/zeronet-logo.svg")) self.showMaximized() def contextMenuEvent(self, event): print(event) def update_url_bar(self, q, browser=None): if browser != self.tabs.currentWidget(): # If this signal is not from the current tab, ignore return url_array = q.toString().split('/')[3:] formatted_url = '/'.join(str(x) for x in url_array) self.navigation.url_bar.setText('zero://' + formatted_url) self.navigation.url_bar.setCursorPosition(0) if (self.tabs.currentWidget().can_go_back()): self.navigation.back_btn.setDisabled(False) else: self.navigation.back_btn.setDisabled(True) if (self.tabs.currentWidget().can_go_forward()): self.navigation.next_btn.setDisabled(False) else: self.navigation.next_btn.setDisabled(True) def navigate_to_url(self): # Get url url = self.navigation.url_bar.text() if url.startswith('zero://'): # ZeroNet protocol url_array = url.split('/') url = 'http://127.0.0.1:43110/' + url_array[2] elif url.startswith('http://'): # http protocol pass else: # Nothing mentionned url = 'http://127.0.0.1:43110/' + url self.tabs.currentWidget().setUrl(QUrl(url)) def go_home(self): self.tabs.currentWidget().setUrl( QUrl("http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D/")) def new_tab_clicked(self): self.add_new_tab( "http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D/", "Home") def get_link_url_from_context_menu(self): tab = self.tabs.currentWidget() page = tab.page() context = page.contextMenuData() qurl = context.linkUrl() return qurl.url() def open_in_new_tab(self): url = self.get_link_url_from_context_menu() self.add_new_tab(url, "Home") # Doesnt feel right to have it here but it is working def open_in_new_window(self): url = self.get_link_url_from_context_menu() kwargs = {"url": url} self.window = self.__class__(**kwargs) def add_new_tab(self, qurl, label): # Instead of browser it should be called WebView ! browser = Browser() # Triggered open in new tab openLinkInNewTabAction = browser.pageAction( QWebEnginePage.OpenLinkInNewTab) openLinkInNewWindowAction = browser.pageAction( QWebEnginePage.OpenLinkInNewWindow) openLinkInNewTabAction.triggered.connect(self.open_in_new_tab) openLinkInNewWindowAction.triggered.connect(self.open_in_new_window) self.addAction(openLinkInNewTabAction) browser.urlChanged.connect( lambda qurl, browser=browser: self.update_url_bar(qurl, browser)) indexTab = self.tabs.addTab(browser, label) # Maybe change current index after loading? self.tabs.setCurrentIndex(indexTab) # We need to update the url ! if qurl.startswith('zero://'): # ZeroNet protocol url_array = qurl.split('/') qurl = 'http://127.0.0.1:43110/' + url_array[2] elif qurl.startswith('http://'): # http protocol pass else: # Nothing mentionned qurl = 'http://127.0.0.1:43110/' + qurl currentTab = self.tabs.currentWidget() currentTab.loadFinished.connect(self.page_loaded) index = self.tabs.currentIndex() currentTab.titleChanged.connect( lambda title, index=index: self.tabs.setTabText(index, title)) currentTab.iconChanged.connect( lambda icon, index=index: self.tabs.setTabIcon(index, icon)) currentTab.setUrl(QUrl(qurl)) return indexTab def page_loaded(self, ok): if ok: currentTab = self.tabs.currentWidget() index = self.tabs.currentIndex() label = currentTab.title() icon = currentTab.icon() self.tabs.setTabIcon(index, icon) self.tabs.setTabText(index, label) def close_tab(self, index): if self.tabs.count() == 1: self.tabs.currentWidget().setUrl( QUrl( "http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D/" )) return self.tabs.removeTab(index) def edit_zeronet_config_file(self): filepath = os.path.join(os.sep, self.zeronet_path, "zeronet.conf") if sys.platform.startswith('darwin'): # macOS subprocess.run(['open', filepath]) elif sys.platform.startswith('win'): # Windows os.startfile(filepath) else: # linux variants subprocess.run(['xdg-open', filepath])
class MainForm(QMainWindow): areas = [] toolBoxs = [] tab = 0 ui_ = None debugLevel = 100 mPAreas = {} # Almacena los nombre de submenus areas de menú pineboo mPModulos = {} # Almacena los nombre de submenus modulos de menú pineboo openTabs = [] favoritosW = None wid = None # widget principal def __init__(self, parent=None): super(MainForm, self).__init__(parent) self.ui_ = None @classmethod def setDebugLevel(self, q): MainForm.debugLevel = q def load(self): self.ui_ = pineboolib.project.conn.managerModules().createUI( filedir('plugins/mainform/pineboo/mainform.ui'), None, self) self.w_ = self frameGm = self.frameGeometry() screen = QApplication.desktop().screenNumber( QApplication.desktop().cursor().pos()) centerPoint = QApplication.desktop().screenGeometry(screen).center() frameGm.moveCenter(centerPoint) self.move(frameGm.topLeft()) self.areasTab = QTabWidget() self.areasTab.setTabPosition(QTabWidget.West) self.formTab = QTabWidget() try: self.areasTab.removeItem = self.areasTab.removeTab self.areasTab.addItem = self.areasTab.addTab except Exception: pass self.dockAreasTab = QDockWidget() self.dockAreasTab.setWindowTitle("Módulos") #self.dockAreas = QtWidgets.QDockWidget() self.dockFavoritos = QDockWidget() self.dockFavoritos.setWindowTitle("Favoritos") self.dockForm = QDockWidget() self.dockConsole = None self.dockAreasTab.setWidget(self.areasTab) self.dockAreasTab.setMaximumWidth(400) self.dockFavoritos.setMaximumWidth(400) self.dockFavoritos.setMaximumHeight(500) # self.dockAreasTab.setMinimumWidth(400) # self.dockAreasTab.setMaximumHeight(500) self.dockForm.setWidget(self.formTab) self.addDockWidget(Qt.RightDockWidgetArea, self.dockForm) # self.dockForm.setMaximumWidth(950) self.addDockWidget(Qt.LeftDockWidgetArea, self.dockFavoritos) self.addDockWidget(Qt.LeftDockWidgetArea, self.dockAreasTab) # self.dockAreasTab.show() # self.dockForm.show() # self.areasTab.removeItem(0) #Borramos tab de ejemplo. self.formTab.setTabsClosable(True) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.formTab.tabCloseRequested[int].connect(self.closeFormTab) self.formTab.removeTab(0) #app_icon = QtGui.QIcon('share/icons/pineboo-logo-16.png') # app_icon.addFile(filedir('share/icons/pineboo-logo-16.png'), # QtCore.QSize(16, 16)) # app_icon.addFile(filedir('share/icons/pineboo-logo-24.png'), # QtCore.QSize(24, 24)) # app_icon.addFile(filedir('share/icons/pineboo-logo-32.png'), # QtCore.QSize(32, 32)) # app_icon.addFile(filedir('share/icons/pineboo-logo-48.png'), # QtCore.QSize(48, 48)) # app_icon.addFile(filedir('share/icons/pineboo-logo-64.png'), # QtCore.QSize(64, 64)) # app_icon.addFile(filedir('share/icons/pineboo-logo-128.png'), # QtCore.QSize(128, 128)) # app_icon.addFile(filedir('share/icons/pineboo-logo-256.png'), # QtCore.QSize(256, 256)) # self.setWindowIcon(app_icon) self.setWindowIcon(QtGui.QIcon('share/icons/pineboo-logo-16.png')) self.actionAcercaQt.triggered.connect(pineboolib.project.aboutQt) self.actionAcercaPineboo.triggered.connect(pineboolib.project.aboutPineboo) self.actionFavoritos.triggered.connect(self.changeStateDockFavoritos) self.dockFavoritos.visibilityChanged.connect(self.changeStateActionFavoritos) self.actionModulos.triggered.connect(self.changeStateDockAreas) self.dockAreasTab.visibilityChanged.connect(self.changeStateActionAreas) self.actionTipografia.triggered.connect(pineboolib.project.chooseFont) self.menuPineboo.addSeparator() # self.actionEstilo.triggered.connect(pineboolib.main.styleDialog) # pineboolib.pnapplication.initStyle(self.configMenu) self.setWindowTitle("Pineboo") logger.info("Módulos y pestañas ...") for k, area in sorted(pineboolib.project.areas.items()): self.loadArea(area) for k, module in sorted(pineboolib.project.modules.items()): self.loadModule(module) # Cargando Area desarrollo si procede ... sett_ = FLSettings() if (sett_.readBoolEntry("application/isDebuggerMode", False)): areaDevelop = Struct(idarea="dvl", descripcion="Desarrollo") self.loadArea(areaDevelop) self.loadDevelop() self.restoreOpenedTabs() self.loadState() # Cargamos nombre de vertical util = FLUtil() verticalName = util.sqlSelect("flsettings", "valor", "flkey='verticalName'") cbPosInfo = util.sqlSelect("flsettings", "valor", "flkey='PosInfo'") statusText = "" if verticalName != None: statusText = verticalName if cbPosInfo == 'True': from pineboolib.pncontrolsfactory import SysType sys_ = SysType() statusText += "\t\t\t" + sys_.nameUser() + "@" + sys_.nameBD() self.statusBar().showMessage(statusText) def closeFormTab(self, numero): if isinstance(numero, str): i = 0 name = numero numero = None for n in self.openTabs: if name == n: numero = i break i = i + 1 if numero is not None: logger.debug("Cerrando pestaña número %s ", numero) self.formTab.removeTab(numero) i = 0 for name in self.openTabs: if i == numero: self.openTabs.remove(name) break i = i + 1 def addFormTab(self, action): widget = action.mainform_widget if action.name in self.openTabs: self.closeFormTab(action.name) logger.debug("Añadiendo Form a pestaña %s", action) icon = None try: icon = action.mod.mod.mainform.actions[action.name].icon self.formTab.addTab(widget, icon, widget.windowTitle()) except Exception as e: logger.warning("addFormTab: No pude localizar icono para %s: %s", action.name, e) self.formTab.addTab(widget, widget.windowTitle()) self.formTab.setCurrentWidget(widget) self.openTabs.append(action.name) def loadArea(self, area): assert area.idarea not in self.areas vl = QWidget() vl.layout = QVBoxLayout() # layout de la pestaña vl.layout.setSpacing(0) vl.layout.setContentsMargins(0, 0, 0, 0) vl.layout.setSizeConstraint(QLayout.SetMinAndMaxSize) moduleToolBox = QToolBox(self) # toolbox de cada módulo self.areas.append(area.idarea) self.toolBoxs.append(moduleToolBox) self.tab = self.tab + 1 vl.setLayout(vl.layout) vl.layout.addWidget(moduleToolBox) self.areasTab.addItem(vl, area.descripcion) def loadModule(self, module): logger.debug("loadModule: Procesando %s ", module.name) # Creamos pestañas de areas y un vBLayout por cada módulo. Despues ahí metemos los actions de cada módulo if module.areaid not in self.areas: self.loadArea(Struct(idarea=module.areaid, descripcion=module.areaid)) moduleToolBox = self.toolBoxs[self.areas.index(module.areaid)] vBLayout = QWidget() vBLayout.layout = QVBoxLayout() # layout de cada módulo. vBLayout.layout.setSizeConstraint(QLayout.SetMinAndMaxSize) vBLayout.layout.setSpacing(0) vBLayout.layout.setContentsMargins(0, 0, 0, 0) vBLayout.setLayout(vBLayout.layout) if module.icon[0] != "": pixmap = QtGui.QPixmap(module.icon) moduleToolBox.addItem(vBLayout, QtGui.QIcon(pixmap), module.description) else: moduleToolBox.addItem(vBLayout, module.description) try: self.moduleLoad(vBLayout.layout, module) except Exception: logger.exception("ERROR al procesar modulo %s", module.name) def moduleLoad(self, vBLayout, module): if not module.loaded: module.load() if not module.loaded: logger.warning("moduleLoad: Ignorando modulo %s por fallo al cargar", module.name) return False logger.trace("moduleLoad: Running module %s . . . ", module.name) iconsize = QtCore.QSize(22, 22) iconsize = QtCore.QSize(16, 16) vBLayout.setSpacing(0) vBLayout.setContentsMargins(0, 0, 0, 0) for key in module.mainform.toolbar: action = module.mainform.actions[key] button = QToolButton() button.setText(action.text) button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) button.setIconSize(iconsize) button.setAutoRaise(True) if action.icon: button.setIcon(action.icon) button.clicked.connect(action.run) vBLayout.addWidget(button) self.addToMenuPineboo(action, module) vBLayout.addStretch() def closeEvent(self, evnt): res = QMessageBox.information( QApplication.activeWindow(), "Salir de Pineboo", "¿ Desea salir ?", QMessageBox.Yes, QMessageBox.No) if res == QMessageBox.No: evnt.ignore() self.saveState() def saveState(self): if self: sett_ = FLSettings() sett_.writeEntryList("application/mainForm/tabsOpened", self.openTabs) sett_.writeEntry("application/mainForm/viewFavorites", self.dockFavoritos.isVisible()) sett_.writeEntry("application/mainForm/FavoritesSize", self.dockFavoritos.size()) sett_.writeEntry("application/mainForm/viewAreas", self.dockAreasTab.isVisible()) sett_.writeEntry("application/mainForm/AreasSize", self.dockFavoritos.size()) sett_.writeEntry("application/mainForm/mainFormSize", self.size()) def addToMenuPineboo(self, ac, mod): #print(mod.name, ac.name, pineboolib.project.areas[mod.areaid].descripcion) # Comprueba si el area ya se ha creado if mod.areaid not in self.mPAreas.keys(): areaM = self.menuPineboo.addMenu(QtGui.QIcon('share/icons/gtk-open.png'), pineboolib.project.areas[mod.areaid].descripcion) self.mPAreas[mod.areaid] = areaM else: areaM = self.mPAreas[mod.areaid] # Comprueba si el modulo ya se ha creado if mod.name not in self.mPModulos.keys(): pixmap = None if mod.icon[0] != "": pixmap = QtGui.QPixmap(mod.icon) if pixmap: moduloM = areaM.addMenu(QtGui.QIcon(pixmap), mod.description) else: moduloM = areaM.addMenu(mod.description) self.mPModulos[mod.name] = moduloM else: moduloM = self.mPModulos[mod.name] action_ = moduloM.addAction(ac.icon, ac.text) action_.triggered.connect(ac.run) def restoreOpenedTabs(self): # Cargamos pestañas abiertas sett_ = FLSettings() tabsOpened_ = sett_.readListEntry("application/mainForm/tabsOpened") if tabsOpened_: for t in tabsOpened_: for k, module in sorted(pineboolib.project.modules.items()): if hasattr(module, "mainform"): if t in module.mainform.actions: module.mainform.actions[t].run() break def loadState(self): sett_ = FLSettings() viewFavorites_ = sett_.readBoolEntry("application/mainForm/viewFavorites", True) viewAreas_ = sett_.readBoolEntry("application/mainForm/viewAreas", True) sizeF_ = sett_.readEntry("application/mainForm/FavoritesSize", None) sizeA_ = sett_.readEntry("application/mainForm/AreasSize", None) sizeMF_ = sett_.readEntry("application/mainForm/mainFormSize", None) if sizeF_ is not None: self.dockFavoritos.resize(sizeF_) if sizeA_ is not None: self.dockAreasTab.resize(sizeA_) if sizeMF_ is not None: self.resize(sizeMF_) else: self.showMaximized() """ self.dockFavoritos.setVisible(viewFavorites_) self.actionFavoritos.setChecked(viewFavorites_) self.dockAreasTab.setVisible(viewAreas_) self.actionModulos.setChecked(viewAreas_) """ def changeStateDockFavoritos(self): visible_ = self.actionFavoritos.isChecked() if visible_: sett_ = FLSettings() sizeF_ = sett_.readEntry("application/mainForm/FavoritesSize", None) if sizeF_ is not None: self.dockFavoritos.resize(sizeF_) self.dockFavoritos.setVisible(visible_) def changeStateActionFavoritos(self): if self.dockFavoritos.isVisible(): self.actionFavoritos.setChecked(True) else: self.actionFavoritos.setChecked(False) def changeStateDockAreas(self): visible_ = self.actionModulos.isChecked() if visible_: sett_ = FLSettings() sizeA_ = sett_.readEntry("application/mainForm/AreasSize", None) if sizeA_ is not None: self.dockAreasTab.resize(sizeA_) self.dockAreasTab.setVisible(visible_) def changeStateActionAreas(self): if self.dockAreasTab.isVisible(): self.actionModulos.setChecked(True) else: self.actionModulos.setChecked(False) def loadDevelop(self): moduleToolBox = self.toolBoxs[self.areas.index("dvl")] vBLayout = QWidget() vBLayout.layout = QVBoxLayout() # layout de cada módulo. vBLayout.layout.setSizeConstraint(QLayout.SetMinAndMaxSize) vBLayout.layout.setSpacing(0) vBLayout.layout.setContentsMargins(0, 0, 0, 0) vBLayout.setLayout(vBLayout.layout) button = QToolButton() button.setText("Consola") button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) iconsize = QtCore.QSize(16, 16) button.setIconSize(iconsize) button.setAutoRaise(True) button.setIcon(QtGui.QIcon('share/icons/terminal.png')) button.clicked.connect(self.showConsole) vBLayout.layout.addWidget(button) moduleToolBox.addItem(vBLayout, "Desarrollo") #self.addToMenuPineboo(action, module) def showConsole(self): if not self.dockConsole: self.dockConsole = QDockWidget() self.dockConsole.setWindowTitle("Consola") self.addDockWidget(Qt.BottomDockWidgetArea, self.dockConsole) self.teo_ = OutputWindow() self.dockConsole.setWidget(self.teo_) self.dockConsole.setVisible(True)