Exemplo n.º 1
0
    def updatePlotPersoButton(self):
        menu = QMenu(self.mw)

        menus = []
        for i in [self.tr("Main"), self.tr("Secondary"), self.tr("Minor")]:
            m = QMenu(i, menu)
            menus.append(m)
            menu.addMenu(m)

        mpr = QSignalMapper(menu)
        for i in range(self.mw.mdlCharacter.rowCount()):
            a = QAction(self.mw.mdlCharacter.name(i), menu)
            a.setIcon(self.mw.mdlCharacter.icon(i))
            a.triggered.connect(mpr.map)
            mpr.setMapping(a, int(self.mw.mdlCharacter.ID(i)))

            imp = toInt(self.mw.mdlCharacter.importance(i))

            menus[2 - imp].addAction(a)

        # Disabling empty menus
        for m in menus:
            if not m.actions():
                m.setEnabled(False)

        mpr.mapped.connect(self.addPlotPerso)
        self.mw.btnAddPlotPerso.setMenu(menu)
Exemplo n.º 2
0
    def updatePlotPersoButton(self):
        menu = QMenu(self.mw)

        menus = []
        for i in [self.tr("Main"), self.tr("Secondary"), self.tr("Minor")]:
            m = QMenu(i, menu)
            menus.append(m)
            menu.addMenu(m)

        mpr = QSignalMapper(menu)
        for i in range(self.mw.mdlCharacter.rowCount()):
            a = QAction(self.mw.mdlCharacter.name(i), menu)
            a.setIcon(self.mw.mdlCharacter.icon(i))
            a.triggered.connect(mpr.map)
            mpr.setMapping(a, int(self.mw.mdlCharacter.ID(i)))

            imp = toInt(self.mw.mdlCharacter.importance(i))

            menus[2 - imp].addAction(a)

        # Disabling empty menus
        for m in menus:
            if not m.actions():
                m.setEnabled(False)

        mpr.mapped.connect(self.addPlotPerso)
        self.mw.btnAddPlotPerso.setMenu(menu)
Exemplo n.º 3
0
class Monitor(QObject):
	"""File monitor

	This monitor can be used to track single files
	"""

	def __init__(self, **kwargs):
		super(Monitor, self).__init__(**kwargs)

		self.watched = WeakValueDictionary()
		self.delMapper = QSignalMapper(self)
		self.delMapper.mapped[str].connect(self.unmonitorFile)

		self.watcher = MonitorWithRename(parent=self)
		self.watcher.fileChanged.connect(self._onFileChanged)

	def monitorFile(self, path):
		"""Monitor a file and return an object that tracks only `path`

		:rtype: SingleFileWatcher
		:return: an object tracking `path`, the same object is returned if the method is called
		         with the same path.
		"""
		path = os.path.abspath(path)

		self.watcher.addPath(path)

		proxy = self.watched.get(path)
		if not proxy:
			proxy = SingleFileWatcher(path)
			proxy.destroyed.connect(self.delMapper.map)
			self.delMapper.setMapping(proxy, path)
			self.watched[path] = proxy

		return proxy

	@Slot(str)
	def unmonitorFile(self, path):
		"""Stop monitoring a file

		Since there is only one :any:`SingleFileWatcher` object per path, all objects monitoring
		`path` will not receive notifications anymore.
		To let only one object stop monitoring the file, simply disconnect its `modified` signal.
		When the :any:`SingleFileWatcher` object returned by method :any:`monitorFile` is
		destroyed, the file is automatically un-monitored.
		"""
		path = os.path.abspath(path)

		self.watcher.removePath(path)
		self.watched.pop(path, None)

	@Slot(str)
	def _onFileChanged(self, path):
		proxy = self.watched.get(path)
		if proxy:
			proxy.modified.emit()
Exemplo n.º 4
0
    def refresh_accounts(self):
        self.menu_change_account.clear()
        signal_mapper = QSignalMapper(self)

        for account_name in sorted(self.app.accounts.keys()):
            action = QAction(account_name, self)
            self.menu_change_account.addAction(action)
            signal_mapper.setMapping(action, account_name)
            action.triggered.connect(signal_mapper.map)
            signal_mapper.mapped[str].connect(self.action_change_account)
Exemplo n.º 5
0
class Monitor(QObject):
    """File monitor

	This monitor can be used to track single files
	"""
    def __init__(self, **kwargs):
        super(Monitor, self).__init__(**kwargs)

        self.watched = WeakValueDictionary()
        self.delMapper = QSignalMapper(self)
        self.delMapper.mapped[str].connect(self.unmonitorFile)

        self.watcher = MonitorWithRename(parent=self)
        self.watcher.fileChanged.connect(self._onFileChanged)

    def monitorFile(self, path):
        """Monitor a file and return an object that tracks only `path`

		:rtype: SingleFileWatcher
		:return: an object tracking `path`, the same object is returned if the method is called
		         with the same path.
		"""
        path = os.path.abspath(path)

        self.watcher.addPath(path)

        proxy = self.watched.get(path)
        if not proxy:
            proxy = SingleFileWatcher(path)
            proxy.destroyed.connect(self.delMapper.map)
            self.delMapper.setMapping(proxy, path)
            self.watched[path] = proxy

        return proxy

    @Slot(str)
    def unmonitorFile(self, path):
        """Stop monitoring a file

		Since there is only one :any:`SingleFileWatcher` object per path, all objects monitoring
		`path` will not receive notifications anymore.
		To let only one object stop monitoring the file, simply disconnect its `modified` signal.
		When the :any:`SingleFileWatcher` object returned by method :any:`monitorFile` is
		destroyed, the file is automatically un-monitored.
		"""
        path = os.path.abspath(path)

        self.watcher.removePath(path)
        self.watched.pop(path, None)

    @Slot(str)
    def _onFileChanged(self, path):
        proxy = self.watched.get(path)
        if proxy:
            proxy.modified.emit()
Exemplo n.º 6
0
	def _add_widgets(self,vbox,apps):
		row=int(len(self.appsWidgets)/self.maxCol)
		col=(self.maxCol*(row+1))-len(self.appsWidgets)
		sigmap_run=QSignalMapper(self)
		sigmap_run.mapped[QString].connect(self._launch)
		for appName,data in apps.items():
			appIcon=data['Icon']
			appDesc=data['Name']
			if QtGui.QIcon.hasThemeIcon(appIcon):
				icnApp=QtGui.QIcon.fromTheme(appIcon)
			elif os.path.isfile(appIcon):
					iconPixmap=QtGui.QPixmap(appIcon)
					scaledIcon=iconPixmap.scaled(QSize(BTN_SIZE*1.2,BTN_SIZE*1.2))
					icnApp=QtGui.QIcon(scaledIcon)
			elif appIcon.startswith("http"):
				if not os.path.isdir("%s/icons"%self.cache):
					os.makedirs("%s/icons"%self.cache)
				tmpfile=os.path.join("%s/icons"%self.cache,appIcon.split("/")[2].split(".")[0])
				if not os.path.isfile(tmpfile):
					try:
						urlretrieve(appIcon,tmpfile)
					except:
						tmpfile=QtGui.QIcon.fromTheme("shell")
				iconPixmap=QtGui.QPixmap(tmpfile)
				scaledIcon=iconPixmap.scaled(QSize(BTN_SIZE*1.2,BTN_SIZE*1.2))
				icnApp=QtGui.QIcon(scaledIcon)
				appIcon=tmpfile
			else:
				continue
			if not appName:
				continue
			self.app_icons[appName]=appIcon
			self._debug("Adding %s"%appName)
			btnApp=navButton(self)
			btnApp.setIcon(icnApp)
			btnApp.setIconSize(QSize(BTN_SIZE,BTN_SIZE))
			btnApp.setToolTip(appDesc)
			btnApp.setFocusPolicy(Qt.NoFocus)
			btnApp.keypress.connect(self._set_focus)
			btnApp.focusIn.connect(self._get_focus)
			self.focusWidgets.append(btnApp)
			self.appsWidgets.append(appName)
			sigmap_run.setMapping(btnApp,appName)
			btnApp.clicked.connect(sigmap_run.map)
			vbox.addWidget(btnApp,row,col,Qt.Alignment(-1))
			col+=1
			if col==self.maxCol:
				col=0
				row+=1
    def setup_board(self):
        gridLayout = QGridLayout()
        mapper = QSignalMapper(self)
        for row in range(3):
            for column in range(3):
                button = QPushButton()
                button.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
                button.setText(" ")
                gridLayout.addWidget(button, row, column)
                self.board.append(button)
                mapper.setMapping(button, len(self.board) - 1)
                button.clicked.connect(mapper.map)

        mapper.mapped.connect(self.handle_button_click)
        self.setLayout(gridLayout)
Exemplo n.º 8
0
	def onSelectionMenuClicked ( self ):
		signalMapper = QSignalMapper ( self )
		acts = self.tabSelectionMenu.actions ()
		activeAction = self.currentIndex ()

		for i in range ( 0, acts.size () ):
			self.tabSelectionMenu.removeAction ( acts[ i ] )

		for i in range ( 0, self.count () ):
			action = self.tabSelectionMenu.addAction ( self.tabText ( i ) )
			action.triggered.connect ( signalMapper.map )
			signalMapper.setMapping ( action, i )

		if activeAction >= 0:
			acts = self.tabSelectionMenu.actions ()
			acts[ activeAction ].setIcon ( QIcon ( "./resources/general_tick.ico" ) )  # TODO: CryIcon

		signalMapper.mapped.connect ( self.setCurrentIndex )
Exemplo n.º 9
0
 def __init__(self):
     super().__init__()
     grid = QGridLayout(self)
     mapper = QSignalMapper(self)
     btns = (('1', '2', '3', '+'),
             ('4', '5', '6', '-'),
             ('7', '8', '9', '*'),
             ('0', '.', '=', '/'),
             )
     self.line = QLineEdit()
     self.flag = False
     grid.addWidget(self.line, 0, 0, 1, 4)
     for row in range(4):
         for col in range(4):
             button = QPushButton(btns[row][col])
             grid.addWidget(button, row + 1, col)
             button.clicked.connect(mapper.map)
             mapper.setMapping(button, button.text())
     mapper.mapped[str].connect(self.on_mapped)
Exemplo n.º 10
0
    def contextMenu(self, parent, index):
        menu = None
        row = index.row()
        if (row >= 0 and row < self.mCommands.size()):
            menu = QMenu(parent)
            if (row > 0):
                action = menu.addAction(self.tr("Move Up"))
                mapper = QSignalMapper(action)
                mapper.setMapping(action, row)
                action.triggered.connect(mapper.map)
                mapper.mapped.connect(self.moveUp)

            if (row+1 < self.mCommands.size()):
                action = menu.addAction(self.tr("Move Down"))
                mapper = QSignalMapper(action)
                mapper.setMapping(action, row + 1)
                action.triggered.connect(mapper.map)
                mapper.mapped.connect(self.moveUp)

            menu.addSeparator()

            action = menu.addAction(self.tr("Execute"))
            mapper = QSignalMapper(action)
            mapper.setMapping(action, row)
            action.triggered.connect(mapper.map)
            mapper.mapped.connect(self.execute)

            if sys.platform in ['linux', 'darwin']:
                action = menu.addAction(self.tr("Execute in Terminal"))
                mapper = QSignalMapper(action)
                mapper.setMapping(action, row)
                action.triggered.connect(mapper.map)
                mapper.mapped.connect(self.executeInTerminal)

            menu.addSeparator()

            action = menu.addAction(self.tr("Delete"))
            mapper = QSignalMapper(action)
            mapper.setMapping(action, row)
            action.triggered.connect(mapper.map)
            mapper.mapped.connect(self.remove)

        return menu
Exemplo n.º 11
0
    def contextMenu(self, parent, index):
        menu = None
        row = index.row()
        if (row >= 0 and row < self.mCommands.size()):
            menu = QMenu(parent)
            if (row > 0):
                action = menu.addAction(self.tr("Move Up"))
                mapper = QSignalMapper(action)
                mapper.setMapping(action, row)
                action.triggered.connect(mapper.map)
                mapper.mapped.connect(self.moveUp)

            if (row + 1 < self.mCommands.size()):
                action = menu.addAction(self.tr("Move Down"))
                mapper = QSignalMapper(action)
                mapper.setMapping(action, row + 1)
                action.triggered.connect(mapper.map)
                mapper.mapped.connect(self.moveUp)

            menu.addSeparator()

            action = menu.addAction(self.tr("Execute"))
            mapper = QSignalMapper(action)
            mapper.setMapping(action, row)
            action.triggered.connect(mapper.map)
            mapper.mapped.connect(self.execute)

            if sys.platform in ['linux', 'darwin']:
                action = menu.addAction(self.tr("Execute in Terminal"))
                mapper = QSignalMapper(action)
                mapper.setMapping(action, row)
                action.triggered.connect(mapper.map)
                mapper.mapped.connect(self.executeInTerminal)

            menu.addSeparator()

            action = menu.addAction(self.tr("Delete"))
            mapper = QSignalMapper(action)
            mapper.setMapping(action, row)
            action.triggered.connect(mapper.map)
            mapper.mapped.connect(self.remove)

        return menu
Exemplo n.º 12
0
class MyWindow(QWidget):
    def __init__(self):
        super(MyWindow, self).__init__()
        self.setGeometry(100, 100, 600, 400)
        self.setWindowTitle("Send to Player")

        self.list = MyListWidget(self)
        self.uploader = Uploader(self.list, self)

        layout = QHBoxLayout(self)
        layout.addWidget(self.list)

        self.browserMapper = QSignalMapper()
        self.openSourceButton = QPushButton('&Open Music Archive')
        self.openSourceButton.released.connect(self.browserMapper.map)
        self.browserMapper.setMapping(self.openSourceButton,
                                      config.mediaSource)
        self.openTargetButton = QPushButton('Open &Player')
        self.openTargetButton.released.connect(self.browserMapper.map)
        self.browserMapper.setMapping(self.openTargetButton,
                                      config.mediaTarget)
        self.browserMapper.mapped[str].connect(
            lambda b: QDesktopServices.openUrl(QUrl(b)))

        self.submitButton = QPushButton('&Send to Player')
        self.submitButton.released.connect(self.uploader.uploadMedia)
        self.deleteSelectedButton = QPushButton('&Delete\nSelected')
        self.deleteSelectedButton.released.connect(self.list.deleteSelected)

        controls = QVBoxLayout()
        controls.addWidget(QLabel('Filesystem Browsers'))
        controls.addWidget(self.openSourceButton)
        controls.addWidget(self.openTargetButton)
        controls.addWidget(Separator(self))
        controls.addWidget(self.deleteSelectedButton)
        controls.addWidget(Separator(self))
        controls.addStretch()
        controls.addWidget(self.submitButton)

        layout.addLayout(controls)

        self.setLayout(layout)
Exemplo n.º 13
0
    def updatePlotPersoButton(self):
        menu = QMenu(self.mw)

        menus = []
        for i in [self.tr("Main"), self.tr("Secondary"), self.tr("Minor")]:
            m = QMenu(i, menu)
            menus.append(m)
            menu.addMenu(m)

        mpr = QSignalMapper(menu)
        for i in range(self.mw.mdlPersos.rowCount()):
            a = QAction(self.mw.mdlPersos.name(i), menu)
            a.setIcon(self.mw.mdlPersos.icon(i))
            a.triggered.connect(mpr.map)
            mpr.setMapping(a, int(self.mw.mdlPersos.ID(i)))

            imp = toInt(self.mw.mdlPersos.importance(i))

            menus[2 - imp].addAction(a)

        mpr.mapped.connect(self.addPlotPerso)
        self.mw.btnAddPlotPerso.setMenu(menu)
Exemplo n.º 14
0
    def __init__(self):
        QMainWindow.__init__(self)
        self.setMinimumSize(QSize(640, 480))
        self.setWindowTitle("Hello world")

        centralWidget = QWidget(self)
        self.setCentralWidget(centralWidget)
        gridLayout = QGridLayout()
        centralWidget.setLayout(gridLayout)
        title = QLabel("Hello World from PyQt", self)
        title.setAlignment(QtCore.Qt.AlignCenter)
        gridLayout.addWidget(title, 0, 0, 1, 10)

        logging_text_edit = QPlainTextEdit()
        gridLayout.addWidget(logging_text_edit, 2, 0, 1, 10)

        mapper = QSignalMapper(self)
        mapper.mapped[int].connect(self.log_button_pressed)
        for x in range(10):
            button = QPushButton(self)
            button.setText("Print %d" % (x,))
            gridLayout.addWidget(button, 3, x, QtCore.Qt.AlignHCenter)
            button.pressed.connect(mapper.map)
            mapper.setMapping(button, x)

        class QLogHandler(logging.Handler):
            def __init__(self):
                logging.Handler.__init__(self)

            def emit(self, record):
                record = self.format(record)
                logging_text_edit.appendPlainText(record)

        q_log_handler = QLogHandler()
        q_log_handler.setLevel(logging.DEBUG)
        q_log_handler.setFormatter(logging.Formatter('%(asctime)s\n%(name)s\n%(levelname)s:%(message)s\n'))
        logging.basicConfig(level=logging.DEBUG)
        logging.getLogger('').addHandler(q_log_handler)
Exemplo n.º 15
0
def main(icon_spec):
    app = QApplication(sys.argv)

    main_window = QMainWindow()

    def sigint_handler(*args):
        main_window.close()
    signal.signal(signal.SIGINT, sigint_handler)
    # the timer enables triggering the sigint_handler
    signal_timer = QTimer()
    signal_timer.start(100)
    signal_timer.timeout.connect(lambda: None)

    tool_bar = QToolBar()
    main_window.addToolBar(Qt.TopToolBarArea, tool_bar)

    table_view = QTableView()
    table_view.setSelectionBehavior(QAbstractItemView.SelectRows)
    table_view.setSelectionMode(QAbstractItemView.SingleSelection)
    table_view.setSortingEnabled(True)
    main_window.setCentralWidget(table_view)

    proxy_model = QSortFilterProxyModel()
    proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive)
    proxy_model.setFilterKeyColumn(1)
    table_view.setModel(proxy_model)
    proxy_model.layoutChanged.connect(table_view.resizeRowsToContents)

    item_model = QStandardItemModel()
    proxy_model.setSourceModel(item_model)

    # get all icons and their available sizes
    icons = []
    all_sizes = set([])
    for context, icon_names in icon_spec:
        for icon_name in icon_names:
            icon = QIcon.fromTheme(icon_name)
            sizes = []
            for size in icon.availableSizes():
                size = (size.width(), size.height())
                sizes.append(size)
                all_sizes.add(size)
            sizes.sort()
            icons.append({
                'context': context,
                'icon_name': icon_name,
                'icon': icon,
                'sizes': sizes,
            })
    all_sizes = list(all_sizes)
    all_sizes.sort()

    # input field for filter
    def filter_changed(value):
        proxy_model.setFilterRegExp(value)
        table_view.resizeRowsToContents()
    filter_line_edit = QLineEdit()
    filter_line_edit.setMaximumWidth(200)
    filter_line_edit.setPlaceholderText('Filter name')
    filter_line_edit.setToolTip('Filter name optionally using regular expressions (' + QKeySequence(QKeySequence.Find).toString() + ')')
    filter_line_edit.textChanged.connect(filter_changed)
    tool_bar.addWidget(filter_line_edit)

    # actions to toggle visibility of available sizes/columns 
    def action_toggled(index):
        column = 2 + index
        table_view.setColumnHidden(column, not table_view.isColumnHidden(column))
        table_view.resizeColumnsToContents()
        table_view.resizeRowsToContents()
    signal_mapper = QSignalMapper()
    for i, size in enumerate(all_sizes):
        action = QAction('%dx%d' % size, tool_bar)
        action.setCheckable(True)
        action.setChecked(True)
        tool_bar.addAction(action)
        action.toggled.connect(signal_mapper.map)
        signal_mapper.setMapping(action, i)
        # set tool tip and handle key sequence
        tool_tip = 'Toggle visibility of column'
        if i < 10:
            digit = ('%d' % (i + 1))[-1]
            tool_tip += ' (%s)' % QKeySequence('Ctrl+%s' % digit).toString()
        action.setToolTip(tool_tip)
    signal_mapper.mapped.connect(action_toggled)

    # label columns
    header_labels = ['context', 'name']
    for width, height in all_sizes:
        header_labels.append('%dx%d' % (width, height))
    item_model.setColumnCount(len(header_labels))
    item_model.setHorizontalHeaderLabels(header_labels)

    # fill rows
    item_model.setRowCount(len(icons))
    for row, icon_data in enumerate(icons):
        # context
        item = QStandardItem(icon_data['context'])
        item.setFlags(item.flags() ^ Qt.ItemIsEditable)
        item_model.setItem(row, 0, item)
        # icon name
        item = QStandardItem(icon_data['icon_name'])
        item.setFlags(item.flags() ^ Qt.ItemIsEditable)
        item_model.setItem(row, 1, item)
        for index_in_all_sizes, size in enumerate(all_sizes):
            column = 2 + index_in_all_sizes
            if size in icon_data['sizes']:
                # icon as pixmap to keep specific size
                item = QStandardItem('')
                pixmap = icon_data['icon'].pixmap(size[0], size[1])
                item.setData(pixmap, Qt.DecorationRole)
                item.setFlags(item.flags() ^ Qt.ItemIsEditable)
                item_model.setItem(row, column, item)
            else:
                # single space to be sortable against icons
                item = QStandardItem(' ')
                item.setFlags(item.flags() ^ Qt.ItemIsEditable)
                item_model.setItem(row, column, item)

    table_view.resizeColumnsToContents()
    # manually set row heights because resizeRowsToContents is not working properly
    for row, icon_data in enumerate(icons):
        if len(icon_data['sizes']) > 0:
            max_size = icon_data['sizes'][-1]
            table_view.setRowHeight(row, max_size[1])

    # enable focus find (ctrl+f) and toggle columns (ctrl+NUM)
    def main_window_keyPressEvent(self, event, old_keyPressEvent=QMainWindow.keyPressEvent):
        if event.matches(QKeySequence.Find):
            filter_line_edit.setFocus()
            return
        if event.modifiers() == Qt.ControlModifier and event.key() >= Qt.Key_0 and event.key() <= Qt.Key_9:
            index = event.key() - Qt.Key_1
            if event.key() == Qt.Key_0:
                index += 10
            action = signal_mapper.mapping(index)
            if action:
                action.toggle()
                return
        old_keyPressEvent(self, event)
    main_window.keyPressEvent = MethodType(main_window_keyPressEvent, table_view)

    # enable copy (ctrl+c) name of icon to clipboard
    def table_view_keyPressEvent(self, event, old_keyPressEvent=QTableView.keyPressEvent):
        if event.matches(QKeySequence.Copy):
            selection_model = self.selectionModel()
            if selection_model.hasSelection():
                index = selection_model.selectedRows()[0]
                source_index = self.model().mapToSource(index)
                item = self.model().sourceModel().item(source_index.row(), 1)
                icon_name = item.data(Qt.EditRole)
                app.clipboard().setText(icon_name.toString())
                return
        old_keyPressEvent(self, event)
    table_view.keyPressEvent = MethodType(table_view_keyPressEvent, table_view)

    main_window.showMaximized()
    return app.exec_()
Exemplo n.º 16
0
class UMLGraphicsView(E5GraphicsView):
    """
    Class implementing a specialized E5GraphicsView for our diagrams.
    
    @signal relayout() emitted to indicate a relayout of the diagram
        is requested
    """
    relayout = pyqtSignal()
    
    def __init__(self, scene, parent=None):
        """
        Constructor
        
        @param scene reference to the scene object (QGraphicsScene)
        @param parent parent widget of the view (QWidget)
        """
        E5GraphicsView.__init__(self, scene, parent)
        self.setObjectName("UMLGraphicsView")
        self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
        
        self.diagramName = "Unnamed"
        self.__itemId = -1
        
        self.border = 10
        self.deltaSize = 100.0
        
        self.__zoomWidget = E5ZoomWidget(
            UI.PixmapCache.getPixmap("zoomOut.png"),
            UI.PixmapCache.getPixmap("zoomIn.png"),
            UI.PixmapCache.getPixmap("zoomReset.png"), self)
        parent.statusBar().addPermanentWidget(self.__zoomWidget)
        self.__zoomWidget.setMapping(
            E5GraphicsView.ZoomLevels, E5GraphicsView.ZoomLevelDefault)
        self.__zoomWidget.valueChanged.connect(self.setZoom)
        self.zoomValueChanged.connect(self.__zoomWidget.setValue)
        
        self.__initActions()
        
        scene.changed.connect(self.__sceneChanged)
        
        self.grabGesture(Qt.PinchGesture)
        
    def __initActions(self):
        """
        Private method to initialize the view actions.
        """
        self.alignMapper = QSignalMapper(self)
        self.alignMapper.mapped[int].connect(self.__alignShapes)
        
        self.deleteShapeAct = \
            QAction(UI.PixmapCache.getIcon("deleteShape.png"),
                    self.tr("Delete shapes"), self)
        self.deleteShapeAct.triggered.connect(self.__deleteShape)
        
        self.incWidthAct = \
            QAction(UI.PixmapCache.getIcon("sceneWidthInc.png"),
                    self.tr("Increase width by {0} points").format(
                        self.deltaSize),
                    self)
        self.incWidthAct.triggered.connect(self.__incWidth)
        
        self.incHeightAct = \
            QAction(UI.PixmapCache.getIcon("sceneHeightInc.png"),
                    self.tr("Increase height by {0} points").format(
                        self.deltaSize),
                    self)
        self.incHeightAct.triggered.connect(self.__incHeight)
        
        self.decWidthAct = \
            QAction(UI.PixmapCache.getIcon("sceneWidthDec.png"),
                    self.tr("Decrease width by {0} points").format(
                        self.deltaSize),
                    self)
        self.decWidthAct.triggered.connect(self.__decWidth)
        
        self.decHeightAct = \
            QAction(UI.PixmapCache.getIcon("sceneHeightDec.png"),
                    self.tr("Decrease height by {0} points").format(
                        self.deltaSize),
                    self)
        self.decHeightAct.triggered.connect(self.__decHeight)
        
        self.setSizeAct = \
            QAction(UI.PixmapCache.getIcon("sceneSize.png"),
                    self.tr("Set size"), self)
        self.setSizeAct.triggered.connect(self.__setSize)
        
        self.rescanAct = \
            QAction(UI.PixmapCache.getIcon("rescan.png"),
                    self.tr("Re-Scan"), self)
        self.rescanAct.triggered.connect(self.__rescan)
        
        self.relayoutAct = \
            QAction(UI.PixmapCache.getIcon("relayout.png"),
                    self.tr("Re-Layout"), self)
        self.relayoutAct.triggered.connect(self.__relayout)
        
        self.alignLeftAct = \
            QAction(UI.PixmapCache.getIcon("shapesAlignLeft.png"),
                    self.tr("Align Left"), self)
        self.alignMapper.setMapping(self.alignLeftAct, Qt.AlignLeft)
        self.alignLeftAct.triggered.connect(self.alignMapper.map)
        
        self.alignHCenterAct = \
            QAction(UI.PixmapCache.getIcon("shapesAlignHCenter.png"),
                    self.tr("Align Center Horizontal"), self)
        self.alignMapper.setMapping(self.alignHCenterAct, Qt.AlignHCenter)
        self.alignHCenterAct.triggered.connect(self.alignMapper.map)
        
        self.alignRightAct = \
            QAction(UI.PixmapCache.getIcon("shapesAlignRight.png"),
                    self.tr("Align Right"), self)
        self.alignMapper.setMapping(self.alignRightAct, Qt.AlignRight)
        self.alignRightAct.triggered.connect(self.alignMapper.map)
        
        self.alignTopAct = \
            QAction(UI.PixmapCache.getIcon("shapesAlignTop.png"),
                    self.tr("Align Top"), self)
        self.alignMapper.setMapping(self.alignTopAct, Qt.AlignTop)
        self.alignTopAct.triggered.connect(self.alignMapper.map)
        
        self.alignVCenterAct = \
            QAction(UI.PixmapCache.getIcon("shapesAlignVCenter.png"),
                    self.tr("Align Center Vertical"), self)
        self.alignMapper.setMapping(self.alignVCenterAct, Qt.AlignVCenter)
        self.alignVCenterAct.triggered.connect(self.alignMapper.map)
        
        self.alignBottomAct = \
            QAction(UI.PixmapCache.getIcon("shapesAlignBottom.png"),
                    self.tr("Align Bottom"), self)
        self.alignMapper.setMapping(self.alignBottomAct, Qt.AlignBottom)
        self.alignBottomAct.triggered.connect(self.alignMapper.map)
        
    def __checkSizeActions(self):
        """
        Private slot to set the enabled state of the size actions.
        """
        diagramSize = self._getDiagramSize(10)
        sceneRect = self.scene().sceneRect()
        if (sceneRect.width() - self.deltaSize) < diagramSize.width():
            self.decWidthAct.setEnabled(False)
        else:
            self.decWidthAct.setEnabled(True)
        if (sceneRect.height() - self.deltaSize) < diagramSize.height():
            self.decHeightAct.setEnabled(False)
        else:
            self.decHeightAct.setEnabled(True)
        
    def __sceneChanged(self, areas):
        """
        Private slot called when the scene changes.
        
        @param areas list of rectangles that contain changes (list of QRectF)
        """
        if len(self.scene().selectedItems()) > 0:
            self.deleteShapeAct.setEnabled(True)
        else:
            self.deleteShapeAct.setEnabled(False)
        
        sceneRect = self.scene().sceneRect()
        newWidth = width = sceneRect.width()
        newHeight = height = sceneRect.height()
        rect = self.scene().itemsBoundingRect()
        # calculate with 10 pixel border on each side
        if sceneRect.right() - 10 < rect.right():
            newWidth = rect.right() + 10
        if sceneRect.bottom() - 10 < rect.bottom():
            newHeight = rect.bottom() + 10
        
        if newHeight != height or newWidth != width:
            self.setSceneSize(newWidth, newHeight)
            self.__checkSizeActions()
        
    def initToolBar(self):
        """
        Public method to populate a toolbar with our actions.
        
        @return the populated toolBar (QToolBar)
        """
        toolBar = QToolBar(self.tr("Graphics"), self)
        toolBar.setIconSize(UI.Config.ToolBarIconSize)
        toolBar.addAction(self.deleteShapeAct)
        toolBar.addSeparator()
        toolBar.addAction(self.alignLeftAct)
        toolBar.addAction(self.alignHCenterAct)
        toolBar.addAction(self.alignRightAct)
        toolBar.addAction(self.alignTopAct)
        toolBar.addAction(self.alignVCenterAct)
        toolBar.addAction(self.alignBottomAct)
        toolBar.addSeparator()
        toolBar.addAction(self.incWidthAct)
        toolBar.addAction(self.incHeightAct)
        toolBar.addAction(self.decWidthAct)
        toolBar.addAction(self.decHeightAct)
        toolBar.addAction(self.setSizeAct)
        toolBar.addSeparator()
        toolBar.addAction(self.rescanAct)
        toolBar.addAction(self.relayoutAct)
        
        return toolBar
        
    def filteredItems(self, items, itemType=UMLItem):
        """
        Public method to filter a list of items.
        
        @param items list of items as returned by the scene object
            (QGraphicsItem)
        @param itemType type to be filtered (class)
        @return list of interesting collision items (QGraphicsItem)
        """
        return [itm for itm in items if isinstance(itm, itemType)]
        
    def selectItems(self, items):
        """
        Public method to select the given items.
        
        @param items list of items to be selected (list of QGraphicsItemItem)
        """
        # step 1: deselect all items
        self.unselectItems()
        
        # step 2: select all given items
        for itm in items:
            if isinstance(itm, UMLItem):
                itm.setSelected(True)
        
    def selectItem(self, item):
        """
        Public method to select an item.
        
        @param item item to be selected (QGraphicsItemItem)
        """
        if isinstance(item, UMLItem):
            item.setSelected(not item.isSelected())
        
    def __deleteShape(self):
        """
        Private method to delete the selected shapes from the display.
        """
        for item in self.scene().selectedItems():
            item.removeAssociations()
            item.setSelected(False)
            self.scene().removeItem(item)
            del item
        
    def __incWidth(self):
        """
        Private method to handle the increase width context menu entry.
        """
        self.resizeScene(self.deltaSize, True)
        self.__checkSizeActions()
        
    def __incHeight(self):
        """
        Private method to handle the increase height context menu entry.
        """
        self.resizeScene(self.deltaSize, False)
        self.__checkSizeActions()
        
    def __decWidth(self):
        """
        Private method to handle the decrease width context menu entry.
        """
        self.resizeScene(-self.deltaSize, True)
        self.__checkSizeActions()
        
    def __decHeight(self):
        """
        Private method to handle the decrease height context menu entry.
        """
        self.resizeScene(-self.deltaSize, False)
        self.__checkSizeActions()
        
    def __setSize(self):
        """
        Private method to handle the set size context menu entry.
        """
        from .UMLSceneSizeDialog import UMLSceneSizeDialog
        rect = self._getDiagramRect(10)
        sceneRect = self.scene().sceneRect()
        dlg = UMLSceneSizeDialog(sceneRect.width(), sceneRect.height(),
                                 rect.width(), rect.height(), self)
        if dlg.exec_() == QDialog.Accepted:
            width, height = dlg.getData()
            self.setSceneSize(width, height)
        self.__checkSizeActions()
        
    def autoAdjustSceneSize(self, limit=False):
        """
        Public method to adjust the scene size to the diagram size.
        
        @param limit flag indicating to limit the scene to the
            initial size (boolean)
        """
        super(UMLGraphicsView, self).autoAdjustSceneSize(limit=limit)
        self.__checkSizeActions()
        
    def saveImage(self):
        """
        Public method to handle the save context menu entry.
        """
        fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
            self,
            self.tr("Save Diagram"),
            "",
            self.tr("Portable Network Graphics (*.png);;"
                    "Scalable Vector Graphics (*.svg)"),
            "",
            E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
        if fname:
            ext = QFileInfo(fname).suffix()
            if not ext:
                ex = selectedFilter.split("(*")[1].split(")")[0]
                if ex:
                    fname += ex
            if QFileInfo(fname).exists():
                res = E5MessageBox.yesNo(
                    self,
                    self.tr("Save Diagram"),
                    self.tr("<p>The file <b>{0}</b> already exists."
                            " Overwrite it?</p>").format(fname),
                    icon=E5MessageBox.Warning)
                if not res:
                    return
            
            success = super(UMLGraphicsView, self).saveImage(
                fname, QFileInfo(fname).suffix().upper())
            if not success:
                E5MessageBox.critical(
                    self,
                    self.tr("Save Diagram"),
                    self.tr(
                        """<p>The file <b>{0}</b> could not be saved.</p>""")
                    .format(fname))
        
    def __relayout(self):
        """
        Private slot to handle the re-layout context menu entry.
        """
        self.__itemId = -1
        self.scene().clear()
        self.relayout.emit()
        
    def __rescan(self):
        """
        Private slot to handle the re-scan context menu entry.
        """
        # 1. save positions of all items and names of selected items
        itemPositions = {}
        selectedItems = []
        for item in self.filteredItems(self.scene().items(), UMLItem):
            name = item.getName()
            if name:
                itemPositions[name] = (item.x(), item.y())
                if item.isSelected():
                    selectedItems.append(name)
        
        # 2. save
        
        # 2. re-layout the diagram
        self.__relayout()
        
        # 3. move known items to the saved positions
        for item in self.filteredItems(self.scene().items(), UMLItem):
            name = item.getName()
            if name in itemPositions:
                item.setPos(*itemPositions[name])
            if name in selectedItems:
                item.setSelected(True)
        
    def printDiagram(self):
        """
        Public slot called to print the diagram.
        """
        printer = QPrinter(mode=QPrinter.ScreenResolution)
        printer.setFullPage(True)
        if Preferences.getPrinter("ColorMode"):
            printer.setColorMode(QPrinter.Color)
        else:
            printer.setColorMode(QPrinter.GrayScale)
        if Preferences.getPrinter("FirstPageFirst"):
            printer.setPageOrder(QPrinter.FirstPageFirst)
        else:
            printer.setPageOrder(QPrinter.LastPageFirst)
        printer.setPageMargins(
            Preferences.getPrinter("LeftMargin") * 10,
            Preferences.getPrinter("TopMargin") * 10,
            Preferences.getPrinter("RightMargin") * 10,
            Preferences.getPrinter("BottomMargin") * 10,
            QPrinter.Millimeter
        )
        printerName = Preferences.getPrinter("PrinterName")
        if printerName:
            printer.setPrinterName(printerName)
        
        printDialog = QPrintDialog(printer, self)
        if printDialog.exec_():
            super(UMLGraphicsView, self).printDiagram(
                printer, self.diagramName)
        
    def printPreviewDiagram(self):
        """
        Public slot called to show a print preview of the diagram.
        """
        from PyQt5.QtPrintSupport import QPrintPreviewDialog
        
        printer = QPrinter(mode=QPrinter.ScreenResolution)
        printer.setFullPage(True)
        if Preferences.getPrinter("ColorMode"):
            printer.setColorMode(QPrinter.Color)
        else:
            printer.setColorMode(QPrinter.GrayScale)
        if Preferences.getPrinter("FirstPageFirst"):
            printer.setPageOrder(QPrinter.FirstPageFirst)
        else:
            printer.setPageOrder(QPrinter.LastPageFirst)
        printer.setPageMargins(
            Preferences.getPrinter("LeftMargin") * 10,
            Preferences.getPrinter("TopMargin") * 10,
            Preferences.getPrinter("RightMargin") * 10,
            Preferences.getPrinter("BottomMargin") * 10,
            QPrinter.Millimeter
        )
        printerName = Preferences.getPrinter("PrinterName")
        if printerName:
            printer.setPrinterName(printerName)
        
        preview = QPrintPreviewDialog(printer, self)
        preview.paintRequested[QPrinter].connect(self.__printPreviewPrint)
        preview.exec_()
        
    def __printPreviewPrint(self, printer):
        """
        Private slot to generate a print preview.
        
        @param printer reference to the printer object (QPrinter)
        """
        super(UMLGraphicsView, self).printDiagram(printer, self.diagramName)
        
    def setDiagramName(self, name):
        """
        Public slot to set the diagram name.
        
        @param name diagram name (string)
        """
        self.diagramName = name
        
    def __alignShapes(self, alignment):
        """
        Private slot to align the selected shapes.
        
        @param alignment alignment type (Qt.AlignmentFlag)
        """
        # step 1: get all selected items
        items = self.scene().selectedItems()
        if len(items) <= 1:
            return
        
        # step 2: find the index of the item to align in relation to
        amount = None
        for i, item in enumerate(items):
            rect = item.sceneBoundingRect()
            if alignment == Qt.AlignLeft:
                if amount is None or rect.x() < amount:
                    amount = rect.x()
                    index = i
            elif alignment == Qt.AlignRight:
                if amount is None or rect.x() + rect.width() > amount:
                    amount = rect.x() + rect.width()
                    index = i
            elif alignment == Qt.AlignHCenter:
                if amount is None or rect.width() > amount:
                    amount = rect.width()
                    index = i
            elif alignment == Qt.AlignTop:
                if amount is None or rect.y() < amount:
                    amount = rect.y()
                    index = i
            elif alignment == Qt.AlignBottom:
                if amount is None or rect.y() + rect.height() > amount:
                    amount = rect.y() + rect.height()
                    index = i
            elif alignment == Qt.AlignVCenter:
                if amount is None or rect.height() > amount:
                    amount = rect.height()
                    index = i
        rect = items[index].sceneBoundingRect()
        
        # step 3: move the other items
        for i, item in enumerate(items):
            if i == index:
                continue
            itemrect = item.sceneBoundingRect()
            xOffset = yOffset = 0
            if alignment == Qt.AlignLeft:
                xOffset = rect.x() - itemrect.x()
            elif alignment == Qt.AlignRight:
                xOffset = (rect.x() + rect.width()) - \
                          (itemrect.x() + itemrect.width())
            elif alignment == Qt.AlignHCenter:
                xOffset = (rect.x() + rect.width() // 2) - \
                          (itemrect.x() + itemrect.width() // 2)
            elif alignment == Qt.AlignTop:
                yOffset = rect.y() - itemrect.y()
            elif alignment == Qt.AlignBottom:
                yOffset = (rect.y() + rect.height()) - \
                          (itemrect.y() + itemrect.height())
            elif alignment == Qt.AlignVCenter:
                yOffset = (rect.y() + rect.height() // 2) - \
                          (itemrect.y() + itemrect.height() // 2)
            item.moveBy(xOffset, yOffset)
        
        self.scene().update()
    
    def __itemsBoundingRect(self, items):
        """
        Private method to calculate the bounding rectangle of the given items.
        
        @param items list of items to operate on (list of UMLItem)
        @return bounding rectangle (QRectF)
        """
        rect = self.scene().sceneRect()
        right = rect.left()
        bottom = rect.top()
        left = rect.right()
        top = rect.bottom()
        for item in items:
            rect = item.sceneBoundingRect()
            left = min(rect.left(), left)
            right = max(rect.right(), right)
            top = min(rect.top(), top)
            bottom = max(rect.bottom(), bottom)
        return QRectF(left, top, right - left, bottom - top)
    
    def keyPressEvent(self, evt):
        """
        Protected method handling key press events.
        
        @param evt reference to the key event (QKeyEvent)
        """
        key = evt.key()
        if key in [Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right]:
            items = self.filteredItems(self.scene().selectedItems())
            if items:
                if evt.modifiers() & Qt.ControlModifier:
                    stepSize = 50
                else:
                    stepSize = 5
                if key == Qt.Key_Up:
                    dx = 0
                    dy = -stepSize
                elif key == Qt.Key_Down:
                    dx = 0
                    dy = stepSize
                elif key == Qt.Key_Left:
                    dx = -stepSize
                    dy = 0
                else:
                    dx = stepSize
                    dy = 0
                for item in items:
                    item.moveBy(dx, dy)
                evt.accept()
                return
        
        super(UMLGraphicsView, self).keyPressEvent(evt)
    
    def wheelEvent(self, evt):
        """
        Protected method to handle wheel events.
        
        @param evt reference to the wheel event (QWheelEvent)
        """
        if evt.modifiers() & Qt.ControlModifier:
            if qVersion() >= "5.0.0":
                delta = evt.angleDelta().y()
            else:
                delta = evt.delta()
            if delta < 0:
                self.zoomOut()
            else:
                self.zoomIn()
            evt.accept()
            return
        
        super(UMLGraphicsView, self).wheelEvent(evt)
    
    def event(self, evt):
        """
        Public method handling events.
        
        @param evt reference to the event (QEvent)
        @return flag indicating, if the event was handled (boolean)
        """
        if evt.type() == QEvent.Gesture:
            self.gestureEvent(evt)
            return True
        
        return super(UMLGraphicsView, self).event(evt)
    
    def gestureEvent(self, evt):
        """
        Protected method handling gesture events.
        
        @param evt reference to the gesture event (QGestureEvent
        """
        pinch = evt.gesture(Qt.PinchGesture)
        if pinch:
            if pinch.state() == Qt.GestureStarted:
                pinch.setScaleFactor(self.zoom() / 100.0)
            else:
                self.setZoom(int(pinch.scaleFactor() * 100))
            evt.accept()
    
    def getItemId(self):
        """
        Public method to get the ID to be assigned to an item.
        
        @return item ID (integer)
        """
        self.__itemId += 1
        return self.__itemId

    def findItem(self, id):
        """
        Public method to find an UML item based on the ID.
        
        @param id of the item to search for (integer)
        @return item found (UMLItem) or None
        """
        for item in self.scene().items():
            try:
                if item.getId() == id:
                    return item
            except AttributeError:
                continue
        
        return None
    
    def findItemByName(self, name):
        """
        Public method to find an UML item based on its name.
        
        @param name name to look for (string)
        @return item found (UMLItem) or None
        """
        for item in self.scene().items():
            try:
                if item.getName() == name:
                    return item
            except AttributeError:
                continue
        
        return None
    
    def getPersistenceData(self):
        """
        Public method to get a list of data to be persisted.
        
        @return list of data to be persisted (list of strings)
        """
        lines = [
            "diagram_name: {0}".format(self.diagramName),
        ]
        
        for item in self.filteredItems(self.scene().items(), UMLItem):
            lines.append("item: id={0}, x={1}, y={2}, item_type={3}{4}".format(
                item.getId(), item.x(), item.y(), item.getItemType(),
                item.buildItemDataString()))
        
        from .AssociationItem import AssociationItem
        for item in self.filteredItems(self.scene().items(), AssociationItem):
            lines.append("association: {0}".format(
                item.buildAssociationItemDataString()))
        
        return lines
    
    def parsePersistenceData(self, version, data):
        """
        Public method to parse persisted data.
        
        @param version version of the data (string)
        @param data persisted data to be parsed (list of string)
        @return tuple of flag indicating success (boolean) and faulty line
            number (integer)
        """
        umlItems = {}
        
        if not data[0].startswith("diagram_name:"):
            return False, 0
        self.diagramName = data[0].split(": ", 1)[1].strip()
        
        from .ClassItem import ClassItem
        from .ModuleItem import ModuleItem
        from .PackageItem import PackageItem
        from .AssociationItem import AssociationItem
        
        linenum = 0
        for line in data[1:]:
            linenum += 1
            if not line.startswith(("item:", "association:")):
                return False, linenum
            
            key, value = line.split(": ", 1)
            if key == "item":
                id, x, y, itemType, itemData = value.split(", ", 4)
                try:
                    id = int(id.split("=", 1)[1].strip())
                    x = float(x.split("=", 1)[1].strip())
                    y = float(y.split("=", 1)[1].strip())
                    itemType = itemType.split("=", 1)[1].strip()
                    if itemType == ClassItem.ItemType:
                        itm = ClassItem(x=x, y=y, scene=self.scene())
                    elif itemType == ModuleItem.ItemType:
                        itm = ModuleItem(x=x, y=y, scene=self.scene())
                    elif itemType == PackageItem.ItemType:
                        itm = PackageItem(x=x, y=y, scene=self.scene())
                    itm.setId(id)
                    umlItems[id] = itm
                    if not itm.parseItemDataString(version, itemData):
                        return False, linenum
                except ValueError:
                    return False, linenum
            elif key == "association":
                srcId, dstId, assocType, topToBottom = \
                    AssociationItem.parseAssociationItemDataString(
                        value.strip())
                assoc = AssociationItem(umlItems[srcId], umlItems[dstId],
                                        assocType, topToBottom)
                self.scene().addItem(assoc)
        
        return True, -1
Exemplo n.º 17
0
class MainWindow(QMainWindow):
    """This create the main window of the application"""
    def __init__(self):
        super(MainWindow, self).__init__()
        # remove close & maximize window buttons
        #self.setWindowFlags(Qt.CustomizeWindowHint|Qt.WindowMinimizeButtonHint)
        self.setMinimumSize(500, 666)
        #self.setMaximumSize(1000,666)
        self.mdiArea = QMdiArea()
        self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.setCentralWidget(self.mdiArea)

        self.mdiArea.subWindowActivated.connect(self.updateMenus)
        self.mdiArea.setViewMode(QMdiArea.TabbedView)

        self.windowMapper = QSignalMapper(self)
        self.windowMapper.mapped[QWidget].connect(self.setActiveSubWindow)

        self.child = None

        self.createActions()
        self.createMenus()
        self.createStatusBar()
        self.updateMenus()
        self.readSettings()
        self.setWindowTitle("LEKTURE")

        mytoolbar = QToolBar()
        #self.toolbar = self.addToolBar()
        mytoolbar.addAction(self.newAct)
        mytoolbar.addAction(self.openAct)
        mytoolbar.addAction(self.saveAct)
        mytoolbar.addAction(self.saveAsAct)
        mytoolbar.addSeparator()
        mytoolbar.addAction(self.outputsAct)
        mytoolbar.addAction(self.scenarioAct)
        self.scenarioAct.setVisible(False)
        mytoolbar.setMovable(False)
        mytoolbar.setFixedWidth(60)
        self.addToolBar(Qt.LeftToolBarArea, mytoolbar)

    def closeEvent(self, scenario):
        """method called when the main window wants to be closed"""
        self.mdiArea.closeAllSubWindows()
        if self.mdiArea.currentSubWindow():
            scenario.ignore()
        else:
            self.writeSettings()
            scenario.accept()

    def newFile(self):
        """creates a new project"""
        child = self.createProjekt()
        child.newFile()
        child.show()
        self.child = child

    def open(self):
        """open a project"""
        fileName, _ = QFileDialog.getOpenFileName(self)
        if fileName:
            existing = self.findProjekt(fileName)
            if existing:
                self.mdiArea.setActiveSubWindow(existing)
                return

            child = self.createProjekt()
            if child.loadFile(fileName):
                self.statusBar().showMessage("File loaded", 2000)
                child.show()
            else:
                child.close()

    def save(self):
        """called when user save a project"""
        if self.activeProjekt() and self.activeProjekt().save():
            self.statusBar().showMessage("File saved", 2000)
        else:
            self.statusBar().showMessage("Error when trying to save the file")

    def saveAs(self):
        """called when user save AS a project"""
        if self.activeProjekt() and self.activeProjekt().saveAs():
            self.statusBar().showMessage("File saved", 2000)
        else:
            self.statusBar().showMessage("Error when trying to save the file")

    def openFolder(self):
        """called when user calls 'reveal in finder' function"""
        if self.activeProjekt() and self.activeProjekt().openFolder():
            self.statusBar().showMessage("File revealed in Finder", 2000)

    def about(self):
        """called when user wants to know a bit more on the app"""
        import sys
        python_version = str(sys.version_info[0])
        python_version_temp = sys.version_info[1:5]
        for item in python_version_temp:
            python_version = python_version + "." + str(item)
        QMessageBox.about(self, "About Lekture",
                            "pylekture build " + str(pylekture.__version__ + "\n" + \
                            "python version " + str(python_version)))

    def updateMenus(self):
        """update menus"""
        hasProjekt = (self.activeProjekt() is not None)
        self.saveAct.setEnabled(hasProjekt)
        self.saveAsAct.setEnabled(hasProjekt)
        self.outputsAct.setEnabled(hasProjekt)
        self.scenarioAct.setEnabled(hasProjekt)
        self.openFolderAct.setEnabled(hasProjekt)
        self.closeAct.setEnabled(hasProjekt)
        self.closeAllAct.setEnabled(hasProjekt)
        self.nextAct.setEnabled(hasProjekt)
        self.previousAct.setEnabled(hasProjekt)
        self.separatorAct.setVisible(hasProjekt)

    def updateWindowMenu(self):
        """unpates menus on the window toolbar"""
        self.windowMenu.clear()
        self.windowMenu.addAction(self.closeAct)
        self.windowMenu.addAction(self.closeAllAct)
        self.windowMenu.addSeparator()
        self.windowMenu.addAction(self.nextAct)
        self.windowMenu.addAction(self.previousAct)
        self.windowMenu.addAction(self.separatorAct)

        windows = self.mdiArea.subWindowList()
        self.separatorAct.setVisible(len(windows) != 0)

        for i, window in enumerate(windows):
            child = window.widget()

            text = "%d %s" % (i + 1, child.userFriendlyCurrentFile())
            if i < 9:
                text = '&' + text

            action = self.windowMenu.addAction(text)
            action.setCheckable(True)
            action.setChecked(child is self.activeProjekt())
            action.triggered.connect(self.windowMapper.map)
            self.windowMapper.setMapping(action, window)

    def createProjekt(self):
        """create a new project"""
        child = Projekt()
        self.mdiArea.addSubWindow(child)
        self.child = child
        return child

    def createActions(self):
        """create all actions"""
        self.newAct = QAction("&New", self,
                              shortcut=QKeySequence.New, statusTip="Create a new file",
                              triggered=self.newFile)

        self.openAct = QAction("&Open...", self,
                               shortcut=QKeySequence.Open, statusTip="Open an existing file",
                               triggered=self.open)

        self.saveAct = QAction("&Save", self,
                               shortcut=QKeySequence.Save,
                               statusTip="Save the document to disk", triggered=self.save)

        self.saveAsAct = QAction("Save &As...", self,
                                 shortcut=QKeySequence.SaveAs,
                                 statusTip="Save the document under a new name",
                                 triggered=self.saveAs)

        self.openFolderAct = QAction("Open Project Folder", self,
                                     statusTip="Reveal Project in Finder",
                                     triggered=self.openFolder)

        self.exitAct = QAction("E&xit", self, shortcut=QKeySequence.Quit,
                               statusTip="Exit the application",
                               triggered=QApplication.instance().closeAllWindows)

        self.closeAct = QAction("Cl&ose", self,
                                statusTip="Close the active window",
                                triggered=self.mdiArea.closeActiveSubWindow)

        self.outputsAct = QAction("Outputs", self,
                                  statusTip="Open the outputs panel",
                                  triggered=self.openOutputsPanel)

        self.scenarioAct = QAction("Scenario", self,
                                   statusTip="Open the scenario panel",
                                   triggered=self.openScenarioPanel)

        self.closeAllAct = QAction("Close &All", self,
                                   statusTip="Close all the windows",
                                   triggered=self.mdiArea.closeAllSubWindows)

        self.nextAct = QAction("Ne&xt", self, shortcut=QKeySequence.NextChild,
                               statusTip="Move the focus to the next window",
                               triggered=self.mdiArea.activateNextSubWindow)

        self.previousAct = QAction("Pre&vious", self,
                                   shortcut=QKeySequence.PreviousChild,
                                   statusTip="Move the focus to the previous window",
                                   triggered=self.mdiArea.activatePreviousSubWindow)

        self.separatorAct = QAction(self)
        self.separatorAct.setSeparator(True)

        self.aboutAct = QAction("&About", self,
                                statusTip="Show the application's About box",
                                triggered=self.about)

    def createMenus(self):
        """create all menus"""
        self.fileMenu = self.menuBar().addMenu("&File")
        self.fileMenu.addAction(self.newAct)
        self.fileMenu.addAction(self.openAct)
        self.fileMenu.addAction(self.saveAct)
        self.fileMenu.addAction(self.saveAsAct)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.openFolderAct)
        self.fileMenu.addAction(self.exitAct)

        self.viewMenu = self.menuBar().addMenu("&View")
        self.viewMenu.addAction(self.outputsAct)
        self.viewMenu.addAction(self.scenarioAct)

        self.windowMenu = self.menuBar().addMenu("&Window")
        self.updateWindowMenu()
        self.windowMenu.aboutToShow.connect(self.updateWindowMenu)

        self.menuBar().addSeparator()

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

    def createStatusBar(self):
        """create the status bar"""
        self.statusBar().showMessage("Ready")

    def readSettings(self):
        """read the settings"""
        settings = QSettings('Pixel Stereo', 'lekture')
        pos = settings.value('pos', QPoint(200, 200))
        size = settings.value('size', QSize(1000, 650))
        self.move(pos)
        self.resize(size)

    def writeSettings(self):
        """write settings"""
        settings = QSettings('Pixel Stereo', 'lekture')
        settings.setValue('pos', self.pos())
        settings.setValue('size', self.size())

    def activeProjekt(self):
        """return the active project object"""
        activeSubWindow = self.mdiArea.activeSubWindow()
        if activeSubWindow:
            return activeSubWindow.widget()
        else:
            return None

    def findProjekt(self, fileName):
        """return the project"""
        canonicalFilePath = QFileInfo(fileName).canonicalFilePath()

        for window in self.mdiArea.subWindowList():
            if window.widget().currentFile() == canonicalFilePath:
                return window
        return None

    def setActiveSubWindow(self, window):
        """set the active sub window"""
        if window:
            self.mdiArea.setActiveSubWindow(window)

    def openOutputsPanel(self):
        """switch to the outputs editor"""
        if self.child:
            project = self.activeProjekt()
            project.scenario_events_group.setVisible(False)
            project.outputs_group.setVisible(True)
            self.scenarioAct.setVisible(True)
            self.outputsAct.setVisible(False)

    def openScenarioPanel(self):
        """switch to the scenario editors"""
        if self.child:
            project = self.activeProjekt()
            project.outputs_group.setVisible(False)
            project.scenario_events_group.setVisible(True)
            self.scenarioAct.setVisible(False)
            self.outputsAct.setVisible(True)
Exemplo n.º 18
0
class UMLGraphicsView(E5GraphicsView):
    """
    Class implementing a specialized E5GraphicsView for our diagrams.
    
    @signal relayout() emitted to indicate a relayout of the diagram
        is requested
    """
    relayout = pyqtSignal()

    def __init__(self, scene, parent=None):
        """
        Constructor
        
        @param scene reference to the scene object (QGraphicsScene)
        @param parent parent widget of the view (QWidget)
        """
        E5GraphicsView.__init__(self, scene, parent)
        self.setObjectName("UMLGraphicsView")
        self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)

        self.diagramName = "Unnamed"
        self.__itemId = -1

        self.border = 10
        self.deltaSize = 100.0

        self.__zoomWidget = E5ZoomWidget(
            UI.PixmapCache.getPixmap("zoomOut.png"),
            UI.PixmapCache.getPixmap("zoomIn.png"),
            UI.PixmapCache.getPixmap("zoomReset.png"), self)
        parent.statusBar().addPermanentWidget(self.__zoomWidget)
        self.__zoomWidget.setMapping(E5GraphicsView.ZoomLevels,
                                     E5GraphicsView.ZoomLevelDefault)
        self.__zoomWidget.valueChanged.connect(self.setZoom)
        self.zoomValueChanged.connect(self.__zoomWidget.setValue)

        self.__initActions()

        scene.changed.connect(self.__sceneChanged)

        self.grabGesture(Qt.PinchGesture)

    def __initActions(self):
        """
        Private method to initialize the view actions.
        """
        self.alignMapper = QSignalMapper(self)
        self.alignMapper.mapped[int].connect(self.__alignShapes)

        self.deleteShapeAct = \
            QAction(UI.PixmapCache.getIcon("deleteShape.png"),
                    self.tr("Delete shapes"), self)
        self.deleteShapeAct.triggered.connect(self.__deleteShape)

        self.incWidthAct = \
            QAction(UI.PixmapCache.getIcon("sceneWidthInc.png"),
                    self.tr("Increase width by {0} points").format(
                        self.deltaSize),
                    self)
        self.incWidthAct.triggered.connect(self.__incWidth)

        self.incHeightAct = \
            QAction(UI.PixmapCache.getIcon("sceneHeightInc.png"),
                    self.tr("Increase height by {0} points").format(
                        self.deltaSize),
                    self)
        self.incHeightAct.triggered.connect(self.__incHeight)

        self.decWidthAct = \
            QAction(UI.PixmapCache.getIcon("sceneWidthDec.png"),
                    self.tr("Decrease width by {0} points").format(
                        self.deltaSize),
                    self)
        self.decWidthAct.triggered.connect(self.__decWidth)

        self.decHeightAct = \
            QAction(UI.PixmapCache.getIcon("sceneHeightDec.png"),
                    self.tr("Decrease height by {0} points").format(
                        self.deltaSize),
                    self)
        self.decHeightAct.triggered.connect(self.__decHeight)

        self.setSizeAct = \
            QAction(UI.PixmapCache.getIcon("sceneSize.png"),
                    self.tr("Set size"), self)
        self.setSizeAct.triggered.connect(self.__setSize)

        self.rescanAct = \
            QAction(UI.PixmapCache.getIcon("rescan.png"),
                    self.tr("Re-Scan"), self)
        self.rescanAct.triggered.connect(self.__rescan)

        self.relayoutAct = \
            QAction(UI.PixmapCache.getIcon("relayout.png"),
                    self.tr("Re-Layout"), self)
        self.relayoutAct.triggered.connect(self.__relayout)

        self.alignLeftAct = \
            QAction(UI.PixmapCache.getIcon("shapesAlignLeft.png"),
                    self.tr("Align Left"), self)
        self.alignMapper.setMapping(self.alignLeftAct, Qt.AlignLeft)
        self.alignLeftAct.triggered.connect(self.alignMapper.map)

        self.alignHCenterAct = \
            QAction(UI.PixmapCache.getIcon("shapesAlignHCenter.png"),
                    self.tr("Align Center Horizontal"), self)
        self.alignMapper.setMapping(self.alignHCenterAct, Qt.AlignHCenter)
        self.alignHCenterAct.triggered.connect(self.alignMapper.map)

        self.alignRightAct = \
            QAction(UI.PixmapCache.getIcon("shapesAlignRight.png"),
                    self.tr("Align Right"), self)
        self.alignMapper.setMapping(self.alignRightAct, Qt.AlignRight)
        self.alignRightAct.triggered.connect(self.alignMapper.map)

        self.alignTopAct = \
            QAction(UI.PixmapCache.getIcon("shapesAlignTop.png"),
                    self.tr("Align Top"), self)
        self.alignMapper.setMapping(self.alignTopAct, Qt.AlignTop)
        self.alignTopAct.triggered.connect(self.alignMapper.map)

        self.alignVCenterAct = \
            QAction(UI.PixmapCache.getIcon("shapesAlignVCenter.png"),
                    self.tr("Align Center Vertical"), self)
        self.alignMapper.setMapping(self.alignVCenterAct, Qt.AlignVCenter)
        self.alignVCenterAct.triggered.connect(self.alignMapper.map)

        self.alignBottomAct = \
            QAction(UI.PixmapCache.getIcon("shapesAlignBottom.png"),
                    self.tr("Align Bottom"), self)
        self.alignMapper.setMapping(self.alignBottomAct, Qt.AlignBottom)
        self.alignBottomAct.triggered.connect(self.alignMapper.map)

    def __checkSizeActions(self):
        """
        Private slot to set the enabled state of the size actions.
        """
        diagramSize = self._getDiagramSize(10)
        sceneRect = self.scene().sceneRect()
        if (sceneRect.width() - self.deltaSize) < diagramSize.width():
            self.decWidthAct.setEnabled(False)
        else:
            self.decWidthAct.setEnabled(True)
        if (sceneRect.height() - self.deltaSize) < diagramSize.height():
            self.decHeightAct.setEnabled(False)
        else:
            self.decHeightAct.setEnabled(True)

    def __sceneChanged(self, areas):
        """
        Private slot called when the scene changes.
        
        @param areas list of rectangles that contain changes (list of QRectF)
        """
        if len(self.scene().selectedItems()) > 0:
            self.deleteShapeAct.setEnabled(True)
        else:
            self.deleteShapeAct.setEnabled(False)

        sceneRect = self.scene().sceneRect()
        newWidth = width = sceneRect.width()
        newHeight = height = sceneRect.height()
        rect = self.scene().itemsBoundingRect()
        # calculate with 10 pixel border on each side
        if sceneRect.right() - 10 < rect.right():
            newWidth = rect.right() + 10
        if sceneRect.bottom() - 10 < rect.bottom():
            newHeight = rect.bottom() + 10

        if newHeight != height or newWidth != width:
            self.setSceneSize(newWidth, newHeight)
            self.__checkSizeActions()

    def initToolBar(self):
        """
        Public method to populate a toolbar with our actions.
        
        @return the populated toolBar (QToolBar)
        """
        toolBar = QToolBar(self.tr("Graphics"), self)
        toolBar.setIconSize(UI.Config.ToolBarIconSize)
        toolBar.addAction(self.deleteShapeAct)
        toolBar.addSeparator()
        toolBar.addAction(self.alignLeftAct)
        toolBar.addAction(self.alignHCenterAct)
        toolBar.addAction(self.alignRightAct)
        toolBar.addAction(self.alignTopAct)
        toolBar.addAction(self.alignVCenterAct)
        toolBar.addAction(self.alignBottomAct)
        toolBar.addSeparator()
        toolBar.addAction(self.incWidthAct)
        toolBar.addAction(self.incHeightAct)
        toolBar.addAction(self.decWidthAct)
        toolBar.addAction(self.decHeightAct)
        toolBar.addAction(self.setSizeAct)
        toolBar.addSeparator()
        toolBar.addAction(self.rescanAct)
        toolBar.addAction(self.relayoutAct)

        return toolBar

    def filteredItems(self, items, itemType=UMLItem):
        """
        Public method to filter a list of items.
        
        @param items list of items as returned by the scene object
            (QGraphicsItem)
        @param itemType type to be filtered (class)
        @return list of interesting collision items (QGraphicsItem)
        """
        return [itm for itm in items if isinstance(itm, itemType)]

    def selectItems(self, items):
        """
        Public method to select the given items.
        
        @param items list of items to be selected (list of QGraphicsItemItem)
        """
        # step 1: deselect all items
        self.unselectItems()

        # step 2: select all given items
        for itm in items:
            if isinstance(itm, UMLItem):
                itm.setSelected(True)

    def selectItem(self, item):
        """
        Public method to select an item.
        
        @param item item to be selected (QGraphicsItemItem)
        """
        if isinstance(item, UMLItem):
            item.setSelected(not item.isSelected())

    def __deleteShape(self):
        """
        Private method to delete the selected shapes from the display.
        """
        for item in self.scene().selectedItems():
            item.removeAssociations()
            item.setSelected(False)
            self.scene().removeItem(item)
            del item

    def __incWidth(self):
        """
        Private method to handle the increase width context menu entry.
        """
        self.resizeScene(self.deltaSize, True)
        self.__checkSizeActions()

    def __incHeight(self):
        """
        Private method to handle the increase height context menu entry.
        """
        self.resizeScene(self.deltaSize, False)
        self.__checkSizeActions()

    def __decWidth(self):
        """
        Private method to handle the decrease width context menu entry.
        """
        self.resizeScene(-self.deltaSize, True)
        self.__checkSizeActions()

    def __decHeight(self):
        """
        Private method to handle the decrease height context menu entry.
        """
        self.resizeScene(-self.deltaSize, False)
        self.__checkSizeActions()

    def __setSize(self):
        """
        Private method to handle the set size context menu entry.
        """
        from .UMLSceneSizeDialog import UMLSceneSizeDialog
        rect = self._getDiagramRect(10)
        sceneRect = self.scene().sceneRect()
        dlg = UMLSceneSizeDialog(sceneRect.width(), sceneRect.height(),
                                 rect.width(), rect.height(), self)
        if dlg.exec_() == QDialog.Accepted:
            width, height = dlg.getData()
            self.setSceneSize(width, height)
        self.__checkSizeActions()

    def autoAdjustSceneSize(self, limit=False):
        """
        Public method to adjust the scene size to the diagram size.
        
        @param limit flag indicating to limit the scene to the
            initial size (boolean)
        """
        super(UMLGraphicsView, self).autoAdjustSceneSize(limit=limit)
        self.__checkSizeActions()

    def saveImage(self):
        """
        Public method to handle the save context menu entry.
        """
        fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
            self, self.tr("Save Diagram"), "",
            self.tr("Portable Network Graphics (*.png);;"
                    "Scalable Vector Graphics (*.svg)"), "",
            E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
        if fname:
            ext = QFileInfo(fname).suffix()
            if not ext:
                ex = selectedFilter.split("(*")[1].split(")")[0]
                if ex:
                    fname += ex
            if QFileInfo(fname).exists():
                res = E5MessageBox.yesNo(
                    self,
                    self.tr("Save Diagram"),
                    self.tr("<p>The file <b>{0}</b> already exists."
                            " Overwrite it?</p>").format(fname),
                    icon=E5MessageBox.Warning)
                if not res:
                    return

            success = super(UMLGraphicsView,
                            self).saveImage(fname,
                                            QFileInfo(fname).suffix().upper())
            if not success:
                E5MessageBox.critical(
                    self, self.tr("Save Diagram"),
                    self.tr(
                        """<p>The file <b>{0}</b> could not be saved.</p>""").
                    format(fname))

    def __relayout(self):
        """
        Private slot to handle the re-layout context menu entry.
        """
        self.__itemId = -1
        self.scene().clear()
        self.relayout.emit()

    def __rescan(self):
        """
        Private slot to handle the re-scan context menu entry.
        """
        # 1. save positions of all items and names of selected items
        itemPositions = {}
        selectedItems = []
        for item in self.filteredItems(self.scene().items(), UMLItem):
            name = item.getName()
            if name:
                itemPositions[name] = (item.x(), item.y())
                if item.isSelected():
                    selectedItems.append(name)

        # 2. save

        # 2. re-layout the diagram
        self.__relayout()

        # 3. move known items to the saved positions
        for item in self.filteredItems(self.scene().items(), UMLItem):
            name = item.getName()
            if name in itemPositions:
                item.setPos(*itemPositions[name])
            if name in selectedItems:
                item.setSelected(True)

    def printDiagram(self):
        """
        Public slot called to print the diagram.
        """
        printer = QPrinter(mode=QPrinter.ScreenResolution)
        printer.setFullPage(True)
        if Preferences.getPrinter("ColorMode"):
            printer.setColorMode(QPrinter.Color)
        else:
            printer.setColorMode(QPrinter.GrayScale)
        if Preferences.getPrinter("FirstPageFirst"):
            printer.setPageOrder(QPrinter.FirstPageFirst)
        else:
            printer.setPageOrder(QPrinter.LastPageFirst)
        printer.setPageMargins(
            Preferences.getPrinter("LeftMargin") * 10,
            Preferences.getPrinter("TopMargin") * 10,
            Preferences.getPrinter("RightMargin") * 10,
            Preferences.getPrinter("BottomMargin") * 10, QPrinter.Millimeter)
        printerName = Preferences.getPrinter("PrinterName")
        if printerName:
            printer.setPrinterName(printerName)

        printDialog = QPrintDialog(printer, self)
        if printDialog.exec_():
            super(UMLGraphicsView, self).printDiagram(printer,
                                                      self.diagramName)

    def printPreviewDiagram(self):
        """
        Public slot called to show a print preview of the diagram.
        """
        from PyQt5.QtPrintSupport import QPrintPreviewDialog

        printer = QPrinter(mode=QPrinter.ScreenResolution)
        printer.setFullPage(True)
        if Preferences.getPrinter("ColorMode"):
            printer.setColorMode(QPrinter.Color)
        else:
            printer.setColorMode(QPrinter.GrayScale)
        if Preferences.getPrinter("FirstPageFirst"):
            printer.setPageOrder(QPrinter.FirstPageFirst)
        else:
            printer.setPageOrder(QPrinter.LastPageFirst)
        printer.setPageMargins(
            Preferences.getPrinter("LeftMargin") * 10,
            Preferences.getPrinter("TopMargin") * 10,
            Preferences.getPrinter("RightMargin") * 10,
            Preferences.getPrinter("BottomMargin") * 10, QPrinter.Millimeter)
        printerName = Preferences.getPrinter("PrinterName")
        if printerName:
            printer.setPrinterName(printerName)

        preview = QPrintPreviewDialog(printer, self)
        preview.paintRequested[QPrinter].connect(self.__printPreviewPrint)
        preview.exec_()

    def __printPreviewPrint(self, printer):
        """
        Private slot to generate a print preview.
        
        @param printer reference to the printer object (QPrinter)
        """
        super(UMLGraphicsView, self).printDiagram(printer, self.diagramName)

    def setDiagramName(self, name):
        """
        Public slot to set the diagram name.
        
        @param name diagram name (string)
        """
        self.diagramName = name

    def __alignShapes(self, alignment):
        """
        Private slot to align the selected shapes.
        
        @param alignment alignment type (Qt.AlignmentFlag)
        """
        # step 1: get all selected items
        items = self.scene().selectedItems()
        if len(items) <= 1:
            return

        # step 2: find the index of the item to align in relation to
        amount = None
        for i, item in enumerate(items):
            rect = item.sceneBoundingRect()
            if alignment == Qt.AlignLeft:
                if amount is None or rect.x() < amount:
                    amount = rect.x()
                    index = i
            elif alignment == Qt.AlignRight:
                if amount is None or rect.x() + rect.width() > amount:
                    amount = rect.x() + rect.width()
                    index = i
            elif alignment == Qt.AlignHCenter:
                if amount is None or rect.width() > amount:
                    amount = rect.width()
                    index = i
            elif alignment == Qt.AlignTop:
                if amount is None or rect.y() < amount:
                    amount = rect.y()
                    index = i
            elif alignment == Qt.AlignBottom:
                if amount is None or rect.y() + rect.height() > amount:
                    amount = rect.y() + rect.height()
                    index = i
            elif alignment == Qt.AlignVCenter:
                if amount is None or rect.height() > amount:
                    amount = rect.height()
                    index = i
        rect = items[index].sceneBoundingRect()

        # step 3: move the other items
        for i, item in enumerate(items):
            if i == index:
                continue
            itemrect = item.sceneBoundingRect()
            xOffset = yOffset = 0
            if alignment == Qt.AlignLeft:
                xOffset = rect.x() - itemrect.x()
            elif alignment == Qt.AlignRight:
                xOffset = (rect.x() + rect.width()) - \
                          (itemrect.x() + itemrect.width())
            elif alignment == Qt.AlignHCenter:
                xOffset = (rect.x() + rect.width() // 2) - \
                          (itemrect.x() + itemrect.width() // 2)
            elif alignment == Qt.AlignTop:
                yOffset = rect.y() - itemrect.y()
            elif alignment == Qt.AlignBottom:
                yOffset = (rect.y() + rect.height()) - \
                          (itemrect.y() + itemrect.height())
            elif alignment == Qt.AlignVCenter:
                yOffset = (rect.y() + rect.height() // 2) - \
                          (itemrect.y() + itemrect.height() // 2)
            item.moveBy(xOffset, yOffset)

        self.scene().update()

    def __itemsBoundingRect(self, items):
        """
        Private method to calculate the bounding rectangle of the given items.
        
        @param items list of items to operate on (list of UMLItem)
        @return bounding rectangle (QRectF)
        """
        rect = self.scene().sceneRect()
        right = rect.left()
        bottom = rect.top()
        left = rect.right()
        top = rect.bottom()
        for item in items:
            rect = item.sceneBoundingRect()
            left = min(rect.left(), left)
            right = max(rect.right(), right)
            top = min(rect.top(), top)
            bottom = max(rect.bottom(), bottom)
        return QRectF(left, top, right - left, bottom - top)

    def keyPressEvent(self, evt):
        """
        Protected method handling key press events.
        
        @param evt reference to the key event (QKeyEvent)
        """
        key = evt.key()
        if key in [Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right]:
            items = self.filteredItems(self.scene().selectedItems())
            if items:
                if evt.modifiers() & Qt.ControlModifier:
                    stepSize = 50
                else:
                    stepSize = 5
                if key == Qt.Key_Up:
                    dx = 0
                    dy = -stepSize
                elif key == Qt.Key_Down:
                    dx = 0
                    dy = stepSize
                elif key == Qt.Key_Left:
                    dx = -stepSize
                    dy = 0
                else:
                    dx = stepSize
                    dy = 0
                for item in items:
                    item.moveBy(dx, dy)
                evt.accept()
                return

        super(UMLGraphicsView, self).keyPressEvent(evt)

    def wheelEvent(self, evt):
        """
        Protected method to handle wheel events.
        
        @param evt reference to the wheel event (QWheelEvent)
        """
        if evt.modifiers() & Qt.ControlModifier:
            if qVersion() >= "5.0.0":
                delta = evt.angleDelta().y()
            else:
                delta = evt.delta()
            if delta < 0:
                self.zoomOut()
            else:
                self.zoomIn()
            evt.accept()
            return

        super(UMLGraphicsView, self).wheelEvent(evt)

    def event(self, evt):
        """
        Public method handling events.
        
        @param evt reference to the event (QEvent)
        @return flag indicating, if the event was handled (boolean)
        """
        if evt.type() == QEvent.Gesture:
            self.gestureEvent(evt)
            return True

        return super(UMLGraphicsView, self).event(evt)

    def gestureEvent(self, evt):
        """
        Protected method handling gesture events.
        
        @param evt reference to the gesture event (QGestureEvent
        """
        pinch = evt.gesture(Qt.PinchGesture)
        if pinch:
            if pinch.state() == Qt.GestureStarted:
                pinch.setScaleFactor(self.zoom() / 100.0)
            else:
                self.setZoom(int(pinch.scaleFactor() * 100))
            evt.accept()

    def getItemId(self):
        """
        Public method to get the ID to be assigned to an item.
        
        @return item ID (integer)
        """
        self.__itemId += 1
        return self.__itemId

    def findItem(self, id):
        """
        Public method to find an UML item based on the ID.
        
        @param id of the item to search for (integer)
        @return item found (UMLItem) or None
        """
        for item in self.scene().items():
            try:
                if item.getId() == id:
                    return item
            except AttributeError:
                continue

        return None

    def findItemByName(self, name):
        """
        Public method to find an UML item based on its name.
        
        @param name name to look for (string)
        @return item found (UMLItem) or None
        """
        for item in self.scene().items():
            try:
                if item.getName() == name:
                    return item
            except AttributeError:
                continue

        return None

    def getPersistenceData(self):
        """
        Public method to get a list of data to be persisted.
        
        @return list of data to be persisted (list of strings)
        """
        lines = [
            "diagram_name: {0}".format(self.diagramName),
        ]

        for item in self.filteredItems(self.scene().items(), UMLItem):
            lines.append("item: id={0}, x={1}, y={2}, item_type={3}{4}".format(
                item.getId(), item.x(), item.y(), item.getItemType(),
                item.buildItemDataString()))

        from .AssociationItem import AssociationItem
        for item in self.filteredItems(self.scene().items(), AssociationItem):
            lines.append("association: {0}".format(
                item.buildAssociationItemDataString()))

        return lines

    def parsePersistenceData(self, version, data):
        """
        Public method to parse persisted data.
        
        @param version version of the data (string)
        @param data persisted data to be parsed (list of string)
        @return tuple of flag indicating success (boolean) and faulty line
            number (integer)
        """
        umlItems = {}

        if not data[0].startswith("diagram_name:"):
            return False, 0
        self.diagramName = data[0].split(": ", 1)[1].strip()

        from .ClassItem import ClassItem
        from .ModuleItem import ModuleItem
        from .PackageItem import PackageItem
        from .AssociationItem import AssociationItem

        linenum = 0
        for line in data[1:]:
            linenum += 1
            if not line.startswith(("item:", "association:")):
                return False, linenum

            key, value = line.split(": ", 1)
            if key == "item":
                id, x, y, itemType, itemData = value.split(", ", 4)
                try:
                    id = int(id.split("=", 1)[1].strip())
                    x = float(x.split("=", 1)[1].strip())
                    y = float(y.split("=", 1)[1].strip())
                    itemType = itemType.split("=", 1)[1].strip()
                    if itemType == ClassItem.ItemType:
                        itm = ClassItem(x=x, y=y, scene=self.scene())
                    elif itemType == ModuleItem.ItemType:
                        itm = ModuleItem(x=x, y=y, scene=self.scene())
                    elif itemType == PackageItem.ItemType:
                        itm = PackageItem(x=x, y=y, scene=self.scene())
                    itm.setId(id)
                    umlItems[id] = itm
                    if not itm.parseItemDataString(version, itemData):
                        return False, linenum
                except ValueError:
                    return False, linenum
            elif key == "association":
                srcId, dstId, assocType, topToBottom = \
                    AssociationItem.parseAssociationItemDataString(
                        value.strip())
                assoc = AssociationItem(umlItems[srcId], umlItems[dstId],
                                        assocType, topToBottom)
                self.scene().addItem(assoc)

        return True, -1
Exemplo n.º 19
0
    def makePopupMenu(self):
        index = self.currentIndex()
        sel = self.getSelection()
        clipboard = qApp.clipboard()

        menu = QMenu(self)

        # Add / remove items
        self.actOpen = QAction(QIcon.fromTheme("go-right"),
                               qApp.translate("outlineBasics", "Open Item"),
                               menu)
        self.actOpen.triggered.connect(self.openItem)
        menu.addAction(self.actOpen)

        menu.addSeparator()

        # Add / remove items
        self.actAddFolder = QAction(
            QIcon.fromTheme("folder-new"),
            qApp.translate("outlineBasics", "New Folder"), menu)
        self.actAddFolder.triggered.connect(self.addFolder)
        menu.addAction(self.actAddFolder)

        self.actAddText = QAction(QIcon.fromTheme("document-new"),
                                  qApp.translate("outlineBasics", "New Text"),
                                  menu)
        self.actAddText.triggered.connect(self.addText)
        menu.addAction(self.actAddText)

        self.actDelete = QAction(QIcon.fromTheme("edit-delete"),
                                 qApp.translate("outlineBasics", "Delete"),
                                 menu)
        self.actDelete.triggered.connect(self.delete)
        menu.addAction(self.actDelete)

        menu.addSeparator()

        # Copy, cut, paste
        self.actCopy = QAction(QIcon.fromTheme("edit-copy"),
                               qApp.translate("outlineBasics", "Copy"), menu)
        self.actCopy.triggered.connect(self.copy)
        menu.addAction(self.actCopy)

        self.actCut = QAction(QIcon.fromTheme("edit-cut"),
                              qApp.translate("outlineBasics", "Cut"), menu)
        self.actCut.triggered.connect(self.cut)
        menu.addAction(self.actCut)

        self.actPaste = QAction(QIcon.fromTheme("edit-paste"),
                                qApp.translate("outlineBasics", "Paste"), menu)
        self.actPaste.triggered.connect(self.paste)
        menu.addAction(self.actPaste)

        menu.addSeparator()

        # POV
        self.menuPOV = QMenu(qApp.translate("outlineBasics", "Set POV"), menu)
        mw = mainWindow()
        a = QAction(QIcon.fromTheme("dialog-no"),
                    qApp.translate("outlineBasics", "None"), self.menuPOV)
        a.triggered.connect(lambda: self.setPOV(""))
        self.menuPOV.addAction(a)
        self.menuPOV.addSeparator()

        menus = []
        for i in [
                qApp.translate("outlineBasics", "Main"),
                qApp.translate("outlineBasics", "Secondary"),
                qApp.translate("outlineBasics", "Minor")
        ]:
            m = QMenu(i, self.menuPOV)
            menus.append(m)
            self.menuPOV.addMenu(m)

        mpr = QSignalMapper(self.menuPOV)
        for i in range(mw.mdlCharacter.rowCount()):
            a = QAction(mw.mdlCharacter.icon(i), mw.mdlCharacter.name(i),
                        self.menuPOV)
            a.triggered.connect(mpr.map)
            mpr.setMapping(a, int(mw.mdlCharacter.ID(i)))

            imp = toInt(mw.mdlCharacter.importance(i))

            menus[2 - imp].addAction(a)

        mpr.mapped.connect(self.setPOV)
        menu.addMenu(self.menuPOV)

        # Status
        self.menuStatus = QMenu(qApp.translate("outlineBasics", "Set Status"),
                                menu)
        # a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuStatus)
        # a.triggered.connect(lambda: self.setStatus(""))
        # self.menuStatus.addAction(a)
        # self.menuStatus.addSeparator()

        mpr = QSignalMapper(self.menuStatus)
        for i in range(mw.mdlStatus.rowCount()):
            a = QAction(mw.mdlStatus.item(i, 0).text(), self.menuStatus)
            a.triggered.connect(mpr.map)
            mpr.setMapping(a, i)
            self.menuStatus.addAction(a)
        mpr.mapped.connect(self.setStatus)
        menu.addMenu(self.menuStatus)

        # Labels
        self.menuLabel = QMenu(qApp.translate("outlineBasics", "Set Label"),
                               menu)
        mpr = QSignalMapper(self.menuLabel)
        for i in range(mw.mdlLabels.rowCount()):
            a = QAction(
                mw.mdlLabels.item(i, 0).icon(),
                mw.mdlLabels.item(i, 0).text(), self.menuLabel)
            a.triggered.connect(mpr.map)
            mpr.setMapping(a, i)
            self.menuLabel.addAction(a)
        mpr.mapped.connect(self.setLabel)
        menu.addMenu(self.menuLabel)

        menu.addSeparator()

        # Custom icons
        self.menuCustomIcons = QMenu(
            qApp.translate("outlineBasics", "Set Custom Icon"), menu)
        a = QAction(qApp.translate("outlineBasics", "Restore to default"),
                    self.menuCustomIcons)
        a.triggered.connect(lambda: self.setCustomIcon(""))
        self.menuCustomIcons.addAction(a)
        self.menuCustomIcons.addSeparator()

        txt = QLineEdit()
        txt.textChanged.connect(self.filterLstIcons)
        txt.setPlaceholderText("Filter icons")
        txt.setStyleSheet("background: transparent; border: none;")
        act = QWidgetAction(self.menuCustomIcons)
        act.setDefaultWidget(txt)
        self.menuCustomIcons.addAction(act)

        self.lstIcons = QListWidget()
        for i in customIcons():
            item = QListWidgetItem()
            item.setIcon(QIcon.fromTheme(i))
            item.setData(Qt.UserRole, i)
            item.setToolTip(i)
            self.lstIcons.addItem(item)
        self.lstIcons.itemClicked.connect(self.setCustomIconFromItem)
        self.lstIcons.setViewMode(self.lstIcons.IconMode)
        self.lstIcons.setUniformItemSizes(True)
        self.lstIcons.setResizeMode(self.lstIcons.Adjust)
        self.lstIcons.setMovement(self.lstIcons.Static)
        self.lstIcons.setStyleSheet(
            "background: transparent; background: none;")
        self.filterLstIcons("")
        act = QWidgetAction(self.menuCustomIcons)
        act.setDefaultWidget(self.lstIcons)
        self.menuCustomIcons.addAction(act)

        menu.addMenu(self.menuCustomIcons)

        # Disabling stuff
        if len(sel) > 0 and index.isValid() and not index.internalPointer().isFolder() \
                or not clipboard.mimeData().hasFormat("application/xml"):
            self.actPaste.setEnabled(False)

        if len(sel) > 0 and index.isValid(
        ) and not index.internalPointer().isFolder():
            self.actAddFolder.setEnabled(False)
            self.actAddText.setEnabled(False)

        if len(sel) == 0:
            self.actOpen.setEnabled(False)
            self.actCopy.setEnabled(False)
            self.actCut.setEnabled(False)
            self.actDelete.setEnabled(False)
            self.menuPOV.setEnabled(False)
            self.menuStatus.setEnabled(False)
            self.menuLabel.setEnabled(False)
            self.menuCustomIcons.setEnabled(False)

        return menu
Exemplo n.º 20
0
class AddSeqTool(AbstractPathTool):
    """Summary

    Attributes:
        apply_button (TYPE): Description
        buttons (list): Description
        dialog (TYPE): Description
        highlighter (TYPE): Description
        seq_box (TYPE): Description
        sequence_radio_button_id (dict): Description
        signal_mapper (TYPE): Description
        use_abstract_sequence (bool): Description
        validated_sequence_to_apply (TYPE): Description
    """
    def __init__(self, manager):
        """Summary

        Args:
            manager (TYPE): Description
        """
        AbstractPathTool.__init__(self, manager)
        self.dialog = QDialog()
        self.buttons = []
        self.seq_box = None
        self.sequence_radio_button_id = {}
        self.use_abstract_sequence = True
        self.validated_sequence_to_apply = None
        self.initDialog()

    def __repr__(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return "add_seq_tool"  # first letter should be lowercase

    def methodPrefix(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return "addSeqTool"  # first letter should be lowercase

    def initDialog(self):
        """Creates buttons for each sequence option and add them to the dialog.
        Maps the clicked signal of those buttons to keep track of what sequence
        gets selected.
        """
        ui_dlg = Ui_AddSeqDialog()
        ui_dlg.setupUi(self.dialog)
        self.signal_mapper = QSignalMapper(self)
        # set up the radio buttons
        for i, name in enumerate(['Abstract', 'Custom'] + sorted(sequences.keys())):
            radio_button = QRadioButton(ui_dlg.group_box)
            radio_button.setObjectName(name + "Button")
            radio_button.setText(name)
            self.buttons.append(radio_button)
            ui_dlg.horizontalLayout.addWidget(radio_button)
            self.signal_mapper.setMapping(radio_button, i)
            radio_button.clicked.connect(self.signal_mapper.map)
            if name in sequences:
                self.sequence_radio_button_id[sequences[name]] = i
        self.signal_mapper.mapped.connect(self.sequenceOptionChangedSlot)
        # disable apply until valid option or custom sequence is chosen
        self.apply_button = ui_dlg.custom_button_box.button(QDialogButtonBox.Apply)
        self.apply_button.setEnabled(False)
        # watch sequence textedit box to validate custom sequences
        self.seq_box = ui_dlg.seq_text_edit
        self.seq_box.textChanged.connect(self.validateCustomSequence)
        self.highlighter = DNAHighlighter(self.seq_box)
        # finally, pre-click the first radio button
        self.buttons[0].click()

    def sequenceOptionChangedSlot(self, option_chosen):
        """
        Connects to signal_mapper to receive a signal whenever user selects
        a sequence option.

        Args:
            option_chosen (TYPE): Description
        """
        option_name = self.buttons[option_chosen].text()
        if option_name == 'Abstract':
            self.use_abstract_sequence = True
        elif option_name == 'Custom':
            self.use_abstract_sequence = False
        else:
            self.use_abstract_sequence = False
            user_sequence = sequences.get(option_name, None)
            if self.seq_box.toPlainText() != user_sequence:
                self.seq_box.setText(user_sequence)

    def validateCustomSequence(self):
        """
        Called when user changes sequence (seq_box emits textChanged signal)
        If sequence is valid, make the apply_button active to click.
        Select an appropriate sequence option radio button, if necessary.
        """
        user_sequence = self.seq_box.toPlainText()
        # Validate the sequence and activate the button if it checks out.
        if re.search(RE_DNA_PATTERN, user_sequence) is None:
            self.apply_button.setEnabled(True)
        else:
            self.apply_button.setEnabled(False)

        if len(user_sequence) == 0:
            # A zero-length custom sequence defaults to Abstract type.
            if not self.buttons[0].isChecked():
                self.buttons[0].click()
        else:
            # Does this match a known sequence?
            if user_sequence in self.sequence_radio_button_id:
                # Handles case where the user might copy & paste in a known sequence
                i = self.sequence_radio_button_id[user_sequence]
                if not self.buttons[i].isChecked():
                    # Select the corresponding radio button for known sequence
                    self.buttons[i].click()
            else:
                # Unrecognized, Custom type
                if not self.buttons[1].isChecked():
                    self.buttons[1].click()

    def applySequence(self, oligo):
        """Summary

        Args:
            oligo (TYPE): Description

        Returns:
            TYPE: Description
        """
        self.dialog.setFocus()
        if self.dialog.exec_():  # apply the sequence if accept was clicked
            if self.use_abstract_sequence:
                oligo.applySequence(None)
                return (oligo.length(), None)
            else:
                self.validated_sequence_to_apply = self.seq_box.toPlainText().upper()
                oligo.applySequence(self.validated_sequence_to_apply)
                return oligo.length(), len(self.validated_sequence_to_apply)
        return (None, None)
Exemplo n.º 21
0
class MainWindow(QMainWindow):
    """This create the main window of the application"""
    def __init__(self):
        super(MainWindow, self).__init__()

        self.ports = serial.listports()
        self.port = None

        # remove close & maximize window buttons
        #self.setWindowFlags(Qt.CustomizeWindowHint|Qt.WindowMinimizeButtonHint)
        self.setMinimumSize(850, 450)

        self.mdiArea = QMdiArea()
        self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.setCentralWidget(self.mdiArea)

        self.mdiArea.subWindowActivated.connect(self.updateMenus)
        self.mdiArea.setViewMode(QMdiArea.TabbedView)

        self.windowMapper = QSignalMapper(self)
        self.windowMapper.mapped[QWidget].connect(self.setActiveSubWindow)

        self.child = None

        self.createActions()
        self.createMenus()
        self.createStatusBar()
        self.updateMenus()
        self.readSettings()
        self.setWindowTitle("VISCAM")

        mytoolbar = QToolBar()
        ports_menu = QComboBox()
        ports_menu.addItem('Output Port')
        ports_menu.insertSeparator(1)
        for port in self.ports:
            ports_menu.addItem(port)
        self.ports_menu = ports_menu
        ports_menu.currentTextChanged.connect(self.setActivePort)
        mytoolbar.addWidget(ports_menu)
        mytoolbar.addSeparator()
        mytoolbar.setMovable(False)
        mytoolbar.setFixedHeight(60)
        self.addToolBar(Qt.TopToolBarArea, mytoolbar)

    def closeEvent(self, scenario):
        self.mdiArea.closeAllSubWindows()
        if self.mdiArea.currentSubWindow():
            scenario.ignore()
        else:
            self.writeSettings()
            scenario.accept()

    def about(self):
        QMessageBox.about(
            self, "About Viscam",
            "<b>Viscam</b> controls and manage your video camera through VISCA protocol."
            "This release is an alpha version. Don't use it in production !!")

    def updateMenus(self):
        hasCamera = (self.activeCamera() is not None)
        self.nextAct.setEnabled(hasCamera)
        self.previousAct.setEnabled(hasCamera)
        self.separatorAct.setVisible(hasCamera)

    def updatePortMenu(self):
        self.PortMenu.clear()
        for i, port in enumerate(self.ports):

            text = "%d %s" % (i + 1, port)
            if i < 9:
                text = '&' + text

            action = self.PortMenu.addAction(text)
            action.setCheckable(True)
            if port == self.port:
                action.setChecked(True)
            action.triggered.connect(self.setActivePort)

    def updateWindowMenu(self):
        self.windowMenu.clear()
        self.windowMenu.addAction(self.nextAct)
        self.windowMenu.addAction(self.previousAct)
        self.windowMenu.addAction(self.separatorAct)

        windows = self.mdiArea.subWindowList()
        self.separatorAct.setVisible(len(windows) != 0)

        for i, window in enumerate(windows):
            child = window.widget()

            text = "%d %s" % (i + 1, child.userFriendlyCurrentFile())
            if i < 9:
                text = '&' + text

            action = self.windowMenu.addAction(text)
            action.setCheckable(True)
            action.setChecked(child is self.activeCamera())
            action.triggered.connect(self.windowMapper.map)
            self.windowMapper.setMapping(action, window)

    def createCamera(self):
        child = Camera(serial)
        self.mdiArea.addSubWindow(child)
        child.newFile()
        self.child = child
        return child

    def createActions(self):
        self.exitAct = QAction(
            "E&xit",
            self,
            shortcut=QKeySequence.Quit,
            statusTip="Exit the application",
            triggered=QApplication.instance().closeAllWindows)

        self.closeAct = QAction("Cl&ose",
                                self,
                                statusTip="Close the active window",
                                triggered=self.mdiArea.closeActiveSubWindow)

        self.closeAllAct = QAction("Close &All",
                                   self,
                                   statusTip="Close all the windows",
                                   triggered=self.mdiArea.closeAllSubWindows)

        self.nextAct = QAction("Ne&xt",
                               self,
                               shortcut=QKeySequence.NextChild,
                               statusTip="Move the focus to the next window",
                               triggered=self.mdiArea.activateNextSubWindow)

        self.previousAct = QAction(
            "Pre&vious",
            self,
            shortcut=QKeySequence.PreviousChild,
            statusTip="Move the focus to the previous window",
            triggered=self.mdiArea.activatePreviousSubWindow)

        self.separatorAct = QAction(self)
        self.separatorAct.setSeparator(True)

        self.aboutAct = QAction("&About",
                                self,
                                statusTip="Show the application's About box",
                                triggered=self.about)

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

        self.PortMenu = self.menuBar().addMenu("&Ports")
        self.updatePortMenu()
        self.PortMenu.aboutToShow.connect(self.updatePortMenu)

        self.windowMenu = self.menuBar().addMenu("&Window")
        self.updateWindowMenu()
        self.windowMenu.aboutToShow.connect(self.updateWindowMenu)

        self.menuBar().addSeparator()

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

    def createStatusBar(self):
        self.statusBar().showMessage("Ready")

    def readSettings(self):
        settings = QSettings('Pixel Stereo', 'viscam')
        port = settings.value('port')
        pos = settings.value('pos', QPoint(200, 200))
        size = settings.value('size', QSize(1000, 650))
        self.move(pos)
        self.resize(size)

    def writeSettings(self):
        settings = QSettings('Pixel Stereo', 'viscam')
        settings.setValue('port', self.port)
        settings.setValue('pos', self.pos())
        settings.setValue('size', self.size())

    def activeCamera(self):
        activeSubWindow = self.mdiArea.activeSubWindow()
        if activeSubWindow:
            return activeSubWindow.widget()
        else:
            return None

    def findCamera(self, fileName):
        canonicalFilePath = QFileInfo(fileName).canonicalFilePath()

        for window in self.mdiArea.subWindowList():
            if window.widget().currentFile() == canonicalFilePath:
                return window
        return None

    def setActiveSubWindow(self, window):
        if window:
            self.mdiArea.setActiveSubWindow(window)

    def setActivePort(self):
        self.port = self.ports_menu.currentText().encode('utf-8')
        self.updatePortMenu()
        serial.open(portname=self.port)
        viscams = _cmd_adress_set(serial)
        _if_clear(serial)
        for v in viscams:
            v = self.createCamera()
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.mdiArea = QMdiArea()
        self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.setCentralWidget(self.mdiArea)

        self.mdiArea.subWindowActivated.connect(self.updateMenus)
        self.windowMapper = QSignalMapper(self)
        self.windowMapper.mapped[QWidget].connect(self.setActiveSubWindow)

        self.createActions()
        self.createMenus()
        self.createToolBars()
        self.createStatusBar()
        self.updateMenus()

        self.readSettings()

        self.setWindowTitle("MDI")

    def closeEvent(self, event):
        self.mdiArea.closeAllSubWindows()
        if self.mdiArea.currentSubWindow():
            event.ignore()
        else:
            self.writeSettings()
            event.accept()

    def newFile(self):
        child = self.createMdiChild()
        child.newFile()
        child.show()

    def open(self):
        fileName, _ = QFileDialog.getOpenFileName(self)
        if fileName:
            existing = self.findMdiChild(fileName)
            if existing:
                self.mdiArea.setActiveSubWindow(existing)
                return

            child = self.createMdiChild()
            if child.loadFile(fileName):
                self.statusBar().showMessage("File loaded", 2000)
                child.show()
            else:
                child.close()

    def save(self):
        if self.activeMdiChild() and self.activeMdiChild().save():
            self.statusBar().showMessage("File saved", 2000)

    def saveAs(self):
        if self.activeMdiChild() and self.activeMdiChild().saveAs():
            self.statusBar().showMessage("File saved", 2000)

    def cut(self):
        if self.activeMdiChild():
            self.activeMdiChild().cut()

    def copy(self):
        if self.activeMdiChild():
            self.activeMdiChild().copy()

    def paste(self):
        if self.activeMdiChild():
            self.activeMdiChild().paste()

    def about(self):
        QMessageBox.about(
            self, "About MDI",
            "The <b>MDI</b> example demonstrates how to write multiple "
            "document interface applications using Qt.")

    def updateMenus(self):
        hasMdiChild = (self.activeMdiChild() is not None)
        self.saveAct.setEnabled(hasMdiChild)
        self.saveAsAct.setEnabled(hasMdiChild)
        self.pasteAct.setEnabled(hasMdiChild)
        self.closeAct.setEnabled(hasMdiChild)
        self.closeAllAct.setEnabled(hasMdiChild)
        self.tileAct.setEnabled(hasMdiChild)
        self.cascadeAct.setEnabled(hasMdiChild)
        self.nextAct.setEnabled(hasMdiChild)
        self.previousAct.setEnabled(hasMdiChild)
        self.separatorAct.setVisible(hasMdiChild)

        hasSelection = (self.activeMdiChild() is not None
                        and self.activeMdiChild().textCursor().hasSelection())
        self.cutAct.setEnabled(hasSelection)
        self.copyAct.setEnabled(hasSelection)

    def updateWindowMenu(self):
        self.windowMenu.clear()
        self.windowMenu.addAction(self.closeAct)
        self.windowMenu.addAction(self.closeAllAct)
        self.windowMenu.addSeparator()
        self.windowMenu.addAction(self.tileAct)
        self.windowMenu.addAction(self.cascadeAct)
        self.windowMenu.addSeparator()
        self.windowMenu.addAction(self.nextAct)
        self.windowMenu.addAction(self.previousAct)
        self.windowMenu.addAction(self.separatorAct)

        windows = self.mdiArea.subWindowList()
        self.separatorAct.setVisible(len(windows) != 0)

        for i, window in enumerate(windows):
            child = window.widget()

            text = "%d %s" % (i + 1, child.userFriendlyCurrentFile())
            if i < 9:
                text = '&' + text

            action = self.windowMenu.addAction(text)
            action.setCheckable(True)
            action.setChecked(child is self.activeMdiChild())
            action.triggered.connect(self.windowMapper.map)
            self.windowMapper.setMapping(action, window)

    def createMdiChild(self):
        child = MdiChild()
        self.mdiArea.addSubWindow(child)

        child.copyAvailable.connect(self.cutAct.setEnabled)
        child.copyAvailable.connect(self.copyAct.setEnabled)

        return child

    def createActions(self):
        self.newAct = QAction(QIcon(':/images/new.png'),
                              "&New",
                              self,
                              shortcut=QKeySequence.New,
                              statusTip="Create a new file",
                              triggered=self.newFile)

        self.openAct = QAction(QIcon(':/images/open.png'),
                               "&Open...",
                               self,
                               shortcut=QKeySequence.Open,
                               statusTip="Open an existing file",
                               triggered=self.open)

        self.saveAct = QAction(QIcon(':/images/save.png'),
                               "&Save",
                               self,
                               shortcut=QKeySequence.Save,
                               statusTip="Save the document to disk",
                               triggered=self.save)

        self.saveAsAct = QAction(
            "Save &As...",
            self,
            shortcut=QKeySequence.SaveAs,
            statusTip="Save the document under a new name",
            triggered=self.saveAs)

        self.exitAct = QAction(
            "E&xit",
            self,
            shortcut=QKeySequence.Quit,
            statusTip="Exit the application",
            triggered=QApplication.instance().closeAllWindows)

        self.cutAct = QAction(
            QIcon(':/images/cut.png'),
            "Cu&t",
            self,
            shortcut=QKeySequence.Cut,
            statusTip="Cut the current selection's contents to the clipboard",
            triggered=self.cut)
        self.suggestAct = QAction(
            "Suggest",
            self,
            shortcut=QKeySequence.Copy,
            statusTip="Cut the current selection's contents to the clipboard",
            triggered=self.copy)
        self.copyAct = QAction(
            QIcon(':/images/copy.png'),
            "&Copy",
            self,
            shortcut=QKeySequence.Copy,
            statusTip="Copy the current selection's contents to the clipboard",
            triggered=self.copy)

        self.pasteAct = QAction(
            QIcon(':/images/paste.png'),
            "&Paste",
            self,
            shortcut=QKeySequence.Paste,
            statusTip=
            "Paste the clipboard's contents into the current selection",
            triggered=self.paste)

        self.closeAct = QAction("Cl&ose",
                                self,
                                statusTip="Close the active window",
                                triggered=self.mdiArea.closeActiveSubWindow)

        self.closeAllAct = QAction("Close &All",
                                   self,
                                   statusTip="Close all the windows",
                                   triggered=self.mdiArea.closeAllSubWindows)

        self.tileAct = QAction("&Tile",
                               self,
                               statusTip="Tile the windows",
                               triggered=self.mdiArea.tileSubWindows)

        self.cascadeAct = QAction("&Cascade",
                                  self,
                                  statusTip="Cascade the windows",
                                  triggered=self.mdiArea.cascadeSubWindows)

        self.nextAct = QAction("Ne&xt",
                               self,
                               shortcut=QKeySequence.NextChild,
                               statusTip="Move the focus to the next window",
                               triggered=self.mdiArea.activateNextSubWindow)

        self.previousAct = QAction(
            "Pre&vious",
            self,
            shortcut=QKeySequence.PreviousChild,
            statusTip="Move the focus to the previous window",
            triggered=self.mdiArea.activatePreviousSubWindow)

        self.separatorAct = QAction(self)
        self.separatorAct.setSeparator(True)

        self.aboutAct = QAction("&About",
                                self,
                                statusTip="Show the application's About box",
                                triggered=self.about)

        self.aboutQtAct = QAction("About &Qt",
                                  self,
                                  statusTip="Show the Qt library's About box",
                                  triggered=QApplication.instance().aboutQt)

    def createMenus(self):
        self.fileMenu = self.menuBar().addMenu("&File")
        self.fileMenu.addAction(self.newAct)
        self.fileMenu.addAction(self.openAct)
        self.fileMenu.addAction(self.saveAct)
        self.fileMenu.addAction(self.saveAsAct)
        self.fileMenu.addSeparator()
        action = self.fileMenu.addAction("Switch layout direction")
        action.triggered.connect(self.switchLayoutDirection)
        self.fileMenu.addAction(self.exitAct)

        self.editMenu = self.menuBar().addMenu("&Edit")
        self.editMenu.addAction(self.cutAct)
        self.editMenu.addAction(self.copyAct)
        self.editMenu.addAction(self.pasteAct)

        self.windowMenu = self.menuBar().addMenu("&Window")
        self.updateWindowMenu()
        self.windowMenu.aboutToShow.connect(self.updateWindowMenu)

        self.menuBar().addSeparator()

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

    def createToolBars(self):
        self.fileToolBar = self.addToolBar("File")
        self.fileToolBar.addAction(self.newAct)
        self.fileToolBar.addAction(self.openAct)
        self.fileToolBar.addAction(self.saveAct)

        self.editToolBar = self.addToolBar("Edit")
        self.editToolBar.addAction(self.cutAct)
        self.editToolBar.addAction(self.copyAct)
        self.editToolBar.addAction(self.pasteAct)

    def createStatusBar(self):
        self.statusBar().showMessage("Ready")

    def readSettings(self):
        settings = QSettings('Trolltech', 'MDI Example')
        pos = settings.value('pos', QPoint(200, 200))
        size = settings.value('size', QSize(400, 400))
        self.move(pos)
        self.resize(size)

    def writeSettings(self):
        settings = QSettings('Trolltech', 'MDI Example')
        settings.setValue('pos', self.pos())
        settings.setValue('size', self.size())

    def activeMdiChild(self):
        activeSubWindow = self.mdiArea.activeSubWindow()
        if activeSubWindow:
            return activeSubWindow.widget()
        return None

    def findMdiChild(self, fileName):
        canonicalFilePath = QFileInfo(fileName).canonicalFilePath()

        for window in self.mdiArea.subWindowList():
            if window.widget().currentFile() == canonicalFilePath:
                return window
        return None

    def switchLayoutDirection(self):
        if self.layoutDirection() == Qt.LeftToRight:
            QApplication.setLayoutDirection(Qt.RightToLeft)
        else:
            QApplication.setLayoutDirection(Qt.LeftToRight)

    def setActiveSubWindow(self, window):
        if window:
            self.mdiArea.setActiveSubWindow(window)

    def contextMenuEvent(self, event):
        menu = QMenu(self)
        menu.addAction(self.cutAct)
        menu.addAction(self.copyAct)
        menu.addAction(self.pasteAct)
        menu.addAction(self.suggestAct)
        menu.exec_(event.globalPos())
Exemplo n.º 23
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()
Exemplo n.º 24
0
class AddSeqTool(AbstractPathTool):
    def __init__(self, controller, parent=None):
        AbstractPathTool.__init__(self, controller, parent)
        self.dialog = QDialog()
        self.buttons = []
        self.seqBox = None
        self.chosenStandardSequence = None  # state for tab switching
        self.customSequenceIsValid = False  # state for tab switching
        self.useCustomSequence = False  # for applying sequence
        self.validatedSequenceToApply = None
        self.initDialog()

    def __repr__(self):
        return "add_seq_tool"  # first letter should be lowercase

    def methodPrefix(self):
        return "addSeqTool"  # first letter should be lowercase

    def initDialog(self):
        """
        1. Create buttons according to available scaffold sequences and
        add them to the dialog.
        2. Map the clicked signal of those buttons to keep track of what
        sequence gets selected.
        3. Watch the tabWidget change signal to determine whether a
        standard or custom sequence should be applied.
        """
        uiDlg = Ui_AddSeqDialog()
        uiDlg.setupUi(self.dialog)
        self.signalMapper = QSignalMapper(self)
        # set up the radio buttons
        for i, name in enumerate(sorted(sequences.keys())):
            radioButton = QRadioButton(uiDlg.groupBox)
            radioButton.setObjectName(name + "Button")
            radioButton.setText(name)
            self.buttons.append(radioButton)
            uiDlg.verticalLayout.addWidget(radioButton)
            self.signalMapper.setMapping(radioButton, i)
            radioButton.clicked.connect(self.signalMapper.map)
        self.signalMapper.mapped.connect(self.standardSequenceChangedSlot)
        uiDlg.tabWidget.currentChanged.connect(self.tabWidgetChangedSlot)
        # disable apply until valid option or custom sequence is chosen
        self.applyButton = uiDlg.customButtonBox.button(QDialogButtonBox.Apply)
        self.applyButton.setEnabled(False)
        # watch sequence textedit box to validate custom sequences
        self.seqBox = uiDlg.seqTextEdit
        self.seqBox.textChanged.connect(self.validateCustomSequence)
        self.highlighter = DNAHighlighter(self.seqBox)
        # finally, pre-click the M13mp18 radio button
        self.buttons[0].click()
        buttons = self.buttons

        self.dialog.setFocusProxy(uiDlg.groupBox)
        self.dialog.setFocusPolicy(Qt.TabFocus)
        uiDlg.groupBox.setFocusPolicy(Qt.TabFocus)
        for i in range(len(buttons)-1):
            uiDlg.groupBox.setTabOrder(buttons[i], buttons[i+1])

    def tabWidgetChangedSlot(self, index):
        applyEnabled = False
        if index == 1:  # Custom Sequence
            self.validateCustomSequence()
            if self.customSequenceIsValid:
                applyEnabled = True
        else:  # Standard Sequence
            self.useCustomSequence = False
            if self.chosenStandardSequence != None:
                # Overwrite sequence in case custom has been applied
                activeButton = self.buttons[self.chosenStandardSequence]
                sequenceName = str(activeButton.text())
                self.validatedSequenceToApply = sequences.get(sequenceName, None)
                applyEnabled = True
        self.applyButton.setEnabled(applyEnabled)

    def standardSequenceChangedSlot(self, optionChosen):
        """
        Connected to signalMapper to receive a signal whenever user selects
        a different sequence in the standard tab.
        """
        sequenceName = str(self.buttons[optionChosen].text())
        self.validatedSequenceToApply = sequences.get(sequenceName, None)
        self.chosenStandardSequence = optionChosen
        self.applyButton.setEnabled(True)

    def validateCustomSequence(self):
        """
        Called when:
        1. User enters custom sequence (i.e. seqBox emits textChanged signal)
        2. tabWidgetChangedSlot sees the user has switched to custom tab.

        When the sequence is valid, make the applyButton active for clicking.
        Otherwise
        """
        userSequence = self.seqBox.toPlainText()
        if len(userSequence) == 0:
            self.customSequenceIsValid = False
            return  # tabWidgetChangedSlot will disable applyButton
        if dnapattern.indexIn(userSequence) == -1:  # no invalid characters
            self.useCustomSequence = True
            self.customSequenceIsValid = True
            self.applyButton.setEnabled(True)
        else:
            self.customSequenceIsValid = False
            self.applyButton.setEnabled(False)

    def applySequence(self, oligo):
        self.dialog.setFocus()
        if self.dialog.exec_():  # apply the sequence if accept was clicked
            if self.useCustomSequence:
                self.validatedSequenceToApply = str(self.seqBox.toPlainText().toUpper())
            oligo.applySequence(self.validatedSequenceToApply)
            return oligo.length(), len(self.validatedSequenceToApply)
        return (None, None)
Exemplo n.º 25
0
class MainWindow(QMainWindow, Ui_MainWindow):
    """docstring for MainWindow."""
    def __init__(self, parent=None):
        super(MainWindow, self).__init__()
        self._csvFilePath = ""
        self.serialport = serial.Serial()
        self.receiver_thread = readerThread(self)
        self.receiver_thread.setPort(self.serialport)
        self._localEcho = None
        self._viewMode = None
        self._quickSendOptRow = 1

        self.setupUi(self)
        self.setCorner(Qt.TopLeftCorner, Qt.LeftDockWidgetArea)
        self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea)
        font = QtGui.QFont()
        font.setFamily(EDITOR_FONT)
        font.setPointSize(9)
        self.txtEdtOutput.setFont(font)
        self.txtEdtInput.setFont(font)
        #self.quickSendTable.setFont(font)
        if UI_FONT is not None:
            font = QtGui.QFont()
            font.setFamily(UI_FONT)
            font.setPointSize(9)
            self.dockWidget_PortConfig.setFont(font)
            self.dockWidget_SendHex.setFont(font)
            self.dockWidget_QuickSend.setFont(font)
        self.setupMenu()
        self.setupFlatUi()
        self.onEnumPorts()

        icon = QtGui.QIcon(":/MyTerm.ico")
        self.setWindowIcon(icon)
        self.actionAbout.setIcon(icon)

        self.defaultStyleWidget = QWidget()
        self.defaultStyleWidget.setWindowIcon(icon)

        icon = QtGui.QIcon(":/qt_logo_16.ico")
        self.actionAbout_Qt.setIcon(icon)

        self._viewGroup = QActionGroup(self)
        self._viewGroup.addAction(self.actionAscii)
        self._viewGroup.addAction(self.actionHex_lowercase)
        self._viewGroup.addAction(self.actionHEX_UPPERCASE)
        self._viewGroup.setExclusive(True)

        # bind events
        self.actionOpen_Cmd_File.triggered.connect(self.openQuickSend)
        self.actionSave_Log.triggered.connect(self.onSaveLog)
        self.actionExit.triggered.connect(self.onExit)

        self.actionOpen.triggered.connect(self.openPort)
        self.actionClose.triggered.connect(self.closePort)

        self.actionPort_Config_Panel.triggered.connect(self.onTogglePrtCfgPnl)
        self.actionQuick_Send_Panel.triggered.connect(self.onToggleQckSndPnl)
        self.actionSend_Hex_Panel.triggered.connect(self.onToggleHexPnl)
        self.dockWidget_PortConfig.visibilityChanged.connect(self.onVisiblePrtCfgPnl)
        self.dockWidget_QuickSend.visibilityChanged.connect(self.onVisibleQckSndPnl)
        self.dockWidget_SendHex.visibilityChanged.connect(self.onVisibleHexPnl)
        self.actionLocal_Echo.triggered.connect(self.onLocalEcho)
        self.actionAlways_On_Top.triggered.connect(self.onAlwaysOnTop)

        self.actionAscii.triggered.connect(self.onViewChanged)
        self.actionHex_lowercase.triggered.connect(self.onViewChanged)
        self.actionHEX_UPPERCASE.triggered.connect(self.onViewChanged)

        self.actionAbout.triggered.connect(self.onAbout)
        self.actionAbout_Qt.triggered.connect(self.onAboutQt)

        self.btnOpen.clicked.connect(self.onOpen)
        self.btnClear.clicked.connect(self.onClear)
        self.btnSaveLog.clicked.connect(self.onSaveLog)
        self.btnEnumPorts.clicked.connect(self.onEnumPorts)
        self.btnSendHex.clicked.connect(self.onSend)

        self.receiver_thread.read.connect(self.onReceive)
        self.receiver_thread.exception.connect(self.onReaderExcept)
        self._signalMapQuickSendOpt = QSignalMapper(self)
        self._signalMapQuickSendOpt.mapped[int].connect(self.onQuickSendOptions)
        self._signalMapQuickSend = QSignalMapper(self)
        self._signalMapQuickSend.mapped[int].connect(self.onQuickSend)

        # initial action
        self.actionHEX_UPPERCASE.setChecked(True)
        self.receiver_thread.setViewMode(VIEWMODE_HEX_UPPERCASE)
        self.initQuickSend()
        self.restoreLayout()
        self.moveScreenCenter()
        self.syncMenu()
        
        if self.isMaximized():
            self.setMaximizeButton("restore")
        else:
            self.setMaximizeButton("maximize")
            
        self.loadSettings()

    def setupMenu(self):
        self.menuMenu = QtWidgets.QMenu()
        self.menuMenu.setTitle("&File")
        self.menuMenu.setObjectName("menuMenu")
        self.menuView = QtWidgets.QMenu(self.menuMenu)
        self.menuView.setTitle("&View")
        self.menuView.setObjectName("menuView")

        self.menuView.addAction(self.actionAscii)
        self.menuView.addAction(self.actionHex_lowercase)
        self.menuView.addAction(self.actionHEX_UPPERCASE)
        self.menuMenu.addAction(self.actionOpen_Cmd_File)
        self.menuMenu.addAction(self.actionSave_Log)
        self.menuMenu.addSeparator()
        self.menuMenu.addAction(self.actionPort_Config_Panel)
        self.menuMenu.addAction(self.actionQuick_Send_Panel)
        self.menuMenu.addAction(self.actionSend_Hex_Panel)
        self.menuMenu.addAction(self.menuView.menuAction())
        self.menuMenu.addAction(self.actionLocal_Echo)
        self.menuMenu.addAction(self.actionAlways_On_Top)
        self.menuMenu.addSeparator()
        self.menuMenu.addAction(self.actionAbout)
        self.menuMenu.addAction(self.actionAbout_Qt)
        self.menuMenu.addSeparator()
        self.menuMenu.addAction(self.actionExit)

        self.sendOptMenu = QtWidgets.QMenu()
        self.actionSend_Hex = QtWidgets.QAction(self)
        self.actionSend_Hex.setText("Send &Hex")
        self.actionSend_Hex.setStatusTip("Send Hex (e.g. 31 32 FF)")

        self.actionSend_Asc = QtWidgets.QAction(self)
        self.actionSend_Asc.setText("Send &Asc")
        self.actionSend_Asc.setStatusTip("Send Asc (e.g. abc123)")

        self.actionSend_TFH = QtWidgets.QAction(self)
        self.actionSend_TFH.setText("Send &Text file as hex")
        self.actionSend_TFH.setStatusTip('Send text file as hex (e.g. strings "31 32 FF" in the file)')

        self.actionSend_TFA = QtWidgets.QAction(self)
        self.actionSend_TFA.setText("Send t&Ext file as asc")
        self.actionSend_TFA.setStatusTip('Send text file as asc (e.g. strings "abc123" in the file)')

        self.actionSend_FB = QtWidgets.QAction(self)
        self.actionSend_FB.setText("Send &Bin/Hex file")
        self.actionSend_FB.setStatusTip("Send a bin file or a hex file")

        self.sendOptMenu.addAction(self.actionSend_Hex)
        self.sendOptMenu.addAction(self.actionSend_Asc)
        self.sendOptMenu.addAction(self.actionSend_TFH)
        self.sendOptMenu.addAction(self.actionSend_TFA)
        self.sendOptMenu.addAction(self.actionSend_FB)

        self.actionSend_Hex.triggered.connect(self.onSetSendHex)
        self.actionSend_Asc.triggered.connect(self.onSetSendAsc)
        self.actionSend_TFH.triggered.connect(self.onSetSendTFH)
        self.actionSend_TFA.triggered.connect(self.onSetSendTFA)
        self.actionSend_FB.triggered.connect(self.onSetSendFB)

    def setupFlatUi(self):
        self._dragPos = self.pos()
        self._isDragging = False
        self.setMouseTracking(True)
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setStyleSheet("""
            QWidget {
                background-color: %(BackgroundColor)s;
                /*background-image: url(:/background.png);*/
                outline: none;
            }
            QLabel {
                color:%(TextColor)s;
                font-size:12px;
                /*font-family:Century;*/
            }
            
            QComboBox {
                color:%(TextColor)s;
                font-size:12px;
                /*font-family:Century;*/
            }
            QComboBox {
                border: none;
                padding: 1px 1px 1px 3px;
            }
            QComboBox:editable {
                background: white;
            }
            QComboBox:!editable, QComboBox::drop-down:editable {
                background: #62c7e0;
            }
            QComboBox:!editable:hover, QComboBox::drop-down:editable:hover {
                background: #c7eaf3;
            }
            QComboBox:!editable:pressed, QComboBox::drop-down:editable:pressed {
                background: #35b6d7;
            }
            QComboBox:on {
                padding-top: 3px;
                padding-left: 4px;
            }
            QComboBox::drop-down {
                subcontrol-origin: padding;
                subcontrol-position: top right;
                width: 16px;
                border: none;
            }
            QComboBox::down-arrow {
                image: url(:/downarrow.png);
            }
            QComboBox::down-arrow:on {
                image: url(:/uparrow.png);
            }
            
            QGroupBox {
                color:%(TextColor)s;
                font-size:12px;
                /*font-family:Century;*/
                border: 1px solid gray;
                margin-top: 15px;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                subcontrol-position: top left;
                left:5px;
                top:3px;
            }
            
            QCheckBox {
                color:%(TextColor)s;
                spacing: 5px;
                font-size:12px;
                /*font-family:Century;*/
            }
            QCheckBox::indicator:unchecked {
                image: url(:/checkbox_unchecked.png);
            }

            QCheckBox::indicator:unchecked:hover {
                image: url(:/checkbox_unchecked_hover.png);
            }

            QCheckBox::indicator:unchecked:pressed {
                image: url(:/checkbox_unchecked_pressed.png);
            }

            QCheckBox::indicator:checked {
                image: url(:/checkbox_checked.png);
            }

            QCheckBox::indicator:checked:hover {
                image: url(:/checkbox_checked_hover.png);
            }

            QCheckBox::indicator:checked:pressed {
                image: url(:/checkbox_checked_pressed.png);
            }
            
            QScrollBar:horizontal {
                background-color:%(BackgroundColor)s;
                border: none;
                height: 15px;
                margin: 0px 20px 0 20px;
            }
            QScrollBar::handle:horizontal {
                background: %(ScrollBar_Handle)s;
                min-width: 20px;
            }
            QScrollBar::add-line:horizontal {
                image: url(:/rightarrow.png);
                border: none;
                background: %(ScrollBar_Line)s;
                width: 20px;
                subcontrol-position: right;
                subcontrol-origin: margin;
            }
            QScrollBar::sub-line:horizontal {
                image: url(:/leftarrow.png);
                border: none;
                background: %(ScrollBar_Line)s;
                width: 20px;
                subcontrol-position: left;
                subcontrol-origin: margin;
            }
            
            QScrollBar:vertical {
                background-color:%(BackgroundColor)s;
                border: none;
                width: 15px;
                margin: 20px 0px 20px 0px;
            }
            QScrollBar::handle::vertical {
                background: %(ScrollBar_Handle)s;
                min-height: 20px;
            }
            QScrollBar::add-line::vertical {
                image: url(:/downarrow.png);
                border: none;
                background: %(ScrollBar_Line)s;
                height: 20px;
                subcontrol-position: bottom;
                subcontrol-origin: margin;
            }
            QScrollBar::sub-line::vertical {
                image: url(:/uparrow.png);
                border: none;
                background: %(ScrollBar_Line)s;
                height: 20px;
                subcontrol-position: top;
                subcontrol-origin: margin;
            }
            
            QTableView {
                background-color: white;
                /*selection-background-color: #FF92BB;*/
                border: 1px solid %(TableView_Border)s;
                color: %(TextColor)s;
            }
            QTableView::focus {
                /*border: 1px solid #2a7fff;*/
            }
            QTableView QTableCornerButton::section {
                border: none;
                border-right: 1px solid %(TableView_Border)s;
                border-bottom: 1px solid %(TableView_Border)s;
                background-color: %(TableView_Corner)s;
            }
            QTableView QWidget {
                background-color: white;
            }
            QTableView::item:focus {
                border: 1px red;
                background-color: transparent;
                color: %(TextColor)s;
            }
            QHeaderView::section {
                border: none;
                border-right: 1px solid %(TableView_Border)s;
                border-bottom: 1px solid %(TableView_Border)s;
                padding-left: 2px;
                padding-right: 2px;
                color: #444444;
                background-color: %(TableView_Header)s;
            }
            QTextEdit {
                background-color:white;
                color:%(TextColor)s;
                border-top: none;
                border-bottom: none;
                border-left: 2px solid %(BackgroundColor)s;
                border-right: 2px solid %(BackgroundColor)s;
            }
            QTextEdit::focus {
            }
            
            QToolButton, QPushButton {
                background-color:#30a7b8;
                border:none;
                color:#ffffff;
                font-size:12px;
                /*font-family:Century;*/
            }
            QToolButton:hover, QPushButton:hover {
                background-color:#51c0d1;
            }
            QToolButton:pressed, QPushButton:pressed {
                background-color:#3a9ecc;
            }
            
            QMenuBar {
                color: %(TextColor)s;
                height: 24px;
            }
            QMenuBar::item {
                background-color: transparent;
                margin: 8px 0px 0px 0px;
                padding: 1px 8px 1px 8px;
                height: 15px;
            }
            QMenuBar::item:selected {
                background: #51c0d1;
            }
            QMenuBar::item:pressed {
                
            }
            /*
            QMenu {
                color: %(TextColor)s;
                background: #ffffff;
            }
            QMenu {
                margin: 2px;
            }
            QMenu::item {
                padding: 2px 25px 2px 21px;
                border: 1px solid transparent;
            }
            QMenu::item:selected {
                background: #51c0d1;
            }
            QMenu::icon {
                background: transparent;
                border: 2px inset transparent;
            }*/

            QDockWidget {
                font-size:12px;
                /*font-family:Century;*/
                color: %(TextColor)s;
                titlebar-close-icon: none;
                titlebar-normal-icon: none;
            }
            QDockWidget::title {
                margin: 0;
                padding: 2px;
                subcontrol-origin: content;
                subcontrol-position: right top;
                text-align: left;
                background: #67baed;
                
            }
            QDockWidget::float-button {
                max-width: 12px;
                max-height: 12px;
                background-color:transparent;
                border:none;
                image: url(:/restore_inactive.png);
            }
            QDockWidget::float-button:hover {
                background-color:#227582;
                image: url(:/restore_active.png);
            }
            QDockWidget::float-button:pressed {
                padding: 0;
                background-color:#14464e;
                image: url(:/restore_active.png);
            }
            QDockWidget::close-button {
                max-width: 12px;
                max-height: 12px;
                background-color:transparent;
                border:none;
                image: url(:/close_inactive.png);
            }
            QDockWidget::close-button:hover {
                background-color:#ea5e00;
                image: url(:/close_active.png);
            }
            QDockWidget::close-button:pressed {
                background-color:#994005;
                image: url(:/close_active.png);
                padding: 0;
            }
            
        """ % dict(
            BackgroundColor =  '#99d9ea',
            TextColor =        '#202020',
            ScrollBar_Handle = '#61b9e1',
            ScrollBar_Line =   '#7ecfe4',
            TableView_Corner = '#8ae6d2',
            TableView_Header = '#8ae6d2',
            TableView_Border = '#eeeeee'
        ))
        self.dockWidgetContents.setStyleSheet("""
            QPushButton {
                min-height:23px;
            }
        """)
        self.dockWidget_QuickSend.setStyleSheet("""
            QToolButton, QPushButton {
                background-color:#27b798;
                /*font-family:Consolas;*/
                /*font-size:12px;*/
                /*min-width:46px;*/
            }
            QToolButton:hover, QPushButton:hover {
                background-color:#3bd5b4;
            }
            QToolButton:pressed, QPushButton:pressed {
                background-color:#1d8770;
            }
        """)
        self.dockWidgetContents_2.setStyleSheet("""
            QPushButton {
                min-height:23px;
                min-width:50px;
            }
        """)

        w = self.frameGeometry().width()
        self._minBtn = QPushButton(self)
        self._minBtn.setGeometry(w-103,0,28,24)
        self._minBtn.clicked.connect(self.onMinimize)
        self._minBtn.setStyleSheet("""
            QPushButton {
                background-color:transparent;
                border:none;
                outline: none;
                image: url(:/minimize_inactive.png);
            }
            QPushButton:hover {
                background-color:#227582;
                image: url(:/minimize_active.png);
            }
            QPushButton:pressed {
                background-color:#14464e;
                image: url(:/minimize_active.png);
            }
        """)
        
        self._maxBtn = QPushButton(self)
        self._maxBtn.setGeometry(w-74,0,28,24)
        self._maxBtn.clicked.connect(self.onMaximize)
        self.setMaximizeButton("maximize")
        
        self._closeBtn = QPushButton(self)
        self._closeBtn.setGeometry(w-45,0,36,24)
        self._closeBtn.clicked.connect(self.onExit)
        self._closeBtn.setStyleSheet("""
            QPushButton {
                background-color:transparent;
                border:none;
                outline: none;
                image: url(:/close_inactive.png);
            }
            QPushButton:hover {
                background-color:#ea5e00;
                image: url(:/close_active.png);
            }
            QPushButton:pressed {
                background-color:#994005;
                image: url(:/close_active.png);
            }
        """)
        
        self.btnMenu = QtWidgets.QToolButton(self)
        self.btnMenu.setEnabled(True)
        self.btnMenu.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
        self.btnMenu.setIcon(QtGui.QIcon(':/MyTerm.ico'))
        self.btnMenu.setText('Myterm  ')
        self.btnMenu.setGeometry(3,3,80,18)
        self.btnMenu.setMenu(self.menuMenu)
        self.btnMenu.setPopupMode(QtWidgets.QToolButton.InstantPopup)
        
        self.btnRefresh = QtWidgets.QToolButton(self)
        self.btnRefresh.setEnabled(True)
        self.btnRefresh.setIcon(QtGui.QIcon(':/refresh.ico'))
        self.btnRefresh.setGeometry(110,3,18,18)
        self.btnRefresh.clicked.connect(self.onEnumPorts)
        
        self.verticalLayout_1.removeWidget(self.cmbPort)
        self.cmbPort.setParent(self)
        self.cmbPort.setGeometry(128,3,60,18)
        
        self.verticalLayout_1.removeWidget(self.btnOpen)
        self.btnOpen.setParent(self)
        self.btnOpen.setGeometry(210,3,60,18)
        
    def resizeEvent(self, event):
        w = event.size().width()
        self._minBtn.move(w-103,0)
        self._maxBtn.move(w-74,0)
        self._closeBtn.move(w-45,0)

    def onMinimize(self):
        self.showMinimized()
    
    def isMaximized(self):
        return ((self.windowState() == Qt.WindowMaximized))
    
    def onMaximize(self):
        if self.isMaximized():
            self.showNormal()
            self.setMaximizeButton("maximize")
        else:
            self.showMaximized()
            self.setMaximizeButton("restore")
    
    def setMaximizeButton(self, style):
        if "maximize" == style:
            self._maxBtn.setStyleSheet("""
                QPushButton {
                    background-color:transparent;
                    border:none;
                    outline: none;
                    image: url(:/maximize_inactive.png);
                }
                QPushButton:hover {
                    background-color:#227582;
                    image: url(:/maximize_active.png);
                }
                QPushButton:pressed {
                    background-color:#14464e;
                    image: url(:/maximize_active.png);
                }
            """)
        elif "restore" == style:
            self._maxBtn.setStyleSheet("""
                QPushButton {
                    background-color:transparent;
                    border:none;
                    outline: none;
                    image: url(:/restore_inactive.png);
                }
                QPushButton:hover {
                    background-color:#227582;
                    image: url(:/restore_active.png);
                }
                QPushButton:pressed {
                    background-color:#14464e;
                    image: url(:/restore_active.png);
                }
            """)
    
    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self._isDragging = True
            self._dragPos = event.globalPos() - self.pos()
        event.accept()
        
    def mouseMoveEvent(self, event):
        if event.buttons() == Qt.LeftButton and self._isDragging and not self.isMaximized():
            self.move(event.globalPos() - self._dragPos)
        event.accept()

    def mouseReleaseEvent(self, event):
        self._isDragging = False
        event.accept()

    def saveSettings(self):
        root = ET.Element("MyTerm")
        GUISettings = ET.SubElement(root, "GUISettings")

        PortCfg = ET.SubElement(GUISettings, "PortConfig")
        ET.SubElement(PortCfg, "port").text = self.cmbPort.currentText()
        ET.SubElement(PortCfg, "baudrate").text = self.cmbBaudRate.currentText()
        ET.SubElement(PortCfg, "databits").text = self.cmbDataBits.currentText()
        ET.SubElement(PortCfg, "parity").text = self.cmbParity.currentText()
        ET.SubElement(PortCfg, "stopbits").text = self.cmbStopBits.currentText()
        ET.SubElement(PortCfg, "rtscts").text = self.chkRTSCTS.isChecked() and "on" or "off"
        ET.SubElement(PortCfg, "xonxoff").text = self.chkXonXoff.isChecked() and "on" or "off"

        View = ET.SubElement(GUISettings, "View")
        ET.SubElement(View, "LocalEcho").text = self.actionLocal_Echo.isChecked() and "on" or "off"
        ET.SubElement(View, "ReceiveView").text = self._viewGroup.checkedAction().text()

        with open(get_config_path('MyTerm.xml'), 'w') as f:
            f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
            f.write(ET.tostring(root, encoding='utf-8', pretty_print=True).decode("utf-8"))

    def loadSettings(self):
        if os.path.isfile(get_config_path("MyTerm.xml")):
            with open(get_config_path("MyTerm.xml"), 'r') as f:
                tree = safeET.parse(f)

            port = tree.findtext('GUISettings/PortConfig/port', default='')
            if port != '':
                self.cmbPort.setCurrentText(port)

            baudrate = tree.findtext('GUISettings/PortConfig/baudrate', default='38400')
            if baudrate != '':
                self.cmbBaudRate.setCurrentText(baudrate)

            databits = tree.findtext('GUISettings/PortConfig/databits', default='8')
            id = self.cmbDataBits.findText(databits)
            if id >= 0:
                self.cmbDataBits.setCurrentIndex(id)

            parity = tree.findtext('GUISettings/PortConfig/parity', default='None')
            id = self.cmbParity.findText(parity)
            if id >= 0:
                self.cmbParity.setCurrentIndex(id)

            stopbits = tree.findtext('GUISettings/PortConfig/stopbits', default='1')
            id = self.cmbStopBits.findText(stopbits)
            if id >= 0:
                self.cmbStopBits.setCurrentIndex(id)

            rtscts = tree.findtext('GUISettings/PortConfig/rtscts', default='off')
            if 'on' == rtscts:
                self.chkRTSCTS.setChecked(True)
            else:
                self.chkRTSCTS.setChecked(False)

            xonxoff = tree.findtext('GUISettings/PortConfig/xonxoff', default='off')
            if 'on' == xonxoff:
                self.chkXonXoff.setChecked(True)
            else:
                self.chkXonXoff.setChecked(False)

            LocalEcho = tree.findtext('GUISettings/View/LocalEcho', default='off')
            if 'on' == LocalEcho:
                self.actionLocal_Echo.setChecked(True)
                self._localEcho = True
            else:
                self.actionLocal_Echo.setChecked(False)
                self._localEcho = False

            ReceiveView = tree.findtext('GUISettings/View/ReceiveView', default='HEX(UPPERCASE)')
            if 'Ascii' in ReceiveView:
                self.actionAscii.setChecked(True)
                self._viewMode = VIEWMODE_ASCII
            elif 'lowercase' in ReceiveView:
                self.actionHex_lowercase.setChecked(True)
                self._viewMode = VIEWMODE_HEX_LOWERCASE
            elif 'UPPERCASE' in ReceiveView:
                self.actionHEX_UPPERCASE.setChecked(True)
                self._viewMode = VIEWMODE_HEX_UPPERCASE
            self.receiver_thread.setViewMode(self._viewMode)

    def closeEvent(self, event):
        self.saveLayout()
        self.saveQuickSend()
        self.saveSettings()
        event.accept()

    def initQuickSend(self):
        #self.quickSendTable.horizontalHeader().setDefaultSectionSize(40)
        #self.quickSendTable.horizontalHeader().setMinimumSectionSize(25)
        self.quickSendTable.setRowCount(50)
        self.quickSendTable.setColumnCount(3)
        self.quickSendTable.verticalHeader().setSectionsClickable(True)

        for row in range(50):
            self.initQuickSendButton(row)

        if os.path.isfile(get_config_path('QuickSend.csv')):
            self.loadQuickSend(get_config_path('QuickSend.csv'))

        self.quickSendTable.resizeColumnsToContents()

    def initQuickSendButton(self, row, cmd = 'cmd', opt = 'H', dat = ''):
        if self.quickSendTable.cellWidget(row, 0) is None:
            item = QToolButton(self)
            item.setText(cmd)
            item.clicked.connect(self._signalMapQuickSend.map)
            self._signalMapQuickSend.setMapping(item, row)
            self.quickSendTable.setCellWidget(row, 0, item)
        else:
            self.quickSendTable.cellWidget(row, 0).setText(cmd)

        if self.quickSendTable.cellWidget(row, 1) is None:
            item = QToolButton(self)
            item.setText(opt)
            #item.setMaximumSize(QtCore.QSize(16, 16))
            item.clicked.connect(self._signalMapQuickSendOpt.map)
            self._signalMapQuickSendOpt.setMapping(item, row)
            self.quickSendTable.setCellWidget(row, 1, item)
        else:
            self.quickSendTable.cellWidget(row, 1).setText(opt)

        if self.quickSendTable.item(row, 2) is None:
            self.quickSendTable.setItem(row, 2, QTableWidgetItem(dat))
        else:
            self.quickSendTable.item(row, 2).setText(dat)

        self.quickSendTable.setRowHeight(row, 16)

    def onSetSendHex(self):
        item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1)
        item.setText('H')

    def onSetSendAsc(self):
        item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1)
        item.setText('A')

    def onSetSendTFH(self):
        item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1)
        item.setText('FH')

    def onSetSendTFA(self):
        item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1)
        item.setText('FA')

    def onSetSendFB(self):
        item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1)
        item.setText('FB')

    def onQuickSendOptions(self, row):
        self._quickSendOptRow = row
        item = self.quickSendTable.cellWidget(row, 1)
        self.sendOptMenu.popup(item.mapToGlobal(QPoint(item.size().width(), item.size().height())))

    def openQuickSend(self):
        fileName = QFileDialog.getOpenFileName(self, "Select a file",
            os.getcwd(), "CSV Files (*.csv)")[0]
        if fileName:
            self.loadQuickSend(fileName, notifyExcept = True)

    def saveQuickSend(self):
        # scan table
        rows = self.quickSendTable.rowCount()
        #cols = self.quickSendTable.columnCount()

        save_data = [[self.quickSendTable.cellWidget(row, 0).text(),
                      self.quickSendTable.cellWidget(row, 1).text(),
                      self.quickSendTable.item(row, 2) is not None
                      and self.quickSendTable.item(row, 2).text() or ''] for row in range(rows)]

        #import pprint
        #pprint.pprint(save_data, width=120, compact=True)

        # write to file
        with open(get_config_path('QuickSend.csv'), 'w') as csvfile:
            csvwriter = csv.writer(csvfile, delimiter=',', lineterminator='\n')
            csvwriter.writerows(save_data)

    def loadQuickSend(self, path, notifyExcept = False):
        data = []
        set_rows = 0
        set_cols = 0
        try:
            with open(path) as csvfile:
                csvData = csv.reader(csvfile)
                for row in csvData:
                    data.append(row)
                    set_rows = set_rows + 1
                    if len(row) > set_cols:
                        set_cols = len(row)
        except IOError as e:
            print("({})".format(e))
            if notifyExcept:
                QMessageBox.critical(self.defaultStyleWidget, "Open failed",
                    str(e), QMessageBox.Close)
            return

        rows = self.quickSendTable.rowCount()
        cols = self.quickSendTable.columnCount()

        if rows < set_rows:
            rows = set_rows + 10
            self.quickSendTable.setRowCount(rows)

        for row, rowdat in enumerate(data):
            if len(rowdat) >= 3:
                cmd, opt, dat = rowdat[0:3]
                self.initQuickSendButton(row, cmd, opt, dat)
#                self.quickSendTable.cellWidget(row, 0).setText(cmd)
#                self.quickSendTable.cellWidget(row, 1).setText(opt)
#                self.quickSendTable.setItem(row, 2, QTableWidgetItem(dat))

        self.quickSendTable.resizeColumnsToContents()
        #self.quickSendTable.resizeRowsToContents()

    def onQuickSend(self, row):
        if self.quickSendTable.item(row, 2) is not None:
            tablestring = self.quickSendTable.item(row, 2).text()
            format = self.quickSendTable.cellWidget(row, 1).text()
            if 'H' == format:
                self.transmitHex(tablestring)
            elif 'A' == format:
                self.transmitAsc(tablestring)
            elif 'FB' == format:
                try:
                    with open(tablestring, 'rb') as f:
                        bytes = f.read()
                        self.transmitBytearray(bytes)
                except IOError as e:
                    print("({})".format(e))
                    QMessageBox.critical(self.defaultStyleWidget, "Open failed",
                        str(e), QMessageBox.Close)
            else:
                try:
                    with open(tablestring, 'rt') as f:
                        filestring = f.read()
                        if 'FH' == format:
                            self.transmitHex(filestring)
                        elif 'FA' == format:
                            self.transmitAsc(filestring)
                except IOError as e:
                    print("({})".format(e))
                    QMessageBox.critical(self.defaultStyleWidget, "Open failed",
                        str(e), QMessageBox.Close)

    def onSend(self):
        sendstring = self.txtEdtInput.toPlainText()
        self.transmitHex(sendstring)

    def transmitHex(self, hexstring):
        if len(hexstring) > 0:
            hexarray = []
            _hexstring = hexstring.replace(' ', '')
            _hexstring = _hexstring.replace('\r', '')
            _hexstring = _hexstring.replace('\n', '')
            for i in range(0, len(_hexstring), 2):
                word = _hexstring[i:i+2]
                if is_hex(word):
                    hexarray.append(int(word, 16))
                else:
                    QMessageBox.critical(self.defaultStyleWidget, "Error",
                        "'%s' is not hexadecimal." % (word), QMessageBox.Close)
                    return

            self.transmitBytearray(bytearray(hexarray))

    def transmitAsc(self, text):
        if len(text) > 0:
            byteArray = [ord(char) for char in text]
            self.transmitBytearray(bytearray(byteArray))

    def transmitBytearray(self, byteArray):
        if self.serialport.isOpen():
            try:
                self.serialport.write(byteArray)
            except Exception as e:
                QMessageBox.critical(self.defaultStyleWidget,
                    "Exception in transmit", str(e), QMessageBox.Close)
                print("Exception in transmitBytearray(%s)" % text)
            else:
                if self._viewMode == VIEWMODE_ASCII:
                    text = byteArray.decode('unicode_escape')
                elif self._viewMode == VIEWMODE_HEX_LOWERCASE:
                    text = ''.join('%02x ' % t for t in byteArray)
                elif self._viewMode == VIEWMODE_HEX_UPPERCASE:
                    text = ''.join('%02X ' % t for t in byteArray)
                self.appendOutputText("\n%s T->:%s" % (self.timestamp(), text), Qt.blue)

    def onReaderExcept(self, e):
        self.closePort()
        QMessageBox.critical(self.defaultStyleWidget, "Read failed", str(e), QMessageBox.Close)

    def timestamp(self):
        return datetime.datetime.now().time().isoformat()[:-3]

    def onReceive(self, data):
        self.appendOutputText("\n%s R<-:%s" % (self.timestamp(), data))

    def appendOutputText(self, data, color=Qt.black):
        # the qEditText's "append" methon will add a unnecessary newline.
        # self.txtEdtOutput.append(data.decode('utf-8'))

        tc=self.txtEdtOutput.textColor()
        self.txtEdtOutput.moveCursor(QtGui.QTextCursor.End)
        self.txtEdtOutput.setTextColor(QtGui.QColor(color))
        self.txtEdtOutput.insertPlainText(data)
        self.txtEdtOutput.moveCursor(QtGui.QTextCursor.End)
        self.txtEdtOutput.setTextColor(tc)

    def getPort(self):
        return self.cmbPort.currentText()

    def getDataBits(self):
        return {'5':serial.FIVEBITS,
                '6':serial.SIXBITS,
                '7':serial.SEVENBITS, 
                '8':serial.EIGHTBITS}[self.cmbDataBits.currentText()]

    def getParity(self):
        return {'None' :serial.PARITY_NONE,
                'Even' :serial.PARITY_EVEN,
                'Odd'  :serial.PARITY_ODD,
                'Mark' :serial.PARITY_MARK,
                'Space':serial.PARITY_SPACE}[self.cmbParity.currentText()]

    def getStopBits(self):
        return {'1'  :serial.STOPBITS_ONE,
                '1.5':serial.STOPBITS_ONE_POINT_FIVE,
                '2'  :serial.STOPBITS_TWO}[self.cmbStopBits.currentText()]

    def openPort(self):
        if self.serialport.isOpen():
            return

        _port = self.getPort()
        if '' == _port:
            QMessageBox.information(self.defaultStyleWidget, "Invalid parameters", "Port is empty.")
            return

        _baudrate = self.cmbBaudRate.currentText()
        if '' == _baudrate:
            QMessageBox.information(self.defaultStyleWidget, "Invalid parameters", "Baudrate is empty.")
            return

        self.serialport.port     = _port
        self.serialport.baudrate = _baudrate
        self.serialport.bytesize = self.getDataBits()
        self.serialport.stopbits = self.getStopBits()
        self.serialport.parity   = self.getParity()
        self.serialport.rtscts   = self.chkRTSCTS.isChecked()
        self.serialport.xonxoff  = self.chkXonXoff.isChecked()
        # self.serialport.timeout  = THREAD_TIMEOUT
        # self.serialport.writeTimeout = SERIAL_WRITE_TIMEOUT
        try:
            self.serialport.open()
        except Exception as e:
            QMessageBox.critical(self.defaultStyleWidget, 
                "Could not open serial port", str(e), QMessageBox.Close)
        else:
            self._start_reader()
            self.setWindowTitle("%s on %s [%s, %s%s%s%s%s]" % (
                appInfo.title,
                self.serialport.portstr,
                self.serialport.baudrate,
                self.serialport.bytesize,
                self.serialport.parity,
                self.serialport.stopbits,
                self.serialport.rtscts and ' RTS/CTS' or '',
                self.serialport.xonxoff and ' Xon/Xoff' or '',
                )
            )
            pal = self.btnOpen.palette()
            pal.setColor(QtGui.QPalette.Button, QtGui.QColor(0,0xff,0x7f))
            self.btnOpen.setAutoFillBackground(True)
            self.btnOpen.setPalette(pal)
            self.btnOpen.setText('Close')
            self.btnOpen.update()

    def closePort(self):
        if self.serialport.isOpen():
            self._stop_reader()
            self.serialport.close()
            self.setWindowTitle(appInfo.title)
            pal = self.btnOpen.style().standardPalette()
            self.btnOpen.setAutoFillBackground(True)
            self.btnOpen.setPalette(pal)
            self.btnOpen.setText('Open')
            self.btnOpen.update()

    def _start_reader(self):
        """Start reader thread"""
        self.receiver_thread.start()

    def _stop_reader(self):
        """Stop reader thread only, wait for clean exit of thread"""
        self.receiver_thread.join()

    def onTogglePrtCfgPnl(self):
        if self.actionPort_Config_Panel.isChecked():
            self.dockWidget_PortConfig.show()
        else:
            self.dockWidget_PortConfig.hide()

    def onToggleQckSndPnl(self):
        if self.actionQuick_Send_Panel.isChecked():
            self.dockWidget_QuickSend.show()
        else:
            self.dockWidget_QuickSend.hide()

    def onToggleHexPnl(self):
        if self.actionSend_Hex_Panel.isChecked():
            self.dockWidget_SendHex.show()
        else:
            self.dockWidget_SendHex.hide()

    def onVisiblePrtCfgPnl(self, visible):
        self.actionPort_Config_Panel.setChecked(visible)

    def onVisibleQckSndPnl(self, visible):
        self.actionQuick_Send_Panel.setChecked(visible)

    def onVisibleHexPnl(self, visible):
        self.actionSend_Hex_Panel.setChecked(visible)

    def onLocalEcho(self):
        self._localEcho = self.actionLocal_Echo.isChecked()

    def onAlwaysOnTop(self):
        if self.actionAlways_On_Top.isChecked():
            style = self.windowFlags()
            self.setWindowFlags(style|Qt.WindowStaysOnTopHint)
            self.show()
        else:
            style = self.windowFlags()
            self.setWindowFlags(style & ~Qt.WindowStaysOnTopHint)
            self.show()

    def onOpen(self):
        if self.serialport.isOpen():
            self.closePort()
        else:
            self.openPort()

    def onClear(self):
        self.txtEdtOutput.clear()

    def onSaveLog(self):
        fileName = QFileDialog.getSaveFileName(self, "Save as", os.getcwd(),
            "Log files (*.log);;Text files (*.txt);;All files (*.*)")[0]
        if fileName:
            import codecs
            with codecs.open(fileName, 'w', 'utf-8') as f:
                f.write(self.txtEdtOutput.toPlainText())

    def moveScreenCenter(self):
        w = self.frameGeometry().width()
        h = self.frameGeometry().height()
        desktop = QDesktopWidget()
        screenW = desktop.screen().width()
        screenH = desktop.screen().height()
        self.setGeometry((screenW-w)/2, (screenH-h)/2, w, h)

    def onEnumPorts(self):
        self.cmbPort.clear()
        for p in enum_ports():
            self.cmbPort.addItem(p)

    def onAbout(self):
        QMessageBox.about(self.defaultStyleWidget, "About MyTerm", appInfo.aboutme)

    def onAboutQt(self):
        QMessageBox.aboutQt(self.defaultStyleWidget)

    def onExit(self):
        if self.serialport.isOpen():
            self.closePort()
        self.close()

    def restoreLayout(self):
        if os.path.isfile(get_config_path("UILayout.dat")):
            try:
                f=open(get_config_path("UILayout.dat"), 'rb')
                geometry, state=pickle.load(f)
                self.restoreGeometry(geometry)
                self.restoreState(state)
            except Exception as e:
                print("Exception on restoreLayout, {}".format(e))
        else:
            try:
                f=QFile(':/default_layout_qt5.dat')
                f.open(QIODevice.ReadOnly)
                geometry, state=pickle.loads(f.readAll())
                self.restoreGeometry(geometry)
                self.restoreState(state)
            except Exception as e:
                print("Exception on restoreLayout, {}".format(e))

    def saveLayout(self):
        with open(get_config_path("UILayout.dat"), 'wb') as f:
            pickle.dump((self.saveGeometry(), self.saveState()), f)

    def syncMenu(self):
        self.actionPort_Config_Panel.setChecked(not self.dockWidget_PortConfig.isHidden())
        self.actionQuick_Send_Panel.setChecked(not self.dockWidget_QuickSend.isHidden())
        self.actionSend_Hex_Panel.setChecked(not self.dockWidget_SendHex.isHidden())

    def onViewChanged(self):
        checked = self._viewGroup.checkedAction()
        if checked is None:
            self._viewMode = VIEWMODE_HEX_UPPERCASE
            self.actionHEX_UPPERCASE.setChecked(True)
        else:
            if 'Ascii' in checked.text():
                self._viewMode = VIEWMODE_ASCII
            elif 'lowercase' in checked.text():
                self._viewMode = VIEWMODE_HEX_LOWERCASE
            elif 'UPPERCASE' in checked.text():
                self._viewMode = VIEWMODE_HEX_UPPERCASE

        self.receiver_thread.setViewMode(self._viewMode)
Exemplo n.º 26
0
    def makePopupMenu(self):
        index = self.currentIndex()
        sel = self.getSelection()
        clipboard = qApp.clipboard()

        menu = QMenu(self)

        # Add / remove items
        self.actAddFolder = QAction(QIcon.fromTheme("folder-new"), qApp.translate("outlineBasics", "New Folder"), menu)
        self.actAddFolder.triggered.connect(self.addFolder)
        menu.addAction(self.actAddFolder)

        self.actAddText = QAction(QIcon.fromTheme("document-new"), qApp.translate("outlineBasics", "New Text"), menu)
        self.actAddText.triggered.connect(self.addText)
        menu.addAction(self.actAddText)

        self.actDelete = QAction(QIcon.fromTheme("edit-delete"), qApp.translate("outlineBasics", "Delete"), menu)
        self.actDelete.triggered.connect(self.delete)
        menu.addAction(self.actDelete)

        menu.addSeparator()

        # Copy, cut, paste
        self.actCopy = QAction(QIcon.fromTheme("edit-copy"), qApp.translate("outlineBasics", "Copy"), menu)
        self.actCopy.triggered.connect(self.copy)
        menu.addAction(self.actCopy)

        self.actCut = QAction(QIcon.fromTheme("edit-cut"), qApp.translate("outlineBasics", "Cut"), menu)
        self.actCut.triggered.connect(self.cut)
        menu.addAction(self.actCut)

        self.actPaste = QAction(QIcon.fromTheme("edit-paste"), qApp.translate("outlineBasics", "Paste"), menu)
        self.actPaste.triggered.connect(self.paste)
        menu.addAction(self.actPaste)

        menu.addSeparator()

        # POV
        self.menuPOV = QMenu(qApp.translate("outlineBasics", "Set POV"), menu)
        mw = mainWindow()
        a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuPOV)
        a.triggered.connect(lambda: self.setPOV(""))
        self.menuPOV.addAction(a)
        self.menuPOV.addSeparator()

        menus = []
        for i in [self.tr("Main"), self.tr("Secondary"), self.tr("Minor")]:
            m = QMenu(i, self.menuPOV)
            menus.append(m)
            self.menuPOV.addMenu(m)

        mpr = QSignalMapper(self.menuPOV)
        for i in range(mw.mdlPersos.rowCount()):
            a = QAction(mw.mdlPersos.icon(i), mw.mdlPersos.name(i), self.menuPOV)
            a.triggered.connect(mpr.map)
            mpr.setMapping(a, int(mw.mdlPersos.ID(i)))

            imp = toInt(mw.mdlPersos.importance(i))

            menus[2 - imp].addAction(a)

        mpr.mapped.connect(self.setPOV)
        menu.addMenu(self.menuPOV)

        # Status
        self.menuStatus = QMenu(qApp.translate("outlineBasics", "Set Status"), menu)
        # a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuStatus)
        # a.triggered.connect(lambda: self.setStatus(""))
        # self.menuStatus.addAction(a)
        # self.menuStatus.addSeparator()

        mpr = QSignalMapper(self.menuStatus)
        for i in range(mw.mdlStatus.rowCount()):
            a = QAction(mw.mdlStatus.item(i, 0).text(), self.menuStatus)
            a.triggered.connect(mpr.map)
            mpr.setMapping(a, i)
            self.menuStatus.addAction(a)
        mpr.mapped.connect(self.setStatus)
        menu.addMenu(self.menuStatus)

        # Labels
        self.menuLabel = QMenu(qApp.translate("outlineBasics", "Set Label"), menu)
        mpr = QSignalMapper(self.menuLabel)
        for i in range(mw.mdlLabels.rowCount()):
            a = QAction(mw.mdlLabels.item(i, 0).icon(),
                        mw.mdlLabels.item(i, 0).text(),
                        self.menuLabel)
            a.triggered.connect(mpr.map)
            mpr.setMapping(a, i)
            self.menuLabel.addAction(a)
        mpr.mapped.connect(self.setLabel)
        menu.addMenu(self.menuLabel)

        if len(sel) > 0 and index.isValid() and not index.internalPointer().isFolder() \
                or not clipboard.mimeData().hasFormat("application/xml"):
            self.actPaste.setEnabled(False)

        if len(sel) > 0 and index.isValid() and not index.internalPointer().isFolder():
            self.actAddFolder.setEnabled(False)
            self.actAddText.setEnabled(False)

        if len(sel) == 0:
            self.actCopy.setEnabled(False)
            self.actCut.setEnabled(False)
            self.actDelete.setEnabled(False)
            self.menuPOV.setEnabled(False)
            self.menuStatus.setEnabled(False)
            self.menuLabel.setEnabled(False)

        return menu
Exemplo n.º 27
0
class NumPad(QDialog, Ui_numPad):
    """Numpad for user input.

    Enables the user to insert numbers on a touchscreen.

    """

    def __init__(self, parent: QWidget=None, text: str="") -> None:

        super(NumPad, self).__init__(parent)
        self.setupUi(self)  # type: ignore

        try:
            suffixIndex = text.index(" ")
        except ValueError:
            self.outputLineEdit.setText(text)
        else:
            self.outputLineEdit.setText(text[:suffixIndex])

        self.outputLineEdit.setSelection(0, len(self.outputLineEdit.text()))
        self.outputLineEdit.setFocus()

        self.signal_mapper = QSignalMapper(self)
        self.signal_mapper.setMapping(self.button0, "0")
        self.signal_mapper.setMapping(self.button1, "1")
        self.signal_mapper.setMapping(self.button2, "2")
        self.signal_mapper.setMapping(self.button3, "3")
        self.signal_mapper.setMapping(self.button4, "4")
        self.signal_mapper.setMapping(self.button5, "5")
        self.signal_mapper.setMapping(self.button6, "6")
        self.signal_mapper.setMapping(self.button7, "7")
        self.signal_mapper.setMapping(self.button8, "8")
        self.signal_mapper.setMapping(self.button9, "9")
        self.signal_mapper.setMapping(self.buttonDecimal, DECIMAL_SEPARATOR)
        self.signal_mapper.setMapping(self.buttonDel, "DEL")

        self.button0.pressed.connect(self.signal_mapper.map)
        self.button1.pressed.connect(self.signal_mapper.map)
        self.button2.pressed.connect(self.signal_mapper.map)
        self.button3.pressed.connect(self.signal_mapper.map)
        self.button4.pressed.connect(self.signal_mapper.map)
        self.button5.pressed.connect(self.signal_mapper.map)
        self.button6.pressed.connect(self.signal_mapper.map)
        self.button7.pressed.connect(self.signal_mapper.map)
        self.button8.pressed.connect(self.signal_mapper.map)
        self.button9.pressed.connect(self.signal_mapper.map)
        self.buttonDecimal.pressed.connect(self.signal_mapper.map)
        self.buttonDel.pressed.connect(self.signal_mapper.map)

        self.signal_mapper.mapped[str].connect(self.button_pressed)  # type: ignore
        self.buttonOK.pressed.connect(self.accept)
        self.buttonCancel.pressed.connect(self.close)
        self.outputLineEdit.focusOutEvent = self.focusOutEvent

        doubleValidator = QDoubleValidator()
        self.outputLineEdit.setValidator(doubleValidator)

    @pyqtSlot(str)
    def button_pressed(self, value: str) -> None:
        """Handle a pressed button."""
        cursor_position = self.outputLineEdit.cursorPosition()

        # Bei markierten Zeichen diese löschen
        if len(self.outputLineEdit.selectedText()) > 0:

            cursor_position = self.outputLineEdit.selectionStart()
            selection_length = len(self.outputLineEdit.selectedText())

            self.outputLineEdit.setText(
                string_remove_by_index(
                    self.outputLineEdit.text(),
                    start_index=cursor_position,
                    length=selection_length
                )
            )

        # Bei DEL letztes Zeichen löschen
        elif value == "DEL":
            # self.outputLineEdit.setText(self.outputLineEdit.text().remove(
            #    self.outputLineEdit.cursorPosition() - 1, 1))

            self.outputLineEdit.setText(
                string_remove_by_index(
                    self.outputLineEdit.text(),
                    start_index=self.outputLineEdit.cursorPosition() - 1,
                    length=1)
            )

        # Wenn nicht DEL, dann Zeichen schreiben
        if not value == "DEL":
            if value == DECIMAL_SEPARATOR:
                if DECIMAL_SEPARATOR not in self.outputLineEdit.text():
                    self.outputLineEdit.setText(
                        string_insert(self.outputLineEdit.text(),
                                      str(value), cursor_position)
                    )
            else:
                self.outputLineEdit.setText(
                    string_insert(self.outputLineEdit.text(), str(value),
                                  cursor_position)
                )
Exemplo n.º 28
0
class DataLoader(QWidget):
    dataset_changed = pyqtSignal()
    new_file_in_dataset = pyqtSignal(str)
    deembedding_changed = pyqtSignal()
    dut_folder = None
    dut_files = None
    dummy_raw = None
    dummy_deem = None
    dummy = None
    dummy_file = None
    dummy_toggle_status = True
    thru = None
    thru_file = None
    thru_toggle_status = True
    ra = None
    duts = {}

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

        file_icon = QIcon('../icons/file.png')
        folder_icon = QIcon('../icons/folder.png')
        plot_icon = QIcon('../icons/plot.png')
        self.toggleon_icon = QIcon('../icons/on.png')
        self.toggleoff_icon = QIcon('../icons/off.png')

        self.txt_dut = QLineEdit()
        self.btn_browsedut_file = QPushButton(file_icon, '')
        self.btn_browsedut_folder = QPushButton(folder_icon, '')

        self.txt_thru = QLineEdit()
        self.btn_browsethru_file = QPushButton(file_icon, '')
        self.btn_browsethru_folder = QPushButton(folder_icon, '')
        self.btn_togglethru = QPushButton(self.toggleon_icon, '')
        self.btn_plotthru = QPushButton(plot_icon, '')

        self.txt_dummy = QLineEdit()
        self.btn_browsedummy_file = QPushButton(file_icon, '')
        self.btn_browsedummy_folder = QPushButton(folder_icon, '')
        self.btn_toggledummy = QPushButton(self.toggleon_icon, '')
        self.btn_plotdummy = QPushButton(plot_icon, '')

        for w in [
                self.btn_browsedut_file, self.btn_browsethru_file,
                self.btn_browsedummy_file
        ]:
            w.setToolTip('Browse file')
        for w in [
                self.btn_browsedut_folder, self.btn_browsethru_folder,
                self.btn_browsedummy_folder
        ]:
            w.setToolTip('Browse folder')
        for w in [self.btn_togglethru, self.btn_toggledummy]:
            w.setEnabled(False)
        for w in [self.btn_plotthru, self.btn_plotdummy]:
            w.setToolTip('Plot')
            w.setEnabled(False)

        self.btn_load = QPushButton('Load dataset')
        self.txt_ra = QLineEdit()

        l = QVBoxLayout()
        for field in [[
                QLabel('DUT:'), self.txt_dut, self.btn_browsedut_file,
                self.btn_browsedut_folder
        ],
                      [
                          QLabel('Thru:'), self.txt_thru,
                          self.btn_browsethru_file, self.btn_browsethru_folder,
                          self.btn_togglethru, self.btn_plotthru
                      ],
                      [
                          QLabel('Dummy:'), self.txt_dummy,
                          self.btn_browsedummy_file,
                          self.btn_browsedummy_folder, self.btn_toggledummy,
                          self.btn_plotdummy
                      ]]:
            hl = QHBoxLayout()
            for w in field:
                hl.addWidget(w)
            l.addLayout(hl)
        hl = QHBoxLayout()
        for w in [QLabel('Contact resistance:'), self.txt_ra, self.btn_load]:
            hl.addWidget(w)
        l.addLayout(hl)
        self.setLayout(l)

        # initialise data loader
        self.clear()

        # set up folder watcher
        self.timer = QTimer()
        self.timer.setInterval(1000)  # check for changes every second
        self.timer.timeout.connect(self.watch_folder)

        # make connections
        self.map_browse = QSignalMapper(self)
        for x in ['dut', 'thru', 'dummy']:
            self.__dict__['btn_browse' + x + '_folder'].clicked.connect(
                self.map_browse.map)
            self.__dict__['btn_browse' + x + '_file'].clicked.connect(
                self.map_browse.map)
            self.map_browse.setMapping(
                self.__dict__['btn_browse' + x + '_folder'], x + '_folder')
            self.map_browse.setMapping(
                self.__dict__['btn_browse' + x + '_file'], x + '_file')
        self.map_browse.mapped[str].connect(self.browse)
        self.btn_togglethru.clicked.connect(self.toggle_thru)
        self.btn_toggledummy.clicked.connect(self.toggle_dummy)
        self.btn_plotdummy.clicked.connect(self.plot_dummy)
        self.btn_plotthru.clicked.connect(self.plot_thru)
        self.btn_load.clicked.connect(self.load_dataset)

    def browse(self, x):
        # open browser and update the text field
        field, type = x.split('_')
        if type == 'folder':
            folder = QFileDialog.getExistingDirectory(self, 'Choose folder')
            if folder:
                self.__dict__['txt_' + field].setText(folder)
        elif type == 'file':
            filename, filter = QFileDialog.getOpenFileName(
                self, 'Choose file', filter='*.txt *.s2p *.dat')
            if filename:
                self.__dict__['txt_' + field].setText(filename)

    def get_spectra_files(self, path, tellmeifitsafolder=False):
        supported_exts = ['.txt', '.s2p', '.dat']
        folder = None
        files = []
        itsafolder = False

        if os.path.isdir(path):
            folder = path
            for ext in supported_exts:
                files += [
                    os.path.basename(x)
                    for x in sorted(glob(os.path.join(folder, '*' + ext)))
                ]
            itsafolder = True
        elif os.path.isfile(path):
            basename, ext = os.path.splitext(path)
            if ext.lower() in supported_exts:
                folder = os.path.dirname(path)
                files += [path]
        else:
            pass

        if tellmeifitsafolder:
            return folder, files, itsafolder
        else:
            return folder, files

    @pyqtSlot()
    def load_dataset(self, dut=None, thru=None, dummy=None, ra=None):
        # This function inspects the provided folders and will try to load the dummy and thru spectra for de-embedding.
        # It does not load the DUT spectra, since this might take a long time, they can be accessed via the get_spectrum
        # function.

        # tidy up first
        self.empty_cache()
        self.dut_folder = None
        self.dut_files = None
        self.thru_file = None
        self.thru = None
        self.dummy_file = None
        self.dummy_raw = None
        self.dummy_deem = None
        self.dummy = None
        self.btn_toggledummy.setEnabled(False)
        self.btn_togglethru.setEnabled(False)
        self.btn_plotdummy.setEnabled(False)
        self.btn_plotthru.setEnabled(False)

        # take what you can from the function parameters, the rest from the text fields
        dut = str(self.txt_dut.text()) if not dut else dut
        thru = str(self.txt_thru.text()) if not thru else thru
        dummy = str(self.txt_dummy.text()) if not dummy else dummy
        ra = str(self.txt_ra.text()) if not ra else ra

        # update the text fields
        self.txt_dut.setText(dut)
        self.txt_thru.setText(thru)
        self.txt_dummy.setText(dummy)
        self.txt_ra.setText(str(ra) if ra else '0')

        # check provided files
        self.dut_folder, self.dut_files, itsafolder = self.get_spectra_files(
            dut, tellmeifitsafolder=True)
        if not self.dut_files:
            QMessageBox.warning(self, 'Warning',
                                'Please select a valid DUT folder or file')

        # if the user loaded a folder, switch on the folder watcher
        if itsafolder:
            self.timer.start()
        else:
            self.timer.stop()

        thru_folder, thru_files = self.get_spectra_files(thru)
        if len(thru_files) != 1:
            self.txt_thru.setText('Please select a valid thru folder or file')
        else:
            self.thru_file = os.path.join(thru_folder, thru_files[0])

        dummy_folder, dummy_files = self.get_spectra_files(dummy)
        if len(dummy_files) != 1:
            self.txt_dummy.setText(
                'Please select a valid dummy folder or file')
        else:
            self.dummy_file = os.path.join(dummy_folder, dummy_files[0])

        # load the provided files
        if self.dummy_file:
            try:
                self.dummy_raw = Network(self.dummy_file)
                assert self.dummy_raw.number_of_ports == 2
                self.btn_toggledummy.setEnabled(True)
                self.btn_plotdummy.setEnabled(True)
            except Exception as e:
                QMessageBox.warning(
                    self, 'Warning', 'File: ' + self.dummy_file +
                    ' is not a valid 2-port RF spectrum file.')
                self.dummy_raw = None

        if self.thru_file:
            try:
                self.thru = Network(self.thru_file)
                assert self.thru.number_of_ports == 2
                self.btn_togglethru.setEnabled(True)
                self.btn_plotthru.setEnabled(True)
            except Exception as e:
                QMessageBox.warning(
                    self, 'Warning', 'File: ' + self.thru_file +
                    ' is not a valid 2-port RF spectrum file.')
                self.thru = None

        self.dummy_deem = None
        if self.dummy_raw and self.thru:
            # check for mHz deviation, since sometimes the frequency value saved is
            # not 100% equal when importing from different file formats...
            if not check_deembedding_compatibility(self.dummy_raw, self.thru):
                QMessageBox.warning(self, 'Warning',
                                    'Dummy and thru are not compatible')
                self.dummy_raw = None
                self.thru = None
                for w in [
                        self.btn_toggledummy, self.btn_togglethru,
                        self.btn_plotdummy, self.btn_plotthru
                ]:
                    w.setEnabled(False)
            else:
                self.dummy_deem = self.dummy_raw.deembed_thru(self.thru)

        self.dummy = self.dummy_deem if (self.thru_toggle_status
                                         and self.thru) else self.dummy_raw

        self.dataset_changed.emit()

    def get_spectrum(self, index):
        # check if the spectrum is already prepared
        filename = self.dut_files[index]
        if filename in self.duts:
            return self.duts[filename]
        else:
            # try to load spectrum and check its compatibility
            try:
                dut = Network(os.path.join(self.dut_folder, filename))
            except Exception:
                QMessageBox.warning(
                    self, 'Warning',
                    'File: ' + filename + ' is not a valid RF spectrum file.')
                return None
            if self.thru and self.thru_toggle_status:
                if check_deembedding_compatibility(dut, self.thru):
                    dut = dut.deembed_thru(self.thru)
                else:
                    QMessageBox.warning(self, 'Warning',
                                        'Could not deembed thru.')
                    self.thru_toggle_status = False
                    self.btn_togglethru.setIcon(self.toggleoff_icon)
            if self.dummy and self.dummy_toggle_status:
                if check_deembedding_compatibility(dut, self.dummy):
                    dut.y -= self.dummy.y
                else:
                    QMessageBox.warning(self, 'Warning',
                                        'Could not deembed dummy.')
                    self.dummy_toggle_status = False
                    self.btn_toggledummy.setIcon(self.toggleoff_icon)
            try:
                ra = float(self.txt_ra.text())
            except:
                QMessageBox.warning(
                    self, 'Warning',
                    'Invalid value for contact resistance. Using zero.')
                ra = 0.
            if not ra == 0:
                y = np.zeros(dut.y.shape, dtype=complex)
                y[:, 0, 0] = 1. / (1. / dut.y[:, 0, 0] - ra)
                y[:, 0, 1] = 1. / (1. / dut.y[:, 0, 1] + ra)
                y[:, 1, 0] = 1. / (1. / dut.y[:, 1, 0] + ra)
                y[:, 1, 1] = 1. / (1. / dut.y[:, 1, 1] - ra)
                dut.y = y
            self.duts[filename] = dut
            return dut

    def empty_cache(self):
        self.duts = {}  # empty the DUT dictionary

    def toggle_thru(self):
        self.empty_cache()
        self.thru_toggle_status = not self.thru_toggle_status
        self.btn_togglethru.setIcon(
            self.toggleon_icon if self.thru_toggle_status else self.
            toggleoff_icon)
        self.dummy = self.dummy_deem if (self.thru_toggle_status
                                         and self.thru) else self.dummy_raw
        self.deembedding_changed.emit()

    def toggle_dummy(self):
        self.empty_cache()
        self.dummy_toggle_status = not self.dummy_toggle_status
        self.btn_toggledummy.setIcon(
            self.toggleon_icon if self.dummy_toggle_status else self.
            toggleoff_icon)
        self.deembedding_changed.emit()

    def plot_dummy(self):
        dialog = QDialog(self)
        dialog.setWindowTitle('Dummy' + (' (deembedded thru)' if self.thru
                                         and self.thru_toggle_status else ''))
        dialog.setModal(True)
        figure = plt.figure()
        canvas = FigureCanvas(figure)
        toolbar = NavigationToolbar(canvas, dialog)
        l = QVBoxLayout()
        for w in [toolbar, canvas]:
            l.addWidget(w)
        dialog.setLayout(l)
        self.dummy.plot_mat('y', ylim=1e-3, fig=figure)
        figure.suptitle(self.dummy.name)
        figure.subplots_adjust(top=0.9)
        canvas.draw()
        dialog.show()

    def plot_thru(self):
        dialog = QDialog(self)
        dialog.setWindowTitle('Thru')
        dialog.setModal(True)
        figure = plt.figure()
        ax = figure.add_subplot(111)
        canvas = FigureCanvas(figure)
        toolbar = NavigationToolbar(canvas, dialog)
        l = QVBoxLayout()
        for w in [toolbar, canvas]:
            l.addWidget(w)
        dialog.setLayout(l)
        name = self.thru.name
        self.thru.name = None  # workaround to avoid cluttering the legend
        self.thru.plot_s_deg(ax=ax)
        ax.set_title(name)
        self.thru.name = name
        canvas.draw()
        dialog.show()

    def clear(self):
        self.txt_dut.setText('Path to DUT...')
        self.txt_thru.setText('Path to thru...')
        self.txt_dummy.setText('Path to dummy...')
        self.txt_ra.setText('0')

    def watch_folder(self):
        folder, files = self.get_spectra_files(self.dut_folder)
        for f in files:
            if f not in self.dut_files:
                self.dut_files.append(f)
                self.new_file_in_dataset.emit(f)
Exemplo n.º 29
0
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.mdiArea = QMdiArea()
        self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.setCentralWidget(self.mdiArea)

        self.mdiArea.subWindowActivated.connect(self.updateMenus)
        self.windowMapper = QSignalMapper(self)
        self.windowMapper.mapped[QWidget].connect(self.setActiveSubWindow)

        self.createActions()
        self.createMenus()
        self.createToolBars()
        self.createStatusBar()
        self.updateMenus()

        self.readSettings()

        self.setWindowTitle("MDI Test")

    def closeEvent(self, event):
        self.mdiArea.closeAllSubWindows()
        if self.mdiArea.currentSubWindow():
            event.ignore()
        else:
            self.writeSettings()
            event.accept()

    def newFile(self):
        child = self.createMdiChild()
        child.newFile()
        child.show()

    def open(self):
        fileName, _ = QFileDialog.getOpenFileName(self)
        if fileName:
            existing = self.findMdiChild(fileName)
            if existing:
                self.mdiArea.setActiveSubWindow(existing)
                return

            child = self.createMdiChild()
            if child.loadFile(fileName):
                self.statusBar().showMessage("File loaded", 2000)
                child.show()
            else:
                child.close()

    def save(self):
        if self.activeMdiChild() and self.activeMdiChild().save():
            self.statusBar().showMessage("File saved", 2000)

    def saveAs(self):
        if self.activeMdiChild() and self.activeMdiChild().saveAs():
            self.statusBar().showMessage("File saved", 2000)

    def cut(self):
        if self.activeMdiChild():
            self.activeMdiChild().cut()

    def copy(self):
        if self.activeMdiChild():
            self.activeMdiChild().copy()

    def paste(self):
        if self.activeMdiChild():
            self.activeMdiChild().paste()

    def about(self):
        QMessageBox.about(
            self, "About MDI",
            "The <b>MDI</b> example demonstrates how to write multiple "
            "document interface applications using Qt.")

    def updateMenus(self):
        hasMdiChild = (self.activeMdiChild() is not None)
        #self.saveAct.setEnabled(hasMdiChild)
        #self.saveAsAct.setEnabled(hasMdiChild)
        self.pasteAct.setEnabled(hasMdiChild)
        self.closeAct.setEnabled(hasMdiChild)
        self.closeAllAct.setEnabled(hasMdiChild)
        self.tileAct.setEnabled(hasMdiChild)
        self.cascadeAct.setEnabled(hasMdiChild)
        self.nextAct.setEnabled(hasMdiChild)
        self.previousAct.setEnabled(hasMdiChild)
        self.separatorAct.setVisible(hasMdiChild)

        hasSelection = (self.activeMdiChild() is not None
                        and self.activeMdiChild().textCursor().hasSelection())
        self.cutAct.setEnabled(hasSelection)
        self.copyAct.setEnabled(hasSelection)

    def updateWindowMenu(self):
        self.windowMenu.clear()
        self.windowMenu.addAction(self.closeAct)
        self.windowMenu.addAction(self.closeAllAct)
        self.windowMenu.addSeparator()
        self.windowMenu.addAction(self.tileAct)
        self.windowMenu.addAction(self.cascadeAct)
        self.windowMenu.addSeparator()
        self.windowMenu.addAction(self.nextAct)
        self.windowMenu.addAction(self.previousAct)
        self.windowMenu.addAction(self.separatorAct)

        windows = self.mdiArea.subWindowList()
        self.separatorAct.setVisible(len(windows) != 0)

        for i, window in enumerate(windows):
            child = window.widget()

            text = "%d %s" % (i + 1, child.userFriendlyCurrentFile())
            if i < 9:
                text = '&' + text

            action = self.windowMenu.addAction(text)
            action.setCheckable(True)
            action.setChecked(child is self.activeMdiChild())
            action.triggered.connect(self.windowMapper.map)
            self.windowMapper.setMapping(action, window)

    def createMdiChild(self):
        child = MdiChild()
        self.mdiArea.addSubWindow(child)

        child.copyAvailable.connect(self.cutAct.setEnabled)
        child.copyAvailable.connect(self.copyAct.setEnabled)

        return child

    # showntell
    def createMdiChild15(self):
        child = MdiChild_ShowNTell()
        self.mdiArea.addSubWindow(child)
        return child

    #MNIST
    def show_n_tell(self):
        print('show_n_tell....')
        child = self.createMdiChild15()
        print('self.createMdiChild15')
        #child.resize(830,480)
        #print('self.createMdiChild15')
        child.show
        print('child.show()')

    def createActions(self):

        self.cutAct = QAction(
            QIcon(':/images/cut.png'),
            "Cu&t",
            self,
            shortcut=QKeySequence.Cut,
            statusTip="Cut the current selection's contents to the clipboard",
            triggered=self.cut)

        self.copyAct = QAction(
            QIcon(':/images/copy.png'),
            "&Copy",
            self,
            shortcut=QKeySequence.Copy,
            statusTip="Copy the current selection's contents to the clipboard",
            triggered=self.copy)

        self.pasteAct = QAction(
            QIcon(':/images/paste.png'),
            "&Paste",
            self,
            shortcut=QKeySequence.Paste,
            statusTip=
            "Paste the clipboard's contents into the current selection",
            triggered=self.paste)

        self.closeAct = QAction("Cl&ose",
                                self,
                                statusTip="Close the active window",
                                triggered=self.mdiArea.closeActiveSubWindow)

        self.closeAllAct = QAction("Close &All",
                                   self,
                                   statusTip="Close all the windows",
                                   triggered=self.mdiArea.closeAllSubWindows)

        self.tileAct = QAction("&Tile",
                               self,
                               statusTip="Tile the windows",
                               triggered=self.mdiArea.tileSubWindows)

        self.cascadeAct = QAction("&Cascade",
                                  self,
                                  statusTip="Cascade the windows",
                                  triggered=self.mdiArea.cascadeSubWindows)

        self.nextAct = QAction("Ne&xt",
                               self,
                               shortcut=QKeySequence.NextChild,
                               statusTip="Move the focus to the next window",
                               triggered=self.mdiArea.activateNextSubWindow)

        self.previousAct = QAction(
            "Pre&vious",
            self,
            shortcut=QKeySequence.PreviousChild,
            statusTip="Move the focus to the previous window",
            triggered=self.mdiArea.activatePreviousSubWindow)

        self.separatorAct = QAction(self)
        self.separatorAct.setSeparator(True)

        # 메뉴 ACTION을 다이나믹하게 연결해준다
        self.MenuActRef = {
            'NewFileAct': 0,
            'OpnFileAct': 0,
            'SavFileAct': 0,
            'SavASFileAct': 0,
            'AboutAct': 0,
            'AboutQTAct': 0,
            'ExitAct': 0,
            'SwitchLayout': 0,
            'ShowNTell': 0
        }

        # ******* Create the File Menu *******
        self.NewFileAct = QAction(QIcon(':/images/new.png'), '&New File', self)
        self.NewFileAct.setShortcut("Ctrl+N")
        self.NewFileAct.setStatusTip('Create a New  File')
        self.NewFileAct.triggered.connect(self.newFile)
        self.MenuActRef['NewFileAct'] = self.NewFileAct

        #self.newAct = QAction(QIcon(':/images/new.png'), "&New", self,
        #        shortcut=QKeySequence.New, statusTip="Create a new file",
        #        triggered=self.newFile)

        # ******* Open File Menu Items *******
        self.OpnFileAct = QAction(QIcon(':/images/open.png'), '&Open File',
                                  self)
        self.OpnFileAct.setShortcut("Ctrl+O")
        self.OpnFileAct.setStatusTip('Open an Existing File')
        self.OpnFileAct.triggered.connect(self.open)
        self.MenuActRef['OpnFileAct'] = self.OpnFileAct

        #self.openAct = QAction(QIcon(':/images/open.png'), "&Open...", self,
        #        shortcut=QKeySequence.Open, statusTip="Open an existing file",
        #        triggered=self.open)

        # ******* Save File Menu Items *******
        self.SavFileAct = QAction(QIcon(':/images/save.png'), '&Save File',
                                  self)
        self.SavFileAct.setShortcut("Ctrl+S")
        self.SavFileAct.setStatusTip('Save Current  File')
        self.SavFileAct.triggered.connect(self.save)
        self.MenuActRef['SavFileAct'] = self.SavFileAct

        #self.saveAct = QAction(QIcon(':/images/save.png'), "&Save", self,
        #        shortcut=QKeySequence.Save,
        #        statusTip="Save the document to disk", triggered=self.save)

        # ******* SaveAS File Menu Items *******
        self.SavASFileAct = QAction('Save &As File', self)
        self.SavASFileAct.setShortcut("Ctrl+A")
        self.SavASFileAct.setStatusTip('Save Current  File  under a new name ')
        self.SavASFileAct.triggered.connect(self.saveAs)
        self.MenuActRef['SavASFileAct'] = self.SavASFileAct

        #self.saveAsAct = QAction("Save &As...", self,
        #        shortcut=QKeySequence.SaveAs,
        #        statusTip="Save the document under a new name",
        #        triggered=self.saveAs)

        # ******* About Menu Items *******
        self.aboutAct = QAction("&About", self)
        self.aboutAct.setStatusTip("Show the application's About box")
        self.aboutAct.triggered.connect(self.about)
        self.MenuActRef['AboutAct'] = self.aboutAct

        # ******* About QT Menu Items *******
        self.aboutAct = QAction("About &Qt", self)
        self.aboutAct.setStatusTip("Show the Qt library's About box")
        self.aboutAct.triggered.connect(QApplication.instance().aboutQt)
        self.MenuActRef['AboutQTAct'] = self.aboutAct

        # ******* Exit Menu Items *******
        self.exitAct = QAction("E&xit", self)
        self.exitAct.setStatusTip("Exit the application")
        self.exitAct.triggered.connect(QApplication.instance().closeAllWindows)
        self.MenuActRef['ExitAct'] = self.exitAct

        #self.exitAct = QAction("E&xit", self, shortcut=QKeySequence.Quit,
        #        statusTip="Exit the application",
        #        triggered=QApplication.instance().closeAllWindows)

        # ******* Switch layout Items *******
        self.SwitchLayout = QAction("&Switch layout direction", self)
        self.SwitchLayout.setStatusTip("Switch layout direction")
        self.SwitchLayout.triggered.connect(self.switchLayoutDirection)
        self.MenuActRef['SwitchLayout'] = self.SwitchLayout

        #self.SwitchLayout = QAction("&Switch layout direction", self,
        #        statusTip="Switch layout direction",
        #        triggered=self.switchLayoutDirection)

        # ******* Switch layout Items *******
        self.ShowNTell = QAction("&ShowNTell", self)
        self.ShowNTell.setStatusTip("&ShowNTell")
        self.ShowNTell.triggered.connect(self.show_n_tell)
        self.MenuActRef['ShowNTell'] = self.ShowNTell

    #self.ShowNTell = QAction("&show_n_tell", self,
    #        statusTip="show_n_tell",
    #         triggered=self.show_n_tell)

    def createMenus(self):

        # 메뉴를 다이나믹하게 생성하고 연결함
        self.MenuLayout = {
            0: {
                'addMenu': '&File',
                'addToolMenu': 'File'
            },
            1: {
                'addDynamic': 'NewFileAct',
                'addToolbar': 'NewFileAct'
            },
            2: {
                'addDynamic': 'OpnFileAct',
                'addToolbar': 'OpnFileAct'
            },
            3: {
                'addDynamic': 'SavFileAct',
                'addToolbar': 'SavFileAct'
            },
            4: {
                'addDynamic': 'SavASFileAct'
            },
            5: {
                'addSeparator': ''
            },
            6: {
                'addDynamic': 'SwitchLayout'
            },
            7: {
                'addDynamic': 'ExitAct'
            },
            8: {
                'addMenu': '&Edit'
            },
            9: {
                'addAction': self.cutAct
            },
            10: {
                'addAction': self.copyAct
            },
            11: {
                'addAction': self.pasteAct
            },
            12: {
                'addMenu': '&Window'
            },
            13: {
                'updateMenu': ''
            },
            14: {
                'addSeparator': ''
            },
            15: {
                'addMenu': '&Help'
            },
            16: {
                'addDynamic': 'AboutAct'
            },
            17: {
                'addDynamic': 'AboutQTAct'
            },
            18: {
                'addMenu': '&MNIST'
            },
            19: {
                'addDynamic': 'ShowNTell'
            }
        }

        for idx in self.MenuLayout:
            item = self.MenuLayout[idx]

            if 'addMenu' in item.keys():
                self.windowMenu = self.menuBar().addMenu(item['addMenu'])

            elif 'addAction' in item.keys():
                self.windowMenu.addAction(item['addAction'])

            elif 'addSeparator' in item.keys():
                self.windowMenu.addSeparator()

            elif 'updateMenu' in item.keys():
                self.updateWindowMenu()
                self.windowMenu.aboutToShow.connect(self.updateWindowMenu)

            # 메뉴 ACTION을 다이나믹하게
            elif 'addDynamic' in item.keys():
                self.windowMenu.addAction(self.MenuActRef[item['addDynamic']])

    def createToolBars(self):

        for idx in self.MenuLayout:
            item = self.MenuLayout[idx]

            if 'addToolMenu' in item.keys():
                self.fileToolBar = self.addToolBar(item['addToolMenu'])

            elif 'addToolbar' in item.keys():
                self.fileToolBar.addAction(self.MenuActRef[item['addDynamic']])

        #self.fileToolBar = self.addToolBar("File")
        #self.fileToolBar.addAction(self.MenuActRef['NewFileAct'])
        #self.fileToolBar.addAction(self.MenuActRef['OpnFileAct'])
        #self.fileToolBar.addAction(self.MenuActRef['SavFileAct'])
        #self.fileToolBar.addAction(self.newAct)
        #self.fileToolBar.addAction(self.openAct)
        #self.fileToolBar.addAction(self.saveAct)

        self.editToolBar = self.addToolBar("Edit")
        self.editToolBar.addAction(self.cutAct)
        self.editToolBar.addAction(self.copyAct)
        self.editToolBar.addAction(self.pasteAct)

    def createStatusBar(self):
        self.statusBar().showMessage("Ready")

    def readSettings(self):
        settings = QSettings('NH-Soft', 'MDI Example')
        pos = settings.value('pos', QPoint(200, 200))
        size = settings.value('size', QSize(400, 400))
        self.move(pos)
        self.resize(size)

    def writeSettings(self):
        settings = QSettings('NH-Soft', 'MDI Example')
        settings.setValue('pos', self.pos())
        settings.setValue('size', self.size())

    def activeMdiChild(self):
        activeSubWindow = self.mdiArea.activeSubWindow()
        if activeSubWindow:
            return activeSubWindow.widget()
        return None

    def findMdiChild(self, fileName):
        canonicalFilePath = QFileInfo(fileName).canonicalFilePath()

        for window in self.mdiArea.subWindowList():
            if window.widget().currentFile() == canonicalFilePath:
                return window
        return None

    def switchLayoutDirection(self):
        if self.layoutDirection() == Qt.LeftToRight:
            QApplication.setLayoutDirection(Qt.RightToLeft)
        else:
            QApplication.setLayoutDirection(Qt.LeftToRight)

    def setActiveSubWindow(self, window):
        if window:
            self.mdiArea.setActiveSubWindow(window)
Exemplo n.º 30
0
class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        self.mdi = QMdiArea()
        self.setCentralWidget(self.mdi)

        fileNewAction = self.createAction("&New", self.fileNew,
                                          QKeySequence.New, "filenew",
                                          "Create a text file")
        fileOpenAction = self.createAction("&Open...", self.fileOpen,
                                           QKeySequence.Open, "fileopen",
                                           "Open an existing text file")
        fileSaveAction = self.createAction("&Save", self.fileSave,
                                           QKeySequence.Save, "filesave",
                                           "Save the text")
        fileSaveAsAction = self.createAction(
            "Save &As...",
            self.fileSaveAs,
            icon="filesaveas",
            tip="Save the text using a new filename")
        fileSaveAllAction = self.createAction("Save A&ll",
                                              self.fileSaveAll,
                                              "filesave",
                                              tip="Save all the files")
        fileQuitAction = self.createAction("&Quit", self.close, "Ctrl+Q",
                                           "filequit", "Close the application")
        editCopyAction = self.createAction("&Copy", self.editCopy,
                                           QKeySequence.Copy, "editcopy",
                                           "Copy text to the clipboard")
        editCutAction = self.createAction("Cu&t", self.editCut,
                                          QKeySequence.Cut, "editcut",
                                          "Cut text to the clipboard")
        editPasteAction = self.createAction("&Paste", self.editPaste,
                                            QKeySequence.Paste, "editpaste",
                                            "Paste in the clipboard's text")
        self.windowNextAction = self.createAction(
            "&Next", self.mdi.activateNextSubWindow, QKeySequence.NextChild)
        self.windowPrevAction = self.createAction(
            "&Previous", self.mdi.activatePreviousSubWindow,
            QKeySequence.PreviousChild)
        self.windowCascadeAction = self.createAction(
            "Casca&de", self.mdi.cascadeSubWindows)
        self.windowTileAction = self.createAction("&Tile",
                                                  self.mdi.tileSubWindows)
        self.windowRestoreAction = self.createAction("&Restore All",
                                                     self.windowRestoreAll)
        self.windowMinimizeAction = self.createAction("&Iconize All",
                                                      self.windowMinimizeAll)
        #self.windowArrangeIconsAction = self.createAction(
        #       "&Arrange Icons", self.mdi.arrangeIcons)
        self.windowArrangeIconsAction = self.createAction(
            "&Arrange Icons", self.windowMinimizeAll)
        self.windowCloseAction = self.createAction(
            "&Close", self.mdi.closeActiveSubWindow, QKeySequence.Close)
        self.windowMapper = QSignalMapper(self)
        self.windowMapper.mapped[QWidget].connect(self.mdi.setActiveSubWindow)
        fileMenu = self.menuBar().addMenu("&File")
        self.addActions(
            fileMenu,
            (fileNewAction, fileOpenAction, fileSaveAction, fileSaveAsAction,
             fileSaveAllAction, None, fileQuitAction))
        editMenu = self.menuBar().addMenu("&Edit")
        self.addActions(editMenu,
                        (editCopyAction, editCutAction, editPasteAction))
        self.windowMenu = self.menuBar().addMenu("&Window")
        self.windowMenu.aboutToShow.connect(self.updateWindowMenu)
        fileToolbar = self.addToolBar("File")
        fileToolbar.setObjectName("FileToolbar")
        self.addActions(fileToolbar,
                        (fileNewAction, fileOpenAction, fileSaveAction))
        editToolbar = self.addToolBar("Edit")
        editToolbar.setObjectName("EditToolbar")
        self.addActions(editToolbar,
                        (editCopyAction, editCutAction, editPasteAction))

        settings = QSettings()
        if settings.value("MainWindow/Geometry") or settings.value(
                "MainWindow/State"):
            self.restoreGeometry(
                QByteArray(settings.value("MainWindow/Geometry")))
            self.restoreState(QByteArray(settings.value("MainWindow/State")))

        status = self.statusBar()
        status.setSizeGripEnabled(False)
        status.showMessage("Ready", 5000)

        self.updateWindowMenu()
        self.setWindowTitle("Text Editor")
        QTimer.singleShot(0, self.loadFiles)

    def createAction(self,
                     text,
                     slot=None,
                     shortcut=None,
                     icon=None,
                     tip=None,
                     checkable=False,
                     signal="triggered()"):
        action = QAction(text, self)
        if icon is not None:
            action.setIcon(QIcon(":/{0}.png".format(icon)))
        if shortcut is not None:
            action.setShortcut(shortcut)
        if tip is not None:
            action.setToolTip(tip)
            action.setStatusTip(tip)
        if slot is not None:
            action.triggered.connect(slot)
        if checkable:
            action.setCheckable(True)
        return action

    def addActions(self, target, actions):
        for action in actions:
            if action is None:
                target.addSeparator()
            else:
                target.addAction(action)

    def closeEvent(self, event):
        failures = []
        for textEdit in self.mdi.subWindowList():
            textEdit = textEdit.widget()
            if textEdit.isModified():
                try:
                    textEdit.save()
                except IOError as e:
                    failures.append(str(e))
        if (failures and QMessageBox.warning(
                self, "Text Editor -- Save Error",
                "Failed to save{0}\nQuit anyway?".format(
                    "\n\t".join(failures)), QMessageBox.Yes | QMessageBox.No)
                == QMessageBox.No):
            event.ignore()
            return
        settings = QSettings()
        settings.setValue("MainWindow/Geometry", self.saveGeometry())
        settings.setValue("MainWindow/State", self.saveState())
        files = []
        for textEdit in self.mdi.subWindowList():
            textEdit = textEdit.widget()
            if not textEdit.filename.startswith("Unnamed"):
                files.append(textEdit.filename)
        settings.setValue("CurrentFiles", files)
        self.mdi.closeAllSubWindows()

    def loadFiles(self):
        if len(sys.argv) > 1:
            for filename in sys.argv[1:31]:  # Load at most 30 files
                filename = filename
                if QFileInfo(filename).isFile():
                    self.loadFile(filename)
                    QApplication.processEvents()
        else:
            settings = QSettings()
            #files = settings.value("CurrentFiles").toStringList()
            if settings.value("CurrentFiles"):
                files = settings.value("CurrentFiles")
                for filename in files:
                    filename = filename
                    if QFile.exists(filename):
                        self.loadFile(filename)
                        QApplication.processEvents()

    def fileNew(self):
        textEdit = textedit.TextEdit()
        self.mdi.addSubWindow(textEdit)
        textEdit.show()

    def fileOpen(self):
        filename, filetype = QFileDialog.getOpenFileName(
            self, "Text Editor -- Open File")
        if filename:
            for textEdit_MSW in self.mdi.subWindowList():
                textEdit = textEdit_MSW.widget()
                if textEdit.filename == filename:
                    self.mdi.setActiveSubWindow(textEdit_MSW)
                    break
            else:
                self.loadFile(filename)

    def loadFile(self, filename):
        textEdit = textedit.TextEdit(filename)
        try:
            textEdit.load()
        except EnvironmentError as e:
            QMessageBox.warning(self, "Text Editor -- Load Error",
                                "Failed to load {0}: {1}".format(filename, e))
            textEdit.close()
            del textEdit
        else:
            self.mdi.addSubWindow(textEdit)
            textEdit.show()

    def fileSave(self):
        textEdit = self.mdi.activeSubWindow()
        textEdit = textEdit.widget()
        if textEdit is None or not isinstance(textEdit, QTextEdit):
            return True
        try:
            textEdit.save()
            return True
        except EnvironmentError as e:
            QMessageBox.warning(
                self, "Text Editor -- Save Error",
                "Failed to save {0}: {1}".format(textEdit.filename, e))
            return False

    def fileSaveAs(self):
        textEdit = self.mdi.activeSubWindow()
        textEdit = textEdit.widget()
        if textEdit is None or not isinstance(textEdit, QTextEdit):
            return
        filename, filetype = QFileDialog.getSaveFileName(
            self, "Text Editor -- Save File As", textEdit.filename,
            "Text files (*.txt *.*)")
        if filename:
            textEdit.filename = filename
            return self.fileSave()
        return True

    def fileSaveAll(self):
        errors = []
        for textEdit in self.mdi.subWindowList():
            textEdit = textEdit.widget()
            if textEdit.isModified():
                try:
                    textEdit.save()
                except EnvironmentError as e:
                    errors.append("{0}: {1}".format(textEdit.filename, e))
        if errors:
            QMessageBox.warning(
                self, "Text Editor -- Save All Error",
                "Failed to save\n{0}".format("\n".join(errors)))

    def editCopy(self):
        textEdit = self.mdi.activeSubWindow()
        textEdit = textEdit.widget()
        if textEdit is None or not isinstance(textEdit, QTextEdit):
            return
        cursor = textEdit.textCursor()
        text = cursor.selectedText()
        if text:
            clipboard = QApplication.clipboard()
            clipboard.setText(text)

    def editCut(self):
        textEdit = self.mdi.activeSubWindow()
        textEdit = textEdit.widget()
        if textEdit is None or not isinstance(textEdit, QTextEdit):
            return
        cursor = textEdit.textCursor()
        text = cursor.selectedText()
        if text:
            cursor.removeSelectedText()
            clipboard = QApplication.clipboard()
            clipboard.setText(text)

    def editPaste(self):
        textEdit = self.mdi.activeSubWindow()
        textEdit = textEdit.widget()
        if textEdit is None or not isinstance(textEdit, QTextEdit):
            return
        clipboard = QApplication.clipboard()
        textEdit.insertPlainText(clipboard.text())

    def windowRestoreAll(self):
        for textEdit in self.mdi.subWindowList():
            textEdit = textEdit.widget()
            textEdit.showNormal()

    def windowMinimizeAll(self):
        for textEdit in self.mdi.subWindowList():
            textEdit = textEdit.widget()
            textEdit.showMinimized()

    def updateWindowMenu(self):
        self.windowMenu.clear()
        self.addActions(
            self.windowMenu,
            (self.windowNextAction, self.windowPrevAction,
             self.windowCascadeAction, self.windowTileAction,
             self.windowRestoreAction, self.windowMinimizeAction,
             self.windowArrangeIconsAction, None, self.windowCloseAction))
        textEdits = self.mdi.subWindowList()
        if not textEdits:
            return
        self.windowMenu.addSeparator()
        i = 1
        menu = self.windowMenu
        for textEdit_MSW in textEdits:
            textEdit = textEdit_MSW.widget()
            title = textEdit.windowTitle()
            if i == 10:
                self.windowMenu.addSeparator()
                menu = menu.addMenu("&More")
            accel = ""
            if i < 10:
                accel = "&{0} ".format(i)
            elif i < 36:
                accel = "&{0} ".format(chr(i + ord("@") - 9))

            action = menu.addAction("{0}{1}".format(accel, title))
            self.windowMapper.setMapping(action, textEdit_MSW)
            action.triggered.connect(self.windowMapper.map)
            i += 1
Exemplo n.º 31
0
    def makePopupMenu(self):
        index = self.currentIndex()
        sel = self.getSelection()
        clipboard = qApp.clipboard()

        menu = QMenu(self)

        # Add / remove items
        self.actAddFolder = QAction(
            QIcon.fromTheme("folder-new"),
            qApp.translate("outlineBasics", "New Folder"), menu)
        self.actAddFolder.triggered.connect(self.addFolder)
        menu.addAction(self.actAddFolder)

        self.actAddText = QAction(QIcon.fromTheme("document-new"),
                                  qApp.translate("outlineBasics", "New Text"),
                                  menu)
        self.actAddText.triggered.connect(self.addText)
        menu.addAction(self.actAddText)

        self.actDelete = QAction(QIcon.fromTheme("edit-delete"),
                                 qApp.translate("outlineBasics", "Delete"),
                                 menu)
        self.actDelete.triggered.connect(self.delete)
        menu.addAction(self.actDelete)

        menu.addSeparator()

        # Copy, cut, paste
        self.actCopy = QAction(QIcon.fromTheme("edit-copy"),
                               qApp.translate("outlineBasics", "Copy"), menu)
        self.actCopy.triggered.connect(self.copy)
        menu.addAction(self.actCopy)

        self.actCut = QAction(QIcon.fromTheme("edit-cut"),
                              qApp.translate("outlineBasics", "Cut"), menu)
        self.actCut.triggered.connect(self.cut)
        menu.addAction(self.actCut)

        self.actPaste = QAction(QIcon.fromTheme("edit-paste"),
                                qApp.translate("outlineBasics", "Paste"), menu)
        self.actPaste.triggered.connect(self.paste)
        menu.addAction(self.actPaste)

        menu.addSeparator()

        # POV
        self.menuPOV = QMenu(qApp.translate("outlineBasics", "Set POV"), menu)
        mw = mainWindow()
        a = QAction(QIcon.fromTheme("dialog-no"),
                    qApp.translate("outlineBasics", "None"), self.menuPOV)
        a.triggered.connect(lambda: self.setPOV(""))
        self.menuPOV.addAction(a)
        self.menuPOV.addSeparator()

        menus = []
        for i in [self.tr("Main"), self.tr("Secondary"), self.tr("Minor")]:
            m = QMenu(i, self.menuPOV)
            menus.append(m)
            self.menuPOV.addMenu(m)

        mpr = QSignalMapper(self.menuPOV)
        for i in range(mw.mdlCharacter.rowCount()):
            a = QAction(mw.mdlCharacter.icon(i), mw.mdlCharacter.name(i),
                        self.menuPOV)
            a.triggered.connect(mpr.map)
            mpr.setMapping(a, int(mw.mdlCharacter.ID(i)))

            imp = toInt(mw.mdlCharacter.importance(i))

            menus[2 - imp].addAction(a)

        mpr.mapped.connect(self.setPOV)
        menu.addMenu(self.menuPOV)

        # Status
        self.menuStatus = QMenu(qApp.translate("outlineBasics", "Set Status"),
                                menu)
        # a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuStatus)
        # a.triggered.connect(lambda: self.setStatus(""))
        # self.menuStatus.addAction(a)
        # self.menuStatus.addSeparator()

        mpr = QSignalMapper(self.menuStatus)
        for i in range(mw.mdlStatus.rowCount()):
            a = QAction(mw.mdlStatus.item(i, 0).text(), self.menuStatus)
            a.triggered.connect(mpr.map)
            mpr.setMapping(a, i)
            self.menuStatus.addAction(a)
        mpr.mapped.connect(self.setStatus)
        menu.addMenu(self.menuStatus)

        # Labels
        self.menuLabel = QMenu(qApp.translate("outlineBasics", "Set Label"),
                               menu)
        mpr = QSignalMapper(self.menuLabel)
        for i in range(mw.mdlLabels.rowCount()):
            a = QAction(
                mw.mdlLabels.item(i, 0).icon(),
                mw.mdlLabels.item(i, 0).text(), self.menuLabel)
            a.triggered.connect(mpr.map)
            mpr.setMapping(a, i)
            self.menuLabel.addAction(a)
        mpr.mapped.connect(self.setLabel)
        menu.addMenu(self.menuLabel)

        if len(sel) > 0 and index.isValid() and not index.internalPointer().isFolder() \
                or not clipboard.mimeData().hasFormat("application/xml"):
            self.actPaste.setEnabled(False)

        if len(sel) > 0 and index.isValid(
        ) and not index.internalPointer().isFolder():
            self.actAddFolder.setEnabled(False)
            self.actAddText.setEnabled(False)

        if len(sel) == 0:
            self.actCopy.setEnabled(False)
            self.actCut.setEnabled(False)
            self.actDelete.setEnabled(False)
            self.menuPOV.setEnabled(False)
            self.menuStatus.setEnabled(False)
            self.menuLabel.setEnabled(False)

        return menu
Exemplo n.º 32
0
class KnobTurner(QObject, Ui_Dialog):

    _frameCount = 0  # running total frames shown
    _frameRate_RunningAvg = -1.0  # decaying average frame rate (displayed on video)
    _alpha = 0.01  # how fast to update the screen refresh frame rate display variable
    _showHist = False
    _histogramWindowName = "Color Histogram"

    class Mapper(QObject):
        def __init__(self, caller, called, action=None):
            QObject.__init__(self)
            self._caller = caller
            self._called = called
            self._action = action

        @property
        def caller(self):
            return self._caller

        @property
        def called(self):
            return self._called

        @property
        def action(self):
            return self._action

    def __init__(self):
        print(">>>>>>>>>>>>>>>TODO: Restore the latest set of options??")
        Ui_Dialog.__init__(self)
        QObject.__init__(self)

        self._filters = [
            BlurFilter(),
            SimpleMotionDetection(),
            ActivityFilter(),
            HistogramFilter(),
            BlockNumber(),
            EdgeDetector()
        ]

    @pyqtSlot()
    def resetFilterListModel(self):
        # if we get here then the filter list has been drag-rearranged

        # reset the filter list based off the filterList order
        self._filters = []
        prev = None
        for indx in range(0, self.filterList.count()):
            fltr = self.filterList.item(indx).data(Qt.UserRole)
            self._filters.append(fltr)

        for indx in range(0, self.filterList.count()):
            print("Index: %d Name: %s" %
                  (indx, self.filterList.item(indx).data(Qt.UserRole).name))

    def getFilterProperties(self, fltr):
        attribs = [
            funct.replace(FrameProcessor.propStartsWith, "")
            for funct in dir(fltr) if callable(getattr(fltr, funct))
            and funct.startswith(FrameProcessor.propStartsWith)
        ]

        setters = [
            ftn.replace(FrameProcessor.propEndsWithSetter, "")
            for ftn in attribs
            if ftn.endswith(FrameProcessor.propEndsWithSetter)
        ]
        getters = [
            ftn.replace(FrameProcessor.propEndsWithGetter, "")
            for ftn in attribs
            if ftn.endswith(FrameProcessor.propEndsWithGetter)
        ]

        return getters, setters

    def check_state(self, caller):
        validator = caller.validator()
        state = validator.validate(caller.text(), 0)[0]
        if state == QValidator.Acceptable:
            color = '#c4df9b'  # green
        elif state == QValidator.Intermediate:
            color = '#fff79a'  # yellow
        else:
            color = '#f6989d'  # red
        caller.setStyleSheet('QLineEdit { background-color: %s }' % color)
        return (state == QValidator.Acceptable)

    @pyqtSlot(QObject)
    def saveFilterValue(self, obj):
        assert (isinstance(obj.caller, QSpinBox)
                or isinstance(obj.caller, QCheckBox)
                or isinstance(obj.caller, QLineEdit))

        if isinstance(obj.caller, QSpinBox):
            newVal = obj.caller.value()
        elif isinstance(obj.caller, QCheckBox):
            newVal = obj.caller.isChecked()
        elif isinstance(obj.caller, QLineEdit):
            if self.check_state(obj.caller) != True:
                return
            newVal = [int(t) for t in obj.caller.text().split(",")]

        try:
            func = getattr(obj.called, obj.action)
        except AttributeError:
            print("Whoops, for some reason the callback isn't valid.")
        else:
            result = func(newVal)

        # reset the frame rate
        self._frameRate_RunningAvg = -1

    def setupUi(self, dlg):
        Ui_Dialog.setupUi(self, dlg)

        self.btnShowHist.clicked.connect(self.showHistogram)

        self._filterListOrderMapper = FilterListOrderMapper()
        self._filterListOrderMapper.listChanged.connect(
            self.resetFilterListModel)

        self.filterList.installEventFilter(self._filterListOrderMapper)
        self.filterList.setDragDropMode(QAbstractItemView.InternalMove)

        self._optionMapper = QSignalMapper(dlg)
        self._optionMapper.mapped[QObject].connect(self.saveFilterValue)
        self._optionSave = [
        ]  # this is only for making sure the Mapper object doesn't get garbage collected

        #regx = QRegExp( "([0-9]{1,3}[\,][ ]*){2}[0-9]{1,3}" )
        regx = QRegExp("([0-9]{1,3}[\,][ ]*){1,2}[0-9]{1,3}")

        for fltr in self._filters:
            print("Filter added: %s" % fltr.name)

            fltr.loadConfig(None)

            self.label_Status.setText("Loading filters")
            getters, setters = self.getFilterProperties(fltr)
            print("\tAvailable GETable options: ", getters)
            print("\tAvailable SETable options: ", setters)

            group = QGroupBox()
            vbl = QVBoxLayout(group)

            # Row ------
            fm = QFrame()
            vb = QVBoxLayout(fm)

            vb.setSizeConstraint(QLayout.SetFixedSize)

            # add the enabled checkbox first always
            cb2 = QCheckBox(fltr.name)
            cb2.setCheckState(
                getattr(
                    fltr, FrameProcessor.propStartsWith + "Enabled" +
                    FrameProcessor.propEndsWithGetter)())
            cb2.setStyleSheet('background-color:#%02x%02x%02x' %
                              (fltr.color()[::-1]))
            vb.addWidget(cb2)
            # watch for values changing
            cb2.stateChanged.connect(self._optionMapper.map)
            self._optionSave.append(
                KnobTurner.Mapper(
                    cb2, fltr, FrameProcessor.propStartsWith + "Enabled" +
                    FrameProcessor.propEndsWithSetter))
            self._optionMapper.setMapping(cb2, self._optionSave[-1])

            for item in setters:
                prop_type = getattr(fltr,
                                    FrameProcessor.propTypeStartsWith + item)()
                if prop_type is int:
                    hz = QHBoxLayout()
                    lb1 = QLabel("%s:" % item)
                    sp1 = QSpinBox()
                    sp1.setRange(
                        0, 10000000)  # TODO: add this as a query to the filter
                    sp1.setValue(
                        getattr(
                            fltr, FrameProcessor.propStartsWith + item +
                            FrameProcessor.propEndsWithGetter)())
                    vb.addWidget(lb1)
                    vb.addWidget(sp1)

                    # watch for values changing
                    sp1.valueChanged.connect(self._optionMapper.map)
                    self._optionSave.append(
                        KnobTurner.Mapper(
                            sp1, fltr, FrameProcessor.propStartsWith + item +
                            FrameProcessor.propEndsWithSetter))
                    self._optionMapper.setMapping(sp1, self._optionSave[-1])

                elif prop_type is bool:
                    if item == "Enabled":
                        continue
                    else:
                        cb2 = QCheckBox(item)
                    cb2.setCheckState(
                        getattr(
                            fltr, FrameProcessor.propStartsWith + item +
                            FrameProcessor.propEndsWithGetter)())
                    vb.addWidget(cb2)

                    # watch for values changing
                    cb2.stateChanged.connect(self._optionMapper.map)
                    self._optionSave.append(
                        KnobTurner.Mapper(
                            cb2, fltr, FrameProcessor.propStartsWith + item +
                            FrameProcessor.propEndsWithSetter))
                    self._optionMapper.setMapping(cb2, self._optionSave[-1])
                elif prop_type is tuple:
                    hz = QHBoxLayout()
                    lb1 = QLabel("%s:" % item)

                    val = getattr(
                        fltr, FrameProcessor.propStartsWith + item +
                        FrameProcessor.propEndsWithGetter)()
                    le = QLineEdit("{0}".format(val).replace("(", "").replace(
                        ")", "").replace("[", "").replace("]", ""))

                    le.setValidator(QRegExpValidator(regx))
                    le.textChanged.connect(self._optionMapper.map)
                    le.textChanged.emit(le.text())

                    self._optionSave.append(
                        KnobTurner.Mapper(
                            le, fltr, FrameProcessor.propStartsWith + item +
                            FrameProcessor.propEndsWithSetter))
                    self._optionMapper.setMapping(le, self._optionSave[-1])

                    vb.addWidget(lb1)
                    vb.addWidget(le)
            # end Row 2 -----

            vbl.addWidget(fm)
            vbl.setSizeConstraint(QLayout.SetFixedSize)

            lwi = QListWidgetItem()
            lwi.setSizeHint(vbl.sizeHint())
            lwi.setData(
                Qt.UserRole,
                QVariant(fltr))  # attach the filter to this item in the list
            self.filterList.addItem(lwi)
            self.filterList.setItemWidget(lwi, group)

        self.label_Status.setText("Loading filters... Done")

        self.label_Status.setText("Starting cameras...")
        self.startCamera()
        self.label_Status.setText("Starting cameras... Done")

        self.showWindows()

        self.label_Status.setText("Ready")

    def startCamera(self):
        print("Starting camera")
        #vid = "drop.avi"    # use video instead of camera
        vid = None
        self._cameraDevice = CameraDevice(vid)

        self.videoLive.setCamera(self._cameraDevice)
        self.videoLive.newFrame.connect(self.processPreviewFrame)

        self.videoFiltered.setCamera(self._cameraDevice)
        self.videoFiltered.newFrame.connect(self.processFilteredFrame)

    def showHistogram(self):
        self._showHist = not self._showHist

        if not self._showHist:
            cv2.destroyWindow(self._histogramWindowName)

    def updateHistogram(self):
        bin_count = self.hist.shape[0]
        bin_w = 40
        img = np.zeros((256, bin_count * bin_w, 3), np.uint8)
        for i in range(0, bin_count):
            h = int(self.hist[i])
            cv2.rectangle(
                img,
                (i * bin_w + 2, 255),  # top left corner
                ((i + 1) * bin_w - 2, 255 - h),  # bottom right corner
                (int(180.0 * i / bin_count), 255, 255),  # color
                -1)  # -1 is fill shape
        img = cv2.cvtColor(img, cv2.COLOR_HSV2BGR)
        cv2.imshow(self._histogramWindowName, img)

    def showWindows(self):
        self.videoLive.show()
        self.videoFiltered.show()
        print("Live video size: {}", self.videoLive.sizeHint())

    @pyqtSlot(np.ndarray)
    def processPreviewFrame(self, frame):

        # update histogram every 15th frame (picked arbitrarily)
        if self._showHist == True and self._frameCount % 15 == 0:
            hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
            hist = cv2.calcHist([hsv], [0], None, [16], [0, 180])
            cv2.normalize(hist, hist, 0, 255, cv2.NORM_MINMAX)
            self.hist = hist.reshape(-1)

            self.updateHistogram()

        intersect_box = None
        colors = []
        boxes = []
        for fltr in self._filters:
            if fltr.prop_Enabled_get():
                box = fltr.getBoundingBox(
                )  # CAREFUL!!: returns (x1, y1, x2, y2) NOT x, y, w, h ****
                if len(box) != 0:
                    colors.append(fltr.color())
                    boxes.append(box)
                    if intersect_box == None:
                        intersect_box = box
                    else:
                        intersect_box = intersection_rect(intersect_box, box)

        # fade the color for all but the intersection of active areas
        if intersect_box != None:
            rectW = intersect_box[2] - intersect_box[0]
            rectH = intersect_box[3] - intersect_box[1]
            saverect = frame[intersect_box[1]:intersect_box[1] + rectH,
                             intersect_box[0]:intersect_box[0] + rectW]

        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        ## TODO: this crashes now with 'TypeError: No loop matching the specified signature and casting was found for ufunc true_divide' - new numpy
        # frame /= 2  # cut the overal luminocity of the preview video by half to highlight the in-frame portion
        frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)

        # restore the color to only the intersection
        if intersect_box != None:
            frame[intersect_box[1]:intersect_box[1] + rectH,
                  intersect_box[0]:intersect_box[0] + rectW] = saverect

        # paint the boxes back on top of the gray image
        for index, box in enumerate(boxes):
            cv2.rectangle(frame, (box[0], box[1]), (box[2], box[3]),
                          colors[index], 2)

        self.videoLive.setNewFrame(frame)

    @pyqtSlot(np.ndarray)
    def processFilteredFrame(self, frame):
        t = clock()

        for fltr in self._filters:
            if fltr.prop_Enabled_get(
            ) == True:  # I'm cheating here because I know this property exists for all filters
                frame = fltr.processFrame(frame)

        # show time
        delta_t = clock() - t
        if self._frameRate_RunningAvg == -1:
            self._frameRate_RunningAvg = delta_t
        self._frameRate_RunningAvg = (self._alpha * delta_t) + (
            1.0 - self._alpha) * self._frameRate_RunningAvg
        draw_str(frame, 20, 20,
                 'time: %.1f ms' % (self._frameRate_RunningAvg * 1000))

        # last guy puts the frame up
        self.videoFiltered.setNewFrame(frame)

    @pyqtSlot()
    def shutDown(self):
        print(
            ">>>>>>>>>>>>>>>TODO: Print and/or save the latest set of options!!"
        )

        for fltr in self._filters:
            fltr.saveConfig(None)

        print("Shutting down")
Exemplo n.º 33
0
class Panel(QDialog):
    # A list of two-sized tuples (QWidget's name, model field name).
    FIELDS = []
    # Name to use for serialization of persistent data about this panel (geometry).
    # XXX At the time of this writing (ticket #364), there's already a separate system in Cocoa
    # to persist dialog frames. A "clean" implementation would do like we do with the main window
    # and implement frame save/restore in core, but I fear that I'll needlessly complicate things
    # doing so, so for now, I limit myself to a qt-only solution. Later, we should re-evaluate
    # whether it could be a good idea to push this implementation to the core.
    PERSISTENT_NAME = None

    def __init__(self, mainwindow):
        # The flags we pass are that so we don't get the "What's this" button in the title bar
        QDialog.__init__(self, mainwindow, Qt.WindowTitleHint | Qt.WindowSystemMenuHint)
        self._widget2ModelAttr = {}
        self.mainwindow = mainwindow

    def _changeComboBoxItems(self, comboBox, newItems):
        # When a combo box's items are changed, its currentIndex changed with a currentIndexChanged
        # signal, and if that signal results in the model being updated, it messes the model.
        # We thus have to disconnect the combo box's signal before changing the items.
        if comboBox in self._widget2ModelAttr:
            comboBox.currentIndexChanged.disconnect(self.comboBoxCurrentIndexChanged)
        index = comboBox.currentIndex()
        comboBox.clear()
        comboBox.addItems(newItems)
        comboBox.setCurrentIndex(index)
        if comboBox in self._widget2ModelAttr:
            comboBox.currentIndexChanged.connect(self.comboBoxCurrentIndexChanged)

    def _connectSignals(self):
        self._signalMapper = QSignalMapper()
        for widgetName, modelAttr in self.FIELDS:
            widget = getattr(self, widgetName)
            self._widget2ModelAttr[widget] = modelAttr
            self._signalMapper.setMapping(widget, widget)
            if isinstance(widget, QComboBox):
                widget.currentIndexChanged.connect(self._signalMapper.map)
            elif isinstance(widget, QSpinBox):
                widget.valueChanged.connect(self._signalMapper.map)
            elif isinstance(widget, QLineEdit):
                widget.editingFinished.connect(self._signalMapper.map)
            elif isinstance(widget, QPlainTextEdit):
                widget.textChanged.connect(self._signalMapper.map)
            elif isinstance(widget, QCheckBox):
                widget.stateChanged.connect(self._signalMapper.map)
        self._signalMapper.mapped[QWidget].connect(self.widgetChanged)

    def _loadFields(self):
        for widgetName, modelAttr in self.FIELDS:
            widget = getattr(self, widgetName)
            value = getattr(self.model, modelAttr)
            if isinstance(widget, QComboBox):
                widget.setCurrentIndex(value)
            elif isinstance(widget, QSpinBox):
                widget.setValue(value)
            elif isinstance(widget, QLineEdit):
                widget.setText(value)
            elif isinstance(widget, QPlainTextEdit):
                widget.setPlainText(value)
            elif isinstance(widget, QCheckBox):
                widget.setChecked(value)

    def _saveFields(self):
        pass

    def _loadGeometry(self):
        if self.PERSISTENT_NAME:
            self.mainwindow.app.prefs.restoreGeometry('%sGeometry' % self.PERSISTENT_NAME, self)

    def _saveGeometry(self):
        if self.PERSISTENT_NAME:
            self.mainwindow.app.prefs.saveGeometry('%sGeometry' % self.PERSISTENT_NAME, self)

    def accept(self):
        # The setFocus() call is to force the last edited field to "commit". When the save button
        # is clicked, accept() is called before the last field to have focus has a chance to emit
        # its edition signal.
        self.setFocus()
        self.model.save()
        self._saveGeometry()
        QDialog.accept(self)

    def reject(self):
        self._saveGeometry()
        super().reject()

    #--- Event Handlers
    def widgetChanged(self, sender):
        modelAttr = self._widget2ModelAttr[sender]
        if isinstance(sender, QComboBox):
            newvalue = sender.currentIndex()
        elif isinstance(sender, QSpinBox):
            newvalue = sender.value()
        elif isinstance(sender, QLineEdit):
            newvalue = sender.text()
        elif isinstance(sender, QPlainTextEdit):
            newvalue = sender.toPlainText()
        elif isinstance(sender, QCheckBox):
            newvalue = sender.isChecked()
        setattr(self.model, modelAttr, newvalue)

    #--- model --> view
    def pre_load(self):
        self._loadGeometry()

    def pre_save(self):
        self._saveFields()

    def post_load(self):
        if not self._widget2ModelAttr: # signal not connected yet
            self._connectSignals()
        self._loadFields()
        self.show()
        # For initial text edits to have their text selected, we *have to* first select the dialog,
        # then setFocus on it with qt.TabFocusReason. Don't ask, I don't know why either...
        self.setFocus()
        focus = self.nextInFocusChain()
        while focus.focusPolicy() == Qt.NoFocus:
            focus = focus.nextInFocusChain()
        focus.setFocus(Qt.TabFocusReason)
Exemplo n.º 34
0
class AddSeqTool(AbstractPathTool):
    """Summary

    Attributes:
        apply_button (TYPE): Description
        buttons (list): Description
        dialog (TYPE): Description
        highlighter (TYPE): Description
        seq_box (TYPE): Description
        sequence_radio_button_id (dict): Description
        signal_mapper (TYPE): Description
        use_abstract_sequence (bool): Description
        validated_sequence_to_apply (TYPE): Description
    """
    def __init__(self, manager):
        """Summary

        Args:
            manager (TYPE): Description
        """
        AbstractPathTool.__init__(self, manager)
        self.dialog = QDialog()
        self.buttons = []
        self.seq_box = None
        self.sequence_radio_button_id = {}
        self.use_abstract_sequence = True
        self.validated_sequence_to_apply = None
        self.initDialog()

    def __repr__(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return "add_seq_tool"  # first letter should be lowercase

    def methodPrefix(self):
        """Summary

        Returns:
            TYPE: Description
        """
        return "addSeqTool"  # first letter should be lowercase

    def initDialog(self):
        """Creates buttons for each sequence option and add them to the dialog.
        Maps the clicked signal of those buttons to keep track of what sequence
        gets selected.
        """
        ui_dlg = Ui_AddSeqDialog()
        ui_dlg.setupUi(self.dialog)
        self.signal_mapper = QSignalMapper(self)
        # set up the radio buttons
        for i, name in enumerate(['Abstract', 'Custom'] +
                                 sorted(sequences.keys())):
            radio_button = QRadioButton(ui_dlg.group_box)
            radio_button.setObjectName(name + "Button")
            radio_button.setText(name)
            self.buttons.append(radio_button)
            ui_dlg.horizontalLayout.addWidget(radio_button)
            self.signal_mapper.setMapping(radio_button, i)
            radio_button.clicked.connect(self.signal_mapper.map)
            if name in sequences:
                self.sequence_radio_button_id[sequences[name]] = i
        self.signal_mapper.mapped.connect(self.sequenceOptionChangedSlot)
        # disable apply until valid option or custom sequence is chosen
        self.apply_button = ui_dlg.custom_button_box.button(
            QDialogButtonBox.Apply)
        self.apply_button.setEnabled(False)
        # watch sequence textedit box to validate custom sequences
        self.seq_box = ui_dlg.seq_text_edit
        self.seq_box.textChanged.connect(self.validateCustomSequence)
        self.highlighter = DNAHighlighter(self.seq_box)
        # finally, pre-click the first radio button
        self.buttons[0].click()

    def sequenceOptionChangedSlot(self, option_chosen):
        """
        Connects to signal_mapper to receive a signal whenever user selects
        a sequence option.

        Args:
            option_chosen (TYPE): Description
        """
        option_name = self.buttons[option_chosen].text()
        if option_name == 'Abstract':
            self.use_abstract_sequence = True
        elif option_name == 'Custom':
            self.use_abstract_sequence = False
        else:
            self.use_abstract_sequence = False
            user_sequence = sequences.get(option_name, None)
            if self.seq_box.toPlainText() != user_sequence:
                self.seq_box.setText(user_sequence)

    def validateCustomSequence(self):
        """
        Called when user changes sequence (seq_box emits textChanged signal)
        If sequence is valid, make the apply_button active to click.
        Select an appropriate sequence option radio button, if necessary.
        """
        user_sequence = self.seq_box.toPlainText()
        # Validate the sequence and activate the button if it checks out.
        if re.search(RE_DNA_PATTERN, user_sequence) is None:
            self.apply_button.setEnabled(True)
        else:
            self.apply_button.setEnabled(False)

        if len(user_sequence) == 0:
            # A zero-length custom sequence defaults to Abstract type.
            if not self.buttons[0].isChecked():
                self.buttons[0].click()
        else:
            # Does this match a known sequence?
            if user_sequence in self.sequence_radio_button_id:
                # Handles case where the user might copy & paste in a known sequence
                i = self.sequence_radio_button_id[user_sequence]
                if not self.buttons[i].isChecked():
                    # Select the corresponding radio button for known sequence
                    self.buttons[i].click()
            else:
                # Unrecognized, Custom type
                if not self.buttons[1].isChecked():
                    self.buttons[1].click()

    def applySequence(self, oligo):
        """Summary

        Args:
            oligo (TYPE): Description

        Returns:
            TYPE: Description
        """
        self.dialog.setFocus()
        if self.dialog.exec_():  # apply the sequence if accept was clicked
            if self.use_abstract_sequence:
                oligo.applySequence(None)
                return (oligo.length(), None)
            else:
                self.validated_sequence_to_apply = self.seq_box.toPlainText(
                ).upper()
                oligo.applySequence(self.validated_sequence_to_apply)
                return oligo.length(), len(self.validated_sequence_to_apply)
        return (None, None)
Exemplo n.º 35
0
    def makePopupMenu(self):
        index = self.currentIndex()
        sel = self.getSelection()
        clipboard = qApp.clipboard()

        menu = QMenu(self)

        # Get index under cursor
        pos = self.viewport().mapFromGlobal(QCursor.pos())
        mouseIndex = self.indexAt(pos)

        # Get index's title
        if mouseIndex.isValid():
            title = mouseIndex.internalPointer().title()

        elif self.rootIndex().parent().isValid():
            # mouseIndex is the background of an item, so we check the parent
            mouseIndex = self.rootIndex().parent()
            title = mouseIndex.internalPointer().title()

        else:
            title = qApp.translate("outlineBasics", "Root")

        if len(title) > 25:
            title = title[:25] + "…"

        # Open Item action
        self.actOpen = QAction(QIcon.fromTheme("go-right"),
                               qApp.translate("outlineBasics", "Open {}".format(title)),
                               menu)
        self.actOpen.triggered.connect(self.openItem)
        menu.addAction(self.actOpen)

        # Open item(s) in new tab
        if mouseIndex in sel and len(sel) > 1:
            actionTitle = qApp.translate("outlineBasics", "Open {} items in new tabs").format(len(sel))
            self._indexesToOpen = sel
        else:
            actionTitle = qApp.translate("outlineBasics", "Open {} in a new tab").format(title)
            self._indexesToOpen = [mouseIndex]

        self.actNewTab = QAction(QIcon.fromTheme("go-right"), actionTitle, menu)
        self.actNewTab.triggered.connect(self.openItemsInNewTabs)
        menu.addAction(self.actNewTab)

        menu.addSeparator()

        # Add text / folder
        self.actAddFolder = QAction(QIcon.fromTheme("folder-new"),
                                    qApp.translate("outlineBasics", "New &Folder"),
                                    menu)
        self.actAddFolder.triggered.connect(self.addFolder)
        menu.addAction(self.actAddFolder)

        self.actAddText = QAction(QIcon.fromTheme("document-new"),
                                  qApp.translate("outlineBasics", "New &Text"),
                                  menu)
        self.actAddText.triggered.connect(self.addText)
        menu.addAction(self.actAddText)

        menu.addSeparator()

        # Copy, cut, paste, duplicate
        self.actCut = QAction(QIcon.fromTheme("edit-cut"),
                              qApp.translate("outlineBasics", "C&ut"), menu)
        self.actCut.triggered.connect(self.cut)
        menu.addAction(self.actCut)

        self.actCopy = QAction(QIcon.fromTheme("edit-copy"),
                               qApp.translate("outlineBasics", "&Copy"), menu)
        self.actCopy.triggered.connect(self.copy)
        menu.addAction(self.actCopy)

        self.actPaste = QAction(QIcon.fromTheme("edit-paste"),
                                qApp.translate("outlineBasics", "&Paste"), menu)
        self.actPaste.triggered.connect(self.paste)
        menu.addAction(self.actPaste)

        # Rename / duplicate / remove items
        self.actDelete = QAction(QIcon.fromTheme("edit-delete"),
                                 qApp.translate("outlineBasics", "&Delete"),
                                 menu)
        self.actDelete.triggered.connect(self.delete)
        menu.addAction(self.actDelete)

        self.actRename = QAction(QIcon.fromTheme("edit-rename"),
                                 qApp.translate("outlineBasics", "&Rename"),
                                 menu)
        self.actRename.triggered.connect(self.rename)
        menu.addAction(self.actRename)

        menu.addSeparator()

        # POV
        self.menuPOV = QMenu(qApp.translate("outlineBasics", "Set POV"), menu)
        mw = mainWindow()
        a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuPOV)
        a.triggered.connect(lambda: self.setPOV(""))
        self.menuPOV.addAction(a)
        self.menuPOV.addSeparator()

        menus = []
        for i in [qApp.translate("outlineBasics", "Main"),
                  qApp.translate("outlineBasics", "Secondary"),
                  qApp.translate("outlineBasics", "Minor")]:
            m = QMenu(i, self.menuPOV)
            menus.append(m)
            self.menuPOV.addMenu(m)

        mpr = QSignalMapper(self.menuPOV)
        for i in range(mw.mdlCharacter.rowCount()):
            a = QAction(mw.mdlCharacter.icon(i), mw.mdlCharacter.name(i), self.menuPOV)
            a.triggered.connect(mpr.map)
            mpr.setMapping(a, int(mw.mdlCharacter.ID(i)))

            imp = toInt(mw.mdlCharacter.importance(i))

            menus[2 - imp].addAction(a)

        mpr.mapped.connect(self.setPOV)
        menu.addMenu(self.menuPOV)

        # Status
        self.menuStatus = QMenu(qApp.translate("outlineBasics", "Set Status"), menu)
        # a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuStatus)
        # a.triggered.connect(lambda: self.setStatus(""))
        # self.menuStatus.addAction(a)
        # self.menuStatus.addSeparator()

        mpr = QSignalMapper(self.menuStatus)
        for i in range(mw.mdlStatus.rowCount()):
            a = QAction(mw.mdlStatus.item(i, 0).text(), self.menuStatus)
            a.triggered.connect(mpr.map)
            mpr.setMapping(a, i)
            self.menuStatus.addAction(a)
        mpr.mapped.connect(self.setStatus)
        menu.addMenu(self.menuStatus)

        # Labels
        self.menuLabel = QMenu(qApp.translate("outlineBasics", "Set Label"), menu)
        mpr = QSignalMapper(self.menuLabel)
        for i in range(mw.mdlLabels.rowCount()):
            a = QAction(mw.mdlLabels.item(i, 0).icon(),
                        mw.mdlLabels.item(i, 0).text(),
                        self.menuLabel)
            a.triggered.connect(mpr.map)
            mpr.setMapping(a, i)
            self.menuLabel.addAction(a)
        mpr.mapped.connect(self.setLabel)
        menu.addMenu(self.menuLabel)

        menu.addSeparator()

        # Custom icons
        if self.menuCustomIcons:
            menu.addMenu(self.menuCustomIcons)
        else:
            self.menuCustomIcons = QMenu(qApp.translate("outlineBasics", "Set Custom Icon"), menu)
            a = QAction(qApp.translate("outlineBasics", "Restore to default"), self.menuCustomIcons)
            a.triggered.connect(lambda: self.setCustomIcon(""))
            self.menuCustomIcons.addAction(a)
            self.menuCustomIcons.addSeparator()

            txt = QLineEdit()
            txt.textChanged.connect(self.filterLstIcons)
            txt.setPlaceholderText("Filter icons")
            txt.setStyleSheet("QLineEdit { background: transparent; border: none; }")
            act = QWidgetAction(self.menuCustomIcons)
            act.setDefaultWidget(txt)
            self.menuCustomIcons.addAction(act)

            self.lstIcons = QListWidget()
            for i in customIcons():
                item = QListWidgetItem()
                item.setIcon(QIcon.fromTheme(i))
                item.setData(Qt.UserRole, i)
                item.setToolTip(i)
                self.lstIcons.addItem(item)
            self.lstIcons.itemClicked.connect(self.setCustomIconFromItem)
            self.lstIcons.setViewMode(self.lstIcons.IconMode)
            self.lstIcons.setUniformItemSizes(True)
            self.lstIcons.setResizeMode(self.lstIcons.Adjust)
            self.lstIcons.setMovement(self.lstIcons.Static)
            self.lstIcons.setStyleSheet("background: transparent; background: none;")
            self.filterLstIcons("")
            act = QWidgetAction(self.menuCustomIcons)
            act.setDefaultWidget(self.lstIcons)
            self.menuCustomIcons.addAction(act)

            menu.addMenu(self.menuCustomIcons)

        # Disabling stuff
        if not clipboard.mimeData().hasFormat("application/xml"):
            self.actPaste.setEnabled(False)

        if len(sel) == 0:
            self.actCopy.setEnabled(False)
            self.actCut.setEnabled(False)
            self.actRename.setEnabled(False)
            self.actDelete.setEnabled(False)
            self.menuPOV.setEnabled(False)
            self.menuStatus.setEnabled(False)
            self.menuLabel.setEnabled(False)
            self.menuCustomIcons.setEnabled(False)

        if len(sel) > 1:
            self.actRename.setEnabled(False)

        return menu
Exemplo n.º 36
0
class MainWindow(object):

    def __init__(self):
        self.main_window_tab_table = QTabWidget()
        self.hbox = QHBoxLayout()
        self.but = QPushButton('Add')
        self.file_menu = None
        self.help_menu = None
        self.about_qt = None
        self.exit_action = None
        self.choice_interface = None

    def setup_ui(self, main_window):
        self.main_window = main_window
        main_window.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        red, green, blue = 11, 7, 11
        main_window.setPalette(QPalette(QColor(red, green, blue)))
        self.mdiArea = QMdiArea()
        self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.create_menu(main_window)
        self.create_tool_bars(main_window)
        main_window.setCentralWidget(self.mdiArea)
        self.windowMapper = QSignalMapper(main_window)

    def file_menu_action(self):

        self.exit_action = QAction(QIcon('../resources/icons/fileclose.png'), "&Exit", self.file_menu)
        self.exit_action.setShortcut('Ctrl+Q')
        self.exit_action.setStatusTip('Exit application')
        # self.exitAct = QAction("E&xit", self, shortcut=QKeySequence.Quit,
        #                       statusTip="Exit the application",
        #                       triggered=QApplication.instance().closeAllWindows)
        self.choice_interface = QAction(QIcon('../resources/icons/ethernet_card_vista.png'), '&New Table', self.file_menu)
        self.choice_interface.setShortcut('Ctrl+A')
        self.choice_interface.setStatusTip('Exit application')
        # create new table
        self.new_act = QAction(QIcon(':/images/new.png'), "&New", self.file_menu)
        self.new_act.setShortcut('Ctrl+Q')
        self.new_act.setStatusTip('Create a new file')
        # triggered=self.open
        self.open_act = QAction(QIcon(':/images/open.png'), "&Open", self.file_menu)
        self.open_act.setShortcut(QKeySequence.Open)
        self.open_act.setStatusTip('Open an existing file')
        # triggered=self.save
        self.save_act = QAction(QIcon(':/images/save.png'), "&Save", self.file_menu)
        self.save_act.setShortcut(QKeySequence.Save)
        self.save_act.setStatusTip('Save the table to disk')
        # triggered=self.saveAs
        self.save_as_act = QAction(QIcon(':/images/save.png'), "&Save &As", self.file_menu)
        self.save_as_act.setShortcut(QKeySequence.SaveAs)
        self.save_as_act.setStatusTip('Save the document under a new name')
        # triggered=self.cut
        self.cut_act = QAction(QIcon(':/images/save.png'), "&Cut", self.file_menu)
        self.cut_act.setShortcut(QKeySequence.Cut)
        self.cut_act.setStatusTip('Cut the current selections contents to the clipboard')
        # triggered=self.copy
        self.copy_act = QAction(QIcon(':/images/save.png'), "&Copy", self.file_menu)
        self.copy_act.setShortcut(QKeySequence.Copy)
        self.copy_act.setStatusTip('Copy the current selections contents to the clipboard')
        # triggered=self.paste
        self.paste_act = QAction(QIcon(':/images/save.png'), "&Paste", self.file_menu)
        self.paste_act.setShortcut(QKeySequence.Paste)
        self.paste_act.setStatusTip('Paste the clipboards contents into the current selection')
        self.close_act = QAction(QIcon(':/images/save.png'), "&Close", self.file_menu)
        self.close_act.setStatusTip('Close the active window')
        self.close_act.triggered.connect(self.mdiArea.closeActiveSubWindow)
        self.close_all_act = QAction(QIcon(':/images/save.png'), "&Close &All", self.file_menu)
        self.close_all_act.setStatusTip('Close all the windows')
        self.close_all_act.triggered.connect(self.mdiArea.closeAllSubWindows)
        self.tile_act = QAction(QIcon(':/images/save.png'), "&Tile", self.file_menu)
        self.tile_act.setStatusTip('Tile the windows')
        self.tile_act.triggered.connect(self.mdiArea.tileSubWindows)
        self.cascade_act = QAction(QIcon(':/images/save.png'), "&Cascade", self.file_menu)
        self.cascade_act.setStatusTip('Cascade the windows')
        self.cascade_act.triggered.connect(self.mdiArea.cascadeSubWindows)
        self.next_act = QAction(QIcon(':/images/save.png'), "&Next", self.file_menu)
        self.next_act.setShortcut(QKeySequence.NextChild)
        self.next_act.setStatusTip('Move the focus to the next window')
        self.next_act.triggered.connect(self.mdiArea.activateNextSubWindow)
        self.previous_act = QAction(QIcon(':/images/save.png'), "&Previous", self.file_menu)
        self.previous_act.setShortcut(QKeySequence.PreviousChild)
        self.previous_act.setStatusTip('Move the focus to the previous window')
        self.previous_act.triggered.connect(self.mdiArea.activatePreviousSubWindow)
        # triggered=self.about

        # self.separatorAct = QAction(self)
        # self.separatorAct.setSeparator(True)

    def help_menu_action(self):
        self.about_qt = QAction(QIcon('../resources/icons/info.png'), '&About Qt', self.help_menu)
        self.about_qt.setShortcut('Ctrl+L')
        self.about_qt.setStatusTip('Show the Qt librarys About box')
        self.about_qt.triggered.connect(qApp.aboutQt)
        # self.aboutQtAct = QAction("About &Qt", self,
        #                           statusTip="Show the Qt library's About box",
        #                           triggered=QApplication.instance().aboutQt)
        self.about_program_act = QAction(QIcon(':/images/save.png'), "&About Program", self.file_menu)
        self.about_program_act.setStatusTip('Show the applications About box')

    def create_menu(self, main_window):
        self.file_menu = main_window.menuBar().addMenu("&File")
        self.file_menu_action()
        self.file_menu.addAction(self.choice_interface)
        self.file_menu.addAction(self.exit_action)
        self.file_menu.addAction(self.new_act)
        self.file_menu.addAction(self.open_act)
        self.file_menu.addAction(self.save_act)
        self.file_menu.addAction(self.save_as_act)
        # self.fileMenu.addSeparator()
        self.help_menu = main_window.menuBar().addMenu("&Help")
        self.help_menu_action()
        self.help_menu.addAction(self.about_qt)
        self.help_menu.addAction(self.about_program_act)

        self.editMenu = main_window.menuBar().addMenu("&Edit")
        self.editMenu.addAction(self.cut_act)
        self.editMenu.addAction(self.copy_act)
        self.editMenu.addAction(self.paste_act)

        self.windowMenu = main_window.menuBar().addMenu("&Window")
        self.updateWindowMenu()
        self.windowMenu.aboutToShow.connect(self.updateWindowMenu)

        # self.menuBar().addSeparator()

    def about(self):
        QMessageBox.about(self, "About MDI",
                          "The <b>MDI</b> example demonstrates how to write multiple "
                          "document interface applications using Qt.")

    def updateMenus(self):
        hasMdiChild = (self.activeMdiChild() is not None)
        self.save_act.setEnabled(hasMdiChild)
        self.save_as_act.setEnabled(hasMdiChild)
        self.paste_act.setEnabled(hasMdiChild)
        self.close_act.setEnabled(hasMdiChild)
        self.close_all_act.setEnabled(hasMdiChild)
        self.tile_act.setEnabled(hasMdiChild)
        self.cascade_act.setEnabled(hasMdiChild)
        self.next_act.setEnabled(hasMdiChild)
        self.previous_act.setEnabled(hasMdiChild)
        # self.separator_act.setVisible(hasMdiChild)

        hasSelection = (self.activeMdiChild() is not None and
                        self.activeMdiChild().textCursor().hasSelection())
        self.cut_act.setEnabled(hasSelection)
        self.copy_act.setEnabled(hasSelection)

    def updateWindowMenu(self):
        self.windowMenu.clear()
        self.windowMenu.addAction(self.close_act)
        self.windowMenu.addAction(self.close_all_act)
        self.windowMenu.addSeparator()
        self.windowMenu.addAction(self.tile_act)
        self.windowMenu.addAction(self.cascade_act)
        self.windowMenu.addSeparator()
        self.windowMenu.addAction(self.next_act)
        self.windowMenu.addAction(self.previous_act)
        # self.windowMenu.addAction(self.separator_act)

        windows = self.mdiArea.subWindowList()
        # self.separator_act.setVisible(len(windows) != 0)

        for i, window in enumerate(windows):
            child = window.widget()

            text = "%d %s" % (i + 1, child.userFriendlyCurrentFile())
            if i < 9:
                text = '&' + text

            action = self.windowMenu.addAction(text)
            action.setCheckable(True)
            action.setChecked(child is self.main_window.activeMdiChild())
            action.triggered.connect(self.windowMapper.map)
            self.windowMapper.setMapping(action, window)

    # def switchLayoutDirection(self):
    #    if self.layoutDirection() == Qt.LeftToRight:
    #        QApplication.setLayoutDirection(Qt.RightToLeft)
    #    else:
    #        QApplication.setLayoutDirection(Qt.LeftToRight)
    '''
    def createMenus(self):
        self.fileMenu = self.menuBar().addMenu("&File")
        self.fileMenu.addAction(self.newAct)
        self.fileMenu.addAction(self.openAct)
        self.fileMenu.addAction(self.saveAct)
        self.fileMenu.addAction(self.saveAsAct)
        self.fileMenu.addSeparator()
        action = self.fileMenu.addAction("Switch layout direction")
        action.triggered.connect(self.switchLayoutDirection)
        self.fileMenu.addAction(self.exitAct)

        self.editMenu = self.menuBar().addMenu("&Edit")
        self.editMenu.addAction(self.cutAct)
        self.editMenu.addAction(self.copyAct)
        self.editMenu.addAction(self.pasteAct)

        self.windowMenu = self.menuBar().addMenu("&Window")
        self.updateWindowMenu()
        self.windowMenu.aboutToShow.connect(self.updateWindowMenu)

        self.menuBar().addSeparator()

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

    def create_tool_bars(self, main_window):
        self.file_tool_bar = main_window.addToolBar("File")
        self.file_tool_bar.addAction(self.new_act)
        self.file_tool_bar.addAction(self.open_act)
        self.file_tool_bar.addAction(self.save_act)

        self.edit_tool_bar = main_window.addToolBar("Edit")
        self.edit_tool_bar.addAction(self.cut_act)
        self.edit_tool_bar.addAction(self.copy_act)
        self.edit_tool_bar.addAction(self.paste_act)
Exemplo n.º 37
0
class MainWindow(QMainWindow, Ui_MainWindow):
    """docstring for MainWindow."""
    def __init__(self, parent=None):
        super(MainWindow, self).__init__()
        self._csvFilePath = ""
        self.serialport = serial.Serial()
        self.receiver_thread = readerThread(self)
        self.receiver_thread.setPort(self.serialport)
        self._localEcho = None
        self._viewMode = None
        self._quickSendOptRow = 1

        self.setupUi(self)
        self.setCorner(Qt.TopLeftCorner, Qt.LeftDockWidgetArea)
        self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea)
        font = QtGui.QFont()
        font.setFamily(EDITOR_FONT)
        font.setPointSize(9)
        self.txtEdtOutput.setFont(font)
        self.txtEdtInput.setFont(font)
        #self.quickSendTable.setFont(font)
        if UI_FONT is not None:
            font = QtGui.QFont()
            font.setFamily(UI_FONT)
            font.setPointSize(9)
            self.dockWidget_PortConfig.setFont(font)
            self.dockWidget_SendHex.setFont(font)
            self.dockWidget_QuickSend.setFont(font)
        self.setupMenu()
        self.setupFlatUi()
        self.onEnumPorts()

        icon = QtGui.QIcon(":/MyTerm.ico")
        self.setWindowIcon(icon)
        self.actionAbout.setIcon(icon)

        self.defaultStyleWidget = QWidget()
        self.defaultStyleWidget.setWindowIcon(icon)

        icon = QtGui.QIcon(":/qt_logo_16.ico")
        self.actionAbout_Qt.setIcon(icon)

        self._viewGroup = QActionGroup(self)
        self._viewGroup.addAction(self.actionAscii)
        self._viewGroup.addAction(self.actionHex_lowercase)
        self._viewGroup.addAction(self.actionHEX_UPPERCASE)
        self._viewGroup.setExclusive(True)

        # bind events
        self.actionOpen_Cmd_File.triggered.connect(self.openQuickSend)
        self.actionSave_Log.triggered.connect(self.onSaveLog)
        self.actionExit.triggered.connect(self.onExit)

        self.actionOpen.triggered.connect(self.openPort)
        self.actionClose.triggered.connect(self.closePort)

        self.actionPort_Config_Panel.triggered.connect(self.onTogglePrtCfgPnl)
        self.actionQuick_Send_Panel.triggered.connect(self.onToggleQckSndPnl)
        self.actionSend_Hex_Panel.triggered.connect(self.onToggleHexPnl)
        self.dockWidget_PortConfig.visibilityChanged.connect(
            self.onVisiblePrtCfgPnl)
        self.dockWidget_QuickSend.visibilityChanged.connect(
            self.onVisibleQckSndPnl)
        self.dockWidget_SendHex.visibilityChanged.connect(self.onVisibleHexPnl)
        self.actionLocal_Echo.triggered.connect(self.onLocalEcho)
        self.actionAlways_On_Top.triggered.connect(self.onAlwaysOnTop)

        self.actionAscii.triggered.connect(self.onViewChanged)
        self.actionHex_lowercase.triggered.connect(self.onViewChanged)
        self.actionHEX_UPPERCASE.triggered.connect(self.onViewChanged)

        self.actionAbout.triggered.connect(self.onAbout)
        self.actionAbout_Qt.triggered.connect(self.onAboutQt)

        self.btnOpen.clicked.connect(self.onOpen)
        self.btnClear.clicked.connect(self.onClear)
        self.btnSaveLog.clicked.connect(self.onSaveLog)
        self.btnEnumPorts.clicked.connect(self.onEnumPorts)
        self.btnSendHex.clicked.connect(self.onSend)

        self.receiver_thread.read.connect(self.onReceive)
        self.receiver_thread.exception.connect(self.onReaderExcept)
        self._signalMapQuickSendOpt = QSignalMapper(self)
        self._signalMapQuickSendOpt.mapped[int].connect(
            self.onQuickSendOptions)
        self._signalMapQuickSend = QSignalMapper(self)
        self._signalMapQuickSend.mapped[int].connect(self.onQuickSend)

        # initial action
        self.actionHEX_UPPERCASE.setChecked(True)
        self.receiver_thread.setViewMode(VIEWMODE_HEX_UPPERCASE)
        self.initQuickSend()
        self.restoreLayout()
        self.moveScreenCenter()
        self.syncMenu()

        if self.isMaximized():
            self.setMaximizeButton("restore")
        else:
            self.setMaximizeButton("maximize")

        self.loadSettings()

    def setupMenu(self):
        self.menuMenu = QtWidgets.QMenu()
        self.menuMenu.setTitle("&File")
        self.menuMenu.setObjectName("menuMenu")
        self.menuView = QtWidgets.QMenu(self.menuMenu)
        self.menuView.setTitle("&View")
        self.menuView.setObjectName("menuView")

        self.menuView.addAction(self.actionAscii)
        self.menuView.addAction(self.actionHex_lowercase)
        self.menuView.addAction(self.actionHEX_UPPERCASE)
        self.menuMenu.addAction(self.actionOpen_Cmd_File)
        self.menuMenu.addAction(self.actionSave_Log)
        self.menuMenu.addSeparator()
        self.menuMenu.addAction(self.actionPort_Config_Panel)
        self.menuMenu.addAction(self.actionQuick_Send_Panel)
        self.menuMenu.addAction(self.actionSend_Hex_Panel)
        self.menuMenu.addAction(self.menuView.menuAction())
        self.menuMenu.addAction(self.actionLocal_Echo)
        self.menuMenu.addAction(self.actionAlways_On_Top)
        self.menuMenu.addSeparator()
        self.menuMenu.addAction(self.actionAbout)
        self.menuMenu.addAction(self.actionAbout_Qt)
        self.menuMenu.addSeparator()
        self.menuMenu.addAction(self.actionExit)

        self.sendOptMenu = QtWidgets.QMenu()
        self.actionSend_Hex = QtWidgets.QAction(self)
        self.actionSend_Hex.setText("Send &Hex")
        self.actionSend_Hex.setStatusTip("Send Hex (e.g. 31 32 FF)")

        self.actionSend_Asc = QtWidgets.QAction(self)
        self.actionSend_Asc.setText("Send &Asc")
        self.actionSend_Asc.setStatusTip("Send Asc (e.g. abc123)")

        self.actionSend_TFH = QtWidgets.QAction(self)
        self.actionSend_TFH.setText("Send &Text file as hex")
        self.actionSend_TFH.setStatusTip(
            'Send text file as hex (e.g. strings "31 32 FF" in the file)')

        self.actionSend_TFA = QtWidgets.QAction(self)
        self.actionSend_TFA.setText("Send t&Ext file as asc")
        self.actionSend_TFA.setStatusTip(
            'Send text file as asc (e.g. strings "abc123" in the file)')

        self.actionSend_FB = QtWidgets.QAction(self)
        self.actionSend_FB.setText("Send &Bin/Hex file")
        self.actionSend_FB.setStatusTip("Send a bin file or a hex file")

        self.sendOptMenu.addAction(self.actionSend_Hex)
        self.sendOptMenu.addAction(self.actionSend_Asc)
        self.sendOptMenu.addAction(self.actionSend_TFH)
        self.sendOptMenu.addAction(self.actionSend_TFA)
        self.sendOptMenu.addAction(self.actionSend_FB)

        self.actionSend_Hex.triggered.connect(self.onSetSendHex)
        self.actionSend_Asc.triggered.connect(self.onSetSendAsc)
        self.actionSend_TFH.triggered.connect(self.onSetSendTFH)
        self.actionSend_TFA.triggered.connect(self.onSetSendTFA)
        self.actionSend_FB.triggered.connect(self.onSetSendFB)

    def setupFlatUi(self):
        self._dragPos = self.pos()
        self._isDragging = False
        self.setMouseTracking(True)
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setStyleSheet("""
            QWidget {
                background-color: %(BackgroundColor)s;
                /*background-image: url(:/background.png);*/
                outline: none;
            }
            QLabel {
                color:%(TextColor)s;
                font-size:12px;
                /*font-family:Century;*/
            }
            
            QComboBox {
                color:%(TextColor)s;
                font-size:12px;
                /*font-family:Century;*/
            }
            QComboBox {
                border: none;
                padding: 1px 1px 1px 3px;
            }
            QComboBox:editable {
                background: white;
            }
            QComboBox:!editable, QComboBox::drop-down:editable {
                background: #62c7e0;
            }
            QComboBox:!editable:hover, QComboBox::drop-down:editable:hover {
                background: #c7eaf3;
            }
            QComboBox:!editable:pressed, QComboBox::drop-down:editable:pressed {
                background: #35b6d7;
            }
            QComboBox:on {
                padding-top: 3px;
                padding-left: 4px;
            }
            QComboBox::drop-down {
                subcontrol-origin: padding;
                subcontrol-position: top right;
                width: 16px;
                border: none;
            }
            QComboBox::down-arrow {
                image: url(:/downarrow.png);
            }
            QComboBox::down-arrow:on {
                image: url(:/uparrow.png);
            }
            
            QGroupBox {
                color:%(TextColor)s;
                font-size:12px;
                /*font-family:Century;*/
                border: 1px solid gray;
                margin-top: 15px;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                subcontrol-position: top left;
                left:5px;
                top:3px;
            }
            
            QCheckBox {
                color:%(TextColor)s;
                spacing: 5px;
                font-size:12px;
                /*font-family:Century;*/
            }
            QCheckBox::indicator:unchecked {
                image: url(:/checkbox_unchecked.png);
            }

            QCheckBox::indicator:unchecked:hover {
                image: url(:/checkbox_unchecked_hover.png);
            }

            QCheckBox::indicator:unchecked:pressed {
                image: url(:/checkbox_unchecked_pressed.png);
            }

            QCheckBox::indicator:checked {
                image: url(:/checkbox_checked.png);
            }

            QCheckBox::indicator:checked:hover {
                image: url(:/checkbox_checked_hover.png);
            }

            QCheckBox::indicator:checked:pressed {
                image: url(:/checkbox_checked_pressed.png);
            }
            
            QScrollBar:horizontal {
                background-color:%(BackgroundColor)s;
                border: none;
                height: 15px;
                margin: 0px 20px 0 20px;
            }
            QScrollBar::handle:horizontal {
                background: %(ScrollBar_Handle)s;
                min-width: 20px;
            }
            QScrollBar::add-line:horizontal {
                image: url(:/rightarrow.png);
                border: none;
                background: %(ScrollBar_Line)s;
                width: 20px;
                subcontrol-position: right;
                subcontrol-origin: margin;
            }
            QScrollBar::sub-line:horizontal {
                image: url(:/leftarrow.png);
                border: none;
                background: %(ScrollBar_Line)s;
                width: 20px;
                subcontrol-position: left;
                subcontrol-origin: margin;
            }
            
            QScrollBar:vertical {
                background-color:%(BackgroundColor)s;
                border: none;
                width: 15px;
                margin: 20px 0px 20px 0px;
            }
            QScrollBar::handle::vertical {
                background: %(ScrollBar_Handle)s;
                min-height: 20px;
            }
            QScrollBar::add-line::vertical {
                image: url(:/downarrow.png);
                border: none;
                background: %(ScrollBar_Line)s;
                height: 20px;
                subcontrol-position: bottom;
                subcontrol-origin: margin;
            }
            QScrollBar::sub-line::vertical {
                image: url(:/uparrow.png);
                border: none;
                background: %(ScrollBar_Line)s;
                height: 20px;
                subcontrol-position: top;
                subcontrol-origin: margin;
            }
            
            QTableView {
                background-color: white;
                /*selection-background-color: #FF92BB;*/
                border: 1px solid %(TableView_Border)s;
                color: %(TextColor)s;
            }
            QTableView::focus {
                /*border: 1px solid #2a7fff;*/
            }
            QTableView QTableCornerButton::section {
                border: none;
                border-right: 1px solid %(TableView_Border)s;
                border-bottom: 1px solid %(TableView_Border)s;
                background-color: %(TableView_Corner)s;
            }
            QTableView QWidget {
                background-color: white;
            }
            QTableView::item:focus {
                border: 1px red;
                background-color: transparent;
                color: %(TextColor)s;
            }
            QHeaderView::section {
                border: none;
                border-right: 1px solid %(TableView_Border)s;
                border-bottom: 1px solid %(TableView_Border)s;
                padding-left: 2px;
                padding-right: 2px;
                color: #444444;
                background-color: %(TableView_Header)s;
            }
            QTextEdit {
                background-color:white;
                color:%(TextColor)s;
                border-top: none;
                border-bottom: none;
                border-left: 2px solid %(BackgroundColor)s;
                border-right: 2px solid %(BackgroundColor)s;
            }
            QTextEdit::focus {
            }
            
            QToolButton, QPushButton {
                background-color:#30a7b8;
                border:none;
                color:#ffffff;
                font-size:12px;
                /*font-family:Century;*/
            }
            QToolButton:hover, QPushButton:hover {
                background-color:#51c0d1;
            }
            QToolButton:pressed, QPushButton:pressed {
                background-color:#3a9ecc;
            }
            
            QMenuBar {
                color: %(TextColor)s;
                height: 24px;
            }
            QMenuBar::item {
                background-color: transparent;
                margin: 8px 0px 0px 0px;
                padding: 1px 8px 1px 8px;
                height: 15px;
            }
            QMenuBar::item:selected {
                background: #51c0d1;
            }
            QMenuBar::item:pressed {
                
            }
            /*
            QMenu {
                color: %(TextColor)s;
                background: #ffffff;
            }
            QMenu {
                margin: 2px;
            }
            QMenu::item {
                padding: 2px 25px 2px 21px;
                border: 1px solid transparent;
            }
            QMenu::item:selected {
                background: #51c0d1;
            }
            QMenu::icon {
                background: transparent;
                border: 2px inset transparent;
            }*/

            QDockWidget {
                font-size:12px;
                /*font-family:Century;*/
                color: %(TextColor)s;
                titlebar-close-icon: none;
                titlebar-normal-icon: none;
            }
            QDockWidget::title {
                margin: 0;
                padding: 2px;
                subcontrol-origin: content;
                subcontrol-position: right top;
                text-align: left;
                background: #67baed;
                
            }
            QDockWidget::float-button {
                max-width: 12px;
                max-height: 12px;
                background-color:transparent;
                border:none;
                image: url(:/restore_inactive.png);
            }
            QDockWidget::float-button:hover {
                background-color:#227582;
                image: url(:/restore_active.png);
            }
            QDockWidget::float-button:pressed {
                padding: 0;
                background-color:#14464e;
                image: url(:/restore_active.png);
            }
            QDockWidget::close-button {
                max-width: 12px;
                max-height: 12px;
                background-color:transparent;
                border:none;
                image: url(:/close_inactive.png);
            }
            QDockWidget::close-button:hover {
                background-color:#ea5e00;
                image: url(:/close_active.png);
            }
            QDockWidget::close-button:pressed {
                background-color:#994005;
                image: url(:/close_active.png);
                padding: 0;
            }
            
        """ % dict(BackgroundColor='#99d9ea',
                   TextColor='#202020',
                   ScrollBar_Handle='#61b9e1',
                   ScrollBar_Line='#7ecfe4',
                   TableView_Corner='#8ae6d2',
                   TableView_Header='#8ae6d2',
                   TableView_Border='#eeeeee'))
        self.dockWidgetContents.setStyleSheet("""
            QPushButton {
                min-height:23px;
            }
        """)
        self.dockWidget_QuickSend.setStyleSheet("""
            QToolButton, QPushButton {
                background-color:#27b798;
                /*font-family:Consolas;*/
                /*font-size:12px;*/
                /*min-width:46px;*/
            }
            QToolButton:hover, QPushButton:hover {
                background-color:#3bd5b4;
            }
            QToolButton:pressed, QPushButton:pressed {
                background-color:#1d8770;
            }
        """)
        self.dockWidgetContents_2.setStyleSheet("""
            QPushButton {
                min-height:23px;
                min-width:50px;
            }
        """)

        w = self.frameGeometry().width()
        self._minBtn = QPushButton(self)
        self._minBtn.setGeometry(w - 103, 0, 28, 24)
        self._minBtn.clicked.connect(self.onMinimize)
        self._minBtn.setStyleSheet("""
            QPushButton {
                background-color:transparent;
                border:none;
                outline: none;
                image: url(:/minimize_inactive.png);
            }
            QPushButton:hover {
                background-color:#227582;
                image: url(:/minimize_active.png);
            }
            QPushButton:pressed {
                background-color:#14464e;
                image: url(:/minimize_active.png);
            }
        """)

        self._maxBtn = QPushButton(self)
        self._maxBtn.setGeometry(w - 74, 0, 28, 24)
        self._maxBtn.clicked.connect(self.onMaximize)
        self.setMaximizeButton("maximize")

        self._closeBtn = QPushButton(self)
        self._closeBtn.setGeometry(w - 45, 0, 36, 24)
        self._closeBtn.clicked.connect(self.onExit)
        self._closeBtn.setStyleSheet("""
            QPushButton {
                background-color:transparent;
                border:none;
                outline: none;
                image: url(:/close_inactive.png);
            }
            QPushButton:hover {
                background-color:#ea5e00;
                image: url(:/close_active.png);
            }
            QPushButton:pressed {
                background-color:#994005;
                image: url(:/close_active.png);
            }
        """)

        self.btnMenu = QtWidgets.QToolButton(self)
        self.btnMenu.setEnabled(True)
        self.btnMenu.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
        self.btnMenu.setIcon(QtGui.QIcon(':/MyTerm.ico'))
        self.btnMenu.setText('Myterm  ')
        self.btnMenu.setGeometry(3, 3, 80, 18)
        self.btnMenu.setMenu(self.menuMenu)
        self.btnMenu.setPopupMode(QtWidgets.QToolButton.InstantPopup)

        self.btnRefresh = QtWidgets.QToolButton(self)
        self.btnRefresh.setEnabled(True)
        self.btnRefresh.setIcon(QtGui.QIcon(':/refresh.ico'))
        self.btnRefresh.setGeometry(110, 3, 18, 18)
        self.btnRefresh.clicked.connect(self.onEnumPorts)

        self.verticalLayout_1.removeWidget(self.cmbPort)
        self.cmbPort.setParent(self)
        self.cmbPort.setGeometry(128, 3, 60, 18)

        self.verticalLayout_1.removeWidget(self.btnOpen)
        self.btnOpen.setParent(self)
        self.btnOpen.setGeometry(210, 3, 60, 18)

    def resizeEvent(self, event):
        w = event.size().width()
        self._minBtn.move(w - 103, 0)
        self._maxBtn.move(w - 74, 0)
        self._closeBtn.move(w - 45, 0)

    def onMinimize(self):
        self.showMinimized()

    def isMaximized(self):
        return ((self.windowState() == Qt.WindowMaximized))

    def onMaximize(self):
        if self.isMaximized():
            self.showNormal()
            self.setMaximizeButton("maximize")
        else:
            self.showMaximized()
            self.setMaximizeButton("restore")

    def setMaximizeButton(self, style):
        if "maximize" == style:
            self._maxBtn.setStyleSheet("""
                QPushButton {
                    background-color:transparent;
                    border:none;
                    outline: none;
                    image: url(:/maximize_inactive.png);
                }
                QPushButton:hover {
                    background-color:#227582;
                    image: url(:/maximize_active.png);
                }
                QPushButton:pressed {
                    background-color:#14464e;
                    image: url(:/maximize_active.png);
                }
            """)
        elif "restore" == style:
            self._maxBtn.setStyleSheet("""
                QPushButton {
                    background-color:transparent;
                    border:none;
                    outline: none;
                    image: url(:/restore_inactive.png);
                }
                QPushButton:hover {
                    background-color:#227582;
                    image: url(:/restore_active.png);
                }
                QPushButton:pressed {
                    background-color:#14464e;
                    image: url(:/restore_active.png);
                }
            """)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self._isDragging = True
            self._dragPos = event.globalPos() - self.pos()
        event.accept()

    def mouseMoveEvent(self, event):
        if event.buttons(
        ) == Qt.LeftButton and self._isDragging and not self.isMaximized():
            self.move(event.globalPos() - self._dragPos)
        event.accept()

    def mouseReleaseEvent(self, event):
        self._isDragging = False
        event.accept()

    def saveSettings(self):
        root = ET.Element("MyTerm")
        GUISettings = ET.SubElement(root, "GUISettings")

        PortCfg = ET.SubElement(GUISettings, "PortConfig")
        ET.SubElement(PortCfg, "port").text = self.cmbPort.currentText()
        ET.SubElement(PortCfg,
                      "baudrate").text = self.cmbBaudRate.currentText()
        ET.SubElement(PortCfg,
                      "databits").text = self.cmbDataBits.currentText()
        ET.SubElement(PortCfg, "parity").text = self.cmbParity.currentText()
        ET.SubElement(PortCfg,
                      "stopbits").text = self.cmbStopBits.currentText()
        ET.SubElement(
            PortCfg,
            "rtscts").text = self.chkRTSCTS.isChecked() and "on" or "off"
        ET.SubElement(
            PortCfg,
            "xonxoff").text = self.chkXonXoff.isChecked() and "on" or "off"

        View = ET.SubElement(GUISettings, "View")
        ET.SubElement(
            View, "LocalEcho"
        ).text = self.actionLocal_Echo.isChecked() and "on" or "off"
        ET.SubElement(
            View, "ReceiveView").text = self._viewGroup.checkedAction().text()

        with open(get_config_path('MyTerm.xml'), 'w') as f:
            f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
            f.write(
                ET.tostring(root, encoding='utf-8',
                            pretty_print=True).decode("utf-8"))

    def loadSettings(self):
        if os.path.isfile(get_config_path("MyTerm.xml")):
            with open(get_config_path("MyTerm.xml"), 'r') as f:
                tree = safeET.parse(f)

            port = tree.findtext('GUISettings/PortConfig/port', default='')
            if port != '':
                self.cmbPort.setCurrentText(port)

            baudrate = tree.findtext('GUISettings/PortConfig/baudrate',
                                     default='38400')
            if baudrate != '':
                self.cmbBaudRate.setCurrentText(baudrate)

            databits = tree.findtext('GUISettings/PortConfig/databits',
                                     default='8')
            id = self.cmbDataBits.findText(databits)
            if id >= 0:
                self.cmbDataBits.setCurrentIndex(id)

            parity = tree.findtext('GUISettings/PortConfig/parity',
                                   default='None')
            id = self.cmbParity.findText(parity)
            if id >= 0:
                self.cmbParity.setCurrentIndex(id)

            stopbits = tree.findtext('GUISettings/PortConfig/stopbits',
                                     default='1')
            id = self.cmbStopBits.findText(stopbits)
            if id >= 0:
                self.cmbStopBits.setCurrentIndex(id)

            rtscts = tree.findtext('GUISettings/PortConfig/rtscts',
                                   default='off')
            if 'on' == rtscts:
                self.chkRTSCTS.setChecked(True)
            else:
                self.chkRTSCTS.setChecked(False)

            xonxoff = tree.findtext('GUISettings/PortConfig/xonxoff',
                                    default='off')
            if 'on' == xonxoff:
                self.chkXonXoff.setChecked(True)
            else:
                self.chkXonXoff.setChecked(False)

            LocalEcho = tree.findtext('GUISettings/View/LocalEcho',
                                      default='off')
            if 'on' == LocalEcho:
                self.actionLocal_Echo.setChecked(True)
                self._localEcho = True
            else:
                self.actionLocal_Echo.setChecked(False)
                self._localEcho = False

            ReceiveView = tree.findtext('GUISettings/View/ReceiveView',
                                        default='HEX(UPPERCASE)')
            if 'Ascii' in ReceiveView:
                self.actionAscii.setChecked(True)
                self._viewMode = VIEWMODE_ASCII
            elif 'lowercase' in ReceiveView:
                self.actionHex_lowercase.setChecked(True)
                self._viewMode = VIEWMODE_HEX_LOWERCASE
            elif 'UPPERCASE' in ReceiveView:
                self.actionHEX_UPPERCASE.setChecked(True)
                self._viewMode = VIEWMODE_HEX_UPPERCASE
            self.receiver_thread.setViewMode(self._viewMode)

    def closeEvent(self, event):
        self.saveLayout()
        self.saveQuickSend()
        self.saveSettings()
        event.accept()

    def initQuickSend(self):
        #self.quickSendTable.horizontalHeader().setDefaultSectionSize(40)
        #self.quickSendTable.horizontalHeader().setMinimumSectionSize(25)
        self.quickSendTable.setRowCount(50)
        self.quickSendTable.setColumnCount(3)
        self.quickSendTable.verticalHeader().setSectionsClickable(True)

        for row in range(50):
            self.initQuickSendButton(row)

        if os.path.isfile(get_config_path('QuickSend.csv')):
            self.loadQuickSend(get_config_path('QuickSend.csv'))

        self.quickSendTable.resizeColumnsToContents()

    def initQuickSendButton(self, row, cmd='cmd', opt='H', dat=''):
        if self.quickSendTable.cellWidget(row, 0) is None:
            item = QToolButton(self)
            item.setText(cmd)
            item.clicked.connect(self._signalMapQuickSend.map)
            self._signalMapQuickSend.setMapping(item, row)
            self.quickSendTable.setCellWidget(row, 0, item)
        else:
            self.quickSendTable.cellWidget(row, 0).setText(cmd)

        if self.quickSendTable.cellWidget(row, 1) is None:
            item = QToolButton(self)
            item.setText(opt)
            #item.setMaximumSize(QtCore.QSize(16, 16))
            item.clicked.connect(self._signalMapQuickSendOpt.map)
            self._signalMapQuickSendOpt.setMapping(item, row)
            self.quickSendTable.setCellWidget(row, 1, item)
        else:
            self.quickSendTable.cellWidget(row, 1).setText(opt)

        if self.quickSendTable.item(row, 2) is None:
            self.quickSendTable.setItem(row, 2, QTableWidgetItem(dat))
        else:
            self.quickSendTable.item(row, 2).setText(dat)

        self.quickSendTable.setRowHeight(row, 16)

    def onSetSendHex(self):
        item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1)
        item.setText('H')

    def onSetSendAsc(self):
        item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1)
        item.setText('A')

    def onSetSendTFH(self):
        item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1)
        item.setText('FH')

    def onSetSendTFA(self):
        item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1)
        item.setText('FA')

    def onSetSendFB(self):
        item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1)
        item.setText('FB')

    def onQuickSendOptions(self, row):
        self._quickSendOptRow = row
        item = self.quickSendTable.cellWidget(row, 1)
        self.sendOptMenu.popup(
            item.mapToGlobal(QPoint(item.size().width(),
                                    item.size().height())))

    def openQuickSend(self):
        fileName = QFileDialog.getOpenFileName(self, "Select a file",
                                               os.getcwd(),
                                               "CSV Files (*.csv)")[0]
        if fileName:
            self.loadQuickSend(fileName, notifyExcept=True)

    def saveQuickSend(self):
        # scan table
        rows = self.quickSendTable.rowCount()
        #cols = self.quickSendTable.columnCount()

        save_data = [[
            self.quickSendTable.cellWidget(row, 0).text(),
            self.quickSendTable.cellWidget(row, 1).text(),
            self.quickSendTable.item(row, 2) is not None
            and self.quickSendTable.item(row, 2).text() or ''
        ] for row in range(rows)]

        #import pprint
        #pprint.pprint(save_data, width=120, compact=True)

        # write to file
        with open(get_config_path('QuickSend.csv'), 'w') as csvfile:
            csvwriter = csv.writer(csvfile, delimiter=',', lineterminator='\n')
            csvwriter.writerows(save_data)

    def loadQuickSend(self, path, notifyExcept=False):
        data = []
        set_rows = 0
        set_cols = 0
        try:
            with open(path) as csvfile:
                csvData = csv.reader(csvfile)
                for row in csvData:
                    data.append(row)
                    set_rows = set_rows + 1
                    if len(row) > set_cols:
                        set_cols = len(row)
        except IOError as e:
            print("({})".format(e))
            if notifyExcept:
                QMessageBox.critical(self.defaultStyleWidget, "Open failed",
                                     str(e), QMessageBox.Close)
            return

        rows = self.quickSendTable.rowCount()
        cols = self.quickSendTable.columnCount()

        if rows < set_rows:
            rows = set_rows + 10
            self.quickSendTable.setRowCount(rows)

        for row, rowdat in enumerate(data):
            if len(rowdat) >= 3:
                cmd, opt, dat = rowdat[0:3]
                self.initQuickSendButton(row, cmd, opt, dat)


#                self.quickSendTable.cellWidget(row, 0).setText(cmd)
#                self.quickSendTable.cellWidget(row, 1).setText(opt)
#                self.quickSendTable.setItem(row, 2, QTableWidgetItem(dat))

        self.quickSendTable.resizeColumnsToContents()
        #self.quickSendTable.resizeRowsToContents()

    def onQuickSend(self, row):
        if self.quickSendTable.item(row, 2) is not None:
            tablestring = self.quickSendTable.item(row, 2).text()
            format = self.quickSendTable.cellWidget(row, 1).text()
            if 'H' == format:
                self.transmitHex(tablestring)
            elif 'A' == format:
                self.transmitAsc(tablestring)
            elif 'FB' == format:
                try:
                    with open(tablestring, 'rb') as f:
                        bytes = f.read()
                        self.transmitBytearray(bytes)
                except IOError as e:
                    print("({})".format(e))
                    QMessageBox.critical(self.defaultStyleWidget,
                                         "Open failed", str(e),
                                         QMessageBox.Close)
            else:
                try:
                    with open(tablestring, 'rt') as f:
                        filestring = f.read()
                        if 'FH' == format:
                            self.transmitHex(filestring)
                        elif 'FA' == format:
                            self.transmitAsc(filestring)
                except IOError as e:
                    print("({})".format(e))
                    QMessageBox.critical(self.defaultStyleWidget,
                                         "Open failed", str(e),
                                         QMessageBox.Close)

    def onSend(self):
        sendstring = self.txtEdtInput.toPlainText()
        self.transmitHex(sendstring)

    def transmitHex(self, hexstring):
        if len(hexstring) > 0:
            hexarray = []
            _hexstring = hexstring.replace(' ', '')
            _hexstring = _hexstring.replace('\r', '')
            _hexstring = _hexstring.replace('\n', '')
            for i in range(0, len(_hexstring), 2):
                word = _hexstring[i:i + 2]
                if is_hex(word):
                    hexarray.append(int(word, 16))
                else:
                    QMessageBox.critical(self.defaultStyleWidget, "Error",
                                         "'%s' is not hexadecimal." % (word),
                                         QMessageBox.Close)
                    return

            self.transmitBytearray(bytearray(hexarray))

    def transmitAsc(self, text):
        if len(text) > 0:
            byteArray = [ord(char) for char in text]
            self.transmitBytearray(bytearray(byteArray))

    def transmitBytearray(self, byteArray):
        if self.serialport.isOpen():
            try:
                self.serialport.write(byteArray)
            except Exception as e:
                QMessageBox.critical(self.defaultStyleWidget,
                                     "Exception in transmit", str(e),
                                     QMessageBox.Close)
                print("Exception in transmitBytearray(%s)" % text)
            else:
                if self._viewMode == VIEWMODE_ASCII:
                    text = byteArray.decode('unicode_escape')
                elif self._viewMode == VIEWMODE_HEX_LOWERCASE:
                    text = ''.join('%02x ' % t for t in byteArray)
                elif self._viewMode == VIEWMODE_HEX_UPPERCASE:
                    text = ''.join('%02X ' % t for t in byteArray)
                self.appendOutputText("\n%s T->:%s" % (self.timestamp(), text),
                                      Qt.blue)

    def onReaderExcept(self, e):
        self.closePort()
        QMessageBox.critical(self.defaultStyleWidget, "Read failed", str(e),
                             QMessageBox.Close)

    def timestamp(self):
        return datetime.datetime.now().time().isoformat()[:-3]

    def onReceive(self, data):
        self.appendOutputText("\n%s R<-:%s" % (self.timestamp(), data))

    def appendOutputText(self, data, color=Qt.black):
        # the qEditText's "append" methon will add a unnecessary newline.
        # self.txtEdtOutput.append(data.decode('utf-8'))

        tc = self.txtEdtOutput.textColor()
        self.txtEdtOutput.moveCursor(QtGui.QTextCursor.End)
        self.txtEdtOutput.setTextColor(QtGui.QColor(color))
        self.txtEdtOutput.insertPlainText(data)
        self.txtEdtOutput.moveCursor(QtGui.QTextCursor.End)
        self.txtEdtOutput.setTextColor(tc)

    def getPort(self):
        return self.cmbPort.currentText()

    def getDataBits(self):
        return {
            '5': serial.FIVEBITS,
            '6': serial.SIXBITS,
            '7': serial.SEVENBITS,
            '8': serial.EIGHTBITS
        }[self.cmbDataBits.currentText()]

    def getParity(self):
        return {
            'None': serial.PARITY_NONE,
            'Even': serial.PARITY_EVEN,
            'Odd': serial.PARITY_ODD,
            'Mark': serial.PARITY_MARK,
            'Space': serial.PARITY_SPACE
        }[self.cmbParity.currentText()]

    def getStopBits(self):
        return {
            '1': serial.STOPBITS_ONE,
            '1.5': serial.STOPBITS_ONE_POINT_FIVE,
            '2': serial.STOPBITS_TWO
        }[self.cmbStopBits.currentText()]

    def openPort(self):
        if self.serialport.isOpen():
            return

        _port = self.getPort()
        if '' == _port:
            QMessageBox.information(self.defaultStyleWidget,
                                    "Invalid parameters", "Port is empty.")
            return

        _baudrate = self.cmbBaudRate.currentText()
        if '' == _baudrate:
            QMessageBox.information(self.defaultStyleWidget,
                                    "Invalid parameters", "Baudrate is empty.")
            return

        self.serialport.port = _port
        self.serialport.baudrate = _baudrate
        self.serialport.bytesize = self.getDataBits()
        self.serialport.stopbits = self.getStopBits()
        self.serialport.parity = self.getParity()
        self.serialport.rtscts = self.chkRTSCTS.isChecked()
        self.serialport.xonxoff = self.chkXonXoff.isChecked()
        # self.serialport.timeout  = THREAD_TIMEOUT
        # self.serialport.writeTimeout = SERIAL_WRITE_TIMEOUT
        try:
            self.serialport.open()
        except Exception as e:
            QMessageBox.critical(self.defaultStyleWidget,
                                 "Could not open serial port", str(e),
                                 QMessageBox.Close)
        else:
            self._start_reader()
            self.setWindowTitle("%s on %s [%s, %s%s%s%s%s]" % (
                appInfo.title,
                self.serialport.portstr,
                self.serialport.baudrate,
                self.serialport.bytesize,
                self.serialport.parity,
                self.serialport.stopbits,
                self.serialport.rtscts and ' RTS/CTS' or '',
                self.serialport.xonxoff and ' Xon/Xoff' or '',
            ))
            pal = self.btnOpen.palette()
            pal.setColor(QtGui.QPalette.Button, QtGui.QColor(0, 0xff, 0x7f))
            self.btnOpen.setAutoFillBackground(True)
            self.btnOpen.setPalette(pal)
            self.btnOpen.setText('Close')
            self.btnOpen.update()

    def closePort(self):
        if self.serialport.isOpen():
            self._stop_reader()
            self.serialport.close()
            self.setWindowTitle(appInfo.title)
            pal = self.btnOpen.style().standardPalette()
            self.btnOpen.setAutoFillBackground(True)
            self.btnOpen.setPalette(pal)
            self.btnOpen.setText('Open')
            self.btnOpen.update()

    def _start_reader(self):
        """Start reader thread"""
        self.receiver_thread.start()

    def _stop_reader(self):
        """Stop reader thread only, wait for clean exit of thread"""
        self.receiver_thread.join()

    def onTogglePrtCfgPnl(self):
        if self.actionPort_Config_Panel.isChecked():
            self.dockWidget_PortConfig.show()
        else:
            self.dockWidget_PortConfig.hide()

    def onToggleQckSndPnl(self):
        if self.actionQuick_Send_Panel.isChecked():
            self.dockWidget_QuickSend.show()
        else:
            self.dockWidget_QuickSend.hide()

    def onToggleHexPnl(self):
        if self.actionSend_Hex_Panel.isChecked():
            self.dockWidget_SendHex.show()
        else:
            self.dockWidget_SendHex.hide()

    def onVisiblePrtCfgPnl(self, visible):
        self.actionPort_Config_Panel.setChecked(visible)

    def onVisibleQckSndPnl(self, visible):
        self.actionQuick_Send_Panel.setChecked(visible)

    def onVisibleHexPnl(self, visible):
        self.actionSend_Hex_Panel.setChecked(visible)

    def onLocalEcho(self):
        self._localEcho = self.actionLocal_Echo.isChecked()

    def onAlwaysOnTop(self):
        if self.actionAlways_On_Top.isChecked():
            style = self.windowFlags()
            self.setWindowFlags(style | Qt.WindowStaysOnTopHint)
            self.show()
        else:
            style = self.windowFlags()
            self.setWindowFlags(style & ~Qt.WindowStaysOnTopHint)
            self.show()

    def onOpen(self):
        if self.serialport.isOpen():
            self.closePort()
        else:
            self.openPort()

    def onClear(self):
        self.txtEdtOutput.clear()

    def onSaveLog(self):
        fileName = QFileDialog.getSaveFileName(
            self, "Save as", os.getcwd(),
            "Log files (*.log);;Text files (*.txt);;All files (*.*)")[0]
        if fileName:
            import codecs
            with codecs.open(fileName, 'w', 'utf-8') as f:
                f.write(self.txtEdtOutput.toPlainText())

    def moveScreenCenter(self):
        w = self.frameGeometry().width()
        h = self.frameGeometry().height()
        desktop = QDesktopWidget()
        screenW = desktop.screen().width()
        screenH = desktop.screen().height()
        self.setGeometry((screenW - w) / 2, (screenH - h) / 2, w, h)

    def onEnumPorts(self):
        self.cmbPort.clear()
        for p in enum_ports():
            self.cmbPort.addItem(p)

    def onAbout(self):
        QMessageBox.about(self.defaultStyleWidget, "About MyTerm",
                          appInfo.aboutme)

    def onAboutQt(self):
        QMessageBox.aboutQt(self.defaultStyleWidget)

    def onExit(self):
        if self.serialport.isOpen():
            self.closePort()
        self.close()

    def restoreLayout(self):
        if os.path.isfile(get_config_path("UILayout.dat")):
            try:
                f = open(get_config_path("UILayout.dat"), 'rb')
                geometry, state = pickle.load(f)
                self.restoreGeometry(geometry)
                self.restoreState(state)
            except Exception as e:
                print("Exception on restoreLayout, {}".format(e))
        else:
            try:
                f = QFile(':/default_layout_qt5.dat')
                f.open(QIODevice.ReadOnly)
                geometry, state = pickle.loads(f.readAll())
                self.restoreGeometry(geometry)
                self.restoreState(state)
            except Exception as e:
                print("Exception on restoreLayout, {}".format(e))

    def saveLayout(self):
        with open(get_config_path("UILayout.dat"), 'wb') as f:
            pickle.dump((self.saveGeometry(), self.saveState()), f)

    def syncMenu(self):
        self.actionPort_Config_Panel.setChecked(
            not self.dockWidget_PortConfig.isHidden())
        self.actionQuick_Send_Panel.setChecked(
            not self.dockWidget_QuickSend.isHidden())
        self.actionSend_Hex_Panel.setChecked(
            not self.dockWidget_SendHex.isHidden())

    def onViewChanged(self):
        checked = self._viewGroup.checkedAction()
        if checked is None:
            self._viewMode = VIEWMODE_HEX_UPPERCASE
            self.actionHEX_UPPERCASE.setChecked(True)
        else:
            if 'Ascii' in checked.text():
                self._viewMode = VIEWMODE_ASCII
            elif 'lowercase' in checked.text():
                self._viewMode = VIEWMODE_HEX_LOWERCASE
            elif 'UPPERCASE' in checked.text():
                self._viewMode = VIEWMODE_HEX_UPPERCASE

        self.receiver_thread.setViewMode(self._viewMode)
Exemplo n.º 38
0
class MainWindow(QMainWindow):  #Main window of the Serie Browser
    def __init__(self, series_list, nameWindow):
        # series_list = list of series to be displayed and retrieved from the API,
        # nameWindow = name to be displayed on top of the indow

        super().__init__()
        self.__nDisplay = len(series_list)  # Number of series displayed
        self.__seriesList = series_list  # List of series displayed at first

        # Load .ui designed on Qt
        self.__UI = uic.loadUi('main.ui', self)

        # Show window on desktop
        self.showMaximized()

        # Name of the window
        self.__textLabel = QLabel(nameWindow)
        self.__textLabel.setText(nameWindow)
        self.__textLabel.setTextFormat(QtCore.Qt.RichText)
        self.__textLabel.setText(
            "<span style=' font-size:16pt; font-weight:600; color:#aa0000;'>" +
            nameWindow + "</span>")
        self.__UI.horizontalLayout.addWidget(self.__textLabel)

        # Define a Scroll area for serie display
        self.__serieWind = QWidget()  # Create a widget for the scroll area
        self.__scrollArea = QScrollArea()  # I Create a Scroll Area
        self.__scrollArea.setWidgetResizable(True)
        self.__scrollArea.setWidget(
            self.__serieWind)  # Insert the scroll area in the widget
        self.__gridLayout = QGridLayout(
        )  # Create a grid layout for the scroll area
        self.__serieWind.setLayout(
            self.__gridLayout)  # Insert the grid layout in the scroll area
        self.__UI.horizontalLayout_2.addWidget(
            self.__scrollArea
        )  # Insert the scroll area in the main horizontal layout from .ui

        self.__serieWind.setObjectName("serieWind")
        self.setStyleSheet("#serieWind{background-color: black;}"
                           )  # Define color of the scroll area

        #  Add research bar
        self.__searchWidget = QLineEdit()
        self.__searchWidget.setFixedSize(150, 40)
        self.__searchWidget.returnPressed.connect(
            self.slot_research
        )  # Connect the signal return pressed to slot_research
        self.__UI.horizontalLayout.addWidget(
            self.__searchWidget)  # Insert research bar in layout from .ui

        #  Add research button
        self.__researchButton = QPushButton("Search")
        self.__researchButton.setFixedSize(100, 40)
        self.__researchButton.pressed.connect(
            self.slot_research)  # Connect the signal pressed to slot_research
        self.__UI.horizontalLayout.addWidget(
            self.__researchButton
        )  # Insert research button in layout from .ui  # Add favourites title

        # Favourites Layout
        self.__favLayout = QVBoxLayout()
        self.__UI.horizontalLayout_2.addLayout(self.__favLayout)

        # Favourites Title
        self.__favouritesTitle = QLabel("Favourites")
        self.__favouritesTitle.setText(
            "<span style=' font-size:16pt; font-weight:600; color:#aa0000;'> Favourites </span>"
        )
        self.__favLayout.addWidget(self.__favouritesTitle)

        #  Add favourites list to the layout with a QListWidget
        self.__favouritesWidget = QListWidget()
        self.__favouritesWidget.setMaximumWidth(250)
        self.__favLayout.addWidget(self.__favouritesWidget)
        self.__favButtonsTopLayout = QHBoxLayout()
        self.__favButtonsBottomLayout = QHBoxLayout()
        self.__favLayout.addLayout(self.__favButtonsTopLayout)
        self.__favLayout.addLayout(self.__favButtonsBottomLayout)

        #  Add More Info button for favourites list
        self.__favMoreInfoButton = QPushButton("More Info")
        self.__favButtonsTopLayout.addWidget(self.__favMoreInfoButton)
        self.__favMoreInfoButton.clicked.connect(
            self.slot_open_serie_window
        )  # Connect clicked signal of MoreInfo button to slot_open_serie_window

        #  Add Remove favourite button for favourites list
        self.__removeFavButton = QPushButton("Remove Favourite")
        self.__favButtonsTopLayout.addWidget(self.__removeFavButton)
        self.__removeFavButton.clicked.connect(
            self.slot_remove_favourite
        )  # Connect clicked signal to Remove button to slot_remove_favourites

        # Magic Button : finds a serie that has an episode that is going to be aired soon
        self.__magicButton = QPushButton("MAGIC BUTTON")
        self.__magicButton.clicked.connect(self.slot_magic_add_to_favourites)
        self.__favButtonsBottomLayout.addWidget(self.__magicButton)

        # Clear Favourites Button
        self.__clearFavButton = QPushButton("Clear Favourites")
        self.__favButtonsBottomLayout.addWidget(self.__clearFavButton)
        self.__clearFavButton.clicked.connect(self.slot_clear_favourites)

        # Notifications checkbox
        self.__enableNotifications = QCheckBox()
        self.__enableNotifications.setText("Enable Notifications")
        self.__enableNotifications.setChecked(True)
        self.__enableNotifications.stateChanged.connect(
            self.slot_change_notifications_state)
        self.__favLayout.addWidget(self.__enableNotifications)

        #Create and load favourites list
        self.__favouritesIDList = []  # Creation of a ID list of favourites
        self.__favouriteSeries = [
        ]  # Creation of list of favourites of class Serie
        self.__fileName = "favourites"  #Creation of a file for pickler
        if (os.path.exists(self.__fileName)) and (os.path.getsize(
                self.__fileName) > 0):  #Check if the file exists
            with open(self.__fileName, "rb") as favFile:
                depickler = pickle.Unpickler(favFile)
                self.__favouriteSeries = depickler.load(
                )  #Loading previously saved favourites from file
                for i in range(
                        len(self.__favouriteSeries)
                ):  #Loop to add favourites series to favourite QWidgetList and create favouritesIDList
                    favItem = self.__favouriteSeries[i].name
                    self.__favouritesWidget.addItem(favItem)
                    self.__favouritesIDList += [self.__favouriteSeries[i].id]

        #Alert
        self.__alertWindow = Afficher(self.__favouriteSeries,
                                      self.__enableNotifications.isChecked(),
                                      self)
        self.__alertWindow.start()

        # Signal Mapper to connect slot_add_to_favourites to class MainWidget
        self.__sigMapper = QSignalMapper(self)
        self.__sigMapper.mapped.connect(self.slot_add_to_favourites)

        # Display series MainWidgets on MainWindow
        self.__numberSeriesWidgetLines = ceil(self.__nDisplay /
                                              5)  # 5 widgets per line maximum
        self.__positions = [(i + 1, j)
                            for i in range(self.__numberSeriesWidgetLines)
                            for j in range(5)
                            ]  # Define positions for the grid layout
        self.__seriesWidgetList = []
        for i in range(len(self.__seriesList)
                       ):  #Loop for creation and display of serie MainWidgets
            currentWidget = MainWidget(i, self.__seriesList[i])
            self.__seriesWidgetList += [currentWidget]
            self.__gridLayout.addWidget(currentWidget, *self.__positions[i])
            i += 1
            self.__sigMapper.setMapping(currentWidget.favButton,
                                        currentWidget.id)
            currentWidget.favButton.clicked.connect(
                self.__sigMapper.map
            )  #Connect add to favourite button of MainWidget to signal mapper

    # Getters and setters for seriesList
    @property
    def seriesList(self):
        return self.__seriesList

    @seriesList.setter
    def seriesList(self, newSeriesList):
        self.__seriesList = newSeriesList

    # Methods
    # Slot to add a favourite to the QListWidget
    def slot_add_to_favourites(self,
                               id):  #id = id of the serie to add to favourites
        if (id not in self.__favouritesIDList
            ):  #Check if the serie is already in the favourite
            self.__favouritesIDList += [id]
            serie = searchSerie(id)
            nm = serie.name
            self.__favouriteSeries += [serie]
            self.__favouritesWidget.addItem(nm)
            self.__alertWindow.quit()
            self.__alertWindow = Afficher(
                self.__favouriteSeries, self.__enableNotifications.isChecked(),
                self)
            self.__alertWindow.start()
            with open(self.__fileName, "wb") as favFile:
                pickler = pickle.Pickler(favFile)
                pickler.dump(
                    self.__favouriteSeries)  # Saving refreshed favourites list
        else:  # If the serie is already in the favourites, displaying an error message
            error_dialog = QMessageBox.information(None, "Error",
                                                   "Favourite already added.",
                                                   QMessageBox.Cancel)

    # The slot that adds a serie that has a soon-to-be-aired episode to favourites
    def slot_magic_add_to_favourites(self):
        serie = findForthcomingSerie(self.__favouritesIDList)
        id = serie.id
        self.__favouritesIDList += [id]
        nm = serie.name
        self.__favouriteSeries += [serie]
        self.__favouritesWidget.addItem(nm)
        self.__alertWindow.quit()
        self.__alertWindow = Afficher(self.__favouriteSeries,
                                      self.__enableNotifications.isChecked(),
                                      self)
        self.__alertWindow.start()
        with open(self.__fileName, "wb") as favFile:
            pickler = pickle.Pickler(favFile)
            pickler.dump(
                self.__favouriteSeries)  # Saving refreshed favourites list

    # Slot to remove a serie from user's favourites list
    def slot_remove_favourite(self):
        idx = self.__favouritesWidget.currentRow(
        )  # The index of the selected row
        if (
                idx == -1
        ):  # Exception if the user didn't select a favourite before clicking "remove favourite" button
            QMessageBox.information(None, "Error",
                                    "You didn't select a favourite.",
                                    QMessageBox.Ok)
        else:
            del self.__favouriteSeries[
                idx]  # The favourites widget list and the inner user's favourites list are sorted in the same order
            del self.__favouritesIDList[idx]
            self.__favouritesWidget.takeItem(idx)
            self.__alertWindow.quit()
            self.__alertWindow = Afficher(
                self.__favouriteSeries, self.__enableNotifications.isChecked(),
                self)
            self.__alertWindow.start()
            with open(self.__fileName, "wb") as favFile:
                pickler = pickle.Pickler(favFile)
                pickler.dump(
                    self.__favouriteSeries)  # Saving refreshed favourites list

    # Slot that clears user's favourites list
    def slot_clear_favourites(self):
        self.__favouriteSeries = []
        self.__favouritesIDList = []
        self.__favouritesWidget.clear()
        self.__alertWindow.quit()
        self.__alertWindow = Afficher(self.__favouriteSeries,
                                      self.__enableNotifications.isChecked(),
                                      self)
        self.__alertWindow.start()
        # Clear favourites file
        with open(self.__fileName, "wb") as favFile:
            pickler = pickle.Pickler(favFile)
            pickler.dump(
                self.__favouriteSeries)  # Saving refreshed favourites list

    # Slot to open window with more information for favourites
    def slot_open_serie_window(self):
        idx = self.__favouritesWidget.currentRow()
        try:
            if idx == -1:
                raise ValueError("No row is selected in favourites widget.")
            else:
                ser = self.__favouriteSeries[idx]
                self.__newWindow = NewWindow(ser, self)
                self.__newWindow.exec_()
        except ValueError:  # If the user hasn't selected an element from the list, currentRow returns -1 and an error dialog is displayed
            QMessageBox.information(None, "Error",
                                    "You didn't select a serie in your list.",
                                    QMessageBox.Cancel)

    # Slot to do the research
    def slot_research(self):
        self.__searchText = self.__searchWidget.text()

        # Delete all the widgets displayed in scroll area
        for i in reversed(range(self.__gridLayout.count())):
            self.__gridLayout.itemAt(i).widget().setParent(None)

        # Research on the API
        self.__seriesList = searchSeries(self.__searchText)

        # Add results of the research to the scroll area
        for i in range(len(self.__seriesList)):
            currentWidget = MainWidget(i, self.__seriesList[i])
            self.__gridLayout.addWidget(currentWidget, *self.__positions[i])
            self.__sigMapper.setMapping(currentWidget.favButton,
                                        currentWidget.id)
            currentWidget.favButton.clicked.connect(self.__sigMapper.map)

    # A slot that is activated when the notifications checkbox is checked or unchecked. It starts and stops the notifications thread
    def slot_change_notifications_state(self):
        if (self.__alertWindow.notificationsEnabled):
            self.__alertWindow.notificationsEnabled = False
            self.__alertWindow.quit()
        else:
            self.__alertWindow.notificationsEnabled = True
            self.__alertWindow = Afficher(
                self.__favouriteSeries, self.__enableNotifications.isChecked(),
                self)
            self.__alertWindow.start()
Exemplo n.º 39
0
class processManagerDialog(QDialog):
    def __init__(self, dataTab=None, parent=None):
        super(processManagerDialog, self).__init__(parent=parent)
        self.installDir = GENERAL.get_value('INSTALL_DIR')
        self.mainLayout = QVBoxLayout(self)
        self.upperLayout = QVBoxLayout(self)
        self.lowerLayout = QVBoxLayout(self)
        self.menuBar = QMenuBar(self)
        self.toolBar = QToolBar(self)
        self.processTable = QTableView(self)
        self.tableModel = functionModel(self)
        self.processTablePopMenu = QMenu(self)
        self.importMenu = QMenu(self)
        self.signalMapper = QSignalMapper(self)
        # self.MLProject = MLProject
        self.openedData = GENERAL.get_value('OPENED_DATA')
        self.MainTabWindow = GENERAL.get_value('TABWINDOW')
        # tool bar
        self.addProcessAction = None
        self.importProcessAction = None
        # data
        self.processList = []
        self.processDict = set()
        self.dataTab = dataTab
        self.targetDataset = dataTab.filename if dataTab else None
        # self.curDir = GENERAL.get_value('INSTALL_DIR')
        self.initUI()
        self.initRightClickMenu()

    def initUI(self):
        if self.targetDataset:
            print(self.targetDataset)
        self.setLayout(self.mainLayout)
        self.setMinimumSize(800, 600)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.mainLayout.setContentsMargins(0, 0, 0, 0)
        self.upperLayout.addWidget(self.menuBar)
        self.upperLayout.addWidget(self.toolBar)
        self.lowerLayout.addWidget(self.processTable)
        self.lowerLayout.setContentsMargins(10, 0, 10, 10)
        self.mainLayout.addLayout(self.upperLayout)
        self.mainLayout.addLayout(self.lowerLayout)

        # menu
        fileMenu = QMenu('File', self)
        f1 = QAction('add process', self)
        f1.triggered.connect(self.addProcess)
        fileMenu.addActions([f1])
        self.menuBar.addMenu(fileMenu)

        # toolbar
        self.addProcessAction = QAction(QIcon('./res/add_red_small.ico'), 'add process', self)
        self.addProcessAction.setStatusTip('add process')
        self.addProcessAction.triggered.connect(self.addProcess)

        self.importProcessAction = QAction(QIcon('./res/import.ico'), 'import process', self)
        self.importProcessAction.setStatusTip('import process')
        self.importProcessAction.triggered.connect(self.importProcess)
        self.importProcessAction.setEnabled(False)
        self.toolBar.addActions([self.addProcessAction, self.importProcessAction])

        # table
        self.processTable.setModel(self.tableModel)
        self.processTable.autoScrollMargin()
        self.processTable.setSelectionBehavior(QTableView.SelectRows)
        self.processTable.selectionModel().selectionChanged.connect(self.itemSelected)
        # table double click
        self.processTable.doubleClicked.connect(self.editDescribe)
        self.tableModel.notEmpty.connect(lambda: self.setTableHeaderStyle())
        self.loadProcessList()

        # init mapper
        self.signalMapper.mapped[int].connect(self.addProcess)

    def loadProcessList(self):
        localProcessFile = os.path.join(self.installDir, 'local', 'localProcess.ml')
        if os.path.exists(localProcessFile):
            with open(localProcessFile, 'rb') as f:
                self.processDict, self.processList = pickle.load(f)
                self.tableModel.loadProcessList(self.processList)
        else:
            print('local process file not exist')

    def saveProcessList(self):
        localProcessFile = os.path.join(self.installDir, 'local', 'localProcess.ml')
        with open(localProcessFile, 'wb') as f:
            pickle.dump((self.processDict, self.processList), f)

    def addProcess(self):
        dialog = addProcessDialog(self)
        r = dialog.exec_()
        index = len(self.processList)
        if r == QDialog.Accepted:
            for i, f in enumerate(dialog.functionList):
                f['describe'] = ''
                f['ID'] = hashlib.new('md5', ((f['name'] + f['path']).encode('UTF-8'))).hexdigest()
                if f['ID'] not in self.processDict:
                    self.processList.insert(index + i, f)
                    self.processDict.add(f['ID'])
            self.tableModel.addProcessList(self.processList)
        self.saveProcessList()

    def importProcess(self):
        # import process to process list
        if self.dataTab:
            pass
        else:
            self.importMenu.clear()
            tabList = []
            for index, tab in enumerate(self.openedData):
                t = QAction(os.path.basename(tab.filename), self)
                t.triggered.connect(self.signalMapper.map)
                self.signalMapper.setMapping(t, index)
                tabList.append(t)

            self.importMenu.addActions(tabList)
            # pop menu
            self.importMenu.exec(QCursor.pos())

    def addProcess(self, index):
        tab = self.openedData[index]
        print(tab.filename, index)

    def lightUpDataTab(self, index):
        """
        future feature: light up tab in the main tab window when mouse hover on the button
        """
        pass

    def itemSelected(self, selected: QItemSelection, deselected: QItemSelection):
        print(selected, deselected)
        if selected.count() == 0 or not self.openedData or len(self.openedData) == 0:
            self.importProcessAction.setEnabled(False)
        else:
            self.importProcessAction.setEnabled(True)

    def delProcess(self, point: QPoint):
        if point:
            modelIndexList = self.processTable.selectionModel().selectedRows()
            modelIndex = [modelIndexList[i].row() for i in range(len(modelIndexList))]
        self.tableModel.beginResetModel()
        for row in modelIndex:
            p = self.processList.pop(row)
            self.processDict.remove(p['ID'])
        self.saveProcessList()
        self.tableModel.rows -= len(modelIndex)
        self.tableModel.endResetModel()

    def setTableHeaderStyle(self):
        self.processTable.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
        self.processTable.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeToContents)

    def initRightClickMenu(self):
        self.processTable.setContextMenuPolicy(Qt.CustomContextMenu)
        self.processTable.customContextMenuRequested.connect(self.onProcessTableRightClicked)

    def onProcessTableRightClicked(self, point: QPoint):
        self.processTablePopMenu.clear()
        # numeric
        m1 = QAction('delete process', self)
        m1.triggered.connect(lambda: self.delProcess(point))
        m2 = QAction('edit describe', self)
        m2.triggered.connect(lambda: self.editDescribe(point))
        m3 = QAction('edit source file', self)
        m3.triggered.connect(lambda: self.editSource(point))

        processTableActionList = [m1, m2, m3]
        self.processTablePopMenu.addActions(processTableActionList)
        # test action
        t1 = QAction('test', self)
        # t1.triggered.connect()
        self.processTablePopMenu.addAction(t1)
        # pop menu
        self.processTablePopMenu.exec(QCursor.pos())

    def editDescribe(self, point: QPoint):
        if isinstance(point, QPoint):
            row = self.processTable.indexAt(point).row()
        elif isinstance(point, QModelIndex):
            row = point.row()
        dialog = describeEditDialog(self.processList[row]['describe'], self)
        r = dialog.exec_()
        if r == QDialog.Accepted:
            self.processList[row]['describe'] = dialog.describe
            self.tableModel.update()

    def editSource(self, point: QPoint):
        index = self.processTable.indexAt(point)
        print(index.row(), index.column())
        pass

    def closeEvent(self, event: QCloseEvent):
        self.saveProcessList()
        event.accept()
Exemplo n.º 40
0
def main(icon_spec):
    app = QApplication(sys.argv)

    main_window = QMainWindow()

    def sigint_handler(*args):
        main_window.close()

    signal.signal(signal.SIGINT, sigint_handler)
    # the timer enables triggering the sigint_handler
    signal_timer = QTimer()
    signal_timer.start(100)
    signal_timer.timeout.connect(lambda: None)

    tool_bar = QToolBar()
    main_window.addToolBar(Qt.TopToolBarArea, tool_bar)

    table_view = QTableView()
    table_view.setSelectionBehavior(QAbstractItemView.SelectRows)
    table_view.setSelectionMode(QAbstractItemView.SingleSelection)
    table_view.setSortingEnabled(True)
    main_window.setCentralWidget(table_view)

    proxy_model = QSortFilterProxyModel()
    proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive)
    proxy_model.setFilterKeyColumn(1)
    table_view.setModel(proxy_model)
    proxy_model.layoutChanged.connect(table_view.resizeRowsToContents)

    item_model = QStandardItemModel()
    proxy_model.setSourceModel(item_model)

    # get all icons and their available sizes
    icons = []
    all_sizes = set([])
    QIcon.setThemeName('Tango')
    for context, icon_names in icon_spec:
        for icon_name in icon_names:
            icon = QIcon.fromTheme(icon_name)
            sizes = []
            for size in icon.availableSizes():
                size = (size.width(), size.height())
                sizes.append(size)
                all_sizes.add(size)
            sizes.sort()
            icons.append({
                'context': context,
                'icon_name': icon_name,
                'icon': icon,
                'sizes': sizes,
            })
    all_sizes = list(all_sizes)
    all_sizes.sort()

    # input field for filter
    def filter_changed(value):
        proxy_model.setFilterRegExp(value)
        table_view.resizeRowsToContents()

    filter_line_edit = QLineEdit()
    filter_line_edit.setMaximumWidth(200)
    filter_line_edit.setPlaceholderText('Filter name')
    filter_line_edit.setToolTip(
        'Filter name optionally using regular expressions (' +
        QKeySequence(QKeySequence.Find).toString() + ')')
    filter_line_edit.textChanged.connect(filter_changed)
    tool_bar.addWidget(filter_line_edit)

    # actions to toggle visibility of available sizes/columns
    def action_toggled(index):
        column = 2 + index
        table_view.setColumnHidden(column,
                                   not table_view.isColumnHidden(column))
        table_view.resizeColumnsToContents()
        table_view.resizeRowsToContents()

    signal_mapper = QSignalMapper()
    for i, size in enumerate(all_sizes):
        action = QAction('%dx%d' % size, tool_bar)
        action.setCheckable(True)
        action.setChecked(True)
        tool_bar.addAction(action)
        action.toggled.connect(signal_mapper.map)
        signal_mapper.setMapping(action, i)
        # set tool tip and handle key sequence
        tool_tip = 'Toggle visibility of column'
        if i < 10:
            digit = ('%d' % (i + 1))[-1]
            tool_tip += ' (%s)' % QKeySequence('Ctrl+%s' % digit).toString()
        action.setToolTip(tool_tip)
    signal_mapper.mapped.connect(action_toggled)

    # label columns
    header_labels = ['context', 'name']
    for width, height in all_sizes:
        header_labels.append('%dx%d' % (width, height))
    item_model.setColumnCount(len(header_labels))
    item_model.setHorizontalHeaderLabels(header_labels)

    # fill rows
    item_model.setRowCount(len(icons))
    for row, icon_data in enumerate(icons):
        # context
        item = QStandardItem(icon_data['context'])
        item.setFlags(item.flags() ^ Qt.ItemIsEditable)
        item_model.setItem(row, 0, item)
        # icon name
        item = QStandardItem(icon_data['icon_name'])
        item.setFlags(item.flags() ^ Qt.ItemIsEditable)
        item_model.setItem(row, 1, item)
        for index_in_all_sizes, size in enumerate(all_sizes):
            column = 2 + index_in_all_sizes
            if size in icon_data['sizes']:
                # icon as pixmap to keep specific size
                item = QStandardItem('')
                pixmap = icon_data['icon'].pixmap(size[0], size[1])
                item.setData(pixmap, Qt.DecorationRole)
                item.setFlags(item.flags() ^ Qt.ItemIsEditable)
                item_model.setItem(row, column, item)
            else:
                # single space to be sortable against icons
                item = QStandardItem(' ')
                item.setFlags(item.flags() ^ Qt.ItemIsEditable)
                item_model.setItem(row, column, item)

    table_view.resizeColumnsToContents()
    # manually set row heights because resizeRowsToContents is not working properly
    for row, icon_data in enumerate(icons):
        if len(icon_data['sizes']) > 0:
            max_size = icon_data['sizes'][-1]
            table_view.setRowHeight(row, max_size[1])

    # enable focus find (ctrl+f) and toggle columns (ctrl+NUM)
    def main_window_keyPressEvent(self,
                                  event,
                                  old_keyPressEvent=QMainWindow.keyPressEvent):
        if event.matches(QKeySequence.Find):
            filter_line_edit.setFocus()
            return
        if event.modifiers() == Qt.ControlModifier and event.key(
        ) >= Qt.Key_0 and event.key() <= Qt.Key_9:
            index = event.key() - Qt.Key_1
            if event.key() == Qt.Key_0:
                index += 10
            action = signal_mapper.mapping(index)
            if action:
                action.toggle()
                return
        old_keyPressEvent(self, event)

    main_window.keyPressEvent = MethodType(main_window_keyPressEvent,
                                           table_view)

    # enable copy (ctrl+c) name of icon to clipboard
    def table_view_keyPressEvent(self,
                                 event,
                                 old_keyPressEvent=QTableView.keyPressEvent):
        if event.matches(QKeySequence.Copy):
            selection_model = self.selectionModel()
            if selection_model.hasSelection():
                index = selection_model.selectedRows()[0]
                source_index = self.model().mapToSource(index)
                item = self.model().sourceModel().item(source_index.row(), 1)
                icon_name = item.data(Qt.EditRole)
                app.clipboard().setText(icon_name.toString())
                return
        old_keyPressEvent(self, event)

    table_view.keyPressEvent = MethodType(table_view_keyPressEvent, table_view)

    main_window.showMaximized()
    return app.exec_()
Exemplo n.º 41
0
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.mdiArea = QMdiArea()
        self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.setCentralWidget(self.mdiArea)

        self.mdiArea.subWindowActivated.connect(self.updateMenus)
        self.windowMapper = QSignalMapper(self)
        self.windowMapper.mapped[QWidget].connect(self.setActiveSubWindow)

        self.createActions()
        self.createMenus()
        self.createToolBars()
        self.createStatusBar()
        self.updateMenus()

        self.readSettings()

        self.setWindowTitle("MDI")

    def closeEvent(self, event):
        self.mdiArea.closeAllSubWindows()
        if self.mdiArea.currentSubWindow():
            event.ignore()
        else:
            self.writeSettings()
            event.accept()

    def newFile(self):
        child = self.createMdiChild()
        child.newFile()
        child.show()

    def open(self):
        fileName, _ = QFileDialog.getOpenFileName(self)
        if fileName:
            existing = self.findMdiChild(fileName)
            if existing:
                self.mdiArea.setActiveSubWindow(existing)
                return

            child = self.createMdiChild()
            if child.loadFile(fileName):
                self.statusBar().showMessage("File loaded", 2000)
                child.show()
            else:
                child.close()

    def save(self):
        if self.activeMdiChild() and self.activeMdiChild().save():
            self.statusBar().showMessage("File saved", 2000)

    def saveAs(self):
        if self.activeMdiChild() and self.activeMdiChild().saveAs():
            self.statusBar().showMessage("File saved", 2000)

    def cut(self):
        if self.activeMdiChild():
            self.activeMdiChild().cut()

    def copy(self):
        if self.activeMdiChild():
            self.activeMdiChild().copy()

    def paste(self):
        if self.activeMdiChild():
            self.activeMdiChild().paste()

    def about(self):
        QMessageBox.about(self, "About MDI",
                "The <b>MDI</b> example demonstrates how to write multiple "
                "document interface applications using Qt.")

    def updateMenus(self):
        hasMdiChild = (self.activeMdiChild() is not None)
        self.saveAct.setEnabled(hasMdiChild)
        self.saveAsAct.setEnabled(hasMdiChild)
        self.pasteAct.setEnabled(hasMdiChild)
        self.closeAct.setEnabled(hasMdiChild)
        self.closeAllAct.setEnabled(hasMdiChild)
        self.tileAct.setEnabled(hasMdiChild)
        self.cascadeAct.setEnabled(hasMdiChild)
        self.nextAct.setEnabled(hasMdiChild)
        self.previousAct.setEnabled(hasMdiChild)
        self.separatorAct.setVisible(hasMdiChild)

        hasSelection = (self.activeMdiChild() is not None and
                        self.activeMdiChild().textCursor().hasSelection())
        self.cutAct.setEnabled(hasSelection)
        self.copyAct.setEnabled(hasSelection)

    def updateWindowMenu(self):
        self.windowMenu.clear()
        self.windowMenu.addAction(self.closeAct)
        self.windowMenu.addAction(self.closeAllAct)
        self.windowMenu.addSeparator()
        self.windowMenu.addAction(self.tileAct)
        self.windowMenu.addAction(self.cascadeAct)
        self.windowMenu.addSeparator()
        self.windowMenu.addAction(self.nextAct)
        self.windowMenu.addAction(self.previousAct)
        self.windowMenu.addAction(self.separatorAct)

        windows = self.mdiArea.subWindowList()
        self.separatorAct.setVisible(len(windows) != 0)

        for i, window in enumerate(windows):
            child = window.widget()

            text = "%d %s" % (i + 1, child.userFriendlyCurrentFile())
            if i < 9:
                text = '&' + text

            action = self.windowMenu.addAction(text)
            action.setCheckable(True)
            action.setChecked(child is self.activeMdiChild())
            action.triggered.connect(self.windowMapper.map)
            self.windowMapper.setMapping(action, window)

    def createMdiChild(self):
        child = MdiChild()
        self.mdiArea.addSubWindow(child)

        child.copyAvailable.connect(self.cutAct.setEnabled)
        child.copyAvailable.connect(self.copyAct.setEnabled)

        return child

    def createActions(self):
        self.newAct = QAction(QIcon(':/images/new.png'), "&New", self,
                shortcut=QKeySequence.New, statusTip="Create a new file",
                triggered=self.newFile)

        self.openAct = QAction(QIcon(':/images/open.png'), "&Open...", self,
                shortcut=QKeySequence.Open, statusTip="Open an existing file",
                triggered=self.open)

        self.saveAct = QAction(QIcon(':/images/save.png'), "&Save", self,
                shortcut=QKeySequence.Save,
                statusTip="Save the document to disk", triggered=self.save)

        self.saveAsAct = QAction("Save &As...", self,
                shortcut=QKeySequence.SaveAs,
                statusTip="Save the document under a new name",
                triggered=self.saveAs)

        self.exitAct = QAction("E&xit", self, shortcut=QKeySequence.Quit,
                statusTip="Exit the application",
                triggered=QApplication.instance().closeAllWindows)

        self.cutAct = QAction(QIcon(':/images/cut.png'), "Cu&t", self,
                shortcut=QKeySequence.Cut,
                statusTip="Cut the current selection's contents to the clipboard",
                triggered=self.cut)

        self.copyAct = QAction(QIcon(':/images/copy.png'), "&Copy", self,
                shortcut=QKeySequence.Copy,
                statusTip="Copy the current selection's contents to the clipboard",
                triggered=self.copy)

        self.pasteAct = QAction(QIcon(':/images/paste.png'), "&Paste", self,
                shortcut=QKeySequence.Paste,
                statusTip="Paste the clipboard's contents into the current selection",
                triggered=self.paste)

        self.closeAct = QAction("Cl&ose", self,
                statusTip="Close the active window",
                triggered=self.mdiArea.closeActiveSubWindow)

        self.closeAllAct = QAction("Close &All", self,
                statusTip="Close all the windows",
                triggered=self.mdiArea.closeAllSubWindows)

        self.tileAct = QAction("&Tile", self, statusTip="Tile the windows",
                triggered=self.mdiArea.tileSubWindows)

        self.cascadeAct = QAction("&Cascade", self,
                statusTip="Cascade the windows",
                triggered=self.mdiArea.cascadeSubWindows)

        self.nextAct = QAction("Ne&xt", self, shortcut=QKeySequence.NextChild,
                statusTip="Move the focus to the next window",
                triggered=self.mdiArea.activateNextSubWindow)

        self.previousAct = QAction("Pre&vious", self,
                shortcut=QKeySequence.PreviousChild,
                statusTip="Move the focus to the previous window",
                triggered=self.mdiArea.activatePreviousSubWindow)

        self.separatorAct = QAction(self)
        self.separatorAct.setSeparator(True)

        self.aboutAct = QAction("&About", self,
                statusTip="Show the application's About box",
                triggered=self.about)

        self.aboutQtAct = QAction("About &Qt", self,
                statusTip="Show the Qt library's About box",
                triggered=QApplication.instance().aboutQt)

    def createMenus(self):
        self.fileMenu = self.menuBar().addMenu("&File")
        self.fileMenu.addAction(self.newAct)
        self.fileMenu.addAction(self.openAct)
        self.fileMenu.addAction(self.saveAct)
        self.fileMenu.addAction(self.saveAsAct)
        self.fileMenu.addSeparator()
        action = self.fileMenu.addAction("Switch layout direction")
        action.triggered.connect(self.switchLayoutDirection)
        self.fileMenu.addAction(self.exitAct)

        self.editMenu = self.menuBar().addMenu("&Edit")
        self.editMenu.addAction(self.cutAct)
        self.editMenu.addAction(self.copyAct)
        self.editMenu.addAction(self.pasteAct)

        self.windowMenu = self.menuBar().addMenu("&Window")
        self.updateWindowMenu()
        self.windowMenu.aboutToShow.connect(self.updateWindowMenu)

        self.menuBar().addSeparator()

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

    def createToolBars(self):
        self.fileToolBar = self.addToolBar("File")
        self.fileToolBar.addAction(self.newAct)
        self.fileToolBar.addAction(self.openAct)
        self.fileToolBar.addAction(self.saveAct)

        self.editToolBar = self.addToolBar("Edit")
        self.editToolBar.addAction(self.cutAct)
        self.editToolBar.addAction(self.copyAct)
        self.editToolBar.addAction(self.pasteAct)

    def createStatusBar(self):
        self.statusBar().showMessage("Ready")

    def readSettings(self):
        settings = QSettings('Trolltech', 'MDI Example')
        pos = settings.value('pos', QPoint(200, 200))
        size = settings.value('size', QSize(400, 400))
        self.move(pos)
        self.resize(size)

    def writeSettings(self):
        settings = QSettings('Trolltech', 'MDI Example')
        settings.setValue('pos', self.pos())
        settings.setValue('size', self.size())

    def activeMdiChild(self):
        activeSubWindow = self.mdiArea.activeSubWindow()
        if activeSubWindow:
            return activeSubWindow.widget()
        return None

    def findMdiChild(self, fileName):
        canonicalFilePath = QFileInfo(fileName).canonicalFilePath()

        for window in self.mdiArea.subWindowList():
            if window.widget().currentFile() == canonicalFilePath:
                return window
        return None

    def switchLayoutDirection(self):
        if self.layoutDirection() == Qt.LeftToRight:
            QApplication.setLayoutDirection(Qt.RightToLeft)
        else:
            QApplication.setLayoutDirection(Qt.LeftToRight)

    def setActiveSubWindow(self, window):
        if window:
            self.mdiArea.setActiveSubWindow(window)
Exemplo n.º 42
0
class ToolBox(QFrame):
    """
    A tool box widget.
    """
    # Emitted when a tab is toggled.
    tabToogled = Signal(int, bool)

    def setExclusive(self, exclusive):
        """
        Set exclusive tabs (only one tab can be open at a time).
        """
        if self.__exclusive != exclusive:
            self.__exclusive = exclusive
            self.__tabActionGroup.setExclusive(exclusive)
            checked = self.__tabActionGroup.checkedAction()
            if checked is None:
                # The action group can be out of sync with the actions state
                # when switching between exclusive states.
                actions_checked = [
                    page.action for page in self.__pages
                    if page.action.isChecked()
                ]
                if actions_checked:
                    checked = actions_checked[0]

            # Trigger/toggle remaining open pages
            if exclusive and checked is not None:
                for page in self.__pages:
                    if checked != page.action and page.action.isChecked():
                        page.action.trigger()

    def exclusive(self):
        """
        Are the tabs in the toolbox exclusive.
        """
        return self.__exclusive

    exclusive_ = Property(bool,
                          fget=exclusive,
                          fset=setExclusive,
                          designable=True,
                          doc="Exclusive tabs")

    def __init__(self, parent=None, **kwargs):
        QFrame.__init__(self, parent, **kwargs)

        self.__pages = []
        self.__tabButtonHeight = -1
        self.__tabIconSize = QSize()
        self.__exclusive = False
        self.__setupUi()

    def __setupUi(self):
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)

        # Scroll area for the contents.
        self.__scrollArea = \
                _ToolBoxScrollArea(self, objectName="toolbox-scroll-area")

        self.__scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.__scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.__scrollArea.setSizePolicy(QSizePolicy.MinimumExpanding,
                                        QSizePolicy.MinimumExpanding)
        self.__scrollArea.setFrameStyle(QScrollArea.NoFrame)
        self.__scrollArea.setWidgetResizable(True)

        # A widget with all of the contents.
        # The tabs/contents are placed in the layout inside this widget
        self.__contents = QWidget(self.__scrollArea,
                                  objectName="toolbox-contents")
        self.__contentsLayout = _ToolBoxLayout(
            sizeConstraint=_ToolBoxLayout.SetMinAndMaxSize, spacing=0)
        self.__contentsLayout.setContentsMargins(0, 0, 0, 0)
        self.__contents.setLayout(self.__contentsLayout)

        self.__scrollArea.setWidget(self.__contents)

        layout.addWidget(self.__scrollArea)

        self.setLayout(layout)
        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)

        self.__tabActionGroup = \
                QActionGroup(self, objectName="toolbox-tab-action-group")

        self.__tabActionGroup.setExclusive(self.__exclusive)

        self.__actionMapper = QSignalMapper(self)
        self.__actionMapper.mapped[QObject].connect(self.__onTabActionToogled)

    def setTabButtonHeight(self, height):
        """
        Set the tab button height.
        """
        if self.__tabButtonHeight != height:
            self.__tabButtonHeight = height
            for page in self.__pages:
                page.button.setFixedHeight(height)

    def tabButtonHeight(self):
        """
        Return the tab button height.
        """
        return self.__tabButtonHeight

    def setTabIconSize(self, size):
        """
        Set the tab button icon size.
        """
        if self.__tabIconSize != size:
            self.__tabIconSize = size
            for page in self.__pages:
                page.button.setIconSize(size)

    def tabIconSize(self):
        """
        Return the tab icon size.
        """
        return self.__tabIconSize

    def tabButton(self, index):
        """
        Return the tab button at `index`
        """
        return self.__pages[index].button

    def tabAction(self, index):
        """
        Return open/close action for the tab at `index`.
        """
        return self.__pages[index].action

    def addItem(self, widget, text, icon=None, toolTip=None):
        """
        Append the `widget` in a new tab and return its index.

        Parameters
        ----------
        widget : :class:`QWidget`
            A widget to be inserted. The toolbox takes ownership
            of the widget.

        text : str
            Name/title of the new tab.

        icon : :class:`QIcon`, optional
            An icon for the tab button.

        toolTip : str, optional
            Tool tip for the tab button.

        """
        return self.insertItem(self.count(), widget, text, icon, toolTip)

    def insertItem(self, index, widget, text, icon=None, toolTip=None):
        """
        Insert the `widget` in a new tab at position `index`.

        See also
        --------
        ToolBox.addItem

        """
        button = self.createTabButton(widget, text, icon, toolTip)

        self.__contentsLayout.insertWidget(index * 2, button)
        self.__contentsLayout.insertWidget(index * 2 + 1, widget)

        widget.hide()

        page = _ToolBoxPage(index, widget, button.defaultAction(), button)
        self.__pages.insert(index, page)

        for i in range(index + 1, self.count()):
            self.__pages[i] = self.__pages[i]._replace(index=i)

        self.__updatePositions()

        # Show (open) the first tab.
        if self.count() == 1 and index == 0:
            page.action.trigger()

        self.__updateSelected()

        self.updateGeometry()
        return index

    def removeItem(self, index):
        """
        Remove the widget at `index`.

        .. note:: The widget hidden but is is not deleted.

        """
        self.__contentsLayout.takeAt(2 * index + 1)
        self.__contentsLayout.takeAt(2 * index)
        page = self.__pages.pop(index)

        # Update the page indexes
        for i in range(index, self.count()):
            self.__pages[i] = self.__pages[i]._replace(index=i)

        page.button.deleteLater()

        # Hide the widget and reparent to self
        # This follows QToolBox.removeItem
        page.widget.hide()
        page.widget.setParent(self)

        self.__updatePositions()
        self.__updateSelected()

        self.updateGeometry()

    def count(self):
        """
        Return the number of widgets inserted in the toolbox.
        """
        return len(self.__pages)

    def widget(self, index):
        """
        Return the widget at `index`.
        """
        return self.__pages[index].widget

    def createTabButton(self, widget, text, icon=None, toolTip=None):
        """
        Create the tab button for `widget`.
        """
        action = QAction(text, self)
        action.setCheckable(True)

        if icon:
            action.setIcon(icon)

        if toolTip:
            action.setToolTip(toolTip)
        self.__tabActionGroup.addAction(action)
        self.__actionMapper.setMapping(action, action)
        action.toggled.connect(self.__actionMapper.map)

        button = ToolBoxTabButton(self, objectName="toolbox-tab-button")
        button.setDefaultAction(action)
        button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        button.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed)

        if self.__tabIconSize.isValid():
            button.setIconSize(self.__tabIconSize)

        if self.__tabButtonHeight > 0:
            button.setFixedHeight(self.__tabButtonHeight)

        return button

    def ensureWidgetVisible(self, child, xmargin=50, ymargin=50):
        """
        Scroll the contents so child widget instance is visible inside
        the viewport.

        """
        self.__scrollArea.ensureWidgetVisible(child, xmargin, ymargin)

    def sizeHint(self):
        hint = self.__contentsLayout.sizeHint()

        if self.count():
            # Compute max width of hidden widgets also.
            scroll = self.__scrollArea
            scroll_w = scroll.verticalScrollBar().sizeHint().width()
            frame_w = self.frameWidth() * 2 + scroll.frameWidth() * 2
            max_w = max([p.widget.sizeHint().width() for p in self.__pages])
            hint = QSize(
                max(max_w, hint.width()) + scroll_w + frame_w, hint.height())

        return QSize(200, 200).expandedTo(hint)

    def __onTabActionToogled(self, action):
        page = find(self.__pages, action, key=attrgetter("action"))
        on = action.isChecked()
        page.widget.setVisible(on)
        index = page.index

        if index > 0:
            # Update the `previous` tab buttons style hints
            previous = self.__pages[index - 1].button
            flag = QStyleOptionToolBox.NextIsSelected
            if on:
                previous.selected |= flag
            else:
                previous.selected &= ~flag

            previous.update()

        if index < self.count() - 1:
            next = self.__pages[index + 1].button
            flag = QStyleOptionToolBox.PreviousIsSelected
            if on:
                next.selected |= flag
            else:
                next.selected &= ~flag

            next.update()

        self.tabToogled.emit(index, on)

        self.__contentsLayout.invalidate()

    def __updateSelected(self):
        """Update the tab buttons selected style flags.
        """
        if self.count() == 0:
            return

        opt = QStyleOptionToolBox

        def update(button, next_sel, prev_sel):
            if next_sel:
                button.selected |= opt.NextIsSelected
            else:
                button.selected &= ~opt.NextIsSelected

            if prev_sel:
                button.selected |= opt.PreviousIsSelected
            else:
                button.selected &= ~opt.PreviousIsSelected

            button.update()

        if self.count() == 1:
            update(self.__pages[0].button, False, False)
        elif self.count() >= 2:
            pages = self.__pages
            for i in range(1, self.count() - 1):
                update(pages[i].button, pages[i + 1].action.isChecked(),
                       pages[i - 1].action.isChecked())

    def __updatePositions(self):
        """Update the tab buttons position style flags.
        """
        if self.count() == 0:
            return
        elif self.count() == 1:
            self.__pages[0].button.position = QStyleOptionToolBox.OnlyOneTab
        else:
            self.__pages[0].button.position = QStyleOptionToolBox.Beginning
            self.__pages[-1].button.position = QStyleOptionToolBox.End
            for p in self.__pages[1:-1]:
                p.button.position = QStyleOptionToolBox.Middle

        for p in self.__pages:
            p.button.update()
Exemplo n.º 43
0
    def makePopupMenu(self):
        index = self.currentIndex()
        sel = self.getSelection()
        clipboard = qApp.clipboard()

        menu = QMenu(self)

        # Get index under cursor
        pos = self.viewport().mapFromGlobal(QCursor.pos())
        mouseIndex = self.indexAt(pos)

        # Get index's title
        if mouseIndex.isValid():
            title = mouseIndex.internalPointer().title()

        elif self.rootIndex().parent().isValid():
            # mouseIndex is the background of an item, so we check the parent
            mouseIndex = self.rootIndex().parent()
            title = mouseIndex.internalPointer().title()

        else:
            title = qApp.translate("outlineBasics", "Root")

        if len(title) > 25:
            title = title[:25] + "…"

        # Open Item action
        self.actOpen = QAction(
            QIcon.fromTheme("go-right"),
            qApp.translate("outlineBasics", "Open {}".format(title)), menu)
        self.actOpen.triggered.connect(self.openItem)
        menu.addAction(self.actOpen)

        # Open item(s) in new tab
        if mouseIndex in sel and len(sel) > 1:
            actionTitle = qApp.translate("outlineBasics",
                                         "Open {} items in new tabs").format(
                                             len(sel))
            self._indexesToOpen = sel
        else:
            actionTitle = qApp.translate("outlineBasics",
                                         "Open {} in a new tab").format(title)
            self._indexesToOpen = [mouseIndex]

        self.actNewTab = QAction(QIcon.fromTheme("go-right"), actionTitle,
                                 menu)
        self.actNewTab.triggered.connect(self.openItemsInNewTabs)
        menu.addAction(self.actNewTab)

        menu.addSeparator()

        # Add text / folder
        self.actAddFolder = QAction(
            QIcon.fromTheme("folder-new"),
            qApp.translate("outlineBasics", "New &Folder"), menu)
        self.actAddFolder.triggered.connect(self.addFolder)
        menu.addAction(self.actAddFolder)

        self.actAddText = QAction(QIcon.fromTheme("document-new"),
                                  qApp.translate("outlineBasics", "New &Text"),
                                  menu)
        self.actAddText.triggered.connect(self.addText)
        menu.addAction(self.actAddText)

        menu.addSeparator()

        # Copy, cut, paste, duplicate
        self.actCut = QAction(QIcon.fromTheme("edit-cut"),
                              qApp.translate("outlineBasics", "C&ut"), menu)
        self.actCut.triggered.connect(self.cut)
        menu.addAction(self.actCut)

        self.actCopy = QAction(QIcon.fromTheme("edit-copy"),
                               qApp.translate("outlineBasics", "&Copy"), menu)
        self.actCopy.triggered.connect(self.copy)
        menu.addAction(self.actCopy)

        self.actPaste = QAction(QIcon.fromTheme("edit-paste"),
                                qApp.translate("outlineBasics", "&Paste"),
                                menu)
        self.actPaste.triggered.connect(self.paste)
        menu.addAction(self.actPaste)

        # Rename / duplicate / remove items
        self.actDelete = QAction(QIcon.fromTheme("edit-delete"),
                                 qApp.translate("outlineBasics", "&Delete"),
                                 menu)
        self.actDelete.triggered.connect(self.delete)
        menu.addAction(self.actDelete)

        self.actRename = QAction(QIcon.fromTheme("edit-rename"),
                                 qApp.translate("outlineBasics", "&Rename"),
                                 menu)
        self.actRename.triggered.connect(self.rename)
        menu.addAction(self.actRename)

        menu.addSeparator()

        # POV
        self.menuPOV = QMenu(qApp.translate("outlineBasics", "Set POV"), menu)
        mw = mainWindow()
        a = QAction(QIcon.fromTheme("dialog-no"),
                    qApp.translate("outlineBasics", "None"), self.menuPOV)
        a.triggered.connect(lambda: self.setPOV(""))
        self.menuPOV.addAction(a)
        self.menuPOV.addSeparator()

        menus = []
        for i in [
                qApp.translate("outlineBasics", "Main"),
                qApp.translate("outlineBasics", "Secondary"),
                qApp.translate("outlineBasics", "Minor")
        ]:
            m = QMenu(i, self.menuPOV)
            menus.append(m)
            self.menuPOV.addMenu(m)

        mpr = QSignalMapper(self.menuPOV)
        for i in range(mw.mdlCharacter.rowCount()):
            a = QAction(mw.mdlCharacter.icon(i), mw.mdlCharacter.name(i),
                        self.menuPOV)
            a.triggered.connect(mpr.map)
            mpr.setMapping(a, int(mw.mdlCharacter.ID(i)))

            imp = toInt(mw.mdlCharacter.importance(i))

            menus[2 - imp].addAction(a)

        mpr.mapped.connect(self.setPOV)
        menu.addMenu(self.menuPOV)

        # Status
        self.menuStatus = QMenu(qApp.translate("outlineBasics", "Set Status"),
                                menu)
        # a = QAction(QIcon.fromTheme("dialog-no"), qApp.translate("outlineBasics", "None"), self.menuStatus)
        # a.triggered.connect(lambda: self.setStatus(""))
        # self.menuStatus.addAction(a)
        # self.menuStatus.addSeparator()

        mpr = QSignalMapper(self.menuStatus)
        for i in range(mw.mdlStatus.rowCount()):
            a = QAction(mw.mdlStatus.item(i, 0).text(), self.menuStatus)
            a.triggered.connect(mpr.map)
            mpr.setMapping(a, i)
            self.menuStatus.addAction(a)
        mpr.mapped.connect(self.setStatus)
        menu.addMenu(self.menuStatus)

        # Labels
        self.menuLabel = QMenu(qApp.translate("outlineBasics", "Set Label"),
                               menu)
        mpr = QSignalMapper(self.menuLabel)
        for i in range(mw.mdlLabels.rowCount()):
            a = QAction(
                mw.mdlLabels.item(i, 0).icon(),
                mw.mdlLabels.item(i, 0).text(), self.menuLabel)
            a.triggered.connect(mpr.map)
            mpr.setMapping(a, i)
            self.menuLabel.addAction(a)
        mpr.mapped.connect(self.setLabel)
        menu.addMenu(self.menuLabel)

        menu.addSeparator()

        # Custom icons
        if self.menuCustomIcons:
            menu.addMenu(self.menuCustomIcons)
        else:
            self.menuCustomIcons = QMenu(
                qApp.translate("outlineBasics", "Set Custom Icon"), menu)
            a = QAction(qApp.translate("outlineBasics", "Restore to default"),
                        self.menuCustomIcons)
            a.triggered.connect(lambda: self.setCustomIcon(""))
            self.menuCustomIcons.addAction(a)
            self.menuCustomIcons.addSeparator()

            txt = QLineEdit()
            txt.textChanged.connect(self.filterLstIcons)
            txt.setPlaceholderText("Filter icons")
            txt.setStyleSheet("background: transparent; border: none;")
            act = QWidgetAction(self.menuCustomIcons)
            act.setDefaultWidget(txt)
            self.menuCustomIcons.addAction(act)

            self.lstIcons = QListWidget()
            for i in customIcons():
                item = QListWidgetItem()
                item.setIcon(QIcon.fromTheme(i))
                item.setData(Qt.UserRole, i)
                item.setToolTip(i)
                self.lstIcons.addItem(item)
            self.lstIcons.itemClicked.connect(self.setCustomIconFromItem)
            self.lstIcons.setViewMode(self.lstIcons.IconMode)
            self.lstIcons.setUniformItemSizes(True)
            self.lstIcons.setResizeMode(self.lstIcons.Adjust)
            self.lstIcons.setMovement(self.lstIcons.Static)
            self.lstIcons.setStyleSheet(
                "background: transparent; background: none;")
            self.filterLstIcons("")
            act = QWidgetAction(self.menuCustomIcons)
            act.setDefaultWidget(self.lstIcons)
            self.menuCustomIcons.addAction(act)

            menu.addMenu(self.menuCustomIcons)

        # Disabling stuff
        if not clipboard.mimeData().hasFormat("application/xml"):
            self.actPaste.setEnabled(False)

        if len(sel) == 0:
            self.actCopy.setEnabled(False)
            self.actCut.setEnabled(False)
            self.actRename.setEnabled(False)
            self.actDelete.setEnabled(False)
            self.menuPOV.setEnabled(False)
            self.menuStatus.setEnabled(False)
            self.menuLabel.setEnabled(False)
            self.menuCustomIcons.setEnabled(False)

        if len(sel) > 1:
            self.actRename.setEnabled(False)

        return menu
Exemplo n.º 44
0
class runomatic(QWidget):
	update_signal=pyqtSignal("PyQt_PyObject")
	def __init__(self):
		super().__init__()
		self._plasmaMetaHotkey(enable=False,reconfigure=True)
		self.dbg=False
		exePath=sys.argv[0]
		if os.path.islink(sys.argv[0]):
			exePath=os.path.realpath(sys.argv[0])
		self.baseDir=os.path.abspath(os.path.dirname(exePath))
		signal.signal(signal.SIGUSR1,self._end_process)
		signal.signal(signal.SIGUSR2,self._fail_process)
		self.runoapps="/usr/share/runomatic/applications"
		self.launched=[]
		self.blocked=[]
		self.procMon=[]
		self.categories={}
		self.desktops={}
		self.pid=0
		self.app_icons={}
		self.tab_icons={}
		self.tab_id={}
		self.focusWidgets=[]
		self.appsWidgets=[]
		self.launchErr=False
		self.id=0
		self.firstLaunch=True
		self.currentTab=0
		self.currentBtn=0
		self.closeKey=False
		self.confKey=False
		self.keymap={}
		self.display=os.environ['DISPLAY']
		self.cache="%s/.cache/runomatic/"%os.environ['HOME']
		self.defaultBg="/usr/share/runomatic/rsrc/background2.png"
		self.username=getpass.getuser()
		self.runner=appRun()
		self._set_keymapping()
		self._read_config()
		self._plasmaMetaHotkey(enable=True)
		self._render_gui()
	#def init

	def _plasmaMetaHotkey(self,enable=None,reconfigure=None):
		env=os.environ.copy()
		cmd='kwriteconfig5 --file ~/.config/kwinrc --group ModifierOnlyShortcuts --key Meta "org.kde.plasmashell,/PlasmaShell,org.kde.PlasmaShell,activateLauncherMenu"'
		if enable==False:
			cmd='kwriteconfig5 --file ~/.config/kwinrc --group ModifierOnlyShortcuts --key Meta ""'
		try:
			a=subprocess.Popen(cmd,stdin=None,stdout=None,stderr=None,shell=True,env=env)
			a.wait()
		except Exception as e:
			self._debug("kwriteconfig: %s"%e)
		
		if reconfigure==True:
			try:
				cmd='qdbus org.kde.KWin /KWin reconfigure'
				a=subprocess.Popen(cmd,stdin=None,stdout=None,stderr=None,shell=True,env=env)
				a.wait()
			except Exception as e:
				self._debug("qdbus: %s"%e)

	def _fail_process(self,*args):
		self.launchErr=True
		self._debug("PROCESS %s FAILED TO LAUNCH"%args[0])
	#def _fail_process

	def _end_process(self,*args):
		for thread in self.runner.getDeadProcesses():
			idx=self._get_tabId_from_thread(thread)
			if idx and idx>0:
				if self.launchErr:
					self.blocked.append(self.tab_id[idx]['app'])
				self._on_tabRemove(idx)
				self.focusWidgets[self.currentBtn].index=None
				self.focusWidgets[self.currentBtn].statusBar.setText("")
				if self.launchErr:
					self.launchErr=False
					self.focusWidgets[self.currentBtn].statusBar.label.setStyleSheet(self.focusWidgets[self.currentBtn].cssError)
					self.focusWidgets[self.currentBtn].statusBar.show(state='error')
				#	self._on_tabSelect(0)
					self.focusWidgets[self.currentBtn].setFocus()
	#def _end_process

	def _debug(self,msg):
		if self.dbg:
			print("runomatic: %s"%msg)
	#def _debug

	def _set_keymapping(self):
		#Disable meta association within plasma. I've no eggs to capture and block this event in kde
		for key,value in vars(Qt).items():
			if isinstance(value, Qt.Key):
				self.keymap[value]=key.partition('_')[2]
		self.modmap={
					Qt.ControlModifier: self.keymap[Qt.Key_Control],
					Qt.AltModifier: self.keymap[Qt.Key_Alt],
					Qt.ShiftModifier: self.keymap[Qt.Key_Shift],
					Qt.MetaModifier: self.keymap[Qt.Key_Meta],
					Qt.GroupSwitchModifier: self.keymap[Qt.Key_AltGr],
					Qt.KeypadModifier: self.keymap[Qt.Key_NumLock]
					}
		self.sigmap_tabSelect=QSignalMapper(self)
		self.sigmap_tabSelect.mapped[QInt].connect(self._on_tabSelect)
		self.sigmap_tabRemove=QSignalMapper(self)
		self.sigmap_tabRemove.mapped[QInt].connect(self._on_tabRemove)
		#Shortcut for super_keys


	#def _set_keymapping

	def _read_config(self):
		data=self.runner.get_apps()
		self.categories=data.get('categories')
		self.desktops=data.get('desktops')
		self.keybinds=data.get('keybinds',{})
		self.password=data.get('password')
		self.close_on_exit=data.get('close',False)
		self.bg=data.get('background',self.defaultBg)
		if (not(os.path.isfile(self.bg))):
			self.bg=self.defaultBg
		self.runner.setBg(self.bg)
	#def _read_config(self):

	def _init_gui(self):
		self.setWindowFlags(Qt.FramelessWindowHint)
		self.setWindowFlags(Qt.X11BypassWindowManagerHint)
		self.setWindowState(Qt.WindowFullScreen)
		self.setWindowFlags(Qt.WindowStaysOnTopHint)
		self.setWindowModality(Qt.WindowModal)
		self.bg="/usr/share/runomatic/rsrc/background2.png"
		self.previousIcon=QtGui.QIcon.fromTheme("go-previous")
		self.previousIcon=QtGui.QIcon.fromTheme("go-home")
		btnPrevious=QPushButton()
		btnPrevious.setObjectName("PushButton")
		btnPrevious.setIcon(self.previousIcon)
		btnPrevious.setIconSize(QSize(TAB_BTN_SIZE,TAB_BTN_SIZE))
		self.sigmap_tabSelect.setMapping(btnPrevious,0)
		btnPrevious.clicked.connect(self.sigmap_tabSelect.map)
		self.homeIcon=QtGui.QIcon.fromTheme("go-home")
		btnHome=QPushButton()
		btnHome.setObjectName("PushButton")
		btnHome.setIcon(self.homeIcon)
		btnHome.setIconSize(QSize(TAB_BTN_SIZE,TAB_BTN_SIZE))
		self.tab_id[0]={'index':self.id,'thread':0,'xephyr':None,'wid':0,'show':btnHome,'close':btnPrevious,'display':"%s"%os.environ['DISPLAY']}
		self.closeIcon=QtGui.QIcon.fromTheme("window-close")
		self.grab=False
		self.setStyleSheet(self._define_css())
		monitor=QDesktopWidget().screenGeometry(1)
		self.move(monitor.left(),monitor.top())
		cursor=QtGui.QCursor(Qt.PointingHandCursor)
		self.setCursor(cursor)
		self.showFullScreen()
		#self.show()
	#def _init_gui(self):

	def _render_gui(self):
		#Enable transparent window
#		self.setStyleSheet('background:transparent')
#		self.setAttribute(Qt.WA_TranslucentBackground)
		####
		def launchConf():
				try:
					if os.path.isfile("%s/runoconfig.py"%self.baseDir):
						if self.close():
							os.execv("%s/runoconfig.py"%self.baseDir,["1"])
					else:
						self.showMessage(_("runoconfig not found at %s"%self.baseDir),"error2",20)
				except:
					print(_("runoconfig not found"))
		self._init_gui()
		self.box=QGridLayout()
		self.statusBar=QAnimatedStatusBar.QAnimatedStatusBar()
		self.statusBar.setStateCss("error","background-color:qlineargradient(x1:0 y1:0,x2:0 y2:1,stop:0 rgba(255,255,0,1), stop:1 rgba(255,0,0,0.6));text-decoration:none;color:white;text-align:center;font-size:128px;height:256px")
		self.statusBar.setStateCss("error2","background-color:qlineargradient(x1:0 y1:0,x2:0 y2:1,stop:0 rgba(255,0,0,1), stop:1 rgba(255,0,0,0.6));color:white;text-align:center;text-decoration:none;font-size:12px;height:20px")
		self.statusBar.height_=152
		self.box.addWidget(self.statusBar,0,0,1,2)
		self.tabBar=self._tabBar()
		self.setObjectName("tabbar")
#		self.tabBar.setAttribute(Qt.WA_TranslucentBackground)
		self.tabBar.currentChanged.connect(lambda:self._on_tabChanged(False))
		self.box.addWidget(self.tabBar,0,0,1,1)
		self.setLayout(self.box)
		self.tabBar.setFocusPolicy(Qt.NoFocus)
		if self.focusWidgets:
			self._set_focus("")
		else:
			wdg=QWidget()
			lyt=QVBoxLayout()
			wdg.setLayout(lyt)
			lbl=QLabel(_("There's no launchers to show.\nDid you run the configure app?"))
			lyt.addWidget(lbl)
			btn=QPushButton(_("Launch configuration app"))
			btn.clicked.connect(launchConf)
			lyt.addWidget(btn)
			self.box.addWidget(wdg,0,0,1,1,Qt.AlignCenter)
	#def _render_gui

	def closeEvent(self,event):
		if self.password:
			text=_("Insert the password")
			if self.close_on_exit==True:
				text=_("Insert the password. Current session will also be closed.")
			pwd,resp=QInputDialog.getText(self,_("Confirm close"),text,QLineEdit.Password)
			if resp:
				if not hashpwd.verify(pwd,self.password):
					event.ignore()
			else:
				event.ignore()
		self._plasmaMetaHotkey(reconfigure=True)
		for index in self.tab_id.keys():
			if index:
				th=self.tab_id[index].get('thread',None)
				if th:
					self.runner.send_signal_to_thread("kill",th)
				xe=self.tab_id[index].get('xephyr',None)
				if xe:
					self.runner.send_signal_to_thread("kill",xe)
				dsp=self.tab_id[index].get('display',None)
				if dsp:
					self.runner.stop_display(self.tab_id[index].get('wid',''),dsp)
					xlockFile=os.path.join("/tmp",".X%s-lock"%dsp.replace(":",""))
					if os.path.isfile(xlockFile):
						os.remove(xlockFile)
		if str(self.close_on_exit).lower()=='true':
			print("Closing session...")
			subprocess.run(["loginctl","terminate-user","%s"%self.username])
	#def closeEvent

	def keyPressEvent(self,event):
		key=self.keymap.get(event.key(),event.text())
		if key in ("Alt" ,"Super_L"):
			self.grab=True
		self.grabKeyboard()
	#def eventFilter
	
	def keyReleaseEvent(self,event):
		key=self.keymap.get(event.key(),event.text())
		confKey=''
		if self.keybinds:
			confKey=self.keybinds.get('conf',None)
		if key not in ('Tab','Super_L'):
			if key=='F4' and self.grab:
				self.closeKey=True
			elif key==confKey:
				if os.path.isfile("%s/runoconfig.py"%self.baseDir):
					if self.close():
						os.execv("%s/runoconfig.py"%self.baseDir,["1"])
				else:
					event.ignore()
					self.showMessage(_("runoconfig not found"),"error2",20)
			elif key.isdigit():
				if int(key)<=self.tabBar.count():
					self._on_tabSelect(int(key),True)
					cont=0
					for btn in self.focusWidgets:
						if btn.statusBar.label.text()==key:
							self.currentBtn=cont
							self.focusWidgets[cont].setFocus()
						cont+=1
				else:
					event.ignore()
			self.releaseKeyboard()
		if key in ('Alt','Control','Super_L'):
			if key!='Super_L':
				self.releaseKeyboard()
			else:
				event.setAccepted(True)
				self._on_tabSelect(0,True)
				event.accept()
			self.grab=False
			if key=='Alt':
				if self.closeKey:
					self.closeKey=False
					self.close()
				if self.confKey:
					self.confKey=False
	#def keyReleaseEvent

	def _set_focus(self,key):
		cursor=QtGui.QCursor(Qt.PointingHandCursor)
		self.setCursor(cursor)
		self.grabMouse()
		#cursor.setPos(50,50)
		self.releaseMouse()
		if key=="Space" or key=="NumLock+Enter" or key=="Return":
			self.focusWidgets[self.currentBtn].clicked.emit()
		elif key=="Tab":
			tabCount=self.tabBar.count()
			newTab=self.tabBar.currentIndex()+1
			if newTab>tabCount:
				newTab=0
			self.tabBar.setCurrentIndex(newTab)
		if key=="Alt":
			return(True)

		else:
			self.focusWidgets[self.currentBtn].resize(QSize(BTN_SIZE,BTN_SIZE))
			self.focusWidgets[self.currentBtn].setIconSize(QSize(BTN_SIZE,BTN_SIZE))
			if key=="Left":
				self.currentBtn+=1
				if self.currentBtn>=len(self.focusWidgets):
					self.currentBtn=0
			elif key=="Right":
				self.currentBtn-=1
				if self.currentBtn<0:
					self.currentBtn=len(self.focusWidgets)-1
			elif key=="Up":
				currentBtn=self.currentBtn
				currentBtn-=self.maxCol
				if currentBtn<0:
					currentBtn=self.currentBtn
				self.currentBtn=currentBtn
			elif key=="Down":
				currentBtn=self.currentBtn
				currentBtn+=self.maxCol
				if currentBtn>=len(self.focusWidgets):
					currentBtn=self.currentBtn
				self.currentBtn=currentBtn
			self.focusWidgets[self.currentBtn].setFocus()
			self._debug("Focus to %s"%self.focusWidgets[self.currentBtn])
			self.focusWidgets[self.currentBtn].resize(QSize(BTN_SIZE*1.2,BTN_SIZE*1.2))
			self.focusWidgets[self.currentBtn].setIconSize(QSize(BTN_SIZE*1.2,BTN_SIZE*1.2))
	#def _set_focus(self,key):

	def _get_focus(self,widget):
		self.currentBtn=self.focusWidgets.index(widget)
	#def _get_focus(self,widget)

	def _add_widgets(self,vbox,apps):
		row=int(len(self.appsWidgets)/self.maxCol)
		col=(self.maxCol*(row+1))-len(self.appsWidgets)
		sigmap_run=QSignalMapper(self)
		sigmap_run.mapped[QString].connect(self._launch)
		for appName,data in apps.items():
			appIcon=data['Icon']
			appDesc=data['Name']
			if QtGui.QIcon.hasThemeIcon(appIcon):
				icnApp=QtGui.QIcon.fromTheme(appIcon)
			elif os.path.isfile(appIcon):
					iconPixmap=QtGui.QPixmap(appIcon)
					scaledIcon=iconPixmap.scaled(QSize(BTN_SIZE*1.2,BTN_SIZE*1.2))
					icnApp=QtGui.QIcon(scaledIcon)
			elif appIcon.startswith("http"):
				if not os.path.isdir("%s/icons"%self.cache):
					os.makedirs("%s/icons"%self.cache)
				tmpfile=os.path.join("%s/icons"%self.cache,appIcon.split("/")[2].split(".")[0])
				if not os.path.isfile(tmpfile):
					try:
						urlretrieve(appIcon,tmpfile)
					except:
						tmpfile=QtGui.QIcon.fromTheme("shell")
				iconPixmap=QtGui.QPixmap(tmpfile)
				scaledIcon=iconPixmap.scaled(QSize(BTN_SIZE*1.2,BTN_SIZE*1.2))
				icnApp=QtGui.QIcon(scaledIcon)
				appIcon=tmpfile
			else:
				continue
			if not appName:
				continue
			self.app_icons[appName]=appIcon
			self._debug("Adding %s"%appName)
			btnApp=navButton(self)
			btnApp.setIcon(icnApp)
			btnApp.setIconSize(QSize(BTN_SIZE,BTN_SIZE))
			btnApp.setToolTip(appDesc)
			btnApp.setFocusPolicy(Qt.NoFocus)
			btnApp.keypress.connect(self._set_focus)
			btnApp.focusIn.connect(self._get_focus)
			self.focusWidgets.append(btnApp)
			self.appsWidgets.append(appName)
			sigmap_run.setMapping(btnApp,appName)
			btnApp.clicked.connect(sigmap_run.map)
			vbox.addWidget(btnApp,row,col,Qt.Alignment(-1))
			col+=1
			if col==self.maxCol:
				col=0
				row+=1
	#def _add_widgets
	
	def _tabBar(self):
		tabBar=QTabWidget()
		tabScroll=QWidget()
		tabScroll.setObjectName("scroll")
		tabScroll.setStyleSheet("#scroll{background-image:url('%s');}"%self.bg)
		tabScroll.setFocusPolicy(Qt.NoFocus)
		scrollArea=QScrollArea(tabScroll)
		scrollArea.setFocusPolicy(Qt.NoFocus)
		tabContent=QWidget()
		vbox=QGridLayout()
		scr=app.primaryScreen()
		w=scr.size().width()-BTN_SIZE
		h=scr.size().height()-(2*BTN_SIZE)
		self.maxCol=int(w/BTN_SIZE)-3
		self._debug("Size: %s\nCols: %s"%(self.width(),self.maxCol))
		for desktop in self.desktops:
			apps=self._get_desktop_apps(desktop)
			self._add_widgets(vbox,apps)

		tabContent.setLayout(vbox)
		scrollArea.setWidget(tabContent)
		scrollArea.setObjectName("launcher")
		scrollArea.alignment()
		tabBar.addTab(tabScroll,"")
		tabBar.tabBar().setTabButton(0,QTabBar.LeftSide,self.tab_id[0]['show'])
		tabBar.tabBar().tabButton(0,QTabBar.LeftSide).setFocusPolicy(Qt.NoFocus)
		scrollArea.setGeometry(QRect(0,0,w,h))
		tabBar.setObjectName("tabbar")
		return (tabBar)
	#def _tabBar

	def _on_tabChanged(self,remove=True):
		self._debug("From Tab: %s"%self.currentTab)
		index=self._get_tabId_from_index(self.currentTab)
		self._debug("Tab Index: %s"%index)
		key='show'
		if self.currentTab==0:
			index=0
			key='close'
		self.tabBar.tabBar().setTabButton(self.currentTab,QTabBar.LeftSide,self.tab_id[index][key])
		self._debug("Pressed: %s"%self.tab_id[index][key])
		try:
			self.tabBar.tabBar().tabButton(self.currentTab,QTabBar.LeftSide).setFocusPolicy(Qt.NoFocus)
		except:
			pass
		self.runner.send_signal_to_thread("stop",self.tab_id[index]['thread'])
		index=self._get_tabId_from_index(self.tabBar.currentIndex())
		self.currentTab=self.tabBar.currentIndex()
		key='close'
		if self.currentTab==0:
			self.focusWidgets[self.currentBtn].setFocus()
			index=0
			key='show'
		self._debug("New Tab Index: %s"%self.tab_id[index])
		self._debug("New index: %s"%index)
		self._debug("Btn: %s"%self.tab_id[index][key])
		self.tabBar.tabBar().setTabButton(self.currentTab,QTabBar.LeftSide,self.tab_id[index][key])
		self.tabBar.tabBar().tabButton(self.currentTab,QTabBar.LeftSide).setFocusPolicy(Qt.NoFocus)
		self.runner.send_signal_to_thread("cont",self.tab_id[index]['thread'])
		#os.environ['DISPLAY']=self.tab_id[index]['display']
		self._debug("New Current Tab: %s Icon:%s"%(self.currentTab,key))
	#def _on_tabChanged

	def _on_tabSelect(self,index,btn=None):
		self._debug("Select tab: %s"%index)
		if btn:
			index=self._get_tabId_from_index(index)
		self.tabBar.setCurrentIndex(self.tab_id[index]['index'])
		cursor=QtGui.QCursor()
		self.setCursor(cursor)
		cursor.setPos(300,300)
	#def _on_tabSelect

	def _on_tabRemove(self,index):
		self._debug("Remove tab: %s"%index)
		self.tabBar.blockSignals(True)
		self.tabBar.removeTab(self.tab_id[index]['index'])

		xlockFile=os.path.join("/tmp",".X%s-lock"%self.tab_id[index]['display'].replace(":",""))
		if os.path.isfile(xlockFile):
			os.remove(xlockFile)
		if (self.tab_id[index].get('app',"") in self.launched):
			self.launched.remove(self.tab_id[index]['app'])
		for idx in range(index+1,len(self.tab_id)):
			if idx in self.tab_id.keys():
				self._debug("Tab Array: %s"%self.tab_id)
				if 'index' in self.tab_id[idx].keys():
					self._debug("Reasign %s"%(self.tab_id[idx]['index']))
					self.tab_id[idx]['index']=self.tab_id[idx]['index']-1
					self.focusWidgets[self.appsWidgets.index(self.tab_id[idx]['app'])].statusBar.setText(str(self.tab_id[idx]['index']))
					self.focusWidgets[self.appsWidgets.index(self.tab_id[idx]['app'])].index=self.tab_id[idx]['index']
					self._debug("Reasigned %s -> %s"%(idx,self.tab_id[idx]['index']))
		th=self.tab_id[index].get('thread',None)
		if th:
			self.runner.send_signal_to_thread("term",th)
		xe=self.tab_id[index].get('xephyr',None)
		if xe:
			self.runner.send_signal_to_thread("term",xe)
		dsp=self.tab_id[index].get('display',None)
		self.focusWidgets[self.appsWidgets.index(self.tab_id[index]['app'])].statusBar.hide()
		if dsp:
			self.runner.stop_display(self.tab_id[index].get('wid',''),dsp)
			xlockFile=os.path.join("/tmp",".X%s-lock"%dsp.replace(":",""))
			if os.path.isfile(xlockFile):
				os.remove(xlockFile)
		self.tab_id[index]={}
		if self.launchErr:
			self.currentTab=self._get_tabId_from_index(0)
		else:
			self.currentTab=self._get_tabId_from_index(self.tabBar.currentIndex())
		index=self.currentTab
		self._debug("New tab: %s"%self.currentTab)
		self._on_tabChanged()
		self.tabBar.blockSignals(False)
		self.tabBar.setFocus()
		self.tabBar.setCurrentIndex(index)
		self._debug("Removed tab: %s"%index)
	#def _on_tabRemove

	def _get_category_apps(self,category):
		apps=self.runner.get_category_apps(category.lower())
		return (apps)
	#def _get_category_apps
	
	def _get_desktop_apps(self,desktop):
		#Check if desktop is from run-o-matic
		if "run-o-matic" in self.categories:
			if os.path.isdir(self.runoapps):
				if desktop in os.listdir(self.runoapps):
					desktop=os.path.join(self.runoapps,desktop)
		apps=self.runner.get_desktop_app(desktop,True)
		return (apps)
	#def _get_category_apps

	def _launchZone(self,app):
		tabContent=QWidget()
		box=QVBoxLayout()
		wid=self.runner.get_wid("Xephyr on",self.display)
		self._debug("Window Wid: %s"%wid)
		zone=None
		if wid:
			(zone,zone_window)=appZone(tabContent).createZone(wid)
			zone.setObjectName("appzone")

		if not zone or not wid:
			self._debug("Xephyr failed to launch")
			tabContent.destroy()
			wid=None
		else:
			box.addWidget(zone)
			zone.setFocusPolicy(Qt.NoFocus)
			tabContent.setLayout(box)
			icn=QtGui.QIcon.fromTheme(self.app_icons[app])
			btn=QPushButton()
			btn.setObjectName("PushButton")
			btn.setIconSize(QSize(TAB_BTN_SIZE,TAB_BTN_SIZE))
			btn.setIcon(icn)
			self.id+=1
			self._debug("New Tab id %s"%self.id)
			self.sigmap_tabSelect.setMapping(btn,self.id)
			btn.clicked.connect(self.sigmap_tabSelect.map)
			btn_close=QPushButton()
			btn_close.setObjectName("PushButton")
			btn_close.setIcon(self.closeIcon)
			btn_close.setIconSize(QSize(TAB_BTN_SIZE,TAB_BTN_SIZE))
			self.sigmap_tabRemove.setMapping(btn_close,self.id)
			btn_close.clicked.connect(self.sigmap_tabRemove.map)
			self.tab_id[self.id]={'index':self.tabBar.count(),'thread':None,'wid':zone_window,'show':btn,'close':btn_close,'display':self.display}

			self.tabBar.addTab(tabContent,"")

		return(wid)
	#def _launchZone

	def _launch(self,app):
		if app in self.launched:
			self._on_tabSelect(self.focusWidgets[self.appsWidgets.index(app)].index,True)
			return
		if app in self.blocked:
			return
		cursor=QtGui.QCursor(Qt.WaitCursor)
		self.setCursor(cursor)
		self.grabMouse()
		self.tabBar.setTabIcon(0,self.previousIcon)
		self._debug("Tab count: %s"%self.tabBar.count())
		#Tabs BEFORE new tab is added
		tabCount=self.tabBar.count()
		btn=None
		try:
			btn=self.appsWidgets.index(app)	
		except:
			btn=None
		if btn:
			self.currentBtn=btn

		os.environ["HOME"]="/home/%s"%self.username
		os.environ["XAUTHORITY"]="/home/%s/.Xauthority"%self.username
		self.display,self.pid,x_pid=self.runner.new_Xephyr(self.tabBar)
		if self._launchZone(app):
			self.tab_id[self.id]['thread']=self.runner.launch(app,self.display)
			self.tab_id[self.id]['xephyr']=x_pid
			self.tab_id[self.id]['app']=app
			self.launched.append(app)
		else:
			if self.pid:
				self.runner.send_signal_to_thread("kill",self.pid)
			cursor=QtGui.QCursor(Qt.PointingHandCursor)
			self.setCursor(cursor)
			self.releaseMouse()
			return
		self.tabBar.setCurrentIndex(tabCount)
		self.focusWidgets[self.appsWidgets.index(app)].index=tabCount
		self.focusWidgets[self.appsWidgets.index(app)].statusBar.setText(str(tabCount))
		self.focusWidgets[self.appsWidgets.index(app)].statusBar.label.setStyleSheet(self.focusWidgets[self.currentBtn].cssRun)
		self.focusWidgets[self.appsWidgets.index(app)].statusBar.show(state='error')
		cursor=QtGui.QCursor(Qt.PointingHandCursor)
		self.releaseMouse()
		self.setEnabled(True)
		self.setCursor(cursor)
		self.focusWidgets[self.appsWidgets.index(app)].setEnabled(True)
	#def _launch

	def _get_tabId_from_index(self,index):
		idx=index
		self._debug("Search id for display: %s"%(index))
		self._debug("Current id: %s"%(self.tab_id))
		for key,data in self.tab_id.items():
			if 'index' in data.keys():
				if index==data['index']:
					idx=key
					break
		self._debug("Found tab idx: %s For selected index: %s"%(idx,index))
		return idx
	#def _get_tabId_from_index
	
	def _get_tabId_from_thread(self,thread):
		idx=-1
		self._debug("Search id for thread: %s"%(thread))
		self._debug("Current id: %s"%(self.tab_id))
		for key,data in self.tab_id.items():
			if 'thread' in data.keys():
				if thread==data['thread']:
					idx=key
					break
		self._debug("Found idx: %s For thread: %s"%(idx,thread))
		return idx
	#def _get_tabId_from_thread
	
	def showMessage(self,msg,status="error",height=252):
		return()
		self.statusBar.height_=height
		self.statusBar.setText(msg)
		if status:
			self.statusBar.show(state=status)
		else:
			self.statusBar.show(state=None)
	#def _show_message

	def _define_css(self):
		css="""
		#PushButton{
			padding:10px;
			margin:0px;
			border:0px;
			background-color:transparent;
		}
		#PushButton:active{
			font: 14px Roboto;
			color:black;
			background:none;
			background-color:transparent;
		}	
		#PushButton:focus{
			background:qlineargradient(x1:0,y1:0,x2:1,y2:0,stop:0 silver,stop:1 white);
			border-radius:25px;
		}
		#launcher{
			background-repeat:no-repeat;
			background-position:center;
			border:0px solid red;

		}
		#appzone{
			background-color:transparent;
			border:10px solid red;
		}
		"""
		self._debug("Setting background: %s"%self.bg)
		return(css)
Exemplo n.º 45
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()
class ScanTableWidget(QWidget):
    """
    Used for displaying information in a table.

    ===============================  =========================================
    **Signals:**
    sigScanClicked                   Emitted when the user has clicked
                                     on a row of the table and returns the
                                     current index. This index contains
                                     information about the current rows column
                                     data.

    ===============================  =========================================
    """

    sigScanClicked = pyqtSignal(QModelIndex, name="scanClicked")

    header = [
        "MS level",
        "Index",
        "RT (min)",
        "precursor m/z",
        "charge",
        "ID",
        "PeptideSeq",
        "PeptideIons",
    ]

    def __init__(self, ms_experiment, *args):
        QWidget.__init__(self, *args)
        self.ms_experiment = ms_experiment

        self.table_model = ScanTableModel(self, self.ms_experiment,
                                          self.header)
        self.table_view = QTableView()

        # register a proxy class for filering and sorting the scan table
        self.proxy = QSortFilterProxyModel(self)
        self.proxy.setSourceModel(self.table_model)

        self.table_view.sortByColumn(1, Qt.AscendingOrder)

        # setup selection model
        self.table_view.setSelectionMode(QAbstractItemView.SingleSelection)
        self.table_view.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.table_view.setModel(self.proxy)
        self.table_view.setSelectionModel(QItemSelectionModel(self.proxy))

        # header
        self.horizontalHeader = self.table_view.horizontalHeader()
        self.horizontalHeader.sectionClicked.connect(self.onHeaderClicked)

        # enable sorting
        self.table_view.setSortingEnabled(True)

        # connect signals to slots
        self.table_view.selectionModel().currentChanged.connect(
            self.onCurrentChanged)  # keyboard moves to new row
        self.horizontalHeader.sectionClicked.connect(self.onHeaderClicked)

        layout = QVBoxLayout(self)
        layout.addWidget(self.table_view)
        self.setLayout(layout)

        # hide column 7 with the PepIon data, intern information usage
        self.table_view.setColumnHidden(7, True)

        # Add rt in minutes for better TIC interaction
        self.table_view.setItemDelegateForColumn(2, RTUnitDelegate(self))
        self.table_view.setColumnWidth(2, 160)

        # default : first row selected. in OpenMSWidgets

    def onRowSelected(self, index):
        """se_comment: hard-refactoring to comply to pep8"""
        if index.siblingAtColumn(1).data() is None:
            return  # prevents crash if row gets filtered out
        self.curr_spec = self.ms_experiment.getSpectrum(
            index.siblingAtColumn(1).data())
        self.scanClicked.emit(index)

    def onCurrentChanged(self, new_index, old_index):
        self.onRowSelected(new_index)

    def onHeaderClicked(self, logicalIndex):
        if logicalIndex != 0:
            return  # allow filter on first column only for now

        self.logicalIndex = logicalIndex
        self.menuValues = QMenu(self)
        self.signalMapper = QSignalMapper(self)

        # get unique values from (unfiltered) model
        valuesUnique = set([
            self.table_model.index(row, self.logicalIndex).data()
            for row in range(
                self.table_model.rowCount(
                    self.table_model.index(-1, self.logicalIndex)))
        ])

        if len(valuesUnique) == 1:
            return  # no need to select anything

        actionAll = QAction("Show All", self)
        actionAll.triggered.connect(self.onShowAllRows)
        self.menuValues.addAction(actionAll)
        self.menuValues.addSeparator()
        """se_comment: hard-refactoring to comply to pep8"""
        l: enumerate = enumerate(sorted(list(set(valuesUnique))))
        for actionNumber, actionName in l:
            action = QAction(actionName, self)
            self.signalMapper.setMapping(action, actionNumber)
            action.triggered.connect(self.signalMapper.map)
            self.menuValues.addAction(action)

        self.signalMapper.mapped.connect(self.onSignalMapper)

        # get screen position of table header and open menu
        headerPos = self.table_view.mapToGlobal(self.horizontalHeader.pos())
        posY = headerPos.y() + self.horizontalHeader.height()
        posX = headerPos.x() + \
            self.horizontalHeader.sectionPosition(self.logicalIndex)
        self.menuValues.exec_(QPoint(posX, posY))

    def onShowAllRows(self):
        filterColumn = self.logicalIndex
        filterString = QRegExp("", Qt.CaseInsensitive, QRegExp.RegExp)

        self.proxy.setFilterRegExp(filterString)
        self.proxy.setFilterKeyColumn(filterColumn)

    def onSignalMapper(self, i):
        stringAction = self.signalMapper.mapping(i).text()
        filterColumn = self.logicalIndex
        filterString = QRegExp(stringAction, Qt.CaseSensitive,
                               QRegExp.FixedString)

        self.proxy.setFilterRegExp(filterString)
        self.proxy.setFilterKeyColumn(filterColumn)
Exemplo n.º 47
0
class FilterableTable(SQLTable):
    """a filterable Table Widget that displays content of an SQLite table;
    for individual widgets, subclass 
     and overwrite the create_model method;
    add_color_proxy should be an (INT allele_status-column, INT lab_status-column) tuple
    """
    def __init__(self, log, mydb = ": memory :", add_color_proxy = False, header_dic = None):
        super().__init__(log, mydb)
        self.add_color_proxy = add_color_proxy
        self.header_dic = header_dic
        self.create_model()
        self.fill_UI()
        self.create_filter_model()
        self.update_filterbox()
        
    def fill_UI(self):
        """sets up the layout
        """
        self.log.debug("\t- Setting up the table...")
        self.table = QTableView()
        self.table.setContextMenuPolicy(Qt.CustomContextMenu)
        self.header = self.table.horizontalHeader() # table header
        self.header.setSectionResizeMode(QHeaderView.ResizeToContents)
        self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.table.setAlternatingRowColors(True)
#         self.header.sectionClicked.connect(self.on_header_sectionClicked)
        
        mode = QAbstractItemView.SingleSelection
        self.table.setSelectionMode(mode)
        
        self.grid.addWidget(self.table, 2, 0, 10, 10)

        self.filter_lbl = QLabel("Filter:", self)
        self.grid.addWidget(self.filter_lbl, 1, 2)
        
        self.filter_entry = QLineEdit(self)
        self.grid.addWidget(self.filter_entry, 1, 3)
        self.filter_entry.textChanged.connect(self.on_filter_entry_textChanged)
        self.filter_text = ""
        
        self.filter_cb = QComboBox(self)
        self.grid.addWidget(self.filter_cb, 1, 4)
        self.filter_cb.currentIndexChanged.connect(self.on_filter_cb_IndexChanged)
        
        self.filter_btn = QPushButton("Filter!", self)
        self.grid.addWidget(self.filter_btn, 1, 5)
        self.filter_btn.clicked.connect(self.on_filter_btn_clicked)
        
        self.unfilter_btn = QPushButton("Remove Filter", self)
        self.grid.addWidget(self.unfilter_btn, 1, 6)
        self.unfilter_btn.clicked.connect(self.on_actionAll_triggered)
        
        self.log.debug("\t=> Done!")
    
    def update_filterbox(self):
        """fills the filter-combobox with the header values 
        after the model has been created and set
        """
        column_num = self.model.columnCount()
        if self.header_dic:
            columns = [self.header_dic[i] for i in self.header_dic]
        else:
            columns = [self.proxy.headerData(i, Qt.Horizontal) for i in range(column_num)]
        self.filter_cb.addItems(columns)

    def create_filter_model(self):
        """creates the filter-proxy-model on top of self.model
        """
        self.log.debug("Creating filter model...")
        self.proxy = QSortFilterProxyModel(self)
        if self.add_color_proxy:
            (allele_status_column, lab_status_column) = self.add_color_proxy
            self.log.debug("adding color filter to columns {} and {}".format(allele_status_column, lab_status_column))
            self.color_proxy = ColorProxyModel(self, allele_status_column, lab_status_column)
            self.color_proxy.setSourceModel(self.model)
            self.proxy.setSourceModel(self.color_proxy)
        else:
            self.proxy.setSourceModel(self.model)
        self.table.setSortingEnabled(True)
        self.table.setModel(self.proxy)
        
    def on_filter_cb_IndexChanged(self, index):
        """restricts RegEx filter to selected column
        """
        self.log.debug("Combobox: colum {} selected".format(index))
        self.proxy.setFilterKeyColumn(index)
    
    def on_filter_entry_textChanged(self, text):
        """stores content of filter_entry as self.text 
        """
        self.log.debug("filter text: '{}'".format(text))
        self.filter_text = text
    
    def on_filter_btn_clicked(self):
        """activates RegEx filter to current content of filter_entry and filter_cb
        """
        column = self.filter_cb.currentIndex()
        self.log.debug("Filtering column {} for '{}'".format(column, self.filter_text))
        self.proxy.setFilterKeyColumn(column)
        search = QRegExp(self.filter_text, Qt.CaseInsensitive, QRegExp.RegExp)
        self.proxy.setFilterRegExp(search)
    
    def on_header_sectionClicked(self, logicalIndex):
        """opens a dialog to choose between all unique values for this column,
        or revert to 'All'
        """
        self.log.debug("Header clicked: column {}".format(logicalIndex))
        self.logicalIndex = logicalIndex
        menuValues = QMenu(self)
        self.signalMapper = QSignalMapper(self)  
  
        self.filter_cb.setCurrentIndex(self.logicalIndex)
        self.filter_cb.blockSignals(True)
        self.proxy.setFilterKeyColumn(self.logicalIndex)
        
        valuesUnique = [str(self.model.index(row, self.logicalIndex).data())
                        for row in range(self.model.rowCount())
                        ]
        
        actionAll = QAction("All", self)
        actionAll.triggered.connect(self.on_actionAll_triggered)
        menuValues.addAction(actionAll)
        menuValues.addSeparator()
        
        for actionNumber, actionName in enumerate(sorted(list(set(valuesUnique)))):              
            action = QAction(actionName, self)
            self.signalMapper.setMapping(action, actionNumber)  
            action.triggered.connect(self.signalMapper.map)  
            menuValues.addAction(action)
  
        self.signalMapper.mapped.connect(self.on_signalMapper_mapped)  
  
        headerPos = self.table.mapToGlobal(self.header.pos())        
        posY = headerPos.y() + self.header.height()
        posX = headerPos.x() + self.header.sectionViewportPosition(self.logicalIndex)
  
        menuValues.exec_(QPoint(posX, posY))
      
    def on_actionAll_triggered(self):
        """reverts table to unfiltered state
        """
        self.log.debug("Unfiltering...")
        filterString = QRegExp("", Qt.CaseInsensitive, QRegExp.RegExp)
        self.proxy.setFilterRegExp(filterString)
        self.filter_entry.setText("")
  
    def on_signalMapper_mapped(self, i):
        """filters current column to mapping text
        """
        text = self.signalMapper.mapping(i).text()
        self.log.debug("Filtering column {} to '{}'".format(self.logicalIndex, text))
        filterString = QRegExp(text, Qt.CaseSensitive, QRegExp.FixedString)
        self.proxy.setFilterRegExp(filterString)