def _create_filter_menus(self):
     # Airport filters
     arrDep_action_group = QActionGroup(self)
     self._add_filter_action(arrDep_action_group,
                             'All',
                             self.list_model.filter_arrDep_all,
                             ticked=True)
     self._add_filter_action(arrDep_action_group, 'On map',
                             self.list_model.filter_arrDep_inRange)
     if env.airport_data != None:
         self._add_filter_action(arrDep_action_group, 'Here only',
                                 self.list_model.filter_arrDep_here)
     arrDep_filter_menu = QMenu()
     arrDep_filter_menu.addActions(arrDep_action_group.actions())
     self.filterArrDep_button.setMenu(arrDep_filter_menu)
     # Date filters
     date_action_group = QActionGroup(self)
     self._add_filter_action(date_action_group, '+/- 3 days',
                             self.list_model.filter_date_week)
     self._add_filter_action(date_action_group,
                             'Today (~24 h)',
                             self.list_model.filter_date_today,
                             ticked=True)
     date_filter_menu = QMenu()
     date_filter_menu.addActions(date_action_group.actions())
     self.filterDate_button.setMenu(date_filter_menu)
Ejemplo n.º 2
0
class FullscreenMenu(QMenu):
    def __init__(self, main_win, fullscreen_mngr):
        super().__init__(parent=main_win)
        self.fullscreen_mngr = fullscreen_mngr
        self.main_win = main_win

        self.setTitle("Fullscreen")
        self.setIcon(icons.get("fullscreen_menu_bttn"))

        self.qguiapp = QApplication.instance()
        self.action_group = QActionGroup(self)
        self.stop_fs_action = StopFullscreenAction(
            parent=self, fullscreen_mngr=self.fullscreen_mngr
        )
        self.qscreens = None
        self.refresh_items()

        self.fullscreen_mngr.fullscreenstarted.connect(self.on_fullscreenstarted)
        self.fullscreen_mngr.fullscreenstopped.connect(self.on_fullscreenstopped)

    def refresh_items(self):
        # Clear action group
        for action in self.action_group.actions():
            self.action_group.removeAction(action)
            del action

        # Add qscreen actions to group
        this_qscreen = get_qscreen_at(self.main_win)
        primary_qscreen = self.qguiapp.primaryScreen()
        sorted_qscreens = sorted(self.qguiapp.screens(), key=lambda s: s.name())
        for qscreen in sorted_qscreens:
            is_primary = qscreen == primary_qscreen
            is_this_screen = qscreen == this_qscreen
            action = StartFullscreenAction(
                qscreen=qscreen,
                fullscreen_mngr=self.fullscreen_mngr,
                is_primary=is_primary,
                is_this_screen=is_this_screen,
                main_win=self.main_win,
            )
            action.setCheckable(True)
            action.setIcon(icons.get("display_screen"))
            self.action_group.addAction(action)

        self.action_group.addAction(self.stop_fs_action)
        self.addActions(self.action_group.actions())

    def on_menu_aboutToShow(self):
        self.setChecked(self.fullscreen_mngr.is_fullscreen())

    @pyqtSlot(QAction)
    def on_fullscreenstarted(self, action):
        self.stop_fs_action.setEnabled(True)

    @pyqtSlot()
    def on_fullscreenstopped(self):
        self.stop_fs_action.setEnabled(False)

    def on_aboutToShow(self):
        self.refresh_items()
Ejemplo n.º 3
0
    def create_view_buttons(self):
        self.tb_views.addWidget(QLabel("View mode: "))
        ag_views = QActionGroup(self)
        ag_views.setExclusive(True)
        for v in self.views.keys():
            a = QAction(v)
            a.triggered.connect(self.change_view)
            a.setCheckable(True)
            ag_views.addAction(a)
        self.tb_views.addActions(ag_views.actions())
        ag_views.actions()[0].setChecked(True)

        stretch = QWidget()
        stretch.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum))
        self.tb_views.addWidget(stretch)
Ejemplo n.º 4
0
    def create_toolbar(self, parent):
        from PyQt5.QtWidgets import QAction, QToolBar, QActionGroup
        from PyQt5.QtGui import QIcon
        from PyQt5.QtCore import Qt, QSize
        tb = QToolBar(self.display_name, parent)
        tb.setStyleSheet('QToolBar{spacing:0px;}\n'
                         'QToolButton{padding:0px; margin:0px; border:none;}')
        s = self._icon_size
        tb.setIconSize(QSize(s, s))
        parent.add_tool_bar(self, Qt.LeftToolBarArea, tb)
        group = QActionGroup(tb)
        for mode in self.modes:
            action = QAction(self._icon(mode.icon_path), mode.name, group)
            action.setCheckable(True)

            def button_press_cb(event, mode=mode):
                mname = mode.name
                if ' ' in mname:
                    mname = '"%s"' % mname
                from chimerax.core.commands import run
                run(self.session,
                    'ui mousemode %s %s' % (self.button_to_bind, mname))

            action.triggered.connect(button_press_cb)
            action.vr_mode = lambda m=mode: m  # To handle virtual reality button clicks.
            group.addAction(action)
        tb.addActions(group.actions())
        tb.show()
        return tb
Ejemplo n.º 5
0
 def _get_menu(self):
     # main menu
     menu = QMenu()
     main_menu_action_group = QActionGroup(menu)
     main_menu_action_group.setObjectName("main")
     # character menu
     map_action = QAction(menu)
     map_action.setText("Toggle Map")
     main_menu_action_group.addAction(map_action)
     separator = QAction(menu)
     separator.setSeparator(True)
     main_menu_action_group.addAction(separator)
     characters_action = QAction(menu)
     characters_action.setText("Switch Characters")
     main_menu_action_group.addAction(characters_action)
     separator = QAction(menu)
     separator.setSeparator(True)
     main_menu_action_group.addAction(separator)
     settings_action = QAction(menu)
     settings_action.setText("Settings")
     main_menu_action_group.addAction(settings_action)
     quit_action = QAction(menu)
     quit_action.setText("Quit")
     main_menu_action_group.addAction(quit_action)
     menu.addActions(main_menu_action_group.actions())
     menu.triggered[QAction].connect(self._menu_actions)
     return menu
Ejemplo n.º 6
0
class PangoMenuBarWidget(QToolBar):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setMovable(False)
        self.setIconSize(QSize(16, 16))
        self.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)

        # Widgets
        spacer_left = QWidget()
        spacer_left.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        spacer_right = QWidget()
        spacer_right.setSizePolicy(QSizePolicy.Expanding,
                                   QSizePolicy.Preferred)

        # Actions
        self.action_group = QActionGroup(self)

        self.prefs_action = QAction("Prefs")
        self.prefs_action.setIcon(pango_get_icon("prefs"))
        self.action_group.addAction(self.prefs_action)

        self.open_images_action = QAction("Open Images")
        self.open_images_action.setIcon(pango_get_icon("open"))
        self.action_group.addAction(self.open_images_action)

        self.import_action = QAction("Import")
        self.import_action.setIcon(pango_get_icon("import"))
        self.action_group.addAction(self.import_action)

        self.export_action = QAction("Export")
        self.export_action.setIcon(pango_get_icon("export"))
        self.action_group.addAction(self.export_action)

        self.save_project_action = QAction("Save Project")
        self.save_project_action.setIcon(pango_get_icon("save"))
        self.action_group.addAction(self.save_project_action)

        self.run_action = QAction("PyTorch")
        self.run_action.setIcon(pango_get_icon("fire"))
        self.action_group.addAction(self.run_action)

        self.addAction(self.action_group.actions()[0])
        #self.addWidget(spacer_left)
        self.addActions(self.action_group.actions()[1:-1])
        self.addWidget(spacer_right)
        self.addAction(self.action_group.actions()[-1])
Ejemplo n.º 7
0
    def _context_menu(self, point):
        """
        Called when the widget is right-clicked to display the context menu.

        :param point: the location where the click happened
        """
        logger.debug("Opening widget context menu")
        menu = QMenu(self)

        # Add the network settings
        settings = QAction('Network Settings', menu)
        iconPath = self._plugin.resource('settings.png')
        settings.setIcon(QIcon(iconPath))

        # Add a handler on the action
        def settingsActionTriggered():
            dialog = NetworkSettingsDialog(self._plugin)
            dialog.exec_()

        settings.triggered.connect(settingsActionTriggered)
        menu.addAction(settings)

        # Add each of the servers
        if self._plugin.core.servers:
            menu.addSeparator()
            serverGroup = QActionGroup(self)

            def serverActionTriggered(serverAction):
                if not self._plugin.network.connected and \
                       serverAction.isChecked():
                    self._plugin.network.connect(serverAction._server.host,
                                                 serverAction._server.port)
                else:
                    self._plugin.network.disconnect()

            for server in self._plugin.core.servers:
                isConnected = self._plugin.network.connected \
                              and server.host == self._plugin.network.host \
                              and server.port == self._plugin.network.port
                serverAction = QAction('%s:%d' % (server.host, server.port),
                                       menu,
                                       checkable=True)
                serverAction._server = server
                serverAction.setChecked(isConnected)
                serverGroup.addAction(serverAction)

            menu.addActions(serverGroup.actions())
            serverGroup.triggered.connect(serverActionTriggered)

        # Show the context menu
        menu.exec_(self.mapToGlobal(point))
Ejemplo n.º 8
0
class TrayContextMenu(QtWidgets.QMenu):
    instances = set()

    def __init__(self, trayIcon):
        """ trayIcon = the object with the methods to call
        """
        QtWidgets.QMenu.__init__(self)
        TrayContextMenu.instances.add(self)
        self.trayIcon = trayIcon
        self._buildMenu()

    def _buildMenu(self):
        self.framelessCheck = QtWidgets.QAction("Frameless Window",
                                                self,
                                                checkable=True)
        self.framelessCheck.triggered.connect(self.trayIcon.changeFrameless)
        self.addAction(self.framelessCheck)
        self.addSeparator()
        self.requestCheck = QtWidgets.QAction(
            "Show status request notifications", self, checkable=True)
        self.requestCheck.setChecked(True)
        self.addAction(self.requestCheck)
        self.requestCheck.triggered.connect(self.trayIcon.switchRequest)
        self.alarmCheck = QtWidgets.QAction("Show alarm notifications",
                                            self,
                                            checkable=True)
        self.alarmCheck.setChecked(True)
        self.alarmCheck.triggered.connect(self.trayIcon.switchAlarm)
        self.addAction(self.alarmCheck)
        distanceMenu = self.addMenu("Alarm Distance")
        self.distanceGroup = QActionGroup(self)
        for i in range(0, 6):
            action = QAction("{0} Jumps".format(i), None, checkable=True)
            if i == 0:
                action.setChecked(True)
            action.alarmDistance = i
            action.triggered.connect(self.changeAlarmDistance)
            self.distanceGroup.addAction(action)
            distanceMenu.addAction(action)
        self.addMenu(distanceMenu)
        self.addSeparator()
        self.quitAction = QAction("Quit", self)
        self.quitAction.triggered.connect(self.trayIcon.quit)
        self.addAction(self.quitAction)

    def changeAlarmDistance(self):
        for action in self.distanceGroup.actions():
            if action.isChecked():
                self.trayIcon.alarmDistance = action.alarmDistance
                self.trayIcon.changeAlarmDistance()
Ejemplo n.º 9
0
class ItemHistoryMenu(ItemHistory):
    """ Class for storing the history of items added to a QMenu """
    def __init__(self, fileName: str, maxSize: int, menu: Any,
                 callbackMethod: Any):
        ItemHistory.__init__(self, fileName, maxSize)
        self.menu = menu
        self.callbackMethod = callbackMethod
        items = self.tree.getElementsByTagName("item")
        self.actionGroup = QActionGroup(None)
        for node in items:
            text = self._getText(node)  # type: str
            a = self.menu.addAction(text)  # type: Any
            self.actionGroup.addAction(a)
        self.actionGroup.triggered.connect(self.actionTriggered)

    def _removeLastItem(self) -> None:
        actionList = self.actionGroup.actions()
        if actionList:
            a = actionList[-1]
            self.menu.removeAction(a)
            self.actionGroup.removeAction(a)

    def _insertItem(self, index: int, item: str) -> None:
        actionList = self.actionGroup.actions()
        actions = []  # type: List[str]
        for a in actionList:
            actions.append(a.text())
            self.actionGroup.removeAction(a)
        self.menu.clear()
        actions = [item] + actions
        for action in actions:
            a = self.menu.addAction(action)
            self.actionGroup.addAction(a)
        self.actionGroup.triggered.connect(self.actionTriggered)

    def actionTriggered(self, action: QAction) -> None:
        self.callbackMethod(action.text())
Ejemplo n.º 10
0
    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 rebuildBgMenu(self):
     self.scene.clearBgImg()  # clears background
     bg_action_group = QActionGroup(self)
     bg_action_group.addAction(self.clearBg_action)
     for file_spec, pixmap, scale, title in settings.loose_strip_bay_backgrounds:
         action = QAction(title, self)
         action.setCheckable(True)
         action.triggered.connect(
             lambda b, px=pixmap, sc=scale: self.scene.setBgImg(px, sc))
         bg_action_group.addAction(action)
     bg_menu = QMenu()
     bg_menu.addAction(self.clearBg_action)
     bg_menu.addSeparator()
     bg_menu.addActions(
         bg_action_group.actions()[1:])  # index 0 is self.clearBg_action
     self.background_menuButton.setMenu(bg_menu)
     self.clearBg_action.setChecked(True)
     self.background_menuButton.setEnabled(
         settings.loose_strip_bay_backgrounds != [])
Ejemplo n.º 12
0
 def _create_game_menu(self):
     game_menu = QMenu(self)
     game_menu.setTitle('&Game')
     # New game action
     new_action = QAction('&New', self)
     new_action.setShortcut(QKeySequence('F2'))
     new_action.setObjectName('new_menu_item')
     game_menu.addAction(new_action)
     game_menu.addSeparator()
     # Difficulty group menu items.
     ## Create difficulty actions.
     beginner = game_menu.addAction('Beginner')
     intermediate = game_menu.addAction('Intermediate')
     expert = game_menu.addAction('Expert')
     #custom = game_menu.addAction('Custom', self)
     ## Name each of the actions.
     beginner.setObjectName('beginner')
     intermediate.setObjectName('intermediate')
     expert.setObjectName('expert')
     #custom.setObjectName('custom')
     ## Add each action to the group.
     difficulty_group = QActionGroup(self)
     difficulty_group.addAction(beginner)
     difficulty_group.addAction(intermediate)
     difficulty_group.addAction(expert)
     #difficulty_group.addAction(custom)
     # Make the group checkable and set a check mark in front of the selected difficulty.
     difficulty_group.setExclusive(True)
     for item in difficulty_group.actions():
         item.setCheckable(True)
     expert.setChecked(True)
     game_menu.addSeparator()
     # Quit action
     quit_action = QAction('Quit', self)
     quit_action.setObjectName('quit_menu_item')
     game_menu.addAction(quit_action)
     return game_menu
Ejemplo n.º 13
0
class MainWindow(QMainWindow, Ui_MainWindow):
    dictChanged = pyqtSignal(str)

    # Tab indexes
    TabInfos = 0
    TabSummary = 1
    TabPersos = 2
    TabPlots = 3
    TabWorld = 4
    TabOutline = 5
    TabRedac = 6

    def __init__(self):
        QMainWindow.__init__(self)
        self.setupUi(self)
        self.currentProject = None

        self.readSettings()

        # UI
        self.setupMoreUi()

        # Welcome
        self.welcome.updateValues()
        # self.welcome.btnCreate.clicked.connect
        self.stack.setCurrentIndex(0)

        # Word count
        self.mprWordCount = QSignalMapper(self)
        for t, i in [
            (self.txtSummarySentence, 0),
            (self.txtSummaryPara, 1),
            (self.txtSummaryPage, 2),
            (self.txtSummaryFull, 3)
        ]:
            t.textChanged.connect(self.mprWordCount.map)
            self.mprWordCount.setMapping(t, i)
        self.mprWordCount.mapped.connect(self.wordCount)

        # Snowflake Method Cycle
        self.mapperCycle = QSignalMapper(self)
        for t, i in [
            (self.btnStepTwo, 0),
            (self.btnStepThree, 1),
            (self.btnStepFour, 2),
            (self.btnStepFive, 3),
            (self.btnStepSix, 4),
            (self.btnStepSeven, 5),
            (self.btnStepEight, 6)
        ]:
            t.clicked.connect(self.mapperCycle.map)
            self.mapperCycle.setMapping(t, i)

        self.mapperCycle.mapped.connect(self.clickCycle)
        self.cmbSummary.currentIndexChanged.connect(self.summaryPageChanged)
        self.cmbSummary.setCurrentIndex(0)
        self.cmbSummary.currentIndexChanged.emit(0)

        # Main Menu
        for i in [self.actSave, self.actSaveAs, self.actCloseProject,
                  self.menuEdit, self.menuMode, self.menuView, self.menuTools,
                  self.menuHelp]:
            i.setEnabled(False)

        self.actOpen.triggered.connect(self.welcome.openFile)
        self.actSave.triggered.connect(self.saveDatas)
        self.actSaveAs.triggered.connect(self.welcome.saveAsFile)
        self.actCompile.triggered.connect(self.doCompile)
        self.actLabels.triggered.connect(self.settingsLabel)
        self.actStatus.triggered.connect(self.settingsStatus)
        self.actSettings.triggered.connect(self.settingsWindow)
        self.actCloseProject.triggered.connect(self.closeProject)
        self.actQuit.triggered.connect(self.close)
        self.actToolFrequency.triggered.connect(self.frequencyAnalyzer)
        self.generateViewMenu()

        self.makeUIConnections()

        # self.loadProject(os.path.join(appPath(), "test_project.zip"))

    ###############################################################################
    # SUMMARY
    ###############################################################################

    def summaryPageChanged(self, index):
        fractalButtons = [
            self.btnStepTwo,
            self.btnStepThree,
            self.btnStepFive,
            self.btnStepSeven,
        ]
        for b in fractalButtons:
            b.setVisible(fractalButtons.index(b) == index)

    ###############################################################################
    # OUTLINE
    ###############################################################################

    def outlineRemoveItemsRedac(self):
        self.treeRedacOutline.delete()

    def outlineRemoveItemsOutline(self):
        self.treeOutlineOutline.delete()

    ###############################################################################
    # PERSOS
    ###############################################################################

    def changeCurrentPerso(self, trash=None):

        index = self.lstPersos.currentPersoIndex()

        if not index.isValid():
            self.tabPlot.setEnabled(False)
            return

        self.tabPersos.setEnabled(True)

        for w in [
            self.txtPersoName,
            self.sldPersoImportance,
            self.txtPersoMotivation,
            self.txtPersoGoal,
            self.txtPersoConflict,
            self.txtPersoEpiphany,
            self.txtPersoSummarySentence,
            self.txtPersoSummaryPara,
            self.txtPersoSummaryFull,
            self.txtPersoNotes,
        ]:
            w.setCurrentModelIndex(index)

        # Button color
        self.mdlPersos.updatePersoColor(index)

        # Perso Infos
        self.tblPersoInfos.setRootIndex(index)

        if self.mdlPersos.rowCount(index):
            self.updatePersoInfoView()

    def updatePersoInfoView(self):
        # Hide columns
        for i in range(self.mdlPersos.columnCount()):
            self.tblPersoInfos.hideColumn(i)
        self.tblPersoInfos.showColumn(Perso.infoName.value)
        self.tblPersoInfos.showColumn(Perso.infoData.value)

        self.tblPersoInfos.horizontalHeader().setSectionResizeMode(
                Perso.infoName.value, QHeaderView.ResizeToContents)
        self.tblPersoInfos.horizontalHeader().setSectionResizeMode(
                Perso.infoData.value, QHeaderView.Stretch)
        self.tblPersoInfos.verticalHeader().hide()

    ###############################################################################
    # PLOTS
    ###############################################################################

    def changeCurrentPlot(self):
        index = self.lstPlots.currentPlotIndex()

        if not index.isValid():
            self.tabPlot.setEnabled(False)
            return

        self.tabPlot.setEnabled(True)
        self.txtPlotName.setCurrentModelIndex(index)
        self.txtPlotDescription.setCurrentModelIndex(index)
        self.txtPlotResult.setCurrentModelIndex(index)
        self.sldPlotImportance.setCurrentModelIndex(index)
        self.lstPlotPerso.setRootIndex(index.sibling(index.row(),
                                                     Plot.persos.value))
        subplotindex = index.sibling(index.row(), Plot.subplots.value)
        self.lstSubPlots.setRootIndex(subplotindex)
        if self.mdlPlots.rowCount(subplotindex):
            self.updateSubPlotView()

        # self.txtSubPlotSummary.setCurrentModelIndex(QModelIndex())
        self.txtSubPlotSummary.setEnabled(False)
        self._updatingSubPlot = True
        self.txtSubPlotSummary.setPlainText("")
        self._updatingSubPlot = False
        self.lstPlotPerso.selectionModel().clear()

    def updateSubPlotView(self):
        # Hide columns
        for i in range(self.mdlPlots.columnCount()):
            self.lstSubPlots.hideColumn(i)
        self.lstSubPlots.showColumn(Subplot.name.value)
        self.lstSubPlots.showColumn(Subplot.meta.value)

        self.lstSubPlots.horizontalHeader().setSectionResizeMode(
                Subplot.name.value, QHeaderView.Stretch)
        self.lstSubPlots.horizontalHeader().setSectionResizeMode(
                Subplot.meta.value, QHeaderView.ResizeToContents)
        self.lstSubPlots.verticalHeader().hide()

    def changeCurrentSubPlot(self, index):
        # Got segfaults when using textEditView model system, so ad hoc stuff.
        index = index.sibling(index.row(), Subplot.summary.value)
        item = self.mdlPlots.itemFromIndex(index)
        if not item:
            self.txtSubPlotSummary.setEnabled(False)
            return
        self.txtSubPlotSummary.setEnabled(True)
        txt = item.text()
        self._updatingSubPlot = True
        self.txtSubPlotSummary.setPlainText(txt)
        self._updatingSubPlot = False

    def updateSubPlotSummary(self):
        if self._updatingSubPlot:
            return

        index = self.lstSubPlots.currentIndex()
        if not index.isValid():
            return
        index = index.sibling(index.row(), Subplot.summary.value)
        item = self.mdlPlots.itemFromIndex(index)

        self._updatingSubPlot = True
        item.setText(self.txtSubPlotSummary.toPlainText())
        self._updatingSubPlot = False

    def plotPersoSelectionChanged(self):
        "Enables or disables remove plot perso button."
        self.btnRmPlotPerso.setEnabled(
                len(self.lstPlotPerso.selectedIndexes()) != 0)

    ###############################################################################
    # WORLD
    ###############################################################################

    def changeCurrentWorld(self):
        index = self.mdlWorld.selectedIndex()

        if not index.isValid():
            self.tabWorld.setEnabled(False)
            return

        self.tabWorld.setEnabled(True)
        self.txtWorldName.setCurrentModelIndex(index)
        self.txtWorldDescription.setCurrentModelIndex(index)
        self.txtWorldPassion.setCurrentModelIndex(index)
        self.txtWorldConflict.setCurrentModelIndex(index)

    ###############################################################################
    # LOAD AND SAVE
    ###############################################################################

    def loadProject(self, project, loadFromFile=True):
        """Loads the project ``project``.

        If ``loadFromFile`` is False, then it does not load datas from file.
        It assumes that the datas have been populated in a different way."""
        if loadFromFile and not os.path.exists(project):
            print(self.tr("The file {} does not exist. Try again.").format(project))
            self.statusBar().showMessage(
                    self.tr("The file {} does not exist. Try again.").format(project),
                    5000)
            return

        if loadFromFile:
            # Load empty settings
            imp.reload(settings)

            # Load data
            self.loadEmptyDatas()
            self.loadDatas(project)

        self.makeConnections()

        # Load settings
        for i in settings.openIndexes:
            idx = self.mdlOutline.indexFromPath(i)
            self.mainEditor.setCurrentModelIndex(idx, newTab=True)
        self.generateViewMenu()
        self.mainEditor.sldCorkSizeFactor.setValue(settings.corkSizeFactor)
        self.actSpellcheck.setChecked(settings.spellcheck)
        self.toggleSpellcheck(settings.spellcheck)
        self.updateMenuDict()
        self.setDictionary()

        self.mainEditor.setFolderView(settings.folderView)
        self.mainEditor.updateFolderViewButtons(settings.folderView)
        self.tabMain.setCurrentIndex(settings.lastTab)
        # We force to emit even if it opens on the current tab
        self.tabMain.currentChanged.emit(settings.lastTab)
        self.mainEditor.updateCorkBackground()

        # Set autosave
        self.saveTimer = QTimer()
        self.saveTimer.setInterval(settings.autoSaveDelay * 60 * 1000)
        self.saveTimer.setSingleShot(False)
        self.saveTimer.timeout.connect(self.saveDatas)
        if settings.autoSave:
            self.saveTimer.start()

        # Set autosave if no changes
        self.saveTimerNoChanges = QTimer()
        self.saveTimerNoChanges.setInterval(settings.autoSaveNoChangesDelay * 1000)
        self.saveTimerNoChanges.setSingleShot(True)
        self.mdlFlatData.dataChanged.connect(self.startTimerNoChanges)
        self.mdlOutline.dataChanged.connect(self.startTimerNoChanges)
        self.mdlPersos.dataChanged.connect(self.startTimerNoChanges)
        self.mdlPlots.dataChanged.connect(self.startTimerNoChanges)
        self.mdlWorld.dataChanged.connect(self.startTimerNoChanges)
        # self.mdlPersosInfos.dataChanged.connect(self.startTimerNoChanges)
        self.mdlStatus.dataChanged.connect(self.startTimerNoChanges)
        self.mdlLabels.dataChanged.connect(self.startTimerNoChanges)

        self.saveTimerNoChanges.timeout.connect(self.saveDatas)
        self.saveTimerNoChanges.stop()

        # UI
        for i in [self.actSave, self.actSaveAs, self.actCloseProject,
                  self.menuEdit, self.menuMode, self.menuView, self.menuTools,
                  self.menuHelp]:
            i.setEnabled(True)
        # FIXME: set Window's name: project name

        # Stuff
        # self.checkPersosID()  # Should'n be necessary any longer

        self.currentProject = project
        QSettings().setValue("lastProject", project)

        # Show main Window
        self.stack.setCurrentIndex(1)

    def closeProject(self):
        # Save datas
        self.saveDatas()

        self.currentProject = None
        QSettings().setValue("lastProject", "")

        # FIXME: close all opened tabs in mainEditor

        # Clear datas
        self.loadEmptyDatas()

        self.saveTimer.stop()

        # UI
        for i in [self.actSave, self.actSaveAs, self.actCloseProject,
                  self.menuEdit, self.menuMode, self.menuView, self.menuTools,
                  self.menuHelp]:
            i.setEnabled(False)

        # Reload recent files
        self.welcome.updateValues()

        # Show welcome dialog
        self.stack.setCurrentIndex(0)

    def readSettings(self):
        # Load State and geometry
        sttgns = QSettings(qApp.organizationName(), qApp.applicationName())
        if sttgns.contains("geometry"):
            self.restoreGeometry(sttgns.value("geometry"))
        if sttgns.contains("windowState"):
            self.restoreState(sttgns.value("windowState"))
        else:
            self.dckCheatSheet.hide()
            self.dckSearch.hide()
        if sttgns.contains("metadataState"):
            state = [False if v == "false" else True for v in sttgns.value("metadataState")]
            self.redacMetadata.restoreState(state)
        if sttgns.contains("revisionsState"):
            state = [False if v == "false" else True for v in sttgns.value("revisionsState")]
            self.redacMetadata.revisions.restoreState(state)
        if sttgns.contains("splitterRedacH"):
            self.splitterRedacH.restoreState(sttgns.value("splitterRedacH"))
        if sttgns.contains("splitterRedacV"):
            self.splitterRedacV.restoreState(sttgns.value("splitterRedacV"))
        if sttgns.contains("toolbar"):
            # self.toolbar is not initialized yet, so we just store balue
            self._toolbarState = sttgns.value("toolbar")
        else:
            self._toolbarState = ""


    def closeEvent(self, event):
        # Save State and geometry and other things
        sttgns = QSettings(qApp.organizationName(), qApp.applicationName())
        sttgns.setValue("geometry", self.saveGeometry())
        sttgns.setValue("windowState", self.saveState())
        sttgns.setValue("metadataState", self.redacMetadata.saveState())
        sttgns.setValue("revisionsState", self.redacMetadata.revisions.saveState())
        sttgns.setValue("splitterRedacH", self.splitterRedacH.saveState())
        sttgns.setValue("splitterRedacV", self.splitterRedacV.saveState())
        sttgns.setValue("toolbar", self.toolbar.saveState())

        # Specific settings to save before quitting
        settings.lastTab = self.tabMain.currentIndex()

        if self.currentProject:
            # Remembering the current items
            sel = []
            for i in range(self.mainEditor.tab.count()):
                sel.append(self.mdlOutline.pathToIndex(self.mainEditor.tab.widget(i).currentIndex))
            settings.openIndexes = sel

        # Save data from models
        if self.currentProject and settings.saveOnQuit:
            self.saveDatas()

            # closeEvent
            # QMainWindow.closeEvent(self, event)  # Causin segfaults?

    def startTimerNoChanges(self):
        if settings.autoSaveNoChanges:
            self.saveTimerNoChanges.start()

    def saveDatas(self, projectName=None):
        """Saves the current project (in self.currentProject).

        If ``projectName`` is given, currentProject becomes projectName.
        In other words, it "saves as...".
        """

        if projectName:
            self.currentProject = projectName
            QSettings().setValue("lastProject", projectName)

        # Saving
        files = []

        files.append((saveStandardItemModelXML(self.mdlFlatData),
                      "flatModel.xml"))
        files.append((saveStandardItemModelXML(self.mdlPersos),
                      "perso.xml"))
        files.append((saveStandardItemModelXML(self.mdlWorld),
                      "world.xml"))
        files.append((saveStandardItemModelXML(self.mdlLabels),
                      "labels.xml"))
        files.append((saveStandardItemModelXML(self.mdlStatus),
                      "status.xml"))
        files.append((saveStandardItemModelXML(self.mdlPlots),
                      "plots.xml"))
        files.append((self.mdlOutline.saveToXML(),
                      "outline.xml"))
        files.append((settings.save(),
                      "settings.pickle"))

        saveFilesToZip(files, self.currentProject)

        # Giving some feedback
        print(self.tr("Project {} saved.").format(self.currentProject))
        self.statusBar().showMessage(
                self.tr("Project {} saved.").format(self.currentProject), 5000)

    def loadEmptyDatas(self):
        self.mdlFlatData = QStandardItemModel(self)
        self.mdlPersos = persosModel(self)
        # self.mdlPersosProxy = persosProxyModel(self)
        # self.mdlPersosInfos = QStandardItemModel(self)
        self.mdlLabels = QStandardItemModel(self)
        self.mdlStatus = QStandardItemModel(self)
        self.mdlPlots = plotModel(self)
        self.mdlOutline = outlineModel(self)
        self.mdlWorld = worldModel(self)

    def loadDatas(self, project):
        # Loading
        files = loadFilesFromZip(project)

        errors = []

        if "flatModel.xml" in files:
            loadStandardItemModelXML(self.mdlFlatData,
                                     files["flatModel.xml"], fromString=True)
        else:
            errors.append("flatModel.xml")

        if "perso.xml" in files:
            loadStandardItemModelXML(self.mdlPersos,
                                     files["perso.xml"], fromString=True)
        else:
            errors.append("perso.xml")

        if "world.xml" in files:
            loadStandardItemModelXML(self.mdlWorld,
                                     files["world.xml"], fromString=True)
        else:
            errors.append("world.xml")

        if "labels.xml" in files:
            loadStandardItemModelXML(self.mdlLabels,
                                     files["labels.xml"], fromString=True)
        else:
            errors.append("perso.xml")

        if "status.xml" in files:
            loadStandardItemModelXML(self.mdlStatus,
                                     files["status.xml"], fromString=True)
        else:
            errors.append("perso.xml")

        if "plots.xml" in files:
            loadStandardItemModelXML(self.mdlPlots,
                                     files["plots.xml"], fromString=True)
        else:
            errors.append("perso.xml")

        if "outline.xml" in files:
            self.mdlOutline.loadFromXML(files["outline.xml"], fromString=True)
        else:
            errors.append("perso.xml")

        if "settings.pickle" in files:
            settings.load(files["settings.pickle"], fromString=True)
        else:
            errors.append("perso.xml")

        # Giving some feedback
        if not errors:
            print(self.tr("Project {} loaded.").format(project))
            self.statusBar().showMessage(
                    self.tr("Project {} loaded.").format(project), 5000)
        else:
            print(self.tr("Project {} loaded with some errors:").format(project))
            for e in errors:
                print(self.tr(" * {} wasn't found in project file.").format(e))
            self.statusBar().showMessage(
                    self.tr("Project {} loaded with some errors.").format(project), 5000)

    ###############################################################################
    # MAIN CONNECTIONS
    ###############################################################################

    def makeUIConnections(self):
        "Connections that have to be made once only, event when new project is loaded."
        self.lstPersos.currentItemChanged.connect(self.changeCurrentPerso, AUC)

        self.txtPlotFilter.textChanged.connect(self.lstPlots.setFilter, AUC)
        self.lstPlots.currentItemChanged.connect(self.changeCurrentPlot, AUC)
        self.txtSubPlotSummary.document().contentsChanged.connect(
                self.updateSubPlotSummary, AUC)
        self.lstSubPlots.activated.connect(self.changeCurrentSubPlot, AUC)

        self.btnRedacAddFolder.clicked.connect(self.treeRedacOutline.addFolder, AUC)
        self.btnOutlineAddFolder.clicked.connect(self.treeOutlineOutline.addFolder, AUC)
        self.btnRedacAddText.clicked.connect(self.treeRedacOutline.addText, AUC)
        self.btnOutlineAddText.clicked.connect(self.treeOutlineOutline.addText, AUC)
        self.btnRedacRemoveItem.clicked.connect(self.outlineRemoveItemsRedac, AUC)
        self.btnOutlineRemoveItem.clicked.connect(self.outlineRemoveItemsOutline, AUC)

        self.tabMain.currentChanged.connect(self.toolbar.setCurrentGroup)

    def makeConnections(self):

        # Flat datas (Summary and general infos)
        for widget, col in [
            (self.txtSummarySituation, 0),
            (self.txtSummarySentence, 1),
            (self.txtSummarySentence_2, 1),
            (self.txtSummaryPara, 2),
            (self.txtSummaryPara_2, 2),
            (self.txtPlotSummaryPara, 2),
            (self.txtSummaryPage, 3),
            (self.txtSummaryPage_2, 3),
            (self.txtPlotSummaryPage, 3),
            (self.txtSummaryFull, 4),
            (self.txtPlotSummaryFull, 4),
        ]:
            widget.setModel(self.mdlFlatData)
            widget.setColumn(col)
            widget.setCurrentModelIndex(self.mdlFlatData.index(1, col))

        for widget, col in [
            (self.txtGeneralTitle, 0),
            (self.txtGeneralSubtitle, 1),
            (self.txtGeneralSerie, 2),
            (self.txtGeneralVolume, 3),
            (self.txtGeneralGenre, 4),
            (self.txtGeneralLicense, 5),
            (self.txtGeneralAuthor, 6),
            (self.txtGeneralEmail, 7),
        ]:
            widget.setModel(self.mdlFlatData)
            widget.setColumn(col)
            widget.setCurrentModelIndex(self.mdlFlatData.index(0, col))

        # Persos
        self.lstPersos.setPersosModel(self.mdlPersos)
        self.tblPersoInfos.setModel(self.mdlPersos)

        self.btnAddPerso.clicked.connect(self.mdlPersos.addPerso, AUC)
        self.btnRmPerso.clicked.connect(self.mdlPersos.removePerso, AUC)
        self.btnPersoColor.clicked.connect(self.mdlPersos.chosePersoColor, AUC)

        self.btnPersoAddInfo.clicked.connect(self.mdlPersos.addPersoInfo, AUC)
        self.btnPersoRmInfo.clicked.connect(self.mdlPersos.removePersoInfo, AUC)

        for w, c in [
            (self.txtPersoName, Perso.name.value),
            (self.sldPersoImportance, Perso.importance.value),
            (self.txtPersoMotivation, Perso.motivation.value),
            (self.txtPersoGoal, Perso.goal.value),
            (self.txtPersoConflict, Perso.conflict.value),
            (self.txtPersoEpiphany, Perso.epiphany.value),
            (self.txtPersoSummarySentence, Perso.summarySentence.value),
            (self.txtPersoSummaryPara, Perso.summaryPara.value),
            (self.txtPersoSummaryFull, Perso.summaryFull.value),
            (self.txtPersoNotes, Perso.notes.value)
        ]:
            w.setModel(self.mdlPersos)
            w.setColumn(c)
        self.tabPersos.setEnabled(False)

        # Plots
        self.lstPlots.setPlotModel(self.mdlPlots)
        self.lstPlotPerso.setModel(self.mdlPlots)
        self.lstSubPlots.setModel(self.mdlPlots)
        self._updatingSubPlot = False
        self.btnAddPlot.clicked.connect(self.mdlPlots.addPlot, AUC)
        self.btnRmPlot.clicked.connect(lambda:
                                       self.mdlPlots.removePlot(self.lstPlots.currentPlotIndex()), AUC)
        self.btnAddSubPlot.clicked.connect(self.mdlPlots.addSubPlot, AUC)
        self.btnRmSubPlot.clicked.connect(self.mdlPlots.removeSubPlot, AUC)
        self.lstPlotPerso.selectionModel().selectionChanged.connect(self.plotPersoSelectionChanged)
        self.btnRmPlotPerso.clicked.connect(self.mdlPlots.removePlotPerso, AUC)

        for w, c in [
            (self.txtPlotName, Plot.name.value),
            (self.txtPlotDescription, Plot.description.value),
            (self.txtPlotResult, Plot.result.value),
            (self.sldPlotImportance, Plot.importance.value),
        ]:
            w.setModel(self.mdlPlots)
            w.setColumn(c)

        self.tabPlot.setEnabled(False)
        self.mdlPlots.updatePlotPersoButton()
        self.mdlPersos.dataChanged.connect(self.mdlPlots.updatePlotPersoButton)
        self.lstOutlinePlots.setPlotModel(self.mdlPlots)
        self.lstOutlinePlots.setShowSubPlot(True)
        self.plotPersoDelegate = outlinePersoDelegate(self.mdlPersos, self)
        self.lstPlotPerso.setItemDelegate(self.plotPersoDelegate)
        self.plotDelegate = plotDelegate(self)
        self.lstSubPlots.setItemDelegateForColumn(Subplot.meta.value, self.plotDelegate)

        # World
        self.treeWorld.setModel(self.mdlWorld)
        for i in range(self.mdlWorld.columnCount()):
            self.treeWorld.hideColumn(i)
        self.treeWorld.showColumn(0)
        self.btnWorldEmptyData.setMenu(self.mdlWorld.emptyDataMenu())
        self.treeWorld.selectionModel().selectionChanged.connect(self.changeCurrentWorld, AUC)
        self.btnAddWorld.clicked.connect(self.mdlWorld.addItem, AUC)
        self.btnRmWorld.clicked.connect(self.mdlWorld.removeItem, AUC)
        for w, c in [
            (self.txtWorldName, World.name.value),
            (self.txtWorldDescription, World.description.value),
            (self.txtWorldPassion, World.passion.value),
            (self.txtWorldConflict, World.conflict.value),
        ]:
            w.setModel(self.mdlWorld)
            w.setColumn(c)
        self.tabWorld.setEnabled(False)
        self.treeWorld.expandAll()

        # Outline
        self.treeRedacOutline.setModel(self.mdlOutline)
        self.treeOutlineOutline.setModelPersos(self.mdlPersos)
        self.treeOutlineOutline.setModelLabels(self.mdlLabels)
        self.treeOutlineOutline.setModelStatus(self.mdlStatus)

        self.redacMetadata.setModels(self.mdlOutline, self.mdlPersos,
                                     self.mdlLabels, self.mdlStatus)
        self.outlineItemEditor.setModels(self.mdlOutline, self.mdlPersos,
                                         self.mdlLabels, self.mdlStatus)

        self.treeOutlineOutline.setModel(self.mdlOutline)
        # self.redacEditor.setModel(self.mdlOutline)
        self.storylineView.setModels(self.mdlOutline, self.mdlPersos, self.mdlPlots)

        self.treeOutlineOutline.selectionModel().selectionChanged.connect(lambda:
                                                                          self.outlineItemEditor.selectionChanged(
                                                                                  self.treeOutlineOutline), AUC)
        self.treeOutlineOutline.clicked.connect(lambda:
                                                self.outlineItemEditor.selectionChanged(self.treeOutlineOutline), AUC)

        # Sync selection
        self.treeRedacOutline.selectionModel().selectionChanged.connect(
                lambda: self.redacMetadata.selectionChanged(self.treeRedacOutline), AUC)
        self.treeRedacOutline.clicked.connect(
                lambda: self.redacMetadata.selectionChanged(self.treeRedacOutline), AUC)

        self.treeRedacOutline.selectionModel().selectionChanged.connect(self.mainEditor.selectionChanged, AUC)

        # Cheat Sheet
        self.cheatSheet.setModels()

        # Debug
        self.mdlFlatData.setVerticalHeaderLabels(["Infos générales", "Summary"])
        self.tblDebugFlatData.setModel(self.mdlFlatData)
        self.tblDebugPersos.setModel(self.mdlPersos)
        self.tblDebugPersosInfos.setModel(self.mdlPersos)
        self.tblDebugPersos.selectionModel().currentChanged.connect(
                lambda: self.tblDebugPersosInfos.setRootIndex(self.mdlPersos.index(
                        self.tblDebugPersos.selectionModel().currentIndex().row(),
                        Perso.name.value)), AUC)

        self.tblDebugPlots.setModel(self.mdlPlots)
        self.tblDebugPlotsPersos.setModel(self.mdlPlots)
        self.tblDebugSubPlots.setModel(self.mdlPlots)
        self.tblDebugPlots.selectionModel().currentChanged.connect(
                lambda: self.tblDebugPlotsPersos.setRootIndex(self.mdlPlots.index(
                        self.tblDebugPlots.selectionModel().currentIndex().row(),
                        Plot.persos.value)), AUC)
        self.tblDebugPlots.selectionModel().currentChanged.connect(
                lambda: self.tblDebugSubPlots.setRootIndex(self.mdlPlots.index(
                        self.tblDebugPlots.selectionModel().currentIndex().row(),
                        Plot.subplots.value)), AUC)
        self.treeDebugWorld.setModel(self.mdlWorld)
        self.treeDebugOutline.setModel(self.mdlOutline)
        self.lstDebugLabels.setModel(self.mdlLabels)
        self.lstDebugStatus.setModel(self.mdlStatus)

    ###############################################################################
    # GENERAL AKA UNSORTED
    ###############################################################################

    def clickCycle(self, i):
        if i == 0:  # step 2 - paragraph summary
            self.tabMain.setCurrentIndex(self.TabSummary)
            self.tabSummary.setCurrentIndex(1)
        if i == 1:  # step 3 - characters summary
            self.tabMain.setCurrentIndex(self.TabPersos)
            self.tabPersos.setCurrentIndex(0)
        if i == 2:  # step 4 - page summary
            self.tabMain.setCurrentIndex(self.TabSummary)
            self.tabSummary.setCurrentIndex(2)
        if i == 3:  # step 5 - characters description
            self.tabMain.setCurrentIndex(self.TabPersos)
            self.tabPersos.setCurrentIndex(1)
        if i == 4:  # step 6 - four page synopsis
            self.tabMain.setCurrentIndex(self.TabSummary)
            self.tabSummary.setCurrentIndex(3)
        if i == 5:  # step 7 - full character charts
            self.tabMain.setCurrentIndex(self.TabPersos)
            self.tabPersos.setCurrentIndex(2)
        if i == 6:  # step 8 - scene list
            self.tabMain.setCurrentIndex(self.TabPlots)

    def wordCount(self, i):

        src = {
            0: self.txtSummarySentence,
            1: self.txtSummaryPara,
            2: self.txtSummaryPage,
            3: self.txtSummaryFull
        }[i]

        lbl = {
            0: self.lblSummaryWCSentence,
            1: self.lblSummaryWCPara,
            2: self.lblSummaryWCPage,
            3: self.lblSummaryWCFull
        }[i]

        wc = wordCount(src.toPlainText())
        if i in [2, 3]:
            pages = self.tr(" (~{} pages)").format(int(wc / 25) / 10.)
        else:
            pages = ""
        lbl.setText(self.tr("Words: {}{}").format(wc, pages))

    def setupMoreUi(self):

        # Tool bar on the right
        self.toolbar = collapsibleDockWidgets(Qt.RightDockWidgetArea, self)
        self.toolbar.addCustomWidget(self.tr("Book summary"), self.grpPlotSummary, self.TabPlots)
        self.toolbar.addCustomWidget(self.tr("Project tree"), self.treeRedacWidget, self.TabRedac)
        self.toolbar.addCustomWidget(self.tr("Metadata"), self.redacMetadata, self.TabRedac)
        self.toolbar.addCustomWidget(self.tr("Story line"), self.storylineView, self.TabRedac)
        if self._toolbarState:
            self.toolbar.restoreState(self._toolbarState)

        # Custom "tab" bar on the left
        self.lstTabs.setIconSize(QSize(48, 48))
        for i in range(self.tabMain.count()):
            icons = ["general-128px.png",
                     "summary-128px.png",
                     "characters-128px.png",
                     "plot-128px.png",
                     "world-128px.png",
                     "outline-128px.png",
                     "redaction-128px.png",
                     ""
                     ]
            self.tabMain.setTabIcon(i, QIcon(appPath("icons/Custom/Tabs/{}".format(icons[i]))))
            item = QListWidgetItem(self.tabMain.tabIcon(i),
                                   self.tabMain.tabText(i))
            item.setSizeHint(QSize(item.sizeHint().width(), 64))
            item.setTextAlignment(Qt.AlignCenter)
            self.lstTabs.addItem(item)
        self.tabMain.tabBar().hide()
        self.lstTabs.currentRowChanged.connect(self.tabMain.setCurrentIndex)
        self.tabMain.currentChanged.connect(self.lstTabs.setCurrentRow)

        # Splitters
        self.splitterPersos.setStretchFactor(0, 25)
        self.splitterPersos.setStretchFactor(1, 75)

        self.splitterPlot.setStretchFactor(0, 20)
        self.splitterPlot.setStretchFactor(1, 60)
        self.splitterPlot.setStretchFactor(2, 30)

        self.splitterWorld.setStretchFactor(0, 25)
        self.splitterWorld.setStretchFactor(1, 75)

        self.splitterOutlineH.setStretchFactor(0, 25)
        self.splitterOutlineH.setStretchFactor(1, 75)
        self.splitterOutlineV.setStretchFactor(0, 75)
        self.splitterOutlineV.setStretchFactor(1, 25)

        self.splitterRedacV.setStretchFactor(0, 75)
        self.splitterRedacV.setStretchFactor(1, 25)

        self.splitterRedacH.setStretchFactor(0, 30)
        self.splitterRedacH.setStretchFactor(1, 40)
        self.splitterRedacH.setStretchFactor(2, 30)

        # QFormLayout stretch
        for w in [self.txtWorldDescription, self.txtWorldPassion, self.txtWorldConflict]:
            s = w.sizePolicy()
            s.setVerticalStretch(1)
            w.setSizePolicy(s)

        # Help box
        references = [
            (self.lytTabOverview,
             self.tr("Enter infos about your book, and yourself."),
             0),
            (self.lytSituation,
             self.tr(
                     """The basic situation, in the form of a 'What if...?' question. Ex: 'What if the most dangerous
                     evil wizard could wasn't abled to kill a baby?' (Harry Potter)"""),
             1),
            (self.lytSummary,
             self.tr(
                     """Take time to think about a one sentence (~50 words) summary of your book. Then expand it to
                     a paragraph, then to a page, then to a full summary."""),
             1),
            (self.lytTabPersos,
             self.tr("Create your characters."),
             0),
            (self.lytTabPlot,
             self.tr("Develop plots."),
             0),
            (self.lytTabOutline,
             self.tr("Create the outline of your masterpiece."),
             0),
            (self.lytTabRedac,
             self.tr("Write."),
             0),
            (self.lytTabDebug,
             self.tr("Debug infos. Sometimes useful."),
             0)
        ]

        for widget, text, pos in references:
            label = helpLabel(text, self)
            self.actShowHelp.toggled.connect(label.setVisible, AUC)
            widget.layout().insertWidget(pos, label)

        self.actShowHelp.setChecked(False)

        # Spellcheck
        if enchant:
            self.menuDict = QMenu(self.tr("Dictionary"))
            self.menuDictGroup = QActionGroup(self)
            self.updateMenuDict()
            self.menuTools.addMenu(self.menuDict)

            self.actSpellcheck.toggled.connect(self.toggleSpellcheck, AUC)
            self.dictChanged.connect(self.mainEditor.setDict, AUC)
            self.dictChanged.connect(self.redacMetadata.setDict, AUC)
            self.dictChanged.connect(self.outlineItemEditor.setDict, AUC)

        else:
            # No Spell check support
            self.actSpellcheck.setVisible(False)
            a = QAction(self.tr("Install PyEnchant to use spellcheck"), self)
            a.setIcon(self.style().standardIcon(QStyle.SP_MessageBoxWarning))
            a.triggered.connect(self.openPyEnchantWebPage, AUC)
            self.menuTools.addAction(a)

    ###############################################################################
    # SPELLCHECK
    ###############################################################################

    def updateMenuDict(self):

        if not enchant:
            return

        self.menuDict.clear()
        for i in enchant.list_dicts():
            a = QAction(str(i[0]), self)
            a.setCheckable(True)
            if settings.dict is None:
                settings.dict = enchant.get_default_language()
            if str(i[0]) == settings.dict:
                a.setChecked(True)
            a.triggered.connect(self.setDictionary, AUC)
            self.menuDictGroup.addAction(a)
            self.menuDict.addAction(a)

    def setDictionary(self):
        if not enchant:
            return

        for i in self.menuDictGroup.actions():
            if i.isChecked():
                # self.dictChanged.emit(i.text().replace("&", ""))
                settings.dict = i.text().replace("&", "")

                # Find all textEditView from self, and toggle spellcheck
                for w in self.findChildren(textEditView, QRegExp(".*"),
                                           Qt.FindChildrenRecursively):
                    w.setDict(settings.dict)

    def openPyEnchantWebPage(self):
        from PyQt5.QtGui import QDesktopServices
        QDesktopServices.openUrl(QUrl("http://pythonhosted.org/pyenchant/"))

    def toggleSpellcheck(self, val):
        settings.spellcheck = val

        # Find all textEditView from self, and toggle spellcheck
        for w in self.findChildren(textEditView, QRegExp(".*"),
                                   Qt.FindChildrenRecursively):
            w.toggleSpellcheck(val)

    ###############################################################################
    # SETTINGS
    ###############################################################################

    def settingsLabel(self):
        self.settingsWindow(3)

    def settingsStatus(self):
        self.settingsWindow(4)

    def settingsWindow(self, tab=None):
        self.sw = settingsWindow(self)
        self.sw.hide()
        self.sw.setWindowModality(Qt.ApplicationModal)
        self.sw.setWindowFlags(Qt.Dialog)
        r = self.sw.geometry()
        r2 = self.geometry()
        self.sw.move(r2.center() - r.center())
        if tab:
            self.sw.setTab(tab)
        self.sw.show()

    ###############################################################################
    # TOOLS
    ###############################################################################

    def frequencyAnalyzer(self):
        self.fw = frequencyAnalyzer(self)
        self.fw.show()

    ###############################################################################
    # VIEW MENU
    ###############################################################################

    def generateViewMenu(self):

        values = [
            (self.tr("Nothing"), "Nothing"),
            (self.tr("POV"), "POV"),
            (self.tr("Label"), "Label"),
            (self.tr("Progress"), "Progress"),
            (self.tr("Compile"), "Compile"),
        ]

        menus = [
            (self.tr("Tree"), "Tree"),
            (self.tr("Index cards"), "Cork"),
            (self.tr("Outline"), "Outline")
        ]

        submenus = {
            "Tree": [
                (self.tr("Icon color"), "Icon"),
                (self.tr("Text color"), "Text"),
                (self.tr("Background color"), "Background"),
            ],
            "Cork": [
                (self.tr("Icon"), "Icon"),
                (self.tr("Text"), "Text"),
                (self.tr("Background"), "Background"),
                (self.tr("Border"), "Border"),
                (self.tr("Corner"), "Corner"),
            ],
            "Outline": [
                (self.tr("Icon color"), "Icon"),
                (self.tr("Text color"), "Text"),
                (self.tr("Background color"), "Background"),
            ],
        }

        self.menuView.clear()

        # print("Generating menus with", settings.viewSettings)

        for mnu, mnud in menus:
            m = QMenu(mnu, self.menuView)
            for s, sd in submenus[mnud]:
                m2 = QMenu(s, m)
                agp = QActionGroup(m2)
                for v, vd in values:
                    a = QAction(v, m)
                    a.setCheckable(True)
                    a.setData("{},{},{}".format(mnud, sd, vd))
                    if settings.viewSettings[mnud][sd] == vd:
                        a.setChecked(True)
                    a.triggered.connect(self.setViewSettingsAction, AUC)
                    agp.addAction(a)
                    m2.addAction(a)
                m.addMenu(m2)
            self.menuView.addMenu(m)

    def setViewSettingsAction(self):
        action = self.sender()
        item, part, element = action.data().split(",")
        self.setViewSettings(item, part, element)

    def setViewSettings(self, item, part, element):
        settings.viewSettings[item][part] = element
        if item == "Cork":
            self.mainEditor.updateCorkView()
        if item == "Outline":
            self.mainEditor.updateTreeView()
            self.treeOutlineOutline.viewport().update()
        if item == "Tree":
            self.treeRedacOutline.viewport().update()

    ###############################################################################
    # COMPILE
    ###############################################################################

    def doCompile(self):
        self.compileDialog = compileDialog()
        self.compileDialog.show()
Ejemplo n.º 14
0
class PlotWidget(QMainWindow):
    def __init__(self, window, genotypes, fitness, forces, bridge, fitness_graph=None, threshold_graph=None):
        super().__init__(window)

        self.genotypes = genotypes
        self.fitness = fitness
        self.forces = forces
        self.bridge = bridge
        self.threshold_graph = threshold_graph
        self.fitness_graph = fitness_graph
        self.cached_canvas = [None]*len(genotypes)
        self.current_fig = None
        self.current_toolbar = None
        self.current_canvas = None

        toolbar = QToolBar("Individuals")
        self.addToolBar(Qt.BottomToolBarArea, toolbar)

        self.actiongroup = QActionGroup(self)

        for i in range(len(self.genotypes)):
            action = QAction(QIcon(), str(i+1), self)
            action.setCheckable(True)
            self.actiongroup.addAction(action)

        self.actiongroup.triggered.connect(self.plot_genotype)
        self.boxlayout = QVBoxLayout()

        toolbar.addActions(self.actiongroup.actions())
        self.actiongroup.setExclusive(True)
        self.actiongroup.actions()[0].activate(QAction.Trigger)
        # self.plot(0)

    @pyqtSlot(QAction)
    def plot_genotype(self, action: QAction):
        idx = self.actiongroup.actions().index(action)
        self.plot(idx)

    def onpick(self, event):
        self.plot(event.ind[0])
        self.actiongroup.actions()[event.ind[0]].activate(QAction.Trigger)

    def plot(self, index: int):
        if self.current_toolbar is not None:
            plt.close(self.current_fig)
            self.removeToolBar(self.current_toolbar)
            self.current_toolbar = None

        statically_correct = True

        (x, det, MFmat, RFmat) = self.forces[index]

        if x is None or x != 0 or det == 0:
            statically_correct = False

        b = self.bridge
        b.setGenotype(self.genotypes[index])
        genotype = b.genotype
        supports = b.supports
        nodes = b.nodes
        members = b.members
        materials = b.materials
        loads = b.loads

        force, cost, length = self.fitness[index]

        tmat = np.transpose(nodes[members], axes=(0, 2, 1))
        submat = np.subtract.reduce(tmat, axis=2)
        member_lengths = np.hypot.reduce(submat, axis=1, dtype=float)
        max_member_lengths = np.array([MAX_MEMBER_LENGTHS[
            int(m)] for m in materials]).reshape(-1, 1)

        rows = int(self.fitness_graph is not None) * 100
        fig = plt.figure(figsize=plt.figaspect(
            0.5 if self.fitness_graph is not None else 0.33))
        # fig, axs = plt.subplots(rows, 1, figsize=figsize)

        self.current_fig = fig
        canvas = FigureCanvas(fig)

        ax = plt.subplot(121 + rows)
        material_labels = ["Street", "Wood", "Steel"]
        colors = ["black", "peru", "brown"]
        cmap = ListedColormap(colors, name="materials")
        norm = col.Normalize(vmin=0, vmax=2)

        for i in range(len(members[:, 0])):
            ls = ":" if member_lengths[i] > max_member_lengths[i] else "-"
            c = cmap(norm(int(materials[i, 0])))
            l = material_labels[int(materials[i])]

            ax.plot(nodes[members[i, :], 0],
                    nodes[members[i, :], 1], linestyle=ls, color=c, label=l, lw=5)

        def legend_without_duplicate_labels(ax):
            handles, labels = ax.get_legend_handles_labels()
            unique = [(h, l) for i, (h, l) in enumerate(
                zip(handles, labels)) if l not in labels[:i]]
            ax.legend(*zip(*unique))

        mask = np.ones(nodes.shape[0], dtype=bool)
        mask[supports[:, 0].ravel()] = 0
        _nodes = nodes[mask].reshape(-1, 2)
        _supports = nodes[np.invert(mask)].reshape(-1, 2)

        ax.scatter(_nodes[:, 0], _nodes[:, 1], s=100,
                   zorder=3, color="w", edgecolors="black")
        ax.scatter(_supports[:, 0], _supports[:, 1], s=100, marker="s",
                   zorder=3, color="w", edgecolors="black")

        ax.set_aspect('equal', adjustable='box')

        plt.grid()

        if statically_correct:
            ax = plt.subplot(122 + rows)

            cmap = plt.get_cmap('coolwarm')
            abs_val = np.abs(MFmat)
            max_abs = np.max(abs_val)
            norm = col.Normalize(vmin=-max_abs, vmax=max_abs)

            for i in range(len(members[:, 0])):
                f = MFmat[i, 0]  # TODO out of bounds
                ls = ":" if member_lengths[i] > max_member_lengths[i] else "-"

                ax.plot(nodes[members[i, :], 0],
                        nodes[members[i, :], 1], linestyle=ls, color=cmap(norm(f)), lw=5)
                _nodes = nodes[members[i, :]]
                center = (np.sum(_nodes, axis=0)/2.0).reshape(2, -1)

                if np.abs(f) < 1e-12:
                    c = "k"
                elif f < 0:
                    c = "blue"
                else:
                    c = "red"

                ax.text(*center, "{:.1f}".format(f),
                        backgroundcolor="w", color=c, weight="medium")

            mask = np.ones(nodes.shape[0], dtype=bool)
            mask[supports[:, 0].ravel()] = 0
            _nodes = nodes[mask].reshape(-1, 2)
            _supports = nodes[np.invert(mask)].reshape(-1, 2)

            ax.scatter(_nodes[:, 0], _nodes[:, 1], s=100,
                       zorder=3, color="w", edgecolors="black")
            ax.scatter(_supports[:, 0], _supports[:, 1], s=100, marker="s",
                       zorder=3, color="w", edgecolors="black")
            # plt.colorbar(cm.ScalarMappable(norm=norm, cmap=cmap), ax=ax)
            ax2_divider = make_axes_locatable(ax)
            cax2 = ax2_divider.append_axes("right", size="7%", pad="2%")
            cb2 = fig.colorbar(cm.ScalarMappable(
                norm=norm, cmap=cmap), cax=cax2, orientation="vertical")
            # cax2.xaxis.set_ticks_position("right")

            ax.set_aspect('equal', adjustable='box')

            fig.suptitle(
                "Forces: {:0.5f}, Costs: {:0.5f}, Lenghts: {:0.5f}".format(force, cost, length))

        if self.fitness_graph:
            if len(self.fitness) > 1:
                ax = plt.subplot(124 + rows, projection='3d')
                X = self.fitness

                face_col = normalize(X, norm="max", axis=0)

                ax.scatter(X[:, 0], X[:, 1], zs=X[:, 2],
                           facecolors=face_col,  marker="o", picker=True)
                ax.set_xlabel("Forces")
                ax.set_ylabel("Costs")
                ax.set_zlabel("Lengths")

                ax = plt.subplot(123 + rows)
            else:
                ax = plt.subplot(112 + rows)

            # ax.set_yscale("log")
            color = 'tab:blue'
            ax.plot(range(len(self.fitness_graph)),
                    self.fitness_graph, label="Best Fitness", color=color)
            ax.set_ylabel("Fitness", color=color)
            ax.set_xlabel("Iterations")
            ax.tick_params(axis='y', labelcolor=color)
            ax.grid()

            if self.threshold_graph:
                ax = ax.twinx()  # instantiate a second axes that shares the same x-axis

                color = "tab:orange"
                # we already handled the x-label with ax1
                ax.set_ylabel('Threshold', color=color)
                ax.plot(range(len(self.threshold_graph)),
                        self.threshold_graph, label="Accept", color=color)
                ax.tick_params(axis='y', labelcolor=color)

        fig.tight_layout(pad=0.4, w_pad=0.5, h_pad=1.0)

        fig.canvas.mpl_connect('pick_event', self.onpick)

        self.current_fig = fig
        self.current_canvas = canvas

        self.current_toolbar = NavigationToolbar(canvas, self)
        self.current_toolbar.setObjectName("Figure Control")
        self.addToolBar(Qt.BottomToolBarArea,
                        self.current_toolbar)
        self.insertToolBarBreak(self.current_toolbar)
        self.setCentralWidget(canvas)
class StripRacksPane(QWidget, Ui_stripPane):
    view_modes = DIVIDE, SCROLL, TABBED = range(
        3)  # NB: order matters for menu action descriptions
    windowClosing = pyqtSignal()  # workspace-level signal

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.setupUi(self)
        self.pull_menuButton.setIcon(QIcon(IconFile.pixmap_strip))
        self.deleteStrip_button.setIcon(QIcon(IconFile.button_bin))
        self.rack_filter_model = RackedStripsFilterModel(self)
        self.stripRacks_view.setModel(self.rack_filter_model)
        self.current_view_mode = StripRacksPane.DIVIDE
        self.updateViewFromMode()
        # PULL menu
        self.pullFplDetails_action = QAction('Pull FPL details', self)
        self.pullXpdrDetails_action = QAction('Pull XPDR details', self)
        pull_menu = QMenu()
        pull_menu.addAction(self.pullFplDetails_action)
        pull_menu.addAction(self.pullXpdrDetails_action)
        self.pullFplDetails_action.triggered.connect(
            lambda: pull_FPL_details(self))
        self.pullXpdrDetails_action.triggered.connect(
            lambda: pull_XPDR_details(self))
        self.pull_menuButton.setMenu(pull_menu)
        # VIEW menu
        createNewRack_action = QAction('New rack...', self)
        createNewRack_action.setIcon(QIcon(IconFile.action_newRack))
        createNewRack_action.triggered.connect(self.createNewRack)
        moveRacks_action = QAction('Bring racks to this view...', self)
        moveRacks_action.triggered.connect(self.moveRacksToView)
        self.view_mode_action_group = QActionGroup(self)
        for mode, txt in zip(
                StripRacksPane.view_modes,
            ['Divided width', 'Horizontal scroll', 'Tabbed racks']):
            view_action = QAction(txt, self)
            view_action.setCheckable(True)
            view_action.triggered.connect(
                lambda toggle, m=mode: self.selectViewMode(m))
            self.view_mode_action_group.addAction(view_action)
            if mode == self.current_view_mode:
                view_action.setChecked(True)
        view_menu = QMenu()
        view_menu.addAction(createNewRack_action)
        view_menu.addAction(moveRacks_action)
        view_menu.addSeparator()
        view_menu.addActions(self.view_mode_action_group.actions())
        self.view_menuButton.setMenu(view_menu)
        # Other buttons and signals
        self.updateButtonsAndActions()
        self.deleteStrip_button.clicked.connect(
            lambda: discard_strip(self, selection.strip, False))
        self.shelf_button.stripDropped.connect(
            lambda strip: discard_strip(self, strip, True))
        self.shelf_button.clicked.connect(signals.openShelfRequest.emit)
        # External signals below. CAUTION: these must all be disconnected on widget deletion
        env.strips.columnsRemoved.connect(self.updateAfterRackDeletion)
        signals.rackEdited.connect(self.updateAfterRackEdit)
        signals.rackVisibilityTaken.connect(self.hideRacks)
        signals.selectionChanged.connect(self.updateSelections)
        signals.selectionChanged.connect(self.updateButtonsAndActions)
        signals.mainWindowClosing.connect(self.close)

    def getViewRacks(self):
        '''
		returns the racks currently shown in the panel, in visible order (whether tabbed or in columns)
		'''
        if self.stacked_view_widget.currentWidget() is self.tableView_page:
            hh = self.stripRacks_view.horizontalHeader()
            return [
                self.rack_filter_model.rackName(hh.logicalIndex(i))
                for i in range(hh.count())
            ]
        else:
            return [
                w.singleRackFilter() for w in self.stripRacks_tabs.rackTabs()
            ]

    def setViewRacks(self, racks):
        self.rack_filter_model.setRackFilter(racks)
        hh = self.stripRacks_view.horizontalHeader()
        for vis, rack in enumerate(racks):
            try:
                curr_vis = next(i for i in range(vis + 1, hh.count())
                                if self.rack_filter_model.rackName(
                                    hh.logicalIndex(i)) == rack)
                hh.moveSection(curr_vis, vis)
            except StopIteration:
                pass
        self.stripRacks_tabs.setTabs(racks)
        self.updateViewFromMode()

    ## GUI UPDATES

    def updateAfterRackEdit(self, old_name, new_name):
        if new_name != old_name:
            self.rack_filter_model.updateRackFilter({old_name: new_name})
            self.stripRacks_tabs.updateTabName(old_name, new_name)
        self.stripRacks_tabs.updateTabIcons()
        self.updateSelections()

    def updateAfterRackDeletion(self):
        self.rack_filter_model.updateRackFilter({})
        still_existing = env.strips.rackNames()
        self.stripRacks_tabs.setTabs(
            [r for r in self.getViewRacks() if r in still_existing])
        self.updateViewFromMode()
        self.updateSelections()

    def updateSelections(self):
        self.stripRacks_view.updateSelection()
        self.stripRacks_tabs.updateSelection()

    def updateViewFromMode(self):
        if self.current_view_mode == StripRacksPane.TABBED:
            self.stacked_view_widget.setCurrentWidget(self.tabView_page)
            self.stripRacks_tabs.updateTabIcons()
        else:
            self.stacked_view_widget.setCurrentWidget(self.tableView_page)
            self.stripRacks_view.setDivideHorizWidth(
                self.current_view_mode == StripRacksPane.DIVIDE)

    def updateButtonsAndActions(self):
        self.pull_menuButton.setEnabled(selection.strip != None)
        self.pullXpdrDetails_action.setEnabled(
            selection.strip != None
            and selection.strip.linkedAircraft() != None)
        self.pullFplDetails_action.setEnabled(
            selection.strip != None and selection.strip.linkedFPL() != None)
        self.deleteStrip_button.setEnabled(
            selection.strip != None
            and selection.strip.lookup(rack_detail) != None)

    def hideRacks(self, racks):
        self.setViewRacks([r for r in self.getViewRacks() if r not in racks])

    ## ACTIONS

    def createNewRack(self):
        i = 1
        new_rack_name = 'Rack 1'
        while not env.strips.validNewRackName(new_rack_name):
            i += 1
            new_rack_name = 'Rack %d' % i
        env.strips.addRack(new_rack_name)
        self.setViewRacks(self.getViewRacks() + [new_rack_name])

    def moveRacksToView(self):
        available_racks = [
            r for r in env.strips.rackNames() if r not in self.getViewRacks()
        ]
        dialog = RackVisibilityDialog(available_racks, parent=self)
        dialog.exec()
        if dialog.result() > 0:
            new_rack_visibility = self.getViewRacks() + dialog.getSelection()
            signals.rackVisibilityTaken.emit(new_rack_visibility)
            self.setViewRacks(new_rack_visibility)

    def selectViewMode(self, view_mode):
        rack_order_to_replicate = self.getViewRacks(
        )  # views not necessarily in sync if sections/tabs were moved
        self.current_view_mode = view_mode
        self.updateViewFromMode()
        self.setViewRacks(rack_order_to_replicate)

    ## SAVED STATES

    def stateSave(self):
        return {
            'view_mode':
            str(self.current_view_mode),
            'visible_racks':
            ','.join(
                str(env.strips.rackIndex(r)) for r in self.getViewRacks())
        }

    def restoreState(self, saved_state):
        try:
            view_mode = int(saved_state['view_mode'])
            view_mode_action = self.view_mode_action_group.actions()[view_mode]
            view_mode_action.setChecked(True)
            view_mode_action.trigger()
        except (KeyError, IndexError, ValueError):
            pass  # missing or invalid view mode state attr
        try:
            racks = [
                env.strips.rackName(int(ir))
                for ir in saved_state['visible_racks'].split(',')
            ]
            signals.rackVisibilityTaken.emit(racks)
            self.setViewRacks(racks)
        except KeyError:
            pass  # no visible racks saved
        except (IndexError, ValueError):
            pass  # bad int list value

    ## CLOSING

    def closeEvent(self, event):
        env.strips.columnsRemoved.disconnect(self.updateAfterRackDeletion)
        signals.mainWindowClosing.disconnect(self.close)
        signals.selectionChanged.disconnect(self.updateButtonsAndActions)
        signals.selectionChanged.disconnect(self.updateSelections)
        signals.rackEdited.disconnect(self.updateAfterRackEdit)
        signals.rackVisibilityTaken.disconnect(self.hideRacks)
        event.accept()
        signals.rackVisibilityLost.emit(self.getViewRacks())
        self.windowClosing.emit()
        QWidget.closeEvent(self, event)
Ejemplo n.º 16
0
class Gui(QMainWindow, Ui_MainWindow):
    NOTEON = 0x9
    NOTEOFF = 0x8
    MIDICTRL = 11

    GREEN = ("#cell_frame { border: 0px; border-radius: 10px; "
             "background-color: rgb(125,242,0);}")
    BLUE = ("#cell_frame { border: 0px; border-radius: 10px; "
            "background-color: rgb(0, 130, 240);}")
    RED = ("#cell_frame { border: 0px; border-radius: 10px; "
           "background-color: rgb(255, 21, 65);}")
    AMBER = ("#cell_frame { border: 0px; border-radius: 10px; "
             "background-color: rgb(255, 102, 0);}")
    PURPLE = ("#cell_frame { border: 0px; border-radius: 10px; "
              "background-color: rgb(130, 0, 240);}")
    DEFAULT = ("#cell_frame { border: 0px; border-radius: 10px; "
               "background-color: rgb(217, 217, 217);}")

    RECORD_BLINK = ("QPushButton {background-color: rgb(255, 255, 255);}"
                    "QPushButton:pressed {background-color: "
                    "rgb(98, 98, 98);}")

    RECORD_DEFAULT = ("QPushButton {background-color: rgb(0, 0, 0);}"
                      "QPushButton:pressed {background-color: "
                      "rgb(98, 98, 98);}")

    STATE_COLORS = {Clip.STOP: RED,
                    Clip.STARTING: GREEN,
                    Clip.START: GREEN,
                    Clip.STOPPING: RED,
                    Clip.PREPARE_RECORD: AMBER,
                    Clip.RECORDING: AMBER}
    STATE_BLINK = {Clip.STOP: False,
                   Clip.STARTING: True,
                   Clip.START: False,
                   Clip.STOPPING: True,
                   Clip.PREPARE_RECORD: True,
                   Clip.RECORDING: False}

    BLINK_DURATION = 200
    PROGRESS_PERIOD = 300

    ADD_PORT_LABEL = 'Add new Port...'

    updateUi = pyqtSignal()
    readQueueIn = pyqtSignal()
    updatePorts = pyqtSignal()
    songLoad = pyqtSignal()

    def __init__(self, song, jack_client):
        QObject.__init__(self)
        super(Gui, self).__init__()
        self._jack_client = jack_client
        self.setupUi(self)
        self.clip_volume.knobRadius = 3
        self.is_learn_device_mode = False
        self.queue_out, self.queue_in = Queue(), Queue()
        self.updateUi.connect(self.update)
        self.readQueueIn.connect(self.readQueue)
        self.current_vol_block = 0
        self.last_clip = None

        # Load devices
        self.deviceGroup = QActionGroup(self.menuDevice)
        self.devices = []
        device_settings = QSettings('superboucle', 'devices')
        if ((device_settings.contains('devices')
             and device_settings.value('devices'))):
            for raw_device in device_settings.value('devices'):
                self.devices.append(Device(pickle.loads(raw_device)))
        else:
            self.devices.append(Device({'name': 'No Device',}))
        self.updateDevices()
        self.deviceGroup.triggered.connect(self.onDeviceSelect)

        self.settings = QSettings('superboucle', 'session')
        # Qsetting appear to serialize empty lists as @QInvalid
        # which is then read as None :(

        # Load playlist
        self.playlist = self.settings.value('playlist', []) or []
        # Load paths
        self.paths_used = self.settings.value('paths_used', {})

        self.auto_connect = self.settings.value('auto_connect',
                                                'true') == "true"

        # Load song
        self.port_by_name = {}
        self.initUI(song)

        self.actionNew.triggered.connect(self.onActionNew)
        self.actionOpen.triggered.connect(self.onActionOpen)
        self.actionSave.triggered.connect(self.onActionSave)
        self.actionSave_As.triggered.connect(self.onActionSaveAs)
        self.actionAdd_Device.triggered.connect(self.onAddDevice)
        self.actionManage_Devices.triggered.connect(self.onManageDevice)
        self.actionPlaylist_Editor.triggered.connect(self.onPlaylistEditor)
        self.actionScene_Manager.triggered.connect(self.onSceneManager)
        self.actionPort_Manager.triggered.connect(self.onPortManager)
        self.actionFullScreen.triggered.connect(self.onActionFullScreen)
        self.master_volume.valueChanged.connect(self.onMasterVolumeChange)
        self.bpm.valueChanged.connect(self.onBpmChange)
        self.beat_per_bar.valueChanged.connect(self.onBeatPerBarChange)
        self.rewindButton.clicked.connect(self.onRewindClicked)
        self.playButton.clicked.connect(self._jack_client.transport_start)
        self.pauseButton.clicked.connect(self._jack_client.transport_stop)
        self.gotoButton.clicked.connect(self.onGotoClicked)
        self.recordButton.clicked.connect(self.onRecord)
        self.clip_name.textChanged.connect(self.onClipNameChange)
        self.clip_volume.valueChanged.connect(self.onClipVolumeChange)
        self.beat_diviser.valueChanged.connect(self.onBeatDiviserChange)
        self.output.activated.connect(self.onOutputChange)
        self.mute_group.valueChanged.connect(self.onMuteGroupChange)
        self.frame_offset.valueChanged.connect(self.onFrameOffsetChange)
        self.beat_offset.valueChanged.connect(self.onBeatOffsetChange)
        self.revertButton.clicked.connect(self.onRevertClip)
        self.normalizeButton.clicked.connect(self.onNormalizeClip)
        self.exportButton.clicked.connect(self.onExportClip)
        self.deleteButton.clicked.connect(self.onDeleteClipClicked)

        self.blktimer = QTimer()
        self.blktimer.state = False
        self.blktimer.timeout.connect(self.toggleBlinkButton)
        self.blktimer.start(self.BLINK_DURATION)

        self.disptimer = QTimer()
        self.disptimer.start(self.PROGRESS_PERIOD)
        self.disptimer.timeout.connect(self.updateProgress)

        self._jack_client.set_timebase_callback(self.timebase_callback)
        self.show()

    def initUI(self, song):

        # remove old buttons
        self.btn_matrix = [[None for y in range(song.height)]
                           for x in range(song.width)]
        self.state_matrix = [[-1 for y in range(song.height)]
                             for x in range(song.width)]

        for i in reversed(range(self.gridLayout.count())):
            self.gridLayout.itemAt(i).widget().close()
            self.gridLayout.itemAt(i).widget().setParent(None)

        # first pass without removing old ports
        self.updateJackPorts(song, remove_ports=False)
        self.song = song
        # second pass with removing
        self.updateJackPorts(song, remove_ports=True)

        self.frame_clip.setEnabled(False)
        self.output.clear()
        self.output.addItems(song.outputsPorts)
        self.output.addItem(Gui.ADD_PORT_LABEL)
        self.master_volume.setValue(song.volume * 256)
        self.bpm.setValue(song.bpm)
        self.beat_per_bar.setValue(song.beat_per_bar)
        for x in range(song.width):
            for y in range(song.height):
                clip = song.clips_matrix[x][y]
                cell = Cell(self, clip, x, y)
                self.btn_matrix[x][y] = cell
                self.gridLayout.addWidget(cell, y, x)

        # send init command
        for init_cmd in self.device.init_command:
            self.queue_out.put(init_cmd)

        self.setWindowTitle("Super Boucle - {}"
                            .format(song.file_name or "Empty Song"))

        if self.song.initial_scene in self.song.scenes:
            self.song.loadScene(self.song.initial_scene)
        self.update()
        self.songLoad.emit()

        timer = QTimer()
        timer.singleShot(1000,self.send_clip_state_feedback)

    def openSongFromDisk(self, file_name):
        self._jack_client.transport_stop()
        self._jack_client.transport_locate(0)

        self.setEnabled(False)
        message = QMessageBox(self)
        message.setWindowTitle("Loading ....")
        message.setText("Reading Files, please wait ...")
        message.show()
        self.initUI(load_song_from_file(file_name))
        message.close()
        self.setEnabled(True)

    def closeEvent(self, event):
        device_settings = QSettings('superboucle', 'devices')
        device_settings.setValue('devices',
                                 [pickle.dumps(x.mapping)
                                  for x in self.devices])
        self.settings.setValue('playlist', self.playlist)
        self.settings.setValue('paths_used', self.paths_used)
        self.settings.setValue('auto_connect', self.auto_connect)

    def onStartStopClicked(self):
        clip = self.sender().parent().parent().clip
        self.startStop(clip.x, clip.y)

    def startStop(self, x, y):
        clip = self.btn_matrix[x][y].clip
        if clip is None:
            return
        if self.song.is_record:
            self.song.is_record = False
            self.updateRecordBtn()
            # calculate buffer size
            state, position = self._jack_client.transport_query()
            bps = position['beats_per_minute'] / 60
            fps = position['frame_rate']
            size = (1 / bps) * clip.beat_diviser * fps
            self.song.init_record_buffer(clip, 2, size, fps)
            # set frame offset based on jack block size
            clip.frame_offset = self._jack_client.blocksize
            clip.state = Clip.PREPARE_RECORD
            self.recordButton.setStyleSheet(self.RECORD_DEFAULT)
        else:
            self.song.toggle(clip.x, clip.y)
        self.update()

    def onEdit(self):
        self.last_clip = self.sender().parent().parent().clip
        if self.last_clip:
            self.frame_clip.setEnabled(True)
            self.clip_name.setText(self.last_clip.name)
            self.frame_offset.setValue(self.last_clip.frame_offset)
            self.beat_offset.setValue(self.last_clip.beat_offset)
            self.beat_diviser.setValue(self.last_clip.beat_diviser)
            self.output.setCurrentText(self.last_clip.output)
            self.mute_group.setValue(self.last_clip.mute_group)
            self.clip_volume.setValue(self.last_clip.volume * 256)
            state, position = self._jack_client.transport_query()
            fps = position['frame_rate']
            bps = self.bpm.value() / 60
            if self.bpm.value() and fps:
                size_in_beat = (bps / fps) * self.song.length(self.last_clip)
            else:
                size_in_beat = "No BPM info"
            clip_description = ("Size in sample : %s\nSize in beat : %s"
                                % (self.song.length(self.last_clip),
                                   round(size_in_beat, 1)))

            self.clip_description.setText(clip_description)

    def onAddClipClicked(self):
        cell = self.sender().parent().parent()
        if QApplication.keyboardModifiers() == Qt.ControlModifier:
            cell.setClip(cell.openClip())
        else:
            AddClipDialog(self, cell)

    def onRevertClip(self):
        if self.last_clip and self.last_clip.audio_file:
            audio_file = self.last_clip.audio_file
            self.song.data[audio_file] = self.song.data[audio_file][::-1]

    def onNormalizeClip(self):
        if self.last_clip and self.last_clip.audio_file:
            audio_file = self.last_clip.audio_file
            absolute_val = np.absolute(self.song.data[audio_file])
            current_level = np.ndarray.max(absolute_val)
            self.song.data[audio_file][:] *= (1 / current_level)

    def onExportClip(self):
        if self.last_clip and self.last_clip.audio_file:
            audio_file = self.last_clip.audio_file
            file_name, a = self.getSaveFileName(
                'Export Clip : %s' % self.last_clip.name, 'WAVE (*.wav)')

            if file_name:
                file_name = verify_ext(file_name, 'wav')
                sf.write(self.song.data[audio_file], file_name,
                         self.song.samplerate[audio_file],
                         subtype=sf.default_subtype('WAV'),
                         format='WAV')

    def onDeleteClipClicked(self):
        if self.last_clip:
            response = QMessageBox.question(self,
                                            "Delete Clip ?",
                                            ("Are you sure "
                                             "to delete the clip ?"))
            if response == QMessageBox.Yes:
                self.frame_clip.setEnabled(False)
                self.song.removeClip(self.last_clip)
                self.initUI(self.song)

    def onMasterVolumeChange(self):
        self.song.volume = (self.master_volume.value() / 256)

    def onBpmChange(self):
        self.song.bpm = self.bpm.value()

    def onBeatPerBarChange(self):
        self.song.beat_per_bar = self.beat_per_bar.value()

    def onGotoClicked(self):
        state, position = self._jack_client.transport_query()
        new_position = (position['beats_per_bar']
                        * (self.gotoTarget.value() - 1)
                        * position['frame_rate']
                        * (60 / position['beats_per_minute']))
        self._jack_client.transport_locate(int(round(new_position, 0)))

    def onRecord(self):
        self.song.is_record = not self.song.is_record
        self.updateRecordBtn()

    def updateRecordBtn(self):
        if not self.song.is_record:
            self.recordButton.setStyleSheet(self.RECORD_DEFAULT)
        if self.device.record_btn:
            (msg_type, channel, pitch, velocity) = self.device.record_btn
            if self.song.is_record:
                color = self.device.blink_amber_vel
            else:
                color = self.device.black_vel
            self.queue_out.put(((msg_type << 4) + channel, pitch, color))

    def onRewindClicked(self):
        self._jack_client.transport_locate(0)

    def onClipNameChange(self):
        self.last_clip.name = self.clip_name.text()
        cell = self.btn_matrix[self.last_clip.x][self.last_clip.y]
        cell.clip_name.setText(self.last_clip.name)

    def onClipVolumeChange(self):
        self.last_clip.volume = (self.clip_volume.value() / 256)

    def onBeatDiviserChange(self):
        self.last_clip.beat_diviser = self.beat_diviser.value()

    def onOutputChange(self):
        new_port = self.output.currentText()
        if new_port == Gui.ADD_PORT_LABEL:
            AddPortDialog(self)
        else:
            self.last_clip.output = new_port

    def addPort(self, name):
        self.song.outputsPorts.add(name)
        self.updateJackPorts(self.song)
        if self.output.findText(name) == -1:
            self.output.insertItem(self.output.count() - 1, name)
        if self.last_clip:
            self.last_clip.output = name
            self.output.setCurrentText(name)

    def removePort(self, name):
        if name != Clip.DEFAULT_OUTPUT:
            self.song.outputsPorts.remove(name)
            for c in self.song.clips:
                if c.output == name:
                    c.output = Clip.DEFAULT_OUTPUT
            self.updateJackPorts(self.song)
            self.output.removeItem(self.output.findText(name))
            if self.last_clip:
                self.output.setCurrentText(self.last_clip.output)

    def updateJackPorts(self, song, remove_ports=True):
        '''Update jack port based on clip output settings
        update dict containing ports with shortname as key'''

        current_ports = set()
        for port in self._jack_client.outports:
            current_ports.add(port.shortname)

        wanted_ports = set()
        for port_basename in song.outputsPorts:
            for ch in Song.CHANNEL_NAMES:
                port = Song.CHANNEL_NAME_PATTERN.format(port=port_basename,
                                                        channel=ch)
                wanted_ports.add(port)

        # remove unwanted ports
        if remove_ports:
            port_to_remove = []
            for port in self._jack_client.outports:
                if port.shortname not in wanted_ports:
                    current_ports.remove(port.shortname)
                    port_to_remove.append(port)
            for port in port_to_remove:
                port.unregister()

        # create new ports
        for new_port_name in wanted_ports - current_ports:
            self._jack_client.outports.register(new_port_name)

        self.port_by_name = {port.shortname: port
                             for port in self._jack_client.outports}

        self.updatePorts.emit()

    def onMuteGroupChange(self):
        self.last_clip.mute_group = self.mute_group.value()

    def onFrameOffsetChange(self):
        self.last_clip.frame_offset = self.frame_offset.value()

    def onBeatOffsetChange(self):
        self.last_clip.beat_offset = self.beat_offset.value()

    def onActionNew(self):
        NewSongDialog(self)

    def getOpenFileName(self, title, file_type, parent=None,
                        dialog=QFileDialog.getOpenFileName):
        path = self.paths_used.get(file_type, expanduser('~'))
        file_name, a = dialog(parent or self, title, path, file_type)
        if a and file_name:
            if isinstance(file_name, list):
                self.paths_used[file_type] = dirname(file_name[0])
            else:
                self.paths_used[file_type] = dirname(file_name)
        return file_name, a

    def getSaveFileName(self, *args):
        return self.getOpenFileName(*args, dialog=QFileDialog.getSaveFileName)

    def onActionOpen(self):
        file_name, a = self.getOpenFileName('Open Song',
                                            'Super Boucle Song (*.sbs)')
        if a and file_name:
            self.openSongFromDisk(file_name)

    def onActionSave(self):
        if self.song.file_name:
            self.song.save()
        else:
            self.onActionSaveAs()

    def onActionSaveAs(self):
        file_name, a = self.getSaveFileName('Save Song',
                                            'Super Boucle Song (*.sbs)')

        if file_name:
            file_name = verify_ext(file_name, 'sbs')
            self.song.file_name = file_name
            self.song.save()
            print("File saved to : {}".format(self.song.file_name))

    def onAddDevice(self):
        self.learn_device = LearnDialog(self, self.addDevice)
        self.is_learn_device_mode = True

    def onManageDevice(self):
        ManageDialog(self)

    def onPlaylistEditor(self):
        PlaylistDialog(self)

    def onSceneManager(self):
        SceneManager(self)

    def onPortManager(self):
        PortManager(self)

    def onActionFullScreen(self):
        if self.isFullScreen():
            self.showNormal()
        else:
            self.showFullScreen()
        self.show()

    def send_clip_state_feedback(self):
        for x in range(self.song.width):
            for y in range(self.song.height):
                clip = self.song.clips_matrix[x][y]
                state = clip.state if clip else None
                self._update_clip_state(x, y, state)

    def _update_clip_state(self, x, y, state):
        clip = self.song.clips_matrix[x][y]
        if clip:
            self.btn_matrix[x][y].setColor(state)
        try:
            self.queue_out.put(self.device.generateNote(x, y, state))
        except IndexError:
            # print("No cell associated to %s x %s"
            # % (clp.x, clp.y))
            pass
        self.state_matrix[x][y] = state

    def update(self):
        for x in range(len(self.song.clips_matrix)):
            line = self.song.clips_matrix[x]
            for y in range(len(line)):
                clp = line[y]
                if clp is None:
                    state = None
                else:
                    state = clp.state
                if state != self.state_matrix[x][y]:
                    self._update_clip_state(x, y, state)

    def redraw(self):
        self.state_matrix = [[-1 for x in range(self.song.height)]
                             for x in range(self.song.width)]
        self.update()

    def readQueue(self):
        try:
            while True:
                note = self.queue_in.get(block=False)
                if len(note) == 3:
                    status, pitch, vel = struct.unpack('3B', note)
                    channel = status & 0xF
                    msg_type = status >> 4
                    self.processNote(msg_type, channel, pitch, vel)
                    # else:
                    # print("Invalid message length")
        except Empty:
            pass

    def processNote(self, msg_type, channel, pitch, vel):

        btn_id = (msg_type,
                  channel,
                  pitch,
                  vel)
        btn_id_vel = (msg_type, channel, pitch, -1)
        ctrl_key = (msg_type, channel, pitch)

        # master volume
        if ctrl_key == self.device.master_volume_ctrl:
            self.song.master_volume = vel / 127
            (self.master_volume
             .setValue(self.song.master_volume * 256))
        elif self.device.play_btn in [btn_id, btn_id_vel]:
            self._jack_client.transport_start()
        elif self.device.pause_btn in [btn_id, btn_id_vel]:
            self._jack_client.transport_stop()
        elif self.device.rewind_btn in [btn_id, btn_id_vel]:
            self.onRewindClicked()
        elif self.device.goto_btn in [btn_id, btn_id_vel]:
            self.onGotoClicked()
        elif self.device.record_btn in [btn_id, btn_id_vel]:
            self.onRecord()
        elif ctrl_key in self.device.ctrls:
            try:
                ctrl_index = self.device.ctrls.index(ctrl_key)
                clip = (self.song.clips_matrix
                        [ctrl_index]
                        [self.current_vol_block])
                if clip:
                    clip.volume = vel / 127
                    if self.last_clip == clip:
                        self.clip_volume.setValue(self.last_clip.volume * 256)
            except KeyError:
                pass
        elif (btn_id in self.device.scene_buttons
              or btn_id_vel in self.device.scene_buttons):
            try:
                scene_id = self.device.scene_buttons.index(btn_id)
            except ValueError:
                scene_id = self.device.scene_buttons.index(btn_id_vel)

            try:
                self.song.loadSceneId(scene_id)
                self.update()
            except IndexError:
                print('cannot load scene {} - there are only {} scenes.'
                      ''.format(scene_id, len(self.song.scenes)))

        elif (btn_id in self.device.block_buttons
              or btn_id_vel in self.device.block_buttons):
            try:
                self.current_vol_block = (
                    self.device.block_buttons.index(btn_id))
            except ValueError:
                self.current_vol_block = (
                    self.device.block_buttons.index(btn_id_vel))
            for i in range(len(self.device.block_buttons)):
                (a, b_channel, b_pitch, b) = self.device.block_buttons[i]
                if i == self.current_vol_block:
                    color = self.device.red_vel
                else:
                    color = self.device.black_vel
                self.queue_out.put(((self.NOTEON << 4) + b_channel,
                                    b_pitch,
                                    color))
        else:
            x, y = -1, -1
            try:
                x, y = self.device.getXY(btn_id)
            except IndexError:
                pass
            except KeyError:
                try:
                    x, y = self.device.getXY(btn_id_vel)
                except KeyError:
                    pass

            if (x >= 0 and y >= 0):
                self.startStop(x, y)

    def toggleBlinkButton(self):
        for line in self.btn_matrix:
            for btn in line:
                if btn.blink:
                    if self.blktimer.state:
                        btn.setStyleSheet(btn.color)
                    else:
                        btn.setStyleSheet(self.DEFAULT)
        if self.song.is_record:
            if self.blktimer.state:
                self.recordButton.setStyleSheet(self.RECORD_BLINK)
            else:
                self.recordButton.setStyleSheet(self.RECORD_DEFAULT)

        self.blktimer.state = not self.blktimer.state

    def updateProgress(self):
        state, pos = self._jack_client.transport_query()
        if 'bar' in pos:
            bbt = "%d|%d|%03d" % (pos['bar'], pos['beat'], pos['tick'])
        else:
            bbt = "-|-|-"
        seconds = int(pos['frame'] / pos['frame_rate'])
        (minutes, second) = divmod(seconds, 60)
        (hour, minute) = divmod(minutes, 60)
        time = "%d:%02d:%02d" % (hour, minute, second)
        self.bbtLabel.setText("%s\n%s" % (bbt, time))
        for line in self.btn_matrix:
            for btn in line:
                if btn.clip and btn.clip.audio_file:
                    value = ((btn.clip.last_offset
                              / self.song.length(btn.clip))
                             * 97)
                    btn.clip_position.setValue(value)
                    btn.clip_position.repaint()

    def updateDevices(self):
        for action in self.deviceGroup.actions():
            self.deviceGroup.removeAction(action)
            self.menuDevice.removeAction(action)
        for device in self.devices:
            action = QAction(device.name, self.menuDevice)
            action.setCheckable(True)
            action.setData(device)
            self.menuDevice.addAction(action)
            self.deviceGroup.addAction(action)
        action.setChecked(True)
        self.device = device

    def addDevice(self, device):
        self.devices.append(device)
        self.updateDevices()
        self.is_learn_device_mode = False

    def onDeviceSelect(self):
        self.device = self.deviceGroup.checkedAction().data()
        if self.device:
            if self.device.init_command:
                for note in self.device.init_command:
                    self.queue_out.put(note)
            self.redraw()

    def timebase_callback(self, state, nframes, pos, new_pos):
        if pos.frame_rate == 0:
            return None
        pos.valid = 0x10
        pos.bar_start_tick = BAR_START_TICK
        pos.beats_per_bar = self.beat_per_bar.value()
        pos.beat_type = BEAT_TYPE
        pos.ticks_per_beat = TICKS_PER_BEAT
        pos.beats_per_minute = self.bpm.value()
        ticks_per_second = (pos.beats_per_minute *
                            pos.ticks_per_beat) / 60
        ticks = (ticks_per_second * pos.frame) / pos.frame_rate
        (beats, pos.tick) = divmod(int(round(ticks, 0)),
                                   int(round(pos.ticks_per_beat, 0)))
        (bar, beat) = divmod(beats, int(round(pos.beats_per_bar, 0)))
        (pos.bar, pos.beat) = (bar + 1, beat + 1)
        return None
Ejemplo n.º 17
0
class MainWindow(QMainWindow, Ui_MainWindow):

    def __init__(self):
        QMainWindow.__init__(self)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.setWindowIcon(QIcon('icon.png'))

        self.ui.actionLoad_Lyrics.triggered.connect(self._load_lyrics_thread)
        self.cb_backends()

        # about widget
        self.aboutUi = About()
        self.ui.actionAbout.triggered.connect(self._about)
        self.aboutUi.setWindowModality(Qt.WindowModal)

        self._load_lyrics_thread()

    def cb_backends(self):
        self.cb_backends = QComboBox()

        self.cb_backends.addItem('Auto')

        menuLyricSource = QMenu(self.ui.menuEdit)
        menuLyricSource.setTitle('Lyric source')

        self.lyricGroup = QActionGroup(self)

        def addAction(name, checked=False):
            action = QAction(name, self)
            action.setText(name)
            action.setCheckable(True)
            action.setChecked(checked)
            action.setActionGroup(self.lyricGroup)

            return action

        menuLyricSource.addAction(addAction('Auto', True))
        menuLyricSource.addSeparator()
        menuLyricSource.triggered.connect(self._menu_backend_change)

        for backend in Pyrics.get_backends():
            menuLyricSource.addAction(addAction(backend.__name__))
            self.cb_backends.addItem(backend.__name__)

        self.ui.menuEdit.addMenu(menuLyricSource)
        self.ui.toolBar.addWidget(self.cb_backends)

        self.cb_backends.currentIndexChanged.connect(self._cb_backend_change)

    def _load_lyrics_thread(self):
        self.thread = LoadLyricsWorker(self.cb_backends.currentIndex(), self)

        self.thread.trigger.connect(self._load_lyrics)
        self.thread.start()

    def _load_lyrics(self, content):
        self.ui.txLyrics.setText(content)

    def _about(self):
        self.aboutUi.show()

    def _menu_backend_change(self, action):
        index = self.cb_backends.findText(action.text())
        self._update_backend(index)

    def _cb_backend_change(self, item):
        self._update_backend(item)

    def _update_backend(self, index):
        """Keep lyrics source combo in sync with the lyrics source in the menu"""
        if index >= 0:
            self.cb_backends.setCurrentIndex(index)
            name = self.cb_backends.currentText()

            for action in self.lyricGroup.actions():
                action.setChecked(False)
                if action.text() == name:
                    action.setChecked(True)
Ejemplo n.º 18
0
    def __create_toolbars_and_menus(self):
        """Create the toolbars and menus."""
        toolbar_weeks = QToolBar(self)
        self.addToolBar(Qt.TopToolBarArea, toolbar_weeks)
        toolbar_application = QToolBar(self)
        self.addToolBar(Qt.TopToolBarArea, toolbar_application)
        self.addToolBarBreak()
        toolbar_days = QToolBar(self)
        self.addToolBar(Qt.TopToolBarArea, toolbar_days)

        days_action_group = QActionGroup(self)
        for counter, day in enumerate(WeekDay, start=1):

            if day == WeekDay.Monday:
                tr_name = self.tr('Monday')
            elif day == WeekDay.Tuesday:
                tr_name = self.tr('Tuesday')
            elif day == WeekDay.Wednesday:
                tr_name = self.tr('Wednesday')
            elif day == WeekDay.Thursday:
                tr_name = self.tr('Thursday')
            elif day == WeekDay.Friday:
                tr_name = self.tr('Friday')
            elif day == WeekDay.Saturday:
                tr_name = self.tr('Saturday')
            elif day == WeekDay.Sunday:
                tr_name = self.tr('Sunday')

            action = QAction(QIcon(':/' + day.name.lower() + '.png'), tr_name,
                             self)
            action.setObjectName(day.name)
            action.setShortcut('Alt+' + str(counter))
            action.setCheckable(True)
            action.setStatusTip(self.tr('Go to {day}').format(day=tr_name))
            action.triggered.connect(self.__change_current_day)
            days_action_group.addAction(action)
            self.day_actions[day] = action
            toolbar_days.addAction(action)

        previous_act = QAction(QIcon(':/previous.png'),
                               self.tr('Previous Week'), self)
        previous_act.setShortcut('Ctrl+P')
        previous_act.triggered.connect(self.__previous_week)
        previous_act.setStatusTip(self.tr('Go to Previous Week'))

        next_act = QAction(QIcon(':/next.png'), self.tr('Next Week'), self)
        next_act.setShortcut('Ctrl+N')
        next_act.triggered.connect(self.__next_week)
        next_act.setStatusTip(self.tr('Go to Next Week'))

        today_act = QAction(QIcon(':/today.png'), self.tr('Today'), self)
        today_act.setShortcut('Ctrl+T')
        today_act.triggered.connect(self.__today)
        today_act.setStatusTip(self.tr('Go to today'))

        about_act = QAction(QIcon(':/info.png'), self.tr('About'), self)
        about_act.triggered.connect(self.__about)
        about_act.setStatusTip(self.tr('About this application'))

        about_qt_act = QAction(self.tr('About Qt'), self)
        about_qt_act.triggered.connect(qApp.aboutQt)
        about_qt_act.setStatusTip(self.tr('About Qt'))

        settings_act = QAction(QIcon(':/settings.png'), self.tr('Preferences'),
                               self)
        settings_act.triggered.connect(self.__edit_preferences)
        settings_act.setStatusTip(self.tr('Edit preferences'))

        exit_act = QAction(QIcon(':/exit.png'), self.tr('&Quit'), self)
        exit_act.setShortcut('Ctrl+Q')
        exit_act.setStatusTip(self.tr('Quit application'))
        exit_act.triggered.connect(self.close)

        export_act = QAction(QIcon(':/export.png'), self.tr('Export'), self)
        export_act.setShortcut('Ctrl+E')
        export_act.setStatusTip(self.tr('Export week summary as html table'))
        export_act.triggered.connect(self.__export)

        toolbar_weeks.addAction(today_act)
        toolbar_weeks.addAction(previous_act)
        toolbar_weeks.addAction(next_act)
        toolbar_weeks.addAction(export_act)

        toolbar_application.addAction(exit_act)
        toolbar_application.addAction(settings_act)
        toolbar_application.addAction(about_act)

        menu_bar = self.menuBar()
        menu_bar.setNativeMenuBar(True)

        app_menu = menu_bar.addMenu(self.tr('Application'))
        app_menu.addAction(about_act)
        app_menu.addAction(about_qt_act)
        app_menu.addAction(settings_act)
        app_menu.addAction(exit_act)

        weeks_menu = menu_bar.addMenu(self.tr('Weeks'))
        weeks_menu.addAction(today_act)
        weeks_menu.addAction(previous_act)
        weeks_menu.addAction(next_act)
        weeks_menu.addAction(export_act)

        days_menu = menu_bar.addMenu(self.tr('Days'))
        for action in days_action_group.actions():
            days_menu.addAction(action)
Ejemplo n.º 19
0
class MainWindow(QMainWindow):

    sizeButtonGroup = None
    OtherSize = QStyle.PM_CustomBase
    otherSpinBox = None

    def createImagesGroupBox(self):
        imagesGroupBox = QGroupBox("Images")
        labels = ("Images", "Mode", "State")

        self.imagesTable = QTableWidget()
        self.imagesTable.setSelectionMode(QAbstractItemView.NoSelection)
        self.imagesTable.setItemDelegate(ImageDelegate(self))
        self.imagesTable.horizontalHeader().setDefaultSectionSize(90)
        self.imagesTable.setColumnCount(3)
        self.imagesTable.setHorizontalHeaderLabels(labels)
        self.imagesTable.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.Stretch)
        self.imagesTable.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.Fixed)
        self.imagesTable.horizontalHeader().setSectionResizeMode(
            2, QHeaderView.Fixed)
        self.imagesTable.verticalHeader().hide()
        self.imagesTable.itemChanged.connect(self.changeIcon)

        layout = QVBoxLayout(imagesGroupBox)
        layout.addWidget(self.imagesTable)
        return imagesGroupBox

    def createIconSizeGroupBox(self):
        iconSizeGroupBox = QGroupBox("Icon Size")
        self.sizeButtonGroup = QButtonGroup(iconSizeGroupBox)
        self.sizeButtonGroup.setExclusive(True)

        self.sizeButtonGroup.buttonToggled.connect(self.triggerChangeSize)

        smallRadioButton = QRadioButton()
        smallRadioButton.setChecked(True)
        self.sizeButtonGroup.addButton(smallRadioButton,
                                       QStyle.PM_SmallIconSize)
        largeRadioButton = QRadioButton()
        self.sizeButtonGroup.addButton(largeRadioButton,
                                       QStyle.PM_LargeIconSize)
        toolBarRadioButton = QRadioButton()
        self.sizeButtonGroup.addButton(toolBarRadioButton,
                                       QStyle.PM_ToolBarIconSize)
        iconViewRadioButton = QRadioButton()
        self.sizeButtonGroup.addButton(iconViewRadioButton,
                                       QStyle.PM_IconViewIconSize)
        listViewRadioButton = QRadioButton()
        self.sizeButtonGroup.addButton(listViewRadioButton,
                                       QStyle.PM_ListViewIconSize)
        tabBarRadioButton = QRadioButton()
        self.sizeButtonGroup.addButton(tabBarRadioButton,
                                       QStyle.PM_TabBarIconSize)
        otherRadioButton = QRadioButton("Other:")
        self.sizeButtonGroup.addButton(otherRadioButton, self.OtherSize)
        self.otherSpinBox = IconSizeSpinBox()
        self.otherSpinBox.setRange(8, 256)
        spinBoxToolTip = "Enter a custom size within {}..{}".format(
            self.otherSpinBox.minimum(), self.otherSpinBox.maximum())
        self.otherSpinBox.setValue(64)
        self.otherSpinBox.setToolTip(spinBoxToolTip)
        otherRadioButton.setToolTip(spinBoxToolTip)

        self.otherSpinBox.valueChanged.connect(self.triggerChangeSize)

        otherSizeLayout = QHBoxLayout()
        otherSizeLayout.addWidget(otherRadioButton)
        otherSizeLayout.addWidget(self.otherSpinBox)
        otherSizeLayout.addStretch()

        layout = QGridLayout(iconSizeGroupBox)
        layout.addWidget(smallRadioButton, 0, 0)
        layout.addWidget(largeRadioButton, 1, 0)
        layout.addWidget(toolBarRadioButton, 2, 0)
        layout.addWidget(listViewRadioButton, 0, 1)
        layout.addWidget(iconViewRadioButton, 1, 1)
        layout.addWidget(tabBarRadioButton, 2, 1)
        layout.addLayout(otherSizeLayout, 3, 0, 1, 2)
        layout.setRowStretch(4, 1)

        return iconSizeGroupBox

    def loadImages(self, fileNames):
        for fileName in fileNames:
            row = self.imagesTable.rowCount()
            self.imagesTable.setRowCount(row + 1)
            fileInfo = QFileInfo(fileName)
            imageName = fileInfo.baseName()
            fileImage2x = fileInfo.absolutePath(
            ) + '/' + imageName + "@2x." + fileInfo.suffix()
            fileInfo2x = QFileInfo(fileImage2x)
            image = QImage(fileName)
            toolTip = "Directory: {}\nFile: {}\nFile@2x: {}\nSize: {}x{}".format(
                QDir.toNativeSeparators(fileInfo.absolutePath()),
                fileInfo.fileName(),
                fileInfo2x.fileName() if fileInfo2x.exists else "<None>",
                image.width(), image.height())
            fileItem = QTableWidgetItem(imageName)
            fileItem.setData(Qt.UserRole, fileName)
            fileItem.setIcon(QIcon(QPixmap.fromImage(image)))
            fileItem.setFlags((fileItem.flags() | Qt.ItemIsUserCheckable)
                              & ~Qt.ItemIsEditable)
            fileItem.setToolTip(toolTip)
            self.imagesTable.setItem(row, 0, fileItem)

            mode = QIcon.Normal
            state = QIcon.Off

            if self.guessModeStateAct.isChecked():
                if "_act" in imageName:
                    mode = QIcon.Active
                elif "_dis" in imageName:
                    mode = QIcon.Disabled
                elif "_sel" in imageName:
                    mode = QIcon.Selected

                if "_on" in imageName:
                    mode = QIcon.On

            modeItem = QTableWidgetItem(IconPreviewArea.iconModeNames()[
                IconPreviewArea.iconModes().index(mode)])
            modeItem.setToolTip(toolTip)
            self.imagesTable.setItem(row, 1, modeItem)
            stateItem = QTableWidgetItem(IconPreviewArea.iconStateNames()[
                IconPreviewArea.iconStates().index(state)])
            stateItem.setToolTip(toolTip)
            self.imagesTable.setItem(row, 2, stateItem)
            self.imagesTable.openPersistentEditor(modeItem)
            self.imagesTable.openPersistentEditor(stateItem)
            fileItem.setCheckState(Qt.Checked)

    def addImages(self, directory):
        fileDialog = QFileDialog(self, "Open Images", directory)
        mimeTypeFilters = []
        for mimeTypeName in QImageReader.supportedMimeTypes():
            mimeTypeFilters.append(str(mimeTypeName))
        mimeTypeFilters.sort()
        fileDialog.setMimeTypeFilters(mimeTypeFilters)
        fileDialog.selectMimeTypeFilter('image/png')
        fileDialog.setAcceptMode(QFileDialog.AcceptOpen)
        fileDialog.setFileMode(QFileDialog.ExistingFiles)
        if not self.nativeFileDialogAct.isChecked():
            fileDialog.setOption(QFileDialog.DontUseNativeDialog)
        if fileDialog.exec_() == QDialog.Accepted:
            self.loadImages(fileDialog.selectedFiles())

    def addSampleImages(self):
        self.addImages("C:/Users/hungbn/Pictures")

    def addOtherImages(self):
        pass

    def removeAllImages(self):
        self.imagesTable.setRowCount(0)
        self.changeIcon()

    def createActions(self):
        fileMenu = self.menuBar().addMenu("&File")
        addSampleImagesAct = QAction("Add &Sample Images...", self)
        addSampleImagesAct.triggered.connect(self.addSampleImages)
        fileMenu.addAction(addSampleImagesAct)

        addOtherImagesAct = QAction("&Add Images...", self)
        addOtherImagesAct.setShortcut(QKeySequence.Open)
        addOtherImagesAct.triggered.connect(self.addOtherImages)
        fileMenu.addAction(addOtherImagesAct)

        removeAllImagesAct = QAction("&Remove All Images", self)
        removeAllImagesAct.setShortcut("CTRL+R")
        removeAllImagesAct.triggered.connect(self.removeAllImages)
        fileMenu.addAction(removeAllImagesAct)

        fileMenu.addSeparator()

        exitAct = QAction("&Quit", self)
        exitAct.triggered.connect(QWidget.close)
        exitAct.setShortcuts(QKeySequence.Quit)
        fileMenu.addAction(exitAct)

        viewMenu = self.menuBar().addMenu("&View")

        self.styleActionGroup = QActionGroup(self)
        for styleName in QStyleFactory.keys():
            action = QAction("{} Style".format(styleName),
                             self.styleActionGroup)
            action.setData(styleName)
            action.setCheckable(True)
            action.triggered.connect(self.changeStyle)
            viewMenu.addAction(action)

        settingsMenu = self.menuBar().addMenu("&Settings")

        self.guessModeStateAct = QAction("&Guess Image Mode/State", self)
        self.guessModeStateAct.setCheckable(True)
        self.guessModeStateAct.setChecked(True)
        settingsMenu.addAction(self.guessModeStateAct)

        self.nativeFileDialogAct = QAction("&Use Native File Dialog", self)
        self.nativeFileDialogAct.setCheckable(True)
        self.nativeFileDialogAct.setChecked(True)
        settingsMenu.addAction(self.nativeFileDialogAct)

    def __init__(self, *args):
        super(QMainWindow, self).__init__(*args)

        centerWidget = QWidget(self)

        self.setCentralWidget(centerWidget)

        mainLayout = QGridLayout(centerWidget)

        previewGroupBox = QGroupBox("Preview")
        self.previewArea = IconPreviewArea(previewGroupBox)
        previewLayout = QVBoxLayout(previewGroupBox)
        previewLayout.addWidget(self.previewArea)

        mainLayout.addWidget(previewGroupBox, 0, 0, 1, 2)
        mainLayout.addWidget(self.createImagesGroupBox(), 1, 0)

        vbox = QVBoxLayout()
        vbox.addWidget(self.createIconSizeGroupBox())
        #vbox.addWidget(self.createHighDpiIconSizeGroupBox())
        vbox.addItem(
            QSpacerItem(0, 0, QSizePolicy.Ignored,
                        QSizePolicy.MinimumExpanding))
        mainLayout.addLayout(vbox, 1, 1)

        self.createActions()

        self.setWindowTitle("Icons")
        self.checkCurrentStyle()

    def about(self):
        QMessageBox.about(
            self, "About Icons"
            "The <b>Icons</b> example illustrates how Qt renders an icon in " +
            "different modes (active, normal, disabled, and selected) and " +
            "states (on and off) based on a set of images.")

    def checkCurrentStyle(self):
        for action in self.styleActionGroup.actions():
            styleName = action.data()
            candidate = QStyleFactory.create(styleName)
            if candidate.metaObject().className() == QApplication.style(
            ).metaObject().className():
                action.trigger()
                return

    def changeStyle(self, checked):
        if (not checked):
            return

        action = self.sender()
        style = QStyleFactory.create(action.data())
        QApplication.setStyle(style)

        for button in self.sizeButtonGroup.buttons():
            metric = self.sizeButtonGroup.id(button)
            value = style.pixelMetric(metric)
            {
                QStyle.PM_SmallIconSize:
                lambda: button.setText("Small ({} x {})".format(value, value)),
                QStyle.PM_LargeIconSize:
                lambda: button.setText("Large ({} x {})".format(value, value)),
                QStyle.PM_ToolBarIconSize:
                lambda: button.setText("Toolbars ({} x {})".format(
                    value, value)),
                QStyle.PM_ListViewIconSize:
                lambda: button.setText("List views ({} x {})".format(
                    value, value)),
                QStyle.PM_IconViewIconSize:
                lambda: button.setText("Icon views ({} x {})".format(
                    value, value)),
                QStyle.PM_TabBarIconSize:
                lambda: button.setText("Tab bars ({} x {})".format(
                    value, value)),
            }.get(metric, lambda: "")()

        self.triggerChangeSize()

    def changeIcon(self):
        icon = QIcon()

        for row in range(self.imagesTable.rowCount()):
            fileItem = self.imagesTable.item(row, 0)
            modeItem = self.imagesTable.item(row, 1)
            stateItem = self.imagesTable.item(row, 2)

            if fileItem.checkState() == Qt.Checked:
                modeIndex = IconPreviewArea.iconModeNames().index(
                    modeItem.text())
                stateIndex = IconPreviewArea.iconStateNames().index(
                    stateItem.text())
                mode = IconPreviewArea.iconModes()[modeIndex]
                state = IconPreviewArea.iconStates()[stateIndex]
                fileName = fileItem.data(Qt.UserRole)
                image = QImage(fileName)
                if not image.isNull():
                    icon.addPixmap(QPixmap.fromImage(image), mode, state)

        self.previewArea.setIcon(icon)

    def changeSize(self, id, checked):
        if (not checked):
            return

        other = (id == self.OtherSize)
        extend = self.otherSpinBox.value() if other else QApplication.style(
        ).pixelMetric(id)
        self.previewArea.setSize(QSize(extend, extend))
        self.otherSpinBox.setEnabled(other)

    def triggerChangeSize(self):
        self.changeSize(self.sizeButtonGroup.checkedId(), True)
Ejemplo n.º 20
0
class ListWidget(QWidget):
    deviceSelected = pyqtSignal(TasmotaDevice)
    openRulesEditor = pyqtSignal()
    openConsole = pyqtSignal()
    openTelemetry = pyqtSignal()
    openWebUI = pyqtSignal()

    def __init__(self, parent, *args, **kwargs):
        super(ListWidget, self).__init__(*args, **kwargs)
        self.setWindowTitle("Devices list")
        self.setWindowState(Qt.WindowMaximized)
        self.setLayout(VLayout(margin=0, spacing=0))

        self.mqtt = parent.mqtt
        self.env = parent.env

        self.device = None
        self.idx = None

        self.nam = QNetworkAccessManager()
        self.backup = bytes()

        self.settings = QSettings("{}/TDM/tdm.cfg".format(QDir.homePath()), QSettings.IniFormat)
        views_order = self.settings.value("views_order", [])

        self.views = {}
        self.settings.beginGroup("Views")
        views = self.settings.childKeys()
        if views and views_order:
            for view in views_order.split(";"):
                view_list = self.settings.value(view).split(";")
                self.views[view] = base_view + view_list
        else:
            self.views = default_views
        self.settings.endGroup()

        self.tb = Toolbar(Qt.Horizontal, 24, Qt.ToolButtonTextBesideIcon)
        self.tb_relays = Toolbar(Qt.Horizontal, 24, Qt.ToolButtonIconOnly)
        # self.tb_filter = Toolbar(Qt.Horizontal, 24, Qt.ToolButtonTextBesideIcon)
        self.tb_views = Toolbar(Qt.Horizontal, 24, Qt.ToolButtonTextBesideIcon)

        self.pwm_sliders = []

        self.layout().addWidget(self.tb)
        self.layout().addWidget(self.tb_relays)
        # self.layout().addWidget(self.tb_filter)

        self.device_list = TableView()
        self.device_list.setIconSize(QSize(24, 24))
        self.model = parent.device_model
        self.model.setupColumns(self.views["Home"])

        self.sorted_device_model = QSortFilterProxyModel()
        self.sorted_device_model.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.sorted_device_model.setSourceModel(parent.device_model)
        self.sorted_device_model.setSortRole(Qt.InitialSortOrderRole)
        self.sorted_device_model.setFilterKeyColumn(-1)

        self.device_list.setModel(self.sorted_device_model)
        self.device_list.setupView(self.views["Home"])
        self.device_list.setSortingEnabled(True)
        self.device_list.setWordWrap(True)
        self.device_list.setItemDelegate(DeviceDelegate())
        self.device_list.sortByColumn(self.model.columnIndex("FriendlyName"), Qt.AscendingOrder)
        self.device_list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.device_list.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        self.layout().addWidget(self.device_list)

        self.layout().addWidget(self.tb_views)

        self.device_list.clicked.connect(self.select_device)
        self.device_list.customContextMenuRequested.connect(self.show_list_ctx_menu)

        self.ctx_menu = QMenu()
        self.ctx_menu_relays = None

        self.create_actions()
        self.create_view_buttons()
        # self.create_view_filter()

        self.device_list.doubleClicked.connect(lambda: self.openConsole.emit())

    def create_actions(self):
        self.ctx_menu_cfg = QMenu("Configure")
        self.ctx_menu_cfg.setIcon(QIcon("GUI/icons/settings.png"))
        self.ctx_menu_cfg.addAction("Module", self.configureModule)
        self.ctx_menu_cfg.addAction("GPIO", self.configureGPIO)
        self.ctx_menu_cfg.addAction("Template", self.configureTemplate)
        # self.ctx_menu_cfg.addAction("Wifi", self.ctx_menu_teleperiod)
        # self.ctx_menu_cfg.addAction("Time", self.cfgTime.emit)
        # self.ctx_menu_cfg.addAction("MQTT", self.ctx_menu_teleperiod)

        # self.ctx_menu_cfg.addAction("Logging", self.ctx_menu_teleperiod)

        self.ctx_menu.addMenu(self.ctx_menu_cfg)
        self.ctx_menu.addSeparator()

        self.ctx_menu.addAction(QIcon("GUI/icons/refresh.png"), "Refresh", self.ctx_menu_refresh)

        self.ctx_menu.addSeparator()
        self.ctx_menu.addAction(QIcon("GUI/icons/clear.png"), "Clear retained", self.ctx_menu_clear_retained)
        self.ctx_menu.addAction("Clear Backlog", self.ctx_menu_clear_backlog)
        self.ctx_menu.addSeparator()
        self.ctx_menu.addAction(QIcon("GUI/icons/copy.png"), "Copy", self.ctx_menu_copy)
        self.ctx_menu.addSeparator()
        self.ctx_menu.addAction(QIcon("GUI/icons/restart.png"), "Restart", self.ctx_menu_restart)
        self.ctx_menu.addAction(QIcon(), "Reset", self.ctx_menu_reset)
        self.ctx_menu.addSeparator()
        self.ctx_menu.addAction(QIcon("GUI/icons/delete.png"), "Delete", self.ctx_menu_delete_device)

        console = self.tb.addAction(QIcon("GUI/icons/console.png"), "Console", self.openConsole.emit)
        console.setShortcut("Ctrl+E")

        rules = self.tb.addAction(QIcon("GUI/icons/rules.png"), "Rules", self.openRulesEditor.emit)
        rules.setShortcut("Ctrl+R")

        self.tb.addAction(QIcon("GUI/icons/timers.png"), "Timers", self.configureTimers)

        buttons = self.tb.addAction(QIcon("GUI/icons/buttons.png"), "Buttons", self.configureButtons)
        buttons.setShortcut("Ctrl+B")

        switches = self.tb.addAction(QIcon("GUI/icons/switches.png"), "Switches", self.configureSwitches)
        switches.setShortcut("Ctrl+S")

        power = self.tb.addAction(QIcon("GUI/icons/power.png"), "Power", self.configurePower)
        power.setShortcut("Ctrl+P")

        # setopts = self.tb.addAction(QIcon("GUI/icons/setoptions.png"), "SetOptions", self.configureSO)
        # setopts.setShortcut("Ctrl+S")

        self.tb.addSpacer()

        telemetry = self.tb.addAction(QIcon("GUI/icons/telemetry.png"), "Telemetry", self.openTelemetry.emit)
        telemetry.setShortcut("Ctrl+T")

        webui = self.tb.addAction(QIcon("GUI/icons/web.png"), "WebUI", self.openWebUI.emit)
        webui.setShortcut("Ctrl+U")

        # self.tb.addAction(QIcon(), "Multi Command", self.ctx_menu_webui)

        self.agAllPower = QActionGroup(self)
        self.agAllPower.addAction(QIcon("GUI/icons/P_ON.png"), "All ON")
        self.agAllPower.addAction(QIcon("GUI/icons/P_OFF.png"), "All OFF")
        self.agAllPower.setEnabled(False)
        self.agAllPower.setExclusive(False)
        self.agAllPower.triggered.connect(self.toggle_power_all)
        self.tb_relays.addActions(self.agAllPower.actions())

        self.agRelays = QActionGroup(self)
        self.agRelays.setVisible(False)
        self.agRelays.setExclusive(False)

        for a in range(1, 9):
            act = QAction(QIcon("GUI/icons/P{}_OFF.png".format(a)), "")
            act.setShortcut("F{}".format(a))
            self.agRelays.addAction(act)

        self.agRelays.triggered.connect(self.toggle_power)
        self.tb_relays.addActions(self.agRelays.actions())

        self.tb_relays.addSeparator()
        self.actColor = self.tb_relays.addAction(QIcon("GUI/icons/color.png"), "Color", self.set_color)
        self.actColor.setEnabled(False)

        self.actChannels = self.tb_relays.addAction(QIcon("GUI/icons/sliders.png"), "Channels")
        self.actChannels.setEnabled(False)
        self.mChannels = QMenu()
        self.actChannels.setMenu(self.mChannels)
        self.tb_relays.widgetForAction(self.actChannels).setPopupMode(QToolButton.InstantPopup)

    def create_view_buttons(self):
        self.tb_views.addWidget(QLabel("View mode: "))
        ag_views = QActionGroup(self)
        ag_views.setExclusive(True)
        for v in self.views.keys():
            a = QAction(v)
            a.triggered.connect(self.change_view)
            a.setCheckable(True)
            ag_views.addAction(a)
        self.tb_views.addActions(ag_views.actions())
        ag_views.actions()[0].setChecked(True)

        stretch = QWidget()
        stretch.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum))
        self.tb_views.addWidget(stretch)
        # actEditView = self.tb_views.addAction("Edit views...")

    # def create_view_filter(self):
    #     # self.tb_filter.addWidget(QLabel("Show devices: "))
    #     # self.cbxLWT = QComboBox()
    #     # self.cbxLWT.addItems(["All", "Online"d, "Offline"])
    #     # self.cbxLWT.currentTextChanged.connect(self.build_filter_regex)
    #     # self.tb_filter.addWidget(self.cbxLWT)
    #
    #     self.tb_filter.addWidget(QLabel(" Search: "))
    #     self.leSearch = QLineEdit()
    #     self.leSearch.setClearButtonEnabled(True)
    #     self.leSearch.textChanged.connect(self.build_filter_regex)
    #     self.tb_filter.addWidget(self.leSearch)
    #
    # def build_filter_regex(self, txt):
    #     query = self.leSearch.text()
    #     # if self.cbxLWT.currentText() != "All":
    #     #     query = "{}|{}".format(self.cbxLWT.currentText(), query)
    #     self.sorted_device_model.setFilterRegExp(query)

    def change_view(self, a=None):
        view = self.views[self.sender().text()]
        self.model.setupColumns(view)
        self.device_list.setupView(view)

    def ctx_menu_copy(self):
        if self.idx:
            string = dumps(self.model.data(self.idx))
            if string.startswith('"') and string.endswith('"'):
                string = string[1:-1]
            QApplication.clipboard().setText(string)

    def ctx_menu_clear_retained(self):
        if self.device:
            relays = self.device.power()
            if relays and len(relays.keys()) > 0:
                for r in relays.keys():
                    self.mqtt.publish(self.device.cmnd_topic(r), retain=True)
            QMessageBox.information(self, "Clear retained", "Cleared retained messages.")

    def ctx_menu_clear_backlog(self):
        if self.device:
            self.mqtt.publish(self.device.cmnd_topic("backlog"), "")
            QMessageBox.information(self, "Clear Backlog", "Backlog cleared.")

    def ctx_menu_restart(self):
        if self.device:
            self.mqtt.publish(self.device.cmnd_topic("restart"), payload="1")
            for k in list(self.device.power().keys()):
                self.device.p.pop(k)

    def ctx_menu_reset(self):
        if self.device:
            reset, ok = QInputDialog.getItem(self, "Reset device and restart", "Select reset mode", resets, editable=False)
            if ok:
                self.mqtt.publish(self.device.cmnd_topic("reset"), payload=reset.split(":")[0])
                for k in list(self.device.power().keys()):
                    self.device.p.pop(k)

    def ctx_menu_refresh(self):
        if self.device:
            for k in list(self.device.power().keys()):
                self.device.p.pop(k)

            for c in initial_commands():
                cmd, payload = c
                cmd = self.device.cmnd_topic(cmd)
                self.mqtt.publish(cmd, payload, 1)

    def ctx_menu_delete_device(self):
        if self.device:
            if QMessageBox.question(self, "Confirm", "Do you want to remove the following device?\n'{}' ({})"
                    .format(self.device.p['FriendlyName1'], self.device.p['Topic'])) == QMessageBox.Yes:
                self.model.deleteDevice(self.idx)

    def ctx_menu_teleperiod(self):
        if self.device:
            teleperiod, ok = QInputDialog.getInt(self, "Set telemetry period", "Input 1 to reset to default\n[Min: 10, Max: 3600]", self.device.p['TelePeriod'], 1, 3600)
            if ok:
                if teleperiod != 1 and teleperiod < 10:
                    teleperiod = 10
            self.mqtt.publish(self.device.cmnd_topic("teleperiod"), teleperiod)

    def ctx_menu_config_backup(self):
        if self.device:
            self.backup = bytes()
            self.dl = self.nam.get(QNetworkRequest(QUrl("http://{}/dl".format(self.device.p['IPAddress']))))
            self.dl.readyRead.connect(self.get_dump)
            self.dl.finished.connect(self.save_dump)

    def ctx_menu_ota_set_url(self):
        if self.device:
            url, ok = QInputDialog.getText(self, "Set OTA URL", '100 chars max. Set to "1" to reset to default.', text=self.device.p['OtaUrl'])
            if ok:
                self.mqtt.publish(self.device.cmnd_topic("otaurl"), payload=url)

    def ctx_menu_ota_set_upgrade(self):
        if self.device:
            if QMessageBox.question(self, "OTA Upgrade", "Are you sure to OTA upgrade from\n{}".format(self.device.p['OtaUrl']), QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:
                self.mqtt.publish(self.device.cmnd_topic("upgrade"), payload="1")

    def show_list_ctx_menu(self, at):
        self.select_device(self.device_list.indexAt(at))
        self.ctx_menu.popup(self.device_list.viewport().mapToGlobal(at))

    def select_device(self, idx):
        self.idx = self.sorted_device_model.mapToSource(idx)
        self.device = self.model.deviceAtRow(self.idx.row())
        self.deviceSelected.emit(self.device)

        relays = self.device.power()

        self.agAllPower.setEnabled(len(relays) >= 1)

        for i, a in enumerate(self.agRelays.actions()):
            a.setVisible(len(relays) > 1 and i < len(relays))

        color = self.device.color().get("Color", False)
        has_color = bool(color)
        self.actColor.setEnabled(has_color and not self.device.setoption(68))

        self.actChannels.setEnabled(has_color)

        if has_color:
            self.actChannels.menu().clear()

            max_val = 100
            if self.device.setoption(15) == 0:
                max_val = 1023

            for k, v in self.device.pwm().items():
                channel = SliderAction(self, k)
                channel.slider.setMaximum(max_val)
                channel.slider.setValue(int(v))
                self.mChannels.addAction(channel)
                channel.slider.valueChanged.connect(self.set_channel)

            dimmer = self.device.color().get("Dimmer")
            if dimmer:
                saDimmer = SliderAction(self, "Dimmer")
                saDimmer.slider.setValue(int(dimmer))
                self.mChannels.addAction(saDimmer)
                saDimmer.slider.valueChanged.connect(self.set_channel)

    def toggle_power(self, action):
        if self.device:
            idx = self.agRelays.actions().index(action)
            relay = list(self.device.power().keys())[idx]
            self.mqtt.publish(self.device.cmnd_topic(relay), "toggle")

    def toggle_power_all(self, action):
        if self.device:
            idx = self.agAllPower.actions().index(action)
            for r in self.device.power().keys():
                self.mqtt.publish(self.device.cmnd_topic(r), str(not bool(idx)))

    def set_color(self):
        if self.device:
            color = self.device.color().get("Color")
            if color:
                dlg = QColorDialog()
                new_color = dlg.getColor(QColor("#{}".format(color)))
                if new_color.isValid():
                    new_color = new_color.name()
                    if new_color != color:
                        self.mqtt.publish(self.device.cmnd_topic("color"), new_color)

    def set_channel(self, value=0):
        cmd = self.sender().objectName()

        if self.device:
            self.mqtt.publish(self.device.cmnd_topic(cmd), str(value))

    def configureSO(self):
        if self.device:
            dlg = SetOptionsDialog(self.device)
            dlg.sendCommand.connect(self.mqtt.publish)
            dlg.exec_()

    def configureModule(self):
        if self.device:
            dlg = ModuleDialog(self.device)
            dlg.sendCommand.connect(self.mqtt.publish)
            dlg.exec_()

    def configureGPIO(self):
        if self.device:
            dlg = GPIODialog(self.device)
            dlg.sendCommand.connect(self.mqtt.publish)
            dlg.exec_()

    def configureTemplate(self):
        if self.device:
            dlg = TemplateDialog(self.device)
            dlg.sendCommand.connect(self.mqtt.publish)
            dlg.exec_()

    def configureTimers(self):
        if self.device:
            self.mqtt.publish(self.device.cmnd_topic("timers"))
            timers = TimersDialog(self.device)
            self.mqtt.messageSignal.connect(timers.parseMessage)
            timers.sendCommand.connect(self.mqtt.publish)
            timers.exec_()

    def configureButtons(self):
        if self.device:
            backlog = []
            buttons = ButtonsDialog(self.device)
            if buttons.exec_() == QDialog.Accepted:
                for c, cw in buttons.command_widgets.items():
                    current_value = self.device.p.get(c)
                    new_value = ""

                    if isinstance(cw.input, SpinBox):
                        new_value = cw.input.value()

                    if isinstance(cw.input, QComboBox):
                        new_value = cw.input.currentIndex()

                    if current_value != new_value:
                        backlog.append("{} {}".format(c, new_value))

                for so, sow in buttons.setoption_widgets.items():
                    current_value = self.device.setoption(so)
                    new_value = -1

                    if isinstance(sow.input, SpinBox):
                        new_value = sow.input.value()

                    if isinstance(sow.input, QComboBox):
                        new_value = sow.input.currentIndex()

                    if current_value != new_value:
                        backlog.append("SetOption{} {}".format(so, new_value))

                if backlog:
                    backlog.append("status 3")
                    self.mqtt.publish(self.device.cmnd_topic("backlog"), "; ".join(backlog))

    def configureSwitches(self):
        if self.device:
            backlog = []
            switches = SwitchesDialog(self.device)
            if switches.exec_() == QDialog.Accepted:
                for c, cw in switches.command_widgets.items():
                    current_value = self.device.p.get(c)
                    new_value = ""

                    if isinstance(cw.input, SpinBox):
                        new_value = cw.input.value()

                    if isinstance(cw.input, QComboBox):
                        new_value = cw.input.currentIndex()

                    if current_value != new_value:
                        backlog.append("{} {}".format(c, new_value))

                for so, sow in switches.setoption_widgets.items():
                    current_value = self.device.setoption(so)
                    new_value = -1

                    if isinstance(sow.input, SpinBox):
                        new_value = sow.input.value()

                    if isinstance(sow.input, QComboBox):
                        new_value = sow.input.currentIndex()

                    if current_value != new_value:
                        backlog.append("SetOption{} {}".format(so, new_value))

                for sw, sw_mode in enumerate(self.device.p['SwitchMode']):
                    new_value = switches.sm.inputs[sw].currentIndex()

                    if sw_mode != new_value:
                        backlog.append("switchmode{} {}".format(sw+1, new_value))

                if backlog:
                    backlog.append("status")
                    backlog.append("status 3")
                self.mqtt.publish(self.device.cmnd_topic("backlog"), "; ".join(backlog))

    def configurePower(self):
        if self.device:
            backlog = []
            power = PowerDialog(self.device)
            if power.exec_() == QDialog.Accepted:
                for c, cw in power.command_widgets.items():
                    current_value = self.device.p.get(c)
                    new_value = ""

                    if isinstance(cw.input, SpinBox):
                        new_value = cw.input.value()

                    if isinstance(cw.input, QComboBox):
                        new_value = cw.input.currentIndex()

                    if current_value != new_value:
                        backlog.append("{} {}".format(c, new_value))

                for so, sow in power.setoption_widgets.items():
                    new_value = -1

                    if isinstance(sow.input, SpinBox):
                        new_value = sow.input.value()

                    if isinstance(sow.input, QComboBox):
                        new_value = sow.input.currentIndex()

                    if new_value != self.device.setoption(so):
                        backlog.append("SetOption{} {}".format(so, new_value))

                new_interlock_value = power.ci.input.currentData()
                new_interlock_grps = " ".join([grp.text().replace(" ", "") for grp in power.ci.groups]).rstrip()

                if new_interlock_value != self.device.p.get("Interlock", "OFF"):
                    backlog.append("interlock {}".format(new_interlock_value))

                if new_interlock_grps != self.device.p.get("Groups", ""):
                    backlog.append("interlock {}".format(new_interlock_grps))

                for i, pt in enumerate(power.cpt.inputs):
                    ptime = "PulseTime{}".format(i+1)
                    current_ptime = self.device.p.get(ptime)
                    if current_ptime:
                        current_value = list(current_ptime.keys())[0]
                        new_value = str(pt.value())

                        if new_value != current_value:
                            backlog.append("{} {}".format(ptime, new_value))

                if backlog:
                    backlog.append("status")
                    backlog.append("status 3")
                    self.mqtt.publish(self.device.cmnd_topic("backlog"), "; ".join(backlog))

    def get_dump(self):
        self.backup += self.dl.readAll()

    def save_dump(self):
        fname = self.dl.header(QNetworkRequest.ContentDispositionHeader)
        if fname:
            fname = fname.split('=')[1]
            save_file = QFileDialog.getSaveFileName(self, "Save config backup", "{}/TDM/{}".format(QDir.homePath(), fname))[0]
            if save_file:
                with open(save_file, "wb") as f:
                    f.write(self.backup)

    def check_fulltopic(self, fulltopic):
        fulltopic += "/" if not fulltopic.endswith('/') else ''
        return "%prefix%" in fulltopic and "%topic%" in fulltopic

    def closeEvent(self, event):
        event.ignore()
Ejemplo n.º 21
0
class KEyesWidget(QWidget):

  update_interval = 50 # ms

  faces = {
    "Aaron":     ("keyes-aaron.png",     (49, 63, 12, 8), (79, 63, 12, 8)),
    "Adrian":    ("keyes-adrian.png",    (46, 67, 11, 6), (74, 68, 11, 6)),
    "Cornelius": ("keyes-cornelius.png", (49, 68, 11, 6), (79, 68, 11, 6)),
    "Eva":       ("keyes-eva.png",       (51, 63, 12, 6), (83, 63, 12, 6)),
    "Sebastian": ("keyes-sebastian.png", (50, 58, 14, 7), (83, 58, 14, 7)),
  }


  def __init__(self):

    QLabel.__init__(self)

    self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
    self.setAttribute(Qt.WA_NoSystemBackground)
    self.setMouseTracking(True)

    self.dragPosition  = QPoint(0, 0)
    self.mousePosition = QCursor.pos()

    self.actionFaces = QActionGroup(self)
    allFaceActions   = []

    for name in sorted(self.faces):

      action = QAction(name, self.actionFaces)
      action.setCheckable(True)
      allFaceActions.append(action)

    self.actionFaces.triggered.connect(self.actionUpdateFace)

    startAction = random.choice(allFaceActions)
    startAction.setChecked(True)
    self.actionUpdateFace(startAction)

    self.actionQuit = QAction("Quit", self)
    self.actionQuit.triggered.connect(QApplication.instance().quit)

    self.timer = QTimer()
    self.timer.timeout.connect(self.updateFromMousePosition)

    self.timer.start(self.update_interval)

    self.painter = None


  def actionUpdateFace(self, action):

    self.setFace(action.text())


  def setFace(self, name):

    self.setWindowTitle(name)
    self.pixmap = QPixmap(normalize_path(self.faces[name][0]))

    self.setWindowIcon(QIcon(self.pixmap))

    self.eyes = [Eye(*self.faces[name][1]), Eye(*self.faces[name][2])]

    self.setMask(self.pixmap.createHeuristicMask())

    if self.isVisible():
      self.update()


  def updateFromMousePosition(self):

    newPosition = QCursor.pos()

    if newPosition == self.mousePosition:
      return

    self.mousePosition = newPosition

    if self.isVisible():
      self.update()


  def mousePressEvent(self, event):

    if event.button() == Qt.LeftButton:

      self.dragPosition = event.globalPos() - self.frameGeometry().topLeft()
      event.accept()


  def mouseMoveEvent(self, event):

    if event.buttons() == Qt.LeftButton:

      self.move(event.globalPos() - self.dragPosition)
      event.accept()


  def contextMenuEvent(self, event):

    menu = QMenu(self)

    menu.addActions(self.actionFaces.actions())
    menu.addSeparator()
    menu.addAction(self.actionQuit)

    menu.exec_(event.globalPos())


  def paintEvent(self, event):

    painter      = QPainter(self)
    self.painter = painter

    painter.drawPixmap(QPoint(0, 0), self.pixmap)

    for eye in self.eyes:
      eye.render(self)

    self.painter = None


  def sizeHint(self):

    return self.pixmap.size()
Ejemplo n.º 22
0
class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)

        self.createPreviewGroupBox()
        self.createImagesGroupBox()
        self.createIconSizeGroupBox()

        self.createActions()
        self.createMenus()
        self.createContextMenu()

        mainLayout = QGridLayout()
        mainLayout.addWidget(self.previewGroupBox, 0, 0, 1, 2)
        mainLayout.addWidget(self.imagesGroupBox, 1, 0)
        mainLayout.addWidget(self.iconSizeGroupBox, 1, 1)
        self.centralWidget.setLayout(mainLayout)

        self.setWindowTitle("Icons")
        self.checkCurrentStyle()
        self.otherRadioButton.click()

        self.resize(self.minimumSizeHint())

    def about(self):
        QMessageBox.about(self, "About Icons",
                "The <b>Icons</b> example illustrates how Qt renders an icon "
                "in different modes (active, normal, disabled and selected) "
                "and states (on and off) based on a set of images.")

    def changeStyle(self, checked):
        if not checked:
            return

        action = self.sender()
        style = QStyleFactory.create(action.data())
        if not style:
            return

        QApplication.setStyle(style)

        self.setButtonText(self.smallRadioButton, "Small (%d x %d)",
                style, QStyle.PM_SmallIconSize)
        self.setButtonText(self.largeRadioButton, "Large (%d x %d)",
                style, QStyle.PM_LargeIconSize)
        self.setButtonText(self.toolBarRadioButton, "Toolbars (%d x %d)",
                style, QStyle.PM_ToolBarIconSize)
        self.setButtonText(self.listViewRadioButton, "List views (%d x %d)",
                style, QStyle.PM_ListViewIconSize)
        self.setButtonText(self.iconViewRadioButton, "Icon views (%d x %d)",
                style, QStyle.PM_IconViewIconSize)
        self.setButtonText(self.tabBarRadioButton, "Tab bars (%d x %d)",
                style, QStyle.PM_TabBarIconSize)

        self.changeSize()

    @staticmethod
    def setButtonText(button, label, style, metric):
        metric_value = style.pixelMetric(metric)
        button.setText(label % (metric_value, metric_value))

    def changeSize(self, checked=True):
        if not checked:
            return

        if self.otherRadioButton.isChecked():
            extent = self.otherSpinBox.value()
        else:
            if self.smallRadioButton.isChecked():
                metric = QStyle.PM_SmallIconSize
            elif self.largeRadioButton.isChecked():
                metric = QStyle.PM_LargeIconSize
            elif self.toolBarRadioButton.isChecked():
                metric = QStyle.PM_ToolBarIconSize
            elif self.listViewRadioButton.isChecked():
                metric = QStyle.PM_ListViewIconSize
            elif self.iconViewRadioButton.isChecked():
                metric = QStyle.PM_IconViewIconSize
            else:
                metric = QStyle.PM_TabBarIconSize

            extent = QApplication.style().pixelMetric(metric)

        self.previewArea.setSize(QSize(extent, extent))
        self.otherSpinBox.setEnabled(self.otherRadioButton.isChecked())

    def changeIcon(self):
        icon = QIcon()

        for row in range(self.imagesTable.rowCount()):
            item0 = self.imagesTable.item(row, 0)
            item1 = self.imagesTable.item(row, 1)
            item2 = self.imagesTable.item(row, 2)

            if item0.checkState() == Qt.Checked:
                if item1.text() == "Normal":
                    mode = QIcon.Normal
                elif item1.text() == "Active":
                    mode = QIcon.Active
                elif item1.text() == "Disabled":
                    mode = QIcon.Disabled
                else:
                    mode = QIcon.Selected

                if item2.text() == "On":
                    state = QIcon.On
                else:
                    state = QIcon.Off

                fileName = item0.data(Qt.UserRole)
                image = QImage(fileName)
                if not image.isNull():
                    icon.addPixmap(QPixmap.fromImage(image), mode, state)

        self.previewArea.setIcon(icon)

    def addImage(self):
        fileNames, _ = QFileDialog.getOpenFileNames(self, "Open Images", '',
                "Images (*.png *.xpm *.jpg);;All Files (*)")

        for fileName in fileNames:
            row = self.imagesTable.rowCount()
            self.imagesTable.setRowCount(row + 1)

            imageName = QFileInfo(fileName).baseName()
            item0 = QTableWidgetItem(imageName)
            item0.setData(Qt.UserRole, fileName)
            item0.setFlags(item0.flags() & ~Qt.ItemIsEditable)

            item1 = QTableWidgetItem("Normal")
            item2 = QTableWidgetItem("Off")

            if self.guessModeStateAct.isChecked():
                if '_act' in fileName:
                    item1.setText("Active")
                elif '_dis' in fileName:
                    item1.setText("Disabled")
                elif '_sel' in fileName:
                    item1.setText("Selected")

                if '_on' in fileName:
                    item2.setText("On")

            self.imagesTable.setItem(row, 0, item0)
            self.imagesTable.setItem(row, 1, item1)
            self.imagesTable.setItem(row, 2, item2)
            self.imagesTable.openPersistentEditor(item1)
            self.imagesTable.openPersistentEditor(item2)

            item0.setCheckState(Qt.Checked)

    def removeAllImages(self):
        self.imagesTable.setRowCount(0)
        self.changeIcon()

    def createPreviewGroupBox(self):
        self.previewGroupBox = QGroupBox("Preview")

        self.previewArea = IconPreviewArea()

        layout = QVBoxLayout()
        layout.addWidget(self.previewArea)
        self.previewGroupBox.setLayout(layout)

    def createImagesGroupBox(self):
        self.imagesGroupBox = QGroupBox("Images")

        self.imagesTable = QTableWidget()
        self.imagesTable.setSelectionMode(QAbstractItemView.NoSelection)
        self.imagesTable.setItemDelegate(ImageDelegate(self))

        self.imagesTable.horizontalHeader().setDefaultSectionSize(90)
        self.imagesTable.setColumnCount(3)
        self.imagesTable.setHorizontalHeaderLabels(("Image", "Mode", "State"))
        self.imagesTable.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
        self.imagesTable.horizontalHeader().setSectionResizeMode(1, QHeaderView.Fixed)
        self.imagesTable.horizontalHeader().setSectionResizeMode(2, QHeaderView.Fixed)
        self.imagesTable.verticalHeader().hide()

        self.imagesTable.itemChanged.connect(self.changeIcon)

        layout = QVBoxLayout()
        layout.addWidget(self.imagesTable)
        self.imagesGroupBox.setLayout(layout)

    def createIconSizeGroupBox(self):
        self.iconSizeGroupBox = QGroupBox("Icon Size")

        self.smallRadioButton = QRadioButton()
        self.largeRadioButton = QRadioButton()
        self.toolBarRadioButton = QRadioButton()
        self.listViewRadioButton = QRadioButton()
        self.iconViewRadioButton = QRadioButton()
        self.tabBarRadioButton = QRadioButton()
        self.otherRadioButton = QRadioButton("Other:")

        self.otherSpinBox = IconSizeSpinBox()
        self.otherSpinBox.setRange(8, 128)
        self.otherSpinBox.setValue(64)

        self.smallRadioButton.toggled.connect(self.changeSize)
        self.largeRadioButton.toggled.connect(self.changeSize)
        self.toolBarRadioButton.toggled.connect(self.changeSize)
        self.listViewRadioButton.toggled.connect(self.changeSize)
        self.iconViewRadioButton.toggled.connect(self.changeSize)
        self.tabBarRadioButton.toggled.connect(self.changeSize)
        self.otherRadioButton.toggled.connect(self.changeSize)
        self.otherSpinBox.valueChanged.connect(self.changeSize)

        otherSizeLayout = QHBoxLayout()
        otherSizeLayout.addWidget(self.otherRadioButton)
        otherSizeLayout.addWidget(self.otherSpinBox)
        otherSizeLayout.addStretch()

        layout = QGridLayout()
        layout.addWidget(self.smallRadioButton, 0, 0)
        layout.addWidget(self.largeRadioButton, 1, 0)
        layout.addWidget(self.toolBarRadioButton, 2, 0)
        layout.addWidget(self.listViewRadioButton, 0, 1)
        layout.addWidget(self.iconViewRadioButton, 1, 1)
        layout.addWidget(self.tabBarRadioButton, 2, 1)
        layout.addLayout(otherSizeLayout, 3, 0, 1, 2)
        layout.setRowStretch(4, 1)
        self.iconSizeGroupBox.setLayout(layout)

    def createActions(self):
        self.addImagesAct = QAction("&Add Images...", self, shortcut="Ctrl+A",
                triggered=self.addImage)

        self.removeAllImagesAct = QAction("&Remove All Images", self,
                shortcut="Ctrl+R", triggered=self.removeAllImages)

        self.exitAct = QAction("&Quit", self, shortcut="Ctrl+Q",
                triggered=self.close)

        self.styleActionGroup = QActionGroup(self)
        for styleName in QStyleFactory.keys():
            action = QAction(self.styleActionGroup,
                    text="%s Style" % styleName, checkable=True,
                    triggered=self.changeStyle)
            action.setData(styleName)

        self.guessModeStateAct = QAction("&Guess Image Mode/State", self,
                checkable=True, checked=True)

        self.aboutAct = QAction("&About", self, triggered=self.about)

        self.aboutQtAct = QAction("About &Qt", self,
                triggered=QApplication.instance().aboutQt)

    def createMenus(self):
        self.fileMenu = self.menuBar().addMenu("&File")
        self.fileMenu.addAction(self.addImagesAct)
        self.fileMenu.addAction(self.removeAllImagesAct)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.exitAct)

        self.viewMenu = self.menuBar().addMenu("&View")
        for action in self.styleActionGroup.actions():
            self.viewMenu.addAction(action)
        self.viewMenu.addSeparator()
        self.viewMenu.addAction(self.guessModeStateAct)

        self.menuBar().addSeparator()

        self.helpMenu = self.menuBar().addMenu("&Help")
        self.helpMenu.addAction(self.aboutAct)
        self.helpMenu.addAction(self.aboutQtAct)

    def createContextMenu(self):
        self.imagesTable.setContextMenuPolicy(Qt.ActionsContextMenu)
        self.imagesTable.addAction(self.addImagesAct)
        self.imagesTable.addAction(self.removeAllImagesAct)

    def checkCurrentStyle(self):
        for action in self.styleActionGroup.actions():
            styleName = action.data()
            candidate = QStyleFactory.create(styleName)

            if candidate is None:
                return

            if candidate.metaObject().className() == QApplication.style().metaObject().className():
                action.trigger()
Ejemplo n.º 23
0
    def initUI(self):

        # View widget stuff

        self.view = TextView()
        self.centralWidget = QWidget(self)
        self.layout = QVBoxLayout(self.centralWidget)
        self.view.statusChanged.connect(self.quickViewStatusChanged)
        self.view.sceneGraphError.connect(self.sceneGraphError)

        self.container = QWidget.createWindowContainer(self.view)
        self.container.setMinimumSize(self.view.size())
        self.container.setFocusPolicy(Qt.TabFocus)
        self.layout.addWidget(self.container)

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

        # Menu stuff

        self.toolbar = self.addToolBar('Text Effects')

        # Set up the text effects tools
        actionGroup = QActionGroup(self)

        noneAction = QAction(QIcon("none.png"), "&Clear", self)
        noneAction.setStatusTip("Clear Effects")
        noneAction.triggered.connect(self.view.noEffect)
        actionGroup.addAction(noneAction)

        blurAction = QAction(QIcon("blur.png"), "&Blur", self)
        blurAction.setStatusTip("Blur Text")
        blurAction.triggered.connect(self.view.blur)
        actionGroup.addAction(blurAction)

        opacityAction = QAction(QIcon("opacity.png"), "&Transparency", self)
        opacityAction.setStatusTip("Fade Text")
        opacityAction.triggered.connect(self.view.opacity)
        actionGroup.addAction(opacityAction)

        shadowAction = QAction(QIcon("shadow.png"), "&Drop Shadow", self)
        shadowAction.setStatusTip("Drop-shadow Text")
        shadowAction.triggered.connect(self.view.shadow)
        actionGroup.addAction(shadowAction)

        self.toolbar.addActions(actionGroup.actions())
        self.toolbar.addSeparator()

        # Set up the font selection tools
        boldAction = QAction(QIcon("bold.png"), "&Bold", self)
        boldAction.setStatusTip("Bold Text")
        boldAction.setCheckable(True)
        boldAction.setChecked(False)
        boldAction.triggered[bool].connect(self.view.bold)
        self.toolbar.addAction(boldAction)

        italicAction = QAction(QIcon("italic.png"), "&Italic", self)
        italicAction.setStatusTip("Italic Text")
        italicAction.setCheckable(True)
        italicAction.triggered[bool].connect(self.view.italic)
        self.toolbar.addAction(italicAction)

        self.fontBox = QFontComboBox(self)
        self.fontBox.setCurrentFont(self.view.font)
        self.fontBox.currentFontChanged.connect(self.view.fontFamily)
        self.toolbar.addWidget(self.fontBox)

        self.fontSizeBox = QComboBox(self)
        self.fontSizeBox.setEditable(True)
        strlist = []
        intlist = QFontDatabase.standardSizes()
        for item in intlist:
            strlist.append(str(item))
        self.fontSizeBox.addItems(strlist)
        self.view.fontSize('24')
        self.fontSizeBox.setCurrentText(str(self.view.basicFontSize))
        self.fontSizeBox.currentTextChanged.connect(self.view.fontSize)
        self.toolbar.addWidget(self.fontSizeBox)

        self.setGeometry(300, 300, 600, 500)
        self.setWindowTitle('Renderer')
        self.show()
Ejemplo n.º 24
0
	def __init__(self, launcher, parent=None):
		QMainWindow.__init__(self, parent)
		self.setupUi(self)
		self.central_workspace = WorkspaceWidget(self)
		self.setCentralWidget(self.central_workspace)
		self.installEventFilter(RadioKeyEventFilter(self))
		self.setAttribute(Qt.WA_DeleteOnClose)
		self.launcher = launcher
		settings.controlled_tower_viewer = FlightGearTowerViewer(self)
		settings.session_manager = SessionManager(self)
		self.setWindowTitle('%s - %s (%s)' % (self.windowTitle(), env.locationName(), settings.location_code))
		self.session_start_sound_lock_timer = QTimer(self)
		self.session_start_sound_lock_timer.setSingleShot(True)
		self.session_start_sound_lock_timer.timeout.connect(self.unlockSounds)
		
		## Restore saved dock layout
		try:
			with open(dock_layout_file, 'rb') as f: # Restore saved dock arrangement
				self.restoreState(f.read())
		except FileNotFoundError: # Fallback on default dock arrangement
			# left docks, top zone
			self.tabifyDockWidget(self.selection_info_dock, self.weather_dock)
			self.tabifyDockWidget(self.selection_info_dock, self.towerView_dock)
			self.tabifyDockWidget(self.selection_info_dock, self.navigator_dock)
			self.selection_info_dock.hide()
			# left docks, bottom zone
			self.tabifyDockWidget(self.instructions_dock, self.notepads_dock)
			self.tabifyDockWidget(self.instructions_dock, self.radio_dock)
			self.tabifyDockWidget(self.instructions_dock, self.FPLlist_dock)
			self.tabifyDockWidget(self.instructions_dock, self.CPDLC_dock)
			self.instructions_dock.hide()
			self.notepads_dock.hide()
			self.radio_dock.hide()
			self.CPDLC_dock.hide()
			# right docks
			self.rwyBoxes_dock.hide() # hiding this because bad position (user will drag 1st thing after raise)
			# bottom docks
			self.atcTextChat_dock.hide()
		
		## Permanent tool/status bar widgets
		self.selectionInfo_toolbar.addWidget(SelectionInfoToolbarWidget(self))
		self.METAR_statusBarLabel = QLabel()
		self.PTT_statusBarLabel = QLabel()
		self.RDF_statusBarLabel = QLabel()
		self.RDF_statusBarLabel.setToolTip('Current signal / last QDM')
		self.wind_statusBarLabel = QLabel()
		self.QNH_statusBarLabel = QLabel()
		self.QNH_statusBarLabel.setToolTip('hPa / inHg')
		self.clock_statusBarLabel = QLabel()
		self.alarmClock_statusBarButtons = [AlarmClockButton('1', self), AlarmClockButton('2', self)]
		self.statusbar.addWidget(self.METAR_statusBarLabel)
		self.statusbar.addPermanentWidget(self.PTT_statusBarLabel)
		self.statusbar.addPermanentWidget(self.RDF_statusBarLabel)
		self.statusbar.addPermanentWidget(self.wind_statusBarLabel)
		self.statusbar.addPermanentWidget(self.QNH_statusBarLabel)
		for b in self.alarmClock_statusBarButtons:
			self.statusbar.addPermanentWidget(b)
			b.alarm.connect(self.notification_pane.notifyAlarmClockTimedOut)
		self.statusbar.addPermanentWidget(self.clock_statusBarLabel)
		
		# Populate menus (toolbar visibility and airport viewpoints)
		toolbar_menu = QMenu()
		self.general_viewToolbar_action = self.general_toolbar.toggleViewAction()
		self.stripActions_viewToolbar_action = self.stripActions_toolbar.toggleViewAction()
		self.docks_viewToolbar_action = self.docks_toolbar.toggleViewAction()
		self.selectionInfo_viewToolbar_action = self.selectionInfo_toolbar.toggleViewAction()
		self.radarAssistance_viewToolbar_action = self.radarAssistance_toolbar.toggleViewAction()
		self.workspace_viewToolbar_action = self.workspace_toolbar.toggleViewAction()
		toolbar_menu.addAction(self.general_viewToolbar_action)
		toolbar_menu.addAction(self.stripActions_viewToolbar_action)
		toolbar_menu.addAction(self.docks_viewToolbar_action)
		toolbar_menu.addAction(self.selectionInfo_viewToolbar_action)
		toolbar_menu.addAction(self.radarAssistance_viewToolbar_action)
		toolbar_menu.addAction(self.workspace_viewToolbar_action)
		self.toolbars_view_menuAction.setMenu(toolbar_menu)
		
		if env.airport_data == None or len(env.airport_data.viewpoints) == 0:
			self.viewpointSelection_view_menuAction.setEnabled(False)
		else:
			viewPointSelection_menu = QMenu()
			viewPointSelection_actionGroup = QActionGroup(self)
			for vp_i, (vp_pos, vp_h, vp_name) in enumerate(env.airport_data.viewpoints):
				action = QAction('%s - %d ft ASFC' % (vp_name, vp_h + .5), self)
				action.setCheckable(True)
				action.triggered.connect(lambda ignore_checked, i=vp_i: self.selectIndicateViewpoint(i))
				viewPointSelection_actionGroup.addAction(action)
			actions = viewPointSelection_actionGroup.actions()
			viewPointSelection_menu.addActions(actions)
			self.viewpointSelection_view_menuAction.setMenu(viewPointSelection_menu)
			try:
				actions[settings.selected_viewpoint].setChecked(True)
			except IndexError:
				actions[0].setChecked(True)
		
		## Memory-persistent windows and dialogs
		self.solo_connect_dialog_AD = StartSoloDialog_AD(self)
		self.MP_connect_dialog = StartFlightGearMPdialog(self)
		self.start_student_session_dialog = StartStudentSessionDialog(self)
		self.recall_cheat_dialog = DiscardedStripsDialog(self, ShelfFilterModel(self, env.discarded_strips, False), 'Sent and deleted strips')
		self.shelf_dialog = DiscardedStripsDialog(self, ShelfFilterModel(self, env.discarded_strips, True), 'Strip shelf')
		self.environment_info_dialog = EnvironmentInfoDialog(self)
		self.about_dialog = AboutDialog(self)
		self.teaching_console = TeachingConsole(parent=self)
		self.unit_converter = UnitConversionWindow(parent=self)
		self.world_airport_navigator = WorldAirportNavigator(parent=self)
		self.quick_reference = QuickReference(parent=self)
		for w in self.teaching_console, self.unit_converter, self.world_airport_navigator, self.quick_reference:
			w.setWindowFlags(Qt.Window)
			w.installEventFilter(RadioKeyEventFilter(w))
		
		# Make a few actions always visible
		self.addAction(self.newStrip_action)
		self.addAction(self.newLinkedStrip_action)
		self.addAction(self.newFPL_action)
		self.addAction(self.newLinkedFPL_action)
		self.addAction(self.startTimer1_action)
		self.addAction(self.forceStartTimer1_action)
		self.addAction(self.startTimer2_action)
		self.addAction(self.forceStartTimer2_action)
		self.addAction(self.notificationSounds_options_action)
		self.addAction(self.quickReference_help_action)
		self.addAction(self.saveDockLayout_view_action)
		self.addAction(self.recallWindowState_view_action)
		
		# Populate icons
		self.newStrip_action.setIcon(QIcon(IconFile.action_newStrip))
		self.newLinkedStrip_action.setIcon(QIcon(IconFile.action_newLinkedStrip))
		self.newFPL_action.setIcon(QIcon(IconFile.action_newFPL))
		self.newLinkedFPL_action.setIcon(QIcon(IconFile.action_newLinkedFPL))
		self.teachingConsole_view_action.setIcon(QIcon(IconFile.panel_teaching))
		self.unitConversionTool_view_action.setIcon(QIcon(IconFile.panel_unitConv))
		self.worldAirportNavigator_view_action.setIcon(QIcon(IconFile.panel_airportList))
		self.environmentInfo_view_action.setIcon(QIcon(IconFile.panel_envInfo))
		self.generalSettings_options_action.setIcon(QIcon(IconFile.action_generalSettings))
		self.soloSessionSettings_system_action.setIcon(QIcon(IconFile.action_sessionSettings))
		self.runwaysInUse_options_action.setIcon(QIcon(IconFile.action_runwayUse))
		self.newLooseStripBay_view_action.setIcon(QIcon(IconFile.action_newLooseStripBay))
		self.newRadarScreen_view_action.setIcon(QIcon(IconFile.action_newRadarScreen))
		self.newStripRackPanel_view_action.setIcon(QIcon(IconFile.action_newRackPanel))
		self.popOutCurrentWindow_view_action.setIcon(QIcon(IconFile.action_popOutWindow))
		self.reclaimPoppedOutWindows_view_action.setIcon(QIcon(IconFile.action_reclaimWindows))
		self.primaryRadar_options_action.setIcon(QIcon(IconFile.option_primaryRadar))
		self.approachSpacingHints_options_action.setIcon(QIcon(IconFile.option_approachSpacingHints))
		self.runwayOccupationWarnings_options_action.setIcon(QIcon(IconFile.option_runwayOccupationMonitor))
		self.routeConflictWarnings_options_action.setIcon(QIcon(IconFile.option_routeConflictWarnings))
		self.trafficIdentification_options_action.setIcon(QIcon(IconFile.option_identificationAssistant))
		
		setDockAndActionIcon(IconFile.panel_ATCs, self.handovers_dockView_action, self.handover_dock)
		setDockAndActionIcon(IconFile.panel_atcChat, self.atcTextChat_dockView_action, self.atcTextChat_dock)
		setDockAndActionIcon(IconFile.panel_CPDLC, self.cpdlc_dockView_action, self.CPDLC_dock)
		setDockAndActionIcon(IconFile.panel_FPLs, self.FPLs_dockView_action, self.FPLlist_dock)
		setDockAndActionIcon(IconFile.panel_instructions, self.instructions_dockView_action, self.instructions_dock)
		setDockAndActionIcon(IconFile.panel_navigator, self.navpoints_dockView_action, self.navigator_dock)
		setDockAndActionIcon(IconFile.panel_notepads, self.notepads_dockView_action, self.notepads_dock)
		setDockAndActionIcon(IconFile.panel_notifications, self.notificationArea_dockView_action, self.notification_dock)
		setDockAndActionIcon(IconFile.panel_radios, self.fgcom_dockView_action, self.radio_dock)
		setDockAndActionIcon(IconFile.panel_runwayBox, self.runwayBoxes_dockView_action, self.rwyBoxes_dock)
		setDockAndActionIcon(IconFile.panel_selInfo, self.radarContactDetails_dockView_action, self.selection_info_dock)
		setDockAndActionIcon(IconFile.panel_racks, self.strips_dockView_action, self.strip_dock)
		setDockAndActionIcon(IconFile.panel_txtChat, self.radioTextChat_dockView_action, self.radioTextChat_dock)
		setDockAndActionIcon(IconFile.panel_twrView, self.towerView_dockView_action, self.towerView_dock)
		setDockAndActionIcon(IconFile.panel_weather, self.weather_dockView_action, self.weather_dock)
		
		# action TICKED STATES (set here before connections)
		self.windowedWorkspace_view_action.setChecked(settings.saved_workspace_windowed_view)
		self.verticalRwyBoxLayout_view_action.setChecked(settings.vertical_runway_box_layout)
		self.notificationSounds_options_action.setChecked(settings.notification_sounds_enabled)
		self.primaryRadar_options_action.setChecked(settings.primary_radar_active)
		self.routeConflictWarnings_options_action.setChecked(settings.route_conflict_warnings)
		self.trafficIdentification_options_action.setChecked(settings.traffic_identification_assistant)
		self.runwayOccupationWarnings_options_action.setChecked(settings.monitor_runway_occupation)
		self.approachSpacingHints_options_action.setChecked(settings.APP_spacing_hints)
		
		# action CONNECTIONS
		# non-menu actions
		self.newStrip_action.triggered.connect(lambda: new_strip_dialog(self, default_rack_name, linkToSelection=False))
		self.newLinkedStrip_action.triggered.connect(lambda: new_strip_dialog(self, default_rack_name, linkToSelection=True))
		self.newFPL_action.triggered.connect(lambda: self.FPLlist_pane.createLocalFPL(link=None))
		self.newLinkedFPL_action.triggered.connect(lambda: self.FPLlist_pane.createLocalFPL(link=selection.strip))
		self.startTimer1_action.triggered.connect(lambda: self.startTimer(0, False))
		self.forceStartTimer1_action.triggered.connect(lambda: self.startTimer(0, True))
		self.startTimer2_action.triggered.connect(lambda: self.startTimer(1, False))
		self.forceStartTimer2_action.triggered.connect(lambda: self.startTimer(1, True))
		# system menu
		self.soloSession_system_action.triggered.connect(lambda: self.startStopSession(self.start_solo))
		self.connectFlightGearMP_system_action.triggered.connect(lambda: self.startStopSession(self.start_FlightGearMP))
		self.teacherSession_system_action.triggered.connect(lambda: self.startStopSession(self.start_teaching))
		self.studentSession_system_action.triggered.connect(lambda: self.startStopSession(self.start_learning))
		self.reloadAdditionalViewers_system_action.triggered.connect(self.reloadAdditionalViewers)
		self.reloadBgImages_system_action.triggered.connect(self.reloadBackgroundImages)
		self.reloadColourConfig_system_action.triggered.connect(self.reloadColourConfig)
		self.reloadRoutePresets_system_action.triggered.connect(self.reloadRoutePresets)
		self.reloadEntryExitPoints_system_action.triggered.connect(self.reloadEntryExitPoints)
		self.announceFgSession_system_action.triggered.connect(self.announceFgSession)
		self.fgcomEchoTest_system_action.triggered.connect(self.radio_pane.performEchoTest)
		self.extractSectorFile_system_action.triggered.connect(self.extractSectorFile)
		self.repositionBgImages_system_action.triggered.connect(self.repositionRadarBgImages)
		self.measuringLogsCoordinates_system_action.toggled.connect(self.switchMeasuringCoordsLog)
		self.airportGateway_system_action.triggered.connect(lambda: self.goToURL(airport_gateway_URL))
		self.openStreetMap_system_action.triggered.connect(lambda: self.goToURL(mk_OSM_URL(env.radarPos())))
		self.soloSessionSettings_system_action.triggered.connect(self.openSoloSessionSettings)
		self.locationSettings_system_action.triggered.connect(self.openLocalSettings)
		self.systemSettings_system_action.triggered.connect(self.openSystemSettings)
		self.changeLocation_system_action.triggered.connect(self.changeLocation)
		self.quit_system_action.triggered.connect(self.close)
		# view menu
		self.saveDockLayout_view_action.triggered.connect(self.saveDockLayout)
		self.recallWindowState_view_action.triggered.connect(self.recallWindowState)
		self.handovers_dockView_action.triggered.connect(lambda: self.raiseDock(self.handover_dock))
		self.atcTextChat_dockView_action.triggered.connect(lambda: self.raiseDock(self.atcTextChat_dock))
		self.cpdlc_dockView_action.triggered.connect(lambda: self.raiseDock(self.CPDLC_dock))
		self.FPLs_dockView_action.triggered.connect(lambda: self.raiseDock(self.FPLlist_dock))
		self.instructions_dockView_action.triggered.connect(lambda: self.raiseDock(self.instructions_dock))
		self.navpoints_dockView_action.triggered.connect(lambda: self.raiseDock(self.navigator_dock))
		self.notepads_dockView_action.triggered.connect(lambda: self.raiseDock(self.notepads_dock))
		self.notificationArea_dockView_action.triggered.connect(lambda: self.raiseDock(self.notification_dock))
		self.fgcom_dockView_action.triggered.connect(lambda: self.raiseDock(self.radio_dock))
		self.runwayBoxes_dockView_action.triggered.connect(lambda: self.raiseDock(self.rwyBoxes_dock))
		self.radarContactDetails_dockView_action.triggered.connect(lambda: self.raiseDock(self.selection_info_dock))
		self.strips_dockView_action.triggered.connect(lambda: self.raiseDock(self.strip_dock))
		self.radioTextChat_dockView_action.triggered.connect(lambda: self.raiseDock(self.radioTextChat_dock))
		self.towerView_dockView_action.triggered.connect(lambda: self.raiseDock(self.towerView_dock))
		self.weather_dockView_action.triggered.connect(lambda: self.raiseDock(self.weather_dock))
		self.windowedWorkspace_view_action.toggled.connect(self.central_workspace.switchWindowedView)
		self.popOutCurrentWindow_view_action.triggered.connect(self.central_workspace.popOutCurrentWindow)
		self.reclaimPoppedOutWindows_view_action.triggered.connect(self.central_workspace.reclaimPoppedOutWidgets)
		self.newLooseStripBay_view_action.triggered.connect(lambda: self.central_workspace.addWorkspaceWidget(WorkspaceWidget.LOOSE_BAY))
		self.newRadarScreen_view_action.triggered.connect(lambda: self.central_workspace.addWorkspaceWidget(WorkspaceWidget.RADAR_SCREEN))
		self.newStripRackPanel_view_action.triggered.connect(lambda: self.central_workspace.addWorkspaceWidget(WorkspaceWidget.STRIP_PANEL))
		self.verticalRwyBoxLayout_view_action.toggled.connect(self.switchVerticalRwyBoxLayout)
		self.towerView_view_action.triggered.connect(self.toggleTowerWindow)
		self.addViewer_view_action.triggered.connect(self.addView)
		self.listViewers_view_action.triggered.connect(self.listAdditionalViews)
		self.activateAdditionalViewers_view_action.toggled.connect(self.activateAdditionalViews)
		self.removeViewer_view_action.triggered.connect(self.removeView)
		self.teachingConsole_view_action.triggered.connect(self.teaching_console.show)
		self.unitConversionTool_view_action.triggered.connect(self.unit_converter.show)
		self.worldAirportNavigator_view_action.triggered.connect(self.world_airport_navigator.show)
		self.environmentInfo_view_action.triggered.connect(self.environment_info_dialog.exec)
		# options menu
		self.runwaysInUse_options_action.triggered.connect(self.configureRunwayUse)
		self.notificationSounds_options_action.toggled.connect(self.switchNotificationSounds)
		self.primaryRadar_options_action.toggled.connect(self.switchPrimaryRadar)
		self.routeConflictWarnings_options_action.toggled.connect(self.switchConflictWarnings)
		self.trafficIdentification_options_action.toggled.connect(self.switchTrafficIdentification)
		self.runwayOccupationWarnings_options_action.toggled.connect(self.switchRwyOccupationIndications)
		self.approachSpacingHints_options_action.toggled.connect(self.switchApproachSpacingHints)
		self.generalSettings_options_action.triggered.connect(self.openGeneralSettings)
		# cheat menu
		self.pauseSimulation_cheat_action.toggled.connect(self.pauseSession)
		self.spawnAircraft_cheat_action.triggered.connect(self.spawnAircraft)
		self.killSelectedAircraft_cheat_action.triggered.connect(self.killSelectedAircraft)
		self.popUpMsgOnRejectedInstr_cheat_action.toggled.connect(self.setRejectedInstrPopUp)
		self.showRecognisedVoiceStrings_cheat_action.toggled.connect(self.setShowRecognisedVoiceStrings)
		self.ensureClearWeather_cheat_action.toggled.connect(self.ensureClearWeather)
		self.ensureDayLight_cheat_action.triggered.connect(self.towerView_pane.ensureDayLight)
		self.changeTowerHeight_cheat_action.triggered.connect(self.changeTowerHeight)
		self.recallDiscardedStrip_cheat_action.triggered.connect(self.recall_cheat_dialog.exec)
		self.radarCheatMode_cheat_action.toggled.connect(self.setRadarCheatMode)
		# help menu
		self.quickReference_help_action.triggered.connect(self.quick_reference.show)
		self.videoTutorial_help_action.triggered.connect(lambda: self.goToURL(video_tutorial_URL))
		self.FAQ_help_action.triggered.connect(lambda: self.goToURL(FAQ_URL))
		self.about_help_action.triggered.connect(self.about_dialog.exec)
		
		## More signal connections
		signals.openShelfRequest.connect(self.shelf_dialog.exec)
		signals.privateAtcChatRequest.connect(lambda: self.raiseDock(self.atcTextChat_dock))
		signals.stripRecall.connect(recover_strip)
		env.radar.blip.connect(env.strips.refreshViews)
		env.radar.lostContact.connect(self.aircraftHasDisappeared)
		signals.aircraftKilled.connect(self.aircraftHasDisappeared)
		env.strips.rwyBoxFreed.connect(lambda box, strip: env.airport_data.physicalRunway_restartWtcTimer(box, strip.lookup(FPL.WTC)))
		env.rdf.signalChanged.connect(self.updateRDF)
		signals.statusBarMsg.connect(lambda msg: self.statusbar.showMessage(msg, status_bar_message_timeout))
		signals.newWeather.connect(self.updateWeatherIfPrimary)
		signals.kbdPTT.connect(self.updatePTT)
		signals.sessionStarted.connect(self.sessionHasStarted)
		signals.sessionEnded.connect(self.sessionHasEnded)
		signals.towerViewProcessToggled.connect(self.towerView_view_action.setChecked)
		signals.towerViewProcessToggled.connect(self.towerView_cheat_menu.setEnabled)
		signals.stripInfoChanged.connect(env.strips.refreshViews)
		signals.fastClockTick.connect(self.updateClock)
		signals.fastClockTick.connect(env.cpdlc.updateAckStatuses)
		signals.slowClockTick.connect(strip_auto_print_check)
		signals.stripEditRequest.connect(lambda strip: edit_strip(self, strip))
		signals.selectionChanged.connect(self.updateStripFplActions)
		signals.receiveStrip.connect(receive_strip)
		signals.handoverFailure.connect(self.recoverFailedHandover)
		signals.sessionPaused.connect(env.radar.stopSweeping)
		signals.sessionResumed.connect(env.radar.startSweeping)
		signals.aircraftKilled.connect(env.radar.silentlyForgetContact)
		signals.rackVisibilityLost.connect(self.collectClosedRacks)
		signals.localSettingsChanged.connect(env.rdf.clearAllSignals)
		signals.localSettingsChanged.connect(self.updateRDF)
		
		## MISC GUI setup
		self.strip_pane.setViewRacks([default_rack_name]) # will be moved out if a rack panel's saved "visible_racks" claims it [*1]
		self.strip_pane.restoreState(settings.saved_strip_dock_state) # [*1]
		self.central_workspace.restoreWorkspaceWindows(settings.saved_workspace_windows)
		self.central_workspace.switchWindowedView(settings.saved_workspace_windowed_view) # keep this after restoring windows!
		
		self.subsecond_ticker = Ticker(signals.fastClockTick.emit, parent=self)
		self.subminute_ticker = Ticker(signals.slowClockTick.emit, parent=self)
		self.subsecond_ticker.start_stopOnZero(subsecond_tick_interval)
		self.subminute_ticker.start_stopOnZero(subminute_tick_interval)
		self.towerView_cheat_menu.setEnabled(False)
		self.solo_cheat_menu.setEnabled(False)
		self.updateClock()
		self.updateWeatherIfPrimary(settings.primary_METAR_station, None)
		self.updateStripFplActions()
		self.last_RDF_qdm = None
		self.updateRDF()
		self.updatePTT(0, False)
		# Disable some base airport stuff if doing CTR
		if env.airport_data == None:
			self.towerView_view_action.setEnabled(False)
			self.runwaysInUse_options_action.setEnabled(False)
			self.runwayOccupationWarnings_options_action.setEnabled(False)
		# Finish
		self.atcTextChat_pane.switchAtcChatFilter(None) # Show GUI on general chat room at start
		if speech_recognition_available:
			prepare_SR_language_files()
Ejemplo n.º 25
0
class Ui_MainWindow(QMainWindow, Ui_MainWindowBase):
    updateFrame = pyqtSignal()

    def __init__(self):
        super(Ui_MainWindow, self).__init__()
        self.setupUi(self)

        self.videoPlaybackInit()
        self.imgInit()
        self.menuInit()

        self.radiusSpinBox.valueChanged.connect(self.radiusSpinBoxValueChanged)
        self.lineWidthSpinBox.valueChanged.connect(self.lineWidthSpinBoxValueChanged)
        self.overlayFrameNoSpinBox.valueChanged.connect(self.overlayFrameNoSpinBoxValueChanged)
        self.stackedWidget.currentChanged.connect(self.stackedWidgetCurrentChanged)

        self.arrowCheckBox.stateChanged.connect(self.arrowCheckBoxStateChanged)
        self.pathCheckBox.stateChanged.connect(self.pathCheckBoxStateChanged)
        self.reverseArrowColorCheckBox.stateChanged.connect(self.reverseArrowColorCheckBoxStateChanged)

        self.opaqueCheckBox.stateChanged.connect(self.opaqueCheckBoxStateChanged)

        self.videoPlaybackWidget.setSignalSlotMode()
        self.updateFrame.connect(self.videoPlaybackWidget.videoPlayback)

        self.filter = None
        self.filterIO = None
        self.isInitialized = False
        self.item_dict = {}
        self.data_dict = {}
        self.trackingPathGroup = None
        self.df = {}
        self.inputPixmapItem = None
        self.cv_img = None
        self.filePath = None
        self.savedFlag = True

    def dragEnterEvent(self,event):
        event.acceptProposedAction()

    def dropEvent(self,event):
        mime = event.mimeData()
        if mime.hasUrls():
            urls = mime.urls()
            if len(urls) > 0:
                self.processDropedFile(urls[0].toLocalFile())

        event.acceptProposedAction()

    def closeEvent(self, event):
        if len(self.df.keys())==0 or self.savedFlag:
            return

        quit_msg = "Data is not saved.\nAre you sure you want to exit the program?"
        reply = QtWidgets.QMessageBox.question(
                self,
                'Warning',
                quit_msg,
                QtWidgets.QMessageBox.Yes,
                QtWidgets.QMessageBox.No
                )

        if reply == QtWidgets.QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()

    def processDropedFile(self,filePath):
        root,ext = os.path.splitext(filePath)
        if ext == ".filter":
            # Read Filter
            self.openFilterFile(filePath=filePath)
            return
        elif self.openVideoFile(filePath=filePath):
            return

    def arrowCheckBoxStateChanged(self, state):
        if 'arrow' not in self.item_dict.keys():
            return

        for arrow_item in self.item_dict['arrow']:
            if state==Qt.Unchecked:
                arrow_item.hide()
            if state==Qt.Checked:
                arrow_item.show()

        self.updateInputGraphicsView()

    def pathCheckBoxStateChanged(self, state):
        if self.trackingPathGroup is None:
            return

        if state==Qt.Unchecked:
            self.trackingPathGroup.hide()
        if state==Qt.Checked:
            self.trackingPathGroup.show()

        self.updateInputGraphicsView()

    def reverseArrowColorCheckBoxStateChanged(self, state):
        if 'arrow' not in self.item_dict.keys():
            return

        for arrow_item in self.item_dict['arrow']:
            if state==Qt.Unchecked:
                arrow_item.setColor([0,0,0])
            if state==Qt.Checked:
                arrow_item.setColor([255,255,255])

        self.updateInputGraphicsView()

    def opaqueCheckBoxStateChanged(self, state):
        if self.trackingPathGroup is None:
            return

        if state==Qt.Unchecked:
            self.trackingPathGroup.setOpacity(0.5)
        if state==Qt.Checked:
            self.trackingPathGroup.setOpacity(1.0)

        self.updateInputGraphicsView()

    def reset(self):
        self.videoPlaybackWidget.stop()
        self.videoPlaybackWidget.moveToFrame(0)

    def videoPlaybackInit(self):
        self.videoPlaybackWidget.hide()
        self.videoPlaybackWidget.frameChanged.connect(self.setFrame, type=Qt.QueuedConnection)

    def setFrame(self, frame, frameNo):
        if frame is not None:
            self.cv_img = frame
            self.currentFrameNo = frameNo
            self.updateInputGraphicsView()
            self.evaluate()
        return

    def imgInit(self):
        self.inputScene = QGraphicsScene()
        self.inputGraphicsView.setScene(self.inputScene)
        self.inputGraphicsView.resizeEvent = self.inputGraphicsViewResized

    def menuInit(self):
        self.actionOpenVideo.triggered.connect(self.openVideoFile)
        self.actionOpenFilterSetting.triggered.connect(self.openFilterFile)

        self.actionSaveCSVFile.triggered.connect(self.saveCSVFile)

        self.actionRunObjectTracking.triggered.connect(self.runObjectTracking)
        self.actionTrackingPathColor.triggered.connect(self.openTrackingPathColorSelectorDialog)

        self.menuAlgorithmsActionGroup = QActionGroup(self.menuAlgorithms)

        path_list = [[tracking_system_path, currentDirPath], ]
        if os.path.exists(user_defined_lib_path):
            path_list.append([user_defined_tracking_system_path, user_defined_lib_path])
        for system_path in path_list:
            for module_path in get_modules(system_path[0], system_path[1]):
                module_str = '.'.join(module_path)

                try:
                    module = importlib.import_module(module_str)

                    if not hasattr(module, 'Widget'):
                        continue

                    class_def = getattr(module, "Widget")
                    if not issubclass(class_def, QtWidgets.QWidget):
                        continue

                    widget = class_def(self.stackedWidget)
                    widget.reset.connect(self.resetDataframe)
                    widget.restart.connect(self.restartDataframe)
                    self.stackedWidget.addWidget(widget)

                    action = self.menuAlgorithms.addAction(widget.get_name())
                    action.triggered.connect(self.generateAlgorithmsMenuClicked(widget))
                    action.setCheckable(True)
                    action.setActionGroup(self.menuAlgorithmsActionGroup)

                    if len(self.menuAlgorithmsActionGroup.actions()) == 1:
                        action.setChecked(True)
                        self.algorithmSettingsGroupBox.setTitle(widget.get_name())

                except Exception as e:
                    if system_path[1] is user_defined_lib_path:
                        msg = 'Tracking Lib. Load Fail: {0}\n{1}'.format(module_str, e)
                        self.generateCriticalMessage(msg)
                    continue

    def openTrackingPathColorSelectorDialog(self, activated=False):
        if self.trackingPathGroup is not None:
            self.trackingPathGroup.openColorSelectorDialog(self)

    def generateAlgorithmsMenuClicked(self, widget):
        def action_triggered(activated=False):
            if widget is not self.stackedWidget.currentWidget():
                self.stackedWidget.setCurrentWidget(widget)
            else:
                pass
        return action_triggered

    def initializeEventDialog(self):
        quit_msg = "Data is not saved.\nAre you sure you want to reset?"
        reply = QtWidgets.QMessageBox.question(
                self,
                'Warning',
                quit_msg,
                QtWidgets.QMessageBox.Yes,
                QtWidgets.QMessageBox.No
                )

        if reply == QtWidgets.QMessageBox.Yes:
            return True
        else:
            return False

    def openVideoFile(self, activated=False, filePath = None):
        if filePath is None:
            filePath, _ = QFileDialog.getOpenFileName(None, 'Open Video File', userDir)

        if len(filePath) is not 0:
            if filterOperation is not None and self.videoPlaybackWidget.isOpened():
                if self.initializeEventDialog():
                    global filterOperation
                    filterOperation = None
                    self.removeTrackingGraphicsItems()
                    self.savedFlag = True
                else:
                    return
            self.filePath = filePath
            ret = self.videoPlaybackWidget.openVideo(filePath)
            if not ret:
                return False

            self.videoPlaybackWidget.show()

            self.cv_img = self.videoPlaybackWidget.getCurrentFrame()
            self.currentFrameNo = 0
            self.videoPlaybackWidget.setMaxTickableFrameNo(0)
            self.initializeTrackingSystem()

            return True
        else:
            return False

    def openFilterFile(self, activated=False, filePath = None):
        if filePath is None:
            filePath, _ = QFileDialog.getOpenFileName(None, 'Open Block File', userDir, "Block files (*.filter)")

        if len(filePath) is not 0:
            if filterOperation is not None and self.videoPlaybackWidget.isOpened():
                if self.initializeEventDialog():
                    self.videoPlaybackWidget.closeVideo()
                    self.videoPlaybackWidget.hide()
                    self.removeTrackingGraphicsItems()
                    self.inputScene.removeItem(self.inputPixmapItem)
                    self.savedFlag = True
                else:
                    return
            logger.debug("Open Filter file: {0}".format(filePath))

            self.filterIO = FilterIO(filePath)

            exec(self.filterIO.getFilterCode(), globals())

            self.filter = None

            self.initializeTrackingSystem()
            self.evaluate()

    def saveCSVFile(self, activated=False, filePath = None):
        if len(self.df.keys())!=0:
            dirctory = os.path.dirname(self.filePath)
            base_name = os.path.splitext(os.path.basename(self.filePath))[0]

            path = os.path.join(dirctory, '{0}-{1}.txt'.format(base_name, "info"))
            filePath, _ = QFileDialog.getSaveFileName(None, 'Save Info File', path, "TXT files (*.txt)")

            if len(filePath) is not 0:
                logger.debug("Saving Info file: {0}".format(filePath))

                with open(filePath, 'w') as fp:
                    fp.write(self.videoPlaybackWidget.getVideoInfo())

            for attr, df in self.df.items():
                path = os.path.join(dirctory, '{0}-{1}.csv'.format(base_name, attr))
                filePath, _ = QFileDialog.getSaveFileName(None, 'Save CSV File', path, "CSV files (*.csv)")

                if len(filePath) is not 0:
                    logger.debug("Saving CSV file: {0}".format(filePath))

                    row_max = self.currentFrameNo
                    df = df.loc[:row_max]
                    levels = df.columns.levels
                    col = ['{0}{1}'.format(l,i) for i in levels[0] for l in levels[1]]
                    df.columns = col

                    df.to_csv(filePath)

            for k, v in self.data_dict.items():
                path = os.path.join(dirctory, '{0}-{1}.json'.format(base_name, k))
                filePath, _ = QFileDialog.getSaveFileName(None, 'Save JSON File', path, "JSON files (*.json)")

                if len(filePath) is not 0:
                    logger.debug("Saving JSON file: {0}".format(filePath))
                    with open(filePath, 'w') as f_p:
                        json.dump(v, f_p)

            path = os.path.join(dirctory, '{0}-colors.color'.format(base_name))
            filePath, _ = QFileDialog.getSaveFileName(None, 'Save Color File', path, "Color files (*.color)")
            if len(filePath) is not 0:
                logger.debug("Saving Color file: {0}".format(filePath))
                self.trackingPathGroup.saveColors(filePath)

            self.savedFlag = True

    def radiusSpinBoxValueChanged(self, i):
        if self.trackingPathGroup is not None:
            self.trackingPathGroup.setRadius(i)
        self.updateInputGraphicsView()

    def lineWidthSpinBoxValueChanged(self, i):
        if self.trackingPathGroup is not None:
            self.trackingPathGroup.setLineWidth(i)
        self.updateInputGraphicsView()

    def overlayFrameNoSpinBoxValueChanged(self, i):
        if self.trackingPathGroup is not None:
            self.trackingPathGroup.setOverlayFrameNo(i)
        self.updateInputGraphicsView()

    def stackedWidgetCurrentChanged(self, i):
        currentWidget = self.stackedWidget.currentWidget()

        currentWidget.estimator_init()
        self.algorithmSettingsGroupBox.setTitle(currentWidget.get_name())
        self.resetDataframe()

    def updateInputGraphicsView(self):
        if self.inputPixmapItem is not None:
            self.inputScene.removeItem(self.inputPixmapItem)

        if self.filter is not None and hasattr(self.filter, "resize_flag") and self.filter.resize_flag:
            qimg = misc.cvMatToQImage(cv2.pyrDown(self.cv_img))
        else:
            qimg = misc.cvMatToQImage(self.cv_img)

        pixmap = QPixmap.fromImage(qimg)

        self.inputPixmapItem = QGraphicsPixmapItem(pixmap)
        rect = QtCore.QRectF(pixmap.rect())
        self.inputScene.setSceneRect(rect)
        self.inputScene.addItem(self.inputPixmapItem)

        self.inputGraphicsView.viewport().update()
        self.inputGraphicsViewResized()

    def inputGraphicsViewResized(self, event=None):
        self.inputGraphicsView.fitInView(self.inputScene.sceneRect(), QtCore.Qt.KeepAspectRatio)

    def updatePath(self):
        try:
            attrs = self.stackedWidget.currentWidget().get_attributes()
            attrs.keys()
        except Exception as e:
            msg = 'Tracking Lib. Attributes Error:\n{}'.format(e)
            self.generateCriticalMessage(msg)
            return

        if 'position' in attrs:
            self.trackingPathGroup.setPoints(self.currentFrameNo)

        if 'arrow' in attrs:
            for i, arrow_item in enumerate(self.item_dict['arrow']):
                begin = self.df['position'].loc[self.currentFrameNo, i].as_matrix()
                end = self.df['arrow'].loc[self.currentFrameNo, i].as_matrix()
                arrow_item.setPosition(begin, end)

        if 'path' in attrs:
            for path_item, path_data in zip(self.item_dict['path'], self.data_dict['path'][self.currentFrameNo]):
                poly = QPolygonF()
                for p in path_data:
                    poly.append(QPointF(*p))

                painter_path = QPainterPath()
                painter_path.addPolygon(poly)
                path_item.setPath(painter_path)

                pen = QPen(Qt.blue)
                pen.setWidth(2)
                path_item.setPen(pen)

        if 'polygon' in attrs:
            for path_item, path_data in zip(self.item_dict['polygon'], self.data_dict['polygon'][self.currentFrameNo]):
                poly = QPolygonF()
                for p in path_data:
                    poly.append(QPointF(*p))

                painter_path = QPainterPath()
                painter_path.addPolygon(poly)
                path_item.setPath(painter_path)

                pen = QPen(Qt.black)
                pen.setWidth(1)
                path_item.setPen(pen)

        if 'rect' in attrs:
            for rect_item, rect in zip(self.item_dict['rect'], self.data_dict['rect'][self.currentFrameNo]):
                rect_item.setRect(QRectF(QPointF(*rect[0]), QPointF(*rect[1])))

    def resetDataframe(self):
        self.initializeTrackingSystem()
        self.evaluate()

    def restartDataframe(self):
        if len(self.df.keys())==0:
            return

        for attr in self.df.keys():
            self.df[attr].loc[self.currentFrameNo+1:] = np.nan
        for k in self.data_dict.keys():
            for kk in self.data_dict[k]:
                if kk>currentFrameNo:
                    del self.data_dict[k]

        df = {}
        for attr in self.df.keys():
            df[attr] = self.df[attr].loc[self.currentFrameNo]

        kv = {k:[] for k in self.df.keys()}
        for key, value in kv.items():
            mul_levs = df[key].index.levels
            for i in mul_levs[0]:
                value.append(df[key][i].as_matrix())

        for k, v in self.data_dict.items():
            kv[key] = v

        for key, value in kv.items():
            kv[key] = np.array(value)

        try:
            widget = self.stackedWidget.currentWidget()
            widget.reset_estimator(kv)
        except Exception as e:
            msg = 'Tracking Lib. Reset Fail:\n{}'.format(e)
            self.generateCriticalMessage(msg)

    def removeTrackingGraphicsItems(self):
        if self.trackingPathGroup is not None:
            self.inputScene.removeItem(self.trackingPathGroup)
            self.trackingPathGroup = None

        for k, v in self.item_dict.items():
            [self.inputScene.removeItem(item) for item in v]
            v.clear()

    def initializeTrackingSystem(self):
        self.isInitialized = False
        if  not (self.videoPlaybackWidget.isOpened() and filterOperation is not None):
            return False

        if self.currentFrameNo != 0:
            ret, frame = self.videoPlaybackWidget.readFrame(0)
            self.cv_img = frame
            self.currentFrameNo = 0
            self.videoPlaybackWidget.setSliderValueWithoutSignal(0)

        self.filter = filterOperation(self.cv_img)
        self.filter.fgbg = self.filterIO.getBackgroundImg()
        self.filter.isInit = True

        try:
            tracking_n = self.stackedWidget.currentWidget().get_tracking_n()
            attrs = self.stackedWidget.currentWidget().get_attributes()
            attrs.keys()
        except Exception as e:
            msg = 'Tracking Lib. Tracking N or attributes Error:\n{}'.format(e)
            self.generateCriticalMessage(msg)
            return

        max_frame_pos = self.videoPlaybackWidget.getMaxFramePos()

        self.df = {}
        for k, t in attrs.items():
            if t is None:
                self.data_dict[k] = {}
                self.data_dict[k]['name'] = k
            else:
                tuples = []
                for i in range(tracking_n):
                    for v in t:
                        tuples.append((i, v))
                col = pd.MultiIndex.from_tuples(tuples)
                self.df[k] = pd.DataFrame(index=range(max_frame_pos+1), columns=col, dtype=np.float64).sort_index().sort_index(axis=1)
                self.df[k].index.name = k

        self.removeTrackingGraphicsItems()

        if 'position' in attrs:
            self.trackingPathGroup = TrackingPathGroup()
            self.trackingPathGroup.setRect(self.inputScene.sceneRect())
            if self.pathCheckBox.checkState()==Qt.Unchecked:
                self.trackingPathGroup.hide()

            self.inputScene.addItem(self.trackingPathGroup)
            self.trackingPathGroup.setDataFrame(self.df['position'])

            lw = self.trackingPathGroup.autoAdjustLineWidth(self.cv_img.shape)
            r = self.trackingPathGroup.autoAdjustRadius(self.cv_img.shape)
            self.lineWidthSpinBox.setValue(lw)
            self.radiusSpinBox.setValue(r)

            self.trackingPathGroup.setItemsAreMovable(True)

        if 'rect' in attrs:
            self.item_dict['rect'] = [QGraphicsRectItem() for i in range(tracking_n)]
            for rect_item in self.item_dict['rect']:
                rect_item.setZValue(1000)
                self.inputScene.addItem(rect_item)

        if 'arrow' in attrs:
            self.item_dict['arrow'] = [MovableArrow() for i in range(tracking_n)]
            for arrow_item in self.item_dict['arrow']:
                arrow_item.setZValue(900)
                if self.arrowCheckBox.checkState()==Qt.Unchecked:
                    arrow_item.hide()
                self.inputScene.addItem(arrow_item)

        if 'path' in attrs:
            self.item_dict['path'] = [QGraphicsPathItem() for i in range(tracking_n)]
            for path_item in self.item_dict['path']:
                path_item.setZValue(900)
                self.inputScene.addItem(path_item)

        if 'polygon' in attrs:
            self.item_dict['polygon'] = [QGraphicsPathItem() for i in range(tracking_n)]
            for path_item in self.item_dict['polygon']:
                path_item.setZValue(900)
                self.inputScene.addItem(path_item)

        self.videoPlaybackWidget.setMaxTickableFrameNo(0)
        # if self.currentFrameNo != 0:
        #     self.videoPlaybackWidget.moveToFrame(0)
        self.videoPlaybackWidget.setPlaybackDelta(self.playbackDeltaSpinBox.value())

        self.isInitialized = True

    def evaluate(self, update=True):
        if not self.isInitialized:
            return

        if len(self.df.keys())!=0 and np.all([np.all(pd.notnull(df.loc[self.currentFrameNo])) for df in self.df.values()]):
            print('update')
            self.updatePath()
            self.updateInputGraphicsView()
            self.updateFrame.emit()
            return

        img = self.filter.filterFunc(self.cv_img.copy())

        try:
            widget = self.stackedWidget.currentWidget()
            res = widget.track(self.cv_img.copy(), img)
            attrs = widget.get_attributes()
        except Exception as e:
            msg = 'Tracking Lib. Tracking method Fail:\n{}'.format(e)
            self.generateCriticalMessage(msg)
            return

        for k,v in res.items():
            if k=='path' or k=='rect' or k=='polygon':
                self.data_dict[k][self.currentFrameNo] = ndarray_to_list(v)
                continue
            if not attrs[k]:
                continue
            for i in range(len(v)):
                self.df[k].loc[self.currentFrameNo, i] = v[i]

        self.videoPlaybackWidget.setMaxTickableFrameNo(self.currentFrameNo+self.videoPlaybackWidget.playbackDelta)
        self.savedFlag = False

        if update:
            self.updatePath()
            self.updateInputGraphicsView()
            self.updateFrame.emit()

    def runObjectTracking(self):
        if self.filter is None or not self.videoPlaybackWidget.isOpened():
            return
        minFrame = self.currentFrameNo
        maxFrame = self.videoPlaybackWidget.getMaxFramePos()
        numFrames = maxFrame-minFrame
        progress = QProgressDialog("Running...", "Abort", 0, numFrames, self)

        progress.setWindowModality(Qt.WindowModal)

        currentFrameNo = self.currentFrameNo
        for i, frameNo in enumerate(range(minFrame, maxFrame+1)):
            progress.setValue(i)
            if progress.wasCanceled():
                break

            ret, frame = self.videoPlaybackWidget.readFrame(frameNo)
            self.cv_img = frame
            self.currentFrameNo = frameNo
            self.evaluate(False)

        self.videoPlaybackWidget.moveToFrame(currentFrameNo)
        progress.setValue(numFrames)

    def eventFilter(self, obj, event):
        if event.type() == QEvent.KeyPress:
            print(event.key())
            if Qt.Key_Home <= event.key() <= Qt.Key_PageDown:
                self.videoPlaybackWidget.playbackSlider.keyPressEvent(event)
                return True

        return False

    def generateCriticalMessage(self, msg):
        tb = sys.exc_info()[-1]
        f = tb.tb_frame
        msg = 'File name: {0}\nLine No: {1}\n'.format(f.f_code.co_filename, tb.tb_lineno) + msg
        reply = QtWidgets.QMessageBox.critical(
                self,
                'Critical',
                msg,
                QtWidgets.QMessageBox.Ok,
                QtWidgets.QMessageBox.NoButton
                )
        return reply
Ejemplo n.º 26
0
    def init_ui(self):
        self.setWindowTitle(TITLE)
        self.setWindowIcon(QIcon("img/icon.png"))
        self.ui.action_Help.setIcon(QIcon("img/help-circle.png"))
        self.ui.action_About.triggered.connect(self.act_about_triggered)
        self.ui.act_scan.triggered.connect(self.act_scan_triggered)
        self.ui.act_scan.setIcon(QIcon("img/scan.png"))
        self.ui.act_cut.setIcon(QIcon("img/cut_all.png"))
        self.ui.act_cut.triggered.connect(self.act_cutall_triggered)
        self.ui.tbl_hosts.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.ui.tbl_hosts.verticalHeader().setVisible(False)
        self.ui.tbl_hosts.setColumnCount(TABLE_COLUMN_COUNT)
        self.ui.tbl_hosts.setHorizontalHeaderLabels([
            "IP Address", "MAC Address", "Device Manufacturer", "Custom Name",
            "Status"
        ])
        self.ui.tbl_hosts.setColumnWidth(0, 100)
        self.ui.tbl_hosts.setColumnWidth(1, 100)
        self.ui.tbl_hosts.setColumnWidth(2, 240)
        self.ui.tbl_hosts.setColumnWidth(3, 100)
        self.ui.tbl_hosts.setShowGrid(False)
        self.ui.tbl_hosts.itemChanged.connect(self.hosts_item_changed)

        self.ui.actionShow_Icons_Text.setData(Qt.ToolButtonTextUnderIcon)
        self.ui.actionShow_Icons.setData(Qt.ToolButtonIconOnly)

        self.ui.actionCustom_Name.setData(R_NAME)
        self.ui.actionIP_Address.setData(R_IP)
        self.ui.actionMAC_Address.setData(R_MAC)
        self.ui.actionDevice_Manifacturer.setData(R_MAC_MAN)

        self.ui.actionCustom_Name.setChecked(
            self.settings.value("tbl_show_{}".format(R_NAME), 1, type=int))
        self.ui.actionIP_Address.setChecked(
            self.settings.value("tbl_show_{}".format(R_IP), 1, type=int))
        self.ui.actionMAC_Address.setChecked(
            self.settings.value("tbl_show_{}".format(R_MAC), 1, type=int))
        self.ui.actionDevice_Manifacturer.setChecked(
            self.settings.value("tbl_show_{}".format(R_MAC_MAN), 1, type=int))

        group = QActionGroup(self)
        group.addAction(self.ui.actionShow_Icons)
        group.addAction(self.ui.actionShow_Icons_Text)
        group.triggered.connect(self.act_toolbar_show)

        group2 = QActionGroup(self)
        group2.setExclusive(False)
        group2.addAction(self.ui.actionCustom_Name)
        group2.addAction(self.ui.actionIP_Address)
        group2.addAction(self.ui.actionMAC_Address)
        group2.addAction(self.ui.actionDevice_Manifacturer)
        group2.triggered.connect(self.act_setting_show)

        for a in group2.actions():
            if a.isChecked():
                self.ui.tbl_hosts.showColumn(a.data())
            else:
                self.ui.tbl_hosts.hideColumn(a.data())

        if int(self.settings.value(
                "toolbar_show",
                Qt.ToolButtonIconOnly)) == Qt.ToolButtonIconOnly:
            self.ui.actionShow_Icons.setChecked(True)
            self.ui.toolBar.setToolButtonStyle(Qt.ToolButtonIconOnly)

        else:
            self.ui.actionShow_Icons_Text.setChecked(True)
            self.ui.toolBar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
Ejemplo n.º 27
0
class PangoToolBarWidget(QToolBar):
    del_labels_signal = pyqtSignal(int)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setIconSize(QSize(16, 16))
        self.scene = None

        spacer_left = QWidget()
        spacer_left.setFixedWidth(10)
        spacer_middle = QWidget()
        spacer_middle.setFixedWidth(50)
        spacer_right = QWidget()
        spacer_right.setSizePolicy(QSizePolicy.Expanding,
                                   QSizePolicy.Preferred)

        # Label Related
        self.color_display = QLabel()
        self.color_display.setFixedSize(QSize(50, 20))
        self.label_select = self.LabelSelect(self.color_display)
        self.label_select.lineEdit().returnPressed.connect(self.add)

        self.add_action = QAction("Add")
        self.add_action.triggered.connect(self.add)
        icon = pango_get_icon("add")
        self.add_action.setIcon(icon)

        self.del_action = QAction("Delete")
        self.del_action.triggered.connect(self.delete)
        icon = pango_get_icon("del")
        self.del_action.setIcon(icon)

        self.color_action = QAction("Palette")
        self.color_action.triggered.connect(self.set_color)
        icon = pango_get_icon("palette")
        self.color_action.setIcon(icon)

        # Tool Related
        self.size_select = QSpinBox()
        self.size_select.valueChanged.connect(self.set_tool_size)

        self.size_select.setSuffix("px")
        self.size_select.setRange(1, 99)
        self.size_select.setSingleStep(5)
        self.size_select.setValue(10)

        self.pan_action = QAction("Pan")
        self.lasso_action = QAction("Lasso")
        self.path_action = QAction("Path")
        self.bbox_action = QAction("Bbox")
        self.poly_action = QAction("Poly")

        self.action_group = QActionGroup(self)
        self.action_group.setExclusive(True)
        self.action_group.triggered.connect(self.set_tool)

        self.action_group.addAction(self.pan_action)
        self.action_group.addAction(self.lasso_action)
        self.action_group.addAction(self.bbox_action)
        self.action_group.addAction(self.poly_action)
        self.action_group.addAction(self.path_action)

        for action in self.action_group.actions():
            icon = pango_get_icon(action.text())
            action.setIcon(icon)
            action.setCheckable(True)

        # Other
        font = QFont("Arial", 10)
        self.info_display = QLabel()
        self.info_display.setFixedWidth(100)
        self.info_display.setFont(font)

        self.coord_display = QLabel()
        self.coord_display.setFixedWidth(40)
        self.coord_display.setFont(font)

        # Layouts
        self.addWidget(spacer_left)
        self.addWidget(self.color_display)
        self.addWidget(self.label_select)
        self.addAction(self.add_action)
        self.addAction(self.del_action)
        self.addAction(self.color_action)

        self.addWidget(spacer_middle)
        self.addActions(self.action_group.actions())
        self.addWidget(self.size_select)

        self.addWidget(spacer_right)
        self.addWidget(self.info_display)
        self.addWidget(self.coord_display)

        self.size_select.setEnabled(False)

        self.del_action.setEnabled(False)
        self.action_group.setEnabled(False)
        self.color_action.setEnabled(False)

    def set_color(self):
        dialog = QColorDialog()
        dialog.setOption(QColorDialog.ShowAlphaChannel, False)
        color = dialog.getColor()
        if color == QColor():
            return

        row = self.label_select.currentIndex()
        label = self.label_select.model().item(row, 0)
        label.color = color
        label.set_icon()
        for i in range(0, label.rowCount()):
            shape = label.child(i)
            shape.set_icon()

        # Refresh label (for scene reticle etc.)
        self.label_select.setCurrentIndex(0)
        self.label_select.setCurrentIndex(row)
        self.label_select.color_display.update()

    def add(self):
        self.del_action.setEnabled(True)
        self.color_action.setEnabled(True)
        if self.scene.fpath is not None:
            self.action_group.setEnabled(True)

        item = PangoLabelItem()
        root = self.label_select.model().invisibleRootItem()
        root.appendRow(item)
        item.name = "Unnamed Label " + str(item.row())
        item.visible = True
        item.color = pango_get_palette(item.row())
        item.set_icon()

        bottom_row = self.label_select.model().rowCount() - 1
        self.label_select.setCurrentIndex(bottom_row)
        if bottom_row == 0:
            self.label_select.currentIndexChanged.emit(
                self.label_select.currentIndex())

    def delete(self):
        self.reset_tool()
        self.del_labels_signal.emit(self.label_select.currentIndex())

        if self.label_select.model().rowCount() == 0:
            self.del_action.setEnabled(False)
            self.action_group.setEnabled(False)
            self.color_action.setEnabled(False)

    def set_tool(self, action):
        if self.scene is None:
            return

        self.scene.tool = action.text()
        self.scene.reset_com()
        self.scene.reticle.setVisible(self.scene.tool == "Path")
        self.scene.views()[0].set_cursor(self.scene.tool)

        if action.text() == "Path" or action.text() == "Filled Path":
            self.size_select.setEnabled(True)
        else:
            self.size_select.setEnabled(False)

    def reset_tool(self):
        self.lasso_action.setChecked(True)
        self.set_tool(self.lasso_action)

    def set_tool_size(self, size, additive=False):
        if self.scene is None:
            return

        if additive:
            self.scene.tool_size += size
        else:
            self.scene.tool_size = size
        self.scene.reset_com()

        self.scene.reticle.setRect(-size / 2, -size / 2, size, size)
        if self.size_select.value() != self.scene.tool_size:
            self.size_select.setValue(self.scene.tool_size)

        if not self.scene.sceneRect().contains(self.scene.reticle.pos()):
            view = self.scene.views()[0]
            x = self.size_select.geometry().center().x()
            y = view.rect().top() + size / 2
            self.scene.reticle.setPos(view.mapToScene(QPoint(x, y)))

    def set_scene(self, scene):
        if self.scene is not None:
            self.scene.clear_tool.disconnect(self.reset_tool)

        self.scene = scene
        self.scene.clear_tool.connect(self.reset_tool)
        self.reset_tool()

    class LabelSelect(QComboBox):
        def __init__(self, color_display, parent=None):
            super().__init__(parent)
            self.color_display = color_display
            self.setFixedWidth(150)
            self.setEditable(True)

            self.editTextChanged.connect(self.edit_text_changed)

        def paintEvent(self, event):
            super().paintEvent(event)
            item = self.model().item(self.currentIndex())
            if item is not None and item.color is not None:
                self.color_display.setStyleSheet(
                    "QLabel { background-color : " + item.color.name() + "}")
            else:
                self.color_display.setStyleSheet(
                    "QLabel { background-color : rgba(255, 255, 255, 10)}")

        def edit_text_changed(self, text):
            row = self.currentIndex()
            self.setItemData(row, text, Qt.DisplayRole)

        def select_next_label(self):
            idx = self.currentIndex() + 1
            if idx > -1 and idx < self.count():
                self.setCurrentIndex(idx)

        def select_prev_label(self):
            idx = self.currentIndex() - 1
            if idx > -1 and idx < self.count():
                self.setCurrentIndex(idx)
Ejemplo n.º 28
0
class MainWindow(QMainWindow, Ui_MainWindow):
    dictChanged = pyqtSignal(str)

    # Tab indexes
    TabInfos = 0
    TabSummary = 1
    TabPersos = 2
    TabPlots = 3
    TabWorld = 4
    TabOutline = 5
    TabRedac = 6

    def __init__(self):
        QMainWindow.__init__(self)
        self.setupUi(self)
        self.currentProject = None

        self.readSettings()

        # UI
        self.setupMoreUi()

        # Welcome
        self.welcome.updateValues()
        self.stack.setCurrentIndex(0)

        # Word count
        self.mprWordCount = QSignalMapper(self)
        for t, i in [(self.txtSummarySentence, 0), (self.txtSummaryPara, 1),
                     (self.txtSummaryPage, 2), (self.txtSummaryFull, 3)]:
            t.textChanged.connect(self.mprWordCount.map)
            self.mprWordCount.setMapping(t, i)
        self.mprWordCount.mapped.connect(self.wordCount)

        # Snowflake Method Cycle
        self.mapperCycle = QSignalMapper(self)
        for t, i in [(self.btnStepTwo, 0), (self.btnStepThree, 1),
                     (self.btnStepFour, 2), (self.btnStepFive, 3),
                     (self.btnStepSix, 4), (self.btnStepSeven, 5),
                     (self.btnStepEight, 6)]:
            t.clicked.connect(self.mapperCycle.map)
            self.mapperCycle.setMapping(t, i)

        self.mapperCycle.mapped.connect(self.clickCycle)
        self.cmbSummary.currentIndexChanged.connect(self.summaryPageChanged)
        self.cmbSummary.setCurrentIndex(0)
        self.cmbSummary.currentIndexChanged.emit(0)

        # Main Menu
        for i in [
                self.actSave, self.actSaveAs, self.actCloseProject,
                self.menuEdit, self.menuView, self.menuTools, self.menuHelp
        ]:
            i.setEnabled(False)

        self.actOpen.triggered.connect(self.welcome.openFile)
        self.actSave.triggered.connect(self.saveDatas)
        self.actSaveAs.triggered.connect(self.welcome.saveAsFile)
        self.actCompile.triggered.connect(self.doCompile)
        self.actLabels.triggered.connect(self.settingsLabel)
        self.actStatus.triggered.connect(self.settingsStatus)
        self.actSettings.triggered.connect(self.settingsWindow)
        self.actCloseProject.triggered.connect(self.closeProject)
        self.actQuit.triggered.connect(self.close)
        self.actToolFrequency.triggered.connect(self.frequencyAnalyzer)
        self.generateViewMenu()

        self.actModeGroup = QActionGroup(self)
        self.actModeSimple.setActionGroup(self.actModeGroup)
        self.actModeFiction.setActionGroup(self.actModeGroup)
        self.actModeSnowflake.setActionGroup(self.actModeGroup)
        self.actModeSimple.triggered.connect(self.setViewModeSimple)
        self.actModeFiction.triggered.connect(self.setViewModeFiction)
        self.actModeSnowflake.setEnabled(False)

        self.makeUIConnections()

        # self.loadProject(os.path.join(appPath(), "test_project.zip"))

    ###############################################################################
    # SUMMARY
    ###############################################################################

    def summaryPageChanged(self, index):
        fractalButtons = [
            self.btnStepTwo,
            self.btnStepThree,
            self.btnStepFive,
            self.btnStepSeven,
        ]
        for b in fractalButtons:
            b.setVisible(fractalButtons.index(b) == index)

    ###############################################################################
    # OUTLINE
    ###############################################################################

    def outlineRemoveItemsRedac(self):
        self.treeRedacOutline.delete()

    def outlineRemoveItemsOutline(self):
        self.treeOutlineOutline.delete()

    ###############################################################################
    # CHARACTERS
    ###############################################################################

    def changeCurrentCharacter(self, trash=None):
        """

        @return:
        """
        c = self.lstCharacters.currentCharacter()
        if not c:
            self.tabPlot.setEnabled(False)
            return

        self.tabPersos.setEnabled(True)
        index = c.index()

        for w in [
                self.txtPersoName,
                self.sldPersoImportance,
                self.txtPersoMotivation,
                self.txtPersoGoal,
                self.txtPersoConflict,
                self.txtPersoEpiphany,
                self.txtPersoSummarySentence,
                self.txtPersoSummaryPara,
                self.txtPersoSummaryFull,
                self.txtPersoNotes,
        ]:
            w.setCurrentModelIndex(index)

        # Button color
        self.updateCharacterColor(c.ID())

        # Character Infos
        self.tblPersoInfos.setRootIndex(index)

        if self.mdlCharacter.rowCount(index):
            self.updatePersoInfoView()

    def updatePersoInfoView(self):
        self.tblPersoInfos.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.ResizeToContents)
        self.tblPersoInfos.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.Stretch)
        self.tblPersoInfos.verticalHeader().hide()

    def updateCharacterColor(self, ID):
        c = self.mdlCharacter.getCharacterByID(ID)
        color = c.color().name()
        self.btnPersoColor.setStyleSheet("background:{};".format(color))

    ###############################################################################
    # PLOTS
    ###############################################################################

    def changeCurrentPlot(self):
        index = self.lstPlots.currentPlotIndex()

        if not index.isValid():
            self.tabPlot.setEnabled(False)
            return

        self.tabPlot.setEnabled(True)
        self.txtPlotName.setCurrentModelIndex(index)
        self.txtPlotDescription.setCurrentModelIndex(index)
        self.txtPlotResult.setCurrentModelIndex(index)
        self.sldPlotImportance.setCurrentModelIndex(index)
        self.lstPlotPerso.setRootIndex(
            index.sibling(index.row(), Plot.characters.value))
        subplotindex = index.sibling(index.row(), Plot.steps.value)
        self.lstSubPlots.setRootIndex(subplotindex)
        if self.mdlPlots.rowCount(subplotindex):
            self.updateSubPlotView()

        # self.txtSubPlotSummary.setCurrentModelIndex(QModelIndex())
        self.txtSubPlotSummary.setEnabled(False)
        self._updatingSubPlot = True
        self.txtSubPlotSummary.setPlainText("")
        self._updatingSubPlot = False
        self.lstPlotPerso.selectionModel().clear()

    def updateSubPlotView(self):
        # Hide columns
        for i in range(self.mdlPlots.columnCount()):
            self.lstSubPlots.hideColumn(i)
        self.lstSubPlots.showColumn(PlotStep.name.value)
        self.lstSubPlots.showColumn(PlotStep.meta.value)

        self.lstSubPlots.horizontalHeader().setSectionResizeMode(
            PlotStep.name.value, QHeaderView.Stretch)
        self.lstSubPlots.horizontalHeader().setSectionResizeMode(
            PlotStep.meta.value, QHeaderView.ResizeToContents)
        self.lstSubPlots.verticalHeader().hide()

    def changeCurrentSubPlot(self, index):
        # Got segfaults when using textEditView model system, so ad hoc stuff.
        index = index.sibling(index.row(), PlotStep.summary.value)
        item = self.mdlPlots.itemFromIndex(index)
        if not item:
            self.txtSubPlotSummary.setEnabled(False)
            return
        self.txtSubPlotSummary.setEnabled(True)
        txt = item.text()
        self._updatingSubPlot = True
        self.txtSubPlotSummary.setPlainText(txt)
        self._updatingSubPlot = False

    def updateSubPlotSummary(self):
        if self._updatingSubPlot:
            return

        index = self.lstSubPlots.currentIndex()
        if not index.isValid():
            return
        index = index.sibling(index.row(), PlotStep.summary.value)
        item = self.mdlPlots.itemFromIndex(index)

        self._updatingSubPlot = True
        item.setText(self.txtSubPlotSummary.toPlainText())
        self._updatingSubPlot = False

    def plotPersoSelectionChanged(self):
        "Enables or disables remove plot perso button."
        self.btnRmPlotPerso.setEnabled(
            len(self.lstPlotPerso.selectedIndexes()) != 0)

    ###############################################################################
    # WORLD
    ###############################################################################

    def changeCurrentWorld(self):
        index = self.mdlWorld.selectedIndex()

        if not index.isValid():
            self.tabWorld.setEnabled(False)
            return

        self.tabWorld.setEnabled(True)
        self.txtWorldName.setCurrentModelIndex(index)
        self.txtWorldDescription.setCurrentModelIndex(index)
        self.txtWorldPassion.setCurrentModelIndex(index)

    #     self.txtWorldConflict.setCurrentModelIndex(index)
    #
    # ###############################################################################
    # # LOAD AND SAVE
    # ###############################################################################

    def loadProject(self, project, loadFromFile=True):
        """Loads the project ``project``.

        If ``loadFromFile`` is False, then it does not load datas from file.
        It assumes that the datas have been populated in a different way."""
        if loadFromFile and not os.path.exists(project):
            print(
                self.tr("The file {} does not exist. Try again.").format(
                    project))
            self.statusBar().showMessage(
                self.tr("The file {} does not exist. Try again.").format(
                    project), 5000)
            return

        if loadFromFile:
            # Load empty settings
            imp.reload(settings)

            # Load data
            self.loadEmptyDatas()
            self.loadDatas(project)

        self.makeConnections()

        # Load settings
        for i in settings.openIndexes:
            idx = self.mdlOutline.getIndexByID(i)
            self.mainEditor.setCurrentModelIndex(idx, newTab=True)
        self.generateViewMenu()
        self.mainEditor.sldCorkSizeFactor.setValue(settings.corkSizeFactor)
        self.actSpellcheck.setChecked(settings.spellcheck)
        self.toggleSpellcheck(settings.spellcheck)
        self.updateMenuDict()
        self.setDictionary()

        self.mainEditor.setFolderView(settings.folderView)
        self.mainEditor.updateFolderViewButtons(settings.folderView)
        self.tabMain.setCurrentIndex(settings.lastTab)
        # We force to emit even if it opens on the current tab
        self.tabMain.currentChanged.emit(settings.lastTab)
        self.mainEditor.updateCorkBackground()
        if settings.viewMode == "simple":
            self.setViewModeSimple()
        else:
            self.setViewModeFiction()

        # Set autosave
        self.saveTimer = QTimer()
        self.saveTimer.setInterval(settings.autoSaveDelay * 60 * 1000)
        self.saveTimer.setSingleShot(False)
        self.saveTimer.timeout.connect(self.saveDatas)
        if settings.autoSave:
            self.saveTimer.start()

        # Set autosave if no changes
        self.saveTimerNoChanges = QTimer()
        self.saveTimerNoChanges.setInterval(settings.autoSaveNoChangesDelay *
                                            1000)
        self.saveTimerNoChanges.setSingleShot(True)
        self.mdlFlatData.dataChanged.connect(self.startTimerNoChanges)
        self.mdlOutline.dataChanged.connect(self.startTimerNoChanges)
        self.mdlCharacter.dataChanged.connect(self.startTimerNoChanges)
        self.mdlPlots.dataChanged.connect(self.startTimerNoChanges)
        self.mdlWorld.dataChanged.connect(self.startTimerNoChanges)
        # self.mdlPersosInfos.dataChanged.connect(self.startTimerNoChanges)
        self.mdlStatus.dataChanged.connect(self.startTimerNoChanges)
        self.mdlLabels.dataChanged.connect(self.startTimerNoChanges)

        self.saveTimerNoChanges.timeout.connect(self.saveDatas)
        self.saveTimerNoChanges.stop()

        # UI
        for i in [
                self.actSave, self.actSaveAs, self.actCloseProject,
                self.menuEdit, self.menuView, self.menuTools, self.menuHelp
        ]:
            i.setEnabled(True)
        # FIXME: set Window's name: project name

        # Stuff
        # self.checkPersosID()  # Should'n be necessary any longer

        self.currentProject = project
        QSettings().setValue("lastProject", project)

        # Show main Window
        self.stack.setCurrentIndex(1)

    def closeProject(self):

        if not self.currentProject:
            return

        # Close open tabs in editor
        self.mainEditor.closeAllTabs()

        # Save datas
        self.saveDatas()

        self.currentProject = None
        QSettings().setValue("lastProject", "")

        # Clear datas
        self.loadEmptyDatas()
        self.saveTimer.stop()
        loadSave.clearSaveCache()

        # UI
        for i in [
                self.actSave, self.actSaveAs, self.actCloseProject,
                self.menuEdit, self.menuView, self.menuTools, self.menuHelp
        ]:
            i.setEnabled(False)

        # Reload recent files
        self.welcome.updateValues()

        # Show welcome dialog
        self.stack.setCurrentIndex(0)

    def readSettings(self):
        # Load State and geometry
        sttgns = QSettings(qApp.organizationName(), qApp.applicationName())
        if sttgns.contains("geometry"):
            self.restoreGeometry(sttgns.value("geometry"))
        if sttgns.contains("windowState"):
            self.restoreState(sttgns.value("windowState"))
        else:
            self.dckCheatSheet.hide()
            self.dckSearch.hide()
        if sttgns.contains("metadataState"):
            state = [
                False if v == "false" else True
                for v in sttgns.value("metadataState")
            ]
            self.redacMetadata.restoreState(state)
        if sttgns.contains("revisionsState"):
            state = [
                False if v == "false" else True
                for v in sttgns.value("revisionsState")
            ]
            self.redacMetadata.revisions.restoreState(state)
        if sttgns.contains("splitterRedacH"):
            self.splitterRedacH.restoreState(sttgns.value("splitterRedacH"))
        if sttgns.contains("splitterRedacV"):
            self.splitterRedacV.restoreState(sttgns.value("splitterRedacV"))
        if sttgns.contains("toolbar"):
            # self.toolbar is not initialized yet, so we just store balue
            self._toolbarState = sttgns.value("toolbar")
        else:
            self._toolbarState = ""

    def closeEvent(self, event):
        # Save State and geometry and other things
        sttgns = QSettings(qApp.organizationName(), qApp.applicationName())
        sttgns.setValue("geometry", self.saveGeometry())
        sttgns.setValue("windowState", self.saveState())
        sttgns.setValue("metadataState", self.redacMetadata.saveState())
        sttgns.setValue("revisionsState",
                        self.redacMetadata.revisions.saveState())
        sttgns.setValue("splitterRedacH", self.splitterRedacH.saveState())
        sttgns.setValue("splitterRedacV", self.splitterRedacV.saveState())
        sttgns.setValue("toolbar", self.toolbar.saveState())

        # Specific settings to save before quitting
        settings.lastTab = self.tabMain.currentIndex()

        if self.currentProject:
            # Remembering the current items (stores outlineItem's ID)
            sel = []
            for i in range(self.mainEditor.tab.count()):
                sel.append(
                    self.mdlOutline.ID(
                        self.mainEditor.tab.widget(i).currentIndex))
            settings.openIndexes = sel

        # Save data from models
        if self.currentProject and settings.saveOnQuit:
            self.saveDatas()

            # closeEvent
            # QMainWindow.closeEvent(self, event)  # Causin segfaults?

    def startTimerNoChanges(self):
        if settings.autoSaveNoChanges:
            self.saveTimerNoChanges.start()

    def saveDatas(self, projectName=None):
        """Saves the current project (in self.currentProject).

        If ``projectName`` is given, currentProject becomes projectName.
        In other words, it "saves as...".
        """

        if projectName:
            self.currentProject = projectName
            QSettings().setValue("lastProject", projectName)

        loadSave.saveProject()  # version=0
        self.saveTimerNoChanges.stop()

        # Giving some feedback
        print(self.tr("Project {} saved.").format(self.currentProject))
        self.statusBar().showMessage(
            self.tr("Project {} saved.").format(self.currentProject), 5000)

    def loadEmptyDatas(self):
        self.mdlFlatData = QStandardItemModel(self)
        self.mdlCharacter = characterModel(self)
        # self.mdlPersosProxy = persosProxyModel(self)
        # self.mdlPersosInfos = QStandardItemModel(self)
        self.mdlLabels = QStandardItemModel(self)
        self.mdlStatus = QStandardItemModel(self)
        self.mdlPlots = plotModel(self)
        self.mdlOutline = outlineModel(self)
        self.mdlWorld = worldModel(self)

    def loadDatas(self, project):

        errors = loadSave.loadProject(project)

        # Giving some feedback
        if not errors:
            print(self.tr("Project {} loaded.").format(project))
            self.statusBar().showMessage(
                self.tr("Project {} loaded.").format(project), 5000)
        else:
            print(
                self.tr("Project {} loaded with some errors:").format(project))
            for e in errors:
                print(self.tr(" * {} wasn't found in project file.").format(e))
            self.statusBar().showMessage(
                self.tr("Project {} loaded with some errors.").format(project),
                5000)

    ###############################################################################
    # MAIN CONNECTIONS
    ###############################################################################

    def makeUIConnections(self):
        "Connections that have to be made once only, even when a new project is loaded."
        self.lstCharacters.currentItemChanged.connect(
            self.changeCurrentCharacter, AUC)

        self.txtPlotFilter.textChanged.connect(self.lstPlots.setFilter, AUC)
        self.lstPlots.currentItemChanged.connect(self.changeCurrentPlot, AUC)
        self.txtSubPlotSummary.document().contentsChanged.connect(
            self.updateSubPlotSummary, AUC)
        self.lstSubPlots.activated.connect(self.changeCurrentSubPlot, AUC)

        self.btnRedacAddFolder.clicked.connect(self.treeRedacOutline.addFolder,
                                               AUC)
        self.btnOutlineAddFolder.clicked.connect(
            self.treeOutlineOutline.addFolder, AUC)
        self.btnRedacAddText.clicked.connect(self.treeRedacOutline.addText,
                                             AUC)
        self.btnOutlineAddText.clicked.connect(self.treeOutlineOutline.addText,
                                               AUC)
        self.btnRedacRemoveItem.clicked.connect(self.outlineRemoveItemsRedac,
                                                AUC)
        self.btnOutlineRemoveItem.clicked.connect(
            self.outlineRemoveItemsOutline, AUC)

        self.tabMain.currentChanged.connect(self.toolbar.setCurrentGroup)

    def makeConnections(self):

        # Flat datas (Summary and general infos)
        for widget, col in [
            (self.txtSummarySituation, 0),
            (self.txtSummarySentence, 1),
            (self.txtSummarySentence_2, 1),
            (self.txtSummaryPara, 2),
            (self.txtSummaryPara_2, 2),
            (self.txtPlotSummaryPara, 2),
            (self.txtSummaryPage, 3),
            (self.txtSummaryPage_2, 3),
            (self.txtPlotSummaryPage, 3),
            (self.txtSummaryFull, 4),
            (self.txtPlotSummaryFull, 4),
        ]:
            widget.setModel(self.mdlFlatData)
            widget.setColumn(col)
            widget.setCurrentModelIndex(self.mdlFlatData.index(1, col))

        for widget, col in [
            (self.txtGeneralTitle, 0),
            (self.txtGeneralSubtitle, 1),
            (self.txtGeneralSerie, 2),
            (self.txtGeneralVolume, 3),
            (self.txtGeneralGenre, 4),
            (self.txtGeneralLicense, 5),
            (self.txtGeneralAuthor, 6),
            (self.txtGeneralEmail, 7),
        ]:
            widget.setModel(self.mdlFlatData)
            widget.setColumn(col)
            widget.setCurrentModelIndex(self.mdlFlatData.index(0, col))

        # Characters
        self.lstCharacters.setCharactersModel(self.mdlCharacter)
        self.tblPersoInfos.setModel(self.mdlCharacter)

        self.btnAddPerso.clicked.connect(self.mdlCharacter.addCharacter, AUC)
        try:
            self.btnRmPerso.clicked.connect(self.lstCharacters.removeCharacter,
                                            AUC)
            self.btnPersoColor.clicked.connect(
                self.lstCharacters.choseCharacterColor, AUC)
            self.btnPersoAddInfo.clicked.connect(
                self.lstCharacters.addCharacterInfo, AUC)
            self.btnPersoRmInfo.clicked.connect(
                self.lstCharacters.removeCharacterInfo, AUC)
        except TypeError:
            # Connection has already been made
            pass

        for w, c in [(self.txtPersoName, Character.name.value),
                     (self.sldPersoImportance, Character.importance.value),
                     (self.txtPersoMotivation, Character.motivation.value),
                     (self.txtPersoGoal, Character.goal.value),
                     (self.txtPersoConflict, Character.conflict.value),
                     (self.txtPersoEpiphany, Character.epiphany.value),
                     (self.txtPersoSummarySentence,
                      Character.summarySentence.value),
                     (self.txtPersoSummaryPara, Character.summaryPara.value),
                     (self.txtPersoSummaryFull, Character.summaryFull.value),
                     (self.txtPersoNotes, Character.notes.value)]:
            w.setModel(self.mdlCharacter)
            w.setColumn(c)
        self.tabPersos.setEnabled(False)

        # Plots
        self.lstPlots.setPlotModel(self.mdlPlots)
        self.lstPlotPerso.setModel(self.mdlPlots)
        self.lstSubPlots.setModel(self.mdlPlots)
        self._updatingSubPlot = False
        self.btnAddPlot.clicked.connect(self.mdlPlots.addPlot, AUC)
        self.btnRmPlot.clicked.connect(
            lambda: self.mdlPlots.removePlot(self.lstPlots.currentPlotIndex()),
            AUC)
        self.btnAddSubPlot.clicked.connect(self.mdlPlots.addSubPlot, AUC)
        self.btnRmSubPlot.clicked.connect(self.mdlPlots.removeSubPlot, AUC)
        self.lstPlotPerso.selectionModel().selectionChanged.connect(
            self.plotPersoSelectionChanged)
        self.btnRmPlotPerso.clicked.connect(self.mdlPlots.removePlotPerso, AUC)

        for w, c in [
            (self.txtPlotName, Plot.name.value),
            (self.txtPlotDescription, Plot.description.value),
            (self.txtPlotResult, Plot.result.value),
            (self.sldPlotImportance, Plot.importance.value),
        ]:
            w.setModel(self.mdlPlots)
            w.setColumn(c)

        self.tabPlot.setEnabled(False)
        self.mdlPlots.updatePlotPersoButton()
        self.mdlCharacter.dataChanged.connect(
            self.mdlPlots.updatePlotPersoButton)
        self.lstOutlinePlots.setPlotModel(self.mdlPlots)
        self.lstOutlinePlots.setShowSubPlot(True)
        self.plotCharacterDelegate = outlineCharacterDelegate(
            self.mdlCharacter, self)
        self.lstPlotPerso.setItemDelegate(self.plotCharacterDelegate)
        self.plotDelegate = plotDelegate(self)
        self.lstSubPlots.setItemDelegateForColumn(PlotStep.meta.value,
                                                  self.plotDelegate)

        # World
        self.treeWorld.setModel(self.mdlWorld)
        for i in range(self.mdlWorld.columnCount()):
            self.treeWorld.hideColumn(i)
        self.treeWorld.showColumn(0)
        self.btnWorldEmptyData.setMenu(self.mdlWorld.emptyDataMenu())
        self.treeWorld.selectionModel().selectionChanged.connect(
            self.changeCurrentWorld, AUC)
        self.btnAddWorld.clicked.connect(self.mdlWorld.addItem, AUC)
        self.btnRmWorld.clicked.connect(self.mdlWorld.removeItem, AUC)
        for w, c in [
            (self.txtWorldName, World.name.value),
            (self.txtWorldDescription, World.description.value),
            (self.txtWorldPassion, World.passion.value),
            (self.txtWorldConflict, World.conflict.value),
        ]:
            w.setModel(self.mdlWorld)
            w.setColumn(c)
        self.tabWorld.setEnabled(False)
        self.treeWorld.expandAll()

        # Outline
        self.treeRedacOutline.setModel(self.mdlOutline)
        self.treeOutlineOutline.setModelCharacters(self.mdlCharacter)
        self.treeOutlineOutline.setModelLabels(self.mdlLabels)
        self.treeOutlineOutline.setModelStatus(self.mdlStatus)

        self.redacMetadata.setModels(self.mdlOutline, self.mdlCharacter,
                                     self.mdlLabels, self.mdlStatus)
        self.outlineItemEditor.setModels(self.mdlOutline, self.mdlCharacter,
                                         self.mdlLabels, self.mdlStatus)

        self.treeOutlineOutline.setModel(self.mdlOutline)
        # self.redacEditor.setModel(self.mdlOutline)
        self.storylineView.setModels(self.mdlOutline, self.mdlCharacter,
                                     self.mdlPlots)

        self.treeOutlineOutline.selectionModel().selectionChanged.connect(
            lambda: self.outlineItemEditor.selectionChanged(
                self.treeOutlineOutline), AUC)
        self.treeOutlineOutline.clicked.connect(
            lambda: self.outlineItemEditor.selectionChanged(
                self.treeOutlineOutline), AUC)

        # Sync selection
        self.treeRedacOutline.selectionModel().selectionChanged.connect(
            lambda: self.redacMetadata.selectionChanged(self.treeRedacOutline),
            AUC)
        self.treeRedacOutline.clicked.connect(
            lambda: self.redacMetadata.selectionChanged(self.treeRedacOutline),
            AUC)

        self.treeRedacOutline.selectionModel().selectionChanged.connect(
            self.mainEditor.selectionChanged, AUC)

        # Cheat Sheet
        self.cheatSheet.setModels()

        # Debug
        self.mdlFlatData.setVerticalHeaderLabels(
            ["Infos générales", "Summary"])
        self.tblDebugFlatData.setModel(self.mdlFlatData)
        self.tblDebugPersos.setModel(self.mdlCharacter)
        self.tblDebugPersosInfos.setModel(self.mdlCharacter)
        self.tblDebugPersos.selectionModel().currentChanged.connect(
            lambda: self.tblDebugPersosInfos.setRootIndex(
                self.mdlCharacter.index(
                    self.tblDebugPersos.selectionModel().currentIndex().row(),
                    Character.name.value)), AUC)

        self.tblDebugPlots.setModel(self.mdlPlots)
        self.tblDebugPlotsPersos.setModel(self.mdlPlots)
        self.tblDebugSubPlots.setModel(self.mdlPlots)
        self.tblDebugPlots.selectionModel().currentChanged.connect(
            lambda: self.tblDebugPlotsPersos.setRootIndex(
                self.mdlPlots.index(
                    self.tblDebugPlots.selectionModel().currentIndex().row(),
                    Plot.characters.value)), AUC)
        self.tblDebugPlots.selectionModel().currentChanged.connect(
            lambda: self.tblDebugSubPlots.setRootIndex(
                self.mdlPlots.index(
                    self.tblDebugPlots.selectionModel().currentIndex().row(),
                    Plot.steps.value)), AUC)
        self.treeDebugWorld.setModel(self.mdlWorld)
        self.treeDebugOutline.setModel(self.mdlOutline)
        self.lstDebugLabels.setModel(self.mdlLabels)
        self.lstDebugStatus.setModel(self.mdlStatus)

    ###############################################################################
    # GENERAL AKA UNSORTED
    ###############################################################################

    def clickCycle(self, i):
        if i == 0:  # step 2 - paragraph summary
            self.tabMain.setCurrentIndex(self.TabSummary)
            self.tabSummary.setCurrentIndex(1)
        if i == 1:  # step 3 - characters summary
            self.tabMain.setCurrentIndex(self.TabPersos)
            self.tabPersos.setCurrentIndex(0)
        if i == 2:  # step 4 - page summary
            self.tabMain.setCurrentIndex(self.TabSummary)
            self.tabSummary.setCurrentIndex(2)
        if i == 3:  # step 5 - characters description
            self.tabMain.setCurrentIndex(self.TabPersos)
            self.tabPersos.setCurrentIndex(1)
        if i == 4:  # step 6 - four page synopsis
            self.tabMain.setCurrentIndex(self.TabSummary)
            self.tabSummary.setCurrentIndex(3)
        if i == 5:  # step 7 - full character charts
            self.tabMain.setCurrentIndex(self.TabPersos)
            self.tabPersos.setCurrentIndex(2)
        if i == 6:  # step 8 - scene list
            self.tabMain.setCurrentIndex(self.TabPlots)

    def wordCount(self, i):

        src = {
            0: self.txtSummarySentence,
            1: self.txtSummaryPara,
            2: self.txtSummaryPage,
            3: self.txtSummaryFull
        }[i]

        lbl = {
            0: self.lblSummaryWCSentence,
            1: self.lblSummaryWCPara,
            2: self.lblSummaryWCPage,
            3: self.lblSummaryWCFull
        }[i]

        wc = wordCount(src.toPlainText())
        if i in [2, 3]:
            pages = self.tr(" (~{} pages)").format(int(wc / 25) / 10.)
        else:
            pages = ""
        lbl.setText(self.tr("Words: {}{}").format(wc, pages))

    def setupMoreUi(self):

        # Tool bar on the right
        self.toolbar = collapsibleDockWidgets(Qt.RightDockWidgetArea, self)
        self.toolbar.addCustomWidget(self.tr("Book summary"),
                                     self.grpPlotSummary, self.TabPlots)
        self.toolbar.addCustomWidget(self.tr("Project tree"),
                                     self.treeRedacWidget, self.TabRedac)
        self.toolbar.addCustomWidget(self.tr("Metadata"), self.redacMetadata,
                                     self.TabRedac)
        self.toolbar.addCustomWidget(self.tr("Story line"), self.storylineView,
                                     self.TabRedac)
        if self._toolbarState:
            self.toolbar.restoreState(self._toolbarState)

        # Custom "tab" bar on the left
        self.lstTabs.setIconSize(QSize(48, 48))
        for i in range(self.tabMain.count()):
            icons = [
                "general-128px.png", "summary-128px.png",
                "characters-128px.png", "plot-128px.png", "world-128px.png",
                "outline-128px.png", "redaction-128px.png", ""
            ]
            self.tabMain.setTabIcon(
                i, QIcon(appPath("icons/Custom/Tabs/{}".format(icons[i]))))
            item = QListWidgetItem(self.tabMain.tabIcon(i),
                                   self.tabMain.tabText(i))
            item.setSizeHint(QSize(item.sizeHint().width(), 64))
            item.setTextAlignment(Qt.AlignCenter)
            self.lstTabs.addItem(item)
        self.tabMain.tabBar().hide()
        self.lstTabs.currentRowChanged.connect(self.tabMain.setCurrentIndex)
        self.tabMain.currentChanged.connect(self.lstTabs.setCurrentRow)

        # Splitters
        self.splitterPersos.setStretchFactor(0, 25)
        self.splitterPersos.setStretchFactor(1, 75)

        self.splitterPlot.setStretchFactor(0, 20)
        self.splitterPlot.setStretchFactor(1, 60)
        self.splitterPlot.setStretchFactor(2, 30)

        self.splitterWorld.setStretchFactor(0, 25)
        self.splitterWorld.setStretchFactor(1, 75)

        self.splitterOutlineH.setStretchFactor(0, 25)
        self.splitterOutlineH.setStretchFactor(1, 75)
        self.splitterOutlineV.setStretchFactor(0, 75)
        self.splitterOutlineV.setStretchFactor(1, 25)

        self.splitterRedacV.setStretchFactor(0, 75)
        self.splitterRedacV.setStretchFactor(1, 25)

        self.splitterRedacH.setStretchFactor(0, 30)
        self.splitterRedacH.setStretchFactor(1, 40)
        self.splitterRedacH.setStretchFactor(2, 30)

        # QFormLayout stretch
        for w in [
                self.txtWorldDescription, self.txtWorldPassion,
                self.txtWorldConflict
        ]:
            s = w.sizePolicy()
            s.setVerticalStretch(1)
            w.setSizePolicy(s)

        # Help box
        references = [
            (self.lytTabOverview,
             self.tr("Enter infos about your book, and yourself."), 0),
            (self.lytSituation,
             self.
             tr("""The basic situation, in the form of a 'What if...?' question. Ex: 'What if the most dangerous
                     evil wizard could wasn't abled to kill a baby?' (Harry Potter)"""
                ), 1),
            (self.lytSummary,
             self.
             tr("""Take time to think about a one sentence (~50 words) summary of your book. Then expand it to
                     a paragraph, then to a page, then to a full summary."""),
             1), (self.lytTabPersos, self.tr("Create your characters."), 0),
            (self.lytTabPlot, self.tr("Develop plots."), 0),
            (self.lytTabOutline,
             self.tr("Create the outline of your masterpiece."), 0),
            (self.lytTabRedac, self.tr("Write."), 0),
            (self.lytTabDebug, self.tr("Debug infos. Sometimes useful."), 0)
        ]

        for widget, text, pos in references:
            label = helpLabel(text, self)
            self.actShowHelp.toggled.connect(label.setVisible, AUC)
            widget.layout().insertWidget(pos, label)

        self.actShowHelp.setChecked(False)

        # Spellcheck
        if enchant:
            self.menuDict = QMenu(self.tr("Dictionary"))
            self.menuDictGroup = QActionGroup(self)
            self.updateMenuDict()
            self.menuTools.addMenu(self.menuDict)

            self.actSpellcheck.toggled.connect(self.toggleSpellcheck, AUC)
            self.dictChanged.connect(self.mainEditor.setDict, AUC)
            self.dictChanged.connect(self.redacMetadata.setDict, AUC)
            self.dictChanged.connect(self.outlineItemEditor.setDict, AUC)

        else:
            # No Spell check support
            self.actSpellcheck.setVisible(False)
            a = QAction(self.tr("Install PyEnchant to use spellcheck"), self)
            a.setIcon(self.style().standardIcon(QStyle.SP_MessageBoxWarning))
            a.triggered.connect(self.openPyEnchantWebPage, AUC)
            self.menuTools.addAction(a)

    ###############################################################################
    # SPELLCHECK
    ###############################################################################

    def updateMenuDict(self):

        if not enchant:
            return

        self.menuDict.clear()
        for i in enchant.list_dicts():
            a = QAction(str(i[0]), self)
            a.setCheckable(True)
            if settings.dict is None:
                settings.dict = enchant.get_default_language()
            if str(i[0]) == settings.dict:
                a.setChecked(True)
            a.triggered.connect(self.setDictionary, AUC)
            self.menuDictGroup.addAction(a)
            self.menuDict.addAction(a)

    def setDictionary(self):
        if not enchant:
            return

        for i in self.menuDictGroup.actions():
            if i.isChecked():
                # self.dictChanged.emit(i.text().replace("&", ""))
                settings.dict = i.text().replace("&", "")

                # Find all textEditView from self, and toggle spellcheck
                for w in self.findChildren(textEditView, QRegExp(".*"),
                                           Qt.FindChildrenRecursively):
                    w.setDict(settings.dict)

    def openPyEnchantWebPage(self):
        from PyQt5.QtGui import QDesktopServices
        QDesktopServices.openUrl(QUrl("http://pythonhosted.org/pyenchant/"))

    def toggleSpellcheck(self, val):
        settings.spellcheck = val

        # Find all textEditView from self, and toggle spellcheck
        for w in self.findChildren(textEditView, QRegExp(".*"),
                                   Qt.FindChildrenRecursively):
            w.toggleSpellcheck(val)

    ###############################################################################
    # SETTINGS
    ###############################################################################

    def settingsLabel(self):
        self.settingsWindow(3)

    def settingsStatus(self):
        self.settingsWindow(4)

    def settingsWindow(self, tab=None):
        self.sw = settingsWindow(self)
        self.sw.hide()
        self.sw.setWindowModality(Qt.ApplicationModal)
        self.sw.setWindowFlags(Qt.Dialog)
        r = self.sw.geometry()
        r2 = self.geometry()
        self.sw.move(r2.center() - r.center())
        if tab:
            self.sw.setTab(tab)
        self.sw.show()

    ###############################################################################
    # TOOLS
    ###############################################################################

    def frequencyAnalyzer(self):
        self.fw = frequencyAnalyzer(self)
        self.fw.show()

    ###############################################################################
    # VIEW MENU
    ###############################################################################

    def generateViewMenu(self):

        values = [
            (self.tr("Nothing"), "Nothing"),
            (self.tr("POV"), "POV"),
            (self.tr("Label"), "Label"),
            (self.tr("Progress"), "Progress"),
            (self.tr("Compile"), "Compile"),
        ]

        menus = [(self.tr("Tree"), "Tree"), (self.tr("Index cards"), "Cork"),
                 (self.tr("Outline"), "Outline")]

        submenus = {
            "Tree": [
                (self.tr("Icon color"), "Icon"),
                (self.tr("Text color"), "Text"),
                (self.tr("Background color"), "Background"),
            ],
            "Cork": [
                (self.tr("Icon"), "Icon"),
                (self.tr("Text"), "Text"),
                (self.tr("Background"), "Background"),
                (self.tr("Border"), "Border"),
                (self.tr("Corner"), "Corner"),
            ],
            "Outline": [
                (self.tr("Icon color"), "Icon"),
                (self.tr("Text color"), "Text"),
                (self.tr("Background color"), "Background"),
            ],
        }

        self.menuView.clear()
        self.menuView.addMenu(self.menuMode)
        self.menuView.addSeparator()

        # print("Generating menus with", settings.viewSettings)

        for mnu, mnud in menus:
            m = QMenu(mnu, self.menuView)
            for s, sd in submenus[mnud]:
                m2 = QMenu(s, m)
                agp = QActionGroup(m2)
                for v, vd in values:
                    a = QAction(v, m)
                    a.setCheckable(True)
                    a.setData("{},{},{}".format(mnud, sd, vd))
                    if settings.viewSettings[mnud][sd] == vd:
                        a.setChecked(True)
                    a.triggered.connect(self.setViewSettingsAction, AUC)
                    agp.addAction(a)
                    m2.addAction(a)
                m.addMenu(m2)
            self.menuView.addMenu(m)

    def setViewSettingsAction(self):
        action = self.sender()
        item, part, element = action.data().split(",")
        self.setViewSettings(item, part, element)

    def setViewSettings(self, item, part, element):
        settings.viewSettings[item][part] = element
        if item == "Cork":
            self.mainEditor.updateCorkView()
        if item == "Outline":
            self.mainEditor.updateTreeView()
            self.treeOutlineOutline.viewport().update()
        if item == "Tree":
            self.treeRedacOutline.viewport().update()

    ###############################################################################
    # VIEW MODES
    ###############################################################################

    def setViewModeSimple(self):
        settings.viewMode = "simple"
        self.tabMain.setCurrentIndex(self.TabRedac)
        self.viewModeFictionVisibilitySwitch(False)
        self.actModeSimple.setChecked(True)

    def setViewModeFiction(self):
        settings.viewMode = "fiction"
        self.viewModeFictionVisibilitySwitch(True)
        self.actModeFiction.setChecked(True)

    def viewModeFictionVisibilitySwitch(self, val):
        """
        Swtiches the visibility of some UI components useful for fiction only
        @param val: sets visibility to val
        """

        # Menu navigation & boutton in toolbar
        self.toolbar.setDockVisibility(self.dckNavigation, val)

        # POV in metadatas
        from manuskript.ui.views.propertiesView import propertiesView
        for w in findWidgetsOfClass(propertiesView):
            w.lblPOV.setVisible(val)
            w.cmbPOV.setVisible(val)

        # POV in outline view
        if Outline.POV.value in settings.outlineViewColumns:
            settings.outlineViewColumns.remove(Outline.POV.value)

        from manuskript.ui.views.outlineView import outlineView
        for w in findWidgetsOfClass(outlineView):
            w.hideColumns()

        # TODO: clean up all other fiction things in non-fiction view mode
        # Character in search widget
        # POV in settings / views

    ###############################################################################
    # COMPILE
    ###############################################################################

    def doCompile(self):
        self.compileDialog = compileDialog()
        self.compileDialog.show()
Ejemplo n.º 29
0
class Gui(QMainWindow, Ui_MainWindow):

    NOTEON = 0x9
    NOTEOFF = 0x8
    MIDICTRL = 11

    GREEN = ("#cell_frame { border: 0px; border-radius: 10px; "
             "background-color: rgb(125,242,0);}")
    BLUE = ("#cell_frame { border: 0px; border-radius: 10px; "
            "background-color: rgb(0, 130, 240);}")
    RED = ("#cell_frame { border: 0px; border-radius: 10px; "
           "background-color: rgb(255, 21, 65);}")
    AMBER = ("#cell_frame { border: 0px; border-radius: 10px; "
             "background-color: rgb(255, 102, 0);}")
    PURPLE = ("#cell_frame { border: 0px; border-radius: 10px; "
              "background-color: rgb(130, 0, 240);}")
    DEFAULT = ("#cell_frame { border: 0px; border-radius: 10px; "
               "background-color: rgb(217, 217, 217);}")

    RECORD_BLINK = ("QPushButton {background-color: rgb(255, 255, 255);}"
                    "QPushButton:pressed {background-color: "
                    "rgb(98, 98, 98);}")

    RECORD_DEFAULT = ("QPushButton {background-color: rgb(0, 0, 0);}"
                      "QPushButton:pressed {background-color: "
                      "rgb(98, 98, 98);}")

    STATE_COLORS = {Clip.STOP: RED,
                    Clip.STARTING: GREEN,
                    Clip.START: GREEN,
                    Clip.STOPPING: RED,
                    Clip.PREPARE_RECORD: AMBER,
                    Clip.RECORDING: AMBER}
    STATE_BLINK = {Clip.STOP: False,
                   Clip.STARTING: True,
                   Clip.START: False,
                   Clip.STOPPING: True,
                   Clip.PREPARE_RECORD: True,
                   Clip.RECORDING: False}

    BLINK_DURATION = 200
    PROGRESS_PERIOD = 300

    updateUi = pyqtSignal()
    readQueueIn = pyqtSignal()

    def __init__(self, song, jack_client):
        QObject.__init__(self)
        super(Gui, self).__init__()
        self._jack_client = jack_client
        self.setupUi(self)
        self.clip_volume.knobRadius = 3
        self.is_learn_device_mode = False
        self.queue_out, self.queue_in = Queue(), Queue()
        self.updateUi.connect(self.update)
        self.readQueueIn.connect(self.readQueue)
        self.current_vol_block = 0

        # Load devices
        self.deviceGroup = QActionGroup(self.menuDevice)
        self.devices = []
        settings = QSettings('superboucle', 'devices')
        if settings.contains('devices') and settings.value('devices'):
            for raw_device in settings.value('devices'):
                self.devices.append(Device(pickle.loads(raw_device)))
        else:
            self.devices.append(Device({'name': 'No Device', }))
        self.updateDevices()
        self.deviceGroup.triggered.connect(self.onDeviceSelect)

        # Load song
        self.initUI(song)

        self.actionNew.triggered.connect(self.onActionNew)
        self.actionOpen.triggered.connect(self.onActionOpen)
        self.actionSave.triggered.connect(self.onActionSave)
        self.actionSave_As.triggered.connect(self.onActionSaveAs)
        self.actionAdd_Device.triggered.connect(self.onAddDevice)
        self.actionManage_Devices.triggered.connect(self.onManageDevice)
        self.actionFullScreen.triggered.connect(self.onActionFullScreen)
        self.master_volume.valueChanged.connect(self.onMasterVolumeChange)
        self.rewindButton.clicked.connect(self.onRewindClicked)
        self.playButton.clicked.connect(self._jack_client.transport_start)
        self.pauseButton.clicked.connect(self._jack_client.transport_stop)
        self.gotoButton.clicked.connect(self.onGotoClicked)
        self.recordButton.clicked.connect(self.onRecord)
        self.clip_name.textChanged.connect(self.onClipNameChange)
        self.clip_volume.valueChanged.connect(self.onClipVolumeChange)
        self.beat_diviser.valueChanged.connect(self.onBeatDiviserChange)
        self.frame_offset.valueChanged.connect(self.onFrameOffsetChange)
        self.beat_offset.valueChanged.connect(self.onBeatOffsetChange)
        self.revertButton.clicked.connect(self.onRevertClip)
        self.normalizeButton.clicked.connect(self.onNormalizeClip)
        self.exportButton.clicked.connect(self.onExportClip)
        self.deleteButton.clicked.connect(self.onDeleteClipClicked)

        self.blktimer = QTimer()
        self.blktimer.state = False
        self.blktimer.timeout.connect(self.toogleBlinkButton)
        self.blktimer.start(self.BLINK_DURATION)

        self.disptimer = QTimer()
        self.disptimer.start(self.PROGRESS_PERIOD)
        self.disptimer.timeout.connect(self.updateProgress)

        self._jack_client.set_timebase_callback(self.timebase_callback)

        self.show()

    def initUI(self, song):

        # remove old buttons
        self.btn_matrix = [[None for y in range(song.height)]
                           for x in range(song.width)]
        self.state_matrix = [[-1 for y in range(song.height)]
                             for x in range(song.width)]
        for i in reversed(range(self.gridLayout.count())):
            self.gridLayout.itemAt(i).widget().close()
            self.gridLayout.itemAt(i).widget().setParent(None)

        self.song = song
        self.frame_clip.setEnabled(False)
        self.master_volume.setValue(song.volume*256)
        self.bpm.setValue(song.bpm)
        self.beat_per_bar.setValue(song.beat_per_bar)
        for x in range(song.width):
            for y in range(song.height):
                clip = song.clips_matrix[x][y]
                cell = Cell(self, clip, x, y)
                self.btn_matrix[x][y] = cell
                self.gridLayout.addWidget(cell, y, x)

        # send init command
        for init_cmd in self.device.init_command:
            self.queue_out.put(init_cmd)

        self.update()

    def closeEvent(self, event):
        settings = QSettings('superboucle', 'devices')
        settings.setValue('devices',
                          [pickle.dumps(x.mapping) for x in self.devices])

    def onStartStopClicked(self):
        clip = self.sender().parent().parent().clip
        self.startStop(clip.x, clip.y)

    def startStop(self, x, y):
        clip = self.btn_matrix[x][y].clip
        if clip is None:
            return
        if self.song.is_record:
            self.song.is_record = False
            self.updateRecordBtn()
            # calculate buffer size
            state, position = self._jack_client.transport_query()
            bps = position['beats_per_minute'] / 60
            fps = position['frame_rate']
            size = (1 / bps) * clip.beat_diviser * fps
            self.song.init_record_buffer(clip, 2, size, fps)
            # set frame offset based on jack block size
            clip.frame_offset = self._jack_client.blocksize
            clip.state = Clip.PREPARE_RECORD
            self.recordButton.setStyleSheet(self.RECORD_DEFAULT)
        else:
            self.song.toogle(clip.x, clip.y)
        self.update()

    def onEdit(self):
        self.last_clip = self.sender().parent().parent().clip
        if self.last_clip:
            self.frame_clip.setEnabled(True)
            self.clip_name.setText(self.last_clip.name)
            self.frame_offset.setValue(self.last_clip.frame_offset)
            self.beat_offset.setValue(self.last_clip.beat_offset)
            self.beat_diviser.setValue(self.last_clip.beat_diviser)
            self.clip_volume.setValue(self.last_clip.volume*256)
            state, position = self._jack_client.transport_query()
            fps = position['frame_rate']
            bps = self.bpm.value() / 60
            if self.bpm.value() and fps:
                size_in_beat = (bps/fps)*self.song.length(self.last_clip)
            else:
                size_in_beat = "No BPM info"
            clip_description = ("Size in sample : %s\nSize in beat : %s"
                                % (self.song.length(self.last_clip),
                                   round(size_in_beat, 1)))

            self.clip_description.setText(clip_description)

    def onAddClipClicked(self):
        AddClipDialog(self, self.sender().parent().parent())

    def onRevertClip(self):
        if self.last_clip and self.last_clip.audio_file:
            audio_file = self.last_clip.audio_file
            self.song.data[audio_file] = self.song.data[audio_file][::-1]

    def onNormalizeClip(self):
        if self.last_clip and self.last_clip.audio_file:
            audio_file = self.last_clip.audio_file
            absolute_val = np.absolute(self.song.data[audio_file])
            current_level = np.ndarray.max(absolute_val)
            self.song.data[audio_file][:] *= (1 / current_level)

    def onExportClip(self):
        if self.last_clip and self.last_clip.audio_file:
            audio_file = self.last_clip.audio_file
            file_name, a = (
                QFileDialog.getSaveFileName(self,
                                            'Export Clip : %s'
                                            % self.last_clip.name,
                                            expanduser('~'),
                                            'WAVE (*.wav)'))

            if file_name:
                file_name = verify_ext(file_name, 'wav')
                sf.write(self.song.data[audio_file], file_name,
                         self.song.samplerate[audio_file],
                         subtype=sf.default_subtype('WAV'),
                         format='WAV')

    def onDeleteClipClicked(self):
        if self.last_clip:
            response = QMessageBox.question(self,
                                            "Delete Clip ?",
                                            ("Are you sure "
                                             "to delete the clip ?"))
            if response == QMessageBox.Yes:
                self.frame_clip.setEnabled(False)
                self.song.removeClip(self.last_clip)
                self.initUI(self.song)

    def onMasterVolumeChange(self):
        self.song.volume = (self.master_volume.value() / 256)

    def onStartClicked(self):
        pass
        self._jack_client.transport_start

    def onGotoClicked(self):
        state, position = self._jack_client.transport_query()
        new_position = (position['beats_per_bar']
                        * (self.gotoTarget.value() - 1)
                        * position['frame_rate']
                        * (60 / position['beats_per_minute']))
        self._jack_client.transport_locate(int(round(new_position, 0)))

    def onRecord(self):
        self.song.is_record = not self.song.is_record
        self.updateRecordBtn()

    def updateRecordBtn(self):
        if not self.song.is_record:
            self.recordButton.setStyleSheet(self.RECORD_DEFAULT)
        if self.device.record_btn:
            (msg_type, channel, pitch, velocity) = self.device.record_btn
            if self.song.is_record:
                color = self.device.blink_amber_vel
            else:
                color = self.device.black_vel
            self.queue_out.put(((msg_type << 4) + channel, pitch, color))

    def onRewindClicked(self):
        self._jack_client.transport_locate(0)

    def onClipNameChange(self):
        self.last_clip.name = self.clip_name.text()
        tframe = self.btn_matrix[self.last_clip.x][self.last_clip.y]
        tframe.clip_name.setText(self.last_clip.name)

    def onClipVolumeChange(self):
        self.last_clip.volume = (self.clip_volume.value() / 256)

    def onBeatDiviserChange(self):
        self.last_clip.beat_diviser = self.beat_diviser.value()

    def onFrameOffsetChange(self):
        self.last_clip.frame_offset = self.frame_offset.value()

    def onBeatOffsetChange(self):
        self.last_clip.beat_offset = self.beat_offset.value()

    def onActionNew(self):
        NewSongDialog(self)

    def onActionOpen(self):
        file_name, a = (
            QFileDialog.getOpenFileName(self,
                                        'Open file',
                                        expanduser('~'),
                                        'Super Boucle Song (*.sbs)'))
        if file_name:
            self.setEnabled(False)
            message = QMessageBox(self)
            message.setWindowTitle("Loading ....")
            message.setText("Reading Files, please wait ...")
            message.show()
            self.initUI(load_song_from_file(file_name))
            message.close()
            self.setEnabled(True)

    def onActionSave(self):
        if self.song.file_name:
            self.song.save()
        else:
            self.onActionSaveAs()

    def onActionSaveAs(self):
        file_name, a = (
            QFileDialog.getSaveFileName(self,
                                        'Save As',
                                        expanduser('~'),
                                        'Super Boucle Song (*.sbs)'))

        if file_name:
            file_name = verify_ext(file_name, 'sbs')
            self.song.file_name = file_name
            self.song.save()
            print("File saved to : {}".format(self.song.file_name))

    def onAddDevice(self):
        self.learn_device = LearnDialog(self, self.addDevice)
        self.is_learn_device_mode = True

    def onManageDevice(self):
        ManageDialog(self)

    def onActionFullScreen(self):
        if self.isFullScreen():
            self.showNormal()
        else:
            self.showFullScreen()
        self.show()

    def update(self):
        for x in range(len(self.song.clips_matrix)):
            line = self.song.clips_matrix[x]
            for y in range(len(line)):
                clp = line[y]
                if clp is None:
                    state = None
                else:
                    state = clp.state
                if state != self.state_matrix[x][y]:
                    if clp:
                        self.setCellColor(x,
                                          y,
                                          self.STATE_COLORS[state],
                                          self.STATE_BLINK[state])
                    try:
                        self.queue_out.put(self.device.generateNote(x,
                                                                    y,
                                                                    state))
                    except IndexError:
                        # print("No cell associated to %s x %s"
                        # % (clp.x, clp.y))
                        pass
                self.state_matrix[x][y] = state

    def redraw(self):
        self.state_matrix = [[-1 for x in range(self.song.height)]
                             for x in range(self.song.width)]
        self.update()

    def readQueue(self):
        try:
            while True:
                note = self.queue_in.get(block=False)
                if len(note) == 3:
                    status, pitch, vel = struct.unpack('3B', note)
                    channel = status & 0xF
                    msg_type = status >> 4
                    self.processNote(msg_type, channel, pitch, vel)
                # else:
                # print("Invalid message length")
        except Empty:
            pass

    def processNote(self, msg_type, channel, pitch, vel):

        btn_id = (msg_type,
                  channel,
                  pitch,
                  vel)
        btn_id_vel = (msg_type, channel, pitch, -1)
        ctrl_key = (msg_type, channel, pitch)

        # master volume
        if ctrl_key == self.device.master_volume_ctrl:
            self.song.master_volume = vel / 127
            (self.master_volume
             .setValue(self.song.master_volume * 256))
        elif self.device.play_btn in [btn_id, btn_id_vel]:
            self._jack_client.transport_start()
        elif self.device.pause_btn in [btn_id, btn_id_vel]:
            self._jack_client.transport_stop()
        elif self.device.rewind_btn in [btn_id, btn_id_vel]:
            self.onRewindClicked()
        elif self.device.goto_btn in [btn_id, btn_id_vel]:
            self.onGotoClicked()
        elif self.device.record_btn in [btn_id, btn_id_vel]:
            self.onRecord()
        elif ctrl_key in self.device.ctrls:
            try:
                ctrl_index = self.device.ctrls.index(ctrl_key)
                clip = (self.song.clips_matrix
                        [ctrl_index]
                        [self.current_vol_block])
                if clip:
                    clip.volume = vel / 127
                    if self.last_clip == clip:
                        self.clip_volume.setValue(self.last_clip.volume * 256)
            except KeyError:
                pass
        elif (btn_id in self.device.block_buttons
              or btn_id_vel in self.device.block_buttons):
            try:
                self.current_vol_block = (
                    self.device.block_buttons.index(btn_id))
            except ValueError:
                self.current_vol_block = (
                    self.device.block_buttons.index(btn_id_vel))
            for i in range(len(self.device.block_buttons)):
                (a, b_channel, b_pitch, b) = self.device.block_buttons[i]
                if i == self.current_vol_block:
                    color = self.device.red_vel
                else:
                    color = self.device.black_vel
                self.queue_out.put(((self.NOTEON << 4) + b_channel,
                                    b_pitch,
                                    color))
        else:
            x, y = -1, -1
            try:
                x, y = self.device.getXY(btn_id)
            except IndexError:
                pass
            except KeyError:
                try:
                    x, y = self.device.getXY(btn_id_vel)
                except KeyError:
                    pass

            if (x >= 0 and y >= 0):
                self.startStop(x, y)

    def setCellColor(self, x, y, color, blink=False):
        self.btn_matrix[x][y].setStyleSheet(color)
        self.btn_matrix[x][y].blink = blink
        self.btn_matrix[x][y].color = color

    def toogleBlinkButton(self):
        for line in self.btn_matrix:
            for btn in line:
                if btn.blink:
                    if self.blktimer.state:
                        btn.setStyleSheet(btn.color)
                    else:
                        btn.setStyleSheet(self.DEFAULT)
        if self.song.is_record:
            if self.blktimer.state:
                self.recordButton.setStyleSheet(self.RECORD_BLINK)
            else:
                self.recordButton.setStyleSheet(self.RECORD_DEFAULT)

        self.blktimer.state = not self.blktimer.state

    def updateProgress(self):
        state, pos = self._jack_client.transport_query()
        if 'bar' in pos:
            bbt = "%d|%d|%03d" % (pos['bar'], pos['beat'], pos['tick'])
        else:
            bbt = "-|-|-"
        seconds = int(pos['frame'] / pos['frame_rate'])
        (minutes, second) = divmod(seconds, 60)
        (hour, minute) = divmod(minutes, 60)
        time = "%d:%02d:%02d" % (hour, minute, second)
        self.bbtLabel.setText("%s\n%s" % (bbt, time))
        for line in self.btn_matrix:
            for btn in line:
                if btn.clip and btn.clip.audio_file:
                    value = ((btn.clip.last_offset
                              / self.song.length(btn.clip))
                             * 97)
                    btn.clip_position.setValue(value)
                    btn.clip_position.repaint()

    def updateDevices(self):
        for action in self.deviceGroup.actions():
            self.deviceGroup.removeAction(action)
            self.menuDevice.removeAction(action)
        for device in self.devices:
            action = QAction(device.name, self.menuDevice)
            action.setCheckable(True)
            action.setData(device)
            self.menuDevice.addAction(action)
            self.deviceGroup.addAction(action)
        action.setChecked(True)
        self.device = device

    def addDevice(self, device):
        self.devices.append(device)
        self.updateDevices()
        self.is_learn_device_mode = False

    def onDeviceSelect(self):
        self.device = self.deviceGroup.checkedAction().data()
        if self.device:
            if self.device.init_command:
                for note in self.device.init_command:
                    self.queue_out.put(note)
            self.redraw()

    def timebase_callback(self, state, nframes, pos, new_pos):
        pos.valid = 0x10
        pos.bar_start_tick = BAR_START_TICK
        pos.beats_per_bar = self.beat_per_bar.value()
        pos.beat_type = BEAT_TYPE
        pos.ticks_per_beat = TICKS_PER_BEAT
        pos.beats_per_minute = self.bpm.value()
        ticks = frame2bbt(pos.frame,
                          pos.ticks_per_beat,
                          pos.beats_per_minute,
                          pos.frame_rate)
        (beats, pos.tick) = divmod(int(round(ticks, 0)),
                                   int(round(pos.ticks_per_beat, 0)))
        (bar, beat) = divmod(beats, int(round(pos.beats_per_bar, 0)))
        (pos.bar, pos.beat) = (bar + 1, beat + 1)
        return None
Ejemplo n.º 30
0
class GUI(QtWidgets.QMainWindow):
    def __init__(self, control: "Controller") -> None:
        super(GUI, self).__init__()
        uic.loadUi(
            pkg_resources.resource_filename("labelCloud.resources.interfaces",
                                            "interface.ui"),
            self,
        )
        self.resize(1500, 900)
        self.setWindowTitle("labelCloud")
        self.setStyleSheet(
            STYLESHEET.format(icons_dir=str(
                Path(__file__).resolve().parent.parent.joinpath(
                    "resources").joinpath("icons"))))

        # MENU BAR
        # File
        self.action_setpcdfolder = self.findChild(QtWidgets.QAction,
                                                  "action_setpcdfolder")
        self.action_setlabelfolder = self.findChild(QtWidgets.QAction,
                                                    "action_setlabelfolder")

        # Labels
        self.action_deletelabels = self.findChild(QtWidgets.QAction,
                                                  "action_deletealllabels")
        self.menu_setdefaultclass = self.findChild(QtWidgets.QMenu,
                                                   "menu_setdefaultclass")
        self.actiongroup_defaultclass = QActionGroup(self.menu_setdefaultclass)

        # Settings
        self.action_zrotation = self.findChild(QtWidgets.QAction,
                                               "action_zrotationonly")
        self.action_showfloor = self.findChild(QtWidgets.QAction,
                                               "action_showfloor")
        self.action_showorientation = self.findChild(QtWidgets.QAction,
                                                     "action_showorientation")
        self.action_saveperspective = self.findChild(QtWidgets.QAction,
                                                     "action_saveperspective")
        self.action_alignpcd = self.findChild(QtWidgets.QAction,
                                              "action_alignpcd")
        self.action_change_settings = self.findChild(QtWidgets.QAction,
                                                     "action_changesettings")

        # STATUS BAR
        self.status = self.findChild(QtWidgets.QStatusBar, "statusbar")
        self.mode_status = QtWidgets.QLabel("Navigation Mode")
        self.mode_status.setStyleSheet(
            "font-weight: bold; font-size: 14px; min-width: 235px;")
        self.mode_status.setAlignment(Qt.AlignCenter)
        self.status.addWidget(self.mode_status, stretch=0)
        self.tmp_status = QtWidgets.QLabel()
        self.tmp_status.setStyleSheet("font-size: 14px;")
        self.status.addWidget(self.tmp_status, stretch=1)

        # CENTRAL WIDGET
        self.glWidget: GLWidget = self.findChild(GLWidget, "openGLWidget")

        # LEFT PANEL
        # point cloud management
        self.label_curr_pcd = self.findChild(QtWidgets.QLabel,
                                             "label_pcd_current")
        self.button_prev_pcd = self.findChild(QtWidgets.QPushButton,
                                              "button_pcd_prev")
        self.button_next_pcd = self.findChild(QtWidgets.QPushButton,
                                              "button_pcd_next")
        self.progressbar_pcd = self.findChild(QtWidgets.QProgressBar,
                                              "progressbar_pcds")

        # bbox control section
        self.button_up = self.findChild(QtWidgets.QPushButton,
                                        "button_bbox_up")
        self.button_down = self.findChild(QtWidgets.QPushButton,
                                          "button_bbox_down")
        self.button_left = self.findChild(QtWidgets.QPushButton,
                                          "button_bbox_left")
        self.button_right = self.findChild(QtWidgets.QPushButton,
                                           "button_bbox_right")
        self.button_forward = self.findChild(QtWidgets.QPushButton,
                                             "button_bbox_forward")
        self.button_backward = self.findChild(QtWidgets.QPushButton,
                                              "button_bbox_backward")
        self.dial_zrotation = self.findChild(QtWidgets.QDial,
                                             "dial_bbox_zrotation")
        self.button_decr_dim = self.findChild(QtWidgets.QPushButton,
                                              "button_bbox_decr")
        self.button_incr_dim = self.findChild(QtWidgets.QPushButton,
                                              "button_bbox_incr")

        # 2d image viewer
        self.button_2D = self.findChild(QtWidgets.QPushButton,
                                        "button_open_2D")
        self.button_2D.setVisible(
            config.getboolean("USER_INTERFACE", "show_2d_image"))

        # label mode selection
        self.button_activate_picking = self.findChild(QtWidgets.QPushButton,
                                                      "button_pick_bbox")
        self.button_activate_spanning = self.findChild(QtWidgets.QPushButton,
                                                       "button_span_bbox")
        self.button_save_labels = self.findChild(QtWidgets.QPushButton,
                                                 "button_save_label")

        # RIGHT PANEL
        self.label_list = self.findChild(QtWidgets.QListWidget, "label_list")
        self.curr_class_edit = self.findChild(QtWidgets.QLineEdit,
                                              "current_class_lineedit")
        # self.curr_bbox_stats = self.findChild(QtWidgets.QLabel, "current_bbox_stats")
        self.button_deselect_label = self.findChild(QtWidgets.QPushButton,
                                                    "button_label_deselect")
        self.button_delete_label = self.findChild(QtWidgets.QPushButton,
                                                  "button_label_delete")

        # BOUNDING BOX PARAMETER EDITS
        self.pos_x_edit = self.findChild(QtWidgets.QLineEdit, "pos_x_edit")
        self.pos_y_edit = self.findChild(QtWidgets.QLineEdit, "pos_y_edit")
        self.pos_z_edit = self.findChild(QtWidgets.QLineEdit, "pos_z_edit")

        self.length_edit = self.findChild(QtWidgets.QLineEdit, "length_edit")
        self.width_edit = self.findChild(QtWidgets.QLineEdit, "width_edit")
        self.height_edit = self.findChild(QtWidgets.QLineEdit, "height_edit")

        self.rot_x_edit = self.findChild(QtWidgets.QLineEdit, "rot_x_edit")
        self.rot_y_edit = self.findChild(QtWidgets.QLineEdit, "rot_y_edit")
        self.rot_z_edit = self.findChild(QtWidgets.QLineEdit, "rot_z_edit")

        self.all_line_edits = [
            self.curr_class_edit,
            self.pos_x_edit,
            self.pos_y_edit,
            self.pos_z_edit,
            self.length_edit,
            self.width_edit,
            self.height_edit,
            self.rot_x_edit,
            self.rot_y_edit,
            self.rot_z_edit,
        ]

        self.volume_label = self.findChild(QtWidgets.QLabel,
                                           "volume_value_label")

        # Connect with controller
        self.controller = control
        self.controller.startup(self)

        # Connect all events to functions
        self.connect_events()
        self.set_checkbox_states()  # tick in menu
        self.update_label_completer(
        )  # initialize label completer with classes in config
        self.update_default_object_class_menu()

        # Start event cycle
        self.timer = QtCore.QTimer(self)
        self.timer.setInterval(20)  # period, in milliseconds
        self.timer.timeout.connect(self.controller.loop_gui)
        self.timer.start()

    # Event connectors
    def connect_events(self) -> None:
        # POINTCLOUD CONTROL
        self.button_next_pcd.clicked.connect(
            lambda: self.controller.next_pcd(save=True))
        self.button_prev_pcd.clicked.connect(self.controller.prev_pcd)

        # BBOX CONTROL
        self.button_up.pressed.connect(
            lambda: self.controller.bbox_controller.translate_along_z())
        self.button_down.pressed.connect(
            lambda: self.controller.bbox_controller.translate_along_z(down=True
                                                                      ))
        self.button_left.pressed.connect(
            lambda: self.controller.bbox_controller.translate_along_x(left=True
                                                                      ))
        self.button_right.pressed.connect(
            self.controller.bbox_controller.translate_along_x)
        self.button_forward.pressed.connect(
            lambda: self.controller.bbox_controller.translate_along_y(forward=
                                                                      True))
        self.button_backward.pressed.connect(
            lambda: self.controller.bbox_controller.translate_along_y())

        self.dial_zrotation.valueChanged.connect(
            lambda x: self.controller.bbox_controller.rotate_around_z(
                x, absolute=True))
        self.button_decr_dim.clicked.connect(
            lambda: self.controller.bbox_controller.scale(decrease=True))
        self.button_incr_dim.clicked.connect(
            lambda: self.controller.bbox_controller.scale())

        # LABELING CONTROL
        self.curr_class_edit.textChanged.connect(
            self.controller.bbox_controller.set_classname)
        self.button_deselect_label.clicked.connect(
            self.controller.bbox_controller.deselect_bbox)
        self.button_delete_label.clicked.connect(
            self.controller.bbox_controller.delete_current_bbox)
        self.label_list.currentRowChanged.connect(
            self.controller.bbox_controller.set_active_bbox)

        # open_2D_img
        self.button_2D.pressed.connect(lambda: self.show_2d_image())

        # LABEL CONTROL
        self.button_activate_picking.clicked.connect(
            lambda: self.controller.drawing_mode.set_drawing_strategy(
                PickingStrategy(self)))
        self.button_activate_spanning.clicked.connect(
            lambda: self.controller.drawing_mode.set_drawing_strategy(
                SpanningStrategy(self)))
        self.button_save_labels.clicked.connect(self.controller.save)

        # BOUNDING BOX PARAMETER
        self.pos_x_edit.editingFinished.connect(
            lambda: self.update_bbox_parameter("pos_x"))
        self.pos_y_edit.editingFinished.connect(
            lambda: self.update_bbox_parameter("pos_y"))
        self.pos_z_edit.editingFinished.connect(
            lambda: self.update_bbox_parameter("pos_z"))

        self.length_edit.editingFinished.connect(
            lambda: self.update_bbox_parameter("length"))
        self.width_edit.editingFinished.connect(
            lambda: self.update_bbox_parameter("width"))
        self.height_edit.editingFinished.connect(
            lambda: self.update_bbox_parameter("height"))

        self.rot_x_edit.editingFinished.connect(
            lambda: self.update_bbox_parameter("rot_x"))
        self.rot_y_edit.editingFinished.connect(
            lambda: self.update_bbox_parameter("rot_y"))
        self.rot_z_edit.editingFinished.connect(
            lambda: self.update_bbox_parameter("rot_z"))

        # MENU BAR
        self.action_setpcdfolder.triggered.connect(
            self.change_pointcloud_folder)
        self.action_setlabelfolder.triggered.connect(self.change_label_folder)
        self.actiongroup_defaultclass.triggered.connect(
            self.change_default_object_class)
        self.action_deletelabels.triggered.connect(
            self.controller.bbox_controller.reset)
        self.action_zrotation.toggled.connect(set_zrotation_only)
        self.action_showfloor.toggled.connect(set_floor_visibility)
        self.action_showorientation.toggled.connect(set_orientation_visibility)
        self.action_saveperspective.toggled.connect(
            lambda state: self.controller.pcd_manager.save_current_perspective(
                state))
        self.action_alignpcd.toggled.connect(
            self.controller.align_mode.change_activation)
        self.action_change_settings.triggered.connect(
            self.show_settings_dialog)

    def set_checkbox_states(self) -> None:
        self.action_showfloor.setChecked(
            config.getboolean("USER_INTERFACE", "show_floor"))
        self.action_showorientation.setChecked(
            config.getboolean("USER_INTERFACE", "show_orientation"))
        self.action_zrotation.setChecked(
            config.getboolean("USER_INTERFACE", "z_rotation_only"))

    # Collect, filter and forward events to viewer
    def eventFilter(self, event_object, event) -> bool:
        # Keyboard Events
        # if (event.type() == QEvent.KeyPress) and (not self.line_edited_activated()):
        if (event.type()
                == QEvent.KeyPress) and (event_object
                                         == self):  # TODO: Cleanup old filter
            self.controller.key_press_event(event)
            self.update_bbox_stats(
                self.controller.bbox_controller.get_active_bbox())
            return True  # TODO: Recheck pyqt behaviour
        elif event.type() == QEvent.KeyRelease:
            self.controller.key_release_event(event)

        # Mouse Events
        elif (event.type() == QEvent.MouseMove) and (event_object
                                                     == self.glWidget):
            self.controller.mouse_move_event(event)
            self.update_bbox_stats(
                self.controller.bbox_controller.get_active_bbox())
        elif (event.type() == QEvent.Wheel) and (event_object
                                                 == self.glWidget):
            self.controller.mouse_scroll_event(event)
            self.update_bbox_stats(
                self.controller.bbox_controller.get_active_bbox())
        elif event.type() == QEvent.MouseButtonDblClick and (event_object
                                                             == self.glWidget):
            self.controller.mouse_double_clicked(event)
            return True
        elif (event.type() == QEvent.MouseButtonPress) and (event_object
                                                            == self.glWidget):
            self.controller.mouse_clicked(event)
            self.update_bbox_stats(
                self.controller.bbox_controller.get_active_bbox())
        elif (event.type() == QEvent.MouseButtonPress) and (
                event_object != self.curr_class_edit):
            self.curr_class_edit.clearFocus()
            self.update_bbox_stats(
                self.controller.bbox_controller.get_active_bbox())
        return False

    def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
        logging.info("Closing window after saving ...")
        self.controller.save()
        self.timer.stop()
        a0.accept()

    def show_settings_dialog(self) -> None:
        dialog = SettingsDialog(self)
        dialog.exec()

    def show_2d_image(self):
        """Searches for a 2D image with the point cloud name and displays it in a new window."""
        image_folder = config.getpath("FILE", "image_folder")

        # Look for image files with the name of the point cloud
        files_in_image_folder = sorted(image_folder.iterdir())
        pcd_name = self.controller.pcd_manager.pcd_path.stem
        image_file_pattern = re.compile(
            f"{pcd_name}+(\.(?i:(jpe?g|png|gif|bmp|tiff)))")

        try:
            image_name = next(
                filter(image_file_pattern.search, files_in_image_folder))
        except StopIteration:
            QMessageBox.information(
                self,
                "No 2d image File",
                (f"Could not find a related image in the image folder ({image_folder}).\n"
                 "Check your path to the folder or if an image for this point cloud exists."
                 ),
                QMessageBox.Ok,
            )
        else:
            image_path = image_folder.joinpath(image_name)
            image = QtGui.QImage(QtGui.QImageReader(str(image_path)).read())
            self.imageLabel = QLabel()
            self.imageLabel.setWindowTitle(f"2D Image ({image_name})")
            self.imageLabel.setPixmap(QPixmap.fromImage(image))
            self.imageLabel.show()

    def show_no_pointcloud_dialog(self, pcd_folder: Path,
                                  pcd_extensions: List[str]) -> None:
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Warning)
        msg.setText(
            "<b>labelCloud could not find any valid point cloud files inside the "
            "specified folder.</b>")
        msg.setInformativeText(
            f"Please copy all your point clouds into <em>{pcd_folder}</em> or change "
            "the point cloud folder location. labelCloud supports the following point "
            f"cloud file formats:\n {', '.join(pcd_extensions)}.")
        msg.setWindowTitle("No Point Clouds Found")
        msg.exec_()

    # VISUALIZATION METHODS

    def set_pcd_label(self, pcd_name: str) -> None:
        self.label_curr_pcd.setText("Current: <em>%s</em>" % pcd_name)

    def init_progress(self, min_value, max_value):
        self.progressbar_pcd.setMinimum(min_value)
        self.progressbar_pcd.setMaximum(max_value)

    def update_progress(self, value) -> None:
        self.progressbar_pcd.setValue(value)

    def update_curr_class_edit(self, force: str = None) -> None:
        if force is not None:
            self.curr_class_edit.setText(force)
        else:
            self.curr_class_edit.setText(self.controller.bbox_controller.
                                         get_active_bbox().get_classname())

    def update_label_completer(self, classnames=None) -> None:
        if classnames is None:
            classnames = set()
        classnames.update(config.getlist("LABEL", "object_classes"))
        self.curr_class_edit.setCompleter(QCompleter(classnames))

    def update_bbox_stats(self, bbox) -> None:
        viewing_precision = config.getint("USER_INTERFACE",
                                          "viewing_precision")
        if bbox and not self.line_edited_activated():
            self.pos_x_edit.setText(
                str(round(bbox.get_center()[0], viewing_precision)))
            self.pos_y_edit.setText(
                str(round(bbox.get_center()[1], viewing_precision)))
            self.pos_z_edit.setText(
                str(round(bbox.get_center()[2], viewing_precision)))

            self.length_edit.setText(
                str(round(bbox.get_dimensions()[0], viewing_precision)))
            self.width_edit.setText(
                str(round(bbox.get_dimensions()[1], viewing_precision)))
            self.height_edit.setText(
                str(round(bbox.get_dimensions()[2], viewing_precision)))

            self.rot_x_edit.setText(str(round(bbox.get_x_rotation(), 1)))
            self.rot_y_edit.setText(str(round(bbox.get_y_rotation(), 1)))
            self.rot_z_edit.setText(str(round(bbox.get_z_rotation(), 1)))

            self.volume_label.setText(
                str(round(bbox.get_volume(), viewing_precision)))

    def update_bbox_parameter(self, parameter: str) -> None:
        str_value = None
        self.setFocus()  # Changes the focus from QLineEdit to the window

        if parameter == "pos_x":
            str_value = self.pos_x_edit.text()
        if parameter == "pos_y":
            str_value = self.pos_y_edit.text()
        if parameter == "pos_z":
            str_value = self.pos_z_edit.text()
        if str_value and string_is_float(str_value):
            self.controller.bbox_controller.update_position(
                parameter, float(str_value))
            return True

        if parameter == "length":
            str_value = self.length_edit.text()
        if parameter == "width":
            str_value = self.width_edit.text()
        if parameter == "height":
            str_value = self.height_edit.text()
        if str_value and string_is_float(str_value, recect_negative=True):
            self.controller.bbox_controller.update_dimension(
                parameter, float(str_value))
            return True

        if parameter == "rot_x":
            str_value = self.rot_x_edit.text()
        if parameter == "rot_y":
            str_value = self.rot_y_edit.text()
        if parameter == "rot_z":
            str_value = self.rot_z_edit.text()
        if str_value and string_is_float(str_value):
            self.controller.bbox_controller.update_rotation(
                parameter, float(str_value))
            return True

    # Enables, disables the draw mode
    def activate_draw_modes(self, state: bool) -> None:
        self.button_activate_picking.setEnabled(state)
        self.button_activate_spanning.setEnabled(state)

    def update_status(self, message: str, mode: str = None) -> None:
        self.tmp_status.setText(message)
        if mode:
            self.update_mode_status(mode)

    def line_edited_activated(self) -> bool:
        for line_edit in self.all_line_edits:
            if line_edit.hasFocus():
                return True
        return False

    def update_mode_status(self, mode: str) -> None:
        self.action_alignpcd.setEnabled(True)
        if mode == "drawing":
            text = "Drawing Mode"
            self.action_alignpcd.setEnabled(False)
        elif mode == "correction":
            text = "Correction Mode"
        elif mode == "alignment":
            text = "Alignment Mode"
        else:
            text = "Navigation Mode"
        self.mode_status.setText(text)

    def change_pointcloud_folder(self) -> None:
        path_to_folder = Path(
            QFileDialog.getExistingDirectory(
                self,
                "Change Point Cloud Folder",
                directory=config.get("FILE", "pointcloud_folder"),
            ))
        if not path_to_folder.is_dir():
            logging.warning("Please specify a valid folder path.")
        else:
            self.controller.pcd_manager.pcd_folder = path_to_folder
            self.controller.pcd_manager.read_pointcloud_folder()
            self.controller.pcd_manager.get_next_pcd()
            logging.info("Changed point cloud folder to %s!" % path_to_folder)

    def change_label_folder(self) -> None:
        path_to_folder = Path(
            QFileDialog.getExistingDirectory(
                self,
                "Change Label Folder",
                directory=config.get("FILE", "label_folder"),
            ))
        if not path_to_folder.is_dir():
            logging.warning("Please specify a valid folder path.")
        else:
            self.controller.pcd_manager.label_manager.label_folder = path_to_folder
            self.controller.pcd_manager.label_manager.label_strategy.update_label_folder(
                path_to_folder)
            logging.info("Changed label folder to %s!" % path_to_folder)

    def update_default_object_class_menu(self,
                                         new_classes: Set[str] = None) -> None:
        object_classes = set(config.getlist("LABEL", "object_classes"))
        object_classes.update(new_classes or [])
        existing_classes = {
            action.text()
            for action in self.actiongroup_defaultclass.actions()
        }
        for object_class in object_classes.difference(existing_classes):
            action = self.actiongroup_defaultclass.addAction(
                object_class)  # TODO: Add limiter for number of classes
            action.setCheckable(True)
            if object_class == config.get("LABEL", "std_object_class"):
                action.setChecked(True)

        self.menu_setdefaultclass.addActions(
            self.actiongroup_defaultclass.actions())

    def change_default_object_class(self, action: QAction) -> None:
        config.set("LABEL", "std_object_class", action.text())
        logging.info("Changed default object class to %s.", action.text())
Ejemplo n.º 31
0
    def _setupMenu(self):
        """Initializes menu bar and menu actions.

        Menu bar contains two menus: File and View. File menu contains Open
        (opens file dialog for importing data file) and Exit (closes
        application) actions. View menu contains 3 groups: one for line style
        actions (points/lines), one for line type of the fitted data (step vs.
        smooth curve), and one for plot type (MVF, intensity, or trend test on
        tab 1).
        """
        self.menu = self.menuBar()  # initialize menu bar

        # ---- File menu
        fileMenu = self.menu.addMenu("File")
        # open
        openFile = QAction("Open", self)
        openFile.setShortcut("Ctrl+O")
        openFile.setStatusTip("Import data file")
        openFile.triggered.connect(self.fileOpened)

        # export table (tab 2)
        exportTable2 = QAction("Export Table (Tab 2)", self)
        # exportTable.setShortcut("Ctrl+E")
        exportTable2.setStatusTip("Export tab 2 table to csv")
        exportTable2.triggered.connect(self.exportTable2)

        # export table (tab 3)
        exportTable3 = QAction("Export Table (Tab 3)", self)
        # exportTable3.setShortcut("Ctrl+E")
        exportTable3.setStatusTip("Export tab 3 table to csv")
        exportTable3.triggered.connect(self.exportTable3)

        # exit
        exitApp = QAction("Exit", self)
        exitApp.setShortcut("Ctrl+Q")
        exitApp.setStatusTip("Close application")
        exitApp.triggered.connect(self.closeEvent)

        # add actions to file menu
        fileMenu.addAction(openFile)
        fileMenu.addSeparator()
        fileMenu.addAction(exportTable2)
        fileMenu.addAction(exportTable3)
        fileMenu.addSeparator()
        fileMenu.addAction(exitApp)

        # ---- View menu
        viewMenu = self.menu.addMenu("View")
        # -- plotting style
        # submenu
        viewStyle = QActionGroup(viewMenu)

        # points
        viewPoints = QAction("Show Points", self, checkable=True)
        viewPoints.setShortcut("Ctrl+P")
        viewPoints.setStatusTip("Data shown as points on graphs")
        viewPoints.triggered.connect(self.setPointsView)
        viewStyle.addAction(viewPoints)

        # lines
        viewLines = QAction("Show Lines", self, checkable=True)
        viewLines.setShortcut("Ctrl+L")
        viewLines.setStatusTip("Data shown as lines on graphs")
        viewLines.triggered.connect(self.setLineView)
        viewStyle.addAction(viewLines)

        # points and lines
        viewBoth = QAction("Show Points and Lines", self, checkable=True)
        viewBoth.setShortcut("Ctrl+B")
        viewBoth.setStatusTip("Data shown as points and lines on graphs")
        viewBoth.setChecked(True)
        viewBoth.triggered.connect(self.setLineAndPointsView)
        viewStyle.addAction(viewBoth)

        # add actions to view menu
        viewMenu.addActions(viewStyle.actions())

        # -- line style (step vs smooth)
        lineStyle = QActionGroup(viewMenu)

        # smooth
        smooth = QAction("Smooth Plot (Fitted Models)", self, checkable=True)
        smooth.setShortcut("Ctrl+F")
        smooth.setStatusTip("Fitted model plot shows smooth curves")
        smooth.setChecked(True)
        smooth.triggered.connect(self.setSmoothPlot)
        lineStyle.addAction(smooth)

        # step
        step = QAction("Step Plot (Fitted Models)", self, checkable=True)
        step.setShortcut("Ctrl+D")
        step.setStatusTip("Fitted model plot shown as step")
        step.triggered.connect(self.setStepPlot)
        lineStyle.addAction(step)

        # add actions to view menu
        viewMenu.addSeparator()
        viewMenu.addActions(lineStyle.actions())

        # -- graph display
        graphStyle = QActionGroup(viewMenu)
        # MVF
        self.mvf = QAction("MVF Graph", self, checkable=True)
        self.mvf.setShortcut("Ctrl+M")
        self.mvf.setStatusTip("Graphs display MVF of data")
        self.mvf.setChecked(True)
        self.mvf.triggered.connect(self.setMVFView)
        graphStyle.addAction(self.mvf)
        # intensity
        self.intensity = QAction("Intensity Graph", self, checkable=True)
        self.intensity.setShortcut("Ctrl+I")
        self.intensity.setStatusTip("Graphs display failure intensity")
        self.intensity.triggered.connect(self.setIntensityView)
        graphStyle.addAction(self.intensity)

        # add actions to view menu
        viewMenu.addSeparator()
        viewMenu.addActions(graphStyle.actions())
Ejemplo n.º 32
0
class ToolManager(QObject):
    selectEnabledToolSignal = pyqtSignal()
    selectedToolChanged = pyqtSignal(list)
    
    ##
    # Emitted when the status information of the current tool changed.
    # @see AbstractTool.setStatusInfo()
    ##
    statusInfoChanged = pyqtSignal(str)

    def __init__(self, parent = None):
        super().__init__(parent)
        self.mActionGroup = QActionGroup(self)
        self.mSelectedTool = None
        self.mPreviouslyDisabledTool = None
        self.mMapDocument = None

        self.mActionGroup.setExclusive(True)
        self.mActionGroup.triggered.connect(self.actionTriggered)
        self.selectEnabledToolSignal.connect(self.selectEnabledTool)
        
    def __del__(self):
        pass

    ##
    # Sets the MapDocument on which the registered tools will operate.
    ##
    def setMapDocument(self, mapDocument):
        if (self.mMapDocument == mapDocument):
            return
        self.mMapDocument = mapDocument
        for action in self.mActionGroup.actions():
            tool = action.data()
            tool.setMapDocument(mapDocument)

    ##
    # Registers a new tool. The tool manager does not take ownership over the
    # tool.
    #
    # @return The action for activating the tool.
    ##
    def registerTool(self, tool):
        tool.setMapDocument(self.mMapDocument)
        toolAction = QAction(tool.icon, tool.name, self)
        toolAction.setShortcut(tool.shortcut)
        toolAction.setData(QVariant(tool))
        toolAction.setCheckable(True)
        toolAction.setToolTip("%s (%s)"%(tool.name, tool.shortcut.toString()))
        toolAction.setEnabled(tool.isEnabled())
        self.mActionGroup.addAction(toolAction)
        tool.enabledChanged.connect(self.toolEnabledChanged)
        # Select the first added tool
        if (not self.mSelectedTool and tool.isEnabled()):
            self.setSelectedTool(tool)
            toolAction.setChecked(True)
        return toolAction

    ##
    # Selects the given tool. It should be previously added using
    # registerTool().
    ##
    def selectTool(self, tool):
        if (tool and not tool.isEnabled()): # Refuse to select disabled tools
            return
        for action in self.mActionGroup.actions():
            if (action.data() == tool):
                action.trigger()
                return

        # The given tool was not found. Don't select any tool.
        for action in self.mActionGroup.actions():
            action.setChecked(False)
        self.setSelectedTool(None)

    ##
    # Returns the selected tool.
    ##
    def selectedTool(self):
        return self.mSelectedTool

    def retranslateTools(self):
        # Allow the tools to adapt to the new language
        for action in self.mActionGroup.actions():
            tool = action.data()
            tool.languageChanged()
            # Update the text, shortcut and tooltip of the action
            action.setText(tool.name)
            action.setShortcut(tool.shortcut)
            action.setToolTip("%s (%s)"%(tool.name, tool.shortcut.toString()))

    def actionTriggered(self, action):
        self.setSelectedTool(action.data())

    def toolEnabledChanged(self, enabled):
        tool = self.sender()
        for action in self.mActionGroup.actions():
            if (action.data() == tool):
                action.setEnabled(enabled)
                break

        # Switch to another tool when the current tool gets disabled. This is done
        # with a delayed call since we first want all the tools to update their
        # enabled state.
        if ((not enabled and tool == self.mSelectedTool) or (enabled and not self.mSelectedTool)):
            self.selectEnabledToolSignal.emit()

    def selectEnabledTool(self):
        # Avoid changing tools when it's no longer necessary
        if (self.mSelectedTool and self.mSelectedTool.isEnabled()):
            return
        currentTool = self.mSelectedTool
        # Prefer the tool we switched away from last time
        if (self.mPreviouslyDisabledTool and self.mPreviouslyDisabledTool.isEnabled()):
            self.selectTool(self.mPreviouslyDisabledTool)
        else:
            self.selectTool(self.firstEnabledTool())
        
        self.mPreviouslyDisabledTool = currentTool

    def firstEnabledTool(self):
        for action in self.mActionGroup.actions():
            tool = action.data()
            if tool:
                if (tool.isEnabled()):
                    return tool
        return None

    def setSelectedTool(self, tool):
        if (self.mSelectedTool == tool):
            return
        if (self.mSelectedTool):
            self.mSelectedTool.statusInfoChanged.disconnect(self.statusInfoChanged)

        self.mSelectedTool = tool
        self.selectedToolChanged.emit([self.mSelectedTool])
        if (self.mSelectedTool):
            self.statusInfoChanged.emit(self.mSelectedTool.statusInfo)
            self.mSelectedTool.statusInfoChanged.connect(self.statusInfoChanged)
Ejemplo n.º 33
0
	def __init__(self, supported_exts, parent=None):
		super().__init__(parent)

		self._diasshowRunning = False
		# a dummy widget to center actions
		spacer1 = QWidget()
		spacer1.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
		self.addWidget(spacer1)

		self.supportedExts = supported_exts
		self._fromFile = self.addAction(QIcon("icons/image-outline.svg"), "", self.chooseFile) # load images from file
		self._fromFile.setToolTip("Load image")
		self._fromFolder = self.addAction(QIcon("icons/folder-open.svg"), "", self.chooseFolder) # load images from folder
		self._fromFolder.setToolTip("Load from directory")

		# View in native size, fit width, fit height or fit image
		self._imageMode = QToolButton(self)
		self._imageMode.setIcon(QIcon("icons/eye-outline.svg"))
		self._imageMode.setToolTip("Image view mode")
		self._imageMode.setPopupMode(QToolButton.InstantPopup)
		self.addWidget(self._imageMode)
		
		# imageMode menu
		imageModeMenu = QMenu(self)
		imageModeActions = QActionGroup(imageModeMenu)
		imModeAct1 = imageModeActions.addAction("Native size")
		imModeAct1.setCheckable(True)
		imModeAct1.triggered.connect(lambda: self.imageModeChanged.emit(0))
		imModeAct2 = imageModeActions.addAction("Fit in view")
		imModeAct2.setCheckable(True)
		imModeAct2.triggered.connect(lambda: self.imageModeChanged.emit(1))
		imModeAct3 = imageModeActions.addAction("Fit width")
		imModeAct3.setCheckable(True)
		imModeAct3.triggered.connect(lambda: self.imageModeChanged.emit(2))
		imModeAct4 = imageModeActions.addAction("Fit height")
		imModeAct4.setCheckable(True)
		imModeAct4.triggered.connect(lambda: self.imageModeChanged.emit(3))
		imageModeActions.setExclusive(True)
		imageModeMenu.addActions(imageModeActions.actions())
		self._imageMode.setMenu(imageModeMenu)

		
		self._imgDirection = self.addAction(QIcon("icons/arrow-move-outline.svg"), "", self.imageDirectionChanged.emit) # Horizontal or Vertical
		self._imgDirection.setToolTip("Toggle image direction")

		# start or stop diasshow
		self._playDias = self.addAction(QIcon("icons/media-play-outline.svg"), "", self.diasshowState)
		self._playDias.setToolTip("Start/stop diasshow")

		#diasshow menu
		self._diasMenu = QMenu(self)
		self._diasMenu.addAction("5 seconds", lambda: self.diasshowState(5))
		self._diasMenu.addAction("10 seconds", lambda: self.diasshowState(10))
		self._diasMenu.addAction("30 seconds", lambda: self.diasshowState(30))
		self._diasMenu.addAction("5 minutes", lambda: self.diasshowState(60*5))
		self._diasMenu.addAction("10 minutes", lambda: self.diasshowState(600))
		self._playDias.setMenu(self._diasMenu)


		self._zoomIn = self.addAction(QIcon("icons/zoom-in-outline.svg"), "", lambda: self.zoomChanged.emit(True))
		self._zoomOut = self.addAction(QIcon("icons/zoom-out-outline.svg"), "", lambda: self.zoomChanged.emit(False))
		self._rotateCW = self.addAction(QIcon("icons/rotate-cw-outline.svg"), "", self.rotateChanged.emit) # Clockwise
		self._rotateCW.setToolTip("Rotate Clockwise")
		#self._rotateCCW = self.addAction("Rotate Left") # Counter clockwise

		# a dummy widget to center actions
		spacer2 = QWidget()
		spacer2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
		self.addWidget(spacer2)
Ejemplo n.º 34
0
class MainWindow(QMainWindow):
    sig_chatmessage_added = pyqtSignal(object)
    sig_avatar_loaded = pyqtSignal(str, object)

    def __init__(self, path_to_logs, trayicon):
        """ systems = list of system-objects creted by dotlan.py
        """
        QMainWindow.__init__(self)
        uic.loadUi(resource_path('vi/ui/MainWindow.ui'), self)
        self.setWindowTitle("Vintel " + VERSION)
        self.setWindowIcon(
            QtGui.QIcon(resource_path("vi/ui/res/logo_small.png")))
        self.path_to_logs = path_to_logs
        self.trayicon = trayicon
        self.trayicon.activated.connect(self.systray_activated)
        c = Cache()
        regionname = c.get_from_cache("regionname")
        if not regionname:
            regionname = "Providence"
        # is it a local map?
        svg = None
        try:
            with open(
                    resource_path("vi/ui/res/mapdata/{0}.svg".format(
                        regionname))) as svg_file:
                svg = svg_file.read()
        except Exception as e:
            pass

        try:
            self.dotlan = dotlan.Map(regionname, svg)
        except dotlan.DotlanException as e:
            QMessageBox.critical(None, "Error getting map", str(e),
                                 QMessageBox.Close)
            sys.exit(1)

        if self.dotlan.outdated_cache_error:
            e = self.dotlan.outdated_cache_error
            diatext = "I tried to get and process the data for the map "\
                "but something went wrong. To proceed I use the data I "\
                "have in my cache. This could be outdated.\nIf this problem "\
                "is permanent, there might be a change in the dotlan data "\
                "and VINTEL must be modified. Check for a newer version "\
                "and inform the maintainer.\n\nWhat went wrong: {0} {1}"\
                .format(type(e), str(e))
            QMessageBox.warning(None, "Using map from my cache", diatext,
                                QMessageBox.Ok)

        jumpbridge_url = c.get_from_cache("jumpbridge_url")
        self.set_jumpbridges(jumpbridge_url)
        self.init_map_position = None  # we read this after first rendering
        # self.systems = self.dotlan.systems
        self.chatentries = []

        self.kos_request_thread = KOSCheckerThread()
        self.kos_request_thread.kos_result.connect(self.show_kos_result)
        self.kos_request_thread.start()

        self.avatar_find_thread = AvatarFindThread()
        self.avatar_find_thread.avatar_update.connect(
            self.update_avatar_on_chatentry)
        self.avatar_find_thread.start()

        self.clipboard = QApplication.clipboard()
        self.clipboard.clear(mode=self.clipboard.Clipboard)
        self.old_clipboard_content = (0, "")
        self.clipboard.changed.connect(self.clipboard_changed)

        self.zoomin.clicked.connect(self.zoomMapIn)
        self.zoomout.clicked.connect(self.zoomMapOut)
        self.actionStatistics.clicked.connect(
            self.dotlan.change_statistics_visibility)
        self.chat_large.clicked.connect(self.chat_larger)
        self.chat_small.clicked.connect(self.chat_smaller)
        self.jumpBridgesButton.clicked.connect(self.change_jumpbridge_view)
        self.sound_button.clicked.connect(self.show_sound_setup)

        self.actionInfo.triggered.connect(self.show_info)
        self.actionShow_Chat_Avatars.triggered.connect(
            self.change_show_avatars)
        self.actionAlways_on_top.triggered.connect(self.change_always_on_top)
        self.choose_chatrooms_button.triggered.connect(
            self.show_chatroom_chooser)
        self.choose_region_button.triggered.connect(self.show_region_chooser)
        self.action_show_chat.triggered.connect(self.change_chat_visibility)
        self.actionSound_Setup.triggered.connect(self.show_sound_setup)

        self.opacity_group = QActionGroup(self.menu)
        for i in (100, 80, 60, 40, 20):
            action = QAction("Opacity {0}%".format(i), None, checkable=True)

            if i == 100:
                action.setChecked(True)

            action.opacity = i / 100.0
            action.triggered.connect(self.change_opacity)

            self.opacity_group.addAction(action)
            self.menuTransparency.addAction(action)

        # map with menu =======================================================
        self.custom_content_page = MainWindowPage()
        self.custom_content_page.sig_link_clicked.connect(
            self.map_link_clicked)
        self.map.setPage(self.custom_content_page)
        self.map.page().set_svg(self.dotlan.svg_clean)

        self.map.contextmenu = TrayContextMenu(self.trayicon)

        def map_contextmenu_event(event):
            self.map.contextmenu.exec_(
                self.mapToGlobal(QPoint(event.x(), event.y())))

        self.map.contextMenuEvent = map_contextmenu_event
        # self.map.connect(self.map, Qt.SIGNAL("linkClicked(const QUrl&)"), self.map_link_clicked)
        # self.map.page().linkClicked.connect(self.map_link_clicked)
        # http://stackoverflow.com/questions/40747827/qwebenginepage-disable-links
        # end map =============================================================

        self.filewatcher_thread = filewatcher.FileWatcher(
            self.path_to_logs, 60 * 60 * 24)
        # self.connect(self.filewatcher_thread, QtCore.SIGNAL("fchange"), self.logfile_changed)
        self.filewatcher_thread.fchange.connect(self.logfile_changed)
        self.filewatcher_thread.start()

        if False:
            self.last_statistics_update = 0
            self.maptimer = QtCore.QTimer(self)
            # self.connect(self.maptimer, QtCore.SIGNAL("timeout()"), self.update_map)
            self.maptimer.timeout.connect(self.update_map)
            self.maptimer.start(1000)

        self.evetimer = QtCore.QTimer(self)
        # self.connect(self.maptimer, QtCore.SIGNAL("timeout()"), self.update_map)
        self.evetimer.timeout.connect(self.update_evetime)
        self.evetimer.start(1000)

        self.trayicon.sig_alarm_distance.connect(self.change_alarm_distance)
        self.trayicon.sig_change_frameless.connect(self.change_frameless)

        self.frameButton.clicked.connect(self.change_frameless)
        self.frameButton.setVisible(False)

        self.btn_night_mode.clicked.connect(self.toggle_nightmode)
        self.btn_night_mode.setCheckable(True)
        # self.btn_night_mode.setChecked(True)

        self.actionFrameless_Window.triggered.connect(self.change_frameless)

        self.is_frameless = None  # we need this because 2 places to change
        self.alarm_distance = 0
        self.actionActivate_Sound.triggered.connect(self.change_sound)

        if not sound.SOUND_AVAILABLE:
            self.change_sound(disable=True)
        else:
            self.change_sound()

        self.jumpbridgedata_button.triggered.connect(
            self.show_jumbridge_chooser)

        # load something from cache =====================================
        self.known_playernames = c.get_from_cache("known_playernames")
        if self.known_playernames:
            self.known_playernames = set(self.known_playernames.split(","))
        else:
            self.known_playernames = set()
        roomnames = c.get_from_cache("roomnames")
        if roomnames:
            roomnames = roomnames.split(",")
        else:
            roomnames = ("TheCitadel", "North Provi Intel")
            c.put_into_cache("roomnames", ",".join(roomnames),
                             60 * 60 * 24 * 365 * 5)

        self.set_sound_volume(
            75)  # default - maybe overwritten by the settings

        try:
            settings = c.get_from_cache("settings")
            if settings:
                settings = pickle.loads(base64.b64decode(settings))

                for setting in settings:
                    try:
                        if not setting[0]:
                            obj = self
                        else:
                            obj = getattr(self, setting[0])

                        getattr(obj, setting[1])(setting[2])
                    except Exception as e:
                        log.error(str(e))
        except Exception as e:
            self.trayicon.showMessage(
                "Can't remember",
                "Something went wrong when I load my last state:\n{0}".format(
                    str(e)), 1)
        # load cache ends ===============================================
        self.actionQuit.triggered.connect(self.close)
        self.trayicon.sig_quit.connect(self.close)

        self.chatparser = ChatParser(self.path_to_logs, roomnames,
                                     self.dotlan.systems)

        version_check_thread = drachenjaeger.NotifyNewVersionThread()
        version_check_thread.newer_version.connect(self.notify_newer_version)
        version_check_thread.run()

    def toggle_nightmode(self, pressed):
        if pressed:
            self.set_style_dark()
        else:
            self.set_style_light()

    def set_style_dark(self):
        self.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
        self.map.page().set_style('dark')

    def set_style_light(self):
        self.setStyleSheet('')
        self.map.page().set_style('light')

    def update_evetime(self):
        self.evetime_label.setText(
            datetime.datetime.utcnow().strftime('Current EVE Time: %X'))

    def notify_newer_version(self, newest_version):
        self.trayicon.showMessage("Newer Version",
                                  ("A newer Version of VINTEL is available.\n"
                                   "Find the URL in the info!"), 1)

    def change_chat_visibility(self, new_value=None):
        if new_value is not None:
            self.action_show_chat.setChecked(new_value)
        self.chatbox.setVisible(self.action_show_chat.isChecked())

    def change_opacity(self, lvl=None):
        if isinstance(lvl, float):
            opacityVal = lvl
        elif self.sender():
            opacityVal = self.sender().opacity
        else:
            opacityVal = 1.0

        if opacityVal:
            for action in self.opacity_group.actions():
                if action.opacity == opacityVal:
                    action.setChecked(True)

        action = self.opacity_group.checkedAction()
        self.setWindowOpacity(action.opacity)

    def change_sound(self, new_value=None, disable=False):
        if disable:
            self.actionActivate_Sound.setChecked(False)
            self.actionActivate_Sound.setEnabled(False)
            self.actionSound_Setup.setEnabled(False)
            self.sound_button.setEnabled(False)
            QMessageBox.warning(
                None, "Sound disabled",
                "I can't find the lib 'pygame' which I use to play sounds, so I have to disable the soundsystem.\n"
                "If you want sound, please install the 'pygame' library.",
                QMessageBox.Ok)
        else:
            if new_value is not None:
                self.actionActivate_Sound.setChecked(new_value)
            sound.sound_active = self.actionActivate_Sound.isChecked()

    def add_message_to_intelchat(self, message):
        scroll_to_bottom = False
        if (self.chatListWidget.verticalScrollBar().value() ==
                self.chatListWidget.verticalScrollBar().maximum()):
            scroll_to_bottom = True

        entry = ChatEntry(message)
        listWidgetItem = QtWidgets.QListWidgetItem(self.chatListWidget)
        listWidgetItem.setSizeHint(entry.sizeHint())
        self.chatListWidget.addItem(listWidgetItem)
        self.chatListWidget.setItemWidget(listWidgetItem, entry)
        if ChatEntry.SHOW_AVATAR:
            # log.debug('requesting "{0}" avatar'.format(entry.message.user))
            self.avatar_find_thread.add_chatentry(entry)
        # else:
        # log.debug('requesting "{0}" avatar disabled in options'.format(entry.message.user))
        self.chatentries.append(entry)
        entry.mark_system.connect(self.mark_system_on_map)
        self.sig_chatmessage_added.emit(entry)
        if scroll_to_bottom:
            self.chatListWidget.scrollToBottom()

    def change_always_on_top(self, new_value=None):
        self.hide()
        if new_value is not None:
            self.actionAlways_on_top.setChecked(new_value)
        always_on_top = self.actionAlways_on_top.isChecked()
        if always_on_top:
            self.setWindowFlags(self.windowFlags()
                                | QtCore.Qt.WindowStaysOnTopHint)
        else:
            self.setWindowFlags(self.windowFlags()
                                & (~QtCore.Qt.WindowStaysOnTopHint))
        self.show()

    def change_frameless(self, new_value=None):
        self.hide()
        if new_value is None:
            if self.is_frameless is None:
                self.is_frameless = False
            new_value = not self.is_frameless
        if new_value:
            self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
            self.menubar.setVisible(False)
            self.frameButton.setVisible(True)
        else:
            self.setWindowFlags(self.windowFlags()
                                & (~QtCore.Qt.FramelessWindowHint))
            self.menubar.setVisible(True)
            self.frameButton.setVisible(False)
        self.change_always_on_top(new_value)
        self.is_frameless = new_value
        self.actionFrameless_Window.setChecked(new_value)
        for cm in TrayContextMenu.INSTANCES:
            cm.frameless_check.setChecked(new_value)
        self.show()

    def change_show_avatars(self, new_value=None):
        if new_value is not None:
            self.actionShow_Chat_Avatars.setChecked(new_value)
        show = self.actionShow_Chat_Avatars.isChecked()
        ChatEntry.SHOW_AVATAR = show
        for entry in self.chatentries:
            entry.avatar_label.setVisible(show)

    def chat_smaller(self):
        new_size = ChatEntry.TEXTSIZE - 1
        ChatEntry.TEXTSIZE = new_size
        for entry in self.chatentries:
            entry.change_fontsize(new_size)

    def chat_larger(self):
        new_size = ChatEntry.TEXTSIZE + 1
        ChatEntry.TEXTSIZE = new_size
        for entry in self.chatentries:
            entry.change_fontsize(new_size)

    def change_alarm_distance(self, distance):
        self.alarm_distance = distance
        for cm in TrayContextMenu.INSTANCES:
            for action in cm.distance_group.actions():
                if action.alarm_distance_value == distance:
                    action.setChecked(True)
        self.trayicon.alarm_distance_value = distance

    def change_jumpbridge_view(self):
        self.dotlan.change_jumpbrigde_visibility()
        self.update_map()

    def clipboard_changed(self, mode):
        if mode == 0 and self.action_kos_clipboard_active.isChecked():
            content = str(self.clipboard.text())
            last_modified, old_content = self.old_clipboard_content
            if content == old_content and time.time() - last_modified < 3:
                parts = content.split("\n")
                for part in parts:
                    if part in self.known_playernames:
                        self.trayicon.setIcon(
                            QtGui.QIcon(
                                resource_path(
                                    "vi/ui/res/logo_small_green.png")))
                        self.kos_request_thread.add_request(
                            parts, "clipboard", True)
                        self.old_clipboard_content = (0, "")
                        break
            else:
                self.old_clipboard_content = (time.time(), content)

    def closeEvent(self, event):
        """ writing the cache before closing the window """
        c = Cache()
        # known playernames
        if self.known_playernames:
            value = ",".join(self.known_playernames)
            c.put_into_cache("known_playernames", value, 60 * 60 * 24 * 365)
        # program state to cache (to read it on next startup)
        settings = (
            (None, "restoreGeometry", self.saveGeometry()),
            (None, "restoreState", self.saveState()),
            (None, "change_opacity",
             self.opacity_group.checkedAction().opacity),
            (None, "change_always_on_top",
             self.actionAlways_on_top.isChecked()),
            ("splitter", "restoreGeometry", self.splitter.saveGeometry()),
            ("splitter", "restoreState", self.splitter.saveState()),
            (None, "change_show_avatars",
             self.actionShow_Chat_Avatars.isChecked()),
            (None, "change_alarm_distance", self.alarm_distance),
            ("action_kos_clipboard_active", "setChecked",
             self.action_kos_clipboard_active.isChecked()),
            (None, "change_sound", self.actionActivate_Sound.isChecked()),
            (None, "change_chat_visibility",
             self.action_show_chat.isChecked()),
            ("map", "setZoomFactor", self.map.zoomFactor()),
            (None, "set_init_map_scrollposition",
             (self.map.page().scrollPosition().x(),
              self.map.page().scrollPosition().y())),
            (None, "set_sound_volume", self.sound_volume),
            (None, "change_frameless",
             self.actionFrameless_Window.isChecked()),
        )
        settings = pickle.dumps(settings, protocol=pickle.HIGHEST_PROTOCOL)
        c.put_into_cache("settings", base64.b64encode(settings),
                         60 * 60 * 24 * 365)
        event.accept()

    def map_link_clicked(self, url):
        systemname = str(url.path().split("/")[-1]).upper()
        system = self.dotlan.systems[str(systemname)]
        sc = SystemChat(self, SystemChat.SYSTEM, system, self.chatentries,
                        self.known_playernames)
        self.sig_chatmessage_added.connect(sc.add_chatentry)
        self.sig_avatar_loaded.connect(sc.new_avatar_available)
        sc.sig_location_set.connect(self.set_location)
        sc.show()

    def mark_system_on_map(self, systemname):
        self.map.page().mark_system(systemname)

        # self.dotlan.systems[str(systemname)].mark()
        # self.update_map()

    def set_location(self, char, new_system):
        self.map.page().mark_player(char, new_system)
        """
        for system in self.dotlan.systems.values():
            system.remove_located_character(char)

        if not new_system == "?" and new_system in self.dotlan.systems:
            self.dotlan.systems[new_system].add_located_character(char)
            self.set_map_content(self.dotlan.svg)
        """

    def set_init_map_scrollposition(self, xy):
        self.init_map_position = QPoint(xy[0], xy[1])

    def show_chatroom_chooser(self):
        chooser = ChatroomsChooser(self)
        chooser.rooms_changed.connect(self.changed_roomnames)
        chooser.show()

    def show_jumbridge_chooser(self):
        cache = Cache()
        url = cache.get_from_cache("jumpbridge_url")
        chooser = JumpBridgeChooser(self, url)
        chooser.set_jb_url.connect(self.set_jumpbridges)
        chooser.show()

    def set_sound_volume(self, value):
        if value < 0:
            value = 0
        elif value > 100:
            value = 100

        self.sound_volume = value
        sound.set_sound_volume(float(value) / 100.0)

    def set_jumpbridges(self, url):
        if url is None:
            url = ""
        try:
            data = []
            if url != "":
                content = requests.get(url).text
                for line in content.split("\n"):
                    parts = line.strip().split()
                    if len(parts) == 3:
                        data.append(parts)
            else:
                data = drachenjaeger.get_jumpbridge_data(
                    self.dotlan.region.lower())
            self.dotlan.set_jumpbridges(data)
            c = Cache()
            c.put_into_cache("jumpbridge_url", url, 60 * 60 * 24 * 365 * 8)
        except Exception as e:
            QMessageBox.warning(None, "Loading jumpbridges failed!",
                                "Error: {0}".format(str(e)), QMessageBox.Ok)

    def show_region_chooser(self):
        chooser = RegionChooser(self)
        chooser.show()

    def show_kos_result(self, state, text, request_type, has_kos):
        if has_kos:
            sound.play_sound("beep")

        self.trayicon.setIcon(
            QtGui.QIcon(resource_path("vi/ui/res/logo_small.png")))

        if state == "ok":
            if request_type == "xxx":  # a xxx request out of the chat
                self.trayicon.showMessage("A xxx KOS-Check", text, 1)
            elif request_type == "clipboard":  # request from clipboard-change
                if len(text) <= 0:
                    text = "Noone KOS"

                self.trayicon.showMessage("Your KOS-Check", text, 1)

            text = text.replace("\n\n", "<br>")
            message = chatparser.chatparser.Message("Vintel KOS-Check", text,
                                                    evegate.current_eve_time(),
                                                    "VINTEL", [],
                                                    states.NOT_CHANGE,
                                                    text.upper(), text)

            self.add_message_to_intelchat(message)
        elif state == "error":
            self.trayicon.showMessage("KOS Failure", text, 3)

    def changed_roomnames(self, new_roomnames):
        cache = Cache()
        cache.put_into_cache("roomnames", u",".join(new_roomnames),
                             60 * 60 * 24 * 365 * 5)
        self.chatparser.rooms = new_roomnames

    def show_info(self):
        info_dialog = QDialog(self)
        uic.loadUi(resource_path("vi/ui/Info.ui"), info_dialog)
        info_dialog.version_label.setText(u"Version: {0}".format(VERSION))
        info_dialog.logo_label.setPixmap(
            QtGui.QPixmap(resource_path("vi/ui/res/logo.png")))
        info_dialog.close_button.clicked.connect(info_dialog.accept)
        info_dialog.show()

    def show_sound_setup(self):
        dialog = QDialog(self)
        uic.loadUi(resource_path("vi/ui/SoundSetup.ui"), dialog)
        dialog.volumeSlider.setValue(self.sound_volume)
        dialog.volumeSlider.valueChanged.connect(self.set_sound_volume)
        dialog.testsound_button.clicked.connect(sound.play_sound)
        dialog.close_button.clicked.connect(dialog.accept)
        dialog.show()

    def systray_activated(self, reason):
        if reason == QtWidgets.QSystemTrayIcon.Trigger:
            if self.isMinimized():
                self.showNormal()
                self.activateWindow()
            elif not self.isActiveWindow():
                self.activateWindow()
            else:
                self.showMinimized()

    def update_avatar_on_chatentry(self, chatentry, avatar_data):
        updated = chatentry.update_avatar(avatar_data)
        if not updated:
            self.avatar_find_thread.add_chatentry(chatentry, clear_cache=True)
        else:
            self.avatar_loaded.emit(chatentry.message.user, avatar_data)

    """
    def update_map(self):
        # log.debug('check')

        def update_statistics_on_map(data):
            if data["result"] == "ok":
                self.dotlan.add_system_statistics(data["statistics"])
            elif data["result"] == "error":
                text = data["text"]
                self.trayicon.showMessage("Loading statstics failed", text, 3)

        if self.last_statistics_update < (time.time() - 5 * 60):
            self.last_statistics_update = time.time()
            statistic_thread = MapStatisticsThread()
            # self.connect(statistic_thread, Qt.SIGNAL("statistic_data_update"), update_statistics_on_map)
            statistic_thread.statistic_data_update.connect(update_statistics_on_map)
            statistic_thread.start()

        self.set_map_content(self.dotlan.svg)

    def set_map_content(self, content):
        if self.init_map_position is None:
            scrollposition = self.map.page().scrollPosition()
            # scrollposition = self.map.scrollPosition()
        else:
            scrollposition = self.init_map_position
            self.init_map_position = None

        if True:
            # array = QByteArray().append(content)
            # self.map.page().setContent(array, mimeType=str('image/svg+xml'))

            if not self.set_map_content_done:
                self.map.page().set_svg(content)
                self.set_map_content_done = True
        else:
            # self.map.setContent(content)
            self.map.page().setHtml(content)

            if isinstance(scrollposition, QtCore.QPointF):
                script_str = "window.scrollTo({0}, {1});".format(
                    scrollposition.toPoint().x(),
                    scrollposition.toPoint().y()
                )
            elif isinstance(scrollposition, QtCore.QPoint):
                script_str = "window.scrollTo({0}, {1});".format(scrollposition.x(), scrollposition.y())
            else:
                script_str = None
                log.warning('unknown object type {0}'.format(type(scrollposition)))
            # script_str = "window.scrollTop(340);"

            # log.debug('current scroll is {0}x{1}'.format(scrollposition.toPoint().x(), scrollposition.toPoint().y()))
            # log.debug(script_str)

            # self.map.page().setScrollPosition(scrollposition)
            # self.map.page().scrollPosition = scrollposition;

            self.map.page().runJavaScript(script_str)
        # time.sleep(1)
        # self.map.page().setLinkDelegationPolicy(QWebEnginePage.DelegateAllLinks)
    """

    def zoomMapIn(self):
        self.map.setZoomFactor(self.map.zoomFactor() + 0.1)

    def zoomMapOut(self):
        self.map.setZoomFactor(self.map.zoomFactor() - 0.1)

    def logfile_changed(self, path, roomname):
        messages = self.chatparser.file_modified(path, roomname)
        for message in messages:
            # if players location changed
            if message.status == states.LOCATION:
                self.known_playernames.add(message.user)
                self.set_location(message.user, message.systems[0])
            # soundtest special
            elif message.status == states.SOUNDTEST and message.user in self.known_playernames:
                words = message.message.split()
                if len(words) > 1:
                    sound.play_sound(words[1])
            # KOS request
            elif message.status == states.KOS_STATUS_REQUEST:
                text = message.message[4:]
                text = text.replace("  ", ",")
                parts = (name.strip() for name in text.split(","))
                self.trayicon.setIcon(
                    QtGui.QIcon(
                        resource_path("vi/ui/res/logo_small_green.png")))
                self.kos_request_thread.add_request(parts, "xxx", False)
            # if it is a 'normal' chat message
            elif message.user not in ("EVE-System", "EVE System"
                                      ) and message.status != states.IGNORE:
                self.add_message_to_intelchat(message)

                if message.systems:
                    for system in message.systems:
                        systemname = system.name
                        # self.dotlan.set_system_status(systemname, message.status)
                        self.map.page().set_system_status(system, message)

                        if message.status in (states.REQUEST, states.ALARM) \
                                and message.user not in self.known_playernames:
                            if message.status == states.ALARM:
                                alarm_distance = self.alarm_distance
                            else:
                                alarm_distance = 0

                            for nsystem, data in system.get_neighbours(
                                    alarm_distance).items():
                                distance = data["distance"]
                                chars = nsystem.get_located_characters()
                                if len(chars
                                       ) > 0 and message.user not in chars:
                                    self.trayicon.show_notification(
                                        message, system.name, ", ".join(chars),
                                        distance)
Ejemplo n.º 35
0
class MainWindow(base_class, ui_class):

    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.saved_account_state = None

        notification_center = NotificationCenter()
        notification_center.add_observer(self, name='SIPApplicationWillStart')
        notification_center.add_observer(self, name='SIPApplicationDidStart')
        notification_center.add_observer(self, name='SIPAccountGotMessageSummary')
        notification_center.add_observer(self, name='SIPAccountGotPendingWatcher')
        notification_center.add_observer(self, name='BlinkSessionNewOutgoing')
        notification_center.add_observer(self, name='BlinkSessionDidReinitializeForOutgoing')
        notification_center.add_observer(self, name='BlinkSessionTransferNewOutgoing')
        notification_center.add_observer(self, name='BlinkFileTransferNewIncoming')
        notification_center.add_observer(self, name='BlinkFileTransferNewOutgoing')
        notification_center.add_observer(self, sender=AccountManager())

        icon_manager = IconManager()

        self.pending_watcher_dialogs = []

        self.mwi_icons = [QIcon(Resources.get('icons/mwi-%d.png' % i)) for i in range(0, 11)]
        self.mwi_icons.append(QIcon(Resources.get('icons/mwi-many.png')))

        with Resources.directory:
            self.setupUi()

        self.setWindowTitle('Blink')

        geometry = QSettings().value("main_window/geometry")
        if geometry:
            self.restoreGeometry(geometry)

        self.default_icon_path = Resources.get('icons/default-avatar.png')
        self.default_icon = QIcon(self.default_icon_path)
        self.last_icon_directory = Path('~').normalized
        self.set_user_icon(icon_manager.get('avatar'))

        self.active_sessions_label.hide()
        self.enable_call_buttons(False)
        self.conference_button.setEnabled(False)
        self.hangup_all_button.setEnabled(False)
        self.sip_server_settings_action.setEnabled(False)
        self.search_for_people_action.setEnabled(False)
        self.history_on_server_action.setEnabled(False)
        self.main_view.setCurrentWidget(self.contacts_panel)
        self.contacts_view.setCurrentWidget(self.contact_list_panel)
        self.search_view.setCurrentWidget(self.search_list_panel)

        # System tray
        if QSystemTrayIcon.isSystemTrayAvailable():
            self.system_tray_icon = QSystemTrayIcon(QIcon(Resources.get('icons/blink.png')), self)
            self.system_tray_icon.activated.connect(self._SH_SystemTrayIconActivated)
            menu = QMenu(self)
            menu.addAction("Show", self._AH_SystemTrayShowWindow)
            menu.addAction(QIcon(Resources.get('icons/application-exit.png')), "Quit", self._AH_QuitActionTriggered)
            self.system_tray_icon.setContextMenu(menu)
            self.system_tray_icon.show()
        else:
            self.system_tray_icon = None

        # Accounts
        self.account_model = AccountModel(self)
        self.enabled_account_model = ActiveAccountModel(self.account_model, self)
        self.server_tools_account_model = ServerToolsAccountModel(self.account_model, self)
        self.identity.setModel(self.enabled_account_model)

        # Contacts
        self.contact_model = ContactModel(self)
        self.contact_search_model = ContactSearchModel(self.contact_model, self)
        self.contact_list.setModel(self.contact_model)
        self.search_list.setModel(self.contact_search_model)

        # Sessions (audio)
        self.session_model = AudioSessionModel(self)
        self.session_list.setModel(self.session_model)
        self.session_list.selectionModel().selectionChanged.connect(self._SH_SessionListSelectionChanged)

        # History
        self.history_manager = HistoryManager()

        # Windows, dialogs and panels
        self.about_panel = AboutPanel(self)
        self.conference_dialog = ConferenceDialog(self)
        self.contact_editor_dialog = ContactEditorDialog(self)
        self.filetransfer_window = FileTransferWindow()
        self.preferences_window = PreferencesWindow(self.account_model, None)
        self.server_tools_window = ServerToolsWindow(self.server_tools_account_model, None)

        # Signals
        self.account_state.stateChanged.connect(self._SH_AccountStateChanged)
        self.account_state.clicked.connect(self._SH_AccountStateClicked)
        self.activity_note.editingFinished.connect(self._SH_ActivityNoteEditingFinished)
        self.add_contact_button.clicked.connect(self._SH_AddContactButtonClicked)
        self.add_search_contact_button.clicked.connect(self._SH_AddContactButtonClicked)
        self.audio_call_button.clicked.connect(self._SH_AudioCallButtonClicked)
        self.video_call_button.clicked.connect(self._SH_VideoCallButtonClicked)
        self.chat_session_button.clicked.connect(self._SH_ChatSessionButtonClicked)
        self.back_to_contacts_button.clicked.connect(self.search_box.clear)  # this can be set in designer -Dan
        self.conference_button.makeConference.connect(self._SH_MakeConference)
        self.conference_button.breakConference.connect(self._SH_BreakConference)

        self.contact_list.selectionModel().selectionChanged.connect(self._SH_ContactListSelectionChanged)
        self.contact_model.itemsAdded.connect(self._SH_ContactModelAddedItems)
        self.contact_model.itemsRemoved.connect(self._SH_ContactModelRemovedItems)

        self.display_name.editingFinished.connect(self._SH_DisplayNameEditingFinished)
        self.hangup_all_button.clicked.connect(self._SH_HangupAllButtonClicked)

        self.identity.activated[int].connect(self._SH_IdentityChanged)
        self.identity.currentIndexChanged[int].connect(self._SH_IdentityCurrentIndexChanged)

        self.mute_button.clicked.connect(self._SH_MuteButtonClicked)

        self.search_box.textChanged.connect(self._SH_SearchBoxTextChanged)
        self.search_box.returnPressed.connect(self._SH_SearchBoxReturnPressed)
        self.search_box.shortcut.activated.connect(self.search_box.setFocus)

        self.search_list.selectionModel().selectionChanged.connect(self._SH_SearchListSelectionChanged)

        self.server_tools_account_model.rowsInserted.connect(self._SH_ServerToolsAccountModelChanged)
        self.server_tools_account_model.rowsRemoved.connect(self._SH_ServerToolsAccountModelChanged)

        self.session_model.sessionAdded.connect(self._SH_AudioSessionModelAddedSession)
        self.session_model.sessionRemoved.connect(self._SH_AudioSessionModelRemovedSession)
        self.session_model.structureChanged.connect(self._SH_AudioSessionModelChangedStructure)

        self.silent_button.clicked.connect(self._SH_SilentButtonClicked)
        self.switch_view_button.viewChanged.connect(self._SH_SwitchViewButtonChangedView)

        # Blink menu actions
        self.about_action.triggered.connect(self.about_panel.show)
        self.add_account_action.triggered.connect(self.preferences_window.show_add_account_dialog)
        self.manage_accounts_action.triggered.connect(self.preferences_window.show_for_accounts)
        self.help_action.triggered.connect(partial(QDesktopServices.openUrl, QUrl('http://icanblink.com/help/')))
        self.preferences_action.triggered.connect(self.preferences_window.show)
        self.auto_accept_chat_action.triggered.connect(self._AH_AutoAcceptChatActionTriggered)
        self.received_messages_sound_action.triggered.connect(self._AH_ReceivedMessagesSoundActionTriggered)
        self.answering_machine_action.triggered.connect(self._AH_EnableAnsweringMachineActionTriggered)
        self.release_notes_action.triggered.connect(partial(QDesktopServices.openUrl, QUrl('http://icanblink.com/changelog/')))
        self.quit_action.triggered.connect(self._AH_QuitActionTriggered)

        # Call menu actions
        self.redial_action.triggered.connect(self._AH_RedialActionTriggered)
        self.join_conference_action.triggered.connect(self.conference_dialog.show)
        self.history_menu.aboutToShow.connect(self._SH_HistoryMenuAboutToShow)
        self.history_menu.triggered.connect(self._AH_HistoryMenuTriggered)
        self.output_devices_group.triggered.connect(self._AH_AudioOutputDeviceChanged)
        self.input_devices_group.triggered.connect(self._AH_AudioInputDeviceChanged)
        self.alert_devices_group.triggered.connect(self._AH_AudioAlertDeviceChanged)
        self.video_devices_group.triggered.connect(self._AH_VideoDeviceChanged)
        self.mute_action.triggered.connect(self._SH_MuteButtonClicked)
        self.silent_action.triggered.connect(self._SH_SilentButtonClicked)

        # Tools menu actions
        self.sip_server_settings_action.triggered.connect(self._AH_SIPServerSettings)
        self.search_for_people_action.triggered.connect(self._AH_SearchForPeople)
        self.history_on_server_action.triggered.connect(self._AH_HistoryOnServer)
        self.google_contacts_action.triggered.connect(self._AH_GoogleContactsActionTriggered)

        # Window menu actions
        self.chat_window_action.triggered.connect(self._AH_ChatWindowActionTriggered)
        self.transfers_window_action.triggered.connect(self._AH_TransfersWindowActionTriggered)
        self.logs_window_action.triggered.connect(self._AH_LogsWindowActionTriggered)
        self.received_files_window_action.triggered.connect(self._AH_ReceivedFilesWindowActionTriggered)
        self.screenshots_window_action.triggered.connect(self._AH_ScreenshotsWindowActionTriggered)

    def setupUi(self):
        super(MainWindow, self).setupUi(self)

        self.search_box.shortcut = QShortcut(self.search_box)
        self.search_box.shortcut.setKey('Ctrl+F')

        self.output_devices_group = QActionGroup(self)
        self.input_devices_group = QActionGroup(self)
        self.alert_devices_group = QActionGroup(self)
        self.video_devices_group = QActionGroup(self)

        self.screen_sharing_button.addAction(QAction('Request screen', self.screen_sharing_button, triggered=self._AH_RequestScreenActionTriggered))
        self.screen_sharing_button.addAction(QAction('Share my screen', self.screen_sharing_button, triggered=self._AH_ShareMyScreenActionTriggered))

        # adjust search box height depending on theme as the value set in designer isn't suited for all themes
        search_box = self.search_box
        option = QStyleOptionFrame()
        search_box.initStyleOption(option)
        frame_width = search_box.style().pixelMetric(QStyle.PM_DefaultFrameWidth, option, search_box)
        if frame_width < 4:
            search_box.setMinimumHeight(20 + 2*frame_width)

        # adjust the combo boxes for themes with too much padding (like the default theme on Ubuntu 10.04)
        option = QStyleOptionComboBox()
        self.identity.initStyleOption(option)
        wide_padding = self.identity.style().subControlRect(QStyle.CC_ComboBox, option, QStyle.SC_ComboBoxEditField, self.identity).height() < 10
        self.identity.setStyleSheet("""QComboBox { padding: 0px 4px 0px 4px; }""" if wide_padding else "")

    def closeEvent(self, event):
        QSettings().setValue("main_window/geometry", self.saveGeometry())
        super(MainWindow, self).closeEvent(event)
        self.about_panel.close()
        self.contact_editor_dialog.close()
        self.server_tools_window.close()
        for dialog in self.pending_watcher_dialogs[:]:
            dialog.close()

    def show(self):
        super(MainWindow, self).show()
        self.raise_()
        self.activateWindow()

    def set_user_icon(self, icon):
        self.account_state.setIcon(icon or self.default_icon)

    def enable_call_buttons(self, enabled):  # todo: review this
        self.audio_call_button.setEnabled(enabled)
        self.video_call_button.setEnabled(enabled)
        self.chat_session_button.setEnabled(enabled)
        self.screen_sharing_button.setEnabled(enabled)

    def load_audio_devices(self):
        settings = SIPSimpleSettings()

        action_map = {}

        action = action_map['system_default'] = self.output_device_menu.addAction('System default')
        action.setData('system_default')
        action.setCheckable(True)
        self.output_devices_group.addAction(action)

        self.output_device_menu.addSeparator()

        for device in SIPApplication.engine.output_devices:
            action = action_map[device] = self.output_device_menu.addAction(device)
            action.setData(device)
            action.setCheckable(True)
            self.output_devices_group.addAction(action)

        action = action_map[None] = self.output_device_menu.addAction('None')
        action.setData(None)
        action.setCheckable(True)
        self.output_devices_group.addAction(action)

        active_action = action_map.get(settings.audio.output_device, Null)
        active_action.setChecked(True)

        action_map = {}

        action = action_map['system_default'] = self.input_device_menu.addAction('System default')
        action.setData('system_default')
        action.setCheckable(True)
        self.input_devices_group.addAction(action)

        self.input_device_menu.addSeparator()

        for device in SIPApplication.engine.input_devices:
            action = action_map[device] = self.input_device_menu.addAction(device)
            action.setData(device)
            action.setCheckable(True)
            self.input_devices_group.addAction(action)

        action = action_map[None] = self.input_device_menu.addAction('None')
        action.setData(None)
        action.setCheckable(True)
        self.input_devices_group.addAction(action)

        active_action = action_map.get(settings.audio.input_device, Null)
        active_action.setChecked(True)

        action_map = {}

        action = action_map['system_default'] = self.alert_device_menu.addAction('System default')
        action.setData('system_default')
        action.setCheckable(True)
        self.alert_devices_group.addAction(action)

        self.alert_device_menu.addSeparator()

        for device in SIPApplication.engine.output_devices:
            action = action_map[device] = self.alert_device_menu.addAction(device)
            action.setData(device)
            action.setCheckable(True)
            self.alert_devices_group.addAction(action)

        action = action_map[None] = self.alert_device_menu.addAction('None')
        action.setData(None)
        action.setCheckable(True)
        self.alert_devices_group.addAction(action)

        active_action = action_map.get(settings.audio.alert_device, Null)
        active_action.setChecked(True)

    def load_video_devices(self):
        settings = SIPSimpleSettings()

        action_map = {}

        action = action_map['system_default'] = self.video_camera_menu.addAction('System default')
        action.setData('system_default')
        action.setCheckable(True)
        self.video_devices_group.addAction(action)

        self.video_camera_menu.addSeparator()

        for device in SIPApplication.engine.video_devices:
            action = action_map[device] = self.video_camera_menu.addAction(device)
            action.setData(device)
            action.setCheckable(True)
            self.video_devices_group.addAction(action)

        action = action_map[None] = self.video_camera_menu.addAction('None')
        action.setData(None)
        action.setCheckable(True)
        self.video_devices_group.addAction(action)

        active_action = action_map.get(settings.video.device, Null)
        active_action.setChecked(True)

    def _AH_AccountActionTriggered(self, enabled):
        account = self.sender().data()
        account.enabled = enabled
        account.save()

    def _AH_AudioAlertDeviceChanged(self, action):
        settings = SIPSimpleSettings()
        settings.audio.alert_device = action.data()
        settings.save()

    def _AH_AudioInputDeviceChanged(self, action):
        settings = SIPSimpleSettings()
        settings.audio.input_device = action.data()
        settings.save()

    def _AH_AudioOutputDeviceChanged(self, action):
        settings = SIPSimpleSettings()
        settings.audio.output_device = action.data()
        settings.save()

    def _AH_VideoDeviceChanged(self, action):
        settings = SIPSimpleSettings()
        settings.video.device = action.data()
        settings.save()

    def _AH_AutoAcceptChatActionTriggered(self, checked):
        settings = SIPSimpleSettings()
        settings.chat.auto_accept = checked
        settings.save()

    def _AH_ReceivedMessagesSoundActionTriggered(self, checked):
        settings = SIPSimpleSettings()
        settings.sounds.play_message_alerts = checked
        settings.save()

    def _AH_EnableAnsweringMachineActionTriggered(self, checked):
        settings = SIPSimpleSettings()
        settings.answering_machine.enabled = checked
        settings.save()

    def _AH_GoogleContactsActionTriggered(self):
        settings = SIPSimpleSettings()
        settings.google_contacts.enabled = not settings.google_contacts.enabled
        settings.save()

    def _AH_RedialActionTriggered(self):
        session_manager = SessionManager()
        if session_manager.last_dialed_uri is not None:
            contact, contact_uri = URIUtils.find_contact(session_manager.last_dialed_uri)
            session_manager.create_session(contact, contact_uri, [StreamDescription('audio')])  # TODO: remember used media types and redial with them. -Saul

    def _AH_SIPServerSettings(self, checked):
        account = self.identity.itemData(self.identity.currentIndex()).account
        account = account if account is not BonjourAccount() and account.server.settings_url else None
        self.server_tools_window.open_settings_page(account)

    def _AH_SearchForPeople(self, checked):
        account = self.identity.itemData(self.identity.currentIndex()).account
        account = account if account is not BonjourAccount() and account.server.settings_url else None
        self.server_tools_window.open_search_for_people_page(account)

    def _AH_HistoryOnServer(self, checked):
        account = self.identity.itemData(self.identity.currentIndex()).account
        account = account if account is not BonjourAccount() and account.server.settings_url else None
        self.server_tools_window.open_history_page(account)

    def _AH_ChatWindowActionTriggered(self, checked):
        blink = QApplication.instance()
        blink.chat_window.show()

    def _AH_TransfersWindowActionTriggered(self, checked):
        self.filetransfer_window.show()

    def _AH_LogsWindowActionTriggered(self, checked):
        directory = ApplicationData.get('logs')
        makedirs(directory)
        QDesktopServices.openUrl(QUrl.fromLocalFile(directory))

    def _AH_ReceivedFilesWindowActionTriggered(self, checked):
        settings = BlinkSettings()
        directory = settings.transfers_directory.normalized
        makedirs(directory)
        QDesktopServices.openUrl(QUrl.fromLocalFile(directory))

    def _AH_ScreenshotsWindowActionTriggered(self, checked):
        settings = BlinkSettings()
        directory = settings.screenshots_directory.normalized
        makedirs(directory)
        QDesktopServices.openUrl(QUrl.fromLocalFile(directory))

    def _AH_VoicemailActionTriggered(self, checked):
        account = self.sender().data()
        contact, contact_uri = URIUtils.find_contact(account.voicemail_uri, display_name='Voicemail')
        session_manager = SessionManager()
        session_manager.create_session(contact, contact_uri, [StreamDescription('audio')], account=account)

    def _SH_HistoryMenuAboutToShow(self):
        self.history_menu.clear()
        if self.history_manager.calls:
            for entry in reversed(self.history_manager.calls):
                action = self.history_menu.addAction(entry.icon, entry.text)
                action.entry = entry
                action.setToolTip(entry.uri)
        else:
            action = self.history_menu.addAction("Call history is empty")
            action.setEnabled(False)

    def _AH_HistoryMenuTriggered(self, action):
        account_manager = AccountManager()
        session_manager = SessionManager()
        try:
            account = account_manager.get_account(action.entry.account_id)
        except KeyError:
            account = None
        contact, contact_uri = URIUtils.find_contact(action.entry.uri)
        session_manager.create_session(contact, contact_uri, [StreamDescription('audio')], account=account)  # TODO: memorize media type and use it? -Saul (not sure about history in/out -Dan)

    def _AH_SystemTrayShowWindow(self):
        self.show()
        self.raise_()
        self.activateWindow()

    def _AH_QuitActionTriggered(self):
        if self.system_tray_icon is not None:
            self.system_tray_icon.hide()
        QApplication.instance().quit()

    def _SH_AccountStateChanged(self):
        self.activity_note.setText(self.account_state.note)
        if self.account_state.state is AccountState.Invisible:
            self.activity_note.inactiveText = '(invisible)'
            self.activity_note.setEnabled(False)
        else:
            if not self.activity_note.isEnabled():
                self.activity_note.inactiveText = 'Add an activity note here'
                self.activity_note.setEnabled(True)
        if not self.account_state.state.internal:
            self.saved_account_state = None
        blink_settings = BlinkSettings()
        blink_settings.presence.current_state = PresenceState(self.account_state.state, self.account_state.note)
        blink_settings.presence.state_history = [PresenceState(state, note) for state, note in self.account_state.history]
        blink_settings.save()

    def _SH_AccountStateClicked(self, checked):
        filename = QFileDialog.getOpenFileName(self, 'Select Icon', self.last_icon_directory, "Images (*.png *.tiff *.jpg *.xmp *.svg)")[0]
        if filename:
            self.last_icon_directory = os.path.dirname(filename)
            filename = filename if os.path.realpath(filename) != os.path.realpath(self.default_icon_path) else None
            blink_settings = BlinkSettings()
            icon_manager = IconManager()
            if filename is not None:
                icon = icon_manager.store_file('avatar', filename)
                if icon is not None:
                    blink_settings.presence.icon = IconDescriptor(FileURL(icon.filename), hashlib.sha1(icon.content).hexdigest())
                else:
                    icon_manager.remove('avatar')
                    blink_settings.presence.icon = None
            else:
                icon_manager.remove('avatar')
                blink_settings.presence.icon = None
            blink_settings.save()

    def _SH_ActivityNoteEditingFinished(self):
        self.activity_note.clearFocus()
        note = self.activity_note.text()
        if note != self.account_state.note:
            self.account_state.state.internal = False
            self.account_state.setState(self.account_state.state, note)

    def _SH_AddContactButtonClicked(self, clicked):
        self.contact_editor_dialog.open_for_add(self.search_box.text(), None)

    def _SH_AudioCallButtonClicked(self):
        list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list
        if list_view.detail_view.isVisible():
            list_view.detail_view._AH_StartAudioCall()
        else:
            selected_indexes = list_view.selectionModel().selectedIndexes()
            if selected_indexes:
                contact = selected_indexes[0].data(Qt.UserRole)
                contact_uri = contact.uri
            else:
                contact, contact_uri = URIUtils.find_contact(self.search_box.text())
            session_manager = SessionManager()
            session_manager.create_session(contact, contact_uri, [StreamDescription('audio')])

    def _SH_VideoCallButtonClicked(self):
        list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list
        if list_view.detail_view.isVisible():
            list_view.detail_view._AH_StartVideoCall()
        else:
            selected_indexes = list_view.selectionModel().selectedIndexes()
            if selected_indexes:
                contact = selected_indexes[0].data(Qt.UserRole)
                contact_uri = contact.uri
            else:
                contact, contact_uri = URIUtils.find_contact(self.search_box.text())
            session_manager = SessionManager()
            session_manager.create_session(contact, contact_uri, [StreamDescription('audio'), StreamDescription('video')])

    def _SH_ChatSessionButtonClicked(self):
        list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list
        if list_view.detail_view.isVisible():
            list_view.detail_view._AH_StartChatSession()
        else:
            selected_indexes = list_view.selectionModel().selectedIndexes()
            if selected_indexes:
                contact = selected_indexes[0].data(Qt.UserRole)
                contact_uri = contact.uri
            else:
                contact, contact_uri = URIUtils.find_contact(self.search_box.text())
            session_manager = SessionManager()
            session_manager.create_session(contact, contact_uri, [StreamDescription('chat')], connect=False)

    def _AH_RequestScreenActionTriggered(self):
        list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list
        if list_view.detail_view.isVisible():
            list_view.detail_view._AH_RequestScreen()
        else:
            selected_indexes = list_view.selectionModel().selectedIndexes()
            if selected_indexes:
                contact = selected_indexes[0].data(Qt.UserRole)
                contact_uri = contact.uri
            else:
                contact, contact_uri = URIUtils.find_contact(self.search_box.text())
            session_manager = SessionManager()
            session_manager.create_session(contact, contact_uri, [StreamDescription('screen-sharing', mode='viewer'), StreamDescription('audio')])

    def _AH_ShareMyScreenActionTriggered(self):
        list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list
        if list_view.detail_view.isVisible():
            list_view.detail_view._AH_ShareMyScreen()
        else:
            selected_indexes = list_view.selectionModel().selectedIndexes()
            if selected_indexes:
                contact = selected_indexes[0].data(Qt.UserRole)
                contact_uri = contact.uri
            else:
                contact, contact_uri = URIUtils.find_contact(self.search_box.text())
            session_manager = SessionManager()
            session_manager.create_session(contact, contact_uri, [StreamDescription('screen-sharing', mode='server'), StreamDescription('audio')])

    def _SH_BreakConference(self):
        active_session = self.session_list.selectionModel().selectedIndexes()[0].data(Qt.UserRole)
        self.session_model.breakConference(active_session.client_conference)

    def _SH_ContactListSelectionChanged(self, selected, deselected):
        account_manager = AccountManager()
        selected_items = self.contact_list.selectionModel().selectedIndexes()
        self.enable_call_buttons(account_manager.default_account is not None and len(selected_items) == 1 and isinstance(selected_items[0].data(Qt.UserRole), Contact))

    def _SH_ContactModelAddedItems(self, items):
        if not self.search_box.text():
            return
        active_widget = self.search_list_panel if self.contact_search_model.rowCount() else self.not_found_panel
        self.search_view.setCurrentWidget(active_widget)

    def _SH_ContactModelRemovedItems(self, items):
        if not self.search_box.text():
            return
        if any(type(item) is Contact for item in items) and self.contact_search_model.rowCount() == 0:
            self.search_box.clear()  # check this. it is no longer be the correct behaviour as now contacts can be deleted from remote -Dan
        else:
            active_widget = self.search_list_panel if self.contact_search_model.rowCount() else self.not_found_panel
            self.search_view.setCurrentWidget(active_widget)

    def _SH_DisplayNameEditingFinished(self):
        self.display_name.clearFocus()
        index = self.identity.currentIndex()
        if index != -1:
            name = self.display_name.text()
            account = self.identity.itemData(index).account
            account.display_name = name if name else None
            account.save()

    def _SH_HangupAllButtonClicked(self):
        for session in self.session_model.sessions:
            session.end()

    def _SH_IdentityChanged(self, index):
        account_manager = AccountManager()
        account_manager.default_account = self.identity.itemData(index).account

    def _SH_IdentityCurrentIndexChanged(self, index):
        if index != -1:
            account = self.identity.itemData(index).account
            self.display_name.setText(account.display_name or '')
            self.display_name.setEnabled(True)
            self.activity_note.setEnabled(True)
            self.account_state.setEnabled(True)
        else:
            self.display_name.clear()
            self.display_name.setEnabled(False)
            self.activity_note.setEnabled(False)
            self.account_state.setEnabled(False)
            self.account_state.setState(AccountState.Invisible)
            self.saved_account_state = None

    def _SH_MakeConference(self):
        self.session_model.conferenceSessions([session for session in self.session_model.active_sessions if session.client_conference is None])

    def _SH_MuteButtonClicked(self, muted):
        settings = SIPSimpleSettings()
        settings.audio.muted = muted
        settings.save()

    def _SH_SearchBoxReturnPressed(self):
        address = self.search_box.text()
        if address:
            contact, contact_uri = URIUtils.find_contact(address)
            session_manager = SessionManager()
            session_manager.create_session(contact, contact_uri, [StreamDescription('audio')])

    def _SH_SearchBoxTextChanged(self, text):
        self.contact_search_model.setFilterFixedString(text)
        account_manager = AccountManager()
        if text:
            self.switch_view_button.view = SwitchViewButton.ContactView
            if self.contacts_view.currentWidget() is not self.search_panel:
                self.search_list.selectionModel().clearSelection()
            self.contacts_view.setCurrentWidget(self.search_panel)
            self.search_view.setCurrentWidget(self.search_list_panel if self.contact_search_model.rowCount() else self.not_found_panel)
            selected_items = self.search_list.selectionModel().selectedIndexes()
            self.enable_call_buttons(account_manager.default_account is not None and len(selected_items)<=1)
        else:
            self.contacts_view.setCurrentWidget(self.contact_list_panel)
            selected_items = self.contact_list.selectionModel().selectedIndexes()
            self.enable_call_buttons(account_manager.default_account is not None and len(selected_items) == 1 and type(selected_items[0].data(Qt.UserRole)) is Contact)
        self.search_list.detail_model.contact = None
        self.search_list.detail_view.hide()

    def _SH_SearchListSelectionChanged(self, selected, deselected):
        account_manager = AccountManager()
        selected_items = self.search_list.selectionModel().selectedIndexes()
        self.enable_call_buttons(account_manager.default_account is not None and len(selected_items)<=1)

    def _SH_ServerToolsAccountModelChanged(self, parent_index, start, end):
        server_tools_enabled = self.server_tools_account_model.rowCount() > 0
        self.sip_server_settings_action.setEnabled(server_tools_enabled)
        self.search_for_people_action.setEnabled(server_tools_enabled)
        self.history_on_server_action.setEnabled(server_tools_enabled)

    def _SH_SessionListSelectionChanged(self, selected, deselected):
        selected_indexes = selected.indexes()
        active_session = selected_indexes[0].data(Qt.UserRole) if selected_indexes else Null
        if active_session.client_conference:
            self.conference_button.setEnabled(True)
            self.conference_button.setChecked(True)
        else:
            self.conference_button.setEnabled(len([session for session in self.session_model.active_sessions if session.client_conference is None]) > 1)
            self.conference_button.setChecked(False)

    def _SH_AudioSessionModelAddedSession(self, session_item):
        if len(session_item.blink_session.streams) == 1:
            self.switch_view_button.view = SwitchViewButton.SessionView

    def _SH_AudioSessionModelRemovedSession(self, session_item):
        if self.session_model.rowCount() == 0:
            self.switch_view_button.view = SwitchViewButton.ContactView

    def _SH_AudioSessionModelChangedStructure(self):
        active_sessions = self.session_model.active_sessions
        self.active_sessions_label.setText('There is 1 active call' if len(active_sessions) == 1 else 'There are %d active calls' % len(active_sessions))
        self.active_sessions_label.setVisible(any(active_sessions))
        self.hangup_all_button.setEnabled(any(active_sessions))
        selected_indexes = self.session_list.selectionModel().selectedIndexes()
        active_session = selected_indexes[0].data(Qt.UserRole) if selected_indexes else Null
        if active_session.client_conference:
            self.conference_button.setEnabled(True)
            self.conference_button.setChecked(True)
        else:
            self.conference_button.setEnabled(len([session for session in active_sessions if session.client_conference is None]) > 1)
            self.conference_button.setChecked(False)
        if active_sessions:
            if self.account_state.state is not AccountState.Invisible:
                if self.saved_account_state is None:
                    self.saved_account_state = self.account_state.state, self.activity_note.text()
                self.account_state.setState(AccountState.Busy.Internal, note='On the phone')
        elif self.saved_account_state is not None:
            state, note = self.saved_account_state
            self.saved_account_state = None
            self.account_state.setState(state, note)

    def _SH_SilentButtonClicked(self, silent):
        settings = SIPSimpleSettings()
        settings.audio.silent = silent
        settings.save()

    def _SH_SwitchViewButtonChangedView(self, view):
        self.main_view.setCurrentWidget(self.contacts_panel if view is SwitchViewButton.ContactView else self.sessions_panel)

    def _SH_PendingWatcherDialogFinished(self, result):
        self.pending_watcher_dialogs.remove(self.sender())

    def _SH_SystemTrayIconActivated(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            self.show()
            self.raise_()
            self.activateWindow()

    @run_in_gui_thread
    def handle_notification(self, notification):
        handler = getattr(self, '_NH_%s' % notification.name, Null)
        handler(notification)

    def _NH_SIPApplicationWillStart(self, notification):
        account_manager = AccountManager()
        settings = SIPSimpleSettings()
        self.silent_action.setChecked(settings.audio.silent)
        self.silent_button.setChecked(settings.audio.silent)
        self.answering_machine_action.setChecked(settings.answering_machine.enabled)
        self.auto_accept_chat_action.setChecked(settings.chat.auto_accept)
        self.received_messages_sound_action.setChecked(settings.sounds.play_message_alerts)
        if settings.google_contacts.enabled:
            self.google_contacts_action.setText('Disable &Google Contacts')
        else:
            self.google_contacts_action.setText('Enable &Google Contacts...')
        if not any(account.enabled for account in account_manager.iter_accounts()):
            self.display_name.setEnabled(False)
            self.activity_note.setEnabled(False)
            self.account_state.setEnabled(False)

    def _NH_SIPApplicationDidStart(self, notification):
        self.load_audio_devices()
        self.load_video_devices()
        notification.center.add_observer(self, name='CFGSettingsObjectDidChange')
        notification.center.add_observer(self, name='AudioDevicesDidChange')
        notification.center.add_observer(self, name='VideoDevicesDidChange')
        blink_settings = BlinkSettings()
        self.account_state.history = [(item.state, item.note) for item in blink_settings.presence.state_history]
        state = getattr(AccountState, blink_settings.presence.current_state.state, AccountState.Available)
        self.account_state.setState(state, blink_settings.presence.current_state.note)

    def _NH_AudioDevicesDidChange(self, notification):
        self.output_device_menu.clear()  # because actions are owned by the menu and only referenced by their corresponding action groups,
        self.input_device_menu.clear()   # clearing the menus will result in the actions automatically disappearing from the corresponding
        self.alert_device_menu.clear()   # action groups as well
        if self.session_model.active_sessions:
            added_devices = set(notification.data.new_devices).difference(notification.data.old_devices)
            if added_devices:
                new_device = added_devices.pop()
                settings = SIPSimpleSettings()
                settings.audio.input_device = new_device
                settings.audio.output_device = new_device
                settings.save()
        self.load_audio_devices()

    def _NH_VideoDevicesDidChange(self, notification):
        self.video_camera_menu.clear()  # actions will be removed automatically from the action group because they are owned by the menu and only referenced in the action group
        if self.session_model.active_sessions:
            added_devices = set(notification.data.new_devices).difference(notification.data.old_devices)
            if added_devices:
                settings = SIPSimpleSettings()
                settings.video.device = added_devices.pop()
                settings.save()
        self.load_video_devices()

    def _NH_CFGSettingsObjectDidChange(self, notification):
        settings = SIPSimpleSettings()
        blink_settings = BlinkSettings()
        icon_manager = IconManager()
        if notification.sender is settings:
            if 'audio.muted' in notification.data.modified:
                self.mute_action.setChecked(settings.audio.muted)
                self.mute_button.setChecked(settings.audio.muted)
            if 'audio.silent' in notification.data.modified:
                self.silent_action.setChecked(settings.audio.silent)
                self.silent_button.setChecked(settings.audio.silent)
            if 'audio.output_device' in notification.data.modified:
                action = next(action for action in self.output_devices_group.actions() if action.data() == settings.audio.output_device)
                action.setChecked(True)
            if 'audio.input_device' in notification.data.modified:
                action = next(action for action in self.input_devices_group.actions() if action.data() == settings.audio.input_device)
                action.setChecked(True)
            if 'audio.alert_device' in notification.data.modified:
                action = next(action for action in self.alert_devices_group.actions() if action.data() == settings.audio.alert_device)
                action.setChecked(True)
            if 'video.device' in notification.data.modified:
                action = next(action for action in self.video_devices_group.actions() if action.data() == settings.video.device)
                action.setChecked(True)
            if 'answering_machine.enabled' in notification.data.modified:
                self.answering_machine_action.setChecked(settings.answering_machine.enabled)
            if 'chat.auto_accept' in notification.data.modified:
                self.auto_accept_chat_action.setChecked(settings.chat.auto_accept)
            if 'sounds.play_message_alerts' in notification.data.modified:
                self.received_messages_sound_action.setChecked(settings.sounds.play_message_alerts)
            if 'google_contacts.enabled' in notification.data.modified:
                if notification.sender.google_contacts.enabled:
                    self.google_contacts_action.setText('Disable &Google Contacts')
                else:
                    self.google_contacts_action.setText('Enable &Google Contacts...')
        elif notification.sender is blink_settings:
            if 'presence.current_state' in notification.data.modified:
                state = getattr(AccountState, blink_settings.presence.current_state.state, AccountState.Available)
                self.account_state.setState(state, blink_settings.presence.current_state.note)
            if 'presence.icon' in notification.data.modified:
                self.set_user_icon(icon_manager.get('avatar'))
            if 'presence.offline_note' in notification.data.modified:
                # TODO: set offline note -Saul
                pass
        elif isinstance(notification.sender, (Account, BonjourAccount)):
            account_manager = AccountManager()
            account = notification.sender
            if 'enabled' in notification.data.modified:
                action = next(action for action in self.accounts_menu.actions() if action.data() is account)
                action.setChecked(account.enabled)
            if 'display_name' in notification.data.modified and account is account_manager.default_account:
                self.display_name.setText(account.display_name or '')
            if {'enabled', 'message_summary.enabled', 'message_summary.voicemail_uri'}.intersection(notification.data.modified):
                action = next(action for action in self.voicemail_menu.actions() if action.data() is account)
                action.setVisible(False if account is BonjourAccount() else account.enabled and account.message_summary.enabled)
                action.setEnabled(False if account is BonjourAccount() else account.voicemail_uri is not None)

    def _NH_SIPAccountManagerDidAddAccount(self, notification):
        account = notification.data.account

        action = self.accounts_menu.addAction(account.id if account is not BonjourAccount() else 'Bonjour')
        action.setEnabled(True if account is not BonjourAccount() else BonjourAccount.mdns_available)
        action.setCheckable(True)
        action.setChecked(account.enabled)
        action.setData(account)
        action.triggered.connect(self._AH_AccountActionTriggered)

        action = self.voicemail_menu.addAction(self.mwi_icons[0], account.id)
        action.setVisible(False if account is BonjourAccount() else account.enabled and account.message_summary.enabled)
        action.setEnabled(False if account is BonjourAccount() else account.voicemail_uri is not None)
        action.setData(account)
        action.triggered.connect(self._AH_VoicemailActionTriggered)

    def _NH_SIPAccountManagerDidRemoveAccount(self, notification):
        account = notification.data.account
        action = next(action for action in self.accounts_menu.actions() if action.data() is account)
        self.accounts_menu.removeAction(action)
        action = next(action for action in self.voicemail_menu.actions() if action.data() is account)
        self.voicemail_menu.removeAction(action)

    def _NH_SIPAccountManagerDidChangeDefaultAccount(self, notification):
        if notification.data.account is None:
            self.enable_call_buttons(False)
        else:
            selected_items = self.contact_list.selectionModel().selectedIndexes()
            self.enable_call_buttons(len(selected_items) == 1 and isinstance(selected_items[0].data(Qt.UserRole), Contact))

    def _NH_SIPAccountGotMessageSummary(self, notification):
        account = notification.sender
        summary = notification.data.message_summary
        action = next(action for action in self.voicemail_menu.actions() if action.data() is account)
        action.setEnabled(account.voicemail_uri is not None)
        if summary.messages_waiting:
            try:
                new_messages = limit(int(summary.summaries['voice-message']['new_messages']), min=0, max=11)
            except (KeyError, ValueError):
                new_messages = 0
        else:
            new_messages = 0
        action.setIcon(self.mwi_icons[new_messages])

    def _NH_SIPAccountGotPendingWatcher(self, notification):
        dialog = PendingWatcherDialog(notification.sender, notification.data.uri, notification.data.display_name)
        dialog.finished.connect(self._SH_PendingWatcherDialogFinished)
        self.pending_watcher_dialogs.append(dialog)
        dialog.show()

    def _NH_BlinkSessionNewOutgoing(self, notification):
        self.search_box.clear()

    def _NH_BlinkSessionDidReinitializeForOutgoing(self, notification):
        self.search_box.clear()

    def _NH_BlinkSessionTransferNewOutgoing(self, notification):
        self.search_box.clear()
        self.switch_view_button.view = SwitchViewButton.SessionView

    def _NH_BlinkFileTransferNewIncoming(self, notification):
        self.filetransfer_window.show(activate=QApplication.activeWindow() is not None)

    def _NH_BlinkFileTransferNewOutgoing(self, notification):
        self.filetransfer_window.show(activate=QApplication.activeWindow() is not None)
Ejemplo n.º 36
0
class Ui_MainWindow(QMainWindow, Ui_MainWindowBase):
    updateFrame = pyqtSignal()

    def __init__(self):
        super(Ui_MainWindow, self).__init__()
        self.setupUi(self)

        self.videoPlaybackInit()
        self.imgInit()
        self.menuInit()

        self.radiusSpinBox.valueChanged.connect(self.radiusSpinBoxValueChanged)
        self.lineWidthSpinBox.valueChanged.connect(self.lineWidthSpinBoxValueChanged)
        self.overlayFrameNoSpinBox.valueChanged.connect(self.overlayFrameNoSpinBoxValueChanged)
        self.stackedWidget.currentChanged.connect(self.stackedWidgetCurrentChanged)

        self.arrowCheckBox.stateChanged.connect(self.arrowCheckBoxStateChanged)
        self.pathCheckBox.stateChanged.connect(self.pathCheckBoxStateChanged)
        self.reverseArrowColorCheckBox.stateChanged.connect(self.reverseArrowColorCheckBoxStateChanged)

        self.opaqueCheckBox.stateChanged.connect(self.opaqueCheckBoxStateChanged)

        self.videoPlaybackWidget.setSignalSlotMode()
        self.updateFrame.connect(self.videoPlaybackWidget.videoPlayback)

        self.filter = None
        self.filterIO = None
        self.isInitialized = False
        self.item_dict = {}
        self.data_dict = {}
        self.trackingPathGroup = None
        self.df = {}
        self.inputPixmapItem = None
        self.cv_img = None
        self.filePath = None
        self.savedFlag = True

        QShortcut(QKeySequence("Ctrl+R"), self, self.restartDataframe)
        QShortcut(QKeySequence("Ctrl+S"), self, self.saveCSVFile)

    def dragEnterEvent(self,event):
        event.acceptProposedAction()

    def dropEvent(self,event):
        mime = event.mimeData()
        if mime.hasUrls():
            urls = mime.urls()
            if len(urls) > 0:
                self.processDropedFile(urls[0].toLocalFile())

        event.acceptProposedAction()

    def closeEvent(self, event):
        if len(self.df.keys())==0 or self.savedFlag:
            return

        quit_msg = "Data is not saved.\nAre you sure you want to exit the program?"
        reply = QtWidgets.QMessageBox.question(
                self,
                'Warning',
                quit_msg,
                QtWidgets.QMessageBox.Yes,
                QtWidgets.QMessageBox.No
                )

        if reply == QtWidgets.QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()

    def processDropedFile(self,filePath):
        root,ext = os.path.splitext(filePath)
        if ext == ".filter":
            # Read Filter
            self.openFilterFile(filePath=filePath)
            return
        elif self.openVideoFile(filePath=filePath):
            return

    def arrowCheckBoxStateChanged(self, state):
        if 'arrow' not in self.item_dict.keys():
            return

        for arrow_item in self.item_dict['arrow']:
            if state==Qt.Unchecked:
                arrow_item.hide()
            if state==Qt.Checked:
                arrow_item.show()

        self.updateInputGraphicsView()

    def pathCheckBoxStateChanged(self, state):
        if self.trackingPathGroup is None:
            return

        if state==Qt.Unchecked:
            self.trackingPathGroup.hide()
        if state==Qt.Checked:
            self.trackingPathGroup.show()

        self.updateInputGraphicsView()

    def reverseArrowColorCheckBoxStateChanged(self, state):
        if 'arrow' not in self.item_dict.keys():
            return

        for arrow_item in self.item_dict['arrow']:
            if state==Qt.Unchecked:
                arrow_item.setColor([0,0,0])
            if state==Qt.Checked:
                arrow_item.setColor([255,255,255])

        self.updateInputGraphicsView()

    def opaqueCheckBoxStateChanged(self, state):
        if self.trackingPathGroup is None:
            return

        if state==Qt.Unchecked:
            self.trackingPathGroup.setOpacity(0.5)
        if state==Qt.Checked:
            self.trackingPathGroup.setOpacity(1.0)

        self.updateInputGraphicsView()

    def reset(self):
        self.videoPlaybackWidget.stop()
        self.videoPlaybackWidget.moveToFrame(0)

    def videoPlaybackInit(self):
        self.videoPlaybackWidget.hide()
        self.videoPlaybackWidget.frameChanged.connect(self.setFrame, type=Qt.QueuedConnection)

    def setFrame(self, frame, frameNo):
        if frame is not None:
            self.cv_img = frame
            self.currentFrameNo = frameNo
            self.updateInputGraphicsView()
            self.evaluate()
        return

    def imgInit(self):
        self.inputScene = QGraphicsScene()
        self.inputGraphicsView.setScene(self.inputScene)
        self.inputGraphicsView.resizeEvent = self.inputGraphicsViewResized

    def menuInit(self):
        self.actionOpenVideo.triggered.connect(self.openVideoFile)
        self.actionOpenFilterSetting.triggered.connect(self.openFilterFile)

        self.actionSaveCSVFile.triggered.connect(self.saveCSVFile)

        self.actionRunObjectTracking.triggered.connect(self.runObjectTracking)
        self.actionTrackingPathColor.triggered.connect(self.openTrackingPathColorSelectorDialog)

        self.menuAlgorithmsActionGroup = QActionGroup(self.menuAlgorithms)

        path_list = [[tracking_system_path, currentDirPath], ]
        if os.path.exists(user_defined_lib_path):
            path_list.append([user_defined_tracking_system_path, user_defined_lib_path])
        for system_path in path_list:
            for module_path in get_modules(system_path[0], system_path[1]):
                module_str = '.'.join(module_path)

                try:
                    module = importlib.import_module(module_str)

                    if not hasattr(module, 'Widget'):
                        continue

                    class_def = getattr(module, "Widget")
                    if not issubclass(class_def, QtWidgets.QWidget):
                        continue

                    widget = class_def(self.stackedWidget)
                    widget.reset.connect(self.resetDataframe)
                    widget.restart.connect(self.restartDataframe)
                    self.stackedWidget.addWidget(widget)

                    action = self.menuAlgorithms.addAction(widget.get_name())
                    action.triggered.connect(self.generateAlgorithmsMenuClicked(widget))
                    action.setCheckable(True)
                    action.setActionGroup(self.menuAlgorithmsActionGroup)

                    if len(self.menuAlgorithmsActionGroup.actions()) == 1:
                        action.setChecked(True)
                        self.algorithmSettingsGroupBox.setTitle(widget.get_name())

                except Exception as e:
                    if system_path[1] is user_defined_lib_path:
                        msg = 'Tracking Lib. Load Fail: {0}\n{1}'.format(module_str, e)
                        self.generateCriticalMessage(msg)
                    continue

    def openTrackingPathColorSelectorDialog(self, activated=False):
        if self.trackingPathGroup is not None:
            self.trackingPathGroup.openColorSelectorDialog(self)

    def generateAlgorithmsMenuClicked(self, widget):
        def action_triggered(activated=False):
            if widget is not self.stackedWidget.currentWidget():
                self.stackedWidget.setCurrentWidget(widget)
            else:
                pass
        return action_triggered

    def initializeEventDialog(self):
        quit_msg = "Data is not saved.\nAre you sure you want to reset?"
        reply = QtWidgets.QMessageBox.question(
                self,
                'Warning',
                quit_msg,
                QtWidgets.QMessageBox.Yes,
                QtWidgets.QMessageBox.No
                )

        if reply == QtWidgets.QMessageBox.Yes:
            return True
        else:
            return False

    def openVideoFile(self, activated=False, filePath = None):
        if filePath is None:
            filePath, _ = QFileDialog.getOpenFileName(None, 'Open Video File', userDir)

        global filterOperation
        if len(filePath) is not 0:
            if filterOperation is not None and self.videoPlaybackWidget.isOpened():
                if self.initializeEventDialog():
                    filterOperation = None
                    self.removeTrackingGraphicsItems()
                    self.savedFlag = True
                else:
                    return
            self.filePath = filePath
            ret = self.videoPlaybackWidget.openVideo(filePath)
            if not ret:
                return False

            self.videoPlaybackWidget.show()

            self.cv_img = self.videoPlaybackWidget.getCurrentFrame()
            self.currentFrameNo = 0
            self.videoPlaybackWidget.setMaxTickableFrameNo(0)
            self.initializeTrackingSystem()

            return True
        else:
            return False

    def openFilterFile(self, activated=False, filePath = None):
        if filePath is None:
            filePath, _ = QFileDialog.getOpenFileName(None, 'Open Block File', userDir, "Block files (*.filter)")

        if len(filePath) is not 0:
            if filterOperation is not None and self.videoPlaybackWidget.isOpened():
                if self.initializeEventDialog():
                    self.videoPlaybackWidget.closeVideo()
                    self.videoPlaybackWidget.hide()
                    self.removeTrackingGraphicsItems()
                    self.inputScene.removeItem(self.inputPixmapItem)
                    self.savedFlag = True
                else:
                    return
            logger.debug("Open Filter file: {0}".format(filePath))

            self.filterIO = FilterIO(filePath)

            exec(self.filterIO.getFilterCode(), globals())

            self.filter = None

            self.initializeTrackingSystem()
            self.evaluate()

    def saveCSVFile(self, activated=False, filePath = None):
        if len(self.df.keys())!=0:
            dirctory = os.path.dirname(self.filePath)
            base_name = os.path.splitext(os.path.basename(self.filePath))[0]

            path = os.path.join(dirctory, '{0}-{1}.txt'.format(base_name, "info"))
            filePath, _ = QFileDialog.getSaveFileName(None, 'Save Info File', path, "TXT files (*.txt)")

            if len(filePath) is not 0:
                logger.debug("Saving Info file: {0}".format(filePath))

                with open(filePath, 'w') as fp:
                    fp.write(self.videoPlaybackWidget.getVideoInfo())

            for attr, df in self.df.items():
                path = os.path.join(dirctory, '{0}-{1}.csv'.format(base_name, attr))
                filePath, _ = QFileDialog.getSaveFileName(None, 'Save CSV File', path, "CSV files (*.csv)")

                if len(filePath) is not 0:
                    logger.debug("Saving CSV file: {0}".format(filePath))

                    df = df.copy().dropna()
                    levels = df.columns.levels
                    col = ['{0}{1}'.format(l,i) for i in levels[0] for l in levels[1]]
                    df.columns = col

                    df.to_csv(filePath)

            for k, v in self.data_dict.items():
                path = os.path.join(dirctory, '{0}-{1}.json'.format(base_name, k))
                filePath, _ = QFileDialog.getSaveFileName(None, 'Save JSON File', path, "JSON files (*.json)")

                if len(filePath) is not 0:
                    logger.debug("Saving JSON file: {0}".format(filePath))
                    with open(filePath, 'w') as f_p:
                        json.dump(v, f_p)

            path = os.path.join(dirctory, '{0}-colors.color'.format(base_name))
            filePath, _ = QFileDialog.getSaveFileName(None, 'Save Color File', path, "Color files (*.color)")
            if len(filePath) is not 0:
                logger.debug("Saving Color file: {0}".format(filePath))
                self.trackingPathGroup.saveColors(filePath)

            self.savedFlag = True

    def radiusSpinBoxValueChanged(self, i):
        if self.trackingPathGroup is not None:
            self.trackingPathGroup.setRadius(i)
        self.updateInputGraphicsView()

    def lineWidthSpinBoxValueChanged(self, i):
        if self.trackingPathGroup is not None:
            self.trackingPathGroup.setLineWidth(i)
        self.updateInputGraphicsView()

    def overlayFrameNoSpinBoxValueChanged(self, i):
        if self.trackingPathGroup is not None:
            self.trackingPathGroup.setOverlayFrameNo(i)
        self.updateInputGraphicsView()

    def stackedWidgetCurrentChanged(self, i):
        currentWidget = self.stackedWidget.currentWidget()

        currentWidget.estimator_init()
        self.algorithmSettingsGroupBox.setTitle(currentWidget.get_name())
        self.resetDataframe()

    def updateInputGraphicsView(self):
        if self.inputPixmapItem is not None:
            self.inputScene.removeItem(self.inputPixmapItem)

        if self.filter is not None and hasattr(self.filter, "resize_flag") and self.filter.resize_flag:
            qimg = misc.cvMatToQImage(cv2.pyrDown(self.cv_img))
        else:
            qimg = misc.cvMatToQImage(self.cv_img)

        pixmap = QPixmap.fromImage(qimg)

        self.inputPixmapItem = QGraphicsPixmapItem(pixmap)
        rect = QtCore.QRectF(pixmap.rect())
        self.inputScene.setSceneRect(rect)
        self.inputScene.addItem(self.inputPixmapItem)

        self.inputGraphicsView.viewport().update()
        self.inputGraphicsViewResized()

    def inputGraphicsViewResized(self, event=None):
        self.inputGraphicsView.fitInView(self.inputScene.sceneRect(), QtCore.Qt.KeepAspectRatio)

    def updatePath(self):
        try:
            attrs = self.stackedWidget.currentWidget().get_attributes()
            attrs.keys()
        except Exception as e:
            msg = 'Tracking Lib. Attributes Error:\n{}'.format(e)
            self.generateCriticalMessage(msg)
            return

        if 'position' in attrs:
            self.trackingPathGroup.setPoints(self.currentFrameNo)

        if 'arrow' in attrs:
            for i, arrow_item in enumerate(self.item_dict['arrow']):
                begin = self.df['position'].loc[self.currentFrameNo, i].values
                end = self.df['arrow'].loc[self.currentFrameNo, i].values
                arrow_item.setPosition(begin, end)

        if 'path' in attrs:
            for path_item, path_data in zip(self.item_dict['path'], self.data_dict['path'][self.currentFrameNo]):
                poly = QPolygonF()
                for p in path_data:
                    poly.append(QPointF(*p))

                painter_path = QPainterPath()
                painter_path.addPolygon(poly)
                path_item.setPath(painter_path)

                pen = QPen(Qt.blue)
                pen.setWidth(2)
                path_item.setPen(pen)

        if 'polygon' in attrs:
            for path_item, path_data in zip(self.item_dict['polygon'], self.data_dict['polygon'][self.currentFrameNo]):
                poly = QPolygonF()
                for p in path_data:
                    poly.append(QPointF(*p))

                painter_path = QPainterPath()
                painter_path.addPolygon(poly)
                path_item.setPath(painter_path)

                pen = QPen(Qt.black)
                pen.setWidth(1)
                path_item.setPen(pen)

        if 'rect' in attrs:
            for rect_item, rect in zip(self.item_dict['rect'], self.data_dict['rect'][self.currentFrameNo]):
                rect_item.setRect(QRectF(QPointF(*rect[0]), QPointF(*rect[1])))

    def resetDataframe(self):
        self.initializeTrackingSystem()
        self.evaluate()

    def restartDataframe(self):
        if len(self.df.keys()) == 0:
            return

        for attr in self.df.keys():
            self.df[attr].loc[self.currentFrameNo+1:] = np.nan

        for k in list(self.data_dict.keys()):
            for kk in list(self.data_dict[k].keys()):
                if kk == 'name':
                    continue
                elif int(kk) > self.currentFrameNo:
                    del self.data_dict[k][kk]

        df = {}
        for attr in self.df.keys():
            df[attr] = self.df[attr].loc[self.currentFrameNo]

        kv = {k: [] for k in self.df.keys()}
        for key, value in kv.items():
            mul_levs = df[key].index.levels
            for i in mul_levs[0]:
                value.append(df[key][i].values)

            kv[key] = np.array(value)

        for key, value in self.data_dict.items():
            kv[key] = [np.array(v) for v in value[self.currentFrameNo]]

        self.videoPlaybackWidget.setMaxTickableFrameNo(
            self.currentFrameNo + self.videoPlaybackWidget.playbackDelta
        )

        try:
            widget = self.stackedWidget.currentWidget()
            widget.reset_estimator(kv)
        except Exception as e:
            msg = 'Tracking Lib. Reset Fail:\n{}'.format(e)
            self.generateCriticalMessage(msg)

    def removeTrackingGraphicsItems(self):
        if self.trackingPathGroup is not None:
            self.inputScene.removeItem(self.trackingPathGroup)
            self.trackingPathGroup = None

        for k, v in self.item_dict.items():
            [self.inputScene.removeItem(item) for item in v]
            v.clear()

    def initializeTrackingSystem(self):
        self.isInitialized = False
        self.removeTrackingGraphicsItems()

        if hasattr(self, 'currentFrameNo') and self.currentFrameNo != 0:
            ret, frame = self.videoPlaybackWidget.readFrame(0)
            self.cv_img = frame
            self.updateInputGraphicsView()
            self.currentFrameNo = 0
            self.videoPlaybackWidget.setSliderValueWithoutSignal(0)

        self.videoPlaybackWidget.setMaxTickableFrameNo(0)

        try:
            tracking_n = self.stackedWidget.currentWidget().get_tracking_n()
            attrs = self.stackedWidget.currentWidget().get_attributes()
            is_filter_required = self.stackedWidget.currentWidget().is_filter_required()
            attrs.keys()
        except Exception as e:
            msg = 'Tracking Lib. Tracking N or attributes Error:\n{}'.format(e)
            self.generateCriticalMessage(msg)
            return

        if not (self.videoPlaybackWidget.isOpened() and
           (filterOperation is not None or not is_filter_required)):
            return False


        if is_filter_required:
            self.filter = filterOperation(self.cv_img)
            self.filter.fgbg = self.filterIO.getBackgroundImg()
            self.filter.isInit = True
        else:
            self.filter = None

        max_frame_pos = self.videoPlaybackWidget.getMaxFramePos()

        self.df = {}
        for k, t in attrs.items():
            if t is None:
                self.data_dict[k] = {}
                self.data_dict[k]['name'] = k
            else:
                tuples = []
                for i in range(tracking_n):
                    for v in t:
                        tuples.append((i, v))
                col = pd.MultiIndex.from_tuples(tuples)
                self.df[k] = pd.DataFrame(
                    index=range(0, max_frame_pos+1, self.playbackDeltaSpinBox.value()),
                    columns=col,
                    dtype=np.float64
                    ).sort_index().sort_index(axis=1)
                self.df[k].index.name = k

        if 'position' in attrs:
            self.trackingPathGroup = TrackingPathGroup()
            self.trackingPathGroup.setRect(self.inputScene.sceneRect())
            if self.pathCheckBox.checkState()==Qt.Unchecked:
                self.trackingPathGroup.hide()

            self.inputScene.addItem(self.trackingPathGroup)
            self.trackingPathGroup.setDataFrame(self.df['position'])

            lw = self.trackingPathGroup.autoAdjustLineWidth(self.cv_img.shape)
            r = self.trackingPathGroup.autoAdjustRadius(self.cv_img.shape)
            self.trackingPathGroup.setOverlayFrameNo(self.overlayFrameNoSpinBox.value())
            self.lineWidthSpinBox.setValue(lw)
            self.radiusSpinBox.setValue(r)

            self.trackingPathGroup.setItemsAreMovable(True)

        if 'rect' in attrs:
            self.item_dict['rect'] = [QGraphicsRectItem() for i in range(tracking_n)]
            for rect_item in self.item_dict['rect']:
                rect_item.setZValue(1000)
                self.inputScene.addItem(rect_item)

        if 'arrow' in attrs:
            self.item_dict['arrow'] = [MovableArrow() for i in range(tracking_n)]
            for arrow_item in self.item_dict['arrow']:
                arrow_item.setZValue(900)
                if self.arrowCheckBox.checkState()==Qt.Unchecked:
                    arrow_item.hide()
                self.inputScene.addItem(arrow_item)

        if 'path' in attrs:
            self.item_dict['path'] = [QGraphicsPathItem() for i in range(tracking_n)]
            for path_item in self.item_dict['path']:
                path_item.setZValue(900)
                self.inputScene.addItem(path_item)

        if 'polygon' in attrs:
            self.item_dict['polygon'] = [QGraphicsPathItem() for i in range(tracking_n)]
            for path_item in self.item_dict['polygon']:
                path_item.setZValue(900)
                self.inputScene.addItem(path_item)

        # if self.currentFrameNo != 0:
        #     self.videoPlaybackWidget.moveToFrame(0)
        self.videoPlaybackWidget.setPlaybackDelta(self.playbackDeltaSpinBox.value())

        self.isInitialized = True

    def evaluate(self, update=True):
        if not self.isInitialized:
            return

        if self.currentFrameNo + 1 < self.videoPlaybackWidget.getMaxTickableFrameNo():
            print('update')
            self.updatePath()
            self.updateInputGraphicsView()
            self.updateFrame.emit()
            return

        if self.filter is not None:
            img = self.filter.filterFunc(self.cv_img.copy())
        else:
            img = None

        try:
            widget = self.stackedWidget.currentWidget()
            prev_pos = self.videoPlaybackWidget.getPrevFramePos()
            attrs = widget.get_attributes()

            if prev_pos >= 0:
                prev_data = {
                    k: self.data_dict[k][prev_pos]
                    for k in self.data_dict.keys()
                }

                for k in self.df.keys():
                    df = self.df[k]
                    prev_data[k] = [
                        np.copy(df.loc[prev_pos, i].values)
                        for i in df.columns.levels[0]
                    ]
            else:
                prev_data = {k: None for k in attrs.keys()}

            prev_data['ignore_error'] = (
                self.ignoreMisDetectionErrorCheckBox.checkState() == Qt.Checked
            )

            res = widget.track(
                self.cv_img.copy(),
                img,
                prev_data
            )
        except Exception as e:
            self.videoPlaybackWidget.stop()
            self.videoPlaybackWidget.moveToFrame(
                max(0, self.currentFrameNo - self.videoPlaybackWidget.playbackDelta)
            )
            msg = 'Tracking Lib. Tracking method Fail:\n{}'.format(e)
            self.generateCriticalMessage(msg)
            return

        for k, v in res.items():
            if k == 'path' or k == 'rect' or k == 'polygon':
                self.data_dict[k][self.currentFrameNo] = ndarray_to_list(v)
                continue
            if not attrs[k]:
                continue
            for i in range(len(v)):
                self.df[k].loc[self.currentFrameNo, i] = v[i]

        maxTickableFrameNo = \
            self.currentFrameNo + self.videoPlaybackWidget.playbackDelta
        if maxTickableFrameNo > self.videoPlaybackWidget.getMaxFramePos():
            maxTickableFrameNo = self.currentFrameNo

        self.videoPlaybackWidget.setMaxTickableFrameNo(maxTickableFrameNo)
        self.savedFlag = False

        if update:
            self.updatePath()
            self.updateInputGraphicsView()
            self.updateFrame.emit()

    def runObjectTracking(self):
        if self.filter is None or not self.videoPlaybackWidget.isOpened():
            return
        minFrame = self.currentFrameNo
        maxFrame = self.videoPlaybackWidget.getMaxFramePos()
        numFrames = maxFrame-minFrame
        progress = QProgressDialog("Running...", "Abort", 0, numFrames, self)

        progress.setWindowModality(Qt.WindowModal)

        currentFrameNo = self.currentFrameNo
        for i, frameNo in enumerate(range(minFrame, maxFrame+1)):
            progress.setValue(i)
            if progress.wasCanceled():
                break

            ret, frame = self.videoPlaybackWidget.readFrame(frameNo)
            self.cv_img = frame
            self.currentFrameNo = frameNo
            self.evaluate(False)

        self.videoPlaybackWidget.moveToFrame(currentFrameNo)
        progress.setValue(numFrames)

    def eventFilter(self, obj, event):
        if event.type() == QEvent.Wheel:
            self.videoPlaybackWidget.wheelEvent(event)
            return True

        if event.type() == QEvent.KeyPress:
            qwop = [Qt.Key_Q, Qt.Key_W, Qt.Key_O, Qt.Key_P]
            is_qwop = (True in map(lambda x: x == event.key(), qwop))

            is_arrow = (Qt.Key_Home <= event.key() <= Qt.Key_PageDown)

            if is_arrow or is_qwop:
                self.videoPlaybackWidget.keyPressEvent(event)
                return True

        return False

    def generateCriticalMessage(self, msg):
        tb = sys.exc_info()[-1]
        f = tb.tb_frame
        msg = 'File name: {0}\nLine No: {1}\n'.format(f.f_code.co_filename, tb.tb_lineno) + msg
        reply = QtWidgets.QMessageBox.critical(
                self,
                'Critical',
                msg,
                QtWidgets.QMessageBox.Ok,
                QtWidgets.QMessageBox.NoButton
                )
        return reply
Ejemplo n.º 37
0
    def setupTextActions(self):
        tb = QToolBar(self)
        tb.setWindowTitle("Format Actions")
        self.addToolBar(tb)

        menu = QMenu("F&ormat", self)
        self.menuBar().addMenu(menu)

        self.actionTextBold = QAction(QIcon.fromTheme(
            'format-text-bold', QIcon(rsrcPath + '/textbold.png')),
                                      "&Bold",
                                      self,
                                      priority=QAction.LowPriority,
                                      shortcut=Qt.CTRL + Qt.Key_B,
                                      triggered=self.textBold,
                                      checkable=True)
        bold = QFont()
        bold.setBold(True)
        self.actionTextBold.setFont(bold)
        tb.addAction(self.actionTextBold)
        menu.addAction(self.actionTextBold)

        self.actionTextItalic = QAction(QIcon.fromTheme(
            'format-text-italic', QIcon(rsrcPath + '/textitalic.png')),
                                        "&Italic",
                                        self,
                                        priority=QAction.LowPriority,
                                        shortcut=Qt.CTRL + Qt.Key_I,
                                        triggered=self.textItalic,
                                        checkable=True)
        italic = QFont()
        italic.setItalic(True)
        self.actionTextItalic.setFont(italic)
        tb.addAction(self.actionTextItalic)
        menu.addAction(self.actionTextItalic)

        self.actionTextUnderline = QAction(QIcon.fromTheme(
            'format-text-underline', QIcon(rsrcPath + '/textunder.png')),
                                           "&Underline",
                                           self,
                                           priority=QAction.LowPriority,
                                           shortcut=Qt.CTRL + Qt.Key_U,
                                           triggered=self.textUnderline,
                                           checkable=True)
        underline = QFont()
        underline.setUnderline(True)
        self.actionTextUnderline.setFont(underline)
        tb.addAction(self.actionTextUnderline)
        menu.addAction(self.actionTextUnderline)

        menu.addSeparator()

        grp = QActionGroup(self, triggered=self.textAlign)

        # Make sure the alignLeft is always left of the alignRight.
        if QApplication.isLeftToRight():
            self.actionAlignLeft = QAction(
                QIcon.fromTheme('format-justify-left',
                                QIcon(rsrcPath + '/textleft.png')), "&Left",
                grp)
            self.actionAlignCenter = QAction(
                QIcon.fromTheme('format-justify-center',
                                QIcon(rsrcPath + '/textcenter.png')),
                "C&enter", grp)
            self.actionAlignRight = QAction(
                QIcon.fromTheme('format-justify-right',
                                QIcon(rsrcPath + '/textright.png')), "&Right",
                grp)
        else:
            self.actionAlignRight = QAction(
                QIcon.fromTheme('format-justify-right',
                                QIcon(rsrcPath + '/textright.png')), "&Right",
                grp)
            self.actionAlignCenter = QAction(
                QIcon.fromTheme('format-justify-center',
                                QIcon(rsrcPath + '/textcenter.png')),
                "C&enter", grp)
            self.actionAlignLeft = QAction(
                QIcon.fromTheme('format-justify-left',
                                QIcon(rsrcPath + '/textleft.png')), "&Left",
                grp)

        self.actionAlignJustify = QAction(
            QIcon.fromTheme('format-justify-fill',
                            QIcon(rsrcPath + '/textjustify.png')), "&Justify",
            grp)

        self.actionAlignLeft.setShortcut(Qt.CTRL + Qt.Key_L)
        self.actionAlignLeft.setCheckable(True)
        self.actionAlignLeft.setPriority(QAction.LowPriority)

        self.actionAlignCenter.setShortcut(Qt.CTRL + Qt.Key_E)
        self.actionAlignCenter.setCheckable(True)
        self.actionAlignCenter.setPriority(QAction.LowPriority)

        self.actionAlignRight.setShortcut(Qt.CTRL + Qt.Key_R)
        self.actionAlignRight.setCheckable(True)
        self.actionAlignRight.setPriority(QAction.LowPriority)

        self.actionAlignJustify.setShortcut(Qt.CTRL + Qt.Key_J)
        self.actionAlignJustify.setCheckable(True)
        self.actionAlignJustify.setPriority(QAction.LowPriority)

        tb.addActions(grp.actions())
        menu.addActions(grp.actions())
        menu.addSeparator()

        pix = QPixmap(16, 16)
        pix.fill(Qt.black)
        self.actionTextColor = QAction(QIcon(pix),
                                       "&Color...",
                                       self,
                                       triggered=self.textColor)
        tb.addAction(self.actionTextColor)
        menu.addAction(self.actionTextColor)

        tb = QToolBar(self)
        tb.setAllowedAreas(Qt.TopToolBarArea | Qt.BottomToolBarArea)
        tb.setWindowTitle("Format Actions")
        self.addToolBarBreak(Qt.TopToolBarArea)
        self.addToolBar(tb)

        comboStyle = QComboBox(tb)
        tb.addWidget(comboStyle)
        comboStyle.addItem("Standard")
        comboStyle.addItem("Bullet List (Disc)")
        comboStyle.addItem("Bullet List (Circle)")
        comboStyle.addItem("Bullet List (Square)")
        comboStyle.addItem("Ordered List (Decimal)")
        comboStyle.addItem("Ordered List (Alpha lower)")
        comboStyle.addItem("Ordered List (Alpha upper)")
        comboStyle.addItem("Ordered List (Roman lower)")
        comboStyle.addItem("Ordered List (Roman upper)")
        comboStyle.activated.connect(self.textStyle)

        self.comboFont = QFontComboBox(tb)
        tb.addWidget(self.comboFont)
        self.comboFont.activated[str].connect(self.textFamily)

        self.comboSize = QComboBox(tb)
        self.comboSize.setObjectName("comboSize")
        tb.addWidget(self.comboSize)
        self.comboSize.setEditable(True)

        db = QFontDatabase()
        for size in db.standardSizes():
            self.comboSize.addItem("%s" % (size))

        self.comboSize.activated[str].connect(self.textSize)
        self.comboSize.setCurrentIndex(
            self.comboSize.findText("%s" % (QApplication.font().pointSize())))
Ejemplo n.º 38
0
    def init_menu(self):
        layout_load_act = QAction(tr("MenuFile", "Load saved layout..."), self)
        layout_load_act.setShortcut("Ctrl+O")
        layout_load_act.triggered.connect(self.on_layout_load)

        layout_save_act = QAction(tr("MenuFile", "Save current layout..."),
                                  self)
        layout_save_act.setShortcut("Ctrl+S")
        layout_save_act.triggered.connect(self.on_layout_save)

        sideload_json_act = QAction(tr("MenuFile", "Sideload VIA JSON..."),
                                    self)
        sideload_json_act.triggered.connect(self.on_sideload_json)

        download_via_stack_act = QAction(
            tr("MenuFile", "Download VIA definitions"), self)
        download_via_stack_act.triggered.connect(self.load_via_stack_json)

        load_dummy_act = QAction(tr("MenuFile", "Load dummy JSON..."), self)
        load_dummy_act.triggered.connect(self.on_load_dummy)

        exit_act = QAction(tr("MenuFile", "Exit"), self)
        exit_act.setShortcut("Ctrl+Q")
        exit_act.triggered.connect(qApp.exit)

        file_menu = self.menuBar().addMenu(tr("Menu", "File"))
        file_menu.addAction(layout_load_act)
        file_menu.addAction(layout_save_act)
        file_menu.addSeparator()
        file_menu.addAction(sideload_json_act)
        file_menu.addAction(download_via_stack_act)
        file_menu.addAction(load_dummy_act)
        file_menu.addSeparator()
        file_menu.addAction(exit_act)

        keyboard_unlock_act = QAction(tr("MenuSecurity", "Unlock"), self)
        keyboard_unlock_act.triggered.connect(self.unlock_keyboard)

        keyboard_lock_act = QAction(tr("MenuSecurity", "Lock"), self)
        keyboard_lock_act.triggered.connect(self.lock_keyboard)

        keyboard_reset_act = QAction(
            tr("MenuSecurity", "Reboot to bootloader"), self)
        keyboard_reset_act.triggered.connect(self.reboot_to_bootloader)

        keyboard_layout_menu = self.menuBar().addMenu(
            tr("Menu", "Keyboard layout"))
        keymap_group = QActionGroup(self)
        selected_keymap = self.settings.value("keymap")
        for idx, keymap in enumerate(KEYMAPS):
            act = QAction(tr("KeyboardLayout", keymap[0]), self)
            act.triggered.connect(
                lambda checked, x=idx: self.change_keyboard_layout(x))
            act.setCheckable(True)
            if selected_keymap == keymap[0]:
                self.change_keyboard_layout(idx)
                act.setChecked(True)
            keymap_group.addAction(act)
            keyboard_layout_menu.addAction(act)
        # check "QWERTY" if nothing else is selected
        if keymap_group.checkedAction() is None:
            keymap_group.actions()[0].setChecked(True)

        self.security_menu = self.menuBar().addMenu(tr("Menu", "Security"))
        self.security_menu.addAction(keyboard_unlock_act)
        self.security_menu.addAction(keyboard_lock_act)
        self.security_menu.addSeparator()
        self.security_menu.addAction(keyboard_reset_act)

        self.theme_menu = self.menuBar().addMenu(tr("Menu", "Theme"))
        theme_group = QActionGroup(self)
        selected_theme = self.get_theme()
        for name, _ in [("System", None)] + themes.themes:
            act = QAction(tr("MenuTheme", name), self)
            act.triggered.connect(lambda x, name=name: self.set_theme(name))
            act.setCheckable(True)
            act.setChecked(selected_theme == name)
            theme_group.addAction(act)
            self.theme_menu.addAction(act)
        # check "System" if nothing else is selected
        if theme_group.checkedAction() is None:
            theme_group.actions()[0].setChecked(True)
Ejemplo n.º 39
0
    def setupTextActions(self):
        tb = QToolBar(self)
        tb.setWindowTitle("Format Actions")
        self.addToolBar(tb)

        menu = QMenu("F&ormat", self)
        self.menuBar().addMenu(menu)

        self.actionTextBold = QAction(
                QIcon.fromTheme('format-text-bold',
                        QIcon(rsrcPath + '/textbold.png')),
                "&Bold", self, priority=QAction.LowPriority,
                shortcut=Qt.CTRL + Qt.Key_B, triggered=self.textBold,
                checkable=True)
        bold = QFont()
        bold.setBold(True)
        self.actionTextBold.setFont(bold)
        tb.addAction(self.actionTextBold)
        menu.addAction(self.actionTextBold)

        self.actionTextItalic = QAction(
                QIcon.fromTheme('format-text-italic',
                        QIcon(rsrcPath + '/textitalic.png')),
                "&Italic", self, priority=QAction.LowPriority,
                shortcut=Qt.CTRL + Qt.Key_I, triggered=self.textItalic,
                checkable=True)
        italic = QFont()
        italic.setItalic(True)
        self.actionTextItalic.setFont(italic)
        tb.addAction(self.actionTextItalic)
        menu.addAction(self.actionTextItalic)

        self.actionTextUnderline = QAction(
                QIcon.fromTheme('format-text-underline',
                        QIcon(rsrcPath + '/textunder.png')),
                "&Underline", self, priority=QAction.LowPriority,
                shortcut=Qt.CTRL + Qt.Key_U, triggered=self.textUnderline,
                checkable=True)
        underline = QFont()
        underline.setUnderline(True)
        self.actionTextUnderline.setFont(underline)
        tb.addAction(self.actionTextUnderline)
        menu.addAction(self.actionTextUnderline)

        menu.addSeparator()

        grp = QActionGroup(self, triggered=self.textAlign)

        # Make sure the alignLeft is always left of the alignRight.
        if QApplication.isLeftToRight():
            self.actionAlignLeft = QAction(
                    QIcon.fromTheme('format-justify-left',
                            QIcon(rsrcPath + '/textleft.png')),
                    "&Left", grp)
            self.actionAlignCenter = QAction(
                    QIcon.fromTheme('format-justify-center',
                            QIcon(rsrcPath + '/textcenter.png')),
                    "C&enter", grp)
            self.actionAlignRight = QAction(
                    QIcon.fromTheme('format-justify-right',
                            QIcon(rsrcPath + '/textright.png')),
                    "&Right", grp)
        else:
            self.actionAlignRight = QAction(
                    QIcon.fromTheme('format-justify-right',
                            QIcon(rsrcPath + '/textright.png')),
                    "&Right", grp)
            self.actionAlignCenter = QAction(
                    QIcon.fromTheme('format-justify-center',
                            QIcon(rsrcPath + '/textcenter.png')),
                    "C&enter", grp)
            self.actionAlignLeft = QAction(
                    QIcon.fromTheme('format-justify-left',
                            QIcon(rsrcPath + '/textleft.png')),
                    "&Left", grp)
 
        self.actionAlignJustify = QAction(
                QIcon.fromTheme('format-justify-fill',
                        QIcon(rsrcPath + '/textjustify.png')),
                "&Justify", grp)

        self.actionAlignLeft.setShortcut(Qt.CTRL + Qt.Key_L)
        self.actionAlignLeft.setCheckable(True)
        self.actionAlignLeft.setPriority(QAction.LowPriority)

        self.actionAlignCenter.setShortcut(Qt.CTRL + Qt.Key_E)
        self.actionAlignCenter.setCheckable(True)
        self.actionAlignCenter.setPriority(QAction.LowPriority)

        self.actionAlignRight.setShortcut(Qt.CTRL + Qt.Key_R)
        self.actionAlignRight.setCheckable(True)
        self.actionAlignRight.setPriority(QAction.LowPriority)

        self.actionAlignJustify.setShortcut(Qt.CTRL + Qt.Key_J)
        self.actionAlignJustify.setCheckable(True)
        self.actionAlignJustify.setPriority(QAction.LowPriority)

        tb.addActions(grp.actions())
        menu.addActions(grp.actions())
        menu.addSeparator()

        pix = QPixmap(16, 16)
        pix.fill(Qt.black)
        self.actionTextColor = QAction(QIcon(pix), "&Color...", self,
                triggered=self.textColor)
        tb.addAction(self.actionTextColor)
        menu.addAction(self.actionTextColor)

        tb = QToolBar(self)
        tb.setAllowedAreas(Qt.TopToolBarArea | Qt.BottomToolBarArea)
        tb.setWindowTitle("Format Actions")
        self.addToolBarBreak(Qt.TopToolBarArea)
        self.addToolBar(tb)

        comboStyle = QComboBox(tb)
        tb.addWidget(comboStyle)
        comboStyle.addItem("Standard")
        comboStyle.addItem("Bullet List (Disc)")
        comboStyle.addItem("Bullet List (Circle)")
        comboStyle.addItem("Bullet List (Square)")
        comboStyle.addItem("Ordered List (Decimal)")
        comboStyle.addItem("Ordered List (Alpha lower)")
        comboStyle.addItem("Ordered List (Alpha upper)")
        comboStyle.addItem("Ordered List (Roman lower)")
        comboStyle.addItem("Ordered List (Roman upper)")
        comboStyle.activated.connect(self.textStyle)

        self.comboFont = QFontComboBox(tb)
        tb.addWidget(self.comboFont)
        self.comboFont.activated[str].connect(self.textFamily)

        self.comboSize = QComboBox(tb)
        self.comboSize.setObjectName("comboSize")
        tb.addWidget(self.comboSize)
        self.comboSize.setEditable(True)

        db = QFontDatabase()
        for size in db.standardSizes():
            self.comboSize.addItem("%s" % (size))

        self.comboSize.activated[str].connect(self.textSize)
        self.comboSize.setCurrentIndex(
                self.comboSize.findText(
                        "%s" % (QApplication.font().pointSize())))