Ejemplo n.º 1
1
class MainWindow(QMainWindow):
    
#    class EventFilter(QObject):
#        def __init__(self, parent):
#            super().__init__(parent)
#
#        def eventFilter(self, obj, e):
#            #print(obj.metaObject().className())
#
#            if e.type() == QEvent.KeyPress or e.type() == QEvent.ShortcutOverride:
#                key = e.key()
#                mod = e.modifiers()
#
#                print(str(e) + ' ' + str(e.type()) )
#
#                if mod == Qt.AltModifier:
#                    print('*'*30)
#                    print('alt pressed')
#                    if key == Qt.Key_Left or key == Qt.Key_Right:
#                        print('alt-left') if key == Qt.Key_Left else print('alt-right')
##                       action = QAbstractItemView.MoveLeft if key == Qt.Key_Left else QAbstractItemView.MoveRight
##                       idx = obj.moveCursor(action, Qt.NoModifier)
##                       item = obj.itemFromIndex(idx)
##                       obj.setCurrentItem(item)
#                        return True
#
#            return False


    PROGRAM_NAME = 'KiCad Schematic Component Manager'

    #--------------------------------------------------------------------------------
    class EventFilter(QObject):
        def __init__(self, parent):
            super().__init__(parent)

        def eventFilter(self, obj, e):
            if e.type() == QEvent.KeyPress or e.type() == QEvent.ShortcutOverride:
                key = e.key()
                mod = e.modifiers()

                #print(obj.focusWidget().metaObject().className())

            return False
    #--------------------------------------------------------------------------------
    def scroll_left(self):
        print('alt-left')
        if self.ToolIndex == 3 or self.ToolIndex == 2:
            self.ToolList[self.ToolIndex].finish_edit()
            
        self.ToolIndex -= 1
        if self.ToolIndex < 0:
            self.ToolIndex = len(self.ToolList) - 1
            
        print('Tool Index: ' + str(self.ToolIndex))
        self.ToolList[self.ToolIndex].setFocus()
    #--------------------------------------------------------------------------------    
    def scroll_right(self):
        print('alt-right')
        if self.ToolIndex == 3 or self.ToolIndex == 2:
            self.ToolList[self.ToolIndex].finish_edit()
            
        self.ToolIndex += 1
        if self.ToolIndex == len(self.ToolList):
            self.ToolIndex = 0

        print('Tool Index: ' + str(self.ToolIndex))
        self.ToolList[self.ToolIndex].setFocus()
    #--------------------------------------------------------------------------------    
    def mouse_change_tool(self, s):
        print('Tool ' + s)
        if s == 'CmpTable':
            self.ToolIndex = 0
        elif s == 'Selector':
            self.ToolIndex = 1
        elif s == 'Inspector':
            self.ToolIndex = 2
        elif s == 'FieldInspector':
            self.ToolIndex = 3
            
        if self.ToolIndex != 3:
            self.ToolList[3].finish_edit()  # save field properties when leave field inspector
    #--------------------------------------------------------------------------------    
    def add_user_property(self):
        self.Inspector.save_cmps()
        self.FieldInspector.save_fields()
        
        self.Inspector.add_property()
    #--------------------------------------------------------------------------------
    def remove_user_property(self):
        #self.Inspector.save_cmps()
        self.FieldInspector.save_fields()

        self.Inspector.remove_property()
    #--------------------------------------------------------------------------------        
    def rename_user_property(self):
        #self.Inspector.save_cmps()
        self.FieldInspector.save_fields()
                    
        self.Inspector.rename_property()
    #--------------------------------------------------------------------------------    
    def __init__(self):
        super().__init__()
        
        self.initUI()
        
        self.installEventFilter(self.EventFilter(self))
        
        self.setFocusPolicy(Qt.WheelFocus)
        self.setTabOrder(self.CmpTable,      self.Inspector )
        self.setTabOrder(self.Inspector,     self.Selector)
        self.setTabOrder(self.Selector,      self.FieldInspector)
        #self.setTabOrder(self.FieldInspector, self.CmpTable)

        #----------------------------------------------------
        #
        #   Application Hotkeys
        #
        self.shortcutLeft  = QShortcut(QKeySequence(Qt.ALT + Qt.Key_Left), self)
        self.shortcutRight = QShortcut(QKeySequence(Qt.ALT + Qt.Key_Right), self)
        self.shortcutLeft.setContext(Qt.ApplicationShortcut)
        self.shortcutRight.setContext(Qt.ApplicationShortcut)
        self.shortcutLeft.activated.connect(self.scroll_left)
        self.shortcutRight.activated.connect(self.scroll_right)
        
    #--------------------------------------------------------------------------------    
    def initUI(self):
        
        #----------------------------------------------------
        #
        #    Main Window
        #
        work_zone = QWidget(self)
        Layout    = QHBoxLayout(work_zone)
        self.setCentralWidget(work_zone)
        
        openAction = QAction(QIcon( os.path.join(resources_path, 'open24.png') ), 'Open', self)
        openAction.setShortcut('Ctrl+O')
        openAction.setStatusTip('Open Schematic File')
        openAction.triggered.connect(self.open_file)
        
        saveAction = QAction(QIcon( os.path.join(resources_path, 'save24.png') ), 'Save', self)
        saveAction.setShortcut('Ctrl+S')
        saveAction.setStatusTip('Save Schematic File')
        saveAction.triggered.connect(self.save_file)
                
        saveAsAction = QAction(QIcon( os.path.join(resources_path, 'save-as24.png') ), 'Save As...', self)
        saveAsAction.setShortcut('Ctrl+Shift+S')
        saveAsAction.setStatusTip('Save Schematic File As...')
        saveAsAction.triggered.connect(self.save_file_as)
        
                        
        exitAction = QAction(QIcon( os.path.join(resources_path, 'exit24.png') ), 'Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(self.close)
        
        settingsAction = QAction(QIcon( os.path.join(resources_path, 'settings24.png') ), 'Settings', self)
        settingsAction.setShortcut('Ctrl+Alt+S')
        settingsAction.setStatusTip('Edit settings')
        settingsAction.triggered.connect(self.edit_settings)
        
        helpAction = QAction(QIcon( os.path.join(resources_path, 'help_book24.png') ), 'User\'s Manual', self)
        helpAction.setShortcut('F1')
        helpAction.setStatusTip('User\'s Manual')
        helpAction.triggered.connect(self.show_user_manual_slot)
        
        helpSDAction = QAction(QIcon( os.path.join(resources_path, 'gear24.png') ), 'Settings Dialog', self)
        helpSDAction.setShortcut('Ctrl+F1')
        helpSDAction.setStatusTip('Settings Dialog Help')
        helpSDAction.triggered.connect(self.show_setting_dialog_help_slot)
                
        helpHKAction = QAction(QIcon( os.path.join(resources_path, 'rocket24.png') ), 'Hotkeys', self)
        helpHKAction.setShortcut('Shift+F1')
        helpHKAction.setStatusTip('Hotkeys Help')
        helpHKAction.triggered.connect(self.show_hotkeys_help_slot)

        self.statusBar().showMessage('Ready')

        #--------------------------------------------
        #
        #    Main Menu
        #
        menubar = self.menuBar()
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(openAction)
        fileMenu.addAction(saveAction)
        fileMenu.addAction(saveAsAction)
        fileMenu.addAction(exitAction)

        #--------------------------------------------
        #
        #    Options Menu
        #
        optionsMenu = menubar.addMenu('&Options')
        optionsMenu.addAction(settingsAction)
        
        #--------------------------------------------
        #
        #    Help Menu
        #
        helpMenu = menubar.addMenu('&Help')
        helpMenu.addAction(helpAction)
        helpMenu.addAction(helpSDAction)
        helpMenu.addAction(helpHKAction)
                
        #--------------------------------------------
        #
        #    Toolbar
        #
        toolbar = self.addToolBar('Exit')
        toolbar.addAction(exitAction)        
        toolbar.addAction(openAction)        
        toolbar.addAction(saveAction)        
        toolbar.addAction(saveAsAction)        
        toolbar.addAction(settingsAction)        
        toolbar.addAction(helpAction)        
        
        #----------------------------------------------------
        #
        #    Settings Dialog
        #
        
        #----------------------------------------------------
        #
        #    Components Table
        #
        self.CmpTabBox    = QGroupBox('Components', self)
        self.CmpTabLayout = QVBoxLayout(self.CmpTabBox)
        self.CmpTabLayout.setContentsMargins(4,10,4,4)
        self.CmpTabLayout.setSpacing(10)

        self.CmpTabLayout.setSizeConstraint(QVBoxLayout.SetMaximumSize)

        self.CmpTable       = ComponentsTable(self) 
        #self.CmpChooseButton = QPushButton('Choose', self)
        
        self.CmpTabLayout.addWidget(self.CmpTable)
        #self.CmpTabLayout.addWidget(self.CmpChooseButton)
        
                
        #----------------------------------------------------
        #
        #    Selector
        #
        self.SelectorBox    = QGroupBox('Selector', self)
        self.SelectorLayout = QVBoxLayout(self.SelectorBox)
        self.SelectorLayout.setContentsMargins(4,10,4,4)
        self.SelectorLayout.setSpacing(2)
        
        self.SelectorBtnWidget = QWidget(self)
        self.SelectorBtnLayout = QHBoxLayout(self.SelectorBtnWidget)
        self.SelectorBtnLayout.setContentsMargins(4,10,4,4)
        self.SelectorBtnLayout.setSpacing(10)

        self.Selector = Selector(self)

        self.SelApplyButton = QPushButton('Apply', self)
        self.SelApplyButton.setToolTip('Alt+S: Apply selection patterns to components')

        self.SelClearButton = QPushButton('Clear', self)
        self.SelClearButton.setToolTip('Alt+C: Clear selection patterns')

        self.SelTemplateButton = QPushButton('Use Component', self)
        self.SelTemplateButton.setToolTip('Alt+T: Use Selected Component As Template')


        self.SelectorLayout.addWidget(self.Selector)
        self.SelectorBtnLayout.addWidget(self.SelTemplateButton)
        self.SelectorBtnLayout.addWidget(self.SelApplyButton)
        self.SelectorBtnLayout.addWidget(self.SelClearButton)
        self.SelectorLayout.addWidget(self.SelectorBtnWidget)
        
        self.shortcutSelApply = QShortcut(QKeySequence(Qt.ALT + Qt.Key_S), self)
        self.shortcutSelApply.activated.connect(self.Selector.apply_slot)
        
        self.shortcutSelClear = QShortcut(QKeySequence(Qt.ALT + Qt.Key_C), self)
        self.shortcutSelClear.activated.connect(self.Selector.clear_slot)
                
        self.shortcutSelTemplate = QShortcut(QKeySequence(Qt.ALT + Qt.Key_T), self)
        self.shortcutSelTemplate.activated.connect(self.Selector.use_comp_as_template_slot)
        
        #----------------------------------------------------
        #
        #    Inspector
        #
        self.Inspector       = Inspector(self)
        self.FieldInspector  = FieldInspector(self)

        self.InspectorBtnWidget = QWidget(self)
        self.InspectorBtnLayout = QHBoxLayout(self.InspectorBtnWidget)
        self.InspectorBtnLayout.setContentsMargins(4,10,4,4)
        self.InspectorBtnLayout.setSpacing(10)
        

        self.AddUserProperty    = QPushButton('Add Property', self)
        self.AddUserProperty.setToolTip('Alt+A: Add new user property')
        self.DeleteUserProperty = QPushButton('Delete Property', self)
        self.DeleteUserProperty.setToolTip('Alt+Delete: Delete user property')
        self.RenameUserProperty = QPushButton('Rename Property', self)
        self.RenameUserProperty.setToolTip('Alt+R: Rename user property')
        
        self.InspectorBox    = QGroupBox('Inspector', self)
        self.InspectorSplit  = QSplitter(Qt.Vertical, self)
        self.InspectorLayout = QVBoxLayout(self.InspectorBox)
        self.InspectorLayout.setContentsMargins(4,10,4,4)
        self.InspectorLayout.setSpacing(2)
        
        
        self.InspectorSplit.addWidget(self.Inspector)
        self.InspectorSplit.addWidget(self.FieldInspector)
        self.InspectorLayout.addWidget(self.InspectorSplit)
        
        self.InspectorBtnLayout.addWidget(self.AddUserProperty)
        self.InspectorBtnLayout.addWidget(self.DeleteUserProperty)
        self.InspectorBtnLayout.addWidget(self.RenameUserProperty)
                
        self.InspectorLayout.addWidget(self.InspectorBtnWidget)

        self.shortcutSelApply = QShortcut(QKeySequence(Qt.ALT + Qt.Key_A), self)
        self.shortcutSelApply.activated.connect(self.add_user_property)
        
        self.shortcutSelApply = QShortcut(QKeySequence(Qt.ALT + Qt.Key_Delete), self)
        self.shortcutSelApply.activated.connect(self.remove_user_property)
        
        self.shortcutSelApply = QShortcut(QKeySequence(Qt.ALT + Qt.Key_R), self)
        self.shortcutSelApply.activated.connect(self.rename_user_property)
        
        #----------------------------------------------------

        self.Splitter = QSplitter(self)
        self.Splitter.addWidget(self.CmpTabBox)
        self.Splitter.addWidget(self.SelectorBox)   
        self.Splitter.addWidget(self.InspectorBox) 
                 
        self.centralWidget().layout().addWidget(self.Splitter)
        
        
        #----------------------------------------------------
        #
        #     Signals and Slots connections
        #
        self.CmpTable.cells_chosen.connect(self.Inspector.load_cmp)
        self.CmpTable.cells_chosen.connect(self.Selector.comp_template_slot)
        self.CmpTable.file_load.connect(self.file_loaded_slot)
        self.CmpTable.cmps_updated.connect(self.Selector.process_comps_slot)
        self.CmpTable.cmps_selected.connect(self.set_status_text_slot)

        self.SelApplyButton.clicked.connect(self.Selector.apply_slot)
        self.SelClearButton.clicked.connect(self.Selector.clear_slot)
        self.SelTemplateButton.clicked.connect(self.Selector.use_comp_as_template_slot)
        
        self.Selector.select_comps_signal.connect(self.CmpTable.select_comps_slot)

        self.Inspector.load_field.connect(self.FieldInspector.load_field_slot)
        self.Inspector.update_comps.connect(self.data_changed_slot)
        self.Inspector.update_comps.connect(self.CmpTable.update_cmp_list_slot)
        self.FieldInspector.data_changed.connect(self.data_changed_slot)
        CmpMgr.file_saved.connect(self.file_saved_slot)
        
        self.CmpTable.mouse_click.connect(self.mouse_change_tool)
        self.Inspector.mouse_click.connect(self.mouse_change_tool)
        self.FieldInspector.mouse_click.connect(self.mouse_change_tool)

        self.Inspector.header().sectionResized.connect(self.FieldInspector.column_resize)
        
        self.AddUserProperty.clicked.connect(self.add_user_property)
        self.DeleteUserProperty.clicked.connect(self.remove_user_property)
        self.RenameUserProperty.clicked.connect(self.rename_user_property)
        
        #----------------------------------------------------
        self.ToolList = []
        self.ToolList.append(self.CmpTable)
        self.ToolList.append(self.Selector)
        self.ToolList.append(self.Inspector)
        self.ToolList.append(self.FieldInspector)
        self.ToolIndex = 0
        
        
        #----------------------------------------------------
        #
        #    Window
        #
        self.setWindowTitle(self.PROGRAM_NAME)
        Settings = QSettings('kicad-tools', 'Schematic Component Manager')
        #print(Settings.allKeys())
        if Settings.contains('geometry'):
            self.restoreGeometry( Settings.value('geometry') )
        else:
            self.setGeometry(100, 100, 1024, 768)

        if Settings.contains('cmptable'):
            w0, w1 = Settings.value('cmptable')
            self.CmpTable.setColumnWidth( 0, int(w0) )
            self.CmpTable.setColumnWidth( 1, int(w1) )
            
        if Settings.contains('selector'):
            w0, w1 = Settings.value('selector')
            self.Selector.setColumnWidth( 0, int(w0) )
            self.Selector.setColumnWidth( 1, int(w1) )
                        
        if Settings.contains('inspector'):
            w0, w1 = Settings.value('inspector')
            self.Inspector.setColumnWidth( 0, int(w0) )
            self.Inspector.setColumnWidth( 1, int(w1) )
            self.FieldInspector.setColumnWidth( 0, int(w0) )
            self.FieldInspector.setColumnWidth( 1, int(w1) )
            #self.Inspector.setColumnWidth( 2, int(w2) )
            
        if Settings.contains('splitter'):
            self.Splitter.restoreState( Settings.value('splitter') )
            
        if Settings.contains('inssplitter'):
            self.InspectorSplit.restoreState( Settings.value('inssplitter') )
            
        #----------------------------------------------------
        #
        #    Process command line arguments
        #
        if len(sys.argv) > 1:
            fname = sys.argv[1]
            if os.path.exists(fname):
                self.CmpTable.load_file(fname)
            else:
                print('E: input file "' + fname + '"does not exist')
            
        self.show()
    #---------------------------------------------------------------------------
    def closeEvent(self, event):
        Settings = QSettings('kicad-tools', 'Schematic Component Manager')
        Settings.setValue( 'geometry', self.saveGeometry() )
        Settings.setValue( 'cmptable',  [self.CmpTable.columnWidth(0), self.CmpTable.columnWidth(1)] )
        Settings.setValue( 'selector',  [self.Selector.columnWidth(0), self.Selector.columnWidth(1)] )
        Settings.setValue( 'inspector', [self.Inspector.columnWidth(0), self.Inspector.columnWidth(1)] )
        Settings.setValue( 'splitter', self.Splitter.saveState() )
        Settings.setValue( 'inssplitter', self.InspectorSplit.saveState() )
        QWidget.closeEvent(self, event)
    #---------------------------------------------------------------------------
    def open_file(self):
        #filename = QFileDialog.getOpenFileName(self, 'Open schematic file', '/opt/cad/kicad', 'KiCad Schematic Files (*.sch)')
        dialog = QFileDialog(self)
        dialog.setFileMode(QFileDialog.ExistingFile)
        dialog.setNameFilter('KiCad Schematic Files (*.sch)')
        
        filenames = []
        if dialog.exec_():
            filenames = dialog.selectedFiles()
        if len(filenames) == 0:
            return
        
        CmpMgr.set_curr_file_path( filenames[0] )
        self.CmpTable.load_file( filenames[0] )
    #---------------------------------------------------------------------------
    def save_file(self):
        self.FieldInspector.save_fields()
        self.Inspector.save_cmps()
        
        curr_file = CmpMgr.curr_file_path()
        message = 'Save File "' + curr_file + '"'
        print(message)
        self.statusBar().showMessage(message)
        
        CmpMgr.save_file(curr_file)
    #---------------------------------------------------------------------------
    def save_file_as(self):
        self.Inspector.save_cmps()
        self.FieldInspector.save_fields()
        filenames = QFileDialog.getSaveFileName(self, 'Save File As...', '', 'KiCad Schematic Files (*.sch)')
            
        if filenames[0] == '':
            return
            
        print('Save File As "' + filenames[0] + '"')
        CmpMgr.save_file(filenames[0])
        CmpMgr.set_curr_file_path( filenames[0] )
    #---------------------------------------------------------------------------
    def file_loaded_slot(self):
        text = CmpMgr.curr_file_path()
        self.set_title(text)
    #---------------------------------------------------------------------------
    def data_changed_slot(self):
        text = CmpMgr.curr_file_path() + ' *'
        self.set_title(text)
    #---------------------------------------------------------------------------
    def file_saved_slot(self):
        text = CmpMgr.curr_file_path()
        self.set_title(text)
    #---------------------------------------------------------------------------
    def set_title(self, text = ''):
        text = ' - ' + text if len(text) > 0 else ''
        self.setWindowTitle(self.PROGRAM_NAME + ' v' + VERSION + text)
    #---------------------------------------------------------------------------
    def set_status_text_slot(self, text):
        self.statusBar().showMessage(text)
    #---------------------------------------------------------------------------
    def edit_settings(self):
        print('edit settings')
        SettingsDialog = TSettingsDialog(self)
        SettingsDialog.resize(400, 400)
        SettingsDialog.Tabs.setMinimumWidth(800)
        SettingsDialog.show()
    #---------------------------------------------------------------------------
    def show_user_manual_slot(self):
        help = THelpForm(self, 'User\'s Manual', 'main.html')
    #---------------------------------------------------------------------------
    def show_setting_dialog_help_slot(self):
        help = THelpForm(self, 'Settings Dialog', 'settings.html')
    #---------------------------------------------------------------------------
    def show_hotkeys_help_slot(self):
        help = THelpForm(self, 'Hotkeys', 'hotkeys.html')
class MainWindow(QWidget):
    def __init__(self, appConfig, projectorControl, parent=None):

        self.PHOTO_DELTAS_FILE_NAME = "//MACALLAN/Photos/Photo Info/Deltas/PhotoDeltas.txt"
        QWidget.__init__(self, parent)
        self._projectorControl = projectorControl
        # File used for delta changes to photos
        self.photoDeltas = PhotoDeltas(self.PHOTO_DELTAS_FILE_NAME)
        # Frameless window
        self.setWindowFlags(Qt.FramelessWindowHint)
        # Background to black
        self.setAutoFillBackground(True)
        palette = QPalette()
        palette.setColor(QPalette.Background, Qt.black)
        self.setPalette(palette)
        # Clock
        self.clock = AnimatedClock()
        # Calendar
        self.calendar = AnimatedCalendar(calUpdateSecs=600, calServerUrl=appConfig["calServerUrl"])
        # Image
        # self.photos = AnimatedPhotos("//macallan/photos/PhotosMain/", ["jpg"], maxCols=3, maxRows=4, borders=[0,0,0,0], xBetweenPics=5, yBetweenPics=5, animationSpeed=1.0, picChangeMs=5000)
        self.photos = StaticPhotos("//macallan/photos/PhotosMain/", ["jpg"], self.photoDeltas, picChangeMs=5000)
        #self.photos = CaptionedPhotos("//macallan/photos/PhotosMain/", ["jpg"], picChangeMs=5000)

        # Toolbar
        self.windowToolbar = WindowToolbar(self.close, self)

        # Left pane of page
        self.leftPane = QSplitter(Qt.Vertical)
        self.leftPane.addWidget(self.clock)
        self.leftPane.addWidget(self.calendar)
        # Right pane of page
        self.rightPane = QSplitter(Qt.Vertical)
        self.rightPane.addWidget(self.windowToolbar)
        self.rightPane.addWidget(self.photos)
        # Splitter between left and right panes
        self.horzSplitter = QSplitter(Qt.Horizontal)
        self.horzSplitter.addWidget(self.leftPane)
        self.horzSplitter.addWidget(self.rightPane)
        self.layout = QHBoxLayout(self)
        self.layout.addWidget(self.horzSplitter)
        self.setLayout(self.layout)

        # Remember the locations of the splitter bars to restore next time the program is run
        settings = QSettings("PhotoCalendar")
        settings.beginGroup("MainWindow")
        position = settings.value("Position", QVariant(QPoint(0, 0)))
        self.move(position)
        size = settings.value("Size", QVariant(QSize(1920, 1200)))
        self.resize(size)
        if settings.value("HorzSplitter") is not None:
            self.horzSplitter.restoreState(settings.value("HorzSplitter"))
            #print("Restoring horz", settings.value("HorzSplitter"))
        if settings.value("LeftPaneSplitter") is not None:
            self.leftPane.restoreState(settings.value("LeftPaneSplitter"))
            #print("Restoring left pane", settings.value("LeftPaneSplitter"))
        if settings.value("RightPaneSplitter") is not None:
            self.rightPane.restoreState(settings.value("RightPaneSplitter"))
            #print("Restoring right pane", settings.value("RightPaneSplitter"))
        settings.endGroup()

        # Start rotating photos
        self.photos.start()

        # # Grid layout
        # layout = QGridLayout()
        # # layout.setContentsMargins(0,0,0,0)
        # layout.setSpacing(0)
        # layout.addWidget(self.clock, 0, 0)
        # layout.addWidget(self.calendar, 1, 0)
        # layout.addWidget(self.photos, 0, 1, 2, 1)
        # layout.setColumnStretch(0, 1)
        # layout.setColumnStretch(1, 2.5)
        # self.setLayout(layout)

        # Start photo animation
        self.photos.start()

    def closeEvent(self, event):
        print("Main window close event")
        # Save layout settings
        settings = QSettings("PhotoCalendar")
        settings.beginGroup("MainWindow")
        curSize = self.size()
        settings.setValue("Size", QVariant(curSize))
        curPos = self.pos()
        settings.setValue("Position", QVariant(curPos))
        #settings.setValue("MainWindow/State", QVariant(self.saveState()))
        horzSplitterState = self.horzSplitter.saveState()
        #print("HorzSplitter save", horzSplitterState)
        settings.setValue("HorzSplitter", QVariant(horzSplitterState))
        leftPaneSplitterState = self.leftPane.saveState()
        settings.setValue("LeftPaneSplitter", QVariant(leftPaneSplitterState))
        #print("LeftPaneSplitter save", leftPaneSplitterState)
        rightPaneSplitterState = self.rightPane.saveState()
        settings.setValue("RightPaneSplitter", QVariant(rightPaneSplitterState))
        #print("RightPaneSplitter save", leftPaneSplitterState)
        settings.endGroup()
        # Stop the sub-elements
        self.calendar.stop()
        self.clock.stop()
        self.photos.stop()
        # Accept the close event
        event.accept()

    def resizeEvent(self, evt=None):
        xWindowSize = self.width()
        yWindowSize = self.height()
        print("MainWindow size x,y", xWindowSize, yWindowSize);

    def test(self):
        self._projectorControl.test()

    def keyPressEvent(self, event): #QKeyEvent
        key = event.key()
        if key == QtCore.Qt.Key_Left:
            # print('Left')
            self.photos.movePrev()
        elif key == QtCore.Qt.Key_Right:
            # print('Right')
            self.photos.moveNext()
        elif key == QtCore.Qt.Key_0:
            self.photoDeltas.setRating(self.photos, 0)
            self.photos.reshow()
        elif key == QtCore.Qt.Key_1:
            self.photoDeltas.setRating(self.photos, 1)
            self.photos.reshow()
        elif key == QtCore.Qt.Key_2:
            self.photoDeltas.setRating(self.photos, 2)
            self.photos.reshow()
        elif key == QtCore.Qt.Key_3:
            self.photoDeltas.setRating(self.photos, 3)
            self.photos.reshow()
        elif key == QtCore.Qt.Key_4:
            self.photoDeltas.setRating(self.photos, 4)
            self.photos.reshow()
        elif key == QtCore.Qt.Key_5:
            self.photoDeltas.setRating(self.photos, 5)
            self.photos.reshow()
        elif key == QtCore.Qt.Key_D:
            self.photoDeltas.setDateError()
            self.photos.reshow()
        elif key == QtCore.Qt.Key_L:
            self.photoDeltas.setLocationError()
            self.photos.reshow()
Ejemplo n.º 3
0
class MainWindow(QMainWindow):
    def __init__(self, settings):
        super().__init__()
        # Save the settings object for now and shutdown time.
        self.settings = settings
        # Initialize extras and dicts paths first, as other modules use them
        paths.initialize(settings)
        # Initialize our font db
        fonts.initialize(settings)
        # Set our font, which will propogate to our child widgets.
        fonts.notify_me(self._font_change)  # ask for a signal
        self._font_change(False)  # fake a signal now
        # Initialize the dictionary apparatus
        dictionaries.initialize(settings)
        # Initialize the color choices
        colors.initialize(settings)
        # Initialize the sequence number for opened files
        self.book_number = 0
        # Initialize the path to the last-opened file, used to
        # start file-open dialogs.
        self.last_open_path = '.'
        # Initialize our dict of active panels
        self.panel_dict = PANEL_DICT.copy()
        # Initialize our dict of open documents {seqno:Book}
        self.open_books = {}
        self.focus_book = None  # seqno of book in focus, see _focus_me
        # Initialize the list of recent files
        self.recent_files = []
        # Initialize the handle of a help display widget
        self.help_widget = None  # later, if at all
        # Finished initializing after the app is running
        QTimer.singleShot(300, self.finish_init)
        # Create the main window and set up the menus.
        self._uic()

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # As part of setup we often need to show the user a dialog, but when our
    # __init__ is first called, our window has not been shown and the
    # app.exec_() call has not been made. If ok_cancel_msg() is used in that
    # condition, there is a big delay and spinning cursor on the mac. So this
    # code is called from a one-shot timer 300ms after the window has been
    # created, so we are sure the app is processing events etc.
    def finish_init(self):
        #self.finish_init = False # never do this again
        # Initialize the set of files actually open when we shut down.
        last_session = self._read_flist('mainwindow/open_files')
        if len(last_session):  # there were some files open
            if len(last_session) == 1:
                msg = _TR('Start-up dialog',
                          'One book was open at the end of the last session.')
            else:
                msg = _TR('Start-up dialog',
                          '%n books were open at the end of the last session.',
                          n=len(last_session))
            info = _TR("Start-up dialog", "Click OK to re-open all")
            if utilities.ok_cancel_msg(msg, info, parent=self):
                for file_path in last_session:
                    ftbs = utilities.path_to_stream(file_path)
                    if ftbs:
                        self._open(ftbs)
        if 0 == len(self.open_books):
            # We did not re-open any books, either because there were
            # none, or the user said No, or perhaps they were not found.
            self._new()  # open one, new, book.

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Slot to receive the currentChanged signal from the editview tabset.
    # Look through self.open_books and find the one whose edit widget is
    # now current, and do a focus_me for it.
    def _editview_change(self, index):
        if index > -1:
            eview = self.editview_tabset.widget(index)
            for (seqno, book) in self.open_books.items():
                if eview == book.get_edit_view():
                    self.focus_me(seqno)
                    return
            mainwindow_logger.error('cannot relate editview tab index to book')

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Make a selected book the focus of all panels. This is called explicitly
    # when a book is first created, and when the editview tabset changes the
    # current selection. It is called also when an editview gets a focus-in
    # event.
    # Display that Book's various "-view" objects in panels, in the order
    # that the user left them and with the same active panel as before. Note
    # that a book (editview) can get a focus-in event when it was already the
    # focus in this sense, for example if this app was hidden and then
    # brought to the front. So be prepared for redundant calls.

    def focus_me(self, book_index):
        outgoing = self.focus_book
        if book_index == outgoing: return  # redundant call
        mainwindow_logger.debug('focusing {0} = {1}'.format(
            book_index, self.open_books[book_index].get_book_name()))
        self.focus_book = book_index
        # Record the user's arrangement of panels for the outgoing book,
        # as a list of tuples ('tabname', widget) in correct sequence.
        if outgoing is not None:  # false first time and after File>Close
            out_panel_dict = self.open_books[outgoing].panel_dict
            widg_list = []
            for ix in range(self.panel_tabset.count()):
                widg_list.append((self.panel_tabset.tabText(ix),
                                  self.panel_tabset.widget(ix)))
            out_panel_dict['tab_list'] = widg_list
            out_panel_dict['current'] = self.panel_tabset.currentIndex()
        # Change all the panels to the widgets, in the sequence, of the new book
        in_panel_dict = self.open_books[book_index].panel_dict
        widg_list = in_panel_dict['tab_list']
        self.panel_tabset.clear()
        for ix in range(len(widg_list)):
            (tab_text, widget) = widg_list[ix]
            self.panel_tabset.insertTab(ix, widget, tab_text)
        self.panel_tabset.setCurrentIndex(in_panel_dict['current'])
        self.editview_tabset.setCurrentIndex(
            self.editview_tabset.indexOf(
                self.open_books[book_index].get_edit_view()))

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Called by the current book to make a particular tab the visible one,
    # e.g. to make the Find visible on a ^F. The argument is a widget
    # that should occupy one of the current tabs. Ask the tabset for its
    # index, and if it is found, make that the current index. (If it is
    # not found, log it and do nothing.)

    def make_tab_visible(self, tabwidg):
        ix = self.panel_tabset.indexOf(tabwidg)
        if ix >= 0:  # widget exists in this tabset
            self.panel_tabset.setCurrentIndex(ix)
            return
        mainwindow_logger.error('Request to show nonexistent widget')

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Implement File>New:
    #    Create a Book object
    #    Call its new_empty() method,
    #    Add it to the open_books dict keyed by its sequence number,
    #    Display its text editor in a tab with the document name, and
    #    Give it the focus.
    def _new(self):
        seq = self.book_number
        self.book_number += 1
        new_book = book.Book(seq, self)
        new_book.new_empty()
        self.open_books[seq] = new_book
        index = self.editview_tabset.addTab(new_book.get_edit_view(),
                                            new_book.get_book_name())
        self.editview_tabset.setTabToolTip(
            index,
            _TR('Tooltip of edit of new unsaved file',
                'this file has not been saved'))
        self.focus_me(seq)

    #
    # For use from translators, do the New operation and return the Book
    # that is created so it can be loaded with translated text.

    def do_new(self):
        self._new()
        return self.open_books[self.focus_book]

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Quick check to see if a file path is already open. Called from _open
    # and from _build_recent (menu). Returned value is the sequence number
    # of the open book, or None.
    def _is_already_open(self, path):
        for (seq, book_object) in self.open_books.items():
            if path == book_object.get_book_full_path():
                return seq
        return None

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Implement File>Open. Dialog with the user (file dialog starts with
    # last-used book path). Result is None or a FileBasedTextStream that we
    # pass to _open().
    def _file_open(self):
        fbts = utilities.ask_existing_file(_TR('File:Open dialog',
                                               'Select a book file to open'),
                                           parent=self,
                                           starting_path=self.last_open_path)
        if fbts:  # yes a readable file was chosen.
            self._open(fbts)

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Open a file, given the document as a FileBasedTextStream, and META
    # means our metafile suffix (C.METAFILE_SUFFIX).
    # * If file opened is fname.META, look for a file named fname; if it
    #   exists open it instead, e.g. given foo.txt.META, open foo.txt.
    #   If it doesn't exist, tell the user and exit.
    # * If a file of the same name and path is already open, just focus
    #   it and exit.
    # * Determine if there is a .META file, a .bin file, or neither
    # * Create a metadata input stream if possible
    # * If no .META, look for good_words and bad_words
    # * If the only open book is an "Untitled-n" and
    #     it is unmodified, delete it.
    # * Call Book.old_book() or .new_book() as appropriate
    # * Add this book's editview to the edit tabset
    # * Give this book the focus.

    def _open(self, fbts):
        # look for opening a .META file
        if C.METAFILE_SUFFIX == fbts.suffix():
            fb2 = utilities.file_less_suffix(fbts)
            if fb2 is None:
                m1 = _TR('File:Open', 'Cannot open a metadata file alone')
                m2 = _TR('File:Open', 'There is no book file matching ',
                         'filename follows this') + fbts.filename()
                utilities.warning_msg(m1, m2, parent=self)
                return
            # we see foo.txt with foo.txt.META, silently open it
            fbts = fb2
        # look for already-open file
        seq = self._is_already_open(fbts.fullpath())
        if seq is not None:
            self.focus_me(seq)
            return
        # start collecting auxiliary streams
        gw_stream = None
        bw_stream = None
        #gg_stream = None
        # open the metadata stream, which is always UTF-8
        meta_stream = utilities.related_suffix(fbts,
                                               C.METAFILE_SUFFIX,
                                               encoding=C.ENCODING_UTF)
        if meta_stream is None:
            # opening book without metadata; look for .bin which is always LTN1
            # This is no longer supported - somebody who cares, can write a
            # .bin-to-JSON utility if they want.
            #bin_stream = utilities.related_suffix(fbts,'bin',encoding=C.ENCODING_LATIN)
            #if bin_stream :
            #gg_stream = metadata.translate_bin(bin_stream,fbts)
            # Look for good_words.txt, bad_words.txt.
            gw_stream = utilities.related_file(fbts, 'good_words*.*')
            bw_stream = utilities.related_file(fbts, 'bad_words*.*')
        seq = self.book_number
        # If the only open book is the new one created at startup or when all
        # books are closed (which will have key 0), and it has not been
        # modified, get rid of it.
        if len(self.open_books) == 1 \
        and 0 == list(self.open_books.keys())[0] \
        and self.open_books[0].get_book_name().startswith('Untitled-') \
        and not self.open_books[0].get_save_needed() :
            self.editview_tabset.clear()
            self.panel_tabset.clear()
            self.focus_book = None
            seq = 0
        else:
            # Some other book open, or user typed into the default New one.
            self.book_number += 1
        # Make the Book object and stow it in our open book dict
        a_book = book.Book(seq, self)
        self.open_books[seq] = a_book
        if meta_stream:  # opening a book we previously saved
            a_book.old_book(fbts, meta_stream)
        else:
            a_book.new_book(fbts, gw_stream, bw_stream)
        index = self.editview_tabset.addTab(a_book.get_edit_view(),
                                            a_book.get_book_name())
        self.editview_tabset.setTabToolTip(index, a_book.get_book_folder())
        self.focus_me(seq)
        self.last_open_path = fbts.folderpath()  # start for next open or save
        self._add_to_recent(fbts.fullpath())

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Save the book that is currently in focus under its present name, if it
    # is modified. Return True if the save completed, else False.
    # If the active book is a New one, force a Save-As action instead.
    def _save(self):
        active_book = self.open_books[self.focus_book]
        if active_book.get_save_needed():
            if active_book.get_book_name().startswith('Untitled-'):
                return self._save_as()
            doc_stream = utilities.path_to_output(
                active_book.get_book_full_path())
            if doc_stream:  # successfully opened for output
                meta_stream = utilities.related_output(doc_stream,
                                                       C.METAFILE_SUFFIX)
                if not meta_stream:
                    utilities.warning_msg(
                        _TR('File:Save',
                            'Unable to open metadata file for writing.'),
                        _TR('File:Save', 'Use loglevel=error for details.'),
                        parent=self)
                    return False
            else:
                utilities.warning_msg(_TR(
                    'File:Save', 'Unable to open book file for writing.'),
                                      _TR('File:Save',
                                          'Use loglevel=error for details.'),
                                      parent=self)
                return False
            return active_book.save_book(doc_stream, meta_stream)

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Implement Save As. Query the user for a file path and get that as an
    # output FileBasedTextStream. Call the book to rename itself, which makes
    # it modified. Change the text in the edit tab to match. Discard the FBTS
    # and call _save which will make another one.
    def _save_as(self):
        active_book = self.open_books[self.focus_book]
        fbts = utilities.ask_saving_file(
            _TR('File:Save As dialog',
                'Choose a new location and filename for this book'), self,
            active_book.get_book_folder())
        if fbts:
            active_book.rename_book(fbts)
            self.editview_tabset.setTabText(
                self.editview_tabset.currentIndex(), fbts.filename())
            self.editview_tabset.setTabToolTip(
                self.editview_tabset.currentIndex(),
                active_book.get_book_folder())
            self._add_to_recent(fbts.fullpath())
            fbts = None  # discard that object
            return self._save()
        else:
            return False

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Implement Close. If the active book is modified, ask if it should
    # be saved. If it is 'Untitled-' that will turn into Save As.
    def _close(self):
        target_index = self.focus_book  # active edit tab is to close
        target_book = self.open_books[target_index]
        if target_book.get_save_needed():
            # Compose message of translated parts because _TR does not
            # allow for incorporating strings, only numbers.
            msg = _TR('File Close dialog', 'Book file ',
                      'filename follows here')
            msg += target_book.get_book_name()
            msg += _TR('File Close dialog', ' has been modified!',
                       'filename precedes this')
            ret = utilities.save_discard_cancel_msg(
                msg,
                info=_TR('File Close dialog',
                         'Save it, Discard changes, or Cancel Closing?'),
                parent=self)
            if ret is None:  # Cancel
                return
            if ret:  # True==Save
                self._save()
        # Now, get rid of the active book in 3 steps,
        # 1, close the book's tab in the editview tabset. We don't know which
        # tab it is, because the user can drag tabs around.
        i = self.editview_tabset.indexOf(target_book.get_edit_view())
        # The following causes another tab to be focussed, changing self.focus_book
        # and saving target_book's tabs in target_book, not that we care.
        self.editview_tabset.removeTab(i)
        # 2, remove the book from our dict of open books.
        del self.open_books[target_index]
        # 3, if there are any open books remaining, the tab widget has
        # activated one of them by its rules, which caused a show signal and
        # entry to _focus_me already. However if there are no remaining books
        # there was no show signal or focus_me and the closed book's panels
        # are still in the tabset.
        if 0 == len(self.open_books):
            self.book_number = 0  # restart the sequence
            self.focus_book = None
            self._new()
        # One way or the other, a focus_me has removed all references to
        # active_book's view panels except those in its PANEL_DICT. So the
        # following assignment should remove the last reference to the book,
        # and schedule the book and associated objects for garbage collect.
        target_book = None

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Implement a Translate... submenu command. Call translators.xlt_book
    # with the book object that is currently in focus. If the process works
    # xlt_book will create a new book by calling do_new() and load it.
    #
    def _xlt_a_book(self):
        book = self.open_books[self.focus_book]
        datum = self.sender().data()
        translators.xlt_book(book, datum, self)

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Implement loading and saving find panel user buttons. Start the search
    # for files in the active book's folder. User can navigate to extras
    # if need be.
    def _find_save(self):
        target_book = self.open_books[self.focus_book]
        find_panel = target_book.get_find_panel()
        stream = utilities.ask_saving_file(
            _TR('File:Save Find Buttons open dialog',
                'Choose file to contain find button definitions'),
            self,
            starting_path=target_book.get_last_find_button_path(),
            encoding='UTF-8')
        if stream:  # is not None, file is open
            target_book.set_last_find_button_path(stream.fullpath())
            find_panel.user_button_output(stream)
        # else user hit cancel, forget it

    def _find_load(self):
        target_book = self.open_books[self.focus_book]
        find_panel = target_book.get_find_panel()
        stream = utilities.ask_existing_file(
            _TR('File:Load Find Buttons open dialog',
                'Choose a file of find button definitions'),
            self,
            starting_path=target_book.get_last_find_button_path(),
            encoding='UTF-8')
        if stream:  # is not None, we opened it
            target_book.set_last_find_button_path(stream.fullpath())
            find_panel.user_button_input(stream)
            target_book.metadata_modified(True, C.MD_MOD_FLAG)

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Maintain the list of "recent" file paths. The list is kept in usage
    # order, so if a path is in the list now, delete it and then add it to
    # the front. Keep it at a max of 9 items by deleting the oldest if
    # necessary.
    def _add_to_recent(self, path):
        if path in self.recent_files:
            del self.recent_files[self.recent_files.index(path)]
        self.recent_files.insert(0, path)
        self.recent_files = self.recent_files[:9]

    # Upon the aboutToShow signal from the File menu, populate the Recent
    # submenu with a list of files, but only the ones that are currently
    # accessible. If one is on a volume (e.g. USB stick) and you unmount the
    # volume, the path should not appear in the menu until the volume is
    # mounted again.
    def _open_recent(self):
        path = self.sender().data()
        fbts = utilities.path_to_stream(path)
        if fbts:
            self._open(fbts)

    def _build_recent(self):
        active_files = []
        self.recent_menu.clear()
        self.recent_menu.setEnabled(False)
        for path in self.recent_files:
            seq = self._is_already_open(path)
            if (seq is None) and utilities.file_is_accessible(path):
                active_files.append(path)
        if 0 == len(active_files):
            return
        self.recent_menu.setEnabled(True)
        for (i, path) in enumerate(active_files, start=1):
            (folder, fname) = os.path.split(path)
            act = self.recent_menu.addAction('{0} {1} {2}'.format(
                i, fname, folder))
            act.setData(path)
            act.triggered.connect(self._open_recent)

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # User has chosen a different font; if it is the general font, set
    # that here so it will propogate to our children. n.b. this is never
    # used as the preference for UI font is not implemented.
    def _font_change(self, is_mono):
        if not is_mono:
            self.setFont(fonts.get_general())

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Preferences menu action triggered. Create a Preferences dialog and
    # show it.
    def _preferences(self):
        p = preferences.PreferenceDialog(self)
        r = p.exec_()

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Help menu action triggered. If the Help widget has not yet been
    # created, create it. Otherwise just show it and raise it.
    def _show_help(self):
        if self.help_widget is None:
            self.help_widget = helpview.HelpWidget()
        self.help_widget.show()
        self.help_widget.raise_()

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Create the UI contained within this QMainWindow object. This is a lean
    # main window indeed. We have no toolbar, no status bar, no dock,
    # nothing. Just a splitter with, on the left, a tabset for editviews, and
    # on the right, a scrollbar containing a tabset for panels. (Qt Designer
    # note: it is not possible to build this structure with the Designer. It
    # will not let you put the scroll area into the splitter.)
    #
    # TODO: create a custom QTabWidget using a custom QTabBar to implement
    # drag-out-of-tabset behavior, and use those here.
    def _uic(self):
        global _EDIT_MENU
        # Create the tabset that displays editviews
        self.editview_tabset = QTabWidget()
        self.editview_tabset.setMovable(True)  # let user move tabs around
        self.editview_tabset.currentChanged.connect(self._editview_change)
        # Create the tabset that displays find, notes, help &etc.
        self.panel_tabset = QTabWidget()
        self.panel_tabset.setMovable(True)
        # Create the splitter that contains the above two parts.
        self.splitter = QSplitter(Qt.Horizontal, self)
        self.splitter.setChildrenCollapsible(False)
        # Give just a little margin to the left of the editor
        self.splitter.setContentsMargins(8, 0, 0, 0)
        self.splitter.addWidget(self.editview_tabset)
        self.splitter.addWidget(self.panel_tabset)
        # Set that splitter as the main window's central (and only) widget
        self.setCentralWidget(self.splitter)
        # Populate the panel tabset with empty widgets just so there will
        # be tabs that _swap can reference.
        for key in self.panel_dict.keys():
            widj = QWidget()
            self.panel_tabset.addTab(widj, key)
            self.panel_dict[key] = widj
        # Size and position ourself based on saved settings.
        self.move(self.settings.value("mainwindow/position", QPoint(50, 50)))
        self.resize(
            self.settings.value("mainwindow/size", C.STARTUP_DEFAULT_SIZE))
        self.splitter.restoreState(
            self.settings.value("mainwindow/splitter",
                                C.STARTUP_DEFAULT_SPLITTER))
        self.restoreState(
            self.settings.value("mainwindow/windowstate", QByteArray()))
        # Store a reference to the application menubar. In Mac OS this
        # is a parentless menubar; other platforms it is the default.
        if C.PLATFORM_IS_MAC:
            self.menu_bar = QMenuBar()  # parentless menu bar for Mac OS
        else:
            self.menu_bar = self.menuBar()  # refer to the default one
        # Create the File menu, located in our menu_bar.
        self.file_menu = self.menu_bar.addMenu(_TR('Menu name', '&File'))
        # Populate the File menu with actions.
        #  File:New -> _new()
        work = self.file_menu.addAction(_TR('File menu command', '&New'))
        work.setShortcut(QKeySequence.New)
        work.setToolTip(_TR('File:New tooltip',
                            'Create a new, empty document'))
        work.triggered.connect(self._new)
        #  File:Open -> _file_open()
        work = self.file_menu.addAction(_TR('File menu command', '&Open'))
        work.setShortcut(QKeySequence.Open)
        work.setToolTip(_TR('File:Open tooltip', 'Open an existing book'))
        work.triggered.connect(self._file_open)
        #  File:Save -> _file_save()
        work = self.file_menu.addAction(_TR('File menu command', '&Save'))
        work.setShortcut(QKeySequence.Save)
        work.setToolTip(_TR('File:Save tooltip', 'Save the active book'))
        work.triggered.connect(self._save)
        #  Save As -> _file_save_as()
        work = self.file_menu.addAction(_TR('File menu command', 'Save &As'))
        work.setShortcut(QKeySequence.SaveAs)
        work.setToolTip(
            _TR('File:Save As tooltip',
                'Save the active book under a new name'))
        work.triggered.connect(self._save_as)
        #  Close -> _close()
        work = self.file_menu.addAction(_TR('File menu command', 'Close'))
        work.setShortcut(QKeySequence.Close)
        work.setToolTip(_TR('File:Close tooltip', 'Close the active book'))
        work.triggered.connect(self._close)
        #  Load Find Buttons -> _find_load()
        work = self.file_menu.addAction(
            _TR('File menu command', 'Load Find Buttons'))
        work.setToolTip(
            _TR(
                'File:Load Find Buttons tooltip',
                'Load a file of definitions for the custom buttons in the Find panel'
            ))
        work.triggered.connect(self._find_load)
        #  Save Find Buttons -> _find_save()
        work = self.file_menu.addAction(
            _TR('File menu command', 'Save Find Buttons'))
        work.setToolTip(
            _TR('File:Save Find Buttons tooltip',
                'Save definitions of the custom buttons in the Find panel'))
        work.triggered.connect(self._find_save)

        # Translate... gets a submenu with an entry for every Translator
        # in extras/Translators (if any). The actions connect to _xlt_a_book.
        self.translate_submenu = translators.build_xlt_menu(
            self, self._xlt_a_book)
        self.file_menu.addMenu(self.translate_submenu)

        # Open Recent gets a submenu that is added to the File menu.
        # The aboutToShow signal is connected to our _build_recent slot.
        self.recent_menu = QMenu(_TR('Sub-menu name', '&Recent Files'))
        work = self.file_menu.addMenu(self.recent_menu)
        work.setToolTip(
            _TR('File:Recent tooltip', 'List of recently-used files to open'))
        self.file_menu.aboutToShow.connect(self._build_recent)

        # Put in a divider above the Help, Preferences and Quit actions.
        self.file_menu.addSeparator()

        # Help opens or un-hides the Help viewer
        work = self.file_menu.addAction(_TR('Help menu item', 'Help'))
        work.setToolTip(
            _TR('Help menu item tooltip',
                'Display the Help/User Manual in a separate window'))
        work.triggered.connect(self._show_help)
        self.file_menu.addAction(work)

        # Preferences: On the Mac, Preferences is automatically moved to the app menu.
        work = self.file_menu.addAction(
            _TR('Preferences menu item', 'Preferences'))
        work.setToolTip(
            _TR(
                'Preferences menu item tooltip',
                'Open the Preferences dialog to set paths, fonts, and text styles'
            ))
        work.setMenuRole(QAction.PreferencesRole)
        work.triggered.connect(self._preferences)

        #  Quit choice, with the menu role that moves it to the app menu
        work = QAction(_TR('Quit command', '&Quit'), self)
        work.setMenuRole(QAction.QuitRole)
        work.setShortcut(QKeySequence.Quit)
        work.triggered.connect(self.close)
        self.file_menu.addAction(work)

        # Initialize the list of "recent" files for the File sub-menu.
        # These files were not necessarily open at shutdown, just sometime
        # in the not too distant past.
        self.recent_files = self._read_flist('mainwindow/recent_files')

        # Create the Edit menu in the menu_bar, store a reference to it
        # in a static global, and immediately clear it.
        _EDIT_MENU = self.menu_bar.addMenu(C.ED_MENU_EDIT)
        hide_edit_menu()

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Functions related to shutdown and management of settings.
    #
    # Factor out the job of reading/writing a list of files in the settings.
    # Input is a settings array key string like 'mainwindow/recent_files'
    # Output is a possibly empty list of canonical-file-path strings.
    def _read_flist(self, array_key):
        f_list = []
        f_count = self.settings.beginReadArray(array_key)
        for f in range(f_count):  # which may be 0
            self.settings.setArrayIndex(f)
            f_list.append(self.settings.value('filepath'))
        self.settings.endArray()
        return f_list

    # Input is an array key and a possibly empty list of path strings
    def _write_flist(self, file_list, array_key):
        if len(file_list):
            self.settings.beginWriteArray(array_key, len(file_list))
            for f in range(len(file_list)):
                self.settings.setArrayIndex(f)
                self.settings.setValue('filepath', file_list[f])
            self.settings.endArray()

    # Reimplement QWidget.closeEvent in order to save any open files
    # and update the settings.
    def closeEvent(self, event):
        # If there are any unsaved books, ask the user if they should be
        # saved. If the answer is yes, try to do so.
        unsaved = []
        for (seq, book_object) in self.open_books.items():
            if book_object.get_save_needed():
                unsaved.append(seq)
        if len(unsaved):
            if len(unsaved) == 1:
                msg = _TR('Shutdown message', 'There is one unsaved file')
            else:
                msg = _TR('Shutdown message',
                          'There are %n unsaved files',
                          n=len(unsaved))
            ret = utilities.save_discard_cancel_msg(
                msg,
                _TR('Shutdown message',
                    'Save, Discard changes, or Cancel Quit?'),
                parent=self)
            if ret is None:
                # user wants to cancel shutdown
                event.ignore()
                return
            if ret:
                # User want to save. Focus each unsaved file and call _save.
                # For all but "Untitled-n" documents this will be silent. For
                # those, it will open a save-as dialog. We ignore the return
                # from this because we cannot distinguish between a cancelled
                # file-open dialog and a file write error.
                for seq in unsaved:
                    self.focus_me(seq)
                    self._save()
        # Clear the settings so that old values don't hang around
        self.settings.clear()
        # Tell the submodules to save their current global values.
        colors.shutdown(self.settings)
        fonts.shutdown(self.settings)
        dictionaries.shutdown(self.settings)
        paths.shutdown(self.settings)
        # Save the list of currently-open files in the settings, but do not
        # save any whose filename matches "Untitled-#" because that is an
        # unsaved New file (which the user chose not to save, above).
        open_paths = []
        for (index, book_obj) in self.open_books.items():
            if not book_obj.get_book_name().startswith('Untitled-'):
                open_paths.append(book_obj.get_book_full_path())
        self._write_flist(open_paths, 'mainwindow/open_files')
        # Save the list of "recent" files in the settings.
        self._write_flist(self.recent_files, 'mainwindow/recent_files')
        # Save this window's position and size and splitter state
        self.settings.setValue("mainwindow/size", self.size())
        self.settings.setValue("mainwindow/position", self.pos())
        self.settings.setValue("mainwindow/splitter",
                               self.splitter.saveState())
        self.settings.setValue("mainwindow/windowstate", self.saveState())
        # If the Help window is open, close it -- as a modeless widget,
        # it won't close just because we do.
        if self.help_widget:  # is not None,
            self.help_widget.close()
        # and that's it, we are done finished, over & out.
        event.accept()
Ejemplo n.º 4
0
class MainWindow(QMainWindow):

    KEY_WINDOW_SIZE = 'main_window/size'
    KEY_WINDOW_MAXIMIZED = 'main_window/maximized'
    KEY_WINDOW_POSITION = 'main_window/position'
    KEY_H_SPLITTER_STATE = 'main_window/h_splitter_state'

    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle('Mojuru')
        app_icon = QIcon('images/mojuru_logo.png')
        self.setWindowIcon(app_icon)

        reload_modules_action = QAction('Reload MainWindow', self)
        reload_modules_action.setShortcut('ctrl+shift+alt+r')
        reload_modules_action.triggered.connect(self.reload_central_widget)
        self.addAction(reload_modules_action)

        quit_action = QAction('Quit', self)
        quit_action.setShortcut('ctrl+q')
        quit_action.triggered.connect(self.on_quit)
        self.addAction(quit_action)

        self.vertical_splitter = QSplitter(Qt.Vertical, self)
        self.setCentralWidget(self.vertical_splitter)
        self.load_central_widget()

        self.file_menu = self.menuBar().addMenu(self.tr('&File'))
        self.file_menu.addAction(quit_action)
        self.file_menu.addSeparator()

        self.module_menu = self.menuBar().addMenu(self.tr('&Modules'))
        self.module_menu.addAction(reload_modules_action)
        self.module_menu.addSeparator()

        Alter.invoke_all('main_window_init', self)

        #restore main window state
        size = ModuleManager.core['settings'].Settings.value(
            self.KEY_WINDOW_SIZE, QSize(600, 400))
        maximized = ModuleManager.core['settings'].Settings.value(
            self.KEY_WINDOW_MAXIMIZED, False)
        position = ModuleManager.core['settings'].Settings.value(
            self.KEY_WINDOW_POSITION, QPoint(0, 0))
        if maximized == 'true':
            self.showMaximized()
        else:
            self.resize(size)
            self.move(position)

    def closeEvent(self, event):
        self.on_quit()

    def save_state(self):
        ModuleManager.core['settings'].Settings.set_value(
            self.KEY_WINDOW_SIZE, self.size())
        ModuleManager.core['settings'].Settings.set_value(
            self.KEY_WINDOW_MAXIMIZED, self.isMaximized())
        ModuleManager.core['settings'].Settings.set_value(
            self.KEY_WINDOW_POSITION, self.pos())
        ModuleManager.core['settings'].Settings.set_value(
            self.KEY_H_SPLITTER_STATE, self.horizontal_splitter.saveState())

    def on_quit(self):
        self.save_state()
        self.close()

    def load_central_widget(self):
        self.populate_central_widget()
        self.connect_widgets()

    def populate_central_widget(self):
        self.vertical_widgets = collections.OrderedDict()

        self.horizontal_splitter = QSplitter(Qt.Horizontal,
                                             self.vertical_splitter)
        self.horizontal_widgets = collections.OrderedDict()
        self.vertical_widgets["horizontal_splitter"] = self.horizontal_splitter

        Alter.invoke_all('main_window_add_vertical_widget',
                         self.vertical_widgets, self)
        for widget in self.vertical_widgets.values():
            self.vertical_splitter.addWidget(widget)

        Alter.invoke_all('main_window_add_horizontal_widget',
                         self.horizontal_widgets, self.vertical_splitter)
        for widget in self.horizontal_widgets.values():
            self.horizontal_splitter.addWidget(widget)

        #restore horizontal splitter state
        state = ModuleManager.core['settings'].Settings.value(
            self.KEY_H_SPLITTER_STATE, None)
        if state:
            self.horizontal_splitter.restoreState(state)

    def connect_widgets(self):
        Alter.invoke_all('main_window_connect_widgets', self.vertical_widgets,
                         self.horizontal_widgets)

    def reload_central_widget(self):
        self.save_state()
        for index in range(self.vertical_splitter.count()):
            widget = self.vertical_splitter.widget(index)
            widget.hide()
            widget.setParent(None)
            del widget
        Alter.clear()
        ModuleManager.reload_all_modules('core')
        ModuleManager.reload_all_modules('custom')
        self.load_central_widget()
Ejemplo n.º 5
0
class MainWindow(QMainWindow):

    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.groupsList = QListWidget()
        self.listWidget.itemDoubleClicked.connect(self.grouplistshowItem)
        self.editor = QTextEdit()
        self.messageView = QTextBrowser()
        self.messageSplitter = QSplitter(Qt.Vertical)
        self.messageSplitter.addWidget(self.editor)
        self.messageSplitter.addWidget(self.messageView)
        self.mainSplitter = QSplitter(Qt.Horizontal)
        self.mainSplitter.addWidget(self.groupsList)
        self.mainSplitter.addWidget(self.messageSplitter)
        self.setCentralWidget(self.mainSplitter)
        self.mainSplitter.setStretchFactor(0, 1)
        self.mainSplitter.setStretchFactor(1, 3)
        self.messageSplitter.setStretchFactor(0, 1)
        self.messageSplitter.setStretchFactor(1, 2)

        self.createMenusAndToolbars()

        settings = QSettings()
        if settings.value("MainWindow/Geometry") or \
                      settings.value("MainWindow/State") or \
                      settings.value("MainSplitter"):
            self.restoreGeometry(
                    QByteArray(settings.value("MainWindow/Geometry")))
            self.restoreState(
                    QByteArray(settings.value("MainWindow/State")))
            self.messageSplitter.restoreState(
                    QByteArray(settings.value("MessageSplitter")))
            self.mainSplitter.restoreState(
                    QByteArray(settings.value("MainSplitter")))

        status = self.statusBar()
        status.setSizeGripEnabled(False)
        status.showMessage("Ready", 5000)
        self.setWindowTitle("News Reader")
        self.generateFakeData()


    def createMenusAndToolbars(self):

        menubar = self.menuBar()
        fileMenu = menubar.addMenu('File')
        fileMenu.addAction("new...",self.newFile, "Ctrl+N")
        fileMenu.addAction("open...",self.openfile,"Ctrl+O")
        fileMenu.addAction("save...",self.savefile,"Ctrl+S")
        fileMenu.addAction("save_as...",self.save_asfile,"Ctrl+Shift+S")
        fileMenu.addAction("E&xit", QApplication.instance().quit, "Ctrl+Q")
        fileMenu2 = menubar.addMenu('Settings')
        fileMenu2.addAction("Preference", self.setting, "Ctrl+T")
        app.setStyleSheet('menuBar::item{spacing:100px;margin:100px;}')


        screen = QDesktopWidget().screenGeometry()
        self.setGeometry(50, 50, screen.width() - 100, screen.height() - 100)


    def newFile(self):
        self.editor.clear()
        login = Login()
        login.show()
        login.exec_()
    def grouplistshowItem(self):

        self.databasename=split(self.currentItem().text(),".")[0]
        self.tablename=split(self.currentItem().text(),".")[1]



    # add file location
    def openfile(self,path=None):
        if not path:
            path, _ = QFileDialog.getOpenFileName(self, "Open File", '',
                    "SQL Files (*.sql,*.*)")

        if path:
            inFile = QFile(path)
            if inFile.open(QFile.ReadOnly | QFile.Text):
                text = inFile.readAll()

                try:
                    # Python v3.
                    text = str(text, encoding='ascii')
                except TypeError:
                    # Python v2.
                    text = str(text)

                self.editor.setPlainText(text)

    def savefile(self):
         ...
    def save_asfile(self):
         ...
    def setting(self):
        ...



    def closeEvent(self, event):
        if self.okToContinue():
            settings = QSettings()
            settings.setValue("MainWindow/Geometry",
                              self.saveGeometry())
            settings.setValue("MainWindow/State",
                              self.saveState())
            settings.setValue("MessageSplitter",
                    self.messageSplitter.saveState())
            settings.setValue("MainSplitter",
                    self.mainSplitter.saveState())
        else:
            event.ignore()


    def okToContinue(self):
        return True


    def generateFakeData(self):
        for group in ("ada", "apl", "asm.*", "asm370", "awk", "basic.*",
                "beta", "c.*", "c++.*", "clarion", "clipper.*", "clos",
                "clu", "cobol", "dylan", "eiffel", "forth.*",
                "fortran.*", "functional", "haskell", "hermes", "icon",
                "idl", "idl-pvwave"):
            self.groupsList.addItem("comp.lang.{0}".format(group))
        for topic, author in (
                ):
            self.editor.addItem("{0} from {1}".format(topic, author))
Ejemplo n.º 6
0
class IPWaveformWidget(QWidget):

    """ The IPWaveformWidget holds the waveform and inventory data.  The IPPlotViewer plots the data,
    The IPFilterSettingsWidget holds the filter settings and tells the WaveformWidget when to update that
    data.  The IPStatsView displays the trace data, and the IPStationView displays the station data.
    """

    _sts = None             # streams
    _sts_filtered = None    # filtered streams
    _inv = None             # inventory

    def __init__(self, parent=None, pool=None, project=None):
        super().__init__(parent)

        self._parent = parent
        self.settings = parent.settings
        self._mp_pool = pool

        self.buildUI()

    def buildUI(self):

        self.stationViewer = IPStationView.IPStationView(self)
        self.statsViewer = IPStatsView.IPStatsView(self)
        self.info_tabs = QTabWidget()
        self.info_tabs.addTab(self.statsViewer, 'Trace Info')
        self.info_tabs.addTab(self.stationViewer, 'Station Info')

        self.filterSettingsWidget = IPFilterSettingsWidget.IPFilterSettingsWidget(self)
        self.spectraWidget = IPPSDWidget.IPPSDWidget(self)

        self.plotViewer = IPPlotViewer.IPPlotViewer(self, self.filterSettingsWidget)

        self.lh_splitter = QSplitter(Qt.Vertical)
        self.lh_splitter.setStyleSheet("QSplitter::handle{ background-color: #DDD}")
        self.lh_splitter.addWidget(self.plotViewer)
        self.lh_splitter.addWidget(self.info_tabs)

        self.rh_splitter = QSplitter(Qt.Vertical)
        self.rh_splitter.setStyleSheet("QSplitter::handle{ background-color: #DDD}")
        self.rh_splitter.addWidget(self.spectraWidget)
        self.rh_splitter.addWidget(self.filterSettingsWidget)

        self.main_splitter = QSplitter(Qt.Horizontal)
        self.main_splitter.setStyleSheet("QSplitter::handle{ background-color: #DDD}")
        self.main_splitter.addWidget(self.lh_splitter)
        self.main_splitter.addWidget(self.rh_splitter)

        main_layout = QGridLayout()
        main_layout.addWidget(self.main_splitter)

        self.setLayout(main_layout)

        self.connect_signals_and_slots()

    def connect_signals_and_slots(self):
        self.filterSettingsWidget.sig_filter_changed.connect(self.update_filtered_data)
        self.filterSettingsWidget.sig_filter_display_changed.connect(self.plotViewer.show_hide_lines)

        self.statsViewer.removeTrace.connect(self.remove_trace)

        self.plotViewer.lr_settings_widget.noiseSpinsChanged.connect(self._parent.beamformingWidget.bottomSettings.setNoiseValues)
        self.plotViewer.lr_settings_widget.signalSpinsChanged.connect(self._parent.beamformingWidget.bottomSettings.setSignalValues)
        self.plotViewer.lr_settings_widget.signalSpinsChanged.connect(self._parent.beamformingWidget.updateWaveformRange)
        self.plotViewer.pl_widget.sig_active_plot_changed.connect(self.update_widgets)

        self.spectraWidget.f1_Spin.valueChanged.connect(self._parent.beamformingWidget.bottomSettings.setFmin)
        self.spectraWidget.f2_Spin.valueChanged.connect(self._parent.beamformingWidget.bottomSettings.setFmax)
        self.spectraWidget.psdPlot.getFreqRegion().sigRegionChanged.connect(self._parent.beamformingWidget.bottomSettings.setFreqValues)

    def get_project(self):
        return self._parent.getProject()

    def errorPopup(self, message, title="Oops..."):
        msgBox = QMessageBox()
        msgBox.setIcon(QMessageBox.Information)
        msgBox.setText(message)
        msgBox.setWindowTitle(title)
        msgBox.exec_()


    @pyqtSlot(obspy.core.stream.Stream, obspy.core.inventory.inventory.Inventory)
    def appendTraces(self, newTraces, newInventory):
        if newTraces is None:
            return

        if self._sts is None:
            self._sts = newTraces
        else:
            self._sts += newTraces

        self.update_inventory(newInventory)

        for trace in self._sts:
            trace.data = trace.data - np.mean(trace.data)
            self._sts.merge(fill_value=0)

        # it's possible, if the open failed, that self.waveformWidget._sts is still None, so if it is, bail out
        # if not populate the trace stats viewer and plot the traces
        if self._sts is not None:
            # TODO...is there a better way of doing this?
            self._parent.beamformingWidget.setStreams(self._sts)
            self.stationViewer.setInventory(self._inv)
            self.statsViewer.setStats(self._sts)

            self.update_streams(self._sts)

            self._parent.setStatus("Ready", 5000)
        else:
            return

    @pyqtSlot(obspy.core.stream.Stream, obspy.core.inventory.inventory.Inventory)
    def replaceTraces(self, newTraces, newInventory):
        # same as append, just clear out the old traces and inventory first
        self._sts = None
        self._inv = None
        self.stationViewer.setInventory(self._inv)

        self.appendTraces(newTraces, newInventory)

    @pyqtSlot(Inventory)
    def update_inventory(self, new_inventory):
        if self._inv is None:
            self._inv = new_inventory
        else:
            self._inv += new_inventory
        self.stationViewer.setInventory(self._inv)

    def remove_from_inventory(self, net, sta, loc, cha):
        self.inv_remove(self._inv, network=net, station=sta, location=loc, channel=cha, keep_empty=False)
        self.update_inventory(new_inventory)

    def get_streams(self):
        return self._sts

    def get_filtered_streams(self):
        return self._sts_filtered

    def get_inventory(self):
        return self._inv

    def set_inventory(self, new_inv):
        self._inv = new_inv

    def getTraceName(self, trace):
        traceName = trace.stats['network'] + '.' + trace.stats['station'] + \
            '.' + trace.stats['location'] + '.' + trace.stats['channel']
        return traceName

    def get_earliest_start_time(self):
        return self.plotViewer.pl_widget.earliest_start_time

    @pyqtSlot(Stream)
    def update_streams(self, new_stream):
        # this should be called when you load new streams, or remove traces
        self._sts = new_stream
        self._sts_filtered = self.filter_stream(self._sts,
                                                self.filterSettingsWidget.get_filter_settings())
        self.plotViewer.set_streams(self._sts,
                                    self._sts_filtered,
                                    self.filterSettingsWidget.get_filter_display_settings())

        self.statsViewer.setStats(new_stream)

    def debug_trace(self):  # for debugging, you have to call pyqtRemoveInputHook before set_trace()
        from PyQt5.QtCore import pyqtRemoveInputHook
        from pdb import set_trace
        pyqtRemoveInputHook()
        set_trace()

    @pyqtSlot(dict)
    def update_filtered_data(self, filter_settings):

        # this should be called when settings in the filter widget are changed
        if self._sts is None:
            # Nothing to filter, clear out the filtered_streams and return
            self._sts_filtered = None
            return

        self._sts_filtered = self.filter_stream(self._sts,
                                                filter_settings)

        self.plotViewer.pl_widget.update_filtered_line_data(self._sts_filtered)
        index = self.plotViewer.pl_widget.get_active_plot()
        self.update_widgets(index, 
                            self.plotViewer.get_plot_lines(), 
                            self.plotViewer.get_filtered_plot_lines(), 
                            self.plotViewer.pl_widget.plot_list[index].getSignalRegionRange())

    def filter_stream(self, stream, cfs):

        # cfs: Current Filter Settings
        if stream is None:
            # nothing to do
            return None

        filtered_stream = Stream()

        for trace in stream:

            filtered_trace = trace.copy()

            filtType = cfs['type']

            if filtType == 'High Pass':
                try:
                    filtered_trace.filter('highpass',
                                        freq=cfs['F_high'],
                                        corners=cfs['order'],
                                        zerophase=cfs['zphase'])
                except ValueError as e:
                    self.errorPopup(str(e))

            elif filtType == 'Low Pass':
                try:
                    filtered_trace.filter('lowpass',
                                        freq=cfs['F_low'],
                                        corners=cfs['order'],
                                        zerophase=cfs['zphase'])
                except ValueError as e:
                    self.errorPopup(str(e))

            elif filtType == 'Band Pass':
                try:
                    filtered_trace.filter('bandpass',
                                        freqmin=cfs['F_high'],
                                        freqmax=cfs['F_low'],
                                        corners=cfs['order'],
                                        zerophase=cfs['zphase'])
                except ValueError as e:
                    self.errorPopup(str(e))

            else:
                self.errorPopup(filtType + ' filter not implemented yet')
                return
            filtered_stream += filtered_trace

        return filtered_stream

    def saveWindowGeometrySettings(self):
        self._parent.settings.beginGroup('WaveformWidget')
        self._parent.settings.setValue("main_splitterSettings", self.main_splitter.saveState())
        self._parent.settings.setValue("rh_splitterSettings", self.rh_splitter.saveState())
        self._parent.settings.setValue("lh_splitterSettings", self.lh_splitter.saveState())
        self._parent.settings.setValue("plotviewer_splitterSettings", self.plotViewer.saveState())
        self._parent.settings.endGroup()

    def restoreWindowGeometrySettings(self):
        # Restore settings
        self._parent.settings.beginGroup('WaveformWidget')

        main_splitterSettings = self._parent.settings.value("main_splitterSettings")
        if main_splitterSettings:
            self.main_splitter.restoreState(main_splitterSettings)

        rh_splitterSettings = self._parent.settings.value("rh_splitterSettings")
        if rh_splitterSettings:
            self.rh_splitter.restoreState(rh_splitterSettings)

        lh_splitterSettings = self._parent.settings.value("lh_splitterSettings")
        if lh_splitterSettings:
            self.lh_splitter.restoreState(lh_splitterSettings)

        pv_splitterSettings = self._parent.settings.value("plotviewer_splitterSettings")
        if pv_splitterSettings:
            self.plotViewer.restoreState(pv_splitterSettings)
        else:
            pv_width = self.plotViewer.width()
            wsw = pv_width//6
            pww = pv_width - wsw
            self.plotViewer.setSizes([wsw, pww])

        self._parent.settings.endGroup()

    @QtCore.pyqtSlot(str)
    def remove_trace(self, trace_id):

        for trace in self._sts.select(id=trace_id):
            self._sts.remove(trace)
            self.removeStation(trace.stats['network'], trace.stats['station'])

        self.statsViewer.setStats(self._sts)

        if len(self._sts) == 0:
            self._sts = None

        self.update_streams(self._sts)

    def removeStation(self, net_id, station_id):

        if self._inv is not None:
            try:
                self._inv = self.inv_remove(self._inv, network=net_id, station=station_id)

            except AttributeError as e:
                print(e)

            self.stationViewer.setInventory(self._inv)

    def inv_remove(self,
                   _inventory,
                   network='*',
                   station='*',
                   location='*',
                   channel='*',
                   keep_empty=False):

        selected = _inventory.select(network=network,
                                     station=station,
                                     location=location,
                                     channel=channel)

        selected_networks = [net for net in selected]
        selected_stations = [sta for net in selected_networks for sta in net]
        selected_channels = [cha for net in selected_networks
                             for sta in net for cha in sta]

        networks = []
        for net in _inventory:
            if net in selected_networks and station == '*' and \
                    location == '*' and channel == '*':
                continue
            stations = []
            for sta in net:
                if sta in selected_stations and location == '*' and channel == '*':
                    continue
                channels = []
                for cha in sta:
                    if cha in selected_channels:
                        continue
                    channels.append(cha)
                if not channels and not keep_empty:
                    continue
                sta = copy.copy(sta)
                sta.channels = channels
                stations.append(sta)

            if not stations and not keep_empty:
                continue
            net = copy.copy(net)
            net.stations = stations
            networks.append(net)

        return obspy.core.inventory.inventory.Inventory(networks, 'source')

    def clearWaveforms(self):

        # empty out the streams
        self._sts = None
        self._sts_filtered = None

        # empty out the child widgets
        self.statsViewer.clear()
        self.stationViewer.clear()
        self.plotViewer.clear()
        self.spectraWidget.clearPlot()

    @pyqtSlot(object)
    def update_signal_PSD(self, signal_region_item):

        if len(self._sts) == 0:
            self.spectraWidget.clearPlot()
            return

        signal_region = signal_region_item.getRegion()

        active_plot = self.plotViewer.pl_widget.get_active_plot()

        # calculate the PSD of the ---------------------------tart and finish
        dt = self._sts[active_plot].stats.delta
        start = int(signal_region[0] / dt)
        stop = int(signal_region[1] / dt)

        self.spectraWidget.updateSignalPSD(self._sts[active_plot][start:stop])

    @pyqtSlot(object)
    def update_noise_PSD(self, noise_region_item):

        if len(self._sts) == 0:
            self.spectraWidget.clearPlot()
            return

        noise_region = noise_region_item.getRegion()

        active_plot = self.plotViewer.pl_widget.get_active_plot()

        # calculate the PSD of the data in the current noise region
        dt = self._sts[active_plot].stats.delta
        start = int(noise_region[0] / dt)
        stop = int(noise_region[1] / dt)

        self.spectraWidget.updateNoisePSD(self._sts[active_plot][start:stop])

    @pyqtSlot(int, list, list, tuple)
    def update_widgets(self, index, lines, filtered_lines, signal_region):
        # the -1 is sent if none of the plots are visible
        if len(self._sts) < 1 or index == -1:
            self.spectraWidget.set_title('...')
            self.spectraWidget.clearPlot()

        else:
            self.spectraWidget.set_title(self._sts[index].id)
            self.spectraWidget.set_fs(self._sts[index].stats.sampling_rate)
            # self.spectraWidget.updateSignalPSD()
            #    self.spectraWidget.updateNoiesPSD()

            noise_region_item = self.plotViewer.pl_widget.plot_list[index].getNoiseRegion()
            noise_region_item.sigRegionChanged.emit(noise_region_item)
            signal_region_item = self.plotViewer.pl_widget.plot_list[index].getSignalRegion()
            signal_region_item.sigRegionChanged.emit(signal_region_item)

            current_filter_display_settings = self.filterSettingsWidget.get_filter_display_settings()
            if current_filter_display_settings['apply']:
                self._parent.beamformingWidget.setWaveform(filtered_lines[index], signal_region)
            else:
                self._parent.beamformingWidget.setWaveform(lines[index], signal_region)
Ejemplo n.º 7
0
def restoreSplitter(widget: QSplitter, key: str) -> None:
    key += "Splitter"
    if aqt.mw.pm.profile.get(key):
        widget.restoreState(aqt.mw.pm.profile[key])
Ejemplo n.º 8
0
class MainWindow(QMainWindow):
    
    KEY_WINDOW_SIZE = 'main_window/size'
    KEY_WINDOW_MAXIMIZED = 'main_window/maximized'
    KEY_WINDOW_POSITION = 'main_window/position'
    KEY_H_SPLITTER_STATE = 'main_window/h_splitter_state'
    
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle('Mojuru')
        app_icon = QIcon('images/mojuru_logo.png')
        self.setWindowIcon(app_icon)
        
        reload_modules_action = QAction('Reload MainWindow', self)
        reload_modules_action.setShortcut('ctrl+shift+alt+r')
        reload_modules_action.triggered.connect(self.reload_central_widget)
        self.addAction(reload_modules_action)
        
        quit_action = QAction('Quit', self)
        quit_action.setShortcut('ctrl+q')
        quit_action.triggered.connect(self.on_quit)
        self.addAction(quit_action)
        
        self.vertical_splitter = QSplitter(Qt.Vertical, self)
        self.setCentralWidget(self.vertical_splitter)
        self.load_central_widget()
        
        self.file_menu = self.menuBar().addMenu(self.tr('&File'))
        self.file_menu.addAction(quit_action)
        self.file_menu.addSeparator()
        
        self.module_menu = self.menuBar().addMenu(self.tr('&Modules'))
        self.module_menu.addAction(reload_modules_action)
        self.module_menu.addSeparator()
        
        Alter.invoke_all('main_window_init', self)
        
        #restore main window state
        size = ModuleManager.core['settings'].Settings.value(
            self.KEY_WINDOW_SIZE, QSize(600, 400))
        maximized = ModuleManager.core['settings'].Settings.value(
            self.KEY_WINDOW_MAXIMIZED, False)
        position = ModuleManager.core['settings'].Settings.value(
            self.KEY_WINDOW_POSITION, QPoint(0,0))
        if maximized == 'true':
            self.showMaximized()
        else:
            self.resize(size)
            self.move(position)
    
    def closeEvent(self, event):
        self.on_quit()
    
    def save_state(self):
        ModuleManager.core['settings'].Settings.set_value(
            self.KEY_WINDOW_SIZE, self.size())
        ModuleManager.core['settings'].Settings.set_value(
            self.KEY_WINDOW_MAXIMIZED, self.isMaximized())
        ModuleManager.core['settings'].Settings.set_value(
            self.KEY_WINDOW_POSITION, self.pos())
        ModuleManager.core['settings'].Settings.set_value(
            self.KEY_H_SPLITTER_STATE, self.horizontal_splitter.saveState())
    
    def on_quit(self):
        self.save_state()
        self.close()
    
    def load_central_widget(self):
        self.populate_central_widget()
        self.connect_widgets()
    
    def populate_central_widget(self):
        self.vertical_widgets = collections.OrderedDict()
        
        self.horizontal_splitter = QSplitter(
            Qt.Horizontal, self.vertical_splitter)
        self.horizontal_widgets = collections.OrderedDict()
        self.vertical_widgets["horizontal_splitter"] = self.horizontal_splitter
        
        Alter.invoke_all(
            'main_window_add_vertical_widget',
            self.vertical_widgets,
            self
        )
        for widget in self.vertical_widgets.values():
            self.vertical_splitter.addWidget(widget)
        
        Alter.invoke_all(
            'main_window_add_horizontal_widget',
            self.horizontal_widgets,
            self.vertical_splitter
        )
        for widget in self.horizontal_widgets.values():
            self.horizontal_splitter.addWidget(widget)
        
        #restore horizontal splitter state
        state = ModuleManager.core['settings'].Settings.value(
            self.KEY_H_SPLITTER_STATE,
            None
        )
        if state:
            self.horizontal_splitter.restoreState(state)
    
    def connect_widgets(self):
        Alter.invoke_all(
            'main_window_connect_widgets', 
            self.vertical_widgets, 
            self.horizontal_widgets
        )
    
    def reload_central_widget(self):
        self.save_state()
        for index in range(self.vertical_splitter.count()):
            widget = self.vertical_splitter.widget(index)
            widget.hide()
            widget.setParent(None)
            del widget
        Alter.clear()
        ModuleManager.reload_all_modules('core')
        ModuleManager.reload_all_modules('custom')
        self.load_central_widget()
    
    def add_action(self, name, callback, **kwargs):
        """
        Ajoute une action au context menu et au widget lui même.
        Créer une fonction à la volé pour fournir des arguments aux fonctions
        de rappel.
        """
        action = QAction(name, self)
        if 'icon' in kwargs:
            action.setIcon(kwargs['icon'])
        if 'shortcut' in kwargs:
            action.setShortcut(kwargs['shortcut'])
        action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        action.triggered.connect(self.__wrapper(callback, **kwargs))
        self.addAction(action)
        if 'menu' in kwargs:
            kwargs['menu'].addAction(action)

    def add_separator(self, menu):
        """Simple abstraction of self.context_menu.addSeparator()"""
        menu.addSeparator()
    
    def __wrapper(self, callback, **kwargs):
        def __new_function():
            """
            __new_function représente la forme de tous les callbacks connecté
            à une action pour pouvoir utiliser les raccourcis en même temps que
            le menu contextuel.
            """
            args = [
                kwargs['instance'] if 'instance' in kwargs else self
            ]
            callback(*args)
        return __new_function
Ejemplo n.º 9
0
class DatabaseContainer(QSplitter):
    def __init__(self, orientation=Qt.Horizontal):
        QSplitter.__init__(self, orientation)
        self.pfile = None

        self.lateral_widget = lateral_widget.LateralWidget()
        self.table_widget = table_widget.TableWidget()
        self.query_container = query_container.QueryContainer(self)

        self._vsplitter = QSplitter(Qt.Vertical)
        self._vsplitter.addWidget(self.table_widget)
        self._vsplitter.addWidget(self.query_container)
        self.addWidget(self.lateral_widget)
        self.addWidget(self._vsplitter)

        self.modified = False

        self.__nquery = 1

        # Connections
        # FIXME
        self.lateral_widget.relationClicked.connect(self._on_relation_clicked)

        # lambda i: self.table_widget.stacked.setCurrentIndex(i))
        # For change table widget item when up/down
        # see issue #39
        self.lateral_widget.relationSelectionChanged.connect(
            lambda i: self.table_widget.stacked.setCurrentIndex(i))
        self.query_container.saveEditor['PyQt_PyObject'].connect(
            self.save_query)
        self.setSizes([1, 1])

    def _on_relation_clicked(self, index):
        if not self.table_widget._other_tab.isVisible():
            self.table_widget._tabs.setCurrentIndex(0)
        self.table_widget.stacked.setCurrentIndex(index)

    def dbname(self):
        """ Return display name """

        return self.pfile.display_name

    def is_new(self):
        return self.pfile.is_new

    def create_database(self, data):
        for table in data.get('tables'):
            # Get data
            table_name = table.get('name')
            header = table.get('header')
            tuples = table.get('tuples')

            # Creo el objeto Relation
            rela = relation.Relation()
            rela.header = header
            # Relleno el objeto con las tuplas
            for _tuple in tuples:
                rela.insert(_tuple)
            # Se usa el patrón Modelo/Vista/Delegado
            # Para entender más, leer el código de cáda módulo
            # src.gui.model
            # src.gui.view
            # src.gui.delegate
            _view = self.create_table(rela, table_name)
            # Add relation to relations dict
            self.table_widget.add_relation(table_name, rela)
            # Add table to stacked
            self.table_widget.stacked.addWidget(_view)
            # Add table name to list widget
            self.lateral_widget.relation_list.add_item(table_name,
                                                       rela.cardinality(),
                                                       rela.degree())
        # Select first item
        # self.lateral_widget.relation_list.select_first()

    def create_table(self, relation_obj, relation_name, editable=True):
        """ Se crea la vista, el model y el delegado para @relation_obj """

        _view = view.View()
        header = view.Header()
        _model = model.Model(relation_obj)
        _model.modelModified[bool].connect(self.__on_model_modified)
        _model.cardinalityChanged[int].connect(self.__on_cardinality_changed)
        if not editable:
            _model.editable = False
            header.editable = False
        _view.setModel(_model)
        _view.setItemDelegate(delegate.Delegate())
        _view.setHorizontalHeader(header)
        return _view

    @pyqtSlot(bool)
    def __on_model_modified(self, modified):
        self.modified = modified

    @pyqtSlot(int)
    def __on_cardinality_changed(self, value):
        # self.lateral_widget.update_item(value)
        self.lateral_widget.relation_list.update_cardinality(value)

    def load_relation(self, filenames):
        for filename in filenames:
            with open(filename) as f:
                csv_reader = csv.reader(f)
                header = next(csv_reader)
                rel = relation.Relation()
                rel.header = header
                for i in csv_reader:
                    rel.insert(i)
                relation_name = file_manager.get_basename(filename)
                if not self.table_widget.add_relation(relation_name, rel):
                    QMessageBox.information(
                        self, self.tr("Información"),
                        self.tr("Ya existe una relación "
                                "con el nombre  "
                                "'{}'".format(relation_name)))
                    return False

            self.table_widget.add_table(rel, relation_name)
            # self.lateral_widget.add_item(relation_name, rel.cardinality())
            return True

    def delete_relation(self):
        name = self.lateral_widget.relation_list.current_text()
        index = self.lateral_widget.relation_list.current_index()
        if not name:
            return
        msgbox = QMessageBox(self)
        msgbox.setIcon(QMessageBox.Question)
        msgbox.setWindowTitle(self.tr("Confirmación"))
        msgbox.setText(
            self.tr(
                "Está seguro de eliminar la relación <b>{}</b>?".format(name)))
        msgbox.addButton(self.tr("No!"), QMessageBox.NoRole)

        si = msgbox.addButton(self.tr("Si, estoy seguro"), QMessageBox.YesRole)
        palette = QPalette()
        palette.setColor(QPalette.Button, QColor("#cc575d"))
        palette.setColor(QPalette.ButtonText, QColor("white"))
        si.setPalette(palette)
        msgbox.exec_()
        if msgbox.clickedButton() == si:
            self.lateral_widget.relation_list.remove_item(index)
            self.table_widget.remove_table(index)
            self.table_widget.remove_relation(name)
            return True
        return False

    def __on_data_table_changed(self, row, col, data):
        # current_relation = self.lateral_widget.current_text()
        # # Relation to be update
        # rela = self.table_widget.relations.get(current_relation)
        # # Clear old content
        # rela.clear()
        # current_table = self.table_widget.stacked.currentWidget()
        # model = current_table.model()
        # for i in range(model.rowCount()):
        #     reg = []
        #     for j in range(model.columnCount()):
        #         if row == i and col == j:
        #             reg.append(data)
        #         else:
        #             reg.append(model.item(i, j).text())
        #     # Insert new content
        #     rela.insert(reg)
        # # Update relation
        # self.table_widget.relations[current_relation] = rela
        pass

    def new_query(self, filename):
        editor_tab_at = self.query_container.is_open(filename)
        if editor_tab_at != -1:
            self.query_container.set_focus_editor_tab(editor_tab_at)
        else:
            query_widget = query_container.QueryWidget()
            # Create object file
            ffile = pfile.File(filename)
            editor = query_widget.get_editor()
            editor.pfile = ffile
            if not filename:
                ffile.filename = 'untitled_{n}.pqf'.format(n=self.__nquery)
            else:
                content = ffile.read()
                editor.setPlainText(content)
            self.query_container.add_tab(query_widget, ffile.display_name)
            self.__nquery += 1

    def save_query(self, editor):
        if not editor:
            editor = self.query_container.currentWidget().get_editor()
        if editor.is_new:
            return self.save_query_as(editor)
        # Get content of editor
        content = editor.toPlainText()
        try:
            editor.pfile.save(data=content)
        except Exception as reason:
            QMessageBox.critical(
                self, "Error",
                self.tr("El archivo no se puede abrir!"
                        "\n\n{}".format(reason)))
            return False
        editor.saved()
        return editor.pfile.filename

    def save_query_as(self, editor=None):
        filename = QFileDialog.getSaveFileName(self,
                                               self.tr("Guardar Archivo"),
                                               editor.name,
                                               "Pireal query files(*.pqf)")
        filename = filename[0]
        if not filename:
            return
        # Get the content
        content = editor.toPlainText()
        # Write the file
        editor.pfile.save(data=content, path=filename)
        editor.saved()

    def execute_queries(self):
        self.query_container.execute_queries()

    def execute_selection(self):
        editor = self.query_container.currentWidget().get_editor()
        text_cursor = editor.textCursor()
        if text_cursor.hasSelection():
            query = text_cursor.selectedText()
            self.query_container.execute_queries(query)

    def showEvent(self, event):
        QSplitter.showEvent(self, event)
        qsettings = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat)
        vsizes = qsettings.value('vsplitter_sizes', None)
        if vsizes is not None:
            self._vsplitter.restoreState(vsizes)
        else:
            self._vsplitter.setSizes([self.height() / 3, self.height() / 6])
        hsizes = qsettings.value('hsplitter_sizes', None)
        if hsizes is not None:
            self.restoreState(hsizes)
        else:
            self.setSizes([self.width() / 10, self.width() / 3])

    def save_sizes(self):
        """ Save sizes of Splitters """

        qsettings = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat)
        qsettings.setValue('vsplitter_sizes', self._vsplitter.saveState())
        qsettings.setValue('hsplitter_sizes', self.saveState())
