Exemplo n.º 1
0
	def _createStackLayout(self):
		stack = QStackedWidget()
		stack.setContentsMargins(5, 5, 5, 5)

		#Aqui criar as telas
		"""stack.addWidget(ProductsScreen())
		stack.addWidget(SalesScreen())
		stack.addWidget(ClientsScreen())
		stack.addWidget(CategoriesScreen())"""
		return stack
Exemplo n.º 2
0
class E5SideBar(QWidget):
    """
    Class implementing a sidebar with a widget area, that is hidden or shown,
    if the current tab is clicked again.
    """
    Version = 1

    North = 0
    East = 1
    South = 2
    West = 3

    def __init__(self, orientation=None, delay=200, parent=None):
        """
        Constructor
        
        @param orientation orientation of the sidebar widget (North, East,
            South, West)
        @param delay value for the expand/shrink delay in milliseconds
            (integer)
        @param parent parent widget (QWidget)
        """
        super(E5SideBar, self).__init__(parent)

        self.__tabBar = QTabBar()
        self.__tabBar.setDrawBase(True)
        self.__tabBar.setShape(QTabBar.RoundedNorth)
        self.__tabBar.setUsesScrollButtons(True)
        self.__tabBar.setDrawBase(False)
        self.__stackedWidget = QStackedWidget(self)
        self.__stackedWidget.setContentsMargins(0, 0, 0, 0)
        self.__autoHideButton = QToolButton()
        self.__autoHideButton.setCheckable(True)
        self.__autoHideButton.setIcon(
            UI.PixmapCache.getIcon("autoHideOff.png"))
        self.__autoHideButton.setChecked(True)
        self.__autoHideButton.setToolTip(
            self.tr("Deselect to activate automatic collapsing"))
        self.barLayout = QBoxLayout(QBoxLayout.LeftToRight)
        self.barLayout.setContentsMargins(0, 0, 0, 0)
        self.layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(0)
        self.barLayout.addWidget(self.__autoHideButton)
        self.barLayout.addWidget(self.__tabBar)
        self.layout.addLayout(self.barLayout)
        self.layout.addWidget(self.__stackedWidget)
        self.setLayout(self.layout)

        # initialize the delay timer
        self.__actionMethod = None
        self.__delayTimer = QTimer(self)
        self.__delayTimer.setSingleShot(True)
        self.__delayTimer.setInterval(delay)
        self.__delayTimer.timeout.connect(self.__delayedAction)

        self.__minimized = False
        self.__minSize = 0
        self.__maxSize = 0
        self.__bigSize = QSize()

        self.splitter = None
        self.splitterSizes = []

        self.__hasFocus = False
        # flag storing if this widget or any child has the focus
        self.__autoHide = False

        self.__tabBar.installEventFilter(self)

        self.__orientation = E5SideBar.North
        if orientation is None:
            orientation = E5SideBar.North
        self.setOrientation(orientation)

        self.__tabBar.currentChanged[int].connect(
            self.__stackedWidget.setCurrentIndex)
        e5App().focusChanged.connect(self.__appFocusChanged)
        self.__autoHideButton.toggled[bool].connect(self.__autoHideToggled)

    def setSplitter(self, splitter):
        """
        Public method to set the splitter managing the sidebar.
        
        @param splitter reference to the splitter (QSplitter)
        """
        self.splitter = splitter
        self.splitter.splitterMoved.connect(self.__splitterMoved)
        self.splitter.setChildrenCollapsible(False)
        index = self.splitter.indexOf(self)
        self.splitter.setCollapsible(index, False)

    def __splitterMoved(self, pos, index):
        """
        Private slot to react on splitter moves.
        
        @param pos new position of the splitter handle (integer)
        @param index index of the splitter handle (integer)
        """
        if self.splitter:
            self.splitterSizes = self.splitter.sizes()

    def __delayedAction(self):
        """
        Private slot to handle the firing of the delay timer.
        """
        if self.__actionMethod is not None:
            self.__actionMethod()

    def setDelay(self, delay):
        """
        Public method to set the delay value for the expand/shrink delay in
        milliseconds.
        
        @param delay value for the expand/shrink delay in milliseconds
            (integer)
        """
        self.__delayTimer.setInterval(delay)

    def delay(self):
        """
        Public method to get the delay value for the expand/shrink delay in
        milliseconds.
        
        @return value for the expand/shrink delay in milliseconds (integer)
        """
        return self.__delayTimer.interval()

    def __cancelDelayTimer(self):
        """
        Private method to cancel the current delay timer.
        """
        self.__delayTimer.stop()
        self.__actionMethod = None

    def shrink(self):
        """
        Public method to record a shrink request.
        """
        self.__delayTimer.stop()
        self.__actionMethod = self.__shrinkIt
        self.__delayTimer.start()

    def __shrinkIt(self):
        """
        Private method to shrink the sidebar.
        """
        self.__minimized = True
        self.__bigSize = self.size()
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
            self.__maxSize = self.maximumHeight()
        else:
            self.__minSize = self.minimumSizeHint().width()
            self.__maxSize = self.maximumWidth()
        if self.splitter:
            self.splitterSizes = self.splitter.sizes()

        self.__stackedWidget.hide()

        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.setFixedHeight(self.__tabBar.minimumSizeHint().height())
        else:
            self.setFixedWidth(self.__tabBar.minimumSizeHint().width())

        self.__actionMethod = None

    def expand(self):
        """
        Public method to record a expand request.
        """
        self.__delayTimer.stop()
        self.__actionMethod = self.__expandIt
        self.__delayTimer.start()

    def __expandIt(self):
        """
        Private method to expand the sidebar.
        """
        self.__minimized = False
        self.__stackedWidget.show()
        self.resize(self.__bigSize)
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            minSize = max(self.__minSize, self.minimumSizeHint().height())
            self.setMinimumHeight(minSize)
            self.setMaximumHeight(self.__maxSize)
        else:
            minSize = max(self.__minSize, self.minimumSizeHint().width())
            self.setMinimumWidth(minSize)
            self.setMaximumWidth(self.__maxSize)
        if self.splitter:
            self.splitter.setSizes(self.splitterSizes)

        self.__actionMethod = None

    def isMinimized(self):
        """
        Public method to check the minimized state.
        
        @return flag indicating the minimized state (boolean)
        """
        return self.__minimized

    def isAutoHiding(self):
        """
        Public method to check, if the auto hide function is active.
        
        @return flag indicating the state of auto hiding (boolean)
        """
        return self.__autoHide

    def eventFilter(self, obj, evt):
        """
        Public method to handle some events for the tabbar.
        
        @param obj reference to the object (QObject)
        @param evt reference to the event object (QEvent)
        @return flag indicating, if the event was handled (boolean)
        """
        if obj == self.__tabBar:
            if evt.type() == QEvent.MouseButtonPress:
                pos = evt.pos()
                for i in range(self.__tabBar.count()):
                    if self.__tabBar.tabRect(i).contains(pos):
                        break

                if i == self.__tabBar.currentIndex():
                    if self.isMinimized():
                        self.expand()
                    else:
                        self.shrink()
                    return True
                elif self.isMinimized():
                    self.expand()
            elif evt.type() == QEvent.Wheel:
                if qVersion() >= "5.0.0":
                    delta = evt.angleDelta().y()
                else:
                    delta = evt.delta()
                if delta > 0:
                    self.prevTab()
                else:
                    self.nextTab()
                return True

        return QWidget.eventFilter(self, obj, evt)

    def addTab(self, widget, iconOrLabel, label=None):
        """
        Public method to add a tab to the sidebar.
        
        @param widget reference to the widget to add (QWidget)
        @param iconOrLabel reference to the icon or the label text of the tab
            (QIcon, string)
        @param label the labeltext of the tab (string) (only to be
            used, if the second parameter is a QIcon)
        """
        if label:
            index = self.__tabBar.addTab(iconOrLabel, label)
            self.__tabBar.setTabToolTip(index, label)
        else:
            index = self.__tabBar.addTab(iconOrLabel)
            self.__tabBar.setTabToolTip(index, iconOrLabel)
        self.__stackedWidget.addWidget(widget)
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()

    def insertTab(self, index, widget, iconOrLabel, label=None):
        """
        Public method to insert a tab into the sidebar.
        
        @param index the index to insert the tab at (integer)
        @param widget reference to the widget to insert (QWidget)
        @param iconOrLabel reference to the icon or the labeltext of the tab
            (QIcon, string)
        @param label the labeltext of the tab (string) (only to be
            used, if the second parameter is a QIcon)
        """
        if label:
            index = self.__tabBar.insertTab(index, iconOrLabel, label)
            self.__tabBar.setTabToolTip(index, label)
        else:
            index = self.__tabBar.insertTab(index, iconOrLabel)
            self.__tabBar.setTabToolTip(index, iconOrLabel)
        self.__stackedWidget.insertWidget(index, widget)
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()

    def removeTab(self, index):
        """
        Public method to remove a tab.
        
        @param index the index of the tab to remove (integer)
        """
        self.__stackedWidget.removeWidget(self.__stackedWidget.widget(index))
        self.__tabBar.removeTab(index)
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()

    def clear(self):
        """
        Public method to remove all tabs.
        """
        while self.count() > 0:
            self.removeTab(0)

    def prevTab(self):
        """
        Public slot used to show the previous tab.
        """
        ind = self.currentIndex() - 1
        if ind == -1:
            ind = self.count() - 1

        self.setCurrentIndex(ind)
        self.currentWidget().setFocus()

    def nextTab(self):
        """
        Public slot used to show the next tab.
        """
        ind = self.currentIndex() + 1
        if ind == self.count():
            ind = 0

        self.setCurrentIndex(ind)
        self.currentWidget().setFocus()

    def count(self):
        """
        Public method to get the number of tabs.
        
        @return number of tabs in the sidebar (integer)
        """
        return self.__tabBar.count()

    def currentIndex(self):
        """
        Public method to get the index of the current tab.
        
        @return index of the current tab (integer)
        """
        return self.__stackedWidget.currentIndex()

    def setCurrentIndex(self, index):
        """
        Public slot to set the current index.
        
        @param index the index to set as the current index (integer)
        """
        self.__tabBar.setCurrentIndex(index)
        self.__stackedWidget.setCurrentIndex(index)
        if self.isMinimized():
            self.expand()

    def currentWidget(self):
        """
        Public method to get a reference to the current widget.
        
        @return reference to the current widget (QWidget)
        """
        return self.__stackedWidget.currentWidget()

    def setCurrentWidget(self, widget):
        """
        Public slot to set the current widget.
        
        @param widget reference to the widget to become the current widget
            (QWidget)
        """
        self.__stackedWidget.setCurrentWidget(widget)
        self.__tabBar.setCurrentIndex(self.__stackedWidget.currentIndex())
        if self.isMinimized():
            self.expand()

    def indexOf(self, widget):
        """
        Public method to get the index of the given widget.
        
        @param widget reference to the widget to get the index of (QWidget)
        @return index of the given widget (integer)
        """
        return self.__stackedWidget.indexOf(widget)

    def isTabEnabled(self, index):
        """
        Public method to check, if a tab is enabled.
        
        @param index index of the tab to check (integer)
        @return flag indicating the enabled state (boolean)
        """
        return self.__tabBar.isTabEnabled(index)

    def setTabEnabled(self, index, enabled):
        """
        Public method to set the enabled state of a tab.
        
        @param index index of the tab to set (integer)
        @param enabled enabled state to set (boolean)
        """
        self.__tabBar.setTabEnabled(index, enabled)

    def orientation(self):
        """
        Public method to get the orientation of the sidebar.
        
        @return orientation of the sidebar (North, East, South, West)
        """
        return self.__orientation

    def setOrientation(self, orient):
        """
        Public method to set the orientation of the sidebar.

        @param orient orientation of the sidebar (North, East, South, West)
        """
        if orient == E5SideBar.North:
            self.__tabBar.setShape(QTabBar.RoundedNorth)
            self.__tabBar.setSizePolicy(QSizePolicy.Expanding,
                                        QSizePolicy.Preferred)
            self.barLayout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setAlignment(self.barLayout, Qt.AlignLeft)
        elif orient == E5SideBar.East:
            self.__tabBar.setShape(QTabBar.RoundedEast)
            self.__tabBar.setSizePolicy(QSizePolicy.Preferred,
                                        QSizePolicy.Expanding)
            self.barLayout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setDirection(QBoxLayout.RightToLeft)
            self.layout.setAlignment(self.barLayout, Qt.AlignTop)
        elif orient == E5SideBar.South:
            self.__tabBar.setShape(QTabBar.RoundedSouth)
            self.__tabBar.setSizePolicy(QSizePolicy.Expanding,
                                        QSizePolicy.Preferred)
            self.barLayout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setDirection(QBoxLayout.BottomToTop)
            self.layout.setAlignment(self.barLayout, Qt.AlignLeft)
        elif orient == E5SideBar.West:
            self.__tabBar.setShape(QTabBar.RoundedWest)
            self.__tabBar.setSizePolicy(QSizePolicy.Preferred,
                                        QSizePolicy.Expanding)
            self.barLayout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setAlignment(self.barLayout, Qt.AlignTop)
        self.__orientation = orient

    def tabIcon(self, index):
        """
        Public method to get the icon of a tab.
        
        @param index index of the tab (integer)
        @return icon of the tab (QIcon)
        """
        return self.__tabBar.tabIcon(index)

    def setTabIcon(self, index, icon):
        """
        Public method to set the icon of a tab.
        
        @param index index of the tab (integer)
        @param icon icon to be set (QIcon)
        """
        self.__tabBar.setTabIcon(index, icon)

    def tabText(self, index):
        """
        Public method to get the text of a tab.
        
        @param index index of the tab (integer)
        @return text of the tab (string)
        """
        return self.__tabBar.tabText(index)

    def setTabText(self, index, text):
        """
        Public method to set the text of a tab.
        
        @param index index of the tab (integer)
        @param text text to set (string)
        """
        self.__tabBar.setTabText(index, text)

    def tabToolTip(self, index):
        """
        Public method to get the tooltip text of a tab.
        
        @param index index of the tab (integer)
        @return tooltip text of the tab (string)
        """
        return self.__tabBar.tabToolTip(index)

    def setTabToolTip(self, index, tip):
        """
        Public method to set the tooltip text of a tab.
        
        @param index index of the tab (integer)
        @param tip tooltip text to set (string)
        """
        self.__tabBar.setTabToolTip(index, tip)

    def tabWhatsThis(self, index):
        """
        Public method to get the WhatsThis text of a tab.
        
        @param index index of the tab (integer)
        @return WhatsThis text of the tab (string)
        """
        return self.__tabBar.tabWhatsThis(index)

    def setTabWhatsThis(self, index, text):
        """
        Public method to set the WhatsThis text of a tab.
        
        @param index index of the tab (integer)
        @param text WhatsThis text to set (string)
        """
        self.__tabBar.setTabWhatsThis(index, text)

    def widget(self, index):
        """
        Public method to get a reference to the widget associated with a tab.
        
        @param index index of the tab (integer)
        @return reference to the widget (QWidget)
        """
        return self.__stackedWidget.widget(index)

    def saveState(self):
        """
        Public method to save the state of the sidebar.
        
        @return saved state as a byte array (QByteArray)
        """
        if len(self.splitterSizes) == 0:
            if self.splitter:
                self.splitterSizes = self.splitter.sizes()
            self.__bigSize = self.size()
            if self.__orientation in [E5SideBar.North, E5SideBar.South]:
                self.__minSize = self.minimumSizeHint().height()
                self.__maxSize = self.maximumHeight()
            else:
                self.__minSize = self.minimumSizeHint().width()
                self.__maxSize = self.maximumWidth()

        data = QByteArray()
        stream = QDataStream(data, QIODevice.WriteOnly)
        stream.setVersion(QDataStream.Qt_4_6)

        stream.writeUInt16(self.Version)
        stream.writeBool(self.__minimized)
        stream << self.__bigSize
        stream.writeUInt16(self.__minSize)
        stream.writeUInt16(self.__maxSize)
        stream.writeUInt16(len(self.splitterSizes))
        for size in self.splitterSizes:
            stream.writeUInt16(size)
        stream.writeBool(self.__autoHide)

        return data

    def restoreState(self, state):
        """
        Public method to restore the state of the sidebar.
        
        @param state byte array containing the saved state (QByteArray)
        @return flag indicating success (boolean)
        """
        if state.isEmpty():
            return False

        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            minSize = self.layout.minimumSize().height()
            maxSize = self.maximumHeight()
        else:
            minSize = self.layout.minimumSize().width()
            maxSize = self.maximumWidth()

        data = QByteArray(state)
        stream = QDataStream(data, QIODevice.ReadOnly)
        stream.setVersion(QDataStream.Qt_4_6)
        stream.readUInt16()  # version
        minimized = stream.readBool()

        if minimized and not self.__minimized:
            self.shrink()

        stream >> self.__bigSize
        self.__minSize = max(stream.readUInt16(), minSize)
        self.__maxSize = max(stream.readUInt16(), maxSize)
        count = stream.readUInt16()
        self.splitterSizes = []
        for i in range(count):
            self.splitterSizes.append(stream.readUInt16())

        self.__autoHide = stream.readBool()
        self.__autoHideButton.setChecked(not self.__autoHide)

        if not minimized:
            self.expand()

        return True

    #######################################################################
    ## methods below implement the autohide functionality
    #######################################################################

    def __autoHideToggled(self, checked):
        """
        Private slot to handle the toggling of the autohide button.
        
        @param checked flag indicating the checked state of the button
            (boolean)
        """
        self.__autoHide = not checked
        if self.__autoHide:
            self.__autoHideButton.setIcon(
                UI.PixmapCache.getIcon("autoHideOn.png"))
        else:
            self.__autoHideButton.setIcon(
                UI.PixmapCache.getIcon("autoHideOff.png"))

    def __appFocusChanged(self, old, now):
        """
        Private slot to handle a change of the focus.
        
        @param old reference to the widget, that lost focus (QWidget or None)
        @param now reference to the widget having the focus (QWidget or None)
        """
        self.__hasFocus = self.isAncestorOf(now)
        if self.__autoHide and not self.__hasFocus and not self.isMinimized():
            self.shrink()
        elif self.__autoHide and self.__hasFocus and self.isMinimized():
            self.expand()

    def enterEvent(self, event):
        """
        Protected method to handle the mouse entering this widget.
        
        @param event reference to the event (QEvent)
        """
        if self.__autoHide and self.isMinimized():
            self.expand()
        else:
            self.__cancelDelayTimer()

    def leaveEvent(self, event):
        """
        Protected method to handle the mouse leaving this widget.
        
        @param event reference to the event (QEvent)
        """
        if self.__autoHide and not self.__hasFocus and not self.isMinimized():
            self.shrink()
        else:
            self.__cancelDelayTimer()

    def shutdown(self):
        """
        Public method to shut down the object.
        
        This method does some preparations so the object can be deleted
        properly. It disconnects from the focusChanged signal in order to
        avoid trouble later on.
        """
        e5App().focusChanged.disconnect(self.__appFocusChanged)
Exemplo n.º 3
0
class MachineLearning(QMainWindow):

    populateGallery = pyqtSignal()
    closedWindow = pyqtSignal(object)
    key_pressed = pyqtSignal(object)

    def __init__(self, parent=None):

        super(MachineLearning, self).__init__()
        self.setWindowTitle('Machine Learning')
        self.parent = parent
        self.configure_gui()
        self.create_menu()
        self.create_widgets()
        self.showMaximized()

    def configure_gui(self):

        self.stack = QStackedWidget(self)
        self.setCentralWidget(self.stack)
        self.stack.setContentsMargins(0, 0, 5, 0)

    def create_widgets(self):

        self.design = Design(self)
        self.dataset = Dataset(self)
        self.train = Train(self)

        self.stack.addWidget(self.design)
        self.stack.addWidget(self.dataset)
        self.stack.addWidget(self.train)

        self.statusbar = QStatusBar(self)
        self.setStatusBar(self.statusbar)
        self.statusbar.setFixedHeight(25)

    def create_menu(self):

        self.menubar = self.menuBar()
        self.tooblar = self.addToolBar('')

        # File
        file = self.menubar.addMenu('File')
        self.create_action(file, ['New...', 'Create new project'],
                           self.menuPressEvent, 'Ctrl+N')
        self.create_action(file, ['Open...', 'Load project from file'],
                           self.menuPressEvent, 'Ctrl+O')
        file.addMenu('Open recent')
        file.addSeparator()
        self.create_action(file, ['Save', 'Save project to file'],
                           self.menuPressEvent, 'Ctrl+S')
        file.addAction('Save As...',
                       self.menuPressEvent,
                       shortcut='Ctrl+Shift+S')
        file.addAction('Save a Copy...', self.menuPressEvent)
        file.addAction('Save Selection...', self.menuPressEvent)
        file.addAction('Export...', self.menuPressEvent)
        file.addSeparator()
        file.addAction('Close', self.menuPressEvent, shortcut='Ctrl+F4')
        file.addSeparator()
        file.addAction('Properties', self.menuPressEvent)
        file.addSeparator()
        file.addAction('Quit', self.menuPressEvent, shortcut='Ctrl+Q')

        # Edit
        edit = self.menubar.addMenu('Edit')
        edit.addAction('Undo', self.menuPressEvent, shortcut='Ctrl+Z')
        edit.addAction('Redo', self.menuPressEvent, shortcut='Ctrl+Y')
        edit.addSeparator()
        edit.addAction('Cut', self.menuPressEvent, shortcut='Ctrl+X')
        edit.addAction('Copy', self.menuPressEvent, shortcut='Ctrl+C')
        edit.addAction('Paste', self.menuPressEvent, shortcut='Ctrl+V')
        edit.addAction('Delete', self.menuPressEvent, shortcut='Del')
        edit.addSeparator()
        edit.addAction('Select All', self.menuPressEvent, shortcut='Ctrl+A')
        edit.addAction('Find', self.menuPressEvent, shortcut='Ctrl+F')
        edit.addSeparator()
        edit.addAction('Preferences', self.menuPressEvent)

        # View
        view = self.menubar.addMenu('View')
        view.addAction('Palettes', self.menuPressEvent, shortcut='F9')
        view.addAction('Inspector', self.menuPressEvent, shortcut='F8')
        view.addSeparator()
        view.addAction('Zoom in', self.menuPressEvent, shortcut='Ctrl++')
        view.addAction('Zoom out', self.menuPressEvent, shortcut='Ctrl+-')
        view.addSeparator()
        view.addAction('Fulscreen', self.menuPressEvent, shortcut='F11')

        # Layer
        layer = self.menubar.addMenu('Layer')

        # Tools
        tools = self.menubar.addMenu('Tools')

        # Help
        help = self.menubar.addMenu('Help')

    def create_action(self, menu, text, slot, shortcut):

        if isinstance(text, list):
            menu.addAction(text[0], self.menuPressEvent, shortcut=shortcut)
            self.tooblar.addAction(
                QAction(QIcon.fromTheme('new.bmp'), f'{text[1]} ({shortcut})',
                        self))
        else:
            menu.addAction(text, self.menuPressEvent, shortcut=shortcut)

    def menuPressEvent(self, event=None):

        action = event.text()
        print(action)

    def keyPressEvent(self, event):

        key_press = event.key()

        if key_press == Qt.Key_Escape: self.close()

        else: self.key_pressed.emit(event)

    def closeEvent(self, event):

        self.closedWindow.emit(self)
Exemplo n.º 4
0
class E5SideBar(QWidget):
    """
    Class implementing a sidebar with a widget area, that is hidden or shown,
    if the current tab is clicked again.
    """
    Version = 1
    
    North = 0
    East = 1
    South = 2
    West = 3
    
    def __init__(self, orientation=None, delay=200, parent=None):
        """
        Constructor
        
        @param orientation orientation of the sidebar widget (North, East,
            South, West)
        @param delay value for the expand/shrink delay in milliseconds
            (integer)
        @param parent parent widget (QWidget)
        """
        super(E5SideBar, self).__init__(parent)
        
        self.__tabBar = QTabBar()
        self.__tabBar.setDrawBase(True)
        self.__tabBar.setShape(QTabBar.RoundedNorth)
        self.__tabBar.setUsesScrollButtons(True)
        self.__tabBar.setDrawBase(False)
        self.__stackedWidget = QStackedWidget(self)
        self.__stackedWidget.setContentsMargins(0, 0, 0, 0)
        self.__autoHideButton = QToolButton()
        self.__autoHideButton.setCheckable(True)
        self.__autoHideButton.setIcon(
            UI.PixmapCache.getIcon("autoHideOff.png"))
        self.__autoHideButton.setChecked(True)
        self.__autoHideButton.setToolTip(
            self.tr("Deselect to activate automatic collapsing"))
        self.barLayout = QBoxLayout(QBoxLayout.LeftToRight)
        self.barLayout.setContentsMargins(0, 0, 0, 0)
        self.layout = QBoxLayout(QBoxLayout.TopToBottom)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(0)
        self.barLayout.addWidget(self.__autoHideButton)
        self.barLayout.addWidget(self.__tabBar)
        self.layout.addLayout(self.barLayout)
        self.layout.addWidget(self.__stackedWidget)
        self.setLayout(self.layout)
        
        # initialize the delay timer
        self.__actionMethod = None
        self.__delayTimer = QTimer(self)
        self.__delayTimer.setSingleShot(True)
        self.__delayTimer.setInterval(delay)
        self.__delayTimer.timeout.connect(self.__delayedAction)
        
        self.__minimized = False
        self.__minSize = 0
        self.__maxSize = 0
        self.__bigSize = QSize()
        
        self.splitter = None
        self.splitterSizes = []
        
        self.__hasFocus = False
        # flag storing if this widget or any child has the focus
        self.__autoHide = False
        
        self.__tabBar.installEventFilter(self)
        
        self.__orientation = E5SideBar.North
        if orientation is None:
            orientation = E5SideBar.North
        self.setOrientation(orientation)
        
        self.__tabBar.currentChanged[int].connect(
            self.__stackedWidget.setCurrentIndex)
        e5App().focusChanged[QWidget, QWidget].connect(self.__appFocusChanged)
        self.__autoHideButton.toggled[bool].connect(self.__autoHideToggled)
    
    def setSplitter(self, splitter):
        """
        Public method to set the splitter managing the sidebar.
        
        @param splitter reference to the splitter (QSplitter)
        """
        self.splitter = splitter
        self.splitter.splitterMoved.connect(self.__splitterMoved)
        self.splitter.setChildrenCollapsible(False)
        index = self.splitter.indexOf(self)
        self.splitter.setCollapsible(index, False)
    
    def __splitterMoved(self, pos, index):
        """
        Private slot to react on splitter moves.
        
        @param pos new position of the splitter handle (integer)
        @param index index of the splitter handle (integer)
        """
        if self.splitter:
            self.splitterSizes = self.splitter.sizes()
    
    def __delayedAction(self):
        """
        Private slot to handle the firing of the delay timer.
        """
        if self.__actionMethod is not None:
            self.__actionMethod()
    
    def setDelay(self, delay):
        """
        Public method to set the delay value for the expand/shrink delay in
        milliseconds.
        
        @param delay value for the expand/shrink delay in milliseconds
            (integer)
        """
        self.__delayTimer.setInterval(delay)
    
    def delay(self):
        """
        Public method to get the delay value for the expand/shrink delay in
        milliseconds.
        
        @return value for the expand/shrink delay in milliseconds (integer)
        """
        return self.__delayTimer.interval()
    
    def __cancelDelayTimer(self):
        """
        Private method to cancel the current delay timer.
        """
        self.__delayTimer.stop()
        self.__actionMethod = None
    
    def shrink(self):
        """
        Public method to record a shrink request.
        """
        self.__delayTimer.stop()
        self.__actionMethod = self.__shrinkIt
        self.__delayTimer.start()
   
    def __shrinkIt(self):
        """
        Private method to shrink the sidebar.
        """
        self.__minimized = True
        self.__bigSize = self.size()
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
            self.__maxSize = self.maximumHeight()
        else:
            self.__minSize = self.minimumSizeHint().width()
            self.__maxSize = self.maximumWidth()
        if self.splitter:
            self.splitterSizes = self.splitter.sizes()
        
        self.__stackedWidget.hide()
        
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.setFixedHeight(self.__tabBar.minimumSizeHint().height())
        else:
            self.setFixedWidth(self.__tabBar.minimumSizeHint().width())
        
        self.__actionMethod = None
    
    def expand(self):
        """
        Public method to record a expand request.
        """
        self.__delayTimer.stop()
        self.__actionMethod = self.__expandIt
        self.__delayTimer.start()
    
    def __expandIt(self):
        """
        Private method to expand the sidebar.
        """
        self.__minimized = False
        self.__stackedWidget.show()
        self.resize(self.__bigSize)
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            minSize = max(self.__minSize, self.minimumSizeHint().height())
            self.setMinimumHeight(minSize)
            self.setMaximumHeight(self.__maxSize)
        else:
            minSize = max(self.__minSize, self.minimumSizeHint().width())
            self.setMinimumWidth(minSize)
            self.setMaximumWidth(self.__maxSize)
        if self.splitter:
            self.splitter.setSizes(self.splitterSizes)
        
        self.__actionMethod = None
    
    def isMinimized(self):
        """
        Public method to check the minimized state.
        
        @return flag indicating the minimized state (boolean)
        """
        return self.__minimized
    
    def isAutoHiding(self):
        """
        Public method to check, if the auto hide function is active.
        
        @return flag indicating the state of auto hiding (boolean)
        """
        return self.__autoHide
    
    def eventFilter(self, obj, evt):
        """
        Public method to handle some events for the tabbar.
        
        @param obj reference to the object (QObject)
        @param evt reference to the event object (QEvent)
        @return flag indicating, if the event was handled (boolean)
        """
        if obj == self.__tabBar:
            if evt.type() == QEvent.MouseButtonPress:
                pos = evt.pos()
                for i in range(self.__tabBar.count()):
                    if self.__tabBar.tabRect(i).contains(pos):
                        break
                
                if i == self.__tabBar.currentIndex():
                    if self.isMinimized():
                        self.expand()
                    else:
                        self.shrink()
                    return True
                elif self.isMinimized():
                    self.expand()
            elif evt.type() == QEvent.Wheel:
                if qVersion() >= "5.0.0":
                    delta = evt.angleDelta().y()
                else:
                    delta = evt.delta()
                if delta > 0:
                    self.prevTab()
                else:
                    self.nextTab()
                return True
        
        return QWidget.eventFilter(self, obj, evt)
    
    def addTab(self, widget, iconOrLabel, label=None):
        """
        Public method to add a tab to the sidebar.
        
        @param widget reference to the widget to add (QWidget)
        @param iconOrLabel reference to the icon or the label text of the tab
            (QIcon, string)
        @param label the labeltext of the tab (string) (only to be
            used, if the second parameter is a QIcon)
        """
        if label:
            index = self.__tabBar.addTab(iconOrLabel, label)
            self.__tabBar.setTabToolTip(index, label)
        else:
            index = self.__tabBar.addTab(iconOrLabel)
            self.__tabBar.setTabToolTip(index, iconOrLabel)
        self.__stackedWidget.addWidget(widget)
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()
    
    def insertTab(self, index, widget, iconOrLabel, label=None):
        """
        Public method to insert a tab into the sidebar.
        
        @param index the index to insert the tab at (integer)
        @param widget reference to the widget to insert (QWidget)
        @param iconOrLabel reference to the icon or the labeltext of the tab
            (QIcon, string)
        @param label the labeltext of the tab (string) (only to be
            used, if the second parameter is a QIcon)
        """
        if label:
            index = self.__tabBar.insertTab(index, iconOrLabel, label)
            self.__tabBar.setTabToolTip(index, label)
        else:
            index = self.__tabBar.insertTab(index, iconOrLabel)
            self.__tabBar.setTabToolTip(index, iconOrLabel)
        self.__stackedWidget.insertWidget(index, widget)
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()
    
    def removeTab(self, index):
        """
        Public method to remove a tab.
        
        @param index the index of the tab to remove (integer)
        """
        self.__stackedWidget.removeWidget(self.__stackedWidget.widget(index))
        self.__tabBar.removeTab(index)
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            self.__minSize = self.minimumSizeHint().height()
        else:
            self.__minSize = self.minimumSizeHint().width()
    
    def clear(self):
        """
        Public method to remove all tabs.
        """
        while self.count() > 0:
            self.removeTab(0)
    
    def prevTab(self):
        """
        Public slot used to show the previous tab.
        """
        ind = self.currentIndex() - 1
        if ind == -1:
            ind = self.count() - 1
            
        self.setCurrentIndex(ind)
        self.currentWidget().setFocus()
    
    def nextTab(self):
        """
        Public slot used to show the next tab.
        """
        ind = self.currentIndex() + 1
        if ind == self.count():
            ind = 0
            
        self.setCurrentIndex(ind)
        self.currentWidget().setFocus()
    
    def count(self):
        """
        Public method to get the number of tabs.
        
        @return number of tabs in the sidebar (integer)
        """
        return self.__tabBar.count()
    
    def currentIndex(self):
        """
        Public method to get the index of the current tab.
        
        @return index of the current tab (integer)
        """
        return self.__stackedWidget.currentIndex()
    
    def setCurrentIndex(self, index):
        """
        Public slot to set the current index.
        
        @param index the index to set as the current index (integer)
        """
        self.__tabBar.setCurrentIndex(index)
        self.__stackedWidget.setCurrentIndex(index)
        if self.isMinimized():
            self.expand()
    
    def currentWidget(self):
        """
        Public method to get a reference to the current widget.
        
        @return reference to the current widget (QWidget)
        """
        return self.__stackedWidget.currentWidget()
    
    def setCurrentWidget(self, widget):
        """
        Public slot to set the current widget.
        
        @param widget reference to the widget to become the current widget
            (QWidget)
        """
        self.__stackedWidget.setCurrentWidget(widget)
        self.__tabBar.setCurrentIndex(self.__stackedWidget.currentIndex())
        if self.isMinimized():
            self.expand()
    
    def indexOf(self, widget):
        """
        Public method to get the index of the given widget.
        
        @param widget reference to the widget to get the index of (QWidget)
        @return index of the given widget (integer)
        """
        return self.__stackedWidget.indexOf(widget)
    
    def isTabEnabled(self, index):
        """
        Public method to check, if a tab is enabled.
        
        @param index index of the tab to check (integer)
        @return flag indicating the enabled state (boolean)
        """
        return self.__tabBar.isTabEnabled(index)
    
    def setTabEnabled(self, index, enabled):
        """
        Public method to set the enabled state of a tab.
        
        @param index index of the tab to set (integer)
        @param enabled enabled state to set (boolean)
        """
        self.__tabBar.setTabEnabled(index, enabled)
    
    def orientation(self):
        """
        Public method to get the orientation of the sidebar.
        
        @return orientation of the sidebar (North, East, South, West)
        """
        return self.__orientation
    
    def setOrientation(self, orient):
        """
        Public method to set the orientation of the sidebar.

        @param orient orientation of the sidebar (North, East, South, West)
        """
        if orient == E5SideBar.North:
            self.__tabBar.setShape(QTabBar.RoundedNorth)
            self.__tabBar.setSizePolicy(
                QSizePolicy.Expanding, QSizePolicy.Preferred)
            self.barLayout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setAlignment(self.barLayout, Qt.AlignLeft)
        elif orient == E5SideBar.East:
            self.__tabBar.setShape(QTabBar.RoundedEast)
            self.__tabBar.setSizePolicy(
                QSizePolicy.Preferred, QSizePolicy.Expanding)
            self.barLayout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setDirection(QBoxLayout.RightToLeft)
            self.layout.setAlignment(self.barLayout, Qt.AlignTop)
        elif orient == E5SideBar.South:
            self.__tabBar.setShape(QTabBar.RoundedSouth)
            self.__tabBar.setSizePolicy(
                QSizePolicy.Expanding, QSizePolicy.Preferred)
            self.barLayout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setDirection(QBoxLayout.BottomToTop)
            self.layout.setAlignment(self.barLayout, Qt.AlignLeft)
        elif orient == E5SideBar.West:
            self.__tabBar.setShape(QTabBar.RoundedWest)
            self.__tabBar.setSizePolicy(
                QSizePolicy.Preferred, QSizePolicy.Expanding)
            self.barLayout.setDirection(QBoxLayout.TopToBottom)
            self.layout.setDirection(QBoxLayout.LeftToRight)
            self.layout.setAlignment(self.barLayout, Qt.AlignTop)
        self.__orientation = orient
    
    def tabIcon(self, index):
        """
        Public method to get the icon of a tab.
        
        @param index index of the tab (integer)
        @return icon of the tab (QIcon)
        """
        return self.__tabBar.tabIcon(index)
    
    def setTabIcon(self, index, icon):
        """
        Public method to set the icon of a tab.
        
        @param index index of the tab (integer)
        @param icon icon to be set (QIcon)
        """
        self.__tabBar.setTabIcon(index, icon)
    
    def tabText(self, index):
        """
        Public method to get the text of a tab.
        
        @param index index of the tab (integer)
        @return text of the tab (string)
        """
        return self.__tabBar.tabText(index)
    
    def setTabText(self, index, text):
        """
        Public method to set the text of a tab.
        
        @param index index of the tab (integer)
        @param text text to set (string)
        """
        self.__tabBar.setTabText(index, text)
    
    def tabToolTip(self, index):
        """
        Public method to get the tooltip text of a tab.
        
        @param index index of the tab (integer)
        @return tooltip text of the tab (string)
        """
        return self.__tabBar.tabToolTip(index)
    
    def setTabToolTip(self, index, tip):
        """
        Public method to set the tooltip text of a tab.
        
        @param index index of the tab (integer)
        @param tip tooltip text to set (string)
        """
        self.__tabBar.setTabToolTip(index, tip)
    
    def tabWhatsThis(self, index):
        """
        Public method to get the WhatsThis text of a tab.
        
        @param index index of the tab (integer)
        @return WhatsThis text of the tab (string)
        """
        return self.__tabBar.tabWhatsThis(index)
    
    def setTabWhatsThis(self, index, text):
        """
        Public method to set the WhatsThis text of a tab.
        
        @param index index of the tab (integer)
        @param text WhatsThis text to set (string)
        """
        self.__tabBar.setTabWhatsThis(index, text)
    
    def widget(self, index):
        """
        Public method to get a reference to the widget associated with a tab.
        
        @param index index of the tab (integer)
        @return reference to the widget (QWidget)
        """
        return self.__stackedWidget.widget(index)
    
    def saveState(self):
        """
        Public method to save the state of the sidebar.
        
        @return saved state as a byte array (QByteArray)
        """
        if len(self.splitterSizes) == 0:
            if self.splitter:
                self.splitterSizes = self.splitter.sizes()
            self.__bigSize = self.size()
            if self.__orientation in [E5SideBar.North, E5SideBar.South]:
                self.__minSize = self.minimumSizeHint().height()
                self.__maxSize = self.maximumHeight()
            else:
                self.__minSize = self.minimumSizeHint().width()
                self.__maxSize = self.maximumWidth()
        
        data = QByteArray()
        stream = QDataStream(data, QIODevice.WriteOnly)
        stream.setVersion(QDataStream.Qt_4_6)
        
        stream.writeUInt16(self.Version)
        stream.writeBool(self.__minimized)
        stream << self.__bigSize
        stream.writeUInt16(self.__minSize)
        stream.writeUInt16(self.__maxSize)
        stream.writeUInt16(len(self.splitterSizes))
        for size in self.splitterSizes:
            stream.writeUInt16(size)
        stream.writeBool(self.__autoHide)
        
        return data
    
    def restoreState(self, state):
        """
        Public method to restore the state of the sidebar.
        
        @param state byte array containing the saved state (QByteArray)
        @return flag indicating success (boolean)
        """
        if state.isEmpty():
            return False
        
        if self.__orientation in [E5SideBar.North, E5SideBar.South]:
            minSize = self.layout.minimumSize().height()
            maxSize = self.maximumHeight()
        else:
            minSize = self.layout.minimumSize().width()
            maxSize = self.maximumWidth()
        
        data = QByteArray(state)
        stream = QDataStream(data, QIODevice.ReadOnly)
        stream.setVersion(QDataStream.Qt_4_6)
        stream.readUInt16()  # version
        minimized = stream.readBool()
        
        if minimized and not self.__minimized:
            self.shrink()
        
        stream >> self.__bigSize
        self.__minSize = max(stream.readUInt16(), minSize)
        self.__maxSize = max(stream.readUInt16(), maxSize)
        count = stream.readUInt16()
        self.splitterSizes = []
        for i in range(count):
            self.splitterSizes.append(stream.readUInt16())
        
        self.__autoHide = stream.readBool()
        self.__autoHideButton.setChecked(not self.__autoHide)
        
        if not minimized:
            self.expand()
        
        return True
    
    #######################################################################
    ## methods below implement the autohide functionality
    #######################################################################
    
    def __autoHideToggled(self, checked):
        """
        Private slot to handle the toggling of the autohide button.
        
        @param checked flag indicating the checked state of the button
            (boolean)
        """
        self.__autoHide = not checked
        if self.__autoHide:
            self.__autoHideButton.setIcon(
                UI.PixmapCache.getIcon("autoHideOn.png"))
        else:
            self.__autoHideButton.setIcon(
                UI.PixmapCache.getIcon("autoHideOff.png"))
    
    def __appFocusChanged(self, old, now):
        """
        Private slot to handle a change of the focus.
        
        @param old reference to the widget, that lost focus (QWidget or None)
        @param now reference to the widget having the focus (QWidget or None)
        """
        self.__hasFocus = self.isAncestorOf(now)
        if self.__autoHide and not self.__hasFocus and not self.isMinimized():
            self.shrink()
        elif self.__autoHide and self.__hasFocus and self.isMinimized():
            self.expand()
    
    def enterEvent(self, event):
        """
        Protected method to handle the mouse entering this widget.
        
        @param event reference to the event (QEvent)
        """
        if self.__autoHide and self.isMinimized():
            self.expand()
        else:
            self.__cancelDelayTimer()
    
    def leaveEvent(self, event):
        """
        Protected method to handle the mouse leaving this widget.
        
        @param event reference to the event (QEvent)
        """
        if self.__autoHide and not self.__hasFocus and not self.isMinimized():
            self.shrink()
        else:
            self.__cancelDelayTimer()
    
    def shutdown(self):
        """
        Public method to shut down the object.
        
        This method does some preparations so the object can be deleted
        properly. It disconnects from the focusChanged signal in order to
        avoid trouble later on.
        """
        e5App().focusChanged[QWidget, QWidget].disconnect(
            self.__appFocusChanged)
Exemplo n.º 5
0
class TwilightInput(QWidget):
    """
    Twilight parameter input for GUI. Contains twilight data types in a ComboBox and corresponding selector
    on whether to scrape, import, or ignore as well as necessary parameter input for scraping (unused if not).
    """
    def __init__(self):
        super().__init__()

        self.__init_twilight()
        self.__init_design()

    def __init_twilight(self):
        self.layout = QVBoxLayout()

        self.__types_options()
        self.__scrape()

        self.layout.addWidget(self.combo_box, 0)
        self.layout.addWidget(self.types_options, 0)
        self.layout.addWidget(self.scrape, 0)

        self.layout.addWidget(
            QLabel(
                "Powered by <a href=\"http://aa.usno.navy.mil/index.php\">USNO"
                " Astronomical Applications</a>"))

        self.setLayout(self.layout)

    def __types_options(self):
        self.types = ["Sun", "Moon", "Nautical", "Civil", "Astronomical"]
        self.__types_combo_box()  # creates the combo box
        self.__types_revolver()  # creates the rotating window
        self.combo_box.setCurrentIndex(
            0
        )  # need to reset because revolver creation ends on last combo box entry
        self.combo_box.currentIndexChanged.connect(
            lambda index: self.combo_switch_window(index))
        # can only link after everything is initialized

    def __types_combo_box(self):
        self.combo_box = QComboBox()
        for name in self.types:
            self.combo_box.addItem(name)

    def __types_revolver(self):
        self.types_options = QStackedWidget()
        self.buttons = {}  # browse buttons for each type
        self.button_groups = {
        }  # dict to store {type name: corresponding button group}
        self.button_reverse_lookup = {
        }  # reverse lookup any radio button to its label
        self.abbreviated_paths = {
        }  # QLineEdit that shows just the file name because no space
        self.full_import_paths = {
        }  # MutableString stores full path for retrieval later (not seen on GUI)

        for name in self.types:
            row = self.__types_block(name)
            self.types_options.addWidget(row[0])
            self.buttons[name] = row[1]
            self.button_groups[name] = row[2]
            for radio in row[2].buttons():
                self.button_reverse_lookup[radio] = name
            self.abbreviated_paths[name] = row[3]
            self.full_import_paths[name] = row[4]

    def __types_block(self, name):
        """
        Makes block for twilight data selection:
            [radio buttons: {none, scrape, import}]
            [browse button (for import), path (for import)]
        Label not included (combo box contains)
        :param name: of type of data
        :return: widget of block, button group of radio buttons, line edit of path
        """
        layout = QVBoxLayout()

        top_row_layout = QHBoxLayout()

        button_group = QButtonGroup()  # radio buttons
        button_group.setExclusive(True)

        none_button = QRadioButton("None")
        scrape_button = QRadioButton("Scrape")
        import_button = QRadioButton("Import")

        button_group.addButton(none_button,
                               1)  # need 1-index because 0 means not found
        button_group.addButton(scrape_button, 2)
        button_group.addButton(import_button, 3)
        top_row_layout.addWidget(none_button)
        top_row_layout.addWidget(scrape_button)
        top_row_layout.addWidget(import_button)
        top_row_layout.addStretch()

        button_group.buttonClicked.connect(
            lambda current: self.update_combo_label_clicked(current))

        self.combo_box.setCurrentIndex(self.types.index(name))
        if name in {"Sun", "Moon", "Nautical"
                    }:  # default setting for Paul Revere Battalion slides
            scrape_button.setChecked(True)
            scrape_button.click()
        else:
            none_button.setChecked(True)
            none_button.click()

        top_row = QWidget()
        top_row.setLayout(top_row_layout)

        bottom_row_layout = QHBoxLayout()

        button = QPushButton("Browse")  # browse button
        abbrev_path = QLineEdit()  # line for import path
        full_path = MutableString("")
        bottom_row_layout.addWidget(button)
        bottom_row_layout.addWidget(abbrev_path)
        button.clicked.connect(lambda: self.set_path(abbrev_path, full_path))
        abbrev_path.textEdited.connect(
            lambda: self.set_path(abbrev_path, full_path, abbrev_path.text()))

        bottom_row = QWidget()
        bottom_row.setLayout(bottom_row_layout)

        layout.addWidget(top_row)
        layout.addWidget(bottom_row)

        out = QWidget()
        out.setLayout(layout)

        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        top_row_layout.setContentsMargins(0, 0, 5, 0)
        bottom_row_layout.setContentsMargins(0, 0, 0, 0)
        bottom_row_layout.setSpacing(5)

        return out, button, button_group, abbrev_path, full_path

    def __scrape(self):
        input_fields = [("Date Start", "text"), ("Date End", "text"),
                        ("City", "text"), ("State", "text"),
                        ("Timezone", "text")]
        input_defaults = {
            "Date Start": "2018MAR28",
            "Date End": "2018JUN28",
            "City": "Cambridge",
            "State": "MA",
            "Country": "US",
            "Timezone": "Eastern"
        }
        self.scrape_layout, self.scrape_fields = basic_form_creator(
            input_fields, input_defaults)

        # adding "Imperial Units" and "Save Weather" options as checkboxes in same row.
        daylight_savings = QCheckBox("Apply Daylight Savings")
        daylight_savings.setChecked(True)  # default is checked
        save = QCheckBox("Save")
        self.scrape_fields["Apply Daylight Savings"] = daylight_savings
        self.scrape_fields["Save Twilight"] = save

        self.checkbox_layout = QHBoxLayout()
        self.checkbox_layout.addWidget(daylight_savings)
        self.checkbox_layout.addWidget(save)
        self.checkbox_layout.addStretch()

        checkbox = QWidget()
        checkbox.setLayout(self.checkbox_layout)
        self.scrape_layout.insertWidget(0, checkbox)

        self.scrape = QWidget()
        self.scrape.setLayout(self.scrape_layout)

    def combo_switch_window(self, index):
        self.types_options.setCurrentIndex(index)

    def update_combo_label_clicked(self, changed):
        """
        Updates label in combo box to match current state of that label (scrape, import, or none)
        :param changed: the label that will be changed
        :return: nothing, mutates object
        """
        current_text = self.combo_box.currentText()
        split = current_text.split(" ")
        new_text = split[0] + " - " + changed.text()
        self.combo_box.setItemText(self.combo_box.currentIndex(), new_text)

    def set_path(self, abbrev, full, path=None):
        """
        Requests a file path from user and updates abbrev and full accordingly
        :param abbrev: QLineEdit to store abbreviated path
        :param full: MutableString to store full path
        :param path: to change to or prompts user, if path is given does not abbreviate
        :return: nothing, mutates parameters
        """
        if path is None:
            path = QFileDialog.getOpenFileName()[0]
            abbrev.setText(path.split("/")[-1])
        full.set_string(path)

    def __init_design(self):
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(0)
        self.layout.addStretch(1)
        self.combo_box.setContentsMargins(0, 0, 0, 0)
        self.types_options.setContentsMargins(0, 5, 0, 0)
        self.scrape_layout.setContentsMargins(0, 0, 0, 0)
        self.scrape_layout.setSpacing(5)
        self.checkbox_layout.setContentsMargins(0, 0, 0, 0)

        self.default_border = self.scrape_fields["City"].styleSheet(
        )  # just get from any existing line edit
        self.error_border = "border: 1px solid red;"
Exemplo n.º 6
0
class Popup(QDialog):
    def __init__(self, schedule: Schedule, parent=None, edit_data=None):
        super(Popup, self).__init__(parent)
        self.schedule = schedule
        self.event_name, self.data = edit_data if edit_data is not None else (
            None, None)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.setWindowTitle("Add New Scheduled Notes" if edit_data is None else
                            "Edit {} Details".format(self.event_name))

        self.layout = QVBoxLayout()
        self.layout.setSpacing(0)
        self.form_layout = QFormLayout()
        self.form_layout.setContentsMargins(0, 0, 0,
                                            self.form_layout.verticalSpacing())
        self.form_layout_widget = QWidget()
        self.form_layout_widget.setLayout(self.form_layout)

        #The amount of fields in the form that come before the block section (name, #blocks, start, end date, color)
        self.rows_before_blocks = 3

        self.event_type = QPushButton()
        event_type_menu = QMenu()
        event_type_menu.addAction("Class")
        event_type_menu.addSection("Event")
        event_type_menu.addAction("One Time Event")
        event_type_menu.addAction("Recurring Event")
        event_type_menu.addAction("One Time Class Event")
        for action in event_type_menu.actions():
            if not action.isSeparator():
                action.triggered.connect(
                    lambda state, x=action.text(): self.set_type(x))
        self.event_type.setMenu(event_type_menu)
        self.form_layout.addRow("Type:", self.event_type)

        #Class Title
        self.name_edit = QLineEdit()
        self.form_layout.addRow("Name:", self.name_edit)
        #Color
        self.color_picker = QColorDialog()
        self.color_button = QPushButton("Pick Color")
        self.color_button.clicked.connect(self.color_picker.open)
        self.color_picker.currentColorChanged.connect(self.update_color)
        self.form_layout.addRow("Color Code:", self.color_button)

        # Initialize widgets to be added later
        self.start_date_model = DateTimePickerSeriesModel(self)
        self.class_start_date = DateTimePickerSeries(self.start_date_model,
                                                     "MMM d yyyy")
        self.event_start_date = DateTimePickerSeries(self.start_date_model,
                                                     "MMM d yyyy")

        self.end_date_model = DateTimePickerSeriesModel(self)
        self.class_end_date = DateTimePickerSeries(self.end_date_model,
                                                   "MMM d yyyy")
        self.event_end_date = DateTimePickerSeries(self.end_date_model,
                                                   "MMM d yyyy")

        self.event_date_model = DateTimePickerSeriesModel(self)
        self.class_event_date = DateTimePickerSeries(self.event_date_model,
                                                     "MMM d yyyy hh:mm:AP")
        self.event_date = DateTimePickerSeries(self.event_date_model,
                                               "MMM d yyyy hh:mm:AP")

        # Blocks
        self.blocks = 1
        self.spin_box = QSpinBox()
        self.spin_box.setValue(1)
        self.spin_box.setMinimum(1)
        self.spin_box.setMaximum(7)
        self.spin_box.valueChanged.connect(self.update_blocks)

        self.class_picker = QPushButton()
        class_picker_menu = QMenu()
        for class_name in self.schedule.schedule.keys():
            if self.schedule.schedule[class_name]["type"] != "class":
                continue
            class_action = QAction(class_name, parent=class_picker_menu)
            class_action.triggered.connect(lambda state, x=class_action.text():
                                           self.class_picker.setText(x))
            class_picker_menu.addAction(class_action)
        class_picker_menu.aboutToShow.connect(
            lambda: class_picker_menu.setMinimumWidth(self.class_picker.width(
            )))
        self.class_picker.setMenu(class_picker_menu)

        self.stack = QStackedWidget()
        self.stack.setContentsMargins(0, 0, 0, 0)

        class_layout = QFormLayout()
        class_layout.setContentsMargins(0, 0, 0,
                                        class_layout.verticalSpacing())
        class_layout.addRow("Start Date:", self.class_start_date)
        class_layout.addRow("End Date:", self.class_end_date)
        class_layout.addRow("Weekly Blocks:", self.spin_box)
        class_layout.addRow("Block Time:", ClassTimePicker())
        self.class_options = QWidget()
        self.class_options.setSizePolicy(QSizePolicy.Ignored,
                                         QSizePolicy.Ignored)
        self.class_options.setLayout(class_layout)

        recurring_event_layout = QFormLayout()
        recurring_event_layout.setContentsMargins(
            0, 0, 0, recurring_event_layout.verticalSpacing())
        recurring_event_layout.addRow("Start Date:", self.event_start_date)
        recurring_event_layout.addRow("End Date:", self.event_end_date)
        self.recurring_event_time_picker = ClassTimePicker()
        recurring_event_layout.addRow("Event Time:",
                                      self.recurring_event_time_picker)
        self.recurring_event_options = QWidget()
        self.recurring_event_options.setSizePolicy(QSizePolicy.Ignored,
                                                   QSizePolicy.Ignored)
        self.recurring_event_options.setLayout(recurring_event_layout)

        one_time_event_layout = QFormLayout()
        one_time_event_layout.setContentsMargins(
            0, 0, 0, one_time_event_layout.verticalSpacing())
        one_time_event_layout.addRow("Event Date:", self.event_date)
        self.one_time_event_options = QWidget()
        self.one_time_event_options.setSizePolicy(QSizePolicy.Ignored,
                                                  QSizePolicy.Ignored)
        self.one_time_event_options.setLayout(one_time_event_layout)

        class_event_layout = QFormLayout()
        class_event_layout.setContentsMargins(
            0, 0, 0, class_event_layout.verticalSpacing())
        class_event_layout.addRow("Class:", self.class_picker)
        class_event_layout.addRow("Event Date:", self.class_event_date)
        self.class_event_options = QWidget()
        self.class_event_options.setSizePolicy(QSizePolicy.Ignored,
                                               QSizePolicy.Ignored)
        self.class_event_options.setLayout(class_event_layout)

        self.stack.addWidget(self.class_event_options)
        self.stack.addWidget(self.one_time_event_options)
        self.stack.addWidget(self.recurring_event_options)
        self.stack.addWidget(self.class_options)

        if self.data is None:
            self.set_type("Class")

        self.layout.addWidget(self.form_layout_widget)
        self.layout.addWidget(self.stack)
        self.setLayout(self.layout)
        self.show_buttons()

        #Update Values if self.data is defined
        if self.data is not None:
            event_type = self.data["type"]
            self.set_type(camel_case(event_type))
            # noinspection PyTypeChecker
            class_layout: QFormLayout = self.stack.currentWidget().layout()
            self.name_edit.setText(self.event_name)
            self.name_edit.setDisabled(True)
            self.color_picker.setCurrentColor(to_qcolor(self.data["color"]))
            if event_type in ["class", "recurring event"]:
                self.start_date_model.content = QDateTime(
                    to_qdate(self.data["start"]))
                self.end_date_model.content = QDateTime(
                    to_qdate(self.data["end"]))
            if event_type == "class":
                blocks = self.data["blocks"]
                self.update_blocks(len(blocks))
                for i, row in enumerate(
                        range(self.rows_before_blocks,
                              class_layout.rowCount())):
                    block = blocks[i]
                    # noinspection PyTypeChecker
                    block_widget: ClassTimePicker = class_layout.itemAt(
                        row, QFormLayout.FieldRole).widget()
                    block_widget.set_time(to_qtime(block["time"]))
                    block_widget.set_day(block["day"])
            if event_type == "recurring event":
                self.recurring_event_time_picker.set_day(self.data["day"])
                self.recurring_event_time_picker.set_time(
                    to_qtime(self.data["time"]))
            if event_type in ["one time event", "one time class event"]:
                date_time = QDateTime()
                date_time.setDate(to_qdate(self.data["date"]))
                date_time.setTime(to_qtime(self.data["time"]))
                self.event_date_model.content = date_time
            if event_type == "one time class event":
                self.class_picker.setText(self.data["class_name"])

    def show_buttons(self):
        save_button = QDialogButtonBox.Save if self.data is None else QDialogButtonBox.Apply
        cancel_button = QDialogButtonBox.Cancel
        buttonBox = QDialogButtonBox(Qt.Horizontal)
        buttonBox.addButton(save_button).clicked.connect(self.accept)
        buttonBox.addButton(cancel_button).clicked.connect(self.reject)
        if self.data is not None:
            delete_button = buttonBox.addButton(QDialogButtonBox.Discard)
            delete_button.setText("Delete")
            delete_button.clicked.connect(self.delete_event)
        self.layout.addWidget(buttonBox)

    def set_type(self, event_type: str):
        if self.event_type.text() == event_type:
            return
        self.event_type.setText(event_type)
        self.stack.currentWidget().setSizePolicy(QSizePolicy.Ignored,
                                                 QSizePolicy.Ignored)
        if event_type == "Class":
            self.class_options.setSizePolicy(QSizePolicy.Expanding,
                                             QSizePolicy.Expanding)
            self.class_options.adjustSize()
            self.stack.setCurrentWidget(self.class_options)
        elif event_type == "Recurring Event":
            self.recurring_event_options.setSizePolicy(QSizePolicy.Expanding,
                                                       QSizePolicy.Expanding)
            self.recurring_event_options.adjustSize()
            self.stack.setCurrentWidget(self.recurring_event_options)
        elif event_type == "One Time Event":
            self.one_time_event_options.setSizePolicy(QSizePolicy.Expanding,
                                                      QSizePolicy.Expanding)
            self.one_time_event_options.adjustSize()
            self.stack.setCurrentWidget(self.one_time_event_options)
        elif event_type == "One Time Class Event":
            self.class_event_options.setSizePolicy(QSizePolicy.Expanding,
                                                   QSizePolicy.Expanding)
            self.class_event_options.adjustSize()
            self.stack.setCurrentWidget(self.class_event_options)
        self.stack.adjustSize()
        max_width = 0
        for i in range(self.form_layout.rowCount()):
            widget = self.form_layout.itemAt(i, QFormLayout.LabelRole).widget()
            widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
            widget.adjustSize()
            max_width = max(widget.size().width(), max_width)
        # noinspection PyTypeChecker
        current_widget_layout: QFormLayout = self.stack.currentWidget().layout(
        )
        for i in range(current_widget_layout.rowCount()):
            widget = current_widget_layout.itemAt(
                i, QFormLayout.LabelRole).widget()
            widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
            widget.adjustSize()
            max_width = max(widget.size().width(), max_width)
        for i in range(self.form_layout.rowCount()):
            self.form_layout.itemAt(
                i, QFormLayout.LabelRole).widget().setMinimumWidth(max_width)
        for i in range(current_widget_layout.rowCount()):
            current_widget_layout.itemAt(
                i, QFormLayout.LabelRole).widget().setMinimumWidth(max_width)
        self.adjustSize()

    def update_color(self):
        self.color_button.setStyleSheet(
            "background-color: rgb({},{},{})".format(
                self.color_picker.currentColor().red(),
                self.color_picker.currentColor().green(),
                self.color_picker.currentColor().blue()))

    def update_blocks(self, value):
        if self.spin_box.value() != value:
            self.spin_box.setValue(value)
            return
        if self.blocks == value:
            return
        old_blocks = self.blocks
        self.blocks = value
        class_options_layout: QFormLayout = self.class_options.layout()
        if self.blocks > old_blocks:
            #Change label of block 1
            if old_blocks == 1:
                class_options_layout.itemAt(
                    self.rows_before_blocks,
                    QFormLayout.LabelRole).widget().setText("Block 1 Time:")
            for i in range(1, self.blocks - old_blocks + 1):
                offset = self.rows_before_blocks + old_blocks + i - 1
                widget = class_options_layout.itemAt(offset,
                                                     QFormLayout.FieldRole)
                label = class_options_layout.itemAt(offset,
                                                    QFormLayout.LabelRole)
                if widget is not None and label is not None:
                    widget = widget.widget()
                    label = label.widget()
                    widget.setSizePolicy(QSizePolicy.Expanding,
                                         QSizePolicy.Expanding)
                    label.setSizePolicy(QSizePolicy.Expanding,
                                        QSizePolicy.Expanding)
                    widget.adjustSize()
                    label.adjustSize()
                    widget.show()
                    label.show()
                else:
                    picker = ClassTimePicker()
                    picker.sizePolicy().setRetainSizeWhenHidden(False)
                    class_options_layout.addRow(
                        "Block {} Time:".format(old_blocks + i), picker)
        elif self.blocks < old_blocks:
            if self.blocks == 1:
                class_options_layout.itemAt(
                    self.rows_before_blocks,
                    QFormLayout.LabelRole).widget().setText("Block Time:")
            for i in range(old_blocks - self.blocks):
                offset = self.rows_before_blocks + old_blocks + i - 1
                widget = class_options_layout.itemAt(
                    offset, QFormLayout.FieldRole).widget()
                label = class_options_layout.itemAt(
                    offset, QFormLayout.LabelRole).widget()
                widget.hide()
                label.hide()
                widget.adjustSize()
                label.adjustSize()
                self.class_options.adjustSize()
                self.stack.adjustSize()
                self.adjustSize()

        # self.class_options.adjustSize()
        # self.stack.adjustSize()
        # self.adjustSize()

    def get_name(self):
        return self.name_edit.text()

    def get_data(self):
        event_type = self.event_type.text()
        data = {
            "type": event_type.lower(),
            "name": self.get_name(),
            "color": {
                "r": self.color_picker.currentColor().red(),
                "g": self.color_picker.currentColor().green(),
                "b": self.color_picker.currentColor().blue(),
            }
        }
        if event_type == "Class":
            block_data = []
            # noinspection PyTypeChecker
            class_layout: QFormLayout = self.stack.currentWidget().layout()
            for row in range(self.rows_before_blocks, class_layout.rowCount()):
                # noinspection PyTypeChecker
                block_widget: ClassTimePicker = class_layout.itemAt(
                    row, QFormLayout.FieldRole).widget()
                if block_widget.isHidden():
                    continue
                time = block_widget.get_time()
                block_data.append({
                    "day": block_widget.day_picker.get_day(),
                    "time": {
                        "hour": time.hour(),
                        "minute": time.minute()
                    }
                })
            data["blocks"] = block_data
        if event_type in ["Class", "Recurring Event"]:
            start_date = self.start_date_model.content.date()
            data["start"] = {
                "day": start_date.day(),
                "month": start_date.month(),
                "year": start_date.year()
            }
            end_date = self.end_date_model.content.date()
            data["end"] = {
                "day": end_date.day(),
                "month": end_date.month(),
                "year": end_date.year()
            }
        if event_type == "Recurring Event":
            data["day"] = self.recurring_event_time_picker.day_picker.get_day()
            time = self.recurring_event_time_picker.get_time()
            data["time"] = {"hour": time.hour(), "minute": time.minute()}
        if event_type == "One Time Class Event":
            data["class_name"] = self.class_picker.text()
        if event_type in ["One Time Event", "One Time Class Event"]:
            date_time = self.event_date_model.content
            date = date_time.date()
            time = date_time.time()
            data["date"] = {
                "day": date.day(),
                "month": date.month(),
                "year": date.year(),
            }
            data["time"] = {"hour": time.hour(), "minute": time.minute()}
        return data

    def delete_event(self):
        error = QMessageBox()
        error.setText("Are you sure you would like to delete this event?")
        error.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel)
        result = error.exec_()
        if result == QMessageBox.Yes:
            self.schedule.delete_event(self.event_name)
            self.reject()

    def accept(self):
        event_type = self.event_type.text()
        if event_type == "":
            error = QMessageBox()
            error.setText("Please select a type for the event.")
            error.exec_()
            self.event_type.setFocus()
            return
        # Check Name
        if len(self.get_name()) == 0:
            error = QMessageBox()
            error.setText("Please enter a name for the event.")
            error.exec_()
            self.name_edit.setFocus()
            return
        if event_type in ["Class", "Recurring Event"]:
            # Check Start/End Date
            start_date = self.start_date_model.content.date()
            end_date = self.end_date_model.content.date()
            if start_date >= end_date:
                error = QMessageBox()
                error.setText("End date cannot {} start date.".format(
                    "be equal to" if start_date ==
                    end_date else "come before"))
                error.exec_()
                if event_type == "Class":
                    self.class_end_date.setFocus()
                else:
                    self.event_end_date.setFocus()
                return
            if event_type == "Class":
                # Check Blocks
                # noinspection PyTypeChecker
                class_layout: QFormLayout = self.stack.currentWidget().layout()
                for row in range(self.rows_before_blocks,
                                 class_layout.rowCount()):
                    block_widget = class_layout.itemAt(
                        row, QFormLayout.FieldRole).widget()
                    if block_widget.isHidden():
                        continue
                    # Make sure a day is selected for each block
                    if not block_widget.is_valid():
                        block_name = "the class block" if self.blocks == 1 else str.lower(
                            class_layout.itemAt(row, QFormLayout.LabelRole).
                            widget().text()).replace(" time:", "")
                        error = QMessageBox()
                        error.setText(
                            "Please select a valid day for {}.".format(
                                block_name))
                        error.exec_()
                        return
                    # Check for duplicate blocks
                    for other in range(self.rows_before_blocks,
                                       class_layout.rowCount() - 1):
                        if row == other:
                            continue
                        other_block_widget = class_layout.itemAt(
                            other, QFormLayout.FieldRole).widget()
                        same_time = block_widget.get_time(
                        ) == other_block_widget.get_time()
                        same_day = block_widget.day_picker.get_day(
                        ) == other_block_widget.day_picker.get_day()
                        if same_time and same_day:
                            error = QMessageBox()
                            error.setText(
                                "Block {} and {} cannot have the same day and time."
                                .format(row - self.rows_before_blocks + 1,
                                        other - self.rows_before_blocks + 1))
                            error.exec_()
                            return
            if event_type == "Recurring Event":
                # Make sure a day is selected
                if not self.recurring_event_time_picker.is_valid():
                    error = QMessageBox()
                    error.setText("Please select a valid day for this event.")
                    error.exec_()
                    self.recurring_event_time_picker.setFocus()
                    return
        if event_type == "One Time Class Event":
            # Check Class
            if len(self.class_picker.text()) == 0:
                error = QMessageBox()
                error.setText("Please select a class for this event.")
                error.exec_()
                self.class_picker.setFocus()
                return
        # Valid name
        if self.get_name() in self.schedule.schedule.keys():
            if self.data is None:
                error = QMessageBox()
                error.setText(
                    "An event with this name already exists, would you like to overwrite it?"
                )
                error.setStandardButtons(error.Apply | error.Cancel)
                result = error.exec_()
                if result == error.Apply:
                    to_overwrite = self.schedule.schedule[self.get_name()]
                    if to_overwrite["type"] == "class":
                        if self.event_type.text() == "One Time Class Event":
                            if self.class_picker.text() == self.get_name():
                                error = QMessageBox()
                                error.setText(
                                    "Cannot overwrite a class with a one time class event for that class.\n"
                                    "Please select a different class.")
                                error.setStandardButtons(QMessageBox.Close)
                                error.exec_()
                                return
                    self.schedule.edit_event(self.get_data())
                    self.reject()
                elif result == error.Cancel:
                    self.name_edit.setFocus()
                    return
            self.schedule.edit_event(self.get_data())
            self.reject()
        super(Popup, self).accept()

    def reject(self):
        super(Popup, self).reject()
Exemplo n.º 7
0
class MainWindow(QMainWindow):
    """
    主窗体
    """
    tray_icon = None  # 托盘图标
    base_widget = None  # 界面最基础容器
    base_layout = None  # 界面最基础布局
    left_frame = None  # 界面左边容器
    left_layout = None  # 界面左边布局
    right_frame = None  # 界面右边容器
    right_layout = None  # 界面右边布局
    option_frame = None  # 功能选项容器
    option_list_widget = None  # 左侧功能选项
    option_stack_widget = None  # 右侧功能页面
    log_widget = None  # 右侧日志界面
    log_text = None  # 日志框
    log_text_signal = pyqtSignal(str)

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self.init_window()
        self.init_tray()
        self.log_text_signal.connect(self.append_text)
        threading.Thread(target=self.read_log).start()

    def init_window(self):
        """
        初始化窗体
        :return:
        """
        self.setWindowTitle("虾皮助手")
        self.setMinimumSize(QSize(800, 600))
        self.setStyleSheet("background-color: rgb(248,248, 255)")

        # 基础容器定义
        self.base_widget = QWidget(self)
        self.base_layout = QHBoxLayout()
        self.base_widget.setLayout(self.base_layout)
        self.setCentralWidget(self.base_widget)

        # 左边容器定义
        self.left_frame = QFrame(self.base_widget)
        self.init_left_frame()

        # 右边容器定义
        self.right_frame = QFrame(self.base_widget)
        self.init_right_frame()
        self.base_layout.addWidget(self.left_frame)
        self.base_layout.addWidget(self.right_frame)

    def init_left_frame(self):
        """
        初始化左边容器
        :return:
        """
        self.left_layout = QVBoxLayout()
        self.left_frame.setLayout(self.left_layout)
        self.left_layout.setContentsMargins(0, 0, 0, 0)
        self.left_frame.setContentsMargins(0, 20, 0, 0)
        self.left_frame.setMinimumWidth(125)
        self.left_frame.setMaximumWidth(125)
        self.left_frame.setStyleSheet("background-color: rgb(222,248, 255)")
        size_policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        size_policy.setHorizontalStretch(1)
        size_policy.setVerticalStretch(0)
        size_policy.setHeightForWidth(
            self.left_frame.sizePolicy().hasHeightForWidth())
        self.left_frame.setSizePolicy(size_policy)

        avatar_label = AvatarLabel(image_path=AVATAR_IMAGE_PATH)
        self.left_layout.addWidget(avatar_label)

        self.option_frame = QWidget()
        self.option_frame.setContentsMargins(0, 30, 0, 0)
        option_layout = QVBoxLayout()
        option_layout.setContentsMargins(0, 0, 0, 0)
        self.option_frame.setLayout(option_layout)
        self.option_list_widget = QListWidget()
        self.option_list_widget.setContentsMargins(0, 0, 0, 0)
        option_layout.addWidget(self.option_list_widget)

        for i in range(len(OPTIONS)):
            item = QListWidgetItem()
            item.setText(OPTIONS[i])
            item.setIcon(
                QIcon(os.path.join(OPTIONS_ICON_DIR, '{}.png'.format(i + 1))))
            item.setTextAlignment(Qt.AlignCenter)
            item.setSizeHint(QSize(125, 25))
            self.option_list_widget.addItem(item)

        self.option_list_widget.setFrameStyle(QListWidget.NoFrame)
        self.left_layout.addWidget(self.option_frame)

    def init_right_frame(self):
        """
        初始化右边容器
        :return:
        """
        self.right_layout = QVBoxLayout()
        self.right_layout.setContentsMargins(0, 0, 0, 0)
        self.right_frame.setContentsMargins(0, 0, 0, 0)
        self.right_frame.setLayout(self.right_layout)
        self.right_frame.setStyleSheet("background-color: rgb(248,248, 255)")
        size_policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        size_policy.setHorizontalStretch(4)
        size_policy.setVerticalStretch(0)
        size_policy.setHeightForWidth(
            self.right_frame.sizePolicy().hasHeightForWidth())
        self.right_frame.setSizePolicy(size_policy)

        self.option_stack_widget = QStackedWidget()
        self.option_list_widget.setMinimumHeight(600)
        self.option_stack_widget.setContentsMargins(0, 0, 0, 0)
        self.option_list_widget.currentRowChanged.connect(
            self.option_stack_widget.setCurrentIndex)
        size_policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        size_policy.setHorizontalStretch(0)
        size_policy.setVerticalStretch(10)
        size_policy.setHeightForWidth(
            self.option_stack_widget.sizePolicy().hasHeightForWidth())

        for frame in FRAMES:
            obj = frame()
            self.option_stack_widget.addWidget(obj)

        self.log_widget = QWidget()
        self.log_widget.setMaximumHeight(200)
        self.log_widget.setContentsMargins(0, 0, 0, 0)
        size_policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        size_policy.setHorizontalStretch(0)
        size_policy.setVerticalStretch(1)
        size_policy.setHeightForWidth(
            self.log_widget.sizePolicy().hasHeightForWidth())
        log_layout = QVBoxLayout()
        log_layout.setContentsMargins(0, 0, 0, 0)
        self.log_widget.setLayout(log_layout)
        log_label = QLabel('日志记录')
        log_label.setContentsMargins(0, 0, 0, 0)
        log_label.setAlignment(Qt.AlignCenter)
        self.log_text = QTextBrowser()
        self.log_text.setContentsMargins(0, 0, 0, 0)
        self.log_text.setAlignment(Qt.AlignLeft)

        log_layout.addWidget(log_label)
        log_layout.addWidget(self.log_text)

        self.right_layout.addWidget(self.option_stack_widget)
        self.right_layout.addWidget(self.log_widget)

    @pyqtSlot(str)
    def append_text(self, text):
        self.log_text.append(text)

    def read_log(self):
        while True:
            while has_log():
                msg = read_log()
                self.log_text_signal.emit(msg)
                time.sleep(0.05)
            time.sleep(0.1)

    def exit(self):
        self.tray_icon = None
        os._exit(0)

    def init_tray(self):
        """
        初始化系统托盘
        :return:
        """
        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setIcon(QIcon(TRAY_ICON))
        show_action = QAction("显示窗口", self)
        quit_action = QAction("退出程序", self)
        hide_action = QAction("隐藏窗口", self)
        show_action.triggered.connect(self.show)
        hide_action.triggered.connect(self.hide)
        quit_action.triggered.connect(self.exit)
        tray_menu = QMenu()
        tray_menu.addAction(show_action)
        tray_menu.addAction(hide_action)
        tray_menu.addAction(quit_action)
        self.tray_icon.setContextMenu(tray_menu)
        self.tray_icon.show()

    def closeEvent(self, event):
        """
        重写右上角X操作的事件
        :param event:
        :return:
        """
        event.ignore()
        self.hide()
        self.tray_icon.showMessage("虾皮助手", "应用已收起至托盘!!!",
                                   QSystemTrayIcon.Information, 2000)