Ejemplo n.º 10
0
class MainWindow(QMainWindow):

    #    class EventFilter(QObject):
    #        def __init__(self, parent):
    #            super().__init__(parent)
    #
    #        def eventFilter(self, obj, e):
    #            #print(obj.metaObject().className())
    #
    #            if e.type() == QEvent.KeyPress or e.type() == QEvent.ShortcutOverride:
    #                key = e.key()
    #                mod = e.modifiers()
    #
    #                print(str(e) + ' ' + str(e.type()) )
    #
    #                if mod == Qt.AltModifier:
    #                    print('*'*30)
    #                    print('alt pressed')
    #                    if key == Qt.Key_Left or key == Qt.Key_Right:
    #                        print('alt-left') if key == Qt.Key_Left else print('alt-right')
    ##                       action = QAbstractItemView.MoveLeft if key == Qt.Key_Left else QAbstractItemView.MoveRight
    ##                       idx = obj.moveCursor(action, Qt.NoModifier)
    ##                       item = obj.itemFromIndex(idx)
    ##                       obj.setCurrentItem(item)
    #                        return True
    #
    #            return False

    PROGRAM_NAME = 'KiCad Schematic Component Manager'

    #--------------------------------------------------------------------------------
    class EventFilter(QObject):
        def __init__(self, parent):
            super().__init__(parent)

        def eventFilter(self, obj, e):
            if e.type() == QEvent.KeyPress or e.type(
            ) == QEvent.ShortcutOverride:
                key = e.key()
                mod = e.modifiers()

                #print(obj.focusWidget().metaObject().className())

            return False

    #--------------------------------------------------------------------------------
    def scroll_left(self):
        print('alt-left')
        if self.ToolIndex == 3 or self.ToolIndex == 2:
            self.ToolList[self.ToolIndex].finish_edit()

        self.ToolIndex -= 1
        if self.ToolIndex < 0:
            self.ToolIndex = len(self.ToolList) - 1

        print('Tool Index: ' + str(self.ToolIndex))
        self.ToolList[self.ToolIndex].setFocus()

    #--------------------------------------------------------------------------------
    def scroll_right(self):
        print('alt-right')
        if self.ToolIndex == 3 or self.ToolIndex == 2:
            self.ToolList[self.ToolIndex].finish_edit()

        self.ToolIndex += 1
        if self.ToolIndex == len(self.ToolList):
            self.ToolIndex = 0

        print('Tool Index: ' + str(self.ToolIndex))
        self.ToolList[self.ToolIndex].setFocus()

    #--------------------------------------------------------------------------------
    def mouse_change_tool(self, s):
        print('Tool ' + s)
        if s == 'CmpTable':
            self.ToolIndex = 0
        elif s == 'Selector':
            self.ToolIndex = 1
        elif s == 'Inspector':
            self.ToolIndex = 2
        elif s == 'FieldInspector':
            self.ToolIndex = 3

        if self.ToolIndex != 3:
            self.ToolList[3].finish_edit(
            )  # save field properties when leave field inspector

    #--------------------------------------------------------------------------------
    def add_user_property(self):
        self.Inspector.save_cmps()
        self.FieldInspector.save_fields()

        self.Inspector.add_property()

    #--------------------------------------------------------------------------------
    def remove_user_property(self):
        #self.Inspector.save_cmps()
        self.FieldInspector.save_fields()

        self.Inspector.remove_property()

    #--------------------------------------------------------------------------------
    def rename_user_property(self):
        #self.Inspector.save_cmps()
        self.FieldInspector.save_fields()

        self.Inspector.rename_property()

    #--------------------------------------------------------------------------------
    def __init__(self):
        super().__init__()

        self.initUI()

        self.installEventFilter(self.EventFilter(self))

        self.setFocusPolicy(Qt.WheelFocus)
        self.setTabOrder(self.CmpTable, self.Inspector)
        self.setTabOrder(self.Inspector, self.Selector)
        self.setTabOrder(self.Selector, self.FieldInspector)
        #self.setTabOrder(self.FieldInspector, self.CmpTable)

        #----------------------------------------------------
        #
        #   Application Hotkeys
        #
        self.shortcutLeft = QShortcut(QKeySequence(Qt.ALT + Qt.Key_Left), self)
        self.shortcutRight = QShortcut(QKeySequence(Qt.ALT + Qt.Key_Right),
                                       self)
        self.shortcutLeft.setContext(Qt.ApplicationShortcut)
        self.shortcutRight.setContext(Qt.ApplicationShortcut)
        self.shortcutLeft.activated.connect(self.scroll_left)
        self.shortcutRight.activated.connect(self.scroll_right)

    #--------------------------------------------------------------------------------
    def initUI(self):

        #----------------------------------------------------
        #
        #    Main Window
        #
        work_zone = QWidget(self)
        Layout = QHBoxLayout(work_zone)
        self.setCentralWidget(work_zone)

        openAction = QAction(QIcon(os.path.join(resources_path, 'open24.png')),
                             'Open', self)
        openAction.setShortcut('Ctrl+O')
        openAction.setStatusTip('Open Schematic File')
        openAction.triggered.connect(self.open_file)

        saveAction = QAction(QIcon(os.path.join(resources_path, 'save24.png')),
                             'Save', self)
        saveAction.setShortcut('Ctrl+S')
        saveAction.setStatusTip('Save Schematic File')
        saveAction.triggered.connect(self.save_file)

        saveAsAction = QAction(
            QIcon(os.path.join(resources_path, 'save-as24.png')), 'Save As...',
            self)
        saveAsAction.setShortcut('Ctrl+Shift+S')
        saveAsAction.setStatusTip('Save Schematic File As...')
        saveAsAction.triggered.connect(self.save_file_as)

        exitAction = QAction(QIcon(os.path.join(resources_path, 'exit24.png')),
                             'Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(self.close)

        settingsAction = QAction(
            QIcon(os.path.join(resources_path, 'settings24.png')), 'Settings',
            self)
        settingsAction.setShortcut('Ctrl+Alt+S')
        settingsAction.setStatusTip('Edit settings')
        settingsAction.triggered.connect(self.edit_settings)

        helpAction = QAction(
            QIcon(os.path.join(resources_path, 'help_book24.png')),
            'User\'s Manual', self)
        helpAction.setShortcut('F1')
        helpAction.setStatusTip('User\'s Manual')
        helpAction.triggered.connect(self.show_user_manual_slot)

        helpSDAction = QAction(
            QIcon(os.path.join(resources_path, 'gear24.png')),
            'Settings Dialog', self)
        helpSDAction.setShortcut('Ctrl+F1')
        helpSDAction.setStatusTip('Settings Dialog Help')
        helpSDAction.triggered.connect(self.show_setting_dialog_help_slot)

        helpHKAction = QAction(
            QIcon(os.path.join(resources_path, 'rocket24.png')), 'Hotkeys',
            self)
        helpHKAction.setShortcut('Shift+F1')
        helpHKAction.setStatusTip('Hotkeys Help')
        helpHKAction.triggered.connect(self.show_hotkeys_help_slot)

        self.statusBar().showMessage('Ready')

        #--------------------------------------------
        #
        #    Main Menu
        #
        menubar = self.menuBar()
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(openAction)
        fileMenu.addAction(saveAction)
        fileMenu.addAction(saveAsAction)
        fileMenu.addAction(exitAction)

        #--------------------------------------------
        #
        #    Options Menu
        #
        optionsMenu = menubar.addMenu('&Options')
        optionsMenu.addAction(settingsAction)

        #--------------------------------------------
        #
        #    Help Menu
        #
        helpMenu = menubar.addMenu('&Help')
        helpMenu.addAction(helpAction)
        helpMenu.addAction(helpSDAction)
        helpMenu.addAction(helpHKAction)

        #--------------------------------------------
        #
        #    Toolbar
        #
        toolbar = self.addToolBar('Exit')
        toolbar.addAction(exitAction)
        toolbar.addAction(openAction)
        toolbar.addAction(saveAction)
        toolbar.addAction(saveAsAction)
        toolbar.addAction(settingsAction)
        toolbar.addAction(helpAction)

        #----------------------------------------------------
        #
        #    Settings Dialog
        #

        #----------------------------------------------------
        #
        #    Components Table
        #
        self.CmpTabBox = QGroupBox('Components', self)
        self.CmpTabLayout = QVBoxLayout(self.CmpTabBox)
        self.CmpTabLayout.setContentsMargins(4, 10, 4, 4)
        self.CmpTabLayout.setSpacing(10)

        self.CmpTabLayout.setSizeConstraint(QVBoxLayout.SetMaximumSize)

        self.CmpTable = ComponentsTable(self)
        #self.CmpChooseButton = QPushButton('Choose', self)

        self.CmpTabLayout.addWidget(self.CmpTable)
        #self.CmpTabLayout.addWidget(self.CmpChooseButton)

        #----------------------------------------------------
        #
        #    Selector
        #
        self.SelectorBox = QGroupBox('Selector', self)
        self.SelectorLayout = QVBoxLayout(self.SelectorBox)
        self.SelectorLayout.setContentsMargins(4, 10, 4, 4)
        self.SelectorLayout.setSpacing(2)

        self.SelectorBtnWidget = QWidget(self)
        self.SelectorBtnLayout = QHBoxLayout(self.SelectorBtnWidget)
        self.SelectorBtnLayout.setContentsMargins(4, 10, 4, 4)
        self.SelectorBtnLayout.setSpacing(10)

        self.Selector = Selector(self)

        self.SelApplyButton = QPushButton('Apply', self)
        self.SelApplyButton.setToolTip(
            'Alt+S: Apply selection patterns to components')

        self.SelClearButton = QPushButton('Clear', self)
        self.SelClearButton.setToolTip('Alt+C: Clear selection patterns')

        self.SelTemplateButton = QPushButton('Use Component', self)
        self.SelTemplateButton.setToolTip(
            'Alt+T: Use Selected Component As Template')

        self.SelectorLayout.addWidget(self.Selector)
        self.SelectorBtnLayout.addWidget(self.SelTemplateButton)
        self.SelectorBtnLayout.addWidget(self.SelApplyButton)
        self.SelectorBtnLayout.addWidget(self.SelClearButton)
        self.SelectorLayout.addWidget(self.SelectorBtnWidget)

        self.shortcutSelApply = QShortcut(QKeySequence(Qt.ALT + Qt.Key_S),
                                          self)
        self.shortcutSelApply.activated.connect(self.Selector.apply_slot)

        self.shortcutSelClear = QShortcut(QKeySequence(Qt.ALT + Qt.Key_C),
                                          self)
        self.shortcutSelClear.activated.connect(self.Selector.clear_slot)

        self.shortcutSelTemplate = QShortcut(QKeySequence(Qt.ALT + Qt.Key_T),
                                             self)
        self.shortcutSelTemplate.activated.connect(
            self.Selector.use_comp_as_template_slot)

        #----------------------------------------------------
        #
        #    Inspector
        #
        self.Inspector = Inspector(self)
        self.FieldInspector = FieldInspector(self)

        self.InspectorBtnWidget = QWidget(self)
        self.InspectorBtnLayout = QHBoxLayout(self.InspectorBtnWidget)
        self.InspectorBtnLayout.setContentsMargins(4, 10, 4, 4)
        self.InspectorBtnLayout.setSpacing(10)

        self.AddUserProperty = QPushButton('Add Property', self)
        self.AddUserProperty.setToolTip('Alt+A: Add new user property')
        self.DeleteUserProperty = QPushButton('Delete Property', self)
        self.DeleteUserProperty.setToolTip('Alt+Delete: Delete user property')
        self.RenameUserProperty = QPushButton('Rename Property', self)
        self.RenameUserProperty.setToolTip('Alt+R: Rename user property')

        self.InspectorBox = QGroupBox('Inspector', self)
        self.InspectorSplit = QSplitter(Qt.Vertical, self)
        self.InspectorLayout = QVBoxLayout(self.InspectorBox)
        self.InspectorLayout.setContentsMargins(4, 10, 4, 4)
        self.InspectorLayout.setSpacing(2)

        self.InspectorSplit.addWidget(self.Inspector)
        self.InspectorSplit.addWidget(self.FieldInspector)
        self.InspectorLayout.addWidget(self.InspectorSplit)

        self.InspectorBtnLayout.addWidget(self.AddUserProperty)
        self.InspectorBtnLayout.addWidget(self.DeleteUserProperty)
        self.InspectorBtnLayout.addWidget(self.RenameUserProperty)

        self.InspectorLayout.addWidget(self.InspectorBtnWidget)

        self.shortcutSelApply = QShortcut(QKeySequence(Qt.ALT + Qt.Key_A),
                                          self)
        self.shortcutSelApply.activated.connect(self.add_user_property)

        self.shortcutSelApply = QShortcut(QKeySequence(Qt.ALT + Qt.Key_Delete),
                                          self)
        self.shortcutSelApply.activated.connect(self.remove_user_property)

        self.shortcutSelApply = QShortcut(QKeySequence(Qt.ALT + Qt.Key_R),
                                          self)
        self.shortcutSelApply.activated.connect(self.rename_user_property)

        #----------------------------------------------------

        self.Splitter = QSplitter(self)
        self.Splitter.addWidget(self.CmpTabBox)
        self.Splitter.addWidget(self.SelectorBox)
        self.Splitter.addWidget(self.InspectorBox)

        self.centralWidget().layout().addWidget(self.Splitter)

        #----------------------------------------------------
        #
        #     Signals and Slots connections
        #
        self.CmpTable.cells_chosen.connect(self.Inspector.load_cmp)
        self.CmpTable.cells_chosen.connect(self.Selector.comp_template_slot)
        self.CmpTable.file_load.connect(self.file_loaded_slot)
        self.CmpTable.cmps_updated.connect(self.Selector.process_comps_slot)
        self.CmpTable.cmps_selected.connect(self.set_status_text_slot)

        self.SelApplyButton.clicked.connect(self.Selector.apply_slot)
        self.SelClearButton.clicked.connect(self.Selector.clear_slot)
        self.SelTemplateButton.clicked.connect(
            self.Selector.use_comp_as_template_slot)

        self.Selector.select_comps_signal.connect(
            self.CmpTable.select_comps_slot)

        self.Inspector.load_field.connect(self.FieldInspector.load_field_slot)
        self.Inspector.update_comps.connect(self.data_changed_slot)
        self.Inspector.update_comps.connect(self.CmpTable.update_cmp_list_slot)
        self.FieldInspector.data_changed.connect(self.data_changed_slot)
        CmpMgr.file_saved.connect(self.file_saved_slot)

        self.CmpTable.mouse_click.connect(self.mouse_change_tool)
        self.Inspector.mouse_click.connect(self.mouse_change_tool)
        self.FieldInspector.mouse_click.connect(self.mouse_change_tool)

        self.Inspector.header().sectionResized.connect(
            self.FieldInspector.column_resize)

        self.AddUserProperty.clicked.connect(self.add_user_property)
        self.DeleteUserProperty.clicked.connect(self.remove_user_property)
        self.RenameUserProperty.clicked.connect(self.rename_user_property)

        #----------------------------------------------------
        self.ToolList = []
        self.ToolList.append(self.CmpTable)
        self.ToolList.append(self.Selector)
        self.ToolList.append(self.Inspector)
        self.ToolList.append(self.FieldInspector)
        self.ToolIndex = 0

        #----------------------------------------------------
        #
        #    Window
        #
        self.setWindowTitle(self.PROGRAM_NAME)
        Settings = QSettings('kicad-tools', 'Schematic Component Manager')
        #print(Settings.allKeys())
        if Settings.contains('geometry'):
            self.restoreGeometry(Settings.value('geometry'))
        else:
            self.setGeometry(100, 100, 1024, 768)

        if Settings.contains('cmptable'):
            w0, w1 = Settings.value('cmptable')
            self.CmpTable.setColumnWidth(0, int(w0))
            self.CmpTable.setColumnWidth(1, int(w1))

        if Settings.contains('selector'):
            w0, w1 = Settings.value('selector')
            self.Selector.setColumnWidth(0, int(w0))
            self.Selector.setColumnWidth(1, int(w1))

        if Settings.contains('inspector'):
            w0, w1 = Settings.value('inspector')
            self.Inspector.setColumnWidth(0, int(w0))
            self.Inspector.setColumnWidth(1, int(w1))
            self.FieldInspector.setColumnWidth(0, int(w0))
            self.FieldInspector.setColumnWidth(1, int(w1))
            #self.Inspector.setColumnWidth( 2, int(w2) )

        if Settings.contains('splitter'):
            self.Splitter.restoreState(Settings.value('splitter'))

        if Settings.contains('inssplitter'):
            self.InspectorSplit.restoreState(Settings.value('inssplitter'))

        #----------------------------------------------------
        #
        #    Process command line arguments
        #
        if len(sys.argv) > 1:
            fname = sys.argv[1]
            if os.path.exists(fname):
                self.CmpTable.load_file(fname)
            else:
                print('E: input file "' + fname + '"does not exist')

        self.show()

    #---------------------------------------------------------------------------
    def closeEvent(self, event):
        Settings = QSettings('kicad-tools', 'Schematic Component Manager')
        Settings.setValue('geometry', self.saveGeometry())
        Settings.setValue(
            'cmptable',
            [self.CmpTable.columnWidth(0),
             self.CmpTable.columnWidth(1)])
        Settings.setValue(
            'selector',
            [self.Selector.columnWidth(0),
             self.Selector.columnWidth(1)])
        Settings.setValue(
            'inspector',
            [self.Inspector.columnWidth(0),
             self.Inspector.columnWidth(1)])
        Settings.setValue('splitter', self.Splitter.saveState())
        Settings.setValue('inssplitter', self.InspectorSplit.saveState())
        QWidget.closeEvent(self, event)

    #---------------------------------------------------------------------------
    def open_file(self):
        #filename = QFileDialog.getOpenFileName(self, 'Open schematic file', '/opt/cad/kicad', 'KiCad Schematic Files (*.sch)')
        dialog = QFileDialog(self)
        dialog.setFileMode(QFileDialog.ExistingFile)
        dialog.setNameFilter('KiCad Schematic Files (*.sch)')

        filenames = []
        if dialog.exec_():
            filenames = dialog.selectedFiles()
        if len(filenames) == 0:
            return

        CmpMgr.set_curr_file_path(filenames[0])
        self.CmpTable.load_file(filenames[0])

    #---------------------------------------------------------------------------
    def save_file(self):
        self.FieldInspector.save_fields()
        self.Inspector.save_cmps()

        curr_file = CmpMgr.curr_file_path()
        message = 'Save File "' + curr_file + '"'
        print(message)
        self.statusBar().showMessage(message)

        CmpMgr.save_file(curr_file)

    #---------------------------------------------------------------------------
    def save_file_as(self):
        self.Inspector.save_cmps()
        self.FieldInspector.save_fields()
        filenames = QFileDialog.getSaveFileName(
            self, 'Save File As...', '', 'KiCad Schematic Files (*.sch)')

        if filenames[0] == '':
            return

        print('Save File As "' + filenames[0] + '"')
        CmpMgr.save_file(filenames[0])
        CmpMgr.set_curr_file_path(filenames[0])

    #---------------------------------------------------------------------------
    def file_loaded_slot(self):
        text = CmpMgr.curr_file_path()
        self.set_title(text)

    #---------------------------------------------------------------------------
    def data_changed_slot(self):
        text = CmpMgr.curr_file_path() + ' *'
        self.set_title(text)

    #---------------------------------------------------------------------------
    def file_saved_slot(self):
        text = CmpMgr.curr_file_path()
        self.set_title(text)

    #---------------------------------------------------------------------------
    def set_title(self, text=''):
        text = ' - ' + text if len(text) > 0 else ''
        self.setWindowTitle(self.PROGRAM_NAME + ' v' + VERSION + text)

    #---------------------------------------------------------------------------
    def set_status_text_slot(self, text):
        self.statusBar().showMessage(text)

    #---------------------------------------------------------------------------
    def edit_settings(self):
        print('edit settings')
        SettingsDialog = TSettingsDialog(self)
        SettingsDialog.resize(400, 400)
        SettingsDialog.Tabs.setMinimumWidth(800)
        SettingsDialog.show()

    #---------------------------------------------------------------------------
    def show_user_manual_slot(self):
        help = THelpForm(self, 'User\'s Manual', 'main.html')

    #---------------------------------------------------------------------------
    def show_setting_dialog_help_slot(self):
        help = THelpForm(self, 'Settings Dialog', 'settings.html')

    #---------------------------------------------------------------------------
    def show_hotkeys_help_slot(self):
        help = THelpForm(self, 'Hotkeys', 'hotkeys.html')
Ejemplo n.º 11
0
class DatabaseContainer(QSplitter):

    def __init__(self, orientation=Qt.Vertical):
        QSplitter.__init__(self, orientation)
        self.pfile = None
        self._hsplitter = QSplitter(Qt.Horizontal)

        self.lateral_widget = lateral_widget.LateralWidget()
        self._hsplitter.addWidget(self.lateral_widget)
        self.table_widget = table_widget.TableWidget()
        self._hsplitter.addWidget(self.table_widget)

        self.addWidget(self._hsplitter)

        self.query_container = query_container.QueryContainer(self)
        self.addWidget(self.query_container)

        self.modified = False

        self.__nquery = 1

        # Connections
        # FIXME
        self.lateral_widget.itemClicked.connect(
            lambda: self.table_widget.stacked.setCurrentIndex(
                self.lateral_widget.row()))
        # For change table widget item when up/down
        # see issue #39
        self.lateral_widget.itemSelectionChanged.connect(
            lambda: self.table_widget.stacked.setCurrentIndex(
                self.lateral_widget.row()))
        self.query_container.saveEditor['PyQt_PyObject'].connect(
            self.save_query)
        self.setSizes([1, 1])

    def dbname(self):
        """ Return display name """

        return self.pfile.display_name

    def is_new(self):
        return self.pfile.is_new

    def create_database(self, data):
        for table in data.get('tables'):
            # Get data
            table_name = table.get('name')
            header = table.get('header')
            tuples = table.get('tuples')

            # Create relation
            rela = relation.Relation()
            rela.header = header

            # Table view widget
            table_view = custom_table.Table()
            # Model
            model = QStandardItemModel()
            model.setHorizontalHeaderLabels(header)

            # Populate table view
            row_count = 0
            for row in tuples:
                for col_count, i in enumerate(row):
                    item = QStandardItem(i)
                    # Set read only
                    item.setFlags(item.flags() & ~Qt.ItemIsEditable)
                    model.setItem(row_count, col_count, item)
                rela.insert(row)
                row_count += 1

            # Set table model
            table_view.setModel(model)
            # Add relation to relations dict
            self.table_widget.add_relation(table_name, rela)
            # Add table to stacked
            self.table_widget.stacked.addWidget(table_view)
            # Add table name to list widget
            self.lateral_widget.add_item(table_name, rela.count())
        # Select first item
        first_item = self.lateral_widget.topLevelItem(0)
        first_item.setSelected(True)

    def load_relation(self, filenames):
        for filename in filenames:
            with open(filename) as f:
                csv_reader = csv.reader(f)
                header = next(csv_reader)
                rel = relation.Relation()
                rel.header = header
                for i in csv_reader:
                    rel.insert(i)
                relation_name = file_manager.get_basename(filename)
                if not self.table_widget.add_relation(relation_name, rel):
                    QMessageBox.information(self, self.tr("Information"),
                                            self.tr("There is already a "
                                                    "relationship with name "
                                                    "'{}'".format(
                                                        relation_name)))
                    return False

            self.table_widget.add_table(rel, relation_name)
            self.lateral_widget.add_item(relation_name, rel.count())
            return True

    def delete_relation(self):
        selected_items = self.lateral_widget.selectedItems()
        if not selected_items:
            return False
        current_row = 0
        if self.lateral_widget.row() != -1:
            current_row = self.lateral_widget.row()
        if len(selected_items) > 1:
            msg = self.tr("Are you sure you want to delete "
                          "the selected relations?")
        else:
            msg = self.tr("Are you sure you want to delete "
                          "the relation <b>{}</b>?".format(
                              self.lateral_widget.item_text(current_row)))
        msgbox = QMessageBox(self)
        msgbox.setIcon(QMessageBox.Question)
        msgbox.setWindowTitle(self.tr("Confirmation"))
        msgbox.setText(msg)
        msgbox.addButton(self.tr("No"), QMessageBox.NoRole)
        yes_btn = msgbox.addButton(self.tr("Yes"), QMessageBox.YesRole)
        palette = QPalette()
        palette.setColor(QPalette.Button, QColor("#cc575d"))
        palette.setColor(QPalette.ButtonText, QColor("white"))
        yes_btn.setPalette(palette)
        msgbox.exec_()
        r = msgbox.clickedButton()
        if r == yes_btn:
            for item in selected_items:
                index = self.lateral_widget.indexOfTopLevelItem(item)
                # Remove from list
                self.lateral_widget.takeTopLevelItem(index)
                # Remove table
                self.table_widget.remove_table(index)
                # Remove relation
                self.table_widget.remove_relation(item.name)
            return True

    def __on_data_table_changed(self, row, col, data):
        current_relation = self.lateral_widget.current_text()
        # Relation to be update
        rela = self.table_widget.relations.get(current_relation)
        # Clear old content
        rela.clear()
        current_table = self.table_widget.stacked.currentWidget()
        model = current_table.model()
        for i in range(model.rowCount()):
            reg = []
            for j in range(model.columnCount()):
                if row == i and col == j:
                    reg.append(data)
                else:
                    reg.append(model.item(i, j).text())
            # Insert new content
            rela.insert(reg)
        # Update relation
        self.table_widget.relations[current_relation] = rela

    def new_query(self, filename):
        editor_tab_at = self.query_container.is_open(filename)
        if editor_tab_at != -1:
            self.query_container.set_focus_editor_tab(editor_tab_at)
        else:
            query_widget = query_container.QueryWidget()
            # Create object file
            ffile = pfile.File(filename)
            editor = query_widget.get_editor()
            editor.pfile = ffile
            if not filename:
                ffile.filename = 'untitled_{n}.pqf'.format(n=self.__nquery)
            else:
                content = ffile.read()
                editor.setPlainText(content)
            self.query_container.add_tab(query_widget, ffile.display_name)
            self.__nquery += 1

    def save_query(self, editor):
        if not editor:
            editor = self.query_container.currentWidget().get_editor()
        if editor.is_new:
            return self.save_query_as(editor)
        # Get content of editor
        content = editor.toPlainText()
        try:
            editor.pfile.save(content=content)
        except Exception as reason:
            QMessageBox.critical(self, "Error",
                                 self.tr("The file couldn't be saved!"
                                         "\n\n{}".format(reason)))
            return False
        editor.saved()
        return editor.pfile.filename

    def save_query_as(self, editor=None):
        filename = QFileDialog.getSaveFileName(self, self.tr("Save File"),
                                               editor.name,
                                               "Pireal query files(*.pqf)")
        filename = filename[0]
        if not filename:
            return
        # Get the content
        content = editor.toPlainText()
        # Write the file
        editor.pfile.save(content=content, new_fname=filename)
        editor.saved()

    def execute_queries(self):
        self.query_container.execute_queries()

    def execute_selection(self):
        editor = self.query_container.currentWidget().get_editor()
        text_cursor = editor.textCursor()
        if text_cursor.hasSelection():
            query = text_cursor.selectedText()
            self.query_container.execute_queries(query)

    def showEvent(self, event):
        QSplitter.showEvent(self, event)
        qsettings = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat)
        hsizes = qsettings.value('hsplitter_sizes', None)
        if hsizes is not None:
            self._hsplitter.restoreState(hsizes)
        else:
            self._hsplitter.setSizes([1, self._hsplitter.width() / 3])
        vsizes = qsettings.value('vsplitter_sizes', None)
        if vsizes is not None:
            self.restoreState(vsizes)
        else:
            self.setSizes([self.height() / 3, self.height() / 3])

    def save_sizes(self):
        """ Save sizes of Splitters """

        qsettings = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat)
        qsettings.setValue('hsplitter_sizes',
                           self._hsplitter.saveState())
        qsettings.setValue('vsplitter_sizes',
                           self.saveState())
Ejemplo n.º 12
0
class OWPythonScript(widget.OWWidget):
    name = "Python Script"
    description = "Executes a Python script."
    icon = "icons/python_script.png"
    priority = 1

    inputs = [("in_object_1", object, "setObject1"),
              ("in_object_2", object, "setObject2"),
              ("in_object_3", object, "setObject3"),
              ("in_object_4", object, "setObject4"),
              ("in_object_5", object, "setObject5"),
              ("in_object_6", object, "setObject6"),
              ("in_object_7", object, "setObject7"),
              ("in_object_8", object, "setObject8"),
              ("in_object_9", object, "setObject9"),
              ("in_object_10", object, "setObject10")]

    outputs = [("out_object", object, widget.Dynamic)]

    libraryListSource = \
        Setting([Script("Hello world", "print('Hello world')\n")])
    currentScriptIndex = Setting(0)
    splitterState = Setting(None)
    auto_execute = Setting(False)

    fonts = ["8", "9", "10", "11", "12", "14", "16", "20", "24"]
    font_size = Setting(4)

    def __init__(self):
        super().__init__()

        self.in_data = None
        self.in_distance = None
        self.in_learner = None
        self.in_classifier = None
        self.in_object_1 = None
        self.in_object_2 = None
        self.in_object_3 = None
        self.in_object_4 = None
        self.in_object_5 = None
        self.in_object_6 = None
        self.in_object_7 = None
        self.in_object_8 = None
        self.in_object_9 = None
        self.in_object_10 = None

        # MODIFIED BY LUCA REBUFFI 14/10/2014
        #self.auto_execute = False

        for s in self.libraryListSource:
            s.flags = 0

        self._cachedDocuments = {}

        self.infoBox = gui.widgetBox(self.controlArea, 'Info')
        gui.label(
            self.infoBox, self,
            "<p>Execute python script.</p><p>Input variables:<ul><li> " + \
            "<li>".join([self.inputs[0].name, ".",".",".", self.inputs[-1].name]) + \
            "</ul></p><p>Output variables:<ul><li>" + \
            "<li>".join(t.name for t in self.outputs) + \
            "</ul></p>"
        )

        self.optionBox = oasysgui.widgetBox(self.controlArea, 'Options')

        gui.comboBox(self.optionBox,
                     self,
                     "font_size",
                     label="Font Size",
                     labelWidth=120,
                     items=self.fonts,
                     sendSelectedValue=False,
                     orientation="horizontal",
                     callback=self.changeFont)

        self.libraryList = itemmodels.PyListModel(
            [],
            self,
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable)

        self.libraryList.wrap(self.libraryListSource)

        self.controlBox = gui.widgetBox(self.controlArea, 'Library')
        self.controlBox.layout().setSpacing(1)

        self.libraryView = QListView(
            editTriggers=QListView.DoubleClicked | QListView.EditKeyPressed,
            sizePolicy=QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred))
        self.libraryView.setItemDelegate(ScriptItemDelegate(self))
        self.libraryView.setModel(self.libraryList)

        self.libraryView.selectionModel().selectionChanged.connect(
            self.onSelectedScriptChanged)
        self.controlBox.layout().addWidget(self.libraryView)

        w = itemmodels.ModelActionsWidget()

        self.addNewScriptAction = action = QAction("+", self)
        action.setToolTip("Add a new script to the library")
        action.triggered.connect(self.onAddScript)
        w.addAction(action)

        action = QAction(unicodedata.lookup("MINUS SIGN"), self)
        action.setToolTip("Remove script from library")
        action.triggered.connect(self.onRemoveScript)
        w.addAction(action)

        action = QAction("Update", self)
        action.setToolTip("Save changes in the editor to library")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.triggered.connect(self.commitChangesToLibrary)
        w.addAction(action)

        action = QAction("More", self, toolTip="More actions")

        new_from_file = QAction("Import a script from a file", self)
        save_to_file = QAction("Save selected script to a file", self)
        save_to_file.setShortcut(QKeySequence(QKeySequence.SaveAs))

        new_from_file.triggered.connect(self.onAddScriptFromFile)
        save_to_file.triggered.connect(self.saveScript)

        menu = QMenu(w)
        menu.addAction(new_from_file)
        menu.addAction(save_to_file)
        action.setMenu(menu)
        button = w.addAction(action)
        button.setPopupMode(QToolButton.InstantPopup)

        w.layout().setSpacing(1)

        self.controlBox.layout().addWidget(w)

        self.runBox = gui.widgetBox(self.controlArea, 'Run')
        gui.button(self.runBox, self, "Execute", callback=self.execute)
        gui.checkBox(self.runBox,
                     self,
                     "auto_execute",
                     "Auto execute",
                     tooltip="Run the script automatically whenever " +
                     "the inputs to the widget change.")

        self.splitCanvas = QSplitter(Qt.Vertical, self.mainArea)
        self.mainArea.layout().addWidget(self.splitCanvas)

        self.defaultFont = defaultFont = \
            "Monaco" if sys.platform == "darwin" else "Courier"

        self.textBox = gui.widgetBox(self, 'Python script')
        self.splitCanvas.addWidget(self.textBox)
        self.text = PythonScriptEditor(self)
        self.textBox.layout().addWidget(self.text)

        self.textBox.setAlignment(Qt.AlignVCenter)
        self.text.setTabStopWidth(4)

        self.text.modificationChanged[bool].connect(self.onModificationChanged)

        self.saveAction = action = QAction("&Save", self.text)
        action.setToolTip("Save script to file")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        action.triggered.connect(self.saveScript)

        self.consoleBox = gui.widgetBox(self, 'Console')
        self.splitCanvas.addWidget(self.consoleBox)
        self.console = PythonConsole(self.__dict__, self)
        self.consoleBox.layout().addWidget(self.console)
        self.console.document().setDefaultFont(QFont(defaultFont))
        self.consoleBox.setAlignment(Qt.AlignBottom)
        self.console.setTabStopWidth(4)

        select_row(self.libraryView, self.currentScriptIndex)

        self.splitCanvas.setSizes([2, 1])
        if self.splitterState is not None:
            self.splitCanvas.restoreState(
                QByteArray(bytearray(self.splitterState, "ascii")))

        self.splitCanvas.splitterMoved[int, int].connect(self.onSpliterMoved)
        self.controlArea.layout().addStretch(1)
        self.resize(800, 600)

        self.changeFont()

    def setExampleTable(self, et):
        self.in_data = et

    def setDistanceMatrix(self, dm):
        self.in_distance = dm

    def setLearner(self, learner):
        self.in_learner = learner

    def setClassifier(self, classifier):
        self.in_classifier = classifier

    def setObject1(self, obj):
        self.in_object_1 = obj

    def setObject2(self, obj):
        self.in_object_2 = obj

    def setObject3(self, obj):
        self.in_object_3 = obj

    def setObject4(self, obj):
        self.in_object_4 = obj

    def setObject5(self, obj):
        self.in_object_5 = obj

    def setObject6(self, obj):
        self.in_object_6 = obj

    def setObject7(self, obj):
        self.in_object_7 = obj

    def setObject8(self, obj):
        self.in_object_8 = obj

    def setObject9(self, obj):
        self.in_object_9 = obj

    def setObject10(self, obj):
        self.in_object_10 = obj

    def handleNewSignals(self):
        if self.auto_execute:
            self.execute()

    def selectedScriptIndex(self):
        rows = self.libraryView.selectionModel().selectedRows()
        if rows:
            return [i.row() for i in rows][0]
        else:
            return None

    def setSelectedScript(self, index):
        select_row(self.libraryView, index)

    def onAddScript(self, *args):
        self.libraryList.append(Script("New script", "", 0))
        self.setSelectedScript(len(self.libraryList) - 1)

    def onAddScriptFromFile(self, *args):
        filename = QFileDialog.getOpenFileName(
            self, 'Open Python Script', os.path.expanduser("~/"),
            'Python files (*.py)\nAll files(*.*)')[0]

        filename = str(filename)
        if filename:
            name = os.path.basename(filename)
            contents = open(filename, "rb").read().decode("utf-8",
                                                          errors="ignore")
            self.libraryList.append(Script(name, contents, 0, filename))
            self.setSelectedScript(len(self.libraryList) - 1)

    def onRemoveScript(self, *args):
        index = self.selectedScriptIndex()
        if index is not None:
            del self.libraryList[index]
            select_row(self.libraryView, max(index - 1, 0))

    def onSaveScriptToFile(self, *args):
        index = self.selectedScriptIndex()
        if index is not None:
            self.saveScript()

    def onSelectedScriptChanged(self, selected, deselected):
        index = [i.row() for i in selected.indexes()]
        if index:
            current = index[0]
            if current >= len(self.libraryList):
                self.addNewScriptAction.trigger()
                return

            self.text.setDocument(self.documentForScript(current))
            self.currentScriptIndex = current

    def documentForScript(self, script=0):
        if type(script) != Script:
            script = self.libraryList[script]

        if script not in self._cachedDocuments:
            doc = QtGui.QTextDocument(self)
            doc.setDocumentLayout(QtWidgets.QPlainTextDocumentLayout(doc))
            doc.setPlainText(script.script)
            doc.setDefaultFont(QFont(self.defaultFont))
            doc.highlighter = PythonSyntaxHighlighter(doc)
            doc.modificationChanged[bool].connect(self.onModificationChanged)
            doc.setModified(False)
            self._cachedDocuments[script] = doc
        return self._cachedDocuments[script]

    def commitChangesToLibrary(self, *args):
        index = self.selectedScriptIndex()
        if index is not None:
            self.libraryList[index].script = self.text.toPlainText()
            self.text.document().setModified(False)
            self.libraryList.emitDataChanged(index)

    def onModificationChanged(self, modified):
        index = self.selectedScriptIndex()
        if index is not None:
            self.libraryList[index].flags = Script.Modified if modified else 0
            self.libraryList.emitDataChanged(index)

    def onSpliterMoved(self, pos, ind):
        self.splitterState = str(self.splitCanvas.saveState())

    def updateSelecetdScriptState(self):
        index = self.selectedScriptIndex()
        if index is not None:
            script = self.libraryList[index]
            self.libraryList[index] = Script(script.name,
                                             self.text.toPlainText(), 0)

    def saveScript(self):
        index = self.selectedScriptIndex()
        if index is not None:
            script = self.libraryList[index]
            filename = script.filename
        else:
            filename = os.path.expanduser("~/")

        filename = QFileDialog.getSaveFileName(
            self, 'Save Python Script', filename,
            'Python files (*.py)\nAll files(*.*)')[0]

        if filename:
            fn = ""
            head, tail = os.path.splitext(filename)
            if not tail:
                fn = head + ".py"
            else:
                fn = filename

            f = open(fn, 'w')
            f.write(self.text.toPlainText())
            f.close()

    def execute(self):
        self._script = str(self.text.toPlainText())
        self.console.write("\nRunning script:\n")
        self.console.push("exec(_script)")
        self.console.new_prompt(sys.ps1)
        for out in self.outputs:
            signal = out.name
            self.send(signal, getattr(self, signal, None))

    def changeFont(self):
        font = QFont(self.defaultFont)
        font.setPixelSize(int(self.fonts[self.font_size]))
        self.text.setFont(font)
        self.console.setFont(font)
Ejemplo n.º 13
0
class FontsDialog(QDialog):
    """Dialog to show available fonts"""

    _selected_fonts = {
        'music': _default_fonts['music'],
        'brace': _default_fonts['brace'],
        'roman': _default_fonts['roman'],
        'sans': _default_fonts['sans'],
        'typewriter': _default_fonts['typewriter']
    }

    def __init__(self, parent, lilypond_info=None, show_music=True):
        QApplication.setOverrideCursor(Qt.WaitCursor)
        super(FontsDialog, self).__init__(parent, )
        self.info = lilypond_info
        if not lilypond_info:
            import documentinfo
            self.info = documentinfo.lilyinfo(parent.currentDocument())

        self.result = ''
        self.available_fonts = fonts.available(self.info)

        # Notation fonts (and preview) are limited to LilyPond >= 2.19.12
        # At some point we may remove the old dialog altogether
        # and instead make this dialog behave differently
        # (i.e. hide the music font stuff and use old font selection code)
        # self.show_music = self.info.version() >= (2, 19, 12).
        # For now this distinction is made by the action and simply
        # the dialog to be used is chosen. At some point the old
        # "Set document fonts" dialog should be dropped.
        #
        # Also, it may at some point be indicated to make this
        # dialog usable to *only* choose text fonts, e.g. from
        # the "Fonts & Colors" Preference page.
        #
        # NOTE: All facilities that *seemed* to support this functionality
        # have been removed to avoid confusion.
        self.show_music = show_music

        # Basic dialog attributes
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setWindowModality(Qt.WindowModal)

        layout = QVBoxLayout()
        self.setLayout(layout)

        # Create a QSplitter as main widget
        self.splitter = QSplitter()
        self.splitter.setOrientation(Qt.Horizontal)
        layout.addWidget(self.splitter)

        # Left side layout
        self.left_column = QWidget()
        left_layout = QVBoxLayout()
        self.left_column.setLayout(left_layout)
        self.splitter.addWidget(self.left_column)

        # Status area
        # TODO: Populate that widget with a QStackedLayout where
        # different widgets are shown corresponding to the visible tab.
        self.status_area = QWidget()
        left_layout.addWidget(self.status_area)

        # Create the QTabWidget for the dialog's left part
        self.tab_widget = QTabWidget()
        left_layout.addWidget(self.tab_widget)

        # Text Fonts Tab
        self.tab_widget.addTab(textfonts.TextFontsWidget(self), '')
        # Music Fonts Tab
        self.tab_widget.addTab(musicfonts.MusicFontsWidget(self), '')
        # Show/configure the generated font setting command
        self.font_command_tab = fontcommand.FontCommandWidget(self)
        self.tab_widget.addTab(self.font_command_tab, '')
        # Show various fontconfig information
        self.tab_widget.addTab(
            textfonts.MiscFontsInfoWidget(self.available_fonts), '')

        # Create the RHS score preview pane.
        self.preview_pane = preview.FontsPreviewWidget(self)
        self.splitter.addWidget(self.preview_pane)

        # Bottom area: button box
        self._button_box = bb = QDialogButtonBox()
        layout.addWidget(bb)
        self.restore_button = bb.addButton(QDialogButtonBox.RestoreDefaults)
        self.copy_button = bb.addButton(QDialogButtonBox.Save)
        self.insert_button = bb.addButton(QDialogButtonBox.Ok)
        self.close_button = bb.addButton(QDialogButtonBox.Close)
        # Add and connect help button
        userguide.addButton(self._button_box, "documentfonts")

        app.translateUI(self)
        self.loadSettings()
        self.connectSignals()

        # Trigger the generation of a preview
        self.invalidate_command()

    def connectSignals(self):
        self.finished.connect(self.saveSettings)
        self.restore_button.clicked.connect(self.restore)
        self.copy_button.clicked.connect(self.copy_result)
        self.insert_button.clicked.connect(self.insert_result)
        self.close_button.clicked.connect(self.close)

    def translateUI(self):
        self.setWindowTitle(app.caption(_("Document Fonts")))
        self.copy_button.setText(_("&Copy"))
        self.copy_button.setToolTip(_("Copy font command to clipboard"))
        self.insert_button.setText(_("&Use"))
        self.insert_button.setToolTip(
            _("Insert font command at the current cursor position"))
        self.tab_widget.setTabText(0, _("Text Fonts"))
        self.tab_widget.setTabText(1, _("Music Fonts"))
        self.tab_widget.setTabText(2, _("Font Command"))
        self.tab_widget.setTabText(3, _("Miscellaneous"))

    def loadSettings(self):
        s = QSettings()
        s.beginGroup('document-fonts-dialog')

        self.select_font('music', s.value('music-font', 'emmentaler', str))
        self.select_font('brace', s.value('brace-font', 'emmentaler', str))
        self.select_font('roman', s.value('roman-font', 'TeXGyre Schola', str))
        self.select_font('sans', s.value('sans-font', 'TeXGyre Heros', str))
        self.select_font('typewriter',
                         s.value('typewriter-font', 'TeXGyre Cursor', str))
        self.splitter.restoreState(
            s.value('splitter-state', QByteArray(), QByteArray))

    def select_font(self, family, name):
        self._selected_fonts[family] = name

    def selected_font(self, family):
        return self._selected_fonts[family]

    def saveSettings(self):
        s = QSettings()
        s.beginGroup('document-fonts-dialog')

        s.setValue('music-font', self.selected_font('music'))
        s.setValue('brace-font', self.selected_font('brace'))
        s.setValue('roman-font', self.selected_font('roman'))
        s.setValue('sans-font', self.selected_font('sans'))
        s.setValue('typewriter-font', self.selected_font('typewriter'))

        # Dialog layout
        s.setValue('splitter-state', self.splitter.saveState())

    def copy_result(self):
        """Copies the font command (as shown) to the clipboard."""
        from PyQt5.QtGui import QGuiApplication
        cmd = self.font_cmd()
        if cmd[-1] != '\n':
            cmd = cmd + '\n'
        QGuiApplication.clipboard().setText(cmd)
        self.accept()

    def font_cmd(self, approach=None):
        """Return the font setting command as shown in the Font Command tab."""
        approach = approach or self.font_command_tab.approach
        return self.font_command_tab.command(approach)

    def font_full_cmd(self, approach=None):
        """Return the "full" command with all properties/fonts."""
        approach = approach or self.font_command_tab.approach
        return self.font_command_tab.full_cmd(approach)

    def insert_result(self):
        """Inserts the font command (as shown) at the current position"""
        self.result = self.font_cmd()
        self.accept()

    def invalidate_command(self):
        """Triggers a regeneration of the font command and new display of the
        example (if necessary).
        """
        self.font_command_tab.invalidate_command()

    def restore(self):
        """Reset fonts to defaults"""
        for name in _default_fonts:
            self.select_font(name, _default_fonts[name])
        self.invalidate_command()

    def show_sample(self):
        """Call the preview pane to show the current sample."""
        self.preview_pane.show_sample()
Ejemplo n.º 14
0
class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self._version = "0.1.11"
        self.setWindowIcon(QIcon("GUI/icons/logo.png"))
        self.setWindowTitle("Tasmota Device Manager {}".format(self._version))

        self.main_splitter = QSplitter()
        self.devices_splitter = QSplitter(Qt.Vertical)

        self.fulltopic_queue = []
        self.settings = QSettings()
        self.setMinimumSize(QSize(1280,800))

        self.device_model = TasmotaDevicesModel()
        self.telemetry_model = TasmotaDevicesTree()
        self.console_model = ConsoleModel()

        self.sorted_console_model = QSortFilterProxyModel()
        self.sorted_console_model.setSourceModel(self.console_model)
        self.sorted_console_model.setFilterKeyColumn(CnsMdl.FRIENDLY_NAME)

        self.setup_mqtt()
        self.setup_telemetry_view()
        self.setup_main_layout()
        self.add_devices_tab()
        self.build_toolbars()
        self.setStatusBar(QStatusBar())

        self.queue_timer = QTimer()
        self.queue_timer.setSingleShot(True)
        self.queue_timer.timeout.connect(self.mqtt_ask_for_fulltopic)

        self.build_cons_ctx_menu()

        self.load_window_state()

    def setup_main_layout(self):
        self.mdi = QMdiArea()
        self.mdi.setActivationOrder(QMdiArea.ActivationHistoryOrder)
        self.mdi.setViewMode(QMdiArea.TabbedView)
        self.mdi.setDocumentMode(True)

        mdi_widget = QWidget()
        mdi_widget.setLayout(VLayout())
        mdi_widget.layout().addWidget(self.mdi)

        self.devices_splitter.addWidget(mdi_widget)

        vl_console = VLayout()
        self.console_view = TableView()
        self.console_view.setModel(self.sorted_console_model)
        self.console_view.setupColumns(columns_console)
        self.console_view.setAlternatingRowColors(True)
        self.console_view.setSortingEnabled(True)
        self.console_view.sortByColumn(CnsMdl.TIMESTAMP, Qt.DescendingOrder)
        self.console_view.verticalHeader().setDefaultSectionSize(20)
        self.console_view.setMinimumHeight(200)
        self.console_view.setContextMenuPolicy(Qt.CustomContextMenu)

        vl_console.addWidget(self.console_view)

        console_widget = QWidget()
        console_widget.setLayout(vl_console)

        self.devices_splitter.addWidget(console_widget)
        self.main_splitter.insertWidget(0, self.devices_splitter)
        self.setCentralWidget(self.main_splitter)
        self.console_view.clicked.connect(self.select_cons_entry)
        self.console_view.doubleClicked.connect(self.view_payload)
        self.console_view.customContextMenuRequested.connect(self.show_cons_ctx_menu)

    def setup_telemetry_view(self):
        tele_widget = QWidget()
        vl_tele = VLayout()
        self.tview = QTreeView()
        self.tview.setMinimumWidth(300)
        self.tview.setModel(self.telemetry_model)
        self.tview.setAlternatingRowColors(True)
        self.tview.setUniformRowHeights(True)
        self.tview.setIndentation(15)
        self.tview.setSizePolicy(QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum))
        self.tview.expandAll()
        self.tview.resizeColumnToContents(0)
        vl_tele.addWidget(self.tview)
        tele_widget.setLayout(vl_tele)
        self.main_splitter.addWidget(tele_widget)

    def setup_mqtt(self):
        self.mqtt = MqttClient()
        self.mqtt.connecting.connect(self.mqtt_connecting)
        self.mqtt.connected.connect(self.mqtt_connected)
        self.mqtt.disconnected.connect(self.mqtt_disconnected)
        self.mqtt.connectError.connect(self.mqtt_connectError)
        self.mqtt.messageSignal.connect(self.mqtt_message)

    def add_devices_tab(self):
        tabDevicesList = DevicesListWidget(self)
        self.mdi.addSubWindow(tabDevicesList)
        tabDevicesList.setWindowState(Qt.WindowMaximized)

    def load_window_state(self):
        wndGeometry = self.settings.value('window_geometry')
        if wndGeometry:
            self.restoreGeometry(wndGeometry)
        spltState = self.settings.value('splitter_state')
        if spltState:
            self.main_splitter.restoreState(spltState)

    def build_toolbars(self):
        main_toolbar = Toolbar(orientation=Qt.Horizontal, iconsize=32, label_position=Qt.ToolButtonIconOnly)
        main_toolbar.setObjectName("main_toolbar")
        self.addToolBar(main_toolbar)

        main_toolbar.addAction(QIcon("./GUI/icons/connections.png"), "Configure MQTT broker", self.setup_broker)
        agBroker = QActionGroup(self)
        agBroker.setExclusive(True)

        self.actConnect = CheckableAction(QIcon("./GUI/icons/connect.png"), "Connect to the broker", agBroker)
        self.actDisconnect = CheckableAction(QIcon("./GUI/icons/disconnect.png"), "Disconnect from broker", agBroker)

        self.actDisconnect.setChecked(True)

        self.actConnect.triggered.connect(self.mqtt_connect)
        self.actDisconnect.triggered.connect(self.mqtt_disconnect)

        main_toolbar.addActions(agBroker.actions())
        main_toolbar.addSeparator()

    def initial_query(self, idx):
        for q in initial_queries:
            topic = "{}status".format(self.device_model.commandTopic(idx))
            self.mqtt.publish(topic, q)
            q = q if q else ''
            self.console_log(topic, "Asked for STATUS {}".format(q), q)

    def setup_broker(self):
        brokers_dlg = BrokerDialog()
        if brokers_dlg.exec_() == QDialog.Accepted and self.mqtt.state == self.mqtt.Connected:
            self.mqtt.disconnect()

    def mqtt_connect(self):
        self.broker_hostname = self.settings.value('hostname', 'localhost')
        self.broker_port = self.settings.value('port', 1883, int)
        self.broker_username = self.settings.value('username')
        self.broker_password = self.settings.value('password')

        self.mqtt.hostname = self.broker_hostname
        self.mqtt.port = self.broker_port

        if self.broker_username:
            self.mqtt.setAuth(self.broker_username, self.broker_password)

        if self.mqtt.state == self.mqtt.Disconnected:
            self.mqtt.connectToHost()

    def mqtt_disconnect(self):
        self.mqtt.disconnectFromHost()

    def mqtt_connecting(self):
        self.statusBar().showMessage("Connecting to broker")

    def mqtt_connected(self):
        self.statusBar().showMessage("Connected to {}:{} as {}".format(self.broker_hostname, self.broker_port, self.broker_username if self.broker_username else '[anonymous]'))

        self.mqtt_subscribe()

        for d in range(self.device_model.rowCount()):
            idx = self.device_model.index(d, 0)
            self.initial_query(idx)

    def mqtt_subscribe(self):
        main_topics = ["+/stat/+", "+/tele/+", "stat/#", "tele/#"]

        for d in range(self.device_model.rowCount()):
            idx = self.device_model.index(d, 0)
            if not self.device_model.isDefaultTemplate(idx):
                main_topics.append(self.device_model.commandTopic(idx))
                main_topics.append(self.device_model.statTopic(idx))

        for t in main_topics:
            self.mqtt.subscribe(t)

    def mqtt_ask_for_fulltopic(self):
        for i in range(len(self.fulltopic_queue)):
            self.mqtt.publish(self.fulltopic_queue.pop(0))

    def mqtt_disconnected(self):
        self.statusBar().showMessage("Disconnected")

    def mqtt_connectError(self, rc):
        reason = {
            1: "Incorrect protocol version",
            2: "Invalid client identifier",
            3: "Server unavailable",
            4: "Bad username or password",
            5: "Not authorized",
        }
        self.statusBar().showMessage("Connection error: {}".format(reason[rc]))
        self.actDisconnect.setChecked(True)

    def mqtt_message(self, topic, msg):
        found = self.device_model.findDevice(topic)
        if found.reply == 'LWT':
            if not msg:
                msg = "offline"

            if found.index.isValid():
                self.console_log(topic, "LWT update: {}".format(msg), msg)
                self.device_model.updateValue(found.index, DevMdl.LWT, msg)

            elif msg == "Online":
                self.console_log(topic, "LWT for unknown device '{}'. Asking for FullTopic.".format(found.topic), msg, False)
                self.fulltopic_queue.append("cmnd/{}/fulltopic".format(found.topic))
                self.fulltopic_queue.append("{}/cmnd/fulltopic".format(found.topic))
                self.queue_timer.start(1500)

        elif found.reply == 'RESULT':
            full_topic = loads(msg).get('FullTopic')
            new_topic = loads(msg).get('Topic')
            template_name = loads(msg).get('NAME')

            if full_topic:
                # TODO: update FullTopic for existing device AFTER the FullTopic changes externally (the message will arrive from new FullTopic)
                if not found.index.isValid():
                    self.console_log(topic, "FullTopic for {}".format(found.topic), msg, False)

                    new_idx = self.device_model.addDevice(found.topic, full_topic, lwt='online')
                    tele_idx = self.telemetry_model.addDevice(TasmotaDevice, found.topic)
                    self.telemetry_model.devices[found.topic] = tele_idx
                    #TODO: add QSortFilterProxyModel to telemetry treeview and sort devices after adding

                    self.initial_query(new_idx)
                    self.console_log(topic, "Added {} with fulltopic {}, querying for STATE".format(found.topic, full_topic), msg)
                    self.tview.expand(tele_idx)
                    self.tview.resizeColumnToContents(0)

            if new_topic:
                if found.index.isValid() and found.topic != new_topic:
                    self.console_log(topic, "New topic for {}".format(found.topic), msg)

                    self.device_model.updateValue(found.index, DevMdl.TOPIC, new_topic)

                    tele_idx = self.telemetry_model.devices.get(found.topic)

                    if tele_idx:
                        self.telemetry_model.setDeviceName(tele_idx, new_topic)
                        self.telemetry_model.devices[new_topic] = self.telemetry_model.devices.pop(found.topic)

            if template_name:
                self.device_model.updateValue(found.index, DevMdl.MODULE, template_name)

        elif found.index.isValid():
            if found.reply == 'STATUS':
                self.console_log(topic, "Received device status", msg)
                payload = loads(msg)['Status']
                self.device_model.updateValue(found.index, DevMdl.FRIENDLY_NAME, payload['FriendlyName'][0])
                self.telemetry_model.setDeviceFriendlyName(self.telemetry_model.devices[found.topic], payload['FriendlyName'][0])
                self.tview.resizeColumnToContents(0)
                module = payload['Module']
                if module == '0':
                    self.mqtt.publish(self.device_model.commandTopic(found.index)+"template")
                else:
                    self.device_model.updateValue(found.index, DevMdl.MODULE, module)

            elif found.reply == 'STATUS1':
                self.console_log(topic, "Received program information", msg)
                payload = loads(msg)['StatusPRM']
                self.device_model.updateValue(found.index, DevMdl.RESTART_REASON, payload['RestartReason'])

            elif found.reply == 'STATUS2':
                self.console_log(topic, "Received firmware information", msg)
                payload = loads(msg)['StatusFWR']
                self.device_model.updateValue(found.index, DevMdl.FIRMWARE, payload['Version'])
                self.device_model.updateValue(found.index, DevMdl.CORE, payload['Core'])

            elif found.reply == 'STATUS3':
                self.console_log(topic, "Received syslog information", msg)
                payload = loads(msg)['StatusLOG']
                self.device_model.updateValue(found.index, DevMdl.TELEPERIOD, payload['TelePeriod'])

            elif found.reply == 'STATUS5':
                self.console_log(topic, "Received network status", msg)
                payload = loads(msg)['StatusNET']
                self.device_model.updateValue(found.index, DevMdl.MAC, payload['Mac'])
                self.device_model.updateValue(found.index, DevMdl.IP, payload['IPAddress'])

            elif found.reply == 'STATUS8':
                self.console_log(topic, "Received telemetry", msg)
                payload = loads(msg)['StatusSNS']
                self.parse_telemetry(found.index, payload)

            elif found.reply == 'STATUS11':
                self.console_log(topic, "Received device state", msg)
                payload = loads(msg)['StatusSTS']
                self.parse_state(found.index, payload)

            elif found.reply == 'SENSOR':
                self.console_log(topic, "Received telemetry", msg)
                payload = loads(msg)
                self.parse_telemetry(found.index, payload)

            elif found.reply == 'STATE':
                self.console_log(topic, "Received device state", msg)
                payload = loads(msg)
                self.parse_state(found.index, payload)

            elif found.reply.startswith('POWER'):
                self.console_log(topic, "Received {} state".format(found.reply), msg)
                payload = {found.reply: msg}
                self.parse_power(found.index, payload)

    def parse_power(self, index, payload):
        old = self.device_model.power(index)
        power = {k: payload[k] for k in payload.keys() if k.startswith("POWER")}
        needs_update = False
        if old:
            for k in old.keys():
                needs_update |= old[k] != power.get(k, old[k])
                if needs_update:
                    break
        else:
            needs_update = True

        if needs_update:
            self.device_model.updateValue(index, DevMdl.POWER, power)

    def parse_state(self, index, payload):
        bssid = payload['Wifi'].get('BSSId')
        if not bssid:
            bssid = payload['Wifi'].get('APMac')
        self.device_model.updateValue(index, DevMdl.BSSID, bssid)
        self.device_model.updateValue(index, DevMdl.SSID, payload['Wifi']['SSId'])
        self.device_model.updateValue(index, DevMdl.CHANNEL, payload['Wifi'].get('Channel'))
        self.device_model.updateValue(index, DevMdl.RSSI, payload['Wifi']['RSSI'])
        self.device_model.updateValue(index, DevMdl.UPTIME, payload['Uptime'])
        self.device_model.updateValue(index, DevMdl.LOADAVG, payload.get('LoadAvg'))

        self.parse_power(index, payload)

        tele_idx = self.telemetry_model.devices.get(self.device_model.topic(index))

        if tele_idx:
            tele_device = self.telemetry_model.getNode(tele_idx)
            self.telemetry_model.setDeviceFriendlyName(tele_idx, self.device_model.friendly_name(index))

            pr = tele_device.provides()
            for k in pr.keys():
                self.telemetry_model.setData(pr[k], payload.get(k))

    def parse_telemetry(self, index, payload):
        device = self.telemetry_model.devices.get(self.device_model.topic(index))
        if device:
            node = self.telemetry_model.getNode(device)
            time = node.provides()['Time']
            if 'Time' in payload:
                self.telemetry_model.setData(time, payload.pop('Time'))

            temp_unit = "C"
            pres_unit = "hPa"

            if 'TempUnit' in payload:
                temp_unit = payload.pop('TempUnit')

            if 'PressureUnit' in payload:
                pres_unit = payload.pop('PressureUnit')

            for sensor in sorted(payload.keys()):
                if sensor == 'DS18x20':
                    for sns_name in payload[sensor].keys():
                        d = node.devices().get(sensor)
                        if not d:
                            d = self.telemetry_model.addDevice(DS18x20, payload[sensor][sns_name]['Type'], device)
                        self.telemetry_model.getNode(d).setTempUnit(temp_unit)
                        payload[sensor][sns_name]['Id'] = payload[sensor][sns_name].pop('Address')

                        pr = self.telemetry_model.getNode(d).provides()
                        for pk in pr.keys():
                            self.telemetry_model.setData(pr[pk], payload[sensor][sns_name].get(pk))
                        self.tview.expand(d)

                elif sensor.startswith('DS18B20'):
                    d = node.devices().get(sensor)
                    if not d:
                        d = self.telemetry_model.addDevice(DS18x20, sensor, device)
                    self.telemetry_model.getNode(d).setTempUnit(temp_unit)
                    pr = self.telemetry_model.getNode(d).provides()
                    for pk in pr.keys():
                        self.telemetry_model.setData(pr[pk], payload[sensor].get(pk))
                    self.tview.expand(d)

                if sensor == 'COUNTER':
                    d = node.devices().get(sensor)
                    if not d:
                        d = self.telemetry_model.addDevice(CounterSns, "Counter", device)
                    pr = self.telemetry_model.getNode(d).provides()
                    for pk in pr.keys():
                        self.telemetry_model.setData(pr[pk], payload[sensor].get(pk))
                    self.tview.expand(d)

                else:
                    d = node.devices().get(sensor)
                    if not d:
                        d = self.telemetry_model.addDevice(sensor_map.get(sensor, Node), sensor, device)
                    pr = self.telemetry_model.getNode(d).provides()
                    if 'Temperature' in pr:
                        self.telemetry_model.getNode(d).setTempUnit(temp_unit)
                    if 'Pressure' in pr or 'SeaPressure' in pr:
                        self.telemetry_model.getNode(d).setPresUnit(pres_unit)
                    for pk in pr.keys():
                        self.telemetry_model.setData(pr[pk], payload[sensor].get(pk))
                    self.tview.expand(d)
        self.tview.resizeColumnToContents(0)

    def console_log(self, topic, description, payload, known=True):
        device = self.device_model.findDevice(topic)
        fname = self.device_model.friendly_name(device.index)
        self.console_model.addEntry(topic, fname, description, payload, known)
        self.console_view.resizeColumnToContents(1)

    def view_payload(self, idx):
        idx = self.sorted_console_model.mapToSource(idx)
        row = idx.row()
        timestamp = self.console_model.data(self.console_model.index(row, CnsMdl.TIMESTAMP))
        topic = self.console_model.data(self.console_model.index(row, CnsMdl.TOPIC))
        payload = self.console_model.data(self.console_model.index(row, CnsMdl.PAYLOAD))

        dlg = PayloadViewDialog(timestamp, topic, payload)
        dlg.exec_()

    def select_cons_entry(self, idx):
        self.cons_idx = idx

    def build_cons_ctx_menu(self):
        self.cons_ctx_menu = QMenu()
        self.cons_ctx_menu.addAction("View payload", lambda: self.view_payload(self.cons_idx))
        self.cons_ctx_menu.addSeparator()
        self.cons_ctx_menu.addAction("Show only this device", lambda: self.cons_set_filter(self.cons_idx))
        self.cons_ctx_menu.addAction("Show all devices", self.cons_set_filter)

    def show_cons_ctx_menu(self, at):
        self.select_cons_entry(self.console_view.indexAt(at))
        self.cons_ctx_menu.popup(self.console_view.viewport().mapToGlobal(at))

    def cons_set_filter(self, idx=None):
        if idx:
            idx = self.sorted_console_model.mapToSource(idx)
            topic = self.console_model.data(self.console_model.index(idx.row(), CnsMdl.FRIENDLY_NAME))
            self.sorted_console_model.setFilterFixedString(topic)
        else:
            self.sorted_console_model.setFilterFixedString("")

    def closeEvent(self, e):
        self.settings.setValue("window_geometry", self.saveGeometry())
        self.settings.setValue("splitter_state", self.main_splitter.saveState())
        self.settings.sync()
        e.accept()
Ejemplo n.º 15
0
class MainWindow(QMainWindow):
    
    KEY_WINDOW_SIZE = 'main_window/size'
    KEY_WINDOW_MAXIMIZED = 'main_window/maximized'
    KEY_WINDOW_POSITION = 'main_window/position'
    KEY_H_SPLITTER_STATE = 'main_window/h_splitter_state'
    
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle('Mojuru')
        app_icon = QIcon('images/mojuru_logo.png')
        self.setWindowIcon(app_icon)
        
        reload_modules_action = QAction('Reload MainWindow', self)
        reload_modules_action.setShortcut('ctrl+shift+alt+r')
        reload_modules_action.triggered.connect(self.reload_central_widget)
        self.addAction(reload_modules_action)
        
        quit_action = QAction('Quit', self)
        quit_action.setShortcut('ctrl+q')
        quit_action.triggered.connect(self.on_quit)
        self.addAction(quit_action)
        
        self.vertical_splitter = QSplitter(Qt.Vertical, self)
        self.setCentralWidget(self.vertical_splitter)
        self.load_central_widget()
        
        self.file_menu = self.menuBar().addMenu(self.tr('&File'))
        self.file_menu.addAction(quit_action)
        self.file_menu.addSeparator()
        
        self.module_menu = self.menuBar().addMenu(self.tr('&Modules'))
        self.module_menu.addAction(reload_modules_action)
        self.module_menu.addSeparator()
        
        Alter.invoke_all('main_window_init', self)
        
        #restore main window state
        size = ModuleManager.core['settings'].Settings.value(
            self.KEY_WINDOW_SIZE, QSize(600, 400))
        maximized = ModuleManager.core['settings'].Settings.value(
            self.KEY_WINDOW_MAXIMIZED, False)
        position = ModuleManager.core['settings'].Settings.value(
            self.KEY_WINDOW_POSITION, QPoint(0,0))
        if maximized == 'true':
            self.showMaximized()
        else:
            self.resize(size)
            self.move(position)
    
    def closeEvent(self, event):
        self.on_quit()
    
    def save_state(self):
        ModuleManager.core['settings'].Settings.set_value(
            self.KEY_WINDOW_SIZE, self.size())
        ModuleManager.core['settings'].Settings.set_value(
            self.KEY_WINDOW_MAXIMIZED, self.isMaximized())
        ModuleManager.core['settings'].Settings.set_value(
            self.KEY_WINDOW_POSITION, self.pos())
        ModuleManager.core['settings'].Settings.set_value(
            self.KEY_H_SPLITTER_STATE, self.horizontal_splitter.saveState())
    
    def on_quit(self):
        self.save_state()
        self.close()
    
    def load_central_widget(self):
        self.populate_central_widget()
        self.connect_widgets()
    
    def populate_central_widget(self):
        self.vertical_widgets = collections.OrderedDict()
        
        self.horizontal_splitter = QSplitter(
            Qt.Horizontal, self.vertical_splitter)
        self.horizontal_widgets = collections.OrderedDict()
        self.vertical_widgets["horizontal_splitter"] = self.horizontal_splitter
        
        Alter.invoke_all(
            'main_window_add_vertical_widget',
            self.vertical_widgets,
            self
        )
        for widget in self.vertical_widgets.values():
            self.vertical_splitter.addWidget(widget)
        
        Alter.invoke_all(
            'main_window_add_horizontal_widget',
            self.horizontal_widgets,
            self.vertical_splitter
        )
        for widget in self.horizontal_widgets.values():
            self.horizontal_splitter.addWidget(widget)
        
        #restore horizontal splitter state
        state = ModuleManager.core['settings'].Settings.value(
            self.KEY_H_SPLITTER_STATE,
            None
        )
        if state:
            self.horizontal_splitter.restoreState(state)
    
    def connect_widgets(self):
        Alter.invoke_all(
            'main_window_connect_widgets', 
            self.vertical_widgets, 
            self.horizontal_widgets
        )
    
    def reload_central_widget(self):
        self.save_state()
        for index in range(self.vertical_splitter.count()):
            widget = self.vertical_splitter.widget(index)
            widget.hide()
            widget.setParent(None)
            del widget
        Alter.clear()
        ModuleManager.reload_all_modules('core')
        ModuleManager.reload_all_modules('custom')
        self.load_central_widget()
Ejemplo n.º 16
0
class MainWindow(QMainWindow):
    EXIT_CODE_REBOOT = 520

    def __init__(self):
        super().__init__()
        Config.initialize()
        self.initUI()

    def initUI(self):
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.spliter = QSplitter(Qt.Vertical)
        self.spliter.addWidget(TestUnitArea())
        self.spliter.addWidget(TestResultArea())
        self.spliter.setHandleWidth(1)
        self.setCentralWidget(self.spliter)

        tool_menu = QMenu('工具', self.menuBar())
        tool_menu.addAction('数据监听', self.onDebugWindow)
        tool_menu.addAction('单步测试', self.onSingleStep)
        tool_menu.addAction('记录查询', self.onViewData)
        tool_menu.addAction('条码打印', self.onPrintBarCode)
        tool_menu.addAction('异常信息', self.onExceptionWindow)

        setting_menu = QMenu('选项', self.menuBar())
        setting_menu.addAction('参数设置', self.onSetting)
        # setting_menu.addAction('软件重启', self.onRestart)

        help_menu = QMenu('帮助', self.menuBar())
        help_menu.addAction('关于', self.onAbout)

        self.menuBar().addMenu(setting_menu)
        self.menuBar().addMenu(tool_menu)
        self.menuBar().addMenu(help_menu)

        QApplication.setWindowIcon(QIcon(Config.LOGO_IMG))
        QApplication.instance().aboutToQuit.connect(self.onApplicationQuit)
        QApplication.setOrganizationName(Config.ORGANIZATION)
        QApplication.setApplicationName(Config.APP_NAME)
        QApplication.setApplicationVersion(Config.APP_VERSION)
        self.restoreQSettings()
        self.createSystemTray()

    def onDebugWindow(self):
        if not ui.DebugDialog.prev_actived:
            self.debugWin = ui.DebugDialog()
            self.debugWin.show()
        else:
            QApplication.setActiveWindow(ui.DebugDialog.prev_window)
            ui.DebugDialog.prev_window.showNormal()

    def onSingleStep(self):
        if not ui.SingleStepFrame.prev_actived:
            self.singleWin = ui.SingleStepFrame()
            self.singleWin.show()
        else:
            QApplication.setActiveWindow(ui.SingleStepFrame.prev_window)
            ui.SingleStepFrame.prev_window.showNormal()

    def onViewData(self):
        if not ui.SearchWindow.prev_actived:
            self.searchWin = ui.SearchWindow()
            self.searchWin.show()
        else:
            QApplication.setActiveWindow(ui.SearchWindow.prev_window)
            ui.SearchWindow.prev_window.showNormal()

    def onPrintBarCode(self):
        if not CodeDialog.prev_actived:
            self.codeWin = CodeDialog()
            self.codeWin.show()
        else:
            QApplication.setActiveWindow(CodeDialog.prev_window)
            CodeDialog.prev_window.showNormal()

    def onExceptionWindow(self):
        if not ui.ExceptionWindow.prev_actived:
            self.excptionWin = ui.ExceptionWindow()
            self.excptionWin.show()
        else:
            QApplication.setActiveWindow(ui.ExceptionWindow.prev_window)
            ui.ExceptionWindow.prev_window.showNormal()

    def restoreQSettings(self):
        main_win_geo = Config.QSETTING.value('MainWindow/geometry')
        main_win_centerwgt_state = Config.QSETTING.value(
            'MainWindow/CenterWidget/state')

        if main_win_geo: self.restoreGeometry(main_win_geo)
        if main_win_centerwgt_state:
            self.spliter.restoreState(main_win_centerwgt_state)

    def onSetting(self):
        dlg = ui.SettingDialog(self)
        dlg.move(self.x() + 50, self.y() + 50)
        dlg.exec()

    def onRestart(self):
        QApplication.exit(self.EXIT_CODE_REBOOT)

    def onAbout(self):
        dlg = ui.AboutDialog(Config.ABOUT_HTML)
        dlg.resize(400, 300)
        dlg.exec()

    def createSystemTray(self):
        self.systray = QSystemTrayIcon(self)
        self.systray.setIcon(QIcon(Config.LOGO_IMG))
        self.systray.show()

        trayMenu = QMenu()
        trayMenu.addAction('最大化', self.showMaximized)
        trayMenu.addAction('最小化', self.showMinimized)
        trayMenu.addAction('显示窗口', self.showNormal)
        stayOnTop = QAction('总在最前',
                            trayMenu,
                            checkable=True,
                            triggered=self.stayOnTop)
        trayMenu.addAction(stayOnTop)
        trayMenu.addSeparator()
        trayMenu.addAction('退出', QApplication.quit)

        username = platform.node()
        ip = socket.gethostbyname(socket.gethostname())

        self.systray.setToolTip('用户:{}\nIP:{}'.format(username, ip))
        self.systray.activated.connect(self.onSystemTrayActivated)
        self.systray.setContextMenu(trayMenu)

    def onSystemTrayActivated(self, reason):
        if reason in [QSystemTrayIcon.DoubleClick, QSystemTrayIcon.Trigger]:
            self.showNormal()

    def stayOnTop(self, checked):
        self.setWindowFlag(Qt.WindowStaysOnTopHint, self.sender().isChecked())
        self.show()

    def onApplicationQuit(self):
        Config.QSETTING.setValue('MainWindow/geometry', self.saveGeometry())
        Config.QSETTING.setValue('MainWindow/CenterWidget/state',
                                 self.spliter.saveState())
        Config.finalize()
        self.systray.deleteLater()

    def closeEvent(self, event):
        Config.QSETTING.setValue('MainWindow/geometry', self.saveGeometry())
        Config.QSETTING.setValue('MainWindow/CenterWidget/state',
                                 self.spliter.saveState())

        if self.systray.isVisible():
            self.hide()
            event.ignore()
Ejemplo n.º 17
0
class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self._version = "0.1.20"
        self.setWindowIcon(QIcon("GUI/icons/logo.png"))
        self.setWindowTitle("Tasmota Device Manager {}".format(self._version))

        self.main_splitter = QSplitter()
        self.devices_splitter = QSplitter(Qt.Vertical)

        self.mqtt_queue = []
        self.devices = {}

        self.fulltopic_queue = []
        old_settings = QSettings()

        self.settings = QSettings("{}/TDM/tdm.cfg".format(QDir.homePath()),
                                  QSettings.IniFormat)
        self.setMinimumSize(QSize(1280, 800))

        for k in old_settings.allKeys():
            self.settings.setValue(k, old_settings.value(k))
            old_settings.remove(k)

        self.device_model = TasmotaDevicesModel()
        self.telemetry_model = TasmotaDevicesTree()
        self.console_model = ConsoleModel()

        self.sorted_console_model = QSortFilterProxyModel()
        self.sorted_console_model.setSourceModel(self.console_model)
        self.sorted_console_model.setFilterKeyColumn(CnsMdl.FRIENDLY_NAME)

        self.setup_mqtt()
        self.setup_telemetry_view()
        self.setup_main_layout()
        self.add_devices_tab()
        self.build_toolbars()
        self.setStatusBar(QStatusBar())

        self.queue_timer = QTimer()
        self.queue_timer.timeout.connect(self.mqtt_publish_queue)
        self.queue_timer.start(500)

        self.auto_timer = QTimer()
        self.auto_timer.timeout.connect(self.autoupdate)

        self.load_window_state()

        if self.settings.value("connect_on_startup", False, bool):
            self.actToggleConnect.trigger()

    def setup_main_layout(self):
        self.mdi = QMdiArea()
        self.mdi.setActivationOrder(QMdiArea.ActivationHistoryOrder)
        self.mdi.setViewMode(QMdiArea.TabbedView)
        self.mdi.setDocumentMode(True)

        mdi_widget = QWidget()
        mdi_widget.setLayout(VLayout())
        mdi_widget.layout().addWidget(self.mdi)

        self.devices_splitter.addWidget(mdi_widget)

        vl_console = VLayout()
        hl_filter = HLayout()
        self.cbFilter = QCheckBox("Console filtering")
        self.cbxFilterDevice = QComboBox()
        self.cbxFilterDevice.setEnabled(False)
        self.cbxFilterDevice.setFixedWidth(200)
        self.cbxFilterDevice.setModel(self.device_model)
        self.cbxFilterDevice.setModelColumn(DevMdl.FRIENDLY_NAME)
        hl_filter.addWidgets([self.cbFilter, self.cbxFilterDevice])
        hl_filter.addStretch(0)
        vl_console.addLayout(hl_filter)

        self.console_view = TableView()
        self.console_view.setModel(self.console_model)
        self.console_view.setupColumns(columns_console)
        self.console_view.setAlternatingRowColors(True)
        self.console_view.verticalHeader().setDefaultSectionSize(20)
        self.console_view.setMinimumHeight(200)

        vl_console.addWidget(self.console_view)

        console_widget = QWidget()
        console_widget.setLayout(vl_console)

        self.devices_splitter.addWidget(console_widget)
        self.main_splitter.insertWidget(0, self.devices_splitter)
        self.setCentralWidget(self.main_splitter)
        self.console_view.clicked.connect(self.select_cons_entry)
        self.console_view.doubleClicked.connect(self.view_payload)

        self.cbFilter.toggled.connect(self.toggle_console_filter)
        self.cbxFilterDevice.currentTextChanged.connect(
            self.select_console_filter)

    def setup_telemetry_view(self):
        tele_widget = QWidget()
        vl_tele = VLayout()
        self.tview = QTreeView()
        self.tview.setMinimumWidth(300)
        self.tview.setModel(self.telemetry_model)
        self.tview.setAlternatingRowColors(True)
        self.tview.setUniformRowHeights(True)
        self.tview.setIndentation(15)
        self.tview.setSizePolicy(
            QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum))
        self.tview.expandAll()
        self.tview.resizeColumnToContents(0)
        vl_tele.addWidget(self.tview)
        tele_widget.setLayout(vl_tele)
        self.main_splitter.addWidget(tele_widget)

    def setup_mqtt(self):
        self.mqtt = MqttClient()
        self.mqtt.connecting.connect(self.mqtt_connecting)
        self.mqtt.connected.connect(self.mqtt_connected)
        self.mqtt.disconnected.connect(self.mqtt_disconnected)
        self.mqtt.connectError.connect(self.mqtt_connectError)
        self.mqtt.messageSignal.connect(self.mqtt_message)

    def add_devices_tab(self):
        tabDevicesList = DevicesListWidget(self)
        self.mdi.addSubWindow(tabDevicesList)
        tabDevicesList.setWindowState(Qt.WindowMaximized)

    def load_window_state(self):
        wndGeometry = self.settings.value('window_geometry')
        if wndGeometry:
            self.restoreGeometry(wndGeometry)
        spltState = self.settings.value('splitter_state')
        if spltState:
            self.main_splitter.restoreState(spltState)

    def build_toolbars(self):
        main_toolbar = Toolbar(orientation=Qt.Horizontal,
                               iconsize=16,
                               label_position=Qt.ToolButtonTextBesideIcon)
        main_toolbar.setObjectName("main_toolbar")
        self.addToolBar(main_toolbar)

        main_toolbar.addAction(QIcon("./GUI/icons/connections.png"), "Broker",
                               self.setup_broker)
        self.actToggleConnect = QAction(QIcon("./GUI/icons/disconnect.png"),
                                        "MQTT")
        self.actToggleConnect.setCheckable(True)
        self.actToggleConnect.toggled.connect(self.toggle_connect)
        main_toolbar.addAction(self.actToggleConnect)

        self.actToggleAutoUpdate = QAction(QIcon("./GUI/icons/automatic.png"),
                                           "Auto telemetry")
        self.actToggleAutoUpdate.setCheckable(True)
        self.actToggleAutoUpdate.toggled.connect(self.toggle_autoupdate)
        main_toolbar.addAction(self.actToggleAutoUpdate)

        main_toolbar.addSeparator()
        main_toolbar.addAction(QIcon("./GUI/icons/bssid.png"), "BSSId",
                               self.bssid)
        main_toolbar.addAction(QIcon("./GUI/icons/export.png"), "Export list",
                               self.export)

    def initial_query(self, idx, queued=False):
        for q in initial_queries:
            topic = "{}status".format(self.device_model.commandTopic(idx))
            if queued:
                self.mqtt_queue.append([topic, q])
            else:
                self.mqtt.publish(topic, q, 1)
            self.console_log(topic, "Asked for STATUS {}".format(q), q)

    def setup_broker(self):
        brokers_dlg = BrokerDialog()
        if brokers_dlg.exec_(
        ) == QDialog.Accepted and self.mqtt.state == self.mqtt.Connected:
            self.mqtt.disconnect()

    def toggle_autoupdate(self, state):
        if state:
            self.auto_timer.setInterval(5000)
            self.auto_timer.start()

    def toggle_connect(self, state):
        if state and self.mqtt.state == self.mqtt.Disconnected:
            self.broker_hostname = self.settings.value('hostname', 'localhost')
            self.broker_port = self.settings.value('port', 1883, int)
            self.broker_username = self.settings.value('username')
            self.broker_password = self.settings.value('password')

            self.mqtt.hostname = self.broker_hostname
            self.mqtt.port = self.broker_port

            if self.broker_username:
                self.mqtt.setAuth(self.broker_username, self.broker_password)
            self.mqtt.connectToHost()
        elif not state and self.mqtt.state == self.mqtt.Connected:
            self.mqtt_disconnect()

    def autoupdate(self):
        if self.mqtt.state == self.mqtt.Connected:
            for d in range(self.device_model.rowCount()):
                idx = self.device_model.index(d, 0)
                cmnd = self.device_model.commandTopic(idx)
                self.mqtt.publish(cmnd + "STATUS", payload=8)

    def mqtt_connect(self):
        self.broker_hostname = self.settings.value('hostname', 'localhost')
        self.broker_port = self.settings.value('port', 1883, int)
        self.broker_username = self.settings.value('username')
        self.broker_password = self.settings.value('password')

        self.mqtt.hostname = self.broker_hostname
        self.mqtt.port = self.broker_port

        if self.broker_username:
            self.mqtt.setAuth(self.broker_username, self.broker_password)

        if self.mqtt.state == self.mqtt.Disconnected:
            self.mqtt.connectToHost()

    def mqtt_disconnect(self):
        self.mqtt.disconnectFromHost()

    def mqtt_connecting(self):
        self.statusBar().showMessage("Connecting to broker")

    def mqtt_connected(self):
        self.actToggleConnect.setIcon(QIcon("./GUI/icons/connect.png"))
        self.statusBar().showMessage("Connected to {}:{} as {}".format(
            self.broker_hostname, self.broker_port,
            self.broker_username if self.broker_username else '[anonymous]'))

        self.mqtt_subscribe()

        for d in range(self.device_model.rowCount()):
            idx = self.device_model.index(d, 0)
            self.initial_query(idx)

    def mqtt_subscribe(self):
        main_topics = ["+/stat/+", "+/tele/+", "stat/#", "tele/#"]

        for d in range(self.device_model.rowCount()):
            idx = self.device_model.index(d, 0)
            if not self.device_model.isDefaultTemplate(idx):
                main_topics.append(self.device_model.commandTopic(idx))
                main_topics.append(self.device_model.statTopic(idx))

        for t in main_topics:
            self.mqtt.subscribe(t)

    def mqtt_publish_queue(self):
        for q in self.mqtt_queue:
            t, p = q
            self.mqtt.publish(t, p)
            self.mqtt_queue.pop(self.mqtt_queue.index(q))

    def mqtt_disconnected(self):
        self.actToggleConnect.setIcon(QIcon("./GUI/icons/disconnect.png"))
        self.statusBar().showMessage("Disconnected")

    def mqtt_connectError(self, rc):
        reason = {
            1: "Incorrect protocol version",
            2: "Invalid client identifier",
            3: "Server unavailable",
            4: "Bad username or password",
            5: "Not authorized",
        }
        self.statusBar().showMessage("Connection error: {}".format(reason[rc]))
        self.actToggleConnect.setChecked(False)

    def mqtt_message(self, topic, msg):
        found = self.device_model.findDevice(topic)
        if found.reply == 'LWT':
            if not msg:
                msg = "offline"

            if found.index.isValid():
                self.console_log(topic, "LWT update: {}".format(msg), msg)
                self.device_model.updateValue(found.index, DevMdl.LWT, msg)
                self.initial_query(found.index, queued=True)

            elif msg == "Online":
                self.console_log(
                    topic,
                    "LWT for unknown device '{}'. Asking for FullTopic.".
                    format(found.topic), msg, False)
                self.mqtt_queue.append(
                    ["cmnd/{}/fulltopic".format(found.topic), ""])
                self.mqtt_queue.append(
                    ["{}/cmnd/fulltopic".format(found.topic), ""])

        elif found.reply == 'RESULT':
            try:
                full_topic = loads(msg).get('FullTopic')
                new_topic = loads(msg).get('Topic')
                template_name = loads(msg).get('NAME')
                ota_url = loads(msg).get('OtaUrl')
                teleperiod = loads(msg).get('TelePeriod')

                if full_topic:
                    # TODO: update FullTopic for existing device AFTER the FullTopic changes externally (the message will arrive from new FullTopic)
                    if not found.index.isValid():
                        self.console_log(
                            topic, "FullTopic for {}".format(found.topic), msg,
                            False)

                        new_idx = self.device_model.addDevice(found.topic,
                                                              full_topic,
                                                              lwt='online')
                        tele_idx = self.telemetry_model.addDevice(
                            TasmotaDevice, found.topic)
                        self.telemetry_model.devices[found.topic] = tele_idx
                        #TODO: add QSortFilterProxyModel to telemetry treeview and sort devices after adding

                        self.initial_query(new_idx)
                        self.console_log(
                            topic,
                            "Added {} with fulltopic {}, querying for STATE".
                            format(found.topic, full_topic), msg)
                        self.tview.expand(tele_idx)
                        self.tview.resizeColumnToContents(0)

                elif new_topic:
                    if found.index.isValid() and found.topic != new_topic:
                        self.console_log(
                            topic, "New topic for {}".format(found.topic), msg)

                        self.device_model.updateValue(found.index,
                                                      DevMdl.TOPIC, new_topic)

                        tele_idx = self.telemetry_model.devices.get(
                            found.topic)

                        if tele_idx:
                            self.telemetry_model.setDeviceName(
                                tele_idx, new_topic)
                            self.telemetry_model.devices[
                                new_topic] = self.telemetry_model.devices.pop(
                                    found.topic)

                elif template_name:
                    self.device_model.updateValue(
                        found.index, DevMdl.MODULE,
                        "{} (0)".format(template_name))

                elif ota_url:
                    self.device_model.updateValue(found.index, DevMdl.OTA_URL,
                                                  ota_url)

                elif teleperiod:
                    self.device_model.updateValue(found.index,
                                                  DevMdl.TELEPERIOD,
                                                  teleperiod)

            except JSONDecodeError as e:
                self.console_log(
                    topic,
                    "JSON payload decode error. Check error.log for additional info."
                )
                with open("{}/TDM/error.log".format(QDir.homePath()),
                          "a+") as l:
                    l.write("{}\t{}\t{}\t{}\n".format(
                        QDateTime.currentDateTime().toString(
                            "yyyy-MM-dd hh:mm:ss"), topic, msg, e.msg))

        elif found.index.isValid():
            ok = False
            try:
                if msg.startswith("{"):
                    payload = loads(msg)
                else:
                    payload = msg
                ok = True
            except JSONDecodeError as e:
                self.console_log(
                    topic,
                    "JSON payload decode error. Check error.log for additional info."
                )
                with open("{}/TDM/error.log".format(QDir.homePath()),
                          "a+") as l:
                    l.write("{}\t{}\t{}\t{}\n".format(
                        QDateTime.currentDateTime().toString(
                            "yyyy-MM-dd hh:mm:ss"), topic, msg, e.msg))

            if ok:
                try:
                    if found.reply == 'STATUS':
                        self.console_log(topic, "Received device status", msg)
                        payload = payload['Status']
                        self.device_model.updateValue(
                            found.index, DevMdl.FRIENDLY_NAME,
                            payload['FriendlyName'][0])
                        self.telemetry_model.setDeviceFriendlyName(
                            self.telemetry_model.devices[found.topic],
                            payload['FriendlyName'][0])
                        module = payload['Module']
                        if module == 0:
                            self.mqtt.publish(
                                self.device_model.commandTopic(found.index) +
                                "template")
                        else:
                            self.device_model.updateValue(
                                found.index, DevMdl.MODULE,
                                modules.get(module, 'Unknown'))
                        self.device_model.updateValue(found.index,
                                                      DevMdl.MODULE_ID, module)

                    elif found.reply == 'STATUS1':
                        self.console_log(topic, "Received program information",
                                         msg)
                        payload = payload['StatusPRM']
                        self.device_model.updateValue(
                            found.index, DevMdl.RESTART_REASON,
                            payload.get('RestartReason'))
                        self.device_model.updateValue(found.index,
                                                      DevMdl.OTA_URL,
                                                      payload.get('OtaUrl'))

                    elif found.reply == 'STATUS2':
                        self.console_log(topic,
                                         "Received firmware information", msg)
                        payload = payload['StatusFWR']
                        self.device_model.updateValue(found.index,
                                                      DevMdl.FIRMWARE,
                                                      payload['Version'])
                        self.device_model.updateValue(found.index, DevMdl.CORE,
                                                      payload['Core'])

                    elif found.reply == 'STATUS3':
                        self.console_log(topic, "Received syslog information",
                                         msg)
                        payload = payload['StatusLOG']
                        self.device_model.updateValue(found.index,
                                                      DevMdl.TELEPERIOD,
                                                      payload['TelePeriod'])

                    elif found.reply == 'STATUS5':
                        self.console_log(topic, "Received network status", msg)
                        payload = payload['StatusNET']
                        self.device_model.updateValue(found.index, DevMdl.MAC,
                                                      payload['Mac'])
                        self.device_model.updateValue(found.index, DevMdl.IP,
                                                      payload['IPAddress'])

                    elif found.reply in ('STATE', 'STATUS11'):
                        self.console_log(topic, "Received device state", msg)
                        if found.reply == 'STATUS11':
                            payload = payload['StatusSTS']
                        self.parse_state(found.index, payload)

                    elif found.reply in ('SENSOR', 'STATUS8'):
                        self.console_log(topic, "Received telemetry", msg)
                        if found.reply == 'STATUS8':
                            payload = payload['StatusSNS']
                        self.parse_telemetry(found.index, payload)

                    elif found.reply.startswith('POWER'):
                        self.console_log(
                            topic, "Received {} state".format(found.reply),
                            msg)
                        payload = {found.reply: msg}
                        self.parse_power(found.index, payload)

                except KeyError as k:
                    self.console_log(
                        topic,
                        "JSON key error. Check error.log for additional info.")
                    with open("{}/TDM/error.log".format(QDir.homePath()),
                              "a+") as l:
                        l.write("{}\t{}\t{}\tKeyError: {}\n".format(
                            QDateTime.currentDateTime().toString(
                                "yyyy-MM-dd hh:mm:ss"), topic, payload,
                            k.args[0]))

    def parse_power(self, index, payload, from_state=False):
        old = self.device_model.power(index)
        power = {
            k: payload[k]
            for k in payload.keys() if k.startswith("POWER")
        }
        # TODO: fix so that number of relays get updated properly after module/no. of relays change
        needs_update = False
        if old:
            # if from_state and len(old) != len(power):
            #     needs_update = True
            #
            # else:
            for k in old.keys():
                needs_update |= old[k] != power.get(k, old[k])
                if needs_update:
                    break
        else:
            needs_update = True

        if needs_update:
            self.device_model.updateValue(index, DevMdl.POWER, power)

    def parse_state(self, index, payload):
        bssid = payload['Wifi'].get('BSSId')
        if not bssid:
            bssid = payload['Wifi'].get('APMac')
        self.device_model.updateValue(index, DevMdl.BSSID, bssid)
        self.device_model.updateValue(index, DevMdl.SSID,
                                      payload['Wifi']['SSId'])
        self.device_model.updateValue(index, DevMdl.CHANNEL,
                                      payload['Wifi'].get('Channel', "n/a"))
        self.device_model.updateValue(index, DevMdl.RSSI,
                                      payload['Wifi']['RSSI'])
        self.device_model.updateValue(index, DevMdl.UPTIME, payload['Uptime'])
        self.device_model.updateValue(index, DevMdl.LOADAVG,
                                      payload.get('LoadAvg'))
        self.device_model.updateValue(index, DevMdl.LINKCOUNT,
                                      payload['Wifi'].get('LinkCount', "n/a"))
        self.device_model.updateValue(index, DevMdl.DOWNTIME,
                                      payload['Wifi'].get('Downtime', "n/a"))

        self.parse_power(index, payload, True)

        tele_idx = self.telemetry_model.devices.get(
            self.device_model.topic(index))

        if tele_idx:
            tele_device = self.telemetry_model.getNode(tele_idx)
            self.telemetry_model.setDeviceFriendlyName(
                tele_idx, self.device_model.friendly_name(index))

            pr = tele_device.provides()
            for k in pr.keys():
                self.telemetry_model.setData(pr[k], payload.get(k))

    def parse_telemetry(self, index, payload):
        device = self.telemetry_model.devices.get(
            self.device_model.topic(index))
        if device:
            node = self.telemetry_model.getNode(device)
            time = node.provides()['Time']
            if 'Time' in payload:
                self.telemetry_model.setData(time, payload.pop('Time'))

            temp_unit = "C"
            pres_unit = "hPa"

            if 'TempUnit' in payload:
                temp_unit = payload.pop('TempUnit')

            if 'PressureUnit' in payload:
                pres_unit = payload.pop('PressureUnit')

            for sensor in sorted(payload.keys()):
                if sensor == 'DS18x20':
                    for sns_name in payload[sensor].keys():
                        d = node.devices().get(sensor)
                        if not d:
                            d = self.telemetry_model.addDevice(
                                DS18x20, payload[sensor][sns_name]['Type'],
                                device)
                        self.telemetry_model.getNode(d).setTempUnit(temp_unit)
                        payload[sensor][sns_name]['Id'] = payload[sensor][
                            sns_name].pop('Address')

                        pr = self.telemetry_model.getNode(d).provides()
                        for pk in pr.keys():
                            self.telemetry_model.setData(
                                pr[pk], payload[sensor][sns_name].get(pk))
                        self.tview.expand(d)

                elif sensor.startswith('DS18B20'):
                    d = node.devices().get(sensor)
                    if not d:
                        d = self.telemetry_model.addDevice(
                            DS18x20, sensor, device)
                    self.telemetry_model.getNode(d).setTempUnit(temp_unit)
                    pr = self.telemetry_model.getNode(d).provides()
                    for pk in pr.keys():
                        self.telemetry_model.setData(pr[pk],
                                                     payload[sensor].get(pk))
                    self.tview.expand(d)

                if sensor == 'COUNTER':
                    d = node.devices().get(sensor)
                    if not d:
                        d = self.telemetry_model.addDevice(
                            CounterSns, "Counter", device)
                    pr = self.telemetry_model.getNode(d).provides()
                    for pk in pr.keys():
                        self.telemetry_model.setData(pr[pk],
                                                     payload[sensor].get(pk))
                    self.tview.expand(d)

                else:
                    d = node.devices().get(sensor)
                    if not d:
                        d = self.telemetry_model.addDevice(
                            sensor_map.get(sensor, Node), sensor, device)
                    pr = self.telemetry_model.getNode(d).provides()
                    if 'Temperature' in pr:
                        self.telemetry_model.getNode(d).setTempUnit(temp_unit)
                    if 'Pressure' in pr or 'SeaPressure' in pr:
                        self.telemetry_model.getNode(d).setPresUnit(pres_unit)
                    for pk in pr.keys():
                        self.telemetry_model.setData(pr[pk],
                                                     payload[sensor].get(pk))
                    self.tview.expand(d)
        # self.tview.resizeColumnToContents(0)

    def console_log(self, topic, description, payload="", known=True):
        longest_tp = 0
        longest_fn = 0
        short_topic = "/".join(topic.split("/")[0:-1])
        fname = self.devices.get(short_topic, "")
        if not fname:
            device = self.device_model.findDevice(topic)
            fname = self.device_model.friendly_name(device.index)
            self.devices.update({short_topic: fname})
        self.console_model.addEntry(topic, fname, description, payload, known)

        if len(topic) > longest_tp:
            longest_tp = len(topic)
            self.console_view.resizeColumnToContents(1)

        if len(fname) > longest_fn:
            longest_fn = len(fname)
            self.console_view.resizeColumnToContents(1)

    def view_payload(self, idx):
        if self.cbFilter.isChecked():
            idx = self.sorted_console_model.mapToSource(idx)
        row = idx.row()
        timestamp = self.console_model.data(
            self.console_model.index(row, CnsMdl.TIMESTAMP))
        topic = self.console_model.data(
            self.console_model.index(row, CnsMdl.TOPIC))
        payload = self.console_model.data(
            self.console_model.index(row, CnsMdl.PAYLOAD))

        dlg = PayloadViewDialog(timestamp, topic, payload)
        dlg.exec_()

    def select_cons_entry(self, idx):
        self.cons_idx = idx

    def export(self):
        fname, _ = QFileDialog.getSaveFileName(self,
                                               "Export device list as...",
                                               directory=QDir.homePath(),
                                               filter="CSV files (*.csv)")
        if fname:
            if not fname.endswith(".csv"):
                fname += ".csv"

            with open(fname, "w", encoding='utf8') as f:
                column_titles = [
                    'mac', 'topic', 'friendly_name', 'full_topic',
                    'cmnd_topic', 'stat_topic', 'tele_topic', 'module',
                    'module_id', 'firmware', 'core'
                ]
                c = csv.writer(f)
                c.writerow(column_titles)

                for r in range(self.device_model.rowCount()):
                    d = self.device_model.index(r, 0)
                    c.writerow([
                        self.device_model.mac(d),
                        self.device_model.topic(d),
                        self.device_model.friendly_name(d),
                        self.device_model.fullTopic(d),
                        self.device_model.commandTopic(d),
                        self.device_model.statTopic(d),
                        self.device_model.teleTopic(d),
                        modules.get(self.device_model.module(d)),
                        self.device_model.module(d),
                        self.device_model.firmware(d),
                        self.device_model.core(d)
                    ])

    def bssid(self):
        BSSIdDialog().exec_()
        # if dlg.exec_() == QDialog.Accepted:

    def toggle_console_filter(self, state):
        self.cbxFilterDevice.setEnabled(state)
        if state:
            self.console_view.setModel(self.sorted_console_model)
        else:
            self.console_view.setModel(self.console_model)

    def select_console_filter(self, fname):
        self.sorted_console_model.setFilterFixedString(fname)

    def closeEvent(self, e):
        self.settings.setValue("window_geometry", self.saveGeometry())
        self.settings.setValue("splitter_state",
                               self.main_splitter.saveState())
        self.settings.sync()
        e.accept()
Ejemplo n.º 18
0
class IPLocationWidget(QWidget):

    axes = None
    p1 = None
    t1 = None
    label_items = []
    markers = []
    _transform = None
    _projection = None
    _ellipse = None
    _bisl_result = None
    _gt_marker = None  # this will hold the reference to the ground truth marker

    _detections = []
    _trimmed_detections = []
    _dist_matrix = None

    center = []  # will hold the coordinates of the current center of the map
    start_mouse_loc = [
    ]  # will hold the coordinates of the mouse, used in the mouse_move_event
    mouse_moved = True  # this is used for zooming in and out
    startx = 0
    starty = 0

    _mp_pool = None

    signal_start_dist_calc = pyqtSignal()
    signal_start_BISL_calc = pyqtSignal()
    signal_start_cluster_calc = pyqtSignal()
    signal_draw_map = pyqtSignal()

    def __init__(self, parent, pool):
        super().__init__()
        self._parent = parent

        self._mp_pool = pool

        self.buildUI()

    def buildUI(self):

        # BottomTab widgets go here...
        self.consoleBox = QTextEdit()
        self.consoleBox.setReadOnly(True)

        # set up the map widget
        self.mapWidget = IPMapWidget.IPMapWidget(self)

        # set up the distance matrix viewer
        self.dm_view = IPDistanceMatrixWidget()

        # set up dendrogram widget
        self.dendrogram = IPDendrogramWidget()

        # set up the bisl settings widget
        self.bislSettings = BISLSettings(self)

        # set up showgroundtruth widget
        self.showgroundtruth = ShowGroundTruth(self)

        # set up association settings widget
        self.assocSettings = AssociationSettings(self)

        # right hand widgets layout holds the settings widgets
        rh_widget = QWidget()
        rh_layout = QVBoxLayout()
        rh_layout.addWidget(self.bislSettings)
        rh_layout.addWidget(self.showgroundtruth)
        rh_layout.addWidget(self.assocSettings)
        rh_layout.addStretch()
        rh_widget.setLayout(rh_layout)

        # splitter holding the association plots
        self.assoc_splitter = QSplitter(Qt.Vertical)
        self.assoc_splitter.addWidget(self.dm_view)
        self.assoc_splitter.addWidget(self.dendrogram)
        self.assoc_splitter.setSizes([100000, 100000])

        # splitter holding the map canvas and the association plots
        self.loc_splitter = QSplitter(Qt.Horizontal)
        self.loc_splitter.addWidget(self.mapWidget)
        self.loc_splitter.addWidget(self.assoc_splitter)

        # large splitter holding the map, association plots, and the console
        self.mapSplitter = QSplitter(Qt.Vertical)
        self.mapSplitter.addWidget(self.loc_splitter)
        self.mapSplitter.addWidget(self.consoleBox)

        self.mainSplitter = QSplitter(Qt.Horizontal)
        self.mainSplitter.addWidget(self.mapSplitter)
        self.mainSplitter.addWidget(rh_widget)

        main_layout = QBoxLayout(QBoxLayout.TopToBottom)
        main_layout.addWidget(self.mainSplitter)
        self.setLayout(main_layout)

        self.connectSignalsAndSlots()

        # Create threads for the distancematrix calculation, BISL, and clustering
        self.dmThread = QThread()
        self.bislThread = QThread()
        self.clusterThread = QThread()

    def connectSignalsAndSlots(self):

        self.showgroundtruth.sig_groundtruth_changed.connect(
            self.mapWidget.plot_ground_truth)
        self.showgroundtruth.showGT_cb.toggled.connect(
            self.mapWidget.show_hide_ground_truth)

        self.bislSettings.run_bisl_button.clicked.connect(self.run_bisl)
        self.bislSettings.update_dm_button.clicked.connect(
            self.calc_distance_matrix)
        self.bislSettings.rng_max_edit.valueChanged.connect(
            self.mapWidget.update_range_max)

        self.bislSettings.confidence_edit.valueChanged.connect(
            self.bislSettings.enable_update_dm_button)
        self.bislSettings.confidence_edit.valueChanged.connect(
            self.calc_conf_ellipse)

        self.assocSettings.dist_max_edit.valueChanged.connect(
            self.dm_adjust_max_distance)
        self.assocSettings.threshold_edit.valueChanged.connect(
            self.cluster_adjust_threshold)
        self.assocSettings.update_assoc_button.clicked.connect(
            self.calc_associations)

        self.dm_view.signal_trim_detections.connect(self.trim_detections)
        self.dendrogram.signal_new_colors.connect(self.dm_view.set_colors)

    @pyqtSlot()
    def detections_cleared(self):
        self.mapWidget.clear_plot()
        self._detections = []
        self.mapWidget.clear_detections()
        self.dm_view.clear()
        self.dendrogram.clear_plot()
        self.consoleBox.clear()

    @pyqtSlot(list)
    def update_detections(self,
                          new_detections,
                          detection_type='ip_detections',
                          recalc_assoc=True):
        if new_detections is None:
            return  # Nothing to do

        if len(new_detections) < 1:
            self.detections_cleared()
            return

        self._detections = []
        if detection_type == "ip_detections":  # we need to covert to InfrasoundDetections
            for detection in new_detections:
                self._detections.append(detection.to_InfrasoundDetection())
        else:
            for detection in new_detections:
                self._detections.append(detection)

        self.mapWidget.update_detections(
            self._detections,
            self.bislSettings.rng_max_edit.value() * 1000.0)

        if recalc_assoc:
            self.calc_distance_matrix()

    @pyqtSlot(list, str)
    def trim_detections(self, indicies, linecolor='gray'):

        self._trimmed_detections = []
        if len(self._detections) < 1:
            return  # nothing to do

        # lets pick out the detections that we want to show
        for index in indicies:
            self._trimmed_detections.append(self._detections[index])
            self._trimmed_detections[-1].index = index

        self.mapWidget.update_detections(
            self._trimmed_detections,
            self.bislSettings.rng_max_edit.value() * 1000.,
            linecolor=linecolor)

    def run_bisl(self):

        if self._trimmed_detections is None:
            self.errorPopup(
                "no detections loaded. \n You need at least two detections to run BISL."
            )
            return  # nothing to do

        if len(self._trimmed_detections) < 2:
            self.errorPopup(
                "not enough detections loaded. \n You need two or more detections to run BISL."
            )
            return  # you need at least 2 detections to calculate the dist matrix

        self.bisl_workerObject = BISLWorkerObject(
            self._trimmed_detections,
            beam_width=self.bislSettings.bm_width_edit.value(),
            rad_min=self.bislSettings.rad_min_edit.value(),
            rad_max=self.bislSettings.rad_max_edit.value(),
            rng_max=self.bislSettings.rng_max_edit.value(),
            resol=self.bislSettings.resolution_edit.value())

        self.bisl_workerObject.moveToThread(self.bislThread)

        self.signal_start_BISL_calc.connect(self.bisl_workerObject.run)
        self.bisl_workerObject.signal_runFinished.connect(
            self.bisl_run_finished)

        # start the thread
        self.consoleBox.setText("...Calculating...")
        self.bislThread.start()
        self.signal_start_BISL_calc.emit()

    @pyqtSlot(dict)
    def bisl_run_finished(self, result):
        self._bisl_result = result

        self.consoleBox.setText(
            bisl.summarize(result, self.bislSettings.confidence_edit.value()))

        self.calc_conf_ellipse(self.bislSettings.confidence_edit.value())

    @pyqtSlot(int)
    def calc_conf_ellipse(self, confidence):

        if self._bisl_result is None:
            return  # nothing to plot

        conf_dx, conf_dy = bisl.calc_conf_ellipse([0.0, 0.0], [
            self._bisl_result['EW_stdev'], self._bisl_result['NS_stdev'],
            self._bisl_result['covar']
        ], confidence)
        # tell the mapWidget to plot the results
        self.mapWidget.plot_bisl_result(self._bisl_result['lon_mean'],
                                        self._bisl_result['lat_mean'])

        self.mapWidget.plot_conf_ellipse(self._bisl_result['lon_mean'],
                                         self._bisl_result['lat_mean'],
                                         conf_dx, conf_dy)

    @pyqtSlot()
    def calc_distance_matrix(self):

        if len(self._detections) < 1:
            self.errorPopup(
                "No detections loaded.\n You need two or more detections to calculate a distance matrix."
            )
            return  # nothing to do

        if len(self._detections) < 2:
            # self.errorPopup("not enough detections loaded. \n You need 2 or more detections to calculate a distance matrix.")
            return  # you need at least 2 detections to calculate the dist matrix

        self.dist_matrix = None

        self.dm_workerObject = DistanceMatrixWorkerObject(
            self._detections,
            beam_width=self.bislSettings.bm_width_edit.value(),
            rng_max=self.bislSettings.rng_max_edit.value(),
            rad_min=self.bislSettings.rad_min_edit.value(),
            rad_max=self.bislSettings.rad_max_edit.value(),
            resol=self.bislSettings.resolution_edit.value(),
            pool=self._mp_pool)

        self.dm_workerObject.moveToThread(self.dmThread)

        self.signal_start_dist_calc.connect(self.dm_workerObject.run)
        self.dm_workerObject.signal_runFinished.connect(self.dm_run_finished)

        # start the thread
        self.dmThread.start()
        self.signal_start_dist_calc.emit()
        self.dm_view.showCalculatingText()

        self.bislSettings.update_dm_button.setEnabled(False)

    @pyqtSlot(np.ndarray)
    def dm_run_finished(self, data):
        self.dist_matrix_orig = data  # keep this around incase someone twiddles with the max_distance setting
        self.dm_adjust_max_distance()

        self.dm_view.hideCalculatingText()

        if self.dist_matrix is not None:
            self.dm_view.set_data(self.dist_matrix)

        # Now that the distance matrix is set, calculate the association dendrogram
        self.calc_associations()

    def dm_adjust_max_distance(self):
        self.dist_matrix = self.dist_matrix_orig.copy()
        self.dist_matrix[
            self.dist_matrix_orig > self.assocSettings.dist_max_edit.value(
            )] = self.assocSettings.dist_max_edit.value()
        self.assocSettings.update_assoc_button.setEnabled(True)

    def cluster_adjust_threshold(self):
        self.assocSettings.update_assoc_button.setEnabled(True)

    @pyqtSlot()
    def calc_associations(self):

        if self.dist_matrix is None:
            self.errorPopup("No distance matrix...I need a distance matrix")
            return  # Nothing to do

        self.cluster_workerObject = ClusterWorkerObject(
            self.dist_matrix,
            threshold=self.assocSettings.threshold_edit.value())

        self.cluster_workerObject.moveToThread(self.clusterThread)

        self.signal_start_cluster_calc.connect(self.cluster_workerObject.run)
        self.cluster_workerObject.signal_runFinished.connect(
            self.cluster_run_finished)

        # start the thread
        self.clusterThread.start()
        self.signal_start_cluster_calc.emit()

        self.assocSettings.update_assoc_button.setEnabled(False)

    @pyqtSlot(np.ndarray, np.ndarray)
    def cluster_run_finished(self, links, labels):

        self.dendrogram.set_data(links,
                                 self.assocSettings.threshold_edit.value())

        # Sort the distance matrix using the labels
        det_cnt = len(self.dist_matrix)
        sorting = np.array([])
        for n in range(max(labels + 1)):
            sorting = np.concatenate(
                (sorting, np.arange(det_cnt)[labels == n]))
        sorting = sorting.astype(int)

        distance_matrix_sorted = np.empty_like(self.dist_matrix)
        for n1 in range(det_cnt):
            for n2 in range(det_cnt):
                distance_matrix_sorted[n1][n2] = self.dist_matrix[sorting[n1],
                                                                  sorting[n2]]

        self.dm_view.set_data(distance_matrix_sorted, labels)
        self.update_detections(self._detections,
                               detection_type='detections',
                               recalc_assoc=False)

    def errorPopup(self, message):
        msgBox = QMessageBox()
        msgBox.setIcon(QMessageBox.Information)
        msgBox.setText(message)
        msgBox.setWindowTitle("Oops...")
        msgBox.exec_()

    def saveWindowGeometrySettings(self):
        self._parent.settings.beginGroup('LocationWidget')
        self._parent.settings.setValue("windowSize", self.size())
        self._parent.settings.setValue("windowPos", self.pos())
        self._parent.settings.setValue("mapSplitterSettings",
                                       self.mapSplitter.saveState())
        self._parent.settings.setValue("mainSplitterSettings",
                                       self.mainSplitter.saveState())
        self._parent.settings.setValue("assocSplitterSettings",
                                       self.assoc_splitter.saveState())
        self._parent.settings.setValue("loc_splitterSettings",
                                       self.loc_splitter.saveState())
        self._parent.settings.endGroup()

    def restoreWindowGeometrySettings(self):
        # Restore settings
        self._parent.settings.beginGroup('LocationWidget')

        mapSplitterSettings = self._parent.settings.value(
            "mapSplitterSettings")
        if mapSplitterSettings:
            self.mapSplitter.restoreState(mapSplitterSettings)

        mainSplitterSettings = self._parent.settings.value(
            "mainSplitterSettings")
        if mainSplitterSettings:
            self.mainSplitter.restoreState(mainSplitterSettings)

        assocSplitterSettings = self._parent.settings.value(
            "assocSplitterSettings")
        if assocSplitterSettings:
            self.assoc_splitter.restoreState(assocSplitterSettings)

        locSplitterSettings = self._parent.settings.value(
            "loc_splitterSettings")
        if locSplitterSettings:
            self.loc_splitter.restoreState(locSplitterSettings)

        self._parent.settings.endGroup()
Ejemplo n.º 19
0
class SubTabWidget(QWidget):
    _tabChanged = pyqtSignal(int, name = "tabChanged")

    def __init__(self, subtitleData, videoWidget, parent = None):
        super(SubTabWidget, self).__init__(parent)
        self._subtitleData = subtitleData
        self.__initTabWidget(videoWidget)

    def __initTabWidget(self, videoWidget):
        settings = SubSettings()

        mainLayout = QVBoxLayout(self)
        mainLayout.setContentsMargins(0, 0, 0, 0)
        mainLayout.setSpacing(0)

        #TabBar
        self.tabBar = QTabBar(self)

        # Splitter (bookmarks + pages)
        self.splitter = QSplitter(self)
        self.splitter.setObjectName("sidebar_splitter")

        self._toolbox = ToolBox(self._subtitleData, self)
        self._toolbox.setObjectName("sidebar")
        self._toolbox.setMinimumWidth(100)

        self._toolbox.addTool(Details(self._subtitleData, self))
        self._toolbox.addTool(Synchronizer(videoWidget, self._subtitleData, self))
        self._toolbox.addTool(History(self))

        self.rightWidget = QWidget()
        rightLayout = QGridLayout()
        rightLayout.setContentsMargins(0, 0, 0, 0)
        self.rightWidget.setLayout(rightLayout)

        self._mainTab = FileList(_("Subtitles"), self._subtitleData, self)

        self.pages = QStackedWidget(self)
        rightLayout.addWidget(self.pages, 0, 0)

        self.tabBar.addTab(self._mainTab.name)
        self.pages.addWidget(self._mainTab)

        self.splitter.addWidget(self._toolbox)
        self.splitter.addWidget(self.rightWidget)
        self.__drawSplitterHandle(1)

        # Setting widgets
        mainLayout.addWidget(self.tabBar)
        mainLayout.addWidget(self.splitter)

        # Widgets settings
        self.tabBar.setMovable(True)
        self.tabBar.setTabsClosable(True)
        self.tabBar.setExpanding(False)

        # Don't resize left panel if it's not needed
        leftWidgetIndex = self.splitter.indexOf(self._toolbox)
        rightWidgetIndex = self.splitter.indexOf(self.rightWidget)

        self.splitter.setStretchFactor(leftWidgetIndex, 0)
        self.splitter.setStretchFactor(rightWidgetIndex, 1)
        self.splitter.setCollapsible(leftWidgetIndex, False)
        self.splitter.setSizes([250])

        # Some signals
        self.tabBar.currentChanged.connect(self.showTab)
        self.tabBar.tabCloseRequested.connect(self.closeTab)
        self.tabBar.tabMoved.connect(self.moveTab)
        self._mainTab.requestOpen.connect(self.openTab)
        self._mainTab.requestRemove.connect(self.removeFile)

        self.tabChanged.connect(lambda i: self._toolbox.setContentFor(self.tab(i)))

        self.setLayout(mainLayout)

    def __addTab(self, filePath):
        """Returns existing tab index. Creates a new one if it isn't opened and returns its index
        otherwise."""
        for i in range(self.tabBar.count()):
            widget = self.pages.widget(i)
            if not widget.isStatic and filePath == widget.filePath:
                return i
        tab = SubtitleEditor(filePath, self._subtitleData, self)
        newIndex = self.tabBar.addTab(self._createTabName(tab.name, tab.history.isClean()))
        tab.history.cleanChanged.connect(
            lambda clean: self._cleanStateForFileChanged(filePath, clean))
        self.pages.addWidget(tab)
        return newIndex

    def __drawSplitterHandle(self, index):
        splitterHandle = self.splitter.handle(index)

        splitterLayout = QVBoxLayout(splitterHandle)
        splitterLayout.setSpacing(0)
        splitterLayout.setContentsMargins(0, 0, 0, 0)

        line = QFrame(splitterHandle)
        line.setFrameShape(QFrame.HLine)
        line.setFrameShadow(QFrame.Sunken)
        splitterLayout.addWidget(line)
        splitterHandle.setLayout(splitterLayout)

    def _createTabName(self, name, cleanState):
        if cleanState is True:
            return name
        else:
            return "%s +" % name

    def _cleanStateForFileChanged(self, filePath, cleanState):
        page = self.tabByPath(filePath)
        if page is not None:
            for i in range(self.tabBar.count()):
                if self.tabBar.tabText(i)[:len(page.name)] == page.name:
                    self.tabBar.setTabText(i, self._createTabName(page.name, cleanState))
                    return

    def saveWidgetState(self, settings):
        settings.setState(self.splitter, self.splitter.saveState())
        settings.setHidden(self._toolbox, self._toolbox.isHidden())

    def restoreWidgetState(self, settings):
        self.showPanel(not settings.getHidden(self._toolbox))

        splitterState = settings.getState(self.splitter)
        if not splitterState.isEmpty():
            self.splitter.restoreState(settings.getState(self.splitter))

    @pyqtSlot(str, bool)
    def openTab(self, filePath, background=False):
        if self._subtitleData.fileExists(filePath):
            tabIndex = self.__addTab(filePath)
            if background is False:
                self.showTab(tabIndex)
        else:
            log.error(_("SubtitleEditor not created for %s!" % filePath))

    @pyqtSlot(str)
    def removeFile(self, filePath):
        tab = self.tabByPath(filePath)
        command = RemoveFile(filePath)
        if tab is not None:
            index = self.pages.indexOf(tab)
            if self.closeTab(index):
                self._subtitleData.execute(command)
        else:
            self._subtitleData.execute(command)


    @pyqtSlot(int)
    def closeTab(self, index):
        tab = self.tab(index)
        if tab.canClose():
            widgetToRemove = self.pages.widget(index)
            self.tabBar.removeTab(index)
            self.pages.removeWidget(widgetToRemove)
            widgetToRemove.deleteLater()
            return True
        return False


    def count(self):
        return self.tabBar.count()

    def currentIndex(self):
        return self.tabBar.currentIndex()

    def currentPage(self):
        return self.pages.currentWidget()

    @pyqtSlot(int, int)
    def moveTab(self, fromIndex, toIndex):
        fromWidget = self.pages.widget(fromIndex)
        toWidget = self.pages.widget(toIndex)
        if fromWidget.isStatic or toWidget.isStatic:
            self.tabBar.blockSignals(True) # signals would cause infinite recursion
            self.tabBar.moveTab(toIndex, fromIndex)
            self.tabBar.blockSignals(False)
            return
        else:
            self.pages.removeWidget(fromWidget)
            self.pages.removeWidget(toWidget)

            if fromIndex < toIndex:
                self.pages.insertWidget(fromIndex, toWidget)
                self.pages.insertWidget(toIndex, fromWidget)
            else:
                self.pages.insertWidget(toIndex, fromWidget)
                self.pages.insertWidget(fromIndex, toWidget)

            # Hack
            # Qt changes tabs during mouse drag and dropping. The next line is added
            # to prevent it.
            self.showTab(self.tabBar.currentIndex())

    @pyqtSlot(int)
    def showTab(self, index):
        showWidget = self.pages.widget(index)
        if showWidget:
            self.pages.setCurrentWidget(showWidget)
            self.tabBar.blockSignals(True)
            self.tabBar.setCurrentIndex(index)
            self.tabBar.blockSignals(False)

            # Try to update current tab.
            showWidget.updateTab()

            self._tabChanged.emit(index)

    def showPanel(self, val):
        if val is True:
            self._toolbox.show()
        else:
            self._toolbox.hide()

    def togglePanel(self):
        if self._toolbox.isHidden():
            self._toolbox.show()
        else:
            self._toolbox.hide()

    def tab(self, index):
        return self.pages.widget(index)

    def tabByPath(self, path):
        for i in range(self.pages.count()):
            page = self.tab(i)
            if not page.isStatic and page.filePath == path:
                return page
        return None

    @property
    def fileList(self):
        return self._mainTab
Ejemplo n.º 20
0
class MainForm(QDialog):
   def __init__(self, parent = None):
      QDialog.__init__(self, parent)
      
      # If a Nemu instance is already running, this is as far as we go
      self.connectToRunning()
      
      self.holdOpen = False
      self.menuItems = []
      self.allItems = []
      self.favorites = []
      self.currentItem = None
      self.menuFile = os.path.expanduser('~/.nemu/menu')
      self.favoritesFile = os.path.expanduser('~/.nemu/favorites')
      # NOTE: If you change this, also update migrate-settings
      self.settingsFile = os.path.expanduser('~/.nemu/settings')
      self.initSettings()

      self.server = QLocalServer()
      self.server.newConnection.connect(self.handleConnection)
      QLocalServer.removeServer('nemuSocket')
      self.server.listen('nemuSocket')
      
      self.configDir = os.path.expanduser('~/.nemu')
      if not os.path.isdir(self.configDir):
         os.mkdir(self.configDir)
      self.menuItems = self.loadConfig(self.menuFile, self.menuItems)
      self.favorites = self.loadConfig(self.favoritesFile, self.favorites)
      # Don't load directly into self.settings so we can add new default values as needed
      try:
         tempSettings = self.loadConfig(self.settingsFile, self.settings)
         for key, value in tempSettings.items():
            self.settings[key] = value
      except SystemError:
         print('ERROR: Failed to load settings. You may need to run migrate-settings.')
         raise
      # This should never happen, but unfortunately bugs do, so clean up orphaned items.
      # We need to do this because these items won't show up in the UI, but may interfere with
      # merges if they duplicate something that is being merged in.
      self.menuItems[:] = [i for i in self.menuItems if i.parent == None or i.parent in self.menuItems]
      # Look for broken icon paths
      needSave = False
      for i in self.menuItems + self.favorites:
          if not os.path.exists(i.icon):
              i.findIcon()
              needSave = True
      if needSave:
         self.saveMenu()


      for i in self.menuItems:
         if not hasattr(i, 'imported'):
            i.imported = False
      
      self.setupUI()
      
      self.setContextMenuPolicy(Qt.ActionsContextMenu)
      self.createMenu(self)
      
      self.refresh(False)
      
      if len(self.menuItems) == 0:
         self.firstRun()
      
      self.show()
      
      self.keepaliveTimer = QTimer(self)
      self.keepaliveTimer.timeout.connect(self.keepalive)
      self.keepaliveTimer.start(60000)
      
      
   def initSettings(self):
      self.settings = dict()
      self.settings['width'] = 400
      self.settings['height'] = 400
      self.settings['quit'] = False
      self.settings['imported'] = []
      self.settings['iconTheme'] = None
      
      
   def loadConfig(self, filename, default):
      if os.path.exists(filename):
         with open(filename, 'rb') as f:
            data = f.read().replace('PyQt4', 'PyQt5')
            return cPickle.loads(data)
      else:
         return default
      
      
   def setupUI(self):
      self.resize(self.settings['width'], self.settings['height'])
      self.setWindowFlags(Qt.FramelessWindowHint | Qt.CustomizeWindowHint | Qt.WindowStaysOnTopHint)
      #self.setWindowFlags(Qt.X11BypassWindowManagerHint)
      self.setWindowTitle('Nemu')
      self.setMouseTracking(True)
      
      iconPath = os.path.join(os.path.dirname(__file__), 'images')
      iconPath = os.path.join(iconPath, 'nemu.png')
      self.setWindowIcon(IconCache()[iconPath])
      
      self.place()
      
      self.buttonListLayout = QVBoxLayout(self)
      self.setMargins(self.buttonListLayout)
      
      self.buttonLayout = QHBoxLayout()
      self.setMargins(self.buttonLayout)
      
      # Settings and Filter box
      self.filterLayout = QHBoxLayout()
      self.settingsButton = QPushButton()
      self.settingsButton.setIcon(QIcon(iconPath))
      self.settingsButton.setMinimumHeight(35)
      self.settingsButton.clicked.connect(self.settingsClicked)
      self.filterLayout.addWidget(self.settingsButton, 0)
      
      self.filterLabel = QLabel("Filter")
      self.filterLayout.addWidget(self.filterLabel)
      
      self.filterBox = QLineEdit()
      self.filterBox.textChanged.connect(self.refresh)
      self.filterLayout.addWidget(self.filterBox)
      
      self.sizeGrip = QSizeGrip(self)
      self.sizeGrip.setMinimumSize(QSize(25, 25))
      self.filterLayout.addWidget(self.sizeGrip, 0, Qt.AlignRight | Qt.AlignTop)
      
      self.buttonListLayout.addLayout(self.filterLayout)
      
      # Top buttons and labels
      self.backButton = QPushButton('Favorites')
      self.backButton.setMinimumHeight(35)
      self.backButton.clicked.connect(self.backClicked)
      self.buttonLayout.addWidget(self.backButton, 1)
      
      self.currentLabel = QLabel()
      self.currentLabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
      self.buttonLayout.addWidget(self.currentLabel, 1)
      
      self.buttonListLayout.addLayout(self.buttonLayout, 0)
      
      # Menu item display
      self.listSplitter = QSplitter()
      self.buttonListLayout.addWidget(self.listSplitter, 1)
      
      self.leftList = ListWidget(self.clearListMouseOver)
      self.listSplitter.addWidget(self.leftList)
      
      self.rightList = ListWidget(self.clearListMouseOver)
      self.listSplitter.addWidget(self.rightList)
      
      # Has to be done after adding widgets to the splitter or the size will get reset again
      if 'splitterState' in self.settings:
         self.listSplitter.restoreState(self.settings['splitterState'])
      
   def setMargins(self, layout, margin = 0):
      layout.setSpacing(margin)
      layout.setContentsMargins(margin, margin, margin, margin)
      
      
   def createMenu(self, widget):
      addFavoriteAction = QAction('Add to Favorites', self)
      addFavoriteAction.triggered.connect(self.addFavoriteClicked)
      widget.insertAction(None, addFavoriteAction)
      addAction = QAction("New...", self)
      addAction.triggered.connect(self.newClicked)
      widget.insertAction(None, addAction)
      editAction = QAction("Edit...", self)
      editAction.triggered.connect(self.editClicked)
      widget.insertAction(None, editAction)
      deleteAction = QAction("Delete", self)
      deleteAction.triggered.connect(self.deleteClicked)
      widget.insertAction(None, deleteAction)
      
      
   def hideOrClose(self):
      if self.settings['quit']:
         self.close()
      else:
         self.hide()
         
   def closeEvent(self, event):
      self.saveSettings()
      
   def hideEvent(self, event):
      self.releaseMouse()
      self.saveSettings()
      
   def mouseMoveEvent(self, event):
      if self.hasMouse():
         self.releaseMouse()
      
   def leaveEvent(self, event):
      # If we set holdOpen, it means that we've opened a dialog, so we shouldn't grab
      if not self.hasMouse():
         self.grabMouse()
      
   def mousePressEvent(self, event):
      if not self.hasMouse():
         self.hideOrClose()
         
   def hasMouse(self):
      return self.geometry().contains(QCursor.pos())
         

   def saveSettings(self):
      self.settings['splitterState'] = self.listSplitter.saveState()
      self.settings['width'] = self.width()
      self.settings['height'] = self.height()
      with open(self.settingsFile, 'wb') as f:
         cPickle.dump(self.settings, f)
         
   def place(self):
      desktop = qApp.desktop()
      screenSize = desktop.availableGeometry(QCursor.pos())
      self.move(screenSize.x(), screenSize.y() + screenSize.height() - self.height())
         
         
   def newClicked(self):
      form = AddForm()
      
      self.holdOpen = True
      form.exec_()
      self.checkMouse()
      self.holdOpen = False
      
      if form.accepted:
         item = MenuItem()
         item.name = form.name
         item.command = form.command
         item.working = form.working
         item.folder = form.folder
         item.icon = form.icon
         item.findIcon()
         
         clicked = self.getClicked()
         if clicked:
            parent = clicked.item.parent
         elif self.leftList.mouseOver:
            if self.currentItem != None:
               parent = self.currentItem.parent
            else:
               parent = None
         else:
            parent = self.currentItem
         item.parent = parent
         
         self.menuItems.append(item)
         self.refresh()
      
   def editClicked(self):
      form = AddForm()
      clicked = self.getClicked()
      if clicked == None:
         return
      item = clicked.item
      
      form.name = item.name
      form.command = item.command
      form.working = item.working
      form.folder = item.folder
      form.icon = item.icon
      form.populateFields()
      
      self.holdOpen = True
      form.exec_()
      self.checkMouse()
      self.holdOpen = False
      
      if form.accepted:
         item.name = form.name
         item.command = form.command
         item.working = form.working
         item.folder = form.folder
         item.icon = form.icon
         item.imported = False
         item.findIcon()
         self.refresh()
         
         
   def checkMouse(self):
      if not self.hasMouse():
         self.grabMouse()
      
      
   def deleteClicked(self):
      clicked = self.getClicked()
      if clicked == None:
         return
      self.delete(clicked.item)
      self.refresh()
      
   # Delete item and all of its children so we don't leave around orphaned items
   def delete(self, item):
      for i in self.menuItems:
         if i.parent == item:
            i.deleted = True
      
      if item in self.menuItems:
         item.deleted = True
         item.imported = False
      if item in self.favorites:
         self.favorites.remove(item)
      
      
   def addFavoriteClicked(self):
      newFavorite = copy.copy(self.getClicked().item)
      newFavorite.parent = None
      self.favorites.append(newFavorite)
      self.refresh()
      
      
   def getClicked(self):
      for i in self.allItems:
         if i.mouseOver:
            return i
            
   def clearMouseOver(self):
      for i in self.allItems:
         i.mouseOver = False
         
   def clearListMouseOver(self):
      self.leftList.mouseOver = False
      self.rightList.mouseOver = False
      
      
   def refresh(self, save = True):
      self.leftList.clear()
      self.rightList.clear()
      self.allItems = []
      sortedLeft = []
      sortedRight = []
      self.updateFilter()
      
      if self.currentItem != None:
         currParent = self.currentItem.parent
         for i in self.menuItems:
            if i.parent == currParent and not i.deleted and i.matchedFilter:
               sortedLeft.append(i)
      else:
         for i in self.favorites:
            sortedLeft.append(i)
      
      for i in self.menuItems:
         if i.parent == self.currentItem and not i.deleted and i.matchedFilter:
            sortedRight.append(i)
            
      sortedLeft.sort(key = lambda x: x.name)
      sortedLeft.sort(key = lambda x: not x.folder)
      sortedRight.sort(key = lambda x: x.name)
      sortedRight.sort(key = lambda x: not x.folder)
      for i in sortedLeft:
         self.leftList.add(self.createItem(i))
      for i in sortedRight:
         self.rightList.add(self.createItem(i))
         
      if save:
         self.saveMenu()

   def saveMenu(self):
      # Save the current menu status
      with open(self.menuFile, 'wb') as f:
         cPickle.dump(self.menuItems, f)
      with open(self.favoritesFile, 'wb') as f:
         cPickle.dump(self.favorites, f)

   def createItem(self, item):
      newItem = ListItem(item, self.clearMouseOver)
      newItem.clicked.connect(self.itemClicked)
      self.allItems.append(newItem)
      return newItem
      
   def updateFilter(self):
      filterValue = str(self.filterBox.text())
      
      for i in self.menuItems:
         i.checkFilter(filterValue)
            
      
   def itemClicked(self):
      sender = self.sender()
      if sender.item.folder:
         self.setCurrentItem(sender.item)
         self.refresh(False)
      else:
         flags = ['f', 'F', 'u', 'U', 'd', 'D', 'n', 'N', 'i', 'k', 'v', 'm']
         command = sender.item.command
         for i in flags:
            command = command.replace('%' + i, '')
         # %c needs a proper value in some cases
         command = command.replace('%c', '"%s"' % sender.item.name)
         working = sender.item.working
         if not os.path.isdir(working):
            working = None
            
         # Need to redirect stdout and stderr so if the process writes something it won't fail
         with open(os.path.devnull, 'w') as devnull:
            Popen(command + '&', stdout=devnull, stderr=devnull, shell=True, cwd=working)
         self.hideOrClose()
         
         
   def backClicked(self):
      if self.currentItem:
         self.setCurrentItem(self.currentItem.parent)
         self.refresh(False)
         
         
   def setCurrentItem(self, item):
      self.currentItem = item
      if item != None:
         self.currentLabel.setText(item.name)
         if item.parent != None:
            self.backButton.setText(item.parent.name)
         else:
            self.backButton.setText('Favorites')
      else:
         self.currentLabel.setText('')
         self.backButton.setText('Favorites')
         
         
   def settingsClicked(self):
      form = SettingsForm(self)
      form.quitCheck.setChecked(self.settings['quit'])
      theme = self.settings.get('iconTheme')
      if theme:
         form.themeCombo.setCurrentIndex(form.themeCombo.findText(theme))
      
      self.holdOpen = True
      form.exec_()
      self.checkMouse()
      self.holdOpen = False
      
      if form.accepted:
         self.settings['quit'] = form.quitCheck.isChecked()
      
      
   def firstRun(self):
      QMessageBox.information(self, 'First Time?', 'Your menu is currently empty.  It is recommended that you import an existing menu file.')
      self.settingsClicked()
      
      
   def connectToRunning(self):
      self.socket = QLocalSocket()
      self.socket.connectToServer('nemuSocket')
      self.socket.waitForConnected(1000)
      
      if self.socket.state() == QLocalSocket.ConnectedState:
         print 'Server found'
         if self.socket.waitForReadyRead(3000):
            line = self.socket.readLine()
            print line
         else:
            print self.socket.errorString()
         sys.exit()
      else:
         print 'No server running'
      
      
   def handleConnection(self):
      import datetime
      print "Got connection", datetime.datetime.now()
      
      connection = self.server.nextPendingConnection()
      connection.write('connected')
      del connection
      
      self.setCurrentItem(None)
      self.filterBox.setText('')
      self.refresh(False)
      self.show()
      print "Showed", datetime.datetime.now()
      return
      
      
   # Call periodically to keep data resident in memory (hopefully)
   def keepalive(self):
      if self.isHidden():
         self.refresh(False)
Ejemplo n.º 21
0
class MainWindow(QMainWindow):

    def __init__(self, settings):
        super().__init__()
        # Save the settings object for now and shutdown time.
        self.settings = settings
        # Initialize extras and dicts paths first, as other modules use them
        paths.initialize(settings)
        # Initialize our font db
        fonts.initialize(settings)
        # Set our font, which will propogate to our child widgets.
        fonts.notify_me(self._font_change) # ask for a signal
        self._font_change(False) # fake a signal now
        # Initialize the dictionary apparatus
        dictionaries.initialize(settings)
        # Initialize the color choices
        colors.initialize(settings)
        # Initialize the sequence number for opened files
        self.book_number = 0
        # Initialize the path to the last-opened file, used to
        # start file-open dialogs.
        self.last_open_path = '.'
        # Initialize our dict of active panels
        self.panel_dict = PANEL_DICT.copy()
        # Initialize our dict of open documents {seqno:Book}
        self.open_books = {}
        self.focus_book = None # seqno of book in focus, see _focus_me
        # Initialize the list of recent files
        self.recent_files = []

        # Create the main window and set up the menus.
        self._uic()

        # Initialize the set of files actually open when we shut down.
        last_session = self._read_flist('mainwindow/open_files')
        if len(last_session) : # there were some files open
            if len(last_session) == 1 :
                msg = _TR('Start-up dialog', 'One book was open at the end of the last session.')
            else:
                msg = _TR('Start-up dialog', '%n books were open at the end of the last session.',
                          n=len(last_session) )
            info = _TR("Start-up dialog", "Click OK to re-open all")
            if utilities.ok_cancel_msg( msg, info) :
                for file_path in last_session :
                    ftbs = utilities.path_to_stream(file_path)
                    if ftbs :
                        self._open(ftbs)
        if 0 == len(self.open_books) :
            # We did not re-open any books, either because there were
            # none, or the user said No, or perhaps they were not found.
            self._new() # open one, new, book.

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Slot to receive the currentChanged signal from the editview tabset.
    # Look through self.open_books and find the one whose edit widget is
    # now current, and do a focus_me for it.
    def _editview_change(self, index):
        if index > -1 :
            eview = self.editview_tabset.widget(index)
            for (seqno, book) in self.open_books.items() :
                if eview == book.get_edit_view() :
                    self.focus_me(seqno)
                    return
            mainwindow_logger.error('cannot relate editview tab index to book')
    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Make a selected book the focus of all panels. This is called explicitly
    # when a book is first created, and when the editview tabset changes the
    # current selection. It is called also when an editview gets a focus-in
    # event.
    # Display that Book's various "-view" objects in panels, in the order
    # that the user left them and with the same active panel as before. Note
    # that a book (editview) can get a focus-in event when it was already the
    # focus in this sense, for example if this app was hidden and then
    # brought to the front. So be prepared for redundant calls.

    def focus_me(self, book_index):
        outgoing = self.focus_book
        if book_index == outgoing : return # redundant call
        mainwindow_logger.debug(
            'focusing {0} = {1}'.format(book_index,self.open_books[book_index].get_book_name())
        )
        self.focus_book = book_index
        # Record the user's arrangement of panels for the outgoing book,
        # as a list of tuples ('tabname', widget) in correct sequence.
        if outgoing is not None : # false first time and after File>Close
            out_panel_dict = self.open_books[outgoing].panel_dict
            widg_list = []
            for ix in range( self.panel_tabset.count() ):
                widg_list.append (
                    (self.panel_tabset.tabText(ix), self.panel_tabset.widget(ix))
                    )
            out_panel_dict['tab_list'] = widg_list
            out_panel_dict['current'] = self.panel_tabset.currentIndex()
        # Change all the panels to the widgets, in the sequence, of the new book
        in_panel_dict = self.open_books[book_index].panel_dict
        widg_list = in_panel_dict['tab_list']
        self.panel_tabset.clear()
        for ix in range( len(widg_list) ):
            (tab_text, widget) = widg_list[ix]
            self.panel_tabset.insertTab(ix, widget, tab_text)
        self.panel_tabset.setCurrentIndex(in_panel_dict['current'])
        self.editview_tabset.setCurrentIndex(
            self.editview_tabset.indexOf(
                self.open_books[book_index].get_edit_view() ) )

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Called by the current book to make a particular tab the visible one,
    # e.g. to make the Find visible on a ^F. The argument is a widget
    # that should occupy one of the current tabs. Ask the tabset for its
    # index, and if it is found, make that the current index. (If it is
    # not found, log it and do nothing.)

    def make_tab_visible(self, tabwidg):
        ix = self.panel_tabset.indexOf(tabwidg)
        if ix >= 0 : # widget exists in this tabset
            self.panel_tabset.setCurrentIndex(ix)
            return
        mainwindow_logger.error('Request to show nonexistent widget')

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Implement File>New:
    #    Create a Book object
    #    Call its new_empty() method,
    #    Add it to the open_books dict keyed by its sequence number,
    #    Display its text editor in a tab with the document name, and
    #    Give it the focus.
    def _new(self):
        seq = self.book_number
        self.book_number += 1
        new_book = book.Book( seq, self )
        new_book.new_empty()
        self.open_books[seq] = new_book
        index = self.editview_tabset.addTab(
            new_book.get_edit_view(), new_book.get_book_name() )
        self.editview_tabset.setTabToolTip(index,
                _TR('Tooltip of edit of new unsaved file',
                    'this file has not been saved') )
        self.focus_me(seq)


    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Quick check to see if a file path is already open. Called from _open
    # and from _build_recent (menu). Returned value is the sequence number
    # of the open book, or None.
    def _is_already_open(self, path):
        for (seq, book_object) in self.open_books.items():
            if path == book_object.get_book_full_path() :
                return seq
        return None

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Implement File>Open. Dialog with the user (file dialog starts with
    # last-used book path). Result is None or a FileBasedTextStream that we
    # pass to _open().
    def _file_open(self) :
        fbts = utilities.ask_existing_file(
            _TR( 'File:Open dialog','Select a book file to open'),
            parent=self, starting_path=self.last_open_path)
        if fbts : # yes a readable file was chosen.
            self._open( fbts )

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Open a file, given the document as a FileBasedTextStream
    # * If file opened is fname.meta, look for a file named fname; if it
    #   exists open it instead, e.g. given foo.txt.meta, open foo.txt.
    #   If it doesn't exist, tell the user and exit.
    # * If a file of the same name and path is already open, just focus
    #   it and exit.
    # * Determine if there is a .meta file, a .bin file, or neither
    # * Create a metadata input stream if possible
    # * If no .meta, look for good_words and bad_words
    # * If the only open book is an "Untitled-n" and
    #     it is unmodified, delete it.
    # * Call Book.old_book() or .new_book() as appropriate
    # * Add this book's editview to the edit tabset
    # * Give this book the focus.

    def _open(self, fbts):
        # look for opening a .meta file
        if 'meta' == fbts.suffix():
            fb2 = utilities.file_less_suffix(fbts)
            if fb2 is None :
                m1 = _TR('File:Open','Cannot open a .meta file alone')
                m2 = _TR('File:Open','There is no book file matching ',
                         'filename follows this') + fbts.filename()
                utilities.warning_msg(m1, m2)
                return
            # we see foo.txt with foo.txt.meta, silently open it
            fbts = fb2
        # look for already-open file
        seq = self._is_already_open(fbts.fullpath())
        if seq is not None :
            self.focus_me(seq)
            return
        # start collecting auxiliary streams
        gw_stream = None
        bw_stream = None
        gg_stream = None
        # open the metadata stream, which is always UTF-8
        meta_stream = utilities.related_suffix(fbts, 'meta', encoding=C.ENCODING_UTF)
        if meta_stream is None :
            # opening book without .meta; look for .bin which is always LTN1
            bin_stream = utilities.related_suffix(fbts,'bin',encoding=C.ENCODING_LATIN)
            if bin_stream :
                gg_stream = metadata.translate_bin(bin_stream,fbts)
            # Look for good_words.txt, bad_words.txt.
            gw_stream = utilities.related_file( fbts, 'good_words*.*' )
            bw_stream = utilities.related_file( fbts, 'bad_words*.*' )
        seq = self.book_number
        # If the only open book is the new one created at startup or when all
        # books are closed (which will have key 0), and it has not been
        # modified, get rid of it.
        if len(self.open_books) == 1 \
        and 0 == list(self.open_books.keys())[0] \
        and self.open_books[0].get_book_name().startswith('Untitled-') \
        and not self.open_books[0].get_save_needed() :
            self.editview_tabset.clear()
            self.panel_tabset.clear()
            self.focus_book = None
            seq = 0
        else:
            # Some other book open, or user typed into the default New one.
            self.book_number += 1
        # Make the Book object and stow it in our open book dict
        a_book = book.Book( seq, self )
        self.open_books[seq] = a_book
        if meta_stream : # opening a book we previously saved
            a_book.old_book( fbts, meta_stream )
        else :
            a_book.new_book( fbts, gg_stream, gw_stream, bw_stream )
        index = self.editview_tabset.addTab(
            a_book.get_edit_view(), a_book.get_book_name())
        self.editview_tabset.setTabToolTip(index,
            a_book.get_book_folder() )
        self.focus_me(seq)
        self.last_open_path = fbts.folderpath() # start for next open or save
        self._add_to_recent(fbts.fullpath())

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Save the book that is currently in focus under its present name, if it
    # is modified. Return True if the save completed, else False.
    # If the active book is a New one, force a Save-As action instead.
    def _save(self):
        active_book = self.open_books[self.focus_book]
        if active_book.get_save_needed() :
            if active_book.get_book_name().startswith('Untitled-'):
                return self._save_as()
            doc_stream = utilities.path_to_output( active_book.get_book_full_path() )
            if doc_stream : # successfully opened for output
                meta_stream = utilities.related_output(doc_stream,'meta')
                if not meta_stream:
                    utilities.warning_msg(
                        _TR('File:Save', 'Unable to open metadata file for writing.'),
                        _TR('File:Save', 'Use loglevel=error for details.') )
                    return False
            else:
                utilities.warning_msg(
                    _TR('File:Save', 'Unable to open book file for writing.'),
                    _TR('File:Save', 'Use loglevel=error for details.') )
                return False
            return active_book.save_book(doc_stream, meta_stream)

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Implement Save As. Query the user for a file path and get that as an
    # output FileBasedTextStream. Call the book to rename itself, which makes
    # it modified. Change the text in the edit tab to match. Discard the FBTS
    # and call _save which will make another one.
    def _save_as(self):
        active_book = self.open_books[self.focus_book]
        fbts = utilities.ask_saving_file(
            _TR('File:Save As dialog',
                'Choose a new location and filename for this book' ),
            self, active_book.get_book_folder() )
        if fbts :
            active_book.rename_book(fbts)
            self.editview_tabset.setTabText(
                self.editview_tabset.currentIndex(),
                fbts.filename() )
            self._add_to_recent(fbts.fullpath())
            fbts = None # discard that object
            return self._save()
        else:
            return False

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Implement Close. If the active book is modified, ask if it should
    # be saved. If it is 'Untitled-' that will turn into Save As.
    def _close(self):
        target_index = self.focus_book # active edit tab is to close
        target_book = self.open_books[target_index]
        if target_book.get_save_needed() :
            # Compose message of translated parts because _TR does not
            # allow for incorporating strings, only numbers.
            msg = _TR('File Close dialog', 'Book file ', 'filename follows here')
            msg += target_book.get_book_name()
            msg += _TR('File Close dialog', ' has been modified!', 'filename precedes this')
            ret = utilities.save_discard_cancel_msg(
                msg,
                info = _TR('File Close dialog',
                           'Save it, Discard changes, or Cancel Closing?')
                )
            if ret is None : # Cancel
                return
            if ret : # True==Save
                self._save()
        # Now, get rid of the active book in 3 steps,
        # 1, close the book's tab in the editview tabset. We don't know which
        # tab it is, because the user can drag tabs around.
        i = self.editview_tabset.indexOf(target_book.get_edit_view())
        # The following causes another tab to be focussed, changing self.focus_book
        # and saving target_book's tabs in target_book, not that we care.
        self.editview_tabset.removeTab(i)
        # 2, remove the book from our dict of open books.
        del self.open_books[target_index]
        # 3, if there are any open books remaining, the tab widget has
        # activated one of them by its rules, which caused a show signal and
        # entry to _focus_me already. However if there are no remaining books
        # there was no show signal or focus_me and the closed book's panels
        # are still in the tabset.
        if 0 == len(self.open_books) :
            self.book_number = 0 # restart the sequence
            self.focus_book = None
            self._new()
        # One way or the other, a focus_me has removed all references to
        # active_book's view panels except those in its PANEL_DICT. So the
        # following assignment should remove the last reference to the book,
        # and schedule the book and associated objects for garbage collect.
        target_book = None
    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Implement loading and saving find panel user buttons. Start the search
    # for files in the active book's folder. User can navigate to extras
    # if need be.
    def _find_save(self):
        target_book = self.open_books[self.focus_book]
        find_panel = target_book.get_find_panel()
        stream = utilities.ask_saving_file(
            _TR('File:Save Find Buttons open dialog',
                'Choose file to contain find button definitions'),
            self,
            starting_path=target_book.get_book_full_path(),
            encoding='UTF-8')
        if stream : # is not None, file is open
            find_panel.user_button_output(stream)
        # else user hit cancel, forget it

    def _find_load(self):
        target_book = self.open_books[self.focus_book]
        find_panel = target_book.get_find_panel()
        stream = utilities.ask_existing_file(
            _TR('File:Load Find Buttons open dialog',
                'Choose a file of find button definitions'),
            self,
            starting_path=target_book.get_book_full_path(),
            encoding='UTF-8')
        if stream :# is not None, we opened it
            find_panel.user_button_input(stream)
            target_book.metadata_modified(True,C.MD_MOD_FLAG)

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Maintain the list of "recent" file paths. The list is kept in usage
    # order, so if a path is in the list now, delete it and then add it to
    # the front. Keep it at a max of 9 items by deleting the oldest if
    # necessary.
    def _add_to_recent(self, path):
        if path in self.recent_files :
            del self.recent_files[self.recent_files.index(path)]
        self.recent_files.insert(0,path)
        self.recent_files = self.recent_files[:9]

    # Upon the aboutToShow signal from the File menu, populate the Recent
    # submenu with a list of files, but only the ones that are currently
    # accessible. If one is on a volume (e.g. USB stick) and you unmount the
    # volume, the path should not appear in the menu until the volume is
    # mounted again.
    def _open_recent(self, path):
        fbts = utilities.path_to_stream(path)
        if fbts :
            self._open(fbts)

    def _build_recent(self):
        active_files = []
        for path in self.recent_files:
            seq = self._is_already_open(path)
            if (seq is None) and utilities.file_is_accessible(path) :
                active_files.append( (utilities.file_split(path),path) )
        if 0 == len(active_files):
            self.recent_menu.setEnabled(False)
            return
        self.recent_menu.setEnabled(True)
        self.recent_menu.clear()
        i = 1
        for ((fname, folder), path) in active_files:
            act = self.recent_menu.addAction(
                '{0} {1} {2}'.format(i,fname,folder)
                )
            act.triggered.connect( lambda: self._open_recent(path) )
            i += 1

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # User has chosen a different font; if it is the general font, set
    # that here so it will propogate to our children.
    def _font_change(self, is_mono):
        if not is_mono:
            self.setFont(fonts.get_general())

    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Create the UI contained within this QMainWindow object. This is a lean
    # main window indeed. We have no toolbar, no status bar, no dock,
    # nothing. Just a splitter with, on the left, a tabset for editviews, and
    # on the right, a scrollbar containing a tabset for panels. (Qt Designer
    # note: it is not possible to build this structure with the Designer. It
    # will not let you put the scroll area into the splitter.)
    #
    # TODO: create a custom QTabWidget using a custom QTabBar to implement
    # drag-out-of-tabset behavior, and use those here.
    def _uic(self):
        # Create the tabset that displays editviews
        self.editview_tabset = QTabWidget()
        self.editview_tabset.setMovable(True) # let user move tabs around
        self.editview_tabset.currentChanged.connect(self._editview_change)
        # Create the tabset that displays find, notes, help &etc.
        self.panel_tabset = QTabWidget()
        self.panel_tabset.setMovable(True)
        # Create the splitter that contains the above two parts.
        self.splitter = QSplitter(Qt.Horizontal, self)
        self.splitter.setChildrenCollapsible(False)
        # Give just a little margin to the left of the editor
        self.splitter.setContentsMargins(8,0,0,0)
        self.splitter.addWidget(self.editview_tabset)
        self.splitter.addWidget(self.panel_tabset)
        # Set that splitter as the main window's central (and only) widget
        self.setCentralWidget(self.splitter)
        # Populate the panel tabset with empty widgets just so there will
        # be tabs that _swap can reference.
        for key in self.panel_dict.keys():
            widj = QWidget()
            self.panel_tabset.addTab(widj,key)
            self.panel_dict[key] = widj
        # Size and position ourself based on saved settings.
        self.move(self.settings.value("mainwindow/position", QPoint(50,50)))
        self.resize(self.settings.value("mainwindow/size", C.STARTUP_DEFAULT_SIZE))
        self.splitter.restoreState(
           self.settings.value("mainwindow/splitter",C.STARTUP_DEFAULT_SPLITTER) )
        # Store a reference to the application menubar. In Mac OS this
        # is a parentless menubar; other platforms it is the default.
        if C.PLATFORM_IS_MAC :
            self.menu_bar = QMenuBar() # parentless menu bar for Mac OS
        else :
            self.menu_bar = self.menuBar # refer to the default one
        set_menu_bar(self.menu_bar)
        # Create the File menu, located in our menu_bar.
        self.file_menu = self.menu_bar.addMenu(_TR('Menu name', '&File'))
        # Populate the File menu with actions.
        #  File:New -> _new()
        work = self.file_menu.addAction( _TR('File menu command','&New') )
        work.setShortcut(QKeySequence.New)
        work.setToolTip( _TR('File:New tooltip','Create a new, empty document') )
        work.triggered.connect(self._new)
        #  File:Open -> _file_open()
        work = self.file_menu.addAction( _TR('File menu command','&Open') )
        work.setShortcut(QKeySequence.Open)
        work.setToolTip( _TR('File:Open tooltip','Open an existing book') )
        work.triggered.connect(self._file_open)
        #  File:Save -> _file_save()
        work = self.file_menu.addAction( _TR('File menu command', '&Save') )
        work.setShortcut(QKeySequence.Save)
        work.setToolTip( _TR('File:Save tooltip','Save the active book') )
        work.triggered.connect(self._save)
        #  Save As -> _file_save_as()
        work = self.file_menu.addAction( _TR('File menu command', 'Save &As') )
        work.setShortcut(QKeySequence.SaveAs)
        work.setToolTip( _TR('File:Save As tooltip','Save the active book under a new name') )
        work.triggered.connect(self._save_as)
        #  Close -> _close()
        work = self.file_menu.addAction( _TR('File menu command', 'Close') )
        work.setShortcut(QKeySequence.Close)
        work.setToolTip( _TR('File:Close tooltip', 'Close the active book') )
        work.triggered.connect(self._close)

        #  Load Find Buttons -> _find_load()
        work = self.file_menu.addAction( _TR('File menu command', 'Load Find Buttons') )
        work.setToolTip( _TR('File:Load Find Buttons tooltip',
            'Load a file of definitions for the custom buttons in the Find panel' )
                         )
        work.triggered.connect(self._find_load)
        #  Save Find Buttons -> _find_save()
        work = self.file_menu.addAction( _TR('File menu command', 'Save Find Buttons') )
        work.setToolTip( _TR('File:Save Find Buttons tooltip',
                              'Save definitions of the custom buttons in the Find panel' )
                         )
        work.triggered.connect(self._find_save)

        # Open Recent gets a submenu that is added to the File menu.
        # The aboutToShow signal is connected to our _build_recent slot.
        self.recent_menu = QMenu( _TR('Sub-menu name', '&Recent Files') )
        work = self.file_menu.addMenu( self.recent_menu )
        work.setToolTip( _TR('File:Recent tooltip', 'List of recently-used files to open') )
        self.file_menu.aboutToShow.connect(self._build_recent)
        #  divider if not Mac
        if not C.PLATFORM_IS_MAC:
            self.file_menu.addSeparator()
        #  TODO Preferences with the menu role that on mac, moves to the app menu
        #  Quit with the menu role that moves it to the app menu
        work = QAction( _TR('Quit command','&Quit'), self )
        work.setMenuRole(QAction.QuitRole)
        work.setShortcut(QKeySequence.Quit)
        work.triggered.connect(self.close)
        self.file_menu.addAction(work)

        # Initialize the list of "recent" files for the File sub-menu.
        # These files were not necessarily open at shutdown, just sometime
        # in the not too distant past.
        self.recent_files = self._read_flist('mainwindow/recent_files')


    # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # Functions related to shutdown and management of settings.
    #
    # Factor out the job of reading/writing a list of files in the settings.
    # Input is a settings array key string like 'mainwindow/recent_files'
    # Output is a possibly empty list of canonical-file-path strings.
    def _read_flist(self, array_key):
        f_list = []
        f_count = self.settings.beginReadArray(array_key)
        for f in range(f_count): # which may be 0
            self.settings.setArrayIndex(f)
            f_list.append( self.settings.value('filepath') )
        self.settings.endArray()
        return f_list
    # Input is an array key and a possibly empty list of path strings
    def _write_flist(self, file_list, array_key):
        if len(file_list):
            self.settings.beginWriteArray( array_key, len(file_list) )
            for f in range(len(file_list)) :
                self.settings.setArrayIndex( f )
                self.settings.setValue( 'filepath',file_list[f] )
            self.settings.endArray()

    # Reimplement QWidget.closeEvent in order to save any open files
    # and update the settings.
    def closeEvent(self, event):
        # If there are any unsaved books, ask the user if they should be
        # saved. If the answer is yes, try to do so.
        unsaved = []
        for (seq, book_object) in self.open_books.items() :
            if book_object.get_save_needed() :
                unsaved.append(seq)
        if len(unsaved):
            if len(unsaved) == 1 :
                msg = _TR('Shutdown message', 'There is one unsaved file')
            else :
                msg = _TR('Shutdown message', 'There are %n unsaved files', n=len(unsaved))
            ret = utilities.save_discard_cancel_msg(
                msg, _TR('Shutdown message', 'Save, Discard changes, or Cancel Quit?') )
            if ret is None :
                # user wants to cancel shutdown
                event.ignore()
                return
            if ret :
                # User want to save. Focus each unsaved file and call _save.
                # For all but "Untitled-n" documents this will be silent. For
                # those, it will open a save-as dialog. We ignore the return
                # from this because we cannot distinguish between a cancelled
                # file-open dialog and a file write error.
                for seq in unsaved :
                    self.focus_me(seq)
                    self._save()
        # Clear the settings so that old values don't hang around
        self.settings.clear()
        # Tell the submodules to save their current global values.
        colors.shutdown(self.settings)
        fonts.shutdown(self.settings)
        dictionaries.shutdown(self.settings)
        paths.shutdown(self.settings)
        # Save the list of currently-open files in the settings, but do not
        # save any whose filename matches "Untitled-#" because that is an
        # unsaved New file (which the user chose not to save, above).
        open_paths = []
        for (index, book_obj) in self.open_books.items() :
            if not book_obj.get_book_name().startswith('Untitled-'):
                open_paths.append( book_obj.get_book_full_path() )
        self._write_flist( open_paths, 'mainwindow/open_files' )
        # Save the list of "recent" files in the settings.
        self._write_flist(self.recent_files, 'mainwindow/recent_files')
        # Save this window's position and size and splitter state
        self.settings.setValue("mainwindow/size",self.size())
        self.settings.setValue("mainwindow/position",self.pos())
        self.settings.setValue("mainwindow/splitter",self.splitter.saveState())
        # and that's it, we are done finished, over & out.
        event.accept()
Ejemplo n.º 22
0
class MainWindow(QMainWindow):
    def __init__(self, all_actions):
        super().__init__()
        self._actions = all_actions

        self._splitter = QSplitter()
        self.setCentralWidget(QFrame(self))
        QHBoxLayout(self.centralWidget()).addWidget(self._splitter)

        self.menuBar().addMenu(ManageMenu(self))
        self.menuBar().addMenu(EditMenu(self))
        self.menuBar().addMenu(NavigateMenu(self))
        self.addToolBar(ManageToolBar(self))
        self.addToolBar(EditToolBar(self))
        self.addToolBar(NavigateToolBar(self))
        self.setStatusBar(StatusBar(self))

        self._tree = Tree(self._splitter)
        self._editor = Editor(self._splitter)
        self._reference = Reference(self._splitter)
        self._parseTimer = QTimer(interval=1000)
        self.statusBar().gotoError.connect(self._editor.setCursorPosition)
        self.statusBar().gotoError.connect(lambda *_: self._editor.setFocus(Qt.OtherFocusReason))
        self.statusBar().errorChanged.connect(self._editor.setParseError)
        self._editor.modificationChanged.connect(self.onModificationChanged)
        self._editor.cursorPositionChanged.connect(self.statusBar().setCursorPosition)
        self._editor.textChanged.connect(self._parseTimer.start)
        self._openScript = None
        self._confirmClose = ConfirmCloseMessage(self).exec
        self._parseTimer.timeout.connect(self.onParseTimer)

        self.onModificationChanged()

    def show(self, desktop):
        settings = QSettings()
        try:
            self.restoreGeometry(settings.value('windows/main/geometry'))
        except TypeError:
            geometry = desktop.availableGeometry()
            size = geometry.size()
            size = QSize(size.width() * 0.75, size.height() * 0.75)
            self.setGeometry(QStyle.alignedRect(Qt.LeftToRight, Qt.AlignCenter, size, geometry))
        try:
            self.restoreState(settings.value('windows/main/window-state'))
        except TypeError:
            pass
        try:
            self._splitter.restoreState(settings.value('windows/main/splitter-state'))
        except TypeError:
            pass
        super().show()

    def action(self, action_type):
        return self._actions[action_type]

    def tree(self):
        return self._tree

    def editor(self):
        return self._editor

    def reference(self):
        return self._reference

    def openScript(self):
        return self._openScript

    def setOpenScript(self, item, content='', *, force=False):
        if self._openScript:
            if not force and not self._confirmClose():
                self._editor.setFocus(Qt.OtherFocusReason)
                return False
            self._openScript.open = False
            self._openScript = None
            self._editor.close()
        if item:
            self._editor.open(content)
            self._openScript = item
            self._openScript.open = True
        self.statusBar().setScript(self._openScript)
        self.onModificationChanged(False)
        return True

    def onModificationChanged(self, isModified=False):
        title = 'Strainer'
        if self._openScript:
            title = f"{title} ({self._openScript.parent().name}: {self._openScript.name}{'*' if isModified else ''})"
        self.setWindowTitle(title)

    def onParseTimer(self):
        self._parseTimer.stop()
        self.statusBar().parseScript(bytes(self._editor.bytes(0, self._editor.length())[:-1]))

    def closeEvent(self, event):
        if self._confirmClose():
            settings = QSettings()
            settings.setValue('windows/main/splitter-state', self._splitter.saveState())
            settings.setValue('windows/main/window-state', self.saveState())
            settings.setValue('windows/main/geometry', self.saveGeometry())
            event.accept()
        else:
            event.ignore()