Example #1
0
class PluginController(QFrame):
    def __init__(self, plugins, highlighted_plugins=None, parent=None):
        """
        :type plugins: list of Plugin
        :type highlighted_plugins: list of Plugin
        """
        super().__init__(parent)
        self.ui = Ui_FramePlugins()
        self.ui.setupUi(self)
        self.model = PluginListModel(plugins,
                                     highlighted_plugins=highlighted_plugins)
        self.ui.listViewPlugins.setModel(self.model)
        self.settings_layout = QVBoxLayout()
        self.ui.groupBoxSettings.setLayout(self.settings_layout)
        self.create_connects()

    def create_connects(self):
        self.ui.listViewPlugins.selectionModel().selectionChanged.connect(
            self.handle_list_selection_changed)

    def save_enabled_states(self):
        for plugin in self.model.plugins:
            constants.SETTINGS.setValue(plugin.name, plugin.enabled)

    @pyqtSlot()
    def handle_list_selection_changed(self):
        i = self.ui.listViewPlugins.currentIndex().row()
        self.ui.txtEditPluginDescription.setText(
            self.model.plugins[i].description)
        self.model.plugins[i].load_settings_frame()

        if self.settings_layout.count() > 0:
            self.settings_layout.takeAt(0).widget().setParent(None)

        self.settings_layout.addWidget(self.model.plugins[i].settings_frame)
Example #2
0
class MainView(QWidget):
    def __init__(self):
        super().__init__()

        # Set id and styles
        self.setObjectName('MainView')

        # Set layout
        layout = QHBoxLayout(self)
        self.setLayout(layout)

        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)

        # Create SourceList widget
        self.source_list = SourceList()
        self.source_list.itemSelectionChanged.connect(self.on_source_changed)

        # Create widgets
        self.view_holder = QWidget()
        self.view_holder.setObjectName('MainView_view_holder')
        self.view_layout = QVBoxLayout()
        self.view_holder.setLayout(self.view_layout)

        # Add widgets to layout
        layout.addWidget(self.source_list)
        layout.addWidget(self.view_holder)

    def on_source_changed(self):
        self.view_layout.takeAt(0)
        self.view_layout.addWidget(SourceConversationWrapper())
Example #3
0
class EventListWidget(QFrame):
    """
    Class for the event list of the application. This class lists the next occurring events in the calendars.
    """
    def __init__(self, parent, logger):
        self.logger = logger
        self.logger.info('Creating Event List Widget')
        super().__init__(parent)
        self.setFrameStyle(QFrame.Box)
        self.setLineWidth(2)

        self.event_counter = 0
        self.events = []

        self.layout = QVBoxLayout(self)

    def updateEvents(self, events):
        """
        Function for updating Event List Widget with events
        """
        self.logger.info('Updating all events')
        self.clearChildren()
        self.events = events

        for e in self.events:
            self.logger.debug(e)

        self.events.sort(key=lambda x: x.start_date)

        counter = 0

        for event in self.events:
            e_widget = EventWidget(self, event)
            self.layout.addWidget(e_widget)
            counter += 1
            if counter == MAX_WIDGET_AMOUT:
                break

    def clearChildren(self):
        """
        Function for clearing all child objects
        """
        while self.layout.takeAt(0):
            child = self.layout.takeAt(0)
            del child

        self.events.clear()

    def getEventDescription(self):
        """
        Function for getting a single event for the event description. This event will be one of the first 5 events
        """
        if len(self.events) <= self.event_counter:
            return None

        event = self.events[self.event_counter]
        self.event_counter = (self.event_counter + 1) % MAX_WIDGET_AMOUT
        return event
Example #4
0
    def initialize_context(self):

        reloading_layout = self.context_group.layout()

        if reloading_layout is None:
            reloading_layout = QVBoxLayout()
            self.context_group.setLayout(reloading_layout)
        else:
            while not reloading_layout.isEmpty():
                # Our first item is always the button
                item = reloading_layout.takeAt(0)
                self.output_grid_layout.removeItem(item)
                if item.widget():
                    item.widget().deleteLater()
                self.name_label = QLabel()

        button_layout = QGridLayout()

        var_button = QPushButton("Local values")
        var_button.clicked.connect(self.show_locals)
        button_layout.addWidget(var_button, 0, 0)

        func_button = QPushButton("Functions")
        func_button.clicked.connect(self.show_local_funcs)
        button_layout.addWidget(func_button, 0, 1)

        out_button = QPushButton("Outputs")
        out_button.clicked.connect(self.show_outs)
        button_layout.addWidget(out_button, 1, 0)

        del_button = QPushButton("Delete")
        del_button.clicked.connect(self.delete_organ)
        button_layout.addWidget(del_button, 1, 1)
        reloading_layout.addWidget(self.name_label)
        reloading_layout.addLayout(button_layout)
Example #5
0
class LocalButtonsConf(OptionsDialogGroupBox):
    def __init__(self, name, main):
        self.main = main
        OptionsDialogGroupBox.__init__(self, name, main)
        self.buttonBox = QGroupBox("Pins")
        self.buttonBoxLayout = QVBoxLayout()
        self.buttonBox.setLayout(self.buttonBoxLayout)

    def initUI(self):
        vbox = QVBoxLayout()
        self.polBox = QCheckBox("Invert")
        vbox.addWidget(self.polBox)
        self.buttongroup = QButtonGroup()
        self.buttongroup.setExclusive(False)
        vbox.addWidget(self.buttonBox)

        self.setLayout(vbox)

    def initButtons(self, num):
        #delete buttons
        self.num = num

        # Remove buttons
        for i in range(self.buttonBoxLayout.count()):
            b = self.buttonBoxLayout.takeAt(0)
            self.buttonBoxLayout.removeItem(b)
            b.widget().deleteLater()
        for b in self.buttongroup.buttons():
            self.buttongroup.removeButton(b)

        self.buttonBox.update()

        for i in range(self.num):
            cb = QCheckBox(str(i + 1))
            self.buttongroup.addButton(cb, i)
            self.buttonBoxLayout.addWidget(cb)

        def localcb(mask):
            for i in range(self.num):
                self.buttongroup.button(i).setChecked(mask & (1 << i))

        self.main.comms.serialGetAsync("local_btnmask?", localcb, int)

    def apply(self):
        mask = 0
        for i in range(self.num):
            if (self.buttongroup.button(i).isChecked()):
                mask |= 1 << i
        self.main.comms.serialWrite("local_btnmask=" + str(mask))
        self.main.comms.serialWrite("local_btnpol=" +
                                    ("1" if self.polBox.isChecked() else "0"))

    def readValues(self):
        self.main.comms.serialGetAsync("local_btnpins?", self.initButtons, int)

        self.main.comms.serialGetAsync("local_btnpol?", self.polBox.setChecked,
                                       int)
class AnalogInputConf(OptionsDialogGroupBox):
    analogbtns = QButtonGroup()
    axes = 0

    def __init__(self, name, main):
        self.main = main
        OptionsDialogGroupBox.__init__(self, name, main)
        self.analogbtns.setExclusive(False)
        self.buttonBox = QGroupBox("Pins")
        self.buttonBoxLayout = QVBoxLayout()
        self.buttonBox.setLayout(self.buttonBoxLayout)

    def initUI(self):
        layout = QVBoxLayout()
        self.autorangeBox = QCheckBox("Autorange")
        layout.addWidget(self.autorangeBox)
        layout.addWidget(self.buttonBox)
        self.setLayout(layout)

    def readValues(self):
        self.main.comms.serialGetAsync("local_ain_num?", self.createAinButtons,
                                       int)
        self.main.comms.serialGetAsync("local_ain_acal?",
                                       self.autorangeBox.setChecked, int)

    def createAinButtons(self, axes):
        self.axes = axes

        # remove buttons
        for i in range(self.buttonBoxLayout.count()):
            b = self.buttonBoxLayout.takeAt(0)
            self.buttonBoxLayout.removeItem(b)
            b.widget().deleteLater()
        for b in self.analogbtns.buttons():
            self.analogbtns.removeButton(b)

        # add buttons
        for i in range(axes):
            btn = QCheckBox(str(i + 1), self)
            self.analogbtns.addButton(btn, i)
            self.buttonBoxLayout.addWidget(btn)

        def f(axismask):
            for i in range(self.axes):
                self.analogbtns.button(i).setChecked(axismask & (1 << i))

        self.main.comms.serialGetAsync("local_ain_mask?", f, int)

    def apply(self):
        mask = 0
        for i in range(self.axes):
            if (self.analogbtns.button(i).isChecked()):
                mask |= 1 << i
        self.main.comms.serialWrite("local_ain_mask=" + str(mask))
        self.main.comms.serialWrite("local_ain_acal=" + (
            "1" if self.autorangeBox.isChecked() else "0"))
Example #7
0
class QScrollableBox(QWidget):
    def __init__(self, parent):
        super(QScrollableBox, self).__init__(parent)
        mainLayout = QVBoxLayout()
        mainLayout.setContentsMargins(0, 0, 0, 0)
        self.scrollArea = QScrollArea(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        self.scrollArea.setSizePolicy(sizePolicy)
        self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.scrollArea.setWidgetResizable(True)

        mainLayout.addWidget(self.scrollArea)
        self.setLayout(mainLayout)
        scrollContents = QWidget()
        self.m_layout = QVBoxLayout()
        self.m_layout.setContentsMargins(0, 0, 0, 0)
        self.m_layout.setSizeConstraint(QLayout.SetNoConstraint)
        scrollContents.setLayout(self.m_layout)

        self.scrollArea.setWidget(scrollContents)

    def addWidget(self, w):
        if not w:
            return
        count = self.m_layout.count()
        if count > 1:
            self.m_layout.removeItem(self.m_layout.itemAt(count - 1))
        self.m_layout.addWidget(w)
        w.show()
        self.m_layout.addStretch()
        self.scrollArea.update()

    def removeWidget(self, w):
        self.m_layout.removeWidget(w)
        self.scrollArea.update()

    def insertWidget(self, i, w):
        self.m_layout.insertWidget(i, w)
        self.scrollArea.update()

    def clearWidgets(self):
        item = self.m_layout.takeAt(0)
        while item != None:
            item.widget().deleteLater()
            self.m_layout.removeItem(item)
            del item

        self.scrollArea.update()

    def indexOf(self, w):
        return self.m_layout.indexOf(w)
class MainView(QWidget):
    """
    Represents the main content of the application (containing the source list
    and main context view).
    """

    def __init__(self, parent):
        super().__init__(parent)
        self.layout = QHBoxLayout(self)
        self.setLayout(self.layout)
        left_column = QWidget(parent=self)
        left_layout = QVBoxLayout()
        left_column.setLayout(left_layout)
        self.status = QLabel(_('Waiting to Synchronize'))
        self.error_status = QLabel('')
        self.error_status.setObjectName('error_label')
        left_layout.addWidget(self.status)
        left_layout.addWidget(self.error_status)
        filter_widget = QWidget()
        filter_layout = QHBoxLayout()
        filter_widget.setLayout(filter_layout)
        filter_label = QLabel(_('Filter: '))
        self.filter_term = QLineEdit()
        self.source_list = SourceList(left_column)
        filter_layout.addWidget(filter_label)
        filter_layout.addWidget(self.filter_term)
        left_layout.addWidget(filter_widget)
        left_layout.addWidget(self.source_list)
        self.layout.addWidget(left_column, 2)
        self.view_holder = QWidget()
        self.view_layout = QVBoxLayout()
        self.view_holder.setLayout(self.view_layout)
        self.layout.addWidget(self.view_holder, 6)

    def setup(self, controller):
        """
        Pass through the controller object to this widget.
        """
        self.controller = controller
        self.source_list.setup(controller)

    def update_error_status(self, error=None):
        self.error_status.setText(error)

    def update_view(self, widget):
        """
        Update the view holder to contain the referenced widget.
        """
        old_widget = self.view_layout.takeAt(0)
        if old_widget:
            old_widget.widget().setVisible(False)
        widget.setVisible(True)
        self.view_layout.addWidget(widget)
Example #9
0
class PDFViewer(QScrollArea):
    def __init__(self, parent):
        super().__init__(parent)
        self.scrollAreaContents = QWidget()
        self.scrollAreaLayout = QVBoxLayout()

        self.setWidget(self.scrollAreaContents)
        self.setWidgetResizable(True)

        self.scrollAreaContents.setLayout(self.scrollAreaLayout)
        self.pixmapList = []

    def resizeEvent(self, event):
        pass
        # do something about this later
    
    def update(self, pdf):
        self.render(pdf)
        self.clear()
        self.show()

    def render(self, pdf):
        """
        Update the preview shown by rendering a new PDF and drawing it to the scroll area.
        """

        self.pixmapList = []
        pdfView = fitz.Document(stream=pdf, filetype='pdf')
        # render at 4x resolution and scale
        for page in pdfView:
            self.pixmapList.append(page.getPixmap(matrix=fitz.Matrix(4, 4), alpha=False))
                    
    def clear(self):
        while self.scrollAreaLayout.count():
            item = self.scrollAreaLayout.takeAt(0)
            w = item.widget()
            if w:
                w.deleteLater()
    
    def show(self):
        for p in self.pixmapList:
            label = QLabel(parent=self.scrollAreaContents)
            label.setAlignment(Qt.AlignHCenter)
            qtimg = QImage(p.samples, p.width, p.height, p.stride, QImage.Format_RGB888)
            # -45 because of various margins... value obtained by trial and error.
            label.setPixmap(QPixmap.fromImage(qtimg).scaled(self.width()-45, self.height()*2, Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation))
            self.scrollAreaLayout.addWidget(label)
        
        # necessary on Mojave with PyInstaller (or previous contents will be shown)
        self.repaint()
Example #10
0
class ScrollBox(QVBoxLayout):
    def __init__(self, screen):
        if screen in window.screens:
            self.screen = screen
            super(ScrollBox, self).__init__(window.screens[screen])
        else:
            self.screen = None

        self.sw = QWidget()
        self.layout = QVBoxLayout()

        for i in range(20):
            childLayout = QHBoxLayout()
            # childLayout.addWidget(tpb)
            c = QComboBox()
            c.addItems(["1", "2", "3"])
            childLayout.addWidget(c)
            childLayout.addWidget(QPushButton("+"))
            childLayout.addWidget(QPushButton("-"))
            childLayout.addWidget(QPushButton("Edit"))

            self.layout.addLayout(childLayout)

        self.sw.setLayout(self.layout)

        self.scrollbox = QScrollArea()
        self.scrollbox.setWidget(self.sw)
        self.scrollbox.setWidgetResizable(True)
        self.scrollbox.setFixedHeight(400)
        self.addWidget(self.scrollbox)


    def updateContent(self):
        for i in reversed(range(self.layout.count())):
            # print(i)
            self.layout.takeAt(i).deleteLater()
Example #11
0
class PluginFrame(QFrame):
    def __init__(self, plugins, highlighted_plugins=None, parent=None):
        """
        :type plugins: list of Plugin
        :type highlighted_plugins: list of Plugin
        """
        super().__init__(parent)
        self.ui = Ui_FramePlugins()
        self.ui.setupUi(self)
        self.model = PluginListModel(plugins,
                                     highlighted_plugins=highlighted_plugins)
        self.ui.listViewPlugins.setModel(self.model)
        self.settings_layout = QVBoxLayout()
        self.ui.groupBoxSettings.setLayout(self.settings_layout)
        self.create_connects()

        try:
            self.restoreGeometry(
                constants.SETTINGS.value("{}/geometry".format(
                    self.__class__.__name__)))
        except TypeError:
            pass

    def create_connects(self):
        self.ui.listViewPlugins.selectionModel().selectionChanged.connect(
            self.on_list_selection_changed)
        for plugin in self.model.plugins:
            if hasattr(plugin, "show_proto_sniff_dialog_clicked"):
                plugin.show_proto_sniff_dialog_clicked.connect(
                    self.parent().parent().show_proto_sniff_dialog)

    def save_enabled_states(self):
        for plugin in self.model.plugins:
            constants.SETTINGS.setValue(plugin.name, plugin.enabled)

    @pyqtSlot()
    def on_list_selection_changed(self):
        i = self.ui.listViewPlugins.currentIndex().row()
        self.ui.txtEditPluginDescription.setText(
            self.model.plugins[i].description)

        if self.settings_layout.count() > 0:
            widget = self.settings_layout.takeAt(0).widget()
            self.settings_layout.removeWidget(widget)
            widget.setParent(None)

        self.settings_layout.addWidget(self.model.plugins[i].settings_frame)
Example #12
0
class PluginFrame(QFrame):
    def __init__(self, plugins, highlighted_plugins=None, parent=None):
        """
        :type plugins: list of Plugin
        :type highlighted_plugins: list of Plugin
        """
        super().__init__(parent)
        self.ui = Ui_FramePlugins()
        self.ui.setupUi(self)
        self.model = PluginListModel(plugins, highlighted_plugins=highlighted_plugins)
        self.ui.listViewPlugins.setModel(self.model)
        self.settings_layout = QVBoxLayout()
        self.ui.groupBoxSettings.setLayout(self.settings_layout)
        self.create_connects()

        try:
            self.restoreGeometry(constants.SETTINGS.value("{}/geometry".format(self.__class__.__name__)))
        except TypeError:
            pass


    def create_connects(self):
        self.ui.listViewPlugins.selectionModel().selectionChanged.connect(self.on_list_selection_changed)
        for plugin in self.model.plugins:
            if hasattr(plugin, "show_proto_sniff_dialog_clicked"):
                plugin.show_proto_sniff_dialog_clicked.connect(self.parent().parent().show_proto_sniff_dialog)

    def save_enabled_states(self):
        for plugin in self.model.plugins:
            constants.SETTINGS.setValue(plugin.name, plugin.enabled)

    @pyqtSlot()
    def on_list_selection_changed(self):
        i = self.ui.listViewPlugins.currentIndex().row()
        self.ui.txtEditPluginDescription.setText(self.model.plugins[i].description)

        if self.settings_layout.count() > 0:
            widget = self.settings_layout.takeAt(0).widget()
            self.settings_layout.removeWidget(widget)
            widget.setParent(None)

        self.settings_layout.addWidget(self.model.plugins[i].settings_frame)
Example #13
0
class AttributeEditor(QScrollArea):
    def __init__(self, obj, title: str = 'Object', parent=None):
        super().__init__(parent)
        self.myObj = obj
        self.myTitle = title
        self.myDict = {title: obj}
        self.initUi()

    def initUi(self):
        self.myWidget = QWidget(self)
        self.myLayout = QVBoxLayout()
        self.myLayout.setContentsMargins(5, 5, 5, 5)
        self.myWidget.setLayout(self.myLayout)
        self.setWidget(self.myWidget)
        self.setWidgetResizable(True)

    def updateUi(self):
        while self.myLayout.count():
            item = self.myLayout.takeAt(0)
            widget = item.widget()
            if widget is not None:
                widget.deleteLater()
            else:
                self.myLayout.removeItem(item)

        widget = AWFactory.createWidget(self.myDict, self.myTitle, self,
                                        self.dataChanged.emit)
        self.myLayout.addWidget(widget)
        self.myLayout.addStretch(0)

    dataChanged = pyqtSignal()

    def iterObj(self):
        if isinstance(self.myObj, dict):
            for name in self.myObj:
                yield (name)
        elif '__dict__' in dir(self.myObj):
            for name in vars(self.myObj):
                if name[0] != '_':
                    yield (name)
        else:
            raise TypeError('Unsupported object with type ' + type(self.myObj))
Example #14
0
class TagListWidget(QWidget):

    # TagLists are widgets which display all of the tags a user has added to an Image.

    def __init__(self, parent, tags=None):

        # Parent is a QWidget, tags is a list of strings

        super().__init__(parent)
        self.setFixedWidth(parent.width() / 8)

        ####### LAYOUT INITIALIZATION #######

        # Widget needs a layout to be able to contain tags
        self.setLayout(QVBoxLayout())
        # Need a container to restrict the size of the tag layout (layouts don't have width)
        tag_container = QWidget(self)
        tag_container.setFixedWidth(parent.width() / 8)
        # Need to be able to access the layout later to manipulate tags
        self.tag_layout = QVBoxLayout()
        self.tag_layout.setAlignment(Qt.AlignTop)
        tag_container.setLayout(self.tag_layout)

        # Add tags to widget as qlabels
        if tags:
            for tag in tags:
                t = QLabel(tag, self)
                self.tag_layout.addWidget(t)
        self.layout().addWidget(tag_container)

    def addTag(self, tag):
        t = QLabel(tag, self)
        self.tag_layout.addWidget(t)

    def updateTags(self, new_tags=None):
        while self.tag_layout.itemAt(0):
            tag = self.tag_layout.takeAt(0).widget()
            tag.deleteLater()
        for tag in new_tags:
            t = QLabel(tag, self)
            self.tag_layout.addWidget(t)
Example #15
0
class MainView(QWidget):
    """
    Represents the main content of the application (containing the source list
    and main context view).
    """
    def __init__(self, parent):
        super().__init__(parent)
        self.layout = QHBoxLayout(self)
        self.setLayout(self.layout)
        left_column = QWidget(parent=self)
        left_layout = QVBoxLayout()
        left_column.setLayout(left_layout)
        self.status = QLabel(_('Waiting to Synchronize'))
        left_layout.addWidget(self.status)
        filter_widget = QWidget()
        filter_layout = QHBoxLayout()
        filter_widget.setLayout(filter_layout)
        filter_label = QLabel(_('Filter: '))
        self.filter_term = QLineEdit()
        self.source_list = SourceList(left_column)
        filter_layout.addWidget(filter_label)
        filter_layout.addWidget(self.filter_term)
        left_layout.addWidget(filter_widget)
        left_layout.addWidget(self.source_list)
        self.layout.addWidget(left_column, 2)
        self.view_holder = QWidget()
        self.view_layout = QVBoxLayout()
        self.view_holder.setLayout(self.view_layout)
        self.layout.addWidget(self.view_holder, 6)

    def update_view(self, widget):
        """
        Update the view holder to contain the referenced widget.
        """
        old_widget = self.view_layout.takeAt(0)
        if old_widget:
            old_widget.widget().setVisible(False)
        widget.setVisible(True)
        self.view_layout.addWidget(widget)
Example #16
0
class MyApp(QMainWindow):
    def __init__(self):
        super().__init__()

        self.add_btn = QPushButton("Add")
        self.add_btn.clicked.connect(self.click_add_btn)

        self.remove_btn = QPushButton("Remove")
        self.remove_btn.clicked.connect(self.click_remove_btn)

        self.scroll_area = QScrollArea()
        self.scroll_area.setWidgetResizable(True)

        self.init_ui()

    def init_ui(self):
        self.main_layout = QVBoxLayout()
        self.main_layout.addWidget(self.add_btn)
        self.main_layout.addWidget(self.remove_btn)
        self.main_layout.addWidget(TestUI())
        self.centralWidget = QWidget()
        self.centralWidget.setLayout(self.main_layout)
        self.setCentralWidget(self.centralWidget)
        self.show()

    def click_add_btn(self):
        self.main_layout.addWidget(TestUI())
        self.setFixedSize(
            self.main_layout.sizeHint()
            + PyQt5.QtCore.QSize(0, TestUI().frameGeometry().height())
        )

    def click_remove_btn(self):
        if self.main_layout.count() > 3:
            child = self.main_layout.takeAt(self.main_layout.count() - 1)
            child.widget().deleteLater()
            print(self.main_layout.count())
            print(self.main_layout.sizeHint())
            self.setFixedSize(self.main_layout.sizeHint())
Example #17
0
class r2sGUI(QWidget):
    def __init__(self, sp_agent, reddit, redraw_func):
        super().__init__()
        self.sp_agent = sp_agent
        self.reddit = reddit
        self.redraw_func = redraw_func
        self.mvb = QVBoxLayout()
        self.subreddit_count = 0
        self.config = None
        self.settings_file = f"{self.sp_agent.username}.ini"
        self.time_dictionary = self.create_time_dictionary()
        self.initialize_UI()

    def initialize_UI(self):
        config = configparser.ConfigParser()
        if os.path.isfile(self.settings_file):
            config.read(self.settings_file)
            self.config = config
            # should probably check that config file is valid (not corrupted)

        # load subreddits
        if not config.sections():
            self.display_no_reddits()
        else:
            for section in config.sections():
                subreddit_box = self.load_subreddit(config[section])
                subreddit_box.delete_button.hide()  # hide for now
                self.mvb.addWidget(subreddit_box)
                self.subreddit_count += 1

        # add button
        add_widget = uic.loadUi("addsubreddit.ui")
        add_subreddit_button = add_widget.add_subreddit_button
        add_subreddit_button.clicked.connect(self.create_new_subreddit)
        self.mvb.addWidget(add_widget)
        add_widget.hide()  # hide for now

        self.setLayout(self.mvb)
        self.setWindowTitle("reddit2Spotify")

    def resize_after_subreddit_count_change(self):
        #  height = self.mvb.sizeHint().height()
        #  width = self.mvb.sizeHint().width()
        #  self.resize(height + 1, width + 1)  # attempt to update label
        #  self.resize(height, width)
        pass

    def display_no_reddits(self):
        # no_subreddits_m means no_subreddits_message
        username = self.sp_agent.username
        no_subreddits_m = f"No subreddits added for {username}."
        no_subreddits = QLabel(no_subreddits_m)
        no_subreddits.setObjectName("no_subreddits_label")
        self.mvb.addWidget(no_subreddits)

    def load_subreddit(self, section):
        # load ui from qtdesigner-made widget
        ss = uic.loadUi("singlesubreddit.ui")

        # subreddit name
        ss.subreddit_entry_textbox.setText(section["name"])

        # sorting method dropdown
        # only show time sorting if "top" is selected
        def time_lambda():
            self.allow_time(ss.sorting_method_combo,
                            [ss.pre_time_label, ss.time_combo])

        ss.sorting_method_combo.currentIndexChanged.connect(time_lambda)
        index = ss.sorting_method_combo.findText(section["sorting method"],
                                                 QtCore.Qt.MatchFixedString)
        if index >= 0:
            ss.sorting_method_combo.setCurrentIndex(index)

        # flair text
        flair_placeholder_text = "Case sensitive. Leave blank for all posts."
        ss.post_flair_textbox.setPlaceholderText(flair_placeholder_text)
        ss.post_flair_textbox.setText(section["flair text"])

        # new and existing playlist radio buttons
        def playlist_radio_toggled():
            self.playlist_radio_toggled(ss)

        ss.new_pl_radio.toggled.connect(playlist_radio_toggled)
        ss.existing_pl_radio.toggled.connect(playlist_radio_toggled)
        pl_type = section["playlist type"]
        ss.new_pl_radio.setChecked(pl_type == "new")
        ss.existing_pl_radio.setChecked(pl_type == "existing")

        # save button
        #  def save_button_clicked(): self.save_subreddit(ss)
        #  ss.save_button.clicked.connect(save_button_clicked)

        # delete button
        def delete_button_clicked():
            self.delete_subreddit(ss)

        ss.delete_button.clicked.connect(delete_button_clicked)

        # run button
        def run_button_clicked():
            self.run_now(ss)

        ss.run_now_button.clicked.connect(run_button_clicked)

        # schedule button
        def schedule_button_clicked():
            self.schedule(ss)

        ss.schedule_button.clicked.connect(schedule_button_clicked)

        ss.console.text_changed.connect(self.redraw_func)

        return ss

    @QtCore.pyqtSlot(object, list)
    def allow_time(self, sorting, to_be_hidden):
        index = sorting.currentIndex()
        if index == 0 or index == 4:
            #  time_hb.show()
            for widget in to_be_hidden:
                widget.show()
        else:
            #  time.setEnabled(False)
            for widget in to_be_hidden:
                widget.hide()

    @QtCore.pyqtSlot(QWidget)
    def playlist_radio_toggled(self, ss):
        if ss.new_pl_radio.isChecked():  # new
            # hide existing playlist stuff
            ss.existing_pl_id_label.hide()
            ss.existing_pl_id_textbox.hide()
            # show new playlist stuff
            ss.new_pl_name_label.show()
            ss.new_pl_name_textbox.show()
            ss.new_pl_desc_label.show()
            ss.new_pl_desc_textbox.show()
        else:  # existing
            # show existing playlist stuff
            ss.existing_pl_id_label.show()
            ss.existing_pl_id_textbox.show()
            # hide new playlist stuff
            ss.new_pl_name_label.hide()
            ss.new_pl_name_textbox.hide()
            ss.new_pl_desc_label.hide()
            ss.new_pl_desc_textbox.hide()

    def create_new_subreddit(self):
        print("Creating new subreddit...")
        # remove add button
        add_button = self.mvb.takeAt(self.mvb.count() - 1).widget()

        # remove "no subreddit" label if there are currently no subreddits
        if self.subreddit_count == 0:
            no_subreddit_label = self.mvb.takeAt(self.mvb.count() - 1).widget()
            sip.delete(no_subreddit_label)

        # add new subreddit
        new_subreddit = self.load_subreddit(self.config["DEFAULT"])
        self.mvb.addWidget(new_subreddit)
        self.subreddit_count += 1

        # add add button back
        self.mvb.addWidget(add_button)
        self.resize_after_subreddit_count_change()

    @QtCore.pyqtSlot(QWidget)
    def save_subreddit(self, ss):
        section = ss.subreddit_entry_textbox.text().lower()
        if not section:  # no subreddit name
            return

        cf = self.config
        cf.read(self.settings_file)

        try:
            cf.add_section(section)
        except configparser.DuplicateSectionError:
            pass

        cf[section]["name"] = section
        cf[section]["sorting method"] = ss.sorting_method_combo.currentText()
        # saves top time period even if not sorting by top
        cf[section]["top time period"] = ss.time_combo.currentText()
        cf[section]["flair text"] = ss.post_flair_textbox.text()
        pl_type = "new" if ss.new_pl_radio.isChecked() else "existing"
        cf[section]["playlist type"] = pl_type

        with open(self.settings_file, 'w') as configfile:
            cf.write(configfile)

    @QtCore.pyqtSlot(QWidget)
    def delete_subreddit(self, ss):
        print("Deleting subreddit...")
        self.mvb.removeWidget(ss)
        self.remove_subreddit_from_settings(ss)
        sip.delete(ss)  # honestly have no clue what this is but it works
        self.subreddit_count -= 1

        # add "no subreddits" label if we just deleted the last subreddit
        if self.subreddit_count == 0:
            add_button = self.mvb.takeAt(self.mvb.count() - 1).widget()
            self.display_no_reddits()
            self.mvb.addWidget(add_button)

        self.resize_after_subreddit_count_change()
        self.resize_after_subreddit_count_change()

    def remove_subreddit_from_settings(self, ss):
        section = ss.subreddit_entry_textbox.text().lower()
        cf = self.config
        cf.read(self.settings_file)
        deleted = cf.remove_section(section)
        if not deleted:
            print(f"Error deleting section {section}.", file=sys.stderr)
        with open(self.settings_file, 'w') as configfile:
            cf.write(configfile)

    def run_now(self, ss):
        print("Running subreddit settings now...")

        subreddit_name = ss.subreddit_entry_textbox.text()
        if not subreddit_name:
            # first one doesn't update UI, do it twice
            # not a great solution, but works for now
            ss.console.change_text("Error: No subreddit name", 1)
            ss.console.change_text("Error: No subreddit name", 1)
            self.resize_after_subreddit_count_change()
            return
        subreddit = self.reddit.subreddit(subreddit_name)

        self.save_subreddit(ss)
        playlist_id = None
        if ss.new_pl_radio.isChecked():  # new
            pl_name = ss.new_pl_name_textbox.text()
            if not pl_name:  # no name given for new playlist
                pl_name = "r2s playlist for " + self.sp_agent.username
            pl_desc = ss.new_pl_desc_textbox.text()
            playlist = self.sp_agent.create_playlist(pl_name, pl_desc)
            playlist_id = playlist["id"]
        else:  # existing playlist
            playlist_id = ss.existing_pl_id_textbox.text()
        print("Playlist ID:", playlist_id)

        fetching_message = f"Fetching posts..."
        ss.console.change_text(fetching_message, 0)

        sorting_method = ss.sorting_method_combo.currentText()
        time = self.time_dictionary[ss.time_combo.currentText()]
        posts = self.get_posts(subreddit, sorting_method, time)

        for post in posts:
            matches_flair_text = True
            flair = ss.post_flair_textbox.text()
            if flair:
                if post.link_flair_text != flair:
                    matches_flair_text = False

            is_spotify = ("spotify" in post.url)
            is_track = ("track" in post.url)
            if matches_flair_text and is_spotify and is_track:
                valid_part = post.url.split('?')[0]
                ss.console.change_text(f"Adding {valid_part}", 0)
                self.sp_agent.add_songs_to_playlist([valid_part], playlist_id)

        # same as above, doing it twice so it updates UI
        ss.console.change_text("Ready", 0)
        ss.console.change_text("Ready", 0)

    def create_time_dictionary(self):
        return {
            "the past hour": "hour",
            "the past 24 hours": "day",
            "the past week": "week",
            "the past month": "month",
            "the past year": "year",
            "all time": "all"
        }

    # I feel like this function should exist in another module
    def get_posts(self, subreddit, sorting_method, time):
        if sorting_method == "top":
            return subreddit.top(time)
        elif sorting_method == "hot":
            return subreddit.hot()
        elif sorting_method == "new":
            return subreddit.new()
        elif sorting_method == "rising":
            return subreddit.rising()
        elif sorting_method == "controversial":
            return subreddit.controversial(time)
        else:
            print("Error: Invalid sorting method.", file=sys.stderr)
            pass

    def schedule(self, ss):
        print("Scheduling subreddit settings now...")
class FrameCheckOption(QWidget):
    def __init__(self, parent: QObject, controller: TouchManagerController,
                 model: TouchManagerModel):
        super(QWidget, self).__init__()
        self.parent = parent
        self.model = model
        self.controller = controller
        self.main_lay = QVBoxLayout()
        self.bottom_lay = QVBoxLayout()
        self.scroll_wid = QWidget()
        self.scrollable = QScrollArea()
        self.lay = QVBoxLayout()
        self.lbls = []
        self.lblsColors = []
        self.lblImageColors = []
        self.rBtns = []
        self.btnAddCoord = QPushButton()
        self.aroundLbl = QLabel()
        self.cBoxAround = QComboBox()
        self.initMainUI()
        self.initUI({
            'coordinates': [[0.5, 0.5]],
            'values': [[255, 255, 255]],
            'around': 5,
            'currentScreenColors': [[255, 255, 255]]
        })
        self.initConnectors()

    def initMainUI(self):
        self.setLayout(self.main_lay)
        self.aroundLbl.setText("Around factor:")
        self.cBoxAround.addItems(str(i) for i in range(self.model.MAX_AROUND))
        self.cBoxAround.setFixedHeight(20)
        self.cBoxAround.setMaximumWidth(100)
        self.cBoxAround.currentIndexChanged.connect(
            self.controller.requestChangeAround)
        self.btnAddCoord.setText("add coordinate")
        self.btnAddCoord.clicked.connect(
            self.controller.requestFrameCheckCoordAdd)

        self.lay.setAlignment(Qt.AlignTop)
        self.scroll_wid.setLayout(self.lay)
        self.scrollable.setWidgetResizable(True)
        self.scrollable.setWidget(self.scroll_wid)
        self.scrollable.setContentsMargins(0, 0, 0, 0)

        self.main_lay.addWidget(self.scrollable)

        self.bottom_lay.addWidget(self.btnAddCoord)
        h_lay = QHBoxLayout()
        h_lay.addWidget(self.aroundLbl)
        h_lay.addWidget(self.cBoxAround)
        self.bottom_lay.addLayout(h_lay)

        self.main_lay.addLayout(self.bottom_lay)

    def _clearLayout(self):
        self.lbls.clear()
        self.lbls = []
        self.rBtns.clear()
        self.rBtns = []
        self.lblsColors.clear()
        self.lblsColors = []
        self.lblImageColors.clear()
        self.lblImageColors = []
        for i in reversed(range(self.lay.count())):
            row_lay = self.lay.takeAt(i)
            for j in reversed(range(row_lay.count())):
                row_lay.itemAt(j).widget().setParent(None)
            row_lay.setParent(None)

    def _setAroundSafe(self, around):
        self.cBoxAround.blockSignals(True)
        self.cBoxAround.setCurrentIndex(around)
        self.cBoxAround.blockSignals(False)

    def initUI(self, newData: dict):
        if 'around' in newData:
            self._setAroundSafe(newData['around'])
        coords_num = len(newData['coordinates'])
        l = len(newData['currentScreenColors'])
        w, h = self.controller.current_image_size
        for i in range(coords_num):
            lay_row = QHBoxLayout()
            coord = newData['coordinates'][i]
            x, y = int((coord[0] * w)), int((coord[1] * h))
            lbl_point = QLabel("%15s" % ("C%d: %4d , %4d" % (i, x, y)))
            lbl_point.setFixedWidth(80)
            lay_row.addWidget(lbl_point)
            # lay_row.addWidget(QLabel("X: %4d" % (coord[0] * w)))
            # lay_row.addWidget(QLabel("Y: %4d" % (coord[1] * h)))
            colors = newData['values'][i]
            lblColor = QLabel("")
            lblColor.setStyleSheet("background-color: rgb({},{},{});".format(
                colors[0], colors[1], colors[2]))
            lblColor.mousePressEvent = (partial(self.onManualChoose, i))
            lblColor.setToolTip("Target value RGB=(%d, %d, %d)" %
                                (colors[0], colors[1], colors[2]))
            lblColor.setMaximumWidth(40)
            self.lblsColors.append(lblColor)
            btnSet = QPushButton("set->")
            btnSet.setMaximumWidth(45)
            btnSet.clicked.connect(
                partial(
                    self.controller.requestSetCurrentColorToFrameCheckColor,
                    i))
            lblimgColor = QLabel("")
            lblimgColor.setMaximumWidth(20)
            color_ = newData['currentScreenColors'][i]
            lblimgColor.setStyleSheet(
                "background-color: rgb({},{},{});".format(
                    color_[0], color_[1], color_[2]))
            lblimgColor.setToolTip("Current screenshot RGB=(%d, %d, %d)" %
                                   (color_[0], color_[1], color_[2]))
            lblimgColor.setToolTipDuration(20 * 1000)
            self.lblImageColors.append(lblimgColor)
            lay_row.addWidget(lblimgColor)
            lay_row.addWidget(btnSet)
            lay_row.addWidget(lblColor)
            rbtn = QRadioButton()
            self.rBtns.append(rbtn)
            rbtn.setText("")
            rbtn.setFixedWidth(20)
            rbtn.toggled.connect(
                partial(self.controller.onCoordinateSelected, i))
            lay_row.addWidget(rbtn)
            self.lay.addLayout(lay_row)
        if len(self.rBtns) > 0:
            self.rBtns[self.controller.selectedCoordinateIndex].blockSignals(
                True)
            self.rBtns[self.controller.selectedCoordinateIndex].setChecked(
                True)
            self.rBtns[self.controller.selectedCoordinateIndex].blockSignals(
                False)

    def onManualChoose(self, i, event):
        self.controller.rquestFrameCheckCoordinateColorManualChange(i)

    def updateCurrentColors(self, colors_img):
        num = min(len(colors_img), len(self.lblImageColors))
        for i in range(num):
            color = colors_img[i]
            self.lblImageColors[i].setStyleSheet(
                "background-color: rgb({},{},{});".format(
                    color[0], color[1], color[2]))

    def initConnectors(self):
        self.controller.onCurrentScreenColorsChanged.connect(
            self.updateCurrentColors)
        return
        # for i, rbtn in enumerate(self.rBtns):
        #   self.rbtn.toggled.connect(partial(self.controller.onCoordinateSelected, i))

    def changeData(self, new_data):
        self._clearLayout()
        self.initUI(new_data)
        if 'around' in new_data:
            if self.cBoxAround.currentIndex != new_data['around']:
                self._setAroundSafe(new_data['around'])

    def deleteLater(self):
        self.controller.onCurrentScreenColorsChanged.disconnect(
            self.updateCurrentColors)
        super(FrameCheckOption, self).deleteLater()
Example #19
0
class RmsFrame(QFrame):
    def __init__(self, cu):
        super().__init__()
        self.cu = cu
        self.session = RaceSession()
        self.resetRMS()
        self.buildframe()
        self.driverBtn = {}
        self.driverObj = {}
        self.lapcount = {}
        self.totalTime = {}
        self.laptime = {}
        self.bestlaptime = {}
        self.fuelbar = {}
        self.pits = {}
#        QBAcolor = QByteArray()
#        QBAcolor.append('color')
#        self.animation = anim = QPropertyAnimation(self, QBAcolor, self)
#        anim.setDuration(250)
#        anim.setLoopCount(2)
#        anim.setStartValue(QColor(230,230, 0))
#        anim.setEndValue(QColor(0, 0, 0))
#        anim.setKeyValueAt(0.5, QColor(150,100,0))

    def buildframe(self):
        self.vLayout = QVBoxLayout(self)
        self.hBtnLayout = QHBoxLayout()
        self.vLayout.addLayout(self.hBtnLayout)
        # Add driver to grid
        self.addDriverBtn = QPushButton('(A)dd Driver')
        self.addDriverKey = QShortcut(QKeySequence("a"), self)
        self.hBtnLayout.addWidget(self.addDriverBtn)
        self.addDriverBtn.clicked.connect(self.addDriver)
        self.addDriverKey.activated.connect(self.addDriver)
        # Assign Controller
        self.assignCtrlBtn = QPushButton('Assign Controller')
        self.hBtnLayout.addWidget(self.assignCtrlBtn)
        self.assignCtrlBtn.clicked.connect(self.openCtrlDialog)
        # Setup a race
        self.setupRace = QPushButton('Setup a Race')
        self.hBtnLayout.addWidget(self.setupRace)
        self.setupRace.clicked.connect(self.openRaceDlg)
        # Code cars
        self.codeBtn = QPushButton('(C)ode')
        self.codeKey = QShortcut(QKeySequence("c"), self)
        self.hBtnLayout.addWidget(self.codeBtn)
        self.codeBtn.clicked.connect(self.pressCode)
        self.codeKey.activated.connect(self.pressCode)
        # Start pace car
        self.paceBtn = QPushButton('(P)ace')
        self.paceKey = QShortcut(QKeySequence("p"), self)
        self.hBtnLayout.addWidget(self.paceBtn)
        self.paceBtn.clicked.connect(self.setPace)
        self.paceKey.activated.connect(self.setPace)
        # set Speed
        self.setSpeedBtn = QPushButton('Set (S)peed')
        self.setSpeedKey = QShortcut(QKeySequence("s"), self)
        self.hBtnLayout.addWidget(self.setSpeedBtn)
        self.setSpeedBtn.clicked.connect(self.setSpeed)
        self.setSpeedKey.activated.connect(self.setSpeed)
        # set Brakes
        self.setBrakeBtn = QPushButton('Set (B)rake')
        self.setBrakeKey = QShortcut(QKeySequence("b"), self)
        self.hBtnLayout.addWidget(self.setBrakeBtn)
        self.setBrakeBtn.clicked.connect(self.setBrake)
        self.setBrakeKey.activated.connect(self.setBrake)
        # Set Fuel
        self.setFuelBtn = QPushButton('Set (F)uel')
        self.setFuelKey = QShortcut(QKeySequence("f"), self)
        self.hBtnLayout.addWidget(self.setFuelBtn)
        self.setFuelBtn.clicked.connect(self.setFuel)
        self.setFuelKey.activated.connect(self.setFuel)
        # Reset CU
        self.resetBtn = QPushButton('(R)eset')
        self.resetKey = QShortcut(QKeySequence("r"), self)
        self.hBtnLayout.addWidget(self.resetBtn)
        self.resetBtn.clicked.connect(self.resetRMS)
        self.resetKey.activated.connect(self.resetRMS)
        # Start/Pause Race Enter
        self.startRaceBtn = QPushButton(
            'Start Race or Enter changed Settings (Spacebar)')
        self.startRaceBtn.clicked.connect(self.racestart)
        self.spacekey = QShortcut(QKeySequence("Space"), self)
        self.spacekey.activated.connect(self.racestart)
        self.hStartBtnLayout = QHBoxLayout()
        self.hStartBtnLayout.addStretch(1)
        self.hStartBtnLayout.addWidget(self.startRaceBtn)
        self.hStartBtnLayout.setAlignment(self.startRaceBtn, Qt.AlignHCenter)
        self.hStartBtnLayout.addStretch(1)
        self.pitLaneStatus = QLabel()
        self.hStartBtnLayout.addWidget(QLabel('Pitlane'))
        self.hStartBtnLayout.addWidget(self.pitLaneStatus)
        self.fuelmode = QLabel()
        self.hStartBtnLayout.addWidget(QLabel('Fuel Mode'))
        self.hStartBtnLayout.addWidget(self.fuelmode)
        self.lapCounter = QLabel()
        self.hStartBtnLayout.addWidget(QLabel('Lap Counter'))
        self.hStartBtnLayout.addWidget(self.lapCounter)
        self.vLayout.addLayout(self.hStartBtnLayout)
        self.vLayout.setAlignment(self.hStartBtnLayout, Qt.AlignTop)
        #        self.sepline = QFrame()
        #        self.sepline.setFrameShape(QFrame.HLine)
        #        self.sepline.setFrameShadow(QFrame.Sunken)
        #        self.vLayout.addWidget(self.sepline)
        #        self.vLayout.setAlignment(self.sepline, Qt.AlignTop)
        # Driver Grid
        self.vLayout.addLayout(self.buildGrid())
        # Session Info
        self.racemode = QLabel('No Race Started')
        self.racemode.setAlignment(Qt.AlignCenter)
        self.racemode.setStyleSheet(
            "QLabel{ border-radius: 10px; background-color: grey; center; color: blue; font: 30pt}"
        )
        self.vLayout.addWidget(self.racemode)
        self.vLayout.setAlignment(self.racemode, Qt.AlignBottom)

    def buildGrid(self):
        self.mainLayout = QGridLayout()
        self.mainLayout.setSpacing(10)
        self.mainLayout.setHorizontalSpacing(10)
        self.headerFont = QFont()
        self.headerFont.setPointSize(14)
        self.headerFont.setBold(True)
        self.labelArr = [
            'Pos', 'Driver', 'Total', 'Laps', 'Laptime', 'Best Lap', 'Fuel',
            'Pits'
        ]
        for index, label in enumerate(self.labelArr):
            self.headerLabel = QLabel(label)
            self.headerLabel.setFont(self.headerFont)
            self.mainLayout.addWidget(self.headerLabel, 0, index,
                                      Qt.AlignHCenter)
        self.mainLayout.setColumnStretch(1, 1)
        self.mainLayout.setColumnStretch(2, 1)
        self.mainLayout.setColumnStretch(3, 2)
        self.mainLayout.setColumnStretch(4, 3)
        self.mainLayout.setColumnStretch(5, 3)
        self.mainLayout.setColumnStretch(6, 2)
        self.mainLayout.setColumnStretch(7, 1)
        return self.mainLayout

    def openCtrlDialog(self):
        self.ctrlDialog = CtrlDialog(self.driverArr)
        if self.ctrlDialog.exec_():
            self.driverArr = self.ctrlDialog.newDriverArr

    def openRaceDlg(self):
        self.setupRaceDlg = RaceModeDialog()
        self.session.session = None
        self.session.type = None
        if self.setupRaceDlg.exec_():
            for driver in self.driverArr:
                driver.bestLapTime = None
                driver.time = None
                driver.lapcount = 0
                driver.pitcount = 0
            self.session.setRace(self.setupRaceDlg.getRaceModeInfo())
            self.racemode.setText(self.session.session + ' ' +
                                  str(self.session.amount) + ' ' +
                                  self.session.type)
            self.clearCU()
            self.cu.start()
        else:
            self.setupRaceDlg.close()

    def getColor(self):
        if self.mainLayout.itemAtPosition(1, 1):
            return self.mainLayout.itemAtPosition(1,
                                                  1).widget().palette().text()
        else:
            return None

    def setColor(self, color):
        PBwidget = self.mainLayout.itemAtPosition(1, 1).widget()
        palette = PBwidget.palette()
        palette.setColor(PBwidget.foregroundRole(), color)
        PBwidget.setFlat(True)
        PBwidget.setAutoFillBackground(True)
        PBwidget.setPalette(palette)

    color = pyqtProperty(QColor, getColor, setColor)

    def addDriver(self):
        driverRow = self.mainLayout.rowCount()
        if driverRow > 8:
            return
        driver = self.driverArr[driverRow - 1]
        self.posFont = QFont()
        self.posFont.setPointSize(35)
        self.posFont.setBold(True)
        self.driverPos = QLabel(str(driverRow))
        self.driverPos.setStyleSheet(
            "QLabel{ border-radius: 10px; border-color: black; border: 5px solid black; background-color: white}"
        )
        self.driverPos.setSizePolicy(QSizePolicy.Maximum,
                                     QSizePolicy.Expanding)
        self.driverPos.setFont(self.posFont)
        self.mainLayout.addWidget(self.driverPos, driverRow, 0)
        self.driverBtn[driverRow] = driver.getNameBtn()
        self.driverBtn[driverRow].setSizePolicy(QSizePolicy.Preferred,
                                                QSizePolicy.Expanding)
        self.mainLayout.addWidget(self.driverBtn[driverRow], driverRow, 1)
        self.driverObj[driverRow] = driver
        self.driverBtn[driverRow].clicked.connect(lambda: self.changeDriver(
            self.driverBtn[driverRow], self.driverObj[driverRow]))
        self.lapcount[driverRow] = driver.getLapCountLCD()
        self.lapcount[driverRow].setSizePolicy(QSizePolicy.Preferred,
                                               QSizePolicy.Expanding)
        self.mainLayout.addWidget(self.lapcount[driverRow], driverRow, 3)
        self.totalFont = QFont()
        self.totalFont.setPointSize(25)
        self.totalFont.setBold(True)
        self.totalTime[driverRow] = QLabel('00:00')
        self.totalTime[driverRow].setStyleSheet(
            "QLabel{ border-radius: 10px; border-color: black; border: 5px solid black; background-color: white}"
        )
        self.totalTime[driverRow].setSizePolicy(QSizePolicy.Preferred,
                                                QSizePolicy.Expanding)
        self.totalTime[driverRow].setFont(self.totalFont)
        self.mainLayout.addWidget(self.totalTime[driverRow], driverRow, 2)
        self.laptime[driverRow] = driver.getLapLCD()
        self.laptime[driverRow].setSizePolicy(QSizePolicy.Preferred,
                                              QSizePolicy.Expanding)
        self.mainLayout.addWidget(self.laptime[driverRow], driverRow, 4)
        self.bestlaptime[driverRow] = driver.getBestLapLCD()
        self.bestlaptime[driverRow].setSizePolicy(QSizePolicy.Preferred,
                                                  QSizePolicy.Expanding)
        self.mainLayout.addWidget(self.bestlaptime[driverRow], driverRow, 5)
        self.fuelbar[driverRow] = driver.getFuelBar()
        self.fuelbar[driverRow].setSizePolicy(QSizePolicy.Minimum,
                                              QSizePolicy.Preferred)
        self.mainLayout.addWidget(self.fuelbar[driverRow], driverRow, 6)
        self.pits[driverRow] = driver.getPits()
        self.pits[driverRow].setSizePolicy(QSizePolicy.Preferred,
                                           QSizePolicy.Preferred)
        self.mainLayout.addWidget(self.pits[driverRow], driverRow, 7)

    def racestart(self):
        self.cu.start()
#        self.mainLayout.itemAtPosition(1, 5).widget().setPits('Pit')
#        self.mainLayout.itemAtPosition(1, 5).widget().setPits('Track')

    def resetRMS(self):
        if hasattr(self, 'driverArr'):
            for driverObj in self.driverArr:
                driverObj.deleteLater()
        self.start = None
        self.driverArr = [RmsDriver(num) for num in range(1, 9)]

        self.clearCU()

        if hasattr(self, 'mainLayout'):
            while True:
                widgetToRemove = self.mainLayout.takeAt(0)
                if widgetToRemove == None:
                    break
                widgetToRemove.widget().deleteLater()
            racemode = self.vLayout.takeAt(3)
            mainItem = self.vLayout.takeAt(2)
            self.vLayout.removeItem(racemode)
            self.vLayout.removeItem(mainItem)
            mainItem.deleteLater()
            self.vLayout.addLayout(self.buildGrid())
            self.vLayout.addWidget(self.racemode)

    def clearCU(self):
        # discard remaining timer messages
        status = self.cu.request()
        while not isinstance(status, ControlUnit.Status):
            status = self.cu.request()
        self.status = status
        # reset cu timer
        self.cu.reset()

    def pressCode(self):
        print('press Code')
        self.cu.request(b'T8')

    def setFuel(self):
        print('set fuel')
        self.cu.request(b'T7')

    def setPace(self):
        print('pace car')
        self.cu.request(b'T1')

    def setSpeed(self):
        print('set speed')
        self.cu.request(b'T5')

    def setBrake(self):
        print('set brake')
        self.cu.request(b'T6')

    def changeDriver(self, driverButton, driverObj):
        self.driverChangeText = QInputDialog.getText(
            self, 'Change driver name', 'Driver Name', 0,
            driverButton.text().split('\n')[0])
        if self.driverChangeText[1] == True:
            driverButton.setText(self.driverChangeText[0] + '\n' + 'Ctrl: ' +
                                 str(driverObj.CtrlNum))
            driverObj.name = self.driverChangeText[0]

    def updateDisplay(self, binMode):
        if binMode != None:
            if binMode[2] == '1':
                self.fuelmode.setText('Real')
            elif binMode[3] == '1':
                self.fuelmode.setText('On')
            elif binMode[3] == '0':
                self.fuelmode.setText('Off')
            if binMode[1] == '1':
                self.pitLaneStatus.setText('Exists')
            else:
                self.pitLaneStatus.setText('Missing')
            if binMode[0] == '1':
                self.lapCounter.setText('Exists')
            else:
                self.lapCounter.setText('Missing')

        driversInPlay = [driver for driver in self.driverArr if driver.time]
        if len(driversInPlay) + 1 > self.mainLayout.rowCount():
            self.addDriver()
        for pos, driver in enumerate(sorted(driversInPlay, key=posgetter),
                                     start=1):
            if pos == 1:
                if hasattr(self, 'leader') and self.leader != driver:
                    print('pos change')


#                self.animation.start()
                self.leader = driver
                t = formattime(driver.time - self.start, True)
            elif driver.lapcount == self.leader.lapcount:
                t = '+%ss' % formattime(driver.time - self.leader.time)
            else:
                gap = self.leader.lapcount - driver.lapcount
                t = '+%d Lap%s' % (gap, 's' if gap != 1 else '')
            self.driverBtn[pos].setText(driver.name + '\n' + 'Ctrl: ' +
                                        str(driver.CtrlNum))
            self.totalTime[pos].setText(t)
            self.lapcount[pos].display(driver.lapcount)
            self.laptime[pos].display(formattime(driver.lapTime))
            self.bestlaptime[pos].display(formattime(driver.bestLapTime))
            self.fuelbar[pos].setValue(driver.fuellevel)
            if driver.fuellevel > 0:
                self.fuelbar[pos].setStyleSheet(
                    "QProgressBar{ color: white; background-color: black; border: 5px solid black; border-radius: 10px; text-align: center}\
                                                 QProgressBar::chunk { background: qlineargradient(x1: 1, y1: 0.5, x2: 0, y2: 0.5, stop: 0 #00AA00, stop: "
                    + str(0.92 - (1 / (driver.fuellevel))) +
                    " #22FF22, stop: " + str(0.921 - (1 /
                                                      (driver.fuellevel))) +
                    " #22FF22, stop: " + str(1.001 - (1 /
                                                      (driver.fuellevel))) +
                    " red, stop: 1 #550000); }")
            self.pits[pos].display(driver.pitcount)
        if hasattr(self, 'leader') and self.session.session != None:
            if self.session.type != None:
                self.racemode.setText(self.session.session + ' ' +
                                      str(self.session.amount) + ' ' +
                                      self.session.type)
            if self.session.type == 'Laps':
                if self.leader.lapcount > self.session.amount:
                    self.racestart()
                    self.session.saveSessionData(driversInPlay)
                    self.clearCU()
                    self.session.sessionOver()
            elif self.session.type == 'Timed':
                if self.leader.time - self.start > self.session.amount * 60000:
                    self.racestart()
                    self.session.saveSessionData(driversInPlay)
                    self.clearCU()
                    self.session.sessionOver()
            elif self.session.type == None:
                self.session.session = None
                self.showLeaderboard()

    def showLeaderboard(self):
        self.leaderBoard = LBDialog(self.session.leaderboard)
        self.leaderBoard.show()
Example #20
0
class AutoEditor(TableInfoChanger):
    """Ух, шайтан
    класс, в которых запихнули кишки из PathEditor, который основан на ViewShower,
     со всеми вытекающими для совместимости"""
    combo_update = ViewInfoChanger.combo_update

    def __init__(self, header, info, parent):
        super().__init__(header, info, parent)
        self.combo_change_idx = {"Водитель": {}}
        self.slave_drivers_layout = QVBoxLayout()
        push_btn = self.main_layout.takeAt(self.main_layout.count() -
                                           1).widget()

        self.auto_id = info[0]
        add_btn = QPushButton("Добавить")
        add_btn.clicked.connect(lambda e: self.add_cell(-1))
        self.main_layout.addWidget(add_btn)

        way = self.db.execute(
            f"SELECT CONCAT(`Фамилия`,' ', `Имя`,' ',`Отчество`), `назначение автомобиля водителю`.`Табельный номер` FROM `назначение автомобиля водителю` join `водитель` on `водитель`.`Табельный номер` = `назначение автомобиля водителю`.`Табельный номер` where `Номер автомобиля` = '{self.auto_id}'"
        )
        for pos, point in enumerate(way, start=-1):
            auto_info = str(point[0])
            self.combo_change_idx["Водитель"][auto_info] = point[1]
            self.add_cell(pos, auto_info)

        self.main_layout.addLayout(self.slave_drivers_layout)
        self.main_layout.addWidget(push_btn)

    def add_cell(self, pos: int, txt=""):
        """Вставляет ячейку ниже активирующей кнопки для вставки на уровне надо передать ::pos:: = -1"""

        cell = QHBoxLayout()
        edi = QLineEdit()
        edi.setText(txt)

        add_btn = QPushButton("Добавить")
        del_btn = QPushButton("Удалить")

        cmb = QComboBox()
        cmb.addItem(txt)

        edi.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        cmb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        add_btn.clicked.connect(lambda e, c=cell: self.add_cell(c.pos))
        del_btn.clicked.connect(lambda e, c=cell: self.del_cell(c.pos))

        edi.editingFinished.connect(
            lambda c=cmb, t=edi.text: self.combo_update("Водитель", c, t()
                                                        ))  # le-kostyl
        cell.pos = pos
        cell.addWidget(edi)
        cell.addWidget(cmb)
        cell.addWidget(add_btn)
        cell.addWidget(del_btn)
        for i in range(pos + 1, self.slave_drivers_layout.count()):
            cell_to_move = self.slave_drivers_layout.itemAt(i)
            cell_to_move.pos += 1
        cell.pos += 1  # для вставки ниже активированной кнопки

        self.slave_drivers_layout.insertLayout(cell.pos, cell)

    def del_cell(self, pos):
        cell: QVBoxLayout
        cell = self.slave_drivers_layout.takeAt(pos)
        for i in range(cell.count()):
            w = cell.takeAt(0).widget()
            w.deleteLater()
        cell.deleteLater()

        for i in range(pos, self.slave_drivers_layout.count()):
            cell_to_move = self.slave_drivers_layout.itemAt(i)
            cell_to_move.pos -= 1

    def push_point_changes(self):
        params = []
        for i in range(self.slave_drivers_layout.count()):
            cell = self.slave_drivers_layout.itemAt(i)
            w = cell.itemAt(1).widget()
            driver = w.currentText()
            if driver:
                driver_id = self.combo_change_idx["Водитель"][driver]
                params.append((driver_id, self.auto_id))

        query = f" insert into `назначение автомобиля водителю` values(%s, %s)"
        self.db.execute(
            "delete from `назначение автомобиля водителю` where `Номер автомобиля` = %s",
            (self.auto_id, ))
        self.db.executemany(query, params)
        self.db.commit()

    def push_changes(self):
        self.push_point_changes()
        super().push_changes()
Example #21
0
class ConversationView(QWidget):
    """
    Renders a conversation.
    """
    def __init__(self,
                 source_db_object: Source,
                 sdc_home: str,
                 controller: Client,
                 parent=None):
        super().__init__(parent)
        self.source = source_db_object
        self.sdc_home = sdc_home
        self.controller = controller
        self.setStyleSheet("background-color: #fff;")

        self.container = QWidget()
        self.conversation_layout = QVBoxLayout()
        self.container.setLayout(self.conversation_layout)
        self.container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        self.scroll = QScrollArea()
        self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.scroll.setWidget(self.container)
        self.scroll.setWidgetResizable(True)

        # Completely unintuitive way to ensure the view remains scrolled to the
        # bottom.
        sb = self.scroll.verticalScrollBar()
        sb.rangeChanged.connect(self.move_to_bottom)

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.scroll)
        self.setLayout(main_layout)

        self.update_conversation(self.source.collection)

    def update_conversation(self, collection: list) -> None:
        # clear all old items
        while True:
            w = self.conversation_layout.takeAt(0)
            if w:  # pragma: no cover
                del w
            else:
                break

        # add new items
        for conversation_item in collection:
            if conversation_item.filename.endswith('msg.gpg'):
                self.add_item_content_or(self.add_message, conversation_item,
                                         "<Message not yet downloaded>")
            elif conversation_item.filename.endswith('reply.gpg'):
                self.add_item_content_or(self.add_reply, conversation_item,
                                         "<Reply not yet downloaded>")
            else:
                self.add_file(self.source, conversation_item)

    def add_item_content_or(self, adder, item, default):
        """
        Private helper function to add correct message to conversation widgets
        """
        if item.is_downloaded is False:
            adder(item.uuid, default)
        else:
            adder(item.uuid, get_data(self.sdc_home, item.filename))

    def add_file(self, source_db_object, submission_db_object):
        """
        Add a file from the source.
        """
        self.conversation_layout.addWidget(
            FileWidget(source_db_object, submission_db_object,
                       self.controller))

    def move_to_bottom(self, min_val, max_val):
        """
        Handler called when a new item is added to the conversation. Ensures
        it's scrolled to the bottom and thus visible.
        """
        self.scroll.verticalScrollBar().setValue(max_val)

    def add_message(self, message_id: str, message: str) -> None:
        """
        Add a message from the source.
        """
        self.conversation_layout.addWidget(
            MessageWidget(message_id, message,
                          self.controller.message_sync.message_downloaded))

    def add_reply(self, message_id: str, reply: str, files=None) -> None:
        """
        Add a reply from a journalist.
        """
        self.conversation_layout.addWidget(
            ReplyWidget(message_id, reply,
                        self.controller.reply_sync.reply_downloaded))
Example #22
0
class courseTab(QWidget):
	def __init__(self,parent_):
		super(courseTab,self).__init__(parent_)
		self.parent_=parent_
		self.obj=parent_.obj
		self.initUI()

	def initUI(self):
		self.btn = []
		self.lbl = []
		self.cFrames =[]
		self.marker=0
		self.d_=False

		if len(self.obj.courses) is 0:
			self.lbl.append(QLabel(self))
			self.lbl[self.marker].setText('Your credentials have been verified but we couldn\'t configure you up. Wait for auto-update')
			self.lbl[self.marker].setObjectName("slbl")
			self.cFrames.append(errorFrame(self.lbl[self.marker]))

		for i1 in range(self.marker,len(self.obj.courses)):
			self.lbl.append(QLabel(self))
			self.lbl[i1].setText(self.obj.courses[i1].c_name.upper())
			self.lbl[i1].setObjectName("lbl")
			self.btn.append(QPushButton('FILES'))
			self.btn[i1].setObjectName("btn")
			self.btn[i1].id=i1
			self.btn[i1].clicked.connect(partial(self.createItemTab,self.btn[i1].id))


		for i2 in range(self.marker,len(self.obj.courses)):
			self.cFrames.append(courseFrames(self.lbl[i2],self.btn[i2]))

		self.marker=len(self.obj.courses)

		self.widget = QWidget(self)

		self.vbox = QVBoxLayout()
		self.vbox.setContentsMargins(0,0,0,0)
		self.vbox.setSpacing(3)

		for index, frame in enumerate(self.cFrames):
			if index%2==0:
				frame.setObjectName('cFrameEven')
			else:
				frame.setObjectName('cFrameOdd')
			self.vbox.addWidget(frame)

		if len(self.cFrames)<4:
			self.dframe = QFrame()
			self.dframe.setObjectName('nFrameDummy')
			self.vbox.addWidget(self.dframe)
			self.d_=True

		self.widget.setLayout(self.vbox)

		self.scroll = QScrollArea(self)


		self.scroll.setWidget(self.widget)
		self.scroll.setWidgetResizable(True)
		self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
		self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)

		vbox1 = QVBoxLayout()
		vbox1.setContentsMargins(0,0,0,0)
		vbox1.setSpacing(0)

		vbox1.addWidget(self.scroll)
		self.setLayout(vbox1)

	def createItemTab(self,id_):
		self.parent_.callItemTab(id_)

	def updater(self):
		if self.obj.dummy_courses is '':
			return
		else:
			if  self.d_==True:
				child = self.vbox.takeAt(len(self.cFrames))
				if child.widget() is not None:
					child.widget().deleteLater()
				elif child.layout() is not None:
					clearLayout(child.layout())
				self.dframe.deleteLater()

				self.d_=False

			if self.marker==0:
				self.cFrames[0].deleteLater()
				self.lbl[0].deleteLater()
				child = self.vbox.takeAt(0)
				if child.widget() is not None:
					child.widget().deleteLater()
				elif child.layout() is not None:
					clearLayout(child.layout())
				self.lbl.pop(0)
				self.cFrames.pop(0)

			for i1 in range(self.marker,len(self.obj.courses)):
				self.lbl.append(QLabel(self))
				self.lbl[i1].setText(self.obj.courses[i1].c_name.upper())
				self.lbl[i1].setObjectName("lbl")
				self.btn.append(QPushButton('FILES'))
				self.btn[i1].setObjectName("btn")
				self.btn[i1].id=i1
				self.btn[i1].clicked.connect(partial(self.createItemTab,self.btn[i1].id))


			for i2 in range(self.marker,len(self.obj.courses)):
				self.cFrames.append(courseFrames(self.lbl[i2],self.btn[i2]))

			for i3 in range (self.marker,len(self.cFrames)):
				if i3%2==0:
					self.cFrames[i3].setObjectName('cFrameEven')
				else:
					self.cFrames[i3].setObjectName('cFrameEven')

				self.vbox.addWidget(self.cFrames[i3])

			if len(self.cFrames)<4:
				self.dframe = QFrame()
				self.dframe.setObjectName('nFrameDummy')
				self.vbox.addWidget(self.dframe)
				self.d_=True

			self.marker=self.marker+len(self.obj.dummy_courses)
Example #23
0
class E5TextEditSearchWidget(QWidget):
    """
    Class implementing a horizontal search widget for QTextEdit.
    """
    def __init__(self, parent=None, widthForHeight=True):
        """
        Constructor
        
        @param parent reference to the parent widget
        @type QWidget
        @param widthForHeight flag indicating to prefer width for height.
            If this parameter is False, some widgets are shown in a third
            line.
        @type bool
        """
        super(E5TextEditSearchWidget, self).__init__(parent)
        self.__setupUi(widthForHeight)
        
        self.__textedit = None
        self.__texteditType = ""
        self.__findBackwards = True
        
        self.__defaultBaseColor = (
            self.findtextCombo.lineEdit().palette().color(QPalette.Base)
        )
        self.__defaultTextColor = (
            self.findtextCombo.lineEdit().palette().color(QPalette.Text)
        )
        
        self.findHistory = []
        
        self.findtextCombo.setCompleter(None)
        self.findtextCombo.lineEdit().returnPressed.connect(
            self.__findByReturnPressed)
        
        self.__setSearchButtons(False)
        self.infoLabel.hide()
        
        self.setFocusProxy(self.findtextCombo)
    
    def __setupUi(self, widthForHeight):
        """
        Private method to generate the UI.
        
        @param widthForHeight flag indicating to prefer width for height
        @type bool
        """
        self.setObjectName("E5TextEditSearchWidget")
        
        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setObjectName("verticalLayout")
        self.verticalLayout.setContentsMargins(0, 0, 0, 0)
        
        # row 1 of widgets
        self.horizontalLayout1 = QHBoxLayout()
        self.horizontalLayout1.setObjectName("horizontalLayout1")
        
        self.label = QLabel(self)
        self.label.setObjectName("label")
        self.label.setText(self.tr("Find:"))
        self.horizontalLayout1.addWidget(self.label)
        
        self.findtextCombo = E5ClearableComboBox(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.findtextCombo.sizePolicy().hasHeightForWidth())
        self.findtextCombo.setSizePolicy(sizePolicy)
        self.findtextCombo.setMinimumSize(QSize(100, 0))
        self.findtextCombo.setEditable(True)
        self.findtextCombo.setInsertPolicy(QComboBox.InsertAtTop)
        self.findtextCombo.setDuplicatesEnabled(False)
        self.findtextCombo.setObjectName("findtextCombo")
        self.horizontalLayout1.addWidget(self.findtextCombo)
        
        # row 2 (maybe) of widgets
        self.horizontalLayout2 = QHBoxLayout()
        self.horizontalLayout2.setObjectName("horizontalLayout2")
        
        self.caseCheckBox = QCheckBox(self)
        self.caseCheckBox.setObjectName("caseCheckBox")
        self.caseCheckBox.setText(self.tr("Match case"))
        self.horizontalLayout2.addWidget(self.caseCheckBox)
        
        self.wordCheckBox = QCheckBox(self)
        self.wordCheckBox.setObjectName("wordCheckBox")
        self.wordCheckBox.setText(self.tr("Whole word"))
        self.horizontalLayout2.addWidget(self.wordCheckBox)
        
        # layout for the navigation buttons
        self.horizontalLayout3 = QHBoxLayout()
        self.horizontalLayout3.setSpacing(0)
        self.horizontalLayout3.setObjectName("horizontalLayout3")
        
        self.findPrevButton = QToolButton(self)
        self.findPrevButton.setObjectName("findPrevButton")
        self.findPrevButton.setToolTip(self.tr(
            "Press to find the previous occurrence"))
        self.findPrevButton.setIcon(UI.PixmapCache.getIcon("1leftarrow.png"))
        self.horizontalLayout3.addWidget(self.findPrevButton)
        
        self.findNextButton = QToolButton(self)
        self.findNextButton.setObjectName("findNextButton")
        self.findNextButton.setToolTip(self.tr(
            "Press to find the next occurrence"))
        self.findNextButton.setIcon(UI.PixmapCache.getIcon("1rightarrow.png"))
        self.horizontalLayout3.addWidget(self.findNextButton)
        
        self.horizontalLayout2.addLayout(self.horizontalLayout3)
        
        # info label (in row 2 or 3)
        self.infoLabel = QLabel(self)
        self.infoLabel.setText("")
        self.infoLabel.setObjectName("infoLabel")
        
        # place everything together
        self.verticalLayout.addLayout(self.horizontalLayout1)
        self.__addWidthForHeightLayout(widthForHeight)
        self.verticalLayout.addWidget(self.infoLabel)
        
        QMetaObject.connectSlotsByName(self)
        
        self.setTabOrder(self.findtextCombo, self.caseCheckBox)
        self.setTabOrder(self.caseCheckBox, self.wordCheckBox)
        self.setTabOrder(self.wordCheckBox, self.findPrevButton)
        self.setTabOrder(self.findPrevButton, self.findNextButton)
    
    def setWidthForHeight(self, widthForHeight):
        """
        Public method to set the 'width for height'.
        
        @param widthForHeight flag indicating to prefer width
        @type bool
        """
        if self.__widthForHeight:
            self.horizontalLayout1.takeAt(self.__widthForHeightLayoutIndex)
        else:
            self.verticalLayout.takeAt(self.__widthForHeightLayoutIndex)
        self.__addWidthForHeightLayout(widthForHeight)
    
    def __addWidthForHeightLayout(self, widthForHeight):
        """
        Private method to set the middle part of the layout.
        
        @param widthForHeight flag indicating to prefer width
        @type bool
        """
        if widthForHeight:
            self.horizontalLayout1.addLayout(self.horizontalLayout2)
            self.__widthForHeightLayoutIndex = 2
        else:
            self.verticalLayout.insertLayout(1, self.horizontalLayout2)
            self.__widthForHeightLayoutIndex = 1
        
        self.__widthForHeight = widthForHeight
    
    def attachTextEdit(self, textedit, editType="QTextEdit"):
        """
        Public method to attach a QTextEdit widget.
        
        @param textedit reference to the edit widget to be attached
        @type QTextEdit, QWebEngineView or QWebView
        @param editType type of the attached edit widget
        @type str (one of "QTextEdit", "QWebEngineView" or "QWebView")
        """
        assert editType in ["QTextEdit", "QWebEngineView", "QWebView"]
        
        self.__textedit = textedit
        self.__texteditType = editType
        
        self.wordCheckBox.setVisible(editType == "QTextEdit")
    
    def keyPressEvent(self, event):
        """
        Protected slot to handle key press events.
        
        @param event reference to the key press event (QKeyEvent)
        """
        if self.__textedit and event.key() == Qt.Key_Escape:
            self.__textedit.setFocus(Qt.ActiveWindowFocusReason)
            event.accept()
    
    @pyqtSlot(str)
    def on_findtextCombo_editTextChanged(self, txt):
        """
        Private slot to enable/disable the find buttons.
        
        @param txt text of the combobox (string)
        """
        self.__setSearchButtons(txt != "")
        
        self.infoLabel.hide()
        self.__setFindtextComboBackground(False)
    
    def __setSearchButtons(self, enabled):
        """
        Private slot to set the state of the search buttons.
        
        @param enabled flag indicating the state (boolean)
        """
        self.findPrevButton.setEnabled(enabled)
        self.findNextButton.setEnabled(enabled)
    
    def __findByReturnPressed(self):
        """
        Private slot to handle the returnPressed signal of the findtext
        combobox.
        """
        self.__find(self.__findBackwards)
    
    @pyqtSlot()
    def on_findPrevButton_clicked(self):
        """
        Private slot to find the previous occurrence.
        """
        self.__find(True)
    
    @pyqtSlot()
    def on_findNextButton_clicked(self):
        """
        Private slot to find the next occurrence.
        """
        self.__find(False)
    
    def __find(self, backwards):
        """
        Private method to search the associated text edit.
        
        @param backwards flag indicating a backwards search (boolean)
        """
        if not self.__textedit:
            return
        
        self.infoLabel.clear()
        self.infoLabel.hide()
        self.__setFindtextComboBackground(False)
        
        txt = self.findtextCombo.currentText()
        if not txt:
            return
        self.__findBackwards = backwards
        
        # This moves any previous occurrence of this statement to the head
        # of the list and updates the combobox
        if txt in self.findHistory:
            self.findHistory.remove(txt)
        self.findHistory.insert(0, txt)
        self.findtextCombo.clear()
        self.findtextCombo.addItems(self.findHistory)
        
        if self.__texteditType == "QTextEdit":
            ok = self.__findPrevNextQTextEdit(backwards)
            self.__findNextPrevCallback(ok)
        elif self.__texteditType == "QWebEngineView":
            self.__findPrevNextQWebEngineView(backwards)
    
    def __findPrevNextQTextEdit(self, backwards):
        """
        Private method to to search the associated edit widget of
        type QTextEdit.
        
        @param backwards flag indicating a backwards search
        @type bool
        @return flag indicating the search result
        @rtype bool
        """
        if backwards:
            flags = QTextDocument.FindFlags(QTextDocument.FindBackward)
        else:
            flags = QTextDocument.FindFlags()
        if self.caseCheckBox.isChecked():
            flags |= QTextDocument.FindCaseSensitively
        if self.wordCheckBox.isChecked():
            flags |= QTextDocument.FindWholeWords
        
        ok = self.__textedit.find(self.findtextCombo.currentText(), flags)
        if not ok:
            # wrap around once
            cursor = self.__textedit.textCursor()
            if backwards:
                moveOp = QTextCursor.End        # move to end of document
            else:
                moveOp = QTextCursor.Start      # move to start of document
            cursor.movePosition(moveOp)
            self.__textedit.setTextCursor(cursor)
            ok = self.__textedit.find(self.findtextCombo.currentText(), flags)
        
        return ok
    
    def __findPrevNextQWebEngineView(self, backwards):
        """
        Private method to to search the associated edit widget of
        type QWebEngineView.
        
        @param backwards flag indicating a backwards search
        @type bool
        """
        from PyQt5.QtWebEngineWidgets import QWebEnginePage
        
        findFlags = QWebEnginePage.FindFlags()
        if self.caseCheckBox.isChecked():
            findFlags |= QWebEnginePage.FindCaseSensitively
        if backwards:
            findFlags |= QWebEnginePage.FindBackward
        self.__textedit.findText(self.findtextCombo.currentText(),
                                 findFlags, self.__findNextPrevCallback)
    
    def __findNextPrevCallback(self, found):
        """
        Private method to process the result of the last search.
        
        @param found flag indicating if the last search succeeded
        @type bool
        """
        if not found:
            txt = self.findtextCombo.currentText()
            self.infoLabel.setText(
                self.tr("'{0}' was not found.").format(txt))
            self.infoLabel.show()
            self.__setFindtextComboBackground(True)
    
    def __setFindtextComboBackground(self, error):
        """
        Private slot to change the findtext combo background to indicate
        errors.
        
        @param error flag indicating an error condition (boolean)
        """
        le = self.findtextCombo.lineEdit()
        p = le.palette()
        if error:
            p.setBrush(QPalette.Base, QBrush(QColor("#FF6666")))
            p.setBrush(QPalette.Text, QBrush(QColor("#000000")))
        else:
            p.setBrush(QPalette.Base, self.__defaultBaseColor)
            p.setBrush(QPalette.Text, self.__defaultTextColor)
        le.setPalette(p)
        le.update()
Example #24
0
class PromptContainer(QWidget):

    """Container for prompts to be shown above the statusbar.

    This is a per-window object, however each window shows the same prompt.

    Attributes:
        _layout: The layout used to show prompts in.
        _win_id: The window ID this object is associated with.

    Signals:
        update_geometry: Emitted when the geometry should be updated.
    """

    STYLESHEET = """
        QWidget#PromptContainer {
            {% if conf.statusbar.position == 'top' %}
                border-bottom-left-radius: {{ conf.prompt.radius }}px;
                border-bottom-right-radius: {{ conf.prompt.radius }}px;
            {% else %}
                border-top-left-radius: {{ conf.prompt.radius }}px;
                border-top-right-radius: {{ conf.prompt.radius }}px;
            {% endif %}
        }

        QWidget {
            font: {{ conf.fonts.prompts }};
            color: {{ conf.colors.prompts.fg }};
            background-color: {{ conf.colors.prompts.bg }};
        }

        QLineEdit {
            border: {{ conf.colors.prompts.border }};
        }

        QTreeView {
            selection-background-color: {{ conf.colors.prompts.selected.bg }};
            border: {{ conf.colors.prompts.border }};
        }

        QTreeView::branch {
            background-color: {{ conf.colors.prompts.bg }};
        }

        QTreeView::item:selected, QTreeView::item:selected:hover,
        QTreeView::branch:selected {
            background-color: {{ conf.colors.prompts.selected.bg }};
        }
    """
    update_geometry = pyqtSignal()

    def __init__(self, win_id, parent=None):
        super().__init__(parent)
        self._layout = QVBoxLayout(self)
        self._layout.setContentsMargins(10, 10, 10, 10)
        self._win_id = win_id
        self._prompt: Optional[_BasePrompt] = None

        self.setObjectName('PromptContainer')
        self.setAttribute(Qt.WA_StyledBackground, True)
        stylesheet.set_register(self)

        message.global_bridge.prompt_done.connect(self._on_prompt_done)
        prompt_queue.show_prompts.connect(self._on_show_prompts)
        message.global_bridge.mode_left.connect(self._on_global_mode_left)

    def __repr__(self):
        return utils.get_repr(self, win_id=self._win_id)

    @pyqtSlot(usertypes.Question)
    def _on_show_prompts(self, question):
        """Show a prompt for the given question.

        Args:
            question: A Question object or None.
        """
        item = self._layout.takeAt(0)
        if item is not None:
            widget = item.widget()
            log.prompt.debug("Deleting old prompt {}".format(widget))
            widget.hide()
            widget.deleteLater()

        if question is None:
            log.prompt.debug("No prompts left, hiding prompt container.")
            self._prompt = None
            self.hide()
            return

        classes = {
            usertypes.PromptMode.yesno: YesNoPrompt,
            usertypes.PromptMode.text: LineEditPrompt,
            usertypes.PromptMode.user_pwd: AuthenticationPrompt,
            usertypes.PromptMode.download: DownloadFilenamePrompt,
            usertypes.PromptMode.alert: AlertPrompt,
        }
        klass = classes[question.mode]
        prompt = klass(question)

        log.prompt.debug("Displaying prompt {}".format(prompt))
        self._prompt = prompt

        # If this question was interrupted, we already connected the signal
        if not question.interrupted:
            question.aborted.connect(
                functools.partial(self._on_aborted, prompt.KEY_MODE))
        modeman.enter(self._win_id, prompt.KEY_MODE, 'question asked')

        self.setSizePolicy(prompt.sizePolicy())
        self._layout.addWidget(prompt)
        prompt.show()
        self.show()
        prompt.setFocus()
        self.update_geometry.emit()

    @pyqtSlot()
    def _on_aborted(self, key_mode):
        """Leave KEY_MODE whenever a prompt is aborted."""
        try:
            modeman.leave(self._win_id, key_mode, 'aborted', maybe=True)
        except objreg.RegistryUnavailableError:
            # window was deleted: ignore
            pass

    @pyqtSlot(usertypes.KeyMode)
    def _on_prompt_done(self, key_mode):
        """Leave the prompt mode in this window if a question was answered."""
        modeman.leave(self._win_id, key_mode, ':prompt-accept', maybe=True)

    @pyqtSlot(usertypes.KeyMode)
    def _on_global_mode_left(self, mode):
        """Leave prompt/yesno mode in this window if it was left elsewhere.

        This ensures no matter where a prompt was answered, we leave the prompt
        mode and dispose of the prompt object in every window.
        """
        if mode not in [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]:
            return
        modeman.leave(self._win_id, mode, 'left in other window', maybe=True)
        item = self._layout.takeAt(0)
        if item is not None:
            widget = item.widget()
            log.prompt.debug("Deleting prompt {}".format(widget))
            widget.hide()
            widget.deleteLater()

    @cmdutils.register(instance='prompt-container', scope='window',
                       modes=[usertypes.KeyMode.prompt,
                              usertypes.KeyMode.yesno])
    def prompt_accept(self, value=None, *, save=False):
        """Accept the current prompt.

        //

        This executes the next action depending on the question mode, e.g. asks
        for the password or leaves the mode.

        Args:
            value: If given, uses this value instead of the entered one.
                   For boolean prompts, "yes"/"no" are accepted as value.
            save: Save the value to the config.
        """
        assert self._prompt is not None
        question = self._prompt.question

        try:
            done = self._prompt.accept(value, save=save)
        except Error as e:
            raise cmdutils.CommandError(str(e))

        if done:
            message.global_bridge.prompt_done.emit(self._prompt.KEY_MODE)
            question.done()

    @cmdutils.register(instance='prompt-container', scope='window',
                       modes=[usertypes.KeyMode.prompt], maxsplit=0)
    def prompt_open_download(self, cmdline: str = None,
                             pdfjs: bool = False) -> None:
        """Immediately open a download.

        If no specific command is given, this will use the system's default
        application to open the file.

        Args:
            cmdline: The command which should be used to open the file. A `{}`
                     is expanded to the temporary file name. If no `{}` is
                     present, the filename is automatically appended to the
                     cmdline.
            pdfjs: Open the download via PDF.js.
        """
        assert self._prompt is not None
        try:
            self._prompt.download_open(cmdline, pdfjs=pdfjs)
        except UnsupportedOperationError:
            pass

    @cmdutils.register(instance='prompt-container', scope='window',
                       modes=[usertypes.KeyMode.prompt])
    @cmdutils.argument('which', choices=['next', 'prev'])
    def prompt_item_focus(self, which):
        """Shift the focus of the prompt file completion menu to another item.

        Args:
            which: 'next', 'prev'
        """
        assert self._prompt is not None
        try:
            self._prompt.item_focus(which)
        except UnsupportedOperationError:
            pass

    @cmdutils.register(
        instance='prompt-container', scope='window',
        modes=[usertypes.KeyMode.prompt, usertypes.KeyMode.yesno])
    def prompt_yank(self, sel=False):
        """Yank URL to clipboard or primary selection.

        Args:
            sel: Use the primary selection instead of the clipboard.
        """
        assert self._prompt is not None
        question = self._prompt.question
        if question.url is None:
            message.error('No URL found.')
            return
        if sel and utils.supports_selection():
            target = 'primary selection'
        else:
            sel = False
            target = 'clipboard'
        utils.set_clipboard(question.url, sel)
        message.info("Yanked to {}: {}".format(target, question.url))
Example #25
0
class ChapterAddWidget(QWidget):
	CHAPTERS = pyqtSignal(dict)
	def __init__(self, gallery, parent=None):
		super().__init__(parent)
		self.setWindowFlags(Qt.Window)

		self.current_chapters = len(gallery.chapters)
		self.added_chaps = 0

		layout = QFormLayout()
		self.setLayout(layout)
		lbl = QLabel('{} by {}'.format(gallery.title, gallery.artist))
		layout.addRow('Gallery:', lbl)
		layout.addRow('Current chapters:', QLabel('{}'.format(self.current_chapters)))

		new_btn = QPushButton('New')
		new_btn.clicked.connect(self.add_new_chapter)
		new_btn.adjustSize()
		add_btn = QPushButton('Finish')
		add_btn.clicked.connect(self.finish)
		add_btn.adjustSize()
		new_l = QHBoxLayout()
		new_l.addWidget(add_btn, alignment=Qt.AlignLeft)
		new_l.addWidget(new_btn, alignment=Qt.AlignRight)
		layout.addRow(new_l)

		frame = QFrame()
		frame.setFrameShape(frame.StyledPanel)
		layout.addRow(frame)

		self.chapter_l = QVBoxLayout()
		frame.setLayout(self.chapter_l)

		new_btn.click()

		self.setMaximumHeight(550)
		self.setFixedWidth(500)
		if parent:
			self.move(parent.window().frameGeometry().topLeft() +
				parent.window().rect().center() -
				self.rect().center())
		else:
			frect = self.frameGeometry()
			frect.moveCenter(QDesktopWidget().availableGeometry().center())
			self.move(frect.topLeft())
		self.setWindowTitle('Add Chapters')

	def add_new_chapter(self):
		chap_layout = QHBoxLayout()
		self.added_chaps += 1
		curr_chap = self.current_chapters+self.added_chaps

		chp_numb = QSpinBox(self)
		chp_numb.setMinimum(1)
		chp_numb.setValue(curr_chap)
		curr_chap_lbl = QLabel('Chapter {}'.format(curr_chap))
		def ch_lbl(n): curr_chap_lbl.setText('Chapter {}'.format(n))
		chp_numb.valueChanged[int].connect(ch_lbl)
		chp_path = PathLineEdit()
		chp_path.folder = True
		chp_path.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
		chp_path.setPlaceholderText('Right/Left-click to open folder explorer.'+
							  ' Leave empty to not add.')
		chap_layout.addWidget(chp_path, 3)
		chap_layout.addWidget(chp_numb, 0)
		self.chapter_l.addWidget(curr_chap_lbl,
						   alignment=Qt.AlignLeft)
		self.chapter_l.addLayout(chap_layout)

	def finish(self):
		chapters = {}
		widgets = []
		x = True
		while x:
			x = self.chapter_l.takeAt(0)
			if x:
				widgets.append(x)
		for l in range(1, len(widgets), 1):
			layout = widgets[l]
			try:
				line_edit = layout.itemAt(0).widget()
				spin_box = layout.itemAt(1).widget()
			except AttributeError:
				continue
			p = line_edit.text()
			c = spin_box.value() - 1 # because of 0-based index
			if os.path.exists(p):
				chapters[c] = p
		self.CHAPTERS.emit(chapters)
		self.close()
Example #26
0
class DangerConfEditor(ViewInfoChanger):
    def __init__(self, header, info, parent: ViewShower):
        super().__init__(header, info, parent)
        self.way_layout = QVBoxLayout()
        push_btn = self.main_layout.takeAt(self.main_layout.count() -
                                           1).widget()

        self.worker_id = info[0]
        self.slave_combo_config = {
            "название_параметра":
            ("параметры", "*", "название_параметра", "код_параметра")
        }
        self.combo_change_idx["название_параметра"] = {}
        add_btn = QPushButton("Добавить")
        add_btn.clicked.connect(lambda e: self.add_cell(-1))
        self.main_layout.addWidget(add_btn)

        way = self.db.execute(
            f"SELECT код_параметра, название_параметра, нижний_допустимый_порог, верхний_допустимый_порог FROM `подчинённые_допустимые_значения_параметров_view` where `код_категории_вредности` = {self.worker_id}"
        )
        for pos, point in enumerate(way, start=-1):
            param_name = str(point[1])
            self.combo_change_idx["название_параметра"][param_name] = point[0]
            self.add_cell(pos, param_name, point[2], point[3])

        self.main_layout.addLayout(self.way_layout)
        self.main_layout.addWidget(push_btn)

    def add_cell(self, pos: int, txt="", dnw_val="", up_val=""):
        """Вставляет ячейку ниже активирующей кнопки для вставки на уровне надо передать ::pos:: = -1"""

        cell = QHBoxLayout()
        edi = QLineEdit()
        edi.setText(txt)

        dwn_val_edt = QLineEdit()
        dwn_val_edt.setText(str(dnw_val))
        up_val_edt = QLineEdit(str(up_val))

        add_btn = QPushButton("Добавить")
        del_btn = QPushButton("Удалить")

        cmb = QComboBox()
        cmb.addItem(txt)

        edi.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        cmb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        dwn_val_edt.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        up_val_edt.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        add_btn.clicked.connect(lambda e, c=cell: self.add_cell(c.pos))
        del_btn.clicked.connect(lambda e, c=cell: self.del_cell(c.pos))

        edi.editingFinished.connect(
            lambda c=cmb, t=edi.text: self.slave_combo_update(
                "название_параметра", c, t()))  # le-kostyl
        cell.pos = pos
        cell.addWidget(edi)
        cell.addWidget(cmb)
        cell.addWidget(dwn_val_edt)
        cell.addWidget(up_val_edt)
        cell.addWidget(add_btn)
        cell.addWidget(del_btn)
        for i in range(pos + 1, self.way_layout.count()):
            cell_to_move = self.way_layout.itemAt(i)
            cell_to_move.pos += 1
        cell.pos += 1  # для вставки ниже активированной кнопки

        self.way_layout.insertLayout(cell.pos, cell)

    def slave_combo_update(self, c_name: str, c: QComboBox,
                           text: str):  # grand le-kostyl
        tmp = self.combo_config
        self.combo_config = self.slave_combo_config
        self.combo_update(c_name, c, text)
        self.combo_config = tmp

    def del_cell(self, pos):
        cell: QVBoxLayout
        cell = self.way_layout.takeAt(pos)
        for i in range(cell.count()):
            w = cell.takeAt(0).widget()
            w.deleteLater()
        cell.deleteLater()

        for i in range(pos, self.way_layout.count()):
            cell_to_move = self.way_layout.itemAt(i)
            cell_to_move.pos -= 1

    def push_slave_changes(self):
        params = []
        kakoito_set = set()
        try:
            for i in range(self.way_layout.count()):
                cell = self.way_layout.itemAt(i)
                w = cell.itemAt(1).widget()
                param = w.currentText()
                if param:
                    if param in kakoito_set:
                        raise KeyError
                    kakoito_set.add(param)
                    param_id = self.combo_change_idx["название_параметра"][
                        param]
                    dwn_w = cell.itemAt(2).widget()
                    up_w = cell.itemAt(3).widget()
                    dwn_val = dwn_w.text()
                    up_val = up_w.text()
                    if not dwn_val:
                        dwn_val = 0
                    if not up_val:
                        up_val = 0
                    params.append((self.worker_id, param_id, dwn_val, up_val))

            query = f" insert into `допустимые_значения_параметров` values(%s, %s, %s, %s)"
            self.db.execute(
                "delete from `допустимые_значения_параметров` where код_категории_вредности = %s",
                (self.worker_id, ))
            self.db.executemany(query, params)
            self.db.commit()
        except KeyError as er:
            from PyQt5.QtWidgets import QErrorMessage
            error_widget = QErrorMessage()
            error_widget.showMessage(f"Дубликат параметра")
            error_widget.exec()

    def push_changes(self):
        self.push_slave_changes()
        super().push_changes()
Example #27
0
class ApikeysManagerWindow(QWidget):
    def __init__(self, parent: QWidget = None):
        super(ApikeysManagerWindow, self).__init__(parent=parent)
        self._logger = get_logger(__name__)
        self._logger.debug('Constructed window!')
        self.mainwindow = None
        self.emcore = get_core_instance()

        self.setMinimumSize(500, 200)
        self.icon = QIcon('img/pyevemon.png')
        self.setWindowIcon(self.icon)
        self.setWindowTitle(self.tr('API Keys Manager'))

        self._layout = QVBoxLayout()
        self.setLayout(self._layout)

        # labels
        self._lbl_apikeys = QLabel(self.tr('API keys:'), self)

        # buttons
        self._btn_add_apikey = QPushButton(self.tr('Add API key...'), self)
        self._btn_add_apikey.clicked.connect(self.on_click_add_apikey)

        # layouts
        self._layout_top1 = QHBoxLayout()
        self._layout_top1.addWidget(self._lbl_apikeys)
        self._layout_top1.addStretch()
        self._layout_top1.addWidget(self._btn_add_apikey)

        self._layout.addLayout(self._layout_top1, 0)
        self._layout.setSizeConstraint(QLayout.SetMinimumSize)

        self.load_apikeys()
        self.show()

    # void QWidget::closeEvent(QCloseEvent * event)
    def closeEvent(self, close_event: QCloseEvent):
        self._logger.debug('ApikeysManagerWindow.closeEvent()')
        self.mainwindow.apikeysmgrw = None
        close_event.accept()

    def load_apikeys(self):
        apikeys = self.emcore.savedata.get_apikeys()
        for apikey in apikeys:
            apikey_widget = SingleApiKeyWidget(self)
            apikey_widget.set_from_apikey(apikey)
            apikey_widget.editClicked.connect(self.on_click_edit_apikey)
            apikey_widget.removeClicked.connect(self.on_click_remove_apikey)
            self._layout.addWidget(apikey_widget, 0)
        #
        self._layout.addStretch()

    def reload_apikeys(self):
        # clear existing layout
        lc = self._layout.count()
        i = lc - 1  # iterate from the end
        # at index 0 there is a layout_top1
        # we need items from index 1 and to the end
        while i > 0:
            layoutItem = self._layout.takeAt(i)
            if layoutItem is not None:
                if layoutItem.widget() is not None:
                    # from Qt docs: Note: While the functions layout() and spacerItem()
                    # perform casts, this function returns another object: QLayout and
                    # QSpacerItem inherit QLayoutItem, while QWidget does not.
                    # so, we must delete widget separately.
                    widget = layoutItem.widget()
                    if widget is not None:
                        widget.hide()
                        widget.setParent(None)  # from stackoverflow :/
                        sip.delete(
                            widget)  # calls destructor for wrapped C++ object
                        del widget
                sip.delete(layoutItem)
                del layoutItem
            i -= 1
        self.load_apikeys()

    def start_add_or_edit_apikey(self, keyid: str = None):
        apikey = None
        if keyid is not None:
            apikey = self.emcore.savedata.get_apikey_by_keyid(keyid)
            self._logger.debug(
                'Found existing apikey with primary key = {}'.format(
                    apikey.id))
            self._logger.debug(' number of chars: {}'.format(
                len(apikey.apikey_characters)))
        dlg = AddEditApikeyDialog(self, apikey)
        exec_res = dlg.exec_()
        if exec_res == QDialog.Rejected: return
        # apply added/edited apikey
        self.emcore.savedata.store_apikey(dlg.get_apikey(),
                                          check_existing=False)
        self.reload_apikeys()

    def on_click_add_apikey(self):
        self._logger.debug('click')
        self.start_add_or_edit_apikey()

    def on_click_edit_apikey(self, keyid: str):
        self._logger.debug('Start edit, keyid = {}'.format(keyid))
        self.start_add_or_edit_apikey(keyid)

    def on_click_remove_apikey(self, keyid: str):
        answer = QMessageBox.question(
            self, self.tr('Confirmation'),
            self.tr('Are you sure you want to delete API Key?'))
        if answer != QMessageBox.Yes:
            return
        self._logger.debug('keyid = {}'.format(keyid))
        self.emcore.savedata.remove_apikey_by_keyid(keyid)
        self.reload_apikeys()
Example #28
0
class notifyTab(QWidget):

	def __init__(self,parent_):
		super(notifyTab,self).__init__(parent_)
		self.parent_=parent_
		self.obj=parent_.obj
		self.initUI()

	def initUI(self):
		self.btn = []
		self.notif_lbl = []
		self.tag_lbl = []
		self.nFrames =[]
		self.d_ = False

		for i in range(0,len(self.obj.notif)):
			pixmap=QPixmap(self.obj.tagDict[self.obj.notif[i].tag])
			pixmap = pixmap.scaled(45, 45)
			self.tag_lbl.append(QLabel(self))
			self.tag_lbl[i].setPixmap(pixmap)

		for i1 in range(0,len(self.obj.notif)):
			self.notif_lbl.append(QLabel(self))
			self.notif_lbl[i1].setScaledContents(False)
			self.notif_lbl[i1].setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
			self.notif_lbl[i1].setText(self.obj.notif[i1].notif_text)
			self.notif_lbl[i1].setObjectName("nlbl")
			self.btn.append(QPushButton('Get Link'))
			self.btn[i1].setObjectName("btn")

		for i2 in range(0,len(self.obj.notif)):
			self.nFrames.append(notifyFrames(self.notif_lbl[i2],self.tag_lbl[i2],self.btn[i2]))
			tag = self.obj.notif[i2].tag
			if tag ==2 or tag==3 or tag==4:
				self.nFrames[i2].setObjectName('nFrameOdd')
			else:
				self.nFrames[i2].setObjectName('nFrameEven')

		self.widget = QWidget(self)
		self.vbox = QVBoxLayout()

		for index, frame in enumerate(self.nFrames):
			self.vbox.addWidget(frame)

		if len(self.nFrames)<4:
			self.dframe = QFrame()
			self.dframe.setObjectName('nFrameDummy')
			self.vbox.addWidget(self.dframe)
			self.d_ = True

		self.vbox.setContentsMargins(0,0,0,0)
		self.vbox.setSpacing(3)

		self.widget.setLayout(self.vbox)
		self.scroll = QScrollArea(self)
		self.scroll.setWidget(self.widget)
		self.scroll.setWidgetResizable(True)
		self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
		self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)

		vbox1 = QVBoxLayout()
		vbox1.setContentsMargins(0,0,0,0)
		vbox1.setSpacing(0)

		vbox1.addWidget(self.scroll)
		self.setLayout(vbox1)


	def updater(self):
		marker=len(self.nFrames)
		marker1 = len(self.nFrames)

		for i in range(0,len(self.obj.notif)):
			pixmap=QPixmap(self.obj.tagDict[self.obj.notif[i].tag])
			pixmap = pixmap.scaled(45, 45)
			self.tag_lbl.append(QLabel(self))
			self.tag_lbl[marker].setPixmap(pixmap)
			marker=marker+1

		marker=marker1
		for i1 in range(0,len(self.obj.notif)):
			self.notif_lbl.append(QLabel(self))
			self.notif_lbl[marker].setScaledContents(False)
			self.notif_lbl[marker].setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
			self.notif_lbl[marker].setText(self.obj.notif[i1].notif_text)
			self.notif_lbl[marker].setObjectName("nlbl")
			self.btn.append(QPushButton('Get Link'))
			self.btn[marker].setObjectName("btn")
			marker+=1

		marker=marker1

		for i2 in range(0,len(self.obj.notif)):
			self.nFrames.append(notifyFrames(self.notif_lbl[marker],self.tag_lbl[marker],self.btn[marker]))
			marker+=1

		marker=marker1

		for i3 in range(len(self.nFrames)-1,marker-1,-1):
			self.vbox.insertWidget(0,self.nFrames[i3])
			tag = self.obj.notif[i3-marker].tag
			if tag ==2 or tag==3 or tag==4:
				self.nFrames[i3].setObjectName('nFrameOdd')
				self.nFrames[i3].setStyleSheet(' #nFrameOdd{background-color:#F5F5DC;min-height:110px;max-height:110px;min-width:805px;}')
			else:
				self.nFrames[i3].setObjectName('nFrameEven')
				self.nFrames[i3].setStyleSheet(' #nFrameEven{background-color: rgb(255, 250, 175);min-height:110px;max-height:110px;min-width:805px;}')

		if len(self.nFrames)>=4 and self.d_==True:
			child = self.vbox.takeAt(len(self.nFrames))
			if child.widget() is not None:
				child.widget().deleteLater()
			elif child.layout() is not None:
				clearLayout(child.layout())
			self.dframe.deleteLater()
			self.d_=False
Example #29
0
class PromptContainer(QWidget):
    """Container for prompts to be shown above the statusbar.

    This is a per-window object, however each window shows the same prompt.

    Attributes:
        _layout: The layout used to show prompts in.
        _win_id: The window ID this object is associated with.

    Signals:
        update_geometry: Emitted when the geometry should be updated.
    """

    STYLESHEET = """
        {% set prompt_radius = config.get('ui', 'prompt-radius') %}
        QWidget#PromptContainer {
            {% if config.get('ui', 'status-position') == 'top' %}
                border-bottom-left-radius: {{ prompt_radius }}px;
                border-bottom-right-radius: {{ prompt_radius }}px;
            {% else %}
                border-top-left-radius: {{ prompt_radius }}px;
                border-top-right-radius: {{ prompt_radius }}px;
            {% endif %}
        }

        QWidget {
            font: {{ font['prompts'] }};
            color: {{ color['prompts.fg'] }};
            background-color: {{ color['prompts.bg'] }};
        }

        QTreeView {
            selection-background-color: {{ color['prompts.selected.bg'] }};
        }

        QTreeView::item:selected, QTreeView::item:selected:hover {
            background-color: {{ color['prompts.selected.bg'] }};
        }
    """
    update_geometry = pyqtSignal()

    def __init__(self, win_id, parent=None):
        super().__init__(parent)
        self._layout = QVBoxLayout(self)
        self._layout.setContentsMargins(10, 10, 10, 10)
        self._win_id = win_id
        self._prompt = None

        self.setObjectName('PromptContainer')
        self.setAttribute(Qt.WA_StyledBackground, True)
        style.set_register_stylesheet(self)

        message.global_bridge.prompt_done.connect(self._on_prompt_done)
        prompt_queue.show_prompts.connect(self._on_show_prompts)
        message.global_bridge.mode_left.connect(self._on_global_mode_left)

    def __repr__(self):
        return utils.get_repr(self, win_id=self._win_id)

    @pyqtSlot(usertypes.Question)
    def _on_show_prompts(self, question):
        """Show a prompt for the given question.

        Args:
            question: A Question object or None.
        """
        item = self._layout.takeAt(0)
        if item is not None:
            widget = item.widget()
            log.prompt.debug("Deleting old prompt {}".format(widget))
            widget.hide()
            widget.deleteLater()

        if question is None:
            log.prompt.debug("No prompts left, hiding prompt container.")
            self._prompt = None
            self.hide()
            return

        classes = {
            usertypes.PromptMode.yesno: YesNoPrompt,
            usertypes.PromptMode.text: LineEditPrompt,
            usertypes.PromptMode.user_pwd: AuthenticationPrompt,
            usertypes.PromptMode.download: DownloadFilenamePrompt,
            usertypes.PromptMode.alert: AlertPrompt,
        }
        klass = classes[question.mode]
        prompt = klass(question)

        log.prompt.debug("Displaying prompt {}".format(prompt))
        self._prompt = prompt

        if not question.interrupted:
            # If this question was interrupted, we already connected the signal
            question.aborted.connect(
                lambda: modeman.maybe_leave(self._win_id, prompt.KEY_MODE,
                                            'aborted'))
        modeman.enter(self._win_id, prompt.KEY_MODE, 'question asked')

        self.setSizePolicy(prompt.sizePolicy())
        self._layout.addWidget(prompt)
        prompt.show()
        self.show()
        prompt.setFocus()
        self.update_geometry.emit()

    @pyqtSlot(usertypes.KeyMode)
    def _on_prompt_done(self, key_mode):
        """Leave the prompt mode in this window if a question was answered."""
        modeman.maybe_leave(self._win_id, key_mode, ':prompt-accept')

    @pyqtSlot(usertypes.KeyMode)
    def _on_global_mode_left(self, mode):
        """Leave prompt/yesno mode in this window if it was left elsewhere.

        This ensures no matter where a prompt was answered, we leave the prompt
        mode and dispose of the prompt object in every window.
        """
        if mode not in [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]:
            return
        modeman.maybe_leave(self._win_id, mode, 'left in other window')
        item = self._layout.takeAt(0)
        if item is not None:
            widget = item.widget()
            log.prompt.debug("Deleting prompt {}".format(widget))
            widget.hide()
            widget.deleteLater()

    @cmdutils.register(instance='prompt-container', hide=True, scope='window',
                       modes=[usertypes.KeyMode.prompt,
                              usertypes.KeyMode.yesno])
    def prompt_accept(self, value=None):
        """Accept the current prompt.

        //

        This executes the next action depending on the question mode, e.g. asks
        for the password or leaves the mode.

        Args:
            value: If given, uses this value instead of the entered one.
                   For boolean prompts, "yes"/"no" are accepted as value.
        """
        question = self._prompt.question
        try:
            done = self._prompt.accept(value)
        except Error as e:
            raise cmdexc.CommandError(str(e))
        if done:
            message.global_bridge.prompt_done.emit(self._prompt.KEY_MODE)
            question.done()

    @cmdutils.register(instance='prompt-container', hide=True, scope='window',
                       modes=[usertypes.KeyMode.yesno],
                       deprecated='Use :prompt-accept yes instead!')
    def prompt_yes(self):
        """Answer yes to a yes/no prompt."""
        self.prompt_accept('yes')

    @cmdutils.register(instance='prompt-container', hide=True, scope='window',
                       modes=[usertypes.KeyMode.yesno],
                       deprecated='Use :prompt-accept no instead!')
    def prompt_no(self):
        """Answer no to a yes/no prompt."""
        self.prompt_accept('no')

    @cmdutils.register(instance='prompt-container', hide=True, scope='window',
                       modes=[usertypes.KeyMode.prompt], maxsplit=0)
    def prompt_open_download(self, cmdline: str = None):
        """Immediately open a download.

        If no specific command is given, this will use the system's default
        application to open the file.

        Args:
            cmdline: The command which should be used to open the file. A `{}`
                     is expanded to the temporary file name. If no `{}` is
                     present, the filename is automatically appended to the
                     cmdline.
        """
        try:
            self._prompt.download_open(cmdline)
        except UnsupportedOperationError:
            pass

    @cmdutils.register(instance='prompt-container', hide=True, scope='window',
                       modes=[usertypes.KeyMode.prompt])
    @cmdutils.argument('which', choices=['next', 'prev'])
    def prompt_item_focus(self, which):
        """Shift the focus of the prompt file completion menu to another item.

        Args:
            which: 'next', 'prev'
        """
        try:
            self._prompt.item_focus(which)
        except UnsupportedOperationError:
            pass
Example #30
0
class itemTab(QWidget):

	def __init__(self,parent_,id_):
		super(itemTab,self).__init__(parent_)
		self.parent_=parent_
		self.obj = parent_.obj
		self.id_=id_
		self.initUI()

	def initUI(self):
		self.obtn = {}
		self.sbtn = {}
		self.lbl = []
		self.gbtn = []
		self.iFrames =[]
		self.tag_lbl=[]
		self.d_ =False

		self.x = 0
		self.y=0

		self.backBtn = QPushButton(QIcon(':/Assets/close2.png'),'Close')
		self.backBtn.setObjectName('backBtn')
		self.backBtn.clicked.connect(partial(self.parent_.closeItemTab,self.id_))
		self.clbl = QLabel(self)
		self.clbl.setText(self.obj.courses[self.id_].c_name.upper())
		self.clbl.setObjectName('hlbl')

		a = ["assignment","exam","quiz"]

		for iz in range(0,len(self.obj.courses[self.id_].items)):
			if self.obj.courses[self.id_].items[iz].saved==2:
				pixmap=QPixmap(self.obj.tagDict[3])
			else:
				 str_= self.obj.courses[self.id_].items[iz].i_name.lower()
				 if any(x in str_ for x in a):
					 pixmap=QPixmap(self.obj.tagDict[4])
				 else:
					 pixmap=QPixmap(self.obj.tagDict[self.obj.courses[self.id_].items[iz].type+2])

			pixmap = pixmap.scaled(45, 45)
			self.tag_lbl.append(QLabel(self))
			self.tag_lbl[iz].setPixmap(pixmap)

		for i1 in range(0,len(self.obj.courses[self.id_].items)):
			self.lbl.append(ExtendedQLabel(self,i1))
			cname = self.shortenTabName(self.obj.courses[self.id_].items[i1].i_name)
			self.lbl[i1].setText(cname)
			self.lbl[i1].setObjectName("ilbl")

			self.sbtn[i1] = QPushButton('Save')
			self.sbtn[i1].setObjectName("sbtn")

			self.gbtn.append(QPushButton(QIcon(':/Assets/link.png'),''))
			self.gbtn[i1].setObjectName("linkBtn")
			self.gbtn[i1].id_ = i1
			self.gbtn[i1].clicked.connect(partial(self.copyLink,self.gbtn[i1].id_))
			self.sbtn[i1].clicked.connect(partial(self.saveItem,self.gbtn[i1].id_))

			if self.obj.courses[self.id_].items[i1].saved==1:
				self.obtn[i1] = QPushButton('Open')
				if self.obj.courses[self.id_].items[i1].dwnld == 1:
					self.obtn[i1].setObjectName("obtn")
				else:
					self.obtn[i1].setObjectName("rbtn")
				self.obtn[i1].clicked.connect(partial(self.openItem,self.gbtn[i1].id_))

		self.iFrames.append(topFrame(self.backBtn,self.clbl))

		for i2 in range(0,len(self.obj.courses[self.id_].items)):
			if self.obj.courses[self.id_].items[i2].saved==1:
				self.iFrames.append(itemFrames(self.lbl[i2],self.gbtn[i2],self.sbtn[i2],self.obtn[i2],self.tag_lbl[i2]))
			elif self.obj.courses[self.id_].items[i2].saved==0:
				self.iFrames.append(itemFramesNew(self.lbl[i2],self.gbtn[i2],self.sbtn[i2],self.tag_lbl[i2]))
			else:
				self.iFrames.append(itemFramesForum(self.lbl[i2],self.gbtn[i2],self.tag_lbl[i2]))

		self.widget = QWidget(self)

		self.vbox = QVBoxLayout()
		self.vbox.setSpacing(3)

		for index, frame in enumerate(self.iFrames):
			if index%2==0:
				frame.setObjectName('nFrameEven')
			else:
				frame.setObjectName('nFrameEven')
			self.vbox.addWidget(frame)

		self.vbox.setContentsMargins(0,0,0,0)

		if len(self.iFrames)<4:
			self.dframe = QFrame()
			self.dframe.setObjectName('nFrameDummy')
			self.vbox.addWidget(self.dframe)
			self.d_=True

		self.widget.setLayout(self.vbox)
		self.scroll = QScrollArea(self)
		self.scroll.setWidget(self.widget)
		self.scroll.setWidgetResizable(True)
		self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
		self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)

		vbox1 = QVBoxLayout()
		vbox1.setContentsMargins(0,0,0,0)
		vbox1.setSpacing(0)

		vbox1.addWidget(self.scroll)
		self.setLayout(vbox1)


	def shortenTabName(self,cname):
		list1 =[]
		for i, ch in enumerate(cname):
				list1.append(ch)
				if len(list1)==40:
					if list1[i]== ' ':
						list1.pop(i)
					list1.append('...')
					break
		return ''.join(list1)

	def updater(self):
		self.x=+1
		if self.obj.courses[self.id_].dummy_items is '':
			return
		else:
			if  self.d_==True:
				child = self.vbox.takeAt(len(self.iFrames))
				if child.widget() is not None:
					child.widget().deleteLater()
				elif child.layout() is not None:
					clearLayout(child.layout())
				self.dframe.deleteLater()
				self.d_=False

			marker = len(self.iFrames)-1

			a = ["assignment","exam","quiz"]
			for iz in range(marker,len(self.obj.courses[self.id_].items)):
				if self.obj.courses[self.id_].items[iz].saved==2:
					pixmap=QPixmap(self.obj.tagDict[3])
				else:
					 str_= self.obj.courses[self.id_].items[iz].i_name.lower()
					 if any(x in str_ for x in a):
						 pixmap=QPixmap(self.obj.tagDict[4])
					 else:
						 pixmap=QPixmap(self.obj.tagDict[self.obj.courses[self.id_].items[iz].type+2])
				pixmap = pixmap.scaled(45, 45)
				self.tag_lbl.append(QLabel(self))
				self.tag_lbl[iz].setPixmap(pixmap)

			for i1 in range(marker,len(self.obj.courses[self.id_].items)):
				self.lbl.append(ExtendedQLabel(self,i1))
				cname = self.shortenTabName(self.obj.courses[self.id_].items[i1].i_name)
				self.lbl[i1].setText(cname)
				self.lbl[i1].setObjectName("ilbl")
				self.sbtn[i1] = QPushButton('Save')
				self.sbtn[i1].setObjectName("sbtn")
				self.gbtn.append(QPushButton(QIcon(':/Assets/link.png'),''))
				self.gbtn[i1].setObjectName("linkBtn")
				self.gbtn[i1].id_ = i1
				self.gbtn[i1].clicked.connect(partial(self.copyLink,self.gbtn[i1].id_))
				self.sbtn[i1].clicked.connect(partial(self.saveItem,self.gbtn[i1].id_))

				if self.obj.courses[self.id_].items[i1].saved==1:
					self.obtn[i1] = QPushButton('Open')
					if self.obj.courses[self.id_].items[i1].dwnld == 1:
						self.obtn[i1].setObjectName("obtn")
					else:
						self.obtn[i1].setObjectName("rbtn")
					self.obtn[i1].clicked.connect(partial(self.openItem,self.gbtn[i1].id_))

			for i2 in range(marker,len(self.obj.courses[self.id_].items)):
				if self.obj.courses[self.id_].items[i2].saved==1:
					self.iFrames.append(itemFrames(self.lbl[i2],self.gbtn[i2],self.sbtn[i2],self.obtn[i2],self.tag_lbl[i2]))
				elif self.obj.courses[self.id_].items[i2].saved==0:
					self.iFrames.append(itemFramesNew(self.lbl[i2],self.gbtn[i2],self.sbtn[i2],self.tag_lbl[i2]))
				else:
					self.iFrames.append(itemFramesForum(self.lbl[i2],self.gbtn[i2],self.tag_lbl[i2]))

			for i3 in range (marker+1,len(self.iFrames)):
				if i3%2==0:
					self.iFrames[i3].setObjectName('nFrameEven')
				else:
					self.iFrames[i3].setObjectName('nFrameEven')

				self.vbox.addWidget(self.iFrames[i3])
				y=0

			if len(self.iFrames)<4:
				self.dframe = QFrame()
				self.dframe.setObjectName('nFrameDummy')
				self.vbox.addWidget(self.dframe)
				self.d_=True


	def saveItem(self,id_):
		fileName = QFileDialog.getOpenFileName(self, 'Select Your File')
		if fileName[0] is '':
			return
		else:
			self.obtn[id_] = QPushButton('Open')
			self.obtn[id_].setObjectName("rbtn")
			self.obtn[id_].clicked.connect(partial(self.openItem,self.gbtn[id_].id_))
			self.obj.courses[self.id_].items[id_].olink = fileName[0]
			self.obj.courses[self.id_].items[id_].saved=1
			self.iFrames[id_+1].grid.addWidget(self.obtn[id_],1,3)
			self.parent_.parent_.itemsWriter(fileName[0],self.id_,id_)

	def openItem(self,id_):
		fileName = self.obj.courses[self.id_].items[id_].olink
		if sys.platform == "win32":
			if os.path.exists(fileName):
				if self.obj.courses[self.id_].items[id_].type==1 and self.obj.courses[self.id_].items[id_].dwnld==1 :
					self.parent_.openTextBrowser(fileName,self.obj.courses[self.id_].c_name,self.obj.courses[self.id_].items[id_].i_name)
				else:
					os.startfile(fileName)
			else:
				reply = QMessageBox.information(self,'Moodly',"This file no longer exists. Kindly save a new link.", QMessageBox.Ok)
				if reply == QMessageBox.Ok:
					pass
		else:
			if os.path.exists(fileName):
				opener ="open" if sys.platform == "darwin" else "xdg-open"
				subprocess.call([opener, fileName])
			else:
				reply = QMessageBox.information(self,'Moodly',"This file no longer exists. Kindly save a new link.", QMessageBox.Ok)
				if reply == QMessageBox.Ok:
					pass

	def copyLink(self,id_):
		cb = QApplication.clipboard()
		cb.clear(mode=cb.Clipboard)
		cb.setText(self.obj.courses[self.id_].items[id_].glink, mode=cb.Clipboard)
		reply = QMessageBox.information(self,'Moodly',"The link has been copied", QMessageBox.Ok)
		if reply == QMessageBox.Ok:
			pass
Example #31
0
class MainWindow(QMainWindow):
   def __init__(self, client_socket, process_events_method):
      super().__init__()

      self.username = config.user["username"]
      self.client_socket = client_socket
      self.client_socket.recv_message.connect(self.recvMessage)
      self.process_events_method = process_events_method
      self.chats = {}
      self.send_on_enter = True

      self.initMenubar()
      self.initUI()

      for chat in config.chats:
         self.createChat(chat, config.chats[chat]["participants"])

   def initMenubar(self):
      self.createChatAction = QAction("&Create Chat", self)
      #self.exitAction.setShortcut("Ctrl+Q")
      self.createChatAction.triggered.connect(self.createChat)

      self.addFriendAction = QAction("&Add Friend", self)
      self.addFriendAction.triggered.connect(self.addFriend)

      self.menubar = self.menuBar()
      self.chatMenu = self.menubar.addMenu("&Chat")
      self.chatMenu.addAction(self.createChatAction)

      self.friendMenu = self.menubar.addMenu("&Friend")
      self.friendMenu.addAction(self.addFriendAction)

   def initUI(self):
      self.content = QWidget()

      self.hbox = QHBoxLayout(self.content)
      self.setCentralWidget(self.content)

      self.friend_list = QListWidget()
      self.friend_list.itemClicked.connect(self.friendClicked)

      self.message_scroll = QScrollArea()
      self.message_scroll.setWidgetResizable(True)
      #TODO have a setting to disable this
      self.message_scroll.verticalScrollBar().rangeChanged.connect(self.scrollBottom)

      self.message_input = MessageInput()
      self.message_input.sendMessage.connect(self.sendMessage)

      self.message_split = QSplitter(Qt.Vertical)
      self.message_split.addWidget(self.message_scroll)
      self.message_split.addWidget(self.message_input)

      self.main_split = QSplitter(Qt.Horizontal)
      self.main_split.addWidget(self.friend_list)
      self.main_split.addWidget(self.message_split)

      self.hbox.addWidget(self.main_split)

      self.show()

   def addFriend(self, username=None):
      if type(username) is bool:
         add_friend_dialog = AddFriendDialog(self.client_socket)
         add_friend_dialog.exec_()
         if add_friend_dialog.selected_user == None:
            return
         username = add_friend_dialog.selected_user
      self.chats[username] = {"participants":[username], "messages":[]}
      #TODO we should probably sanatize these to prevent directory manipulation
      friend = QListWidgetItem(QIcon(config.ICON_DIR + username + ".png"), username)
      self.friend_list.addItem(friend)

   def createChat(self, chat_name=None, participants=None):
      if type(chat_name) is bool:
         create_chat_dialog = CreateChatDialog()
         create_chat_dialog.exec_()
         if not create_chat_dialog.created_chat:
            return
         chat_name = create_chat_dialog.chat_name
         participants = create_chat_dialog.participants
      self.chats[chat_name] = {"participants":participants, "messages":[]}
      self.friend_list.addItem(QListWidgetItem(chat_name))

   def friendClicked(self, item):
      self.loadMessages(str(item.text()))

   def loadMessages(self, chat):
      #self.clearMessages()
      #TODO make the message history look pretty
      #TODO consider storing a message history for each chat and switch between when needed
      #TODO create a chat class and store the chat name as well as the participants there
      #TODO index message histories by chat name
      self.message_history = QVBoxLayout()
      self.message_history.setSpacing(0)
      self.message_history.setContentsMargins(0,0,0,0)
      self.message_history.insertStretch(-1, 1)
      self.message_history_container = QWidget()
      self.message_history_container.setLayout(self.message_history)
      self.message_scroll.setWidget(self.message_history_container)
      for message in self.chats[chat]["messages"]:
         self.drawMessage(message)

   def clearMessages(self):
      while not self.message_history.isEmpty():
         self.message_history.takeAt(0)

   def sendMessage(self):
      message = self.message_input.toPlainText().strip()
      chat = self.friend_list.selectedItems()[0].text()
      self.client_socket.sendMessage(message, chat, self.chats[chat]["participants"])
      print("Sending: %s to %s" % (message, chat))
      self.message_input.setText("")
      self.recvMessage(chat, {"sender":self.username, "message":message})

   def recvMessage(self, chat, message):
      self.chats[chat]["messages"].append(message)
      current_chat = self.friend_list.selectedItems()[0].text()
      if current_chat == chat:
         self.drawMessage(message)

   def drawMessage(self, message):
      #TODO add a timestamp to messages
      new_message = QLabel(message["sender"] + ':' + message["message"])
      self.message_history.addWidget(new_message)

   def scrollBottom(self):
      self.message_scroll.verticalScrollBar().setValue(self.message_scroll.verticalScrollBar().maximum())

   def disconnect(self):
      self.client_socket.disconnect()
class PathEditor(ViewInfoChanger):
    def __init__(self, header, info, parent: ViewShower):
        super().__init__(header, info, parent)
        self.way_layout = QVBoxLayout()
        push_btn = self.main_layout.takeAt(self.main_layout.count() -
                                           1).widget()

        self.path_id = info[0]

        add_btn = QPushButton("Добавить")
        add_btn.clicked.connect(lambda e: self.add_cell(-1))
        self.main_layout.addWidget(add_btn)

        way = self.db.execute(
            f"SELECT Станция, `Код станции` FROM `состав_маршрута_view` where `Номер маршрута` = {self.path_id}"
        )
        for pos, point in enumerate(way, start=-1):
            station_info = str(point[0])
            self.combo_change_idx["Станция отправления"][station_info] = point[
                1]
            self.add_cell(pos, station_info)

        self.main_layout.addLayout(self.way_layout)
        self.main_layout.addWidget(push_btn)

    def add_cell(self, pos: int, txt=""):
        """Вставляет ячейку ниже активирующей кнопки для вставки на уровне надо передать ::pos:: = -1"""

        cell = QHBoxLayout()
        edi = QLineEdit()
        edi.setText(txt)

        add_btn = QPushButton("Добавить")
        down_btn = QPushButton("Вниз")
        up_btn = QPushButton("Вверх")
        del_btn = QPushButton("Удалить")

        cmb = QComboBox()
        cmb.addItem(txt)

        edi.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        cmb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        up_btn.clicked.connect(lambda e, p=pos, c=cell: self.move_cell_up(c))
        down_btn.clicked.connect(
            lambda e, p=pos, c=cell: self.move_cell_down(c))

        add_btn.clicked.connect(lambda e, c=cell: self.add_cell(c.pos))
        del_btn.clicked.connect(lambda e, c=cell: self.del_cell(c.pos))

        edi.editingFinished.connect(
            lambda c=cmb, t=edi.text: self.combo_update(
                "Станция отправления", c, t()))  # le-kostyl
        cell.pos = pos
        cell.addWidget(edi)
        cell.addWidget(cmb)
        cell.addWidget(add_btn)
        cell.addWidget(down_btn)
        cell.addWidget(up_btn)
        cell.addWidget(del_btn)
        for i in range(pos + 1, self.way_layout.count()):
            cell_to_move = self.way_layout.itemAt(i)
            cell_to_move.pos += 1
        cell.pos += 1  # для вставки ниже активированной кнопки

        self.way_layout.insertLayout(cell.pos, cell)

    def del_cell(self, pos):
        cell: QVBoxLayout
        cell = self.way_layout.takeAt(pos)
        for i in range(cell.count()):
            w = cell.takeAt(0).widget()
            w.deleteLater()
        cell.deleteLater()

        for i in range(pos, self.way_layout.count()):
            cell_to_move = self.way_layout.itemAt(i)
            cell_to_move.pos -= 1

    def move_cell_up(self, cell):
        if cell.pos > 0:
            old_cell = self.way_layout.itemAt(cell.pos - 1)
            self.way_layout.takeAt(cell.pos)
            self.way_layout.insertLayout(cell.pos - 1, cell)
            old_cell.pos += 1
            cell.pos -= 1

    def move_cell_down(self, cell):
        if cell.pos < self.way_layout.count() - 1:
            old_cell = self.way_layout.itemAt(cell.pos + 1)
            self.way_layout.takeAt(cell.pos)
            self.way_layout.insertLayout(cell.pos + 1, cell)
            old_cell.pos -= 1
            cell.pos += 1

    def push_point_changes(self):
        params = []
        station_ord_numb_in_path = 0
        for i in range(self.way_layout.count()):
            cell = self.way_layout.itemAt(i)
            w = cell.itemAt(1).widget()
            station = w.currentText()
            if station:
                station_id = self.combo_change_idx["Станция отправления"][
                    station]
                params.append(
                    (self.path_id, station_ord_numb_in_path, station_id))
                station_ord_numb_in_path += 1

        query = f" insert into `состав маршрута` values(%s, %s, %s)"
        self.db.execute(
            "delete from `состав маршрута` where `Номер маршрута` = %s",
            (self.path_id, ))
        self.db.executemany(query, params)
        self.db.commit()

    def push_changes(self):
        self.push_point_changes()
        super().push_changes()
Example #33
0
class MainWindow(QWidget):
    ##
    # \brief Initialization Function
    def __init__(self):
        super(MainWindow, self).__init__()

        #Default variables
        self.valid = False  #Field to determine if the value is valid
        self.selectorLayout = None  #Layout used for selecting a specific source
        self.sources = ["none", "text", "file", "database"]
        self.source = {"type": None}
        self.dests = ["console", "file"]
        self.dest = {"type": "console"}

        self.sourceValue = None
        self.sourceSchema = None

        #Determine screen settings
        geo = self.frameGeometry()
        self.width = QDesktopWidget().availableGeometry().width()
        self.height = QDesktopWidget().availableGeometry().height()

        #Define window par meters
        self.resize(self.width * .5, self.height * .5)
        self.setWindowTitle("Aqueti Schema Editor")
        self.show()

        #create Layouts in UI
        self.titleLayout = QHBoxLayout()
        self.mainLayout = QVBoxLayout()
        self.sourceLayout = QHBoxLayout()
        self.destLayout = QHBoxLayout()
        self.valueLayout = QVBoxLayout()
        self.buttonLayout = QHBoxLayout()

        #Create frames
        self.sourceFrame = QFrame()
        self.destFrame = QFrame()
        self.valueFrame = QFrame()
        self.sourceFrame.setFrameStyle(QFrame.Box)
        self.valueFrame.setFrameStyle(QFrame.Box)
        self.destFrame.setFrameStyle(QFrame.Box)

        self.sourceFrame.setLayout(self.sourceLayout)
        self.destFrame.setLayout(self.destLayout)
        self.valueFrame.setLayout(self.valueLayout)
        self.valueFrame.setSizePolicy(QSizePolicy.Expanding,
                                      QSizePolicy.Expanding)

        #Create Scoll Area for valueFrame
        self.valueScrollArea = QScrollArea()
        self.valueScrollArea.updateGeometry()
        self.valueScrollArea.setWidget(self.valueFrame)
        self.valueScrollArea.setWidgetResizable(True)

        #Create title
        title = QLabel()
        title.setText("Aqueti Schema Editor")
        self.titleLayout.addWidget(title)

        #Add persistent source items
        sourceTitle = QLabel()
        sourceTitle.setText("Source:")
        self.sourceCombo = QComboBox()
        self.sourceCombo.addItems(self.sources)
        self.sourceCombo.currentTextChanged.connect(
            lambda: self.sourceChangeCallback())
        selectSourceButton = QPushButton("Load")
        self.sourceLayout.addWidget(sourceTitle)
        self.sourceLayout.addWidget(self.sourceCombo)

        self.sourceMetaLayout = QHBoxLayout()
        self.sourceMetaLayout.setSizeConstraint(QHBoxLayout.SetMinimumSize)
        self.sourceLayout.addLayout(self.sourceMetaLayout)
        self.sourceLayout.addWidget(selectSourceButton)

        #Add persistent dest
        destTitle = QLabel()
        destTitle.setText("Dest:")
        self.destCombo = QComboBox()
        self.destCombo.addItems(self.dests)
        self.destCombo.currentTextChanged.connect(
            lambda: self.destChangeCallback())
        selectDestButton = QPushButton("Load")
        self.destLayout.addWidget(destTitle)
        self.destLayout.addWidget(self.destCombo)

        self.destMetaLayout = QHBoxLayout()
        self.destMetaLayout.setSizeConstraint(QHBoxLayout.SetMinimumSize)
        self.destLayout.addLayout(self.destMetaLayout)
        self.destLayout.addWidget(selectDestButton)

        #Add Submit Button
        self.submitButton = QPushButton("Submit")
        self.submitButton.clicked.connect(lambda: self.submitCallback())
        self.buttonLayout.addWidget(self.submitButton)

        #Add cancel Button
        cancelButton = QPushButton("Cancel")
        cancelButton.clicked.connect(lambda: self.cancelCallback())
        self.buttonLayout.addWidget(cancelButton)

        #Add Layouts and draw
        self.mainLayout.addLayout(self.titleLayout)
        self.mainLayout.addWidget(self.sourceFrame)
        self.mainLayout.addWidget(self.destFrame)

        #        self.mainLayout.addWidget( self.valueFrame )
        self.mainLayout.addWidget(self.valueScrollArea)
        #        self.mainLayout.addStretch(1)
        self.mainLayout.addLayout(self.buttonLayout)
        self.draw()

    ##
    # \brief updates the source Layout
    def updateSourceLayout(self):
        #Remove current layout information
        #Remove all widgets from the current layout
        while self.sourceMetaLayout.count():
            item = self.sourceMetaLayout.takeAt(0)
            self.sourceMetaLayout.removeItem(item)
            widget = item.widget()
            if widget is not None:
                widget.deleteLater()
            try:
                item.deleteLater()
            except:
                pass

        #Find what our current source is and set the appropriate index
        index = 0
        for i in range(0, self.sourceCombo.count()):
            if self.sourceCombo.itemText(i) == self.source["type"]:
                index = i

        self.sourceCombo.setCurrentIndex(index)

        #Add fields based on source type
        if self.source["type"] == "file":
            #Add filename
            fileLabel = QLabel()
            fileLabel.setText("file: ")

            try:
                name = self.source["filename"]
            except:
                name = ""

            self.sourceFilenameBox = QLineEdit()
            self.sourceFilenameBox.setSizePolicy(QSizePolicy.Expanding,
                                                 QSizePolicy.Minimum)
            self.sourceFilenameBox.setText(name)
            #            self.sourceFilenameBox.readOnly = True
            #            self.sourceFilenameBox.sizeHint()
            #            self.sourceFilenameBox.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)

            self.sourceMetaLayout.addWidget(fileLabel)
            self.sourceMetaLayout.addWidget(self.sourceFilenameBox)

#        #Add a submitSource Button
#        selectSourceButton = QPushButton("Load")
#        selectSourceButton.clicked.connect( lambda: self.sourceChangeCallback())

#        self.sourceLayout.addWidget(selectSourceButton)

#        self.sourceLayout.addStretch(1)

##
# \brief updates the destination layout
#

    def updateDestLayout(self):
        #Remove current layout information
        #Remove all widgets from the current layout
        while self.destMetaLayout.count():
            item = self.destMetaLayout.takeAt(0)
            self.destMetaLayout.removeItem(item)
            widget = item.widget()
            if widget is not None:
                widget.deleteLater()
            try:
                item.deleteLater()
            except:
                pass
        """
        #############################################
        # Layout to select a destination
        #############################################
        destTitle = QLabel()
        destTitle.setText("Dest:")
        self.destCombo = QComboBox()
        self.destCombo.addItems(self.dests)
        """

        #Find what our current dest is and set the appropriate index
        index = 0
        for i in range(0, self.destCombo.count()):
            if self.destCombo.itemText(i) == self.dest["type"]:
                index = i

        self.destCombo.setCurrentIndex(index)
        self.destCombo.currentTextChanged.connect(
            lambda: self.destChangeCallback())

        #        self.destLayout.addWidget(destTitle)
        #        self.destLayout.addWidget(self.destCombo)
        #        self.destLayout.addStretch(1)

        ####
        # Fill in details base on dest tpye
        ####
        if self.dest["type"] == "console":
            pass
        elif self.dest["type"] == "file":
            fileLabel = QLabel()
            fileLabel.setText("file: ")

            try:
                name = self.dest["filename"]
            except:
                name = ""

            self.fileNameBox = QLineEdit()
            self.fileNameBox.setText(name)

            #            self.destMetaLayout.addWidget(fileLabel)
            self.destMetaLayout.addWidget(self.fileNameBox)

    ##
    # \brief function that is called when the source is changed
    #
    def destChangeCallback(self):
        print("Changing dest")

        newType = self.destCombo.itemText(self.destCombo.currentIndex())

        print("New Type: " + str(newType))

        if newType != self.dest["type"]:
            self.dest = {}

        self.dest["type"] = newType

        if self.dest["type"] == "console":
            pass

        elif self.dest["type"] == "file":
            options = QFileDialog.Options()
            options |= QFileDialog.DontUseNativeDialog
            destName, _ = QFileDialog.getSaveFileName(
                self,
                "QFileDialog.getSaveFileName()",
                "",
                "All Files (*);;JSON Files (*.json)",
                options=options)

            self.dest["filename"] = str(destName)

        else:
            print("Unsupported Type")

        self.draw()

    ##
    # \brief Update the value layout
    def updateValueLayout(self):
        #Remove all widgets from the current layout
        while self.valueLayout.count():
            item = self.valueLayout.takeAt(0)
            self.valueLayout.removeItem(item)
            widget = item.widget()
            if widget is not None:
                widget.deleteLater()
            try:
                item.deleteLater()
            except:
                pass

        #If we have data, let's display it
        if self.sourceSchema != None:

            valueTitle = QLabel()
            valueTitle.setText("Schema")

            self.schemaWidget = SmartWidget().init("Schema",
                                                   self.sourceValue,
                                                   self.sourceSchema,
                                                   showSchema=False)
            self.valueLayout.addWidget(self.schemaWidget.frame)

        #Disable the submit button if we don't have a schema
        if self.sourceSchema == None:
            self.submitButton.setEnabled(False)
        else:
            self.submitButton.setEnabled(True)

        self.setLayout(self.mainLayout)

    ##
    # \brief redraws all dynamic layouts
    def draw(self):
        self.updateDestLayout()
        self.updateSourceLayout()
        self.updateValueLayout()

    ##
    # \brief callback for when the source type changes
    #
    def sourceChangeCallback(self):

        #SDF Add popup to notify of schema loss

        #Clear the schema to disable the submit button
        self.sourceSchema = None
        self.source["type"] = self.sourceCombo.itemText(
            self.sourceCombo.currentIndex())

        if self.source["type"] == "none":
            self.sourceSchema = {"bsonType": "object"}

        #If we are a file  read the file contents as the value
        elif self.source["type"] == "file":
            options = QFileDialog.Options()
            options |= QFileDialog.DontUseNativeDialog
            sourceName, _ = QFileDialog.getOpenFileName(
                self,
                "QFileDialog.getOpenFileName()",
                "",
                "All Files (*);;JSON Files (*.json)",
                options=options)

            self.source["filename"] = str(sourceName)
            print("Loading: " + str(self.source["filename"]))

            with open(self.source["filename"]) as json_file:
                self.sourceSchema = json.load(json_file)

            print("Loaded Schema:" +
                  str(json.dumps(self.sourceSchema, indent=4)))

        self.updateSourceLayout()
        self.updateValueLayout()

    ##
    #\brief callback to get result from SmartWidget
    #
    # This function assumes that the schema is done. It will produce a popup
    # asking where and how to save the data
    #
    def submitCallback(self):
        schema = self.schemaWidget.getSchema()

        if self.dest["type"] == "console":
            print()
            print("Schema: (" + str(time.time()) + ")")
            print(json.dumps(schema, indent=4))

        elif self.dest["type"] == "file":
            print("Writing to: " + str(self.dest["filename"]))
            with open(self.dest["filename"], 'w') as outfile:
                json.dump(schema, outfile, indent=4)
        else:
            print("Source type: " + str(self.dest["type"]) +
                  " is not currently supported")

        self.close()

        #Use save pop-up to save data
        #self.saveWindow = SaveDataWindow(self.source, schema, self.saveCallback )

        print(str(time.time()) + "- schema:")
        print(str(schema))

    ##
    # \brief Function called after data is saved
    #
    def saveCallback(self, success):
        print("Data Result: " + str(success))

    ##
    # \brief Cancels the change and exits
    #
    def cancelCallback(self):
        print("Exited. No changes were saved")
        sys.exit(1)
Example #34
0
class PromptContainer(QWidget):

    """Container for prompts to be shown above the statusbar.

    This is a per-window object, however each window shows the same prompt.

    Attributes:
        _layout: The layout used to show prompts in.
        _win_id: The window ID this object is associated with.

    Signals:
        update_geometry: Emitted when the geometry should be updated.
    """

    STYLESHEET = """
        {% set prompt_radius = config.get('ui', 'prompt-radius') %}
        QWidget#PromptContainer {
            {% if config.get('ui', 'status-position') == 'top' %}
                border-bottom-left-radius: {{ prompt_radius }}px;
                border-bottom-right-radius: {{ prompt_radius }}px;
            {% else %}
                border-top-left-radius: {{ prompt_radius }}px;
                border-top-right-radius: {{ prompt_radius }}px;
            {% endif %}
        }

        QWidget {
            font: {{ font['prompts'] }};
            color: {{ color['prompts.fg'] }};
            background-color: {{ color['prompts.bg'] }};
        }

        QTreeView {
            selection-background-color: {{ color['prompts.selected.bg'] }};
        }

        QTreeView::item:selected, QTreeView::item:selected:hover {
            background-color: {{ color['prompts.selected.bg'] }};
        }
    """
    update_geometry = pyqtSignal()

    def __init__(self, win_id, parent=None):
        super().__init__(parent)
        self._layout = QVBoxLayout(self)
        self._layout.setContentsMargins(10, 10, 10, 10)
        self._win_id = win_id
        self._prompt = None

        self.setObjectName('PromptContainer')
        self.setAttribute(Qt.WA_StyledBackground, True)
        style.set_register_stylesheet(self)

        message.global_bridge.prompt_done.connect(self._on_prompt_done)
        prompt_queue.show_prompts.connect(self._on_show_prompts)
        message.global_bridge.mode_left.connect(self._on_global_mode_left)

    def __repr__(self):
        return utils.get_repr(self, win_id=self._win_id)

    @pyqtSlot(usertypes.Question)
    def _on_show_prompts(self, question):
        """Show a prompt for the given question.

        Args:
            question: A Question object or None.
        """
        item = self._layout.takeAt(0)
        if item is not None:
            widget = item.widget()
            log.prompt.debug("Deleting old prompt {}".format(widget))
            widget.hide()
            widget.deleteLater()

        if question is None:
            log.prompt.debug("No prompts left, hiding prompt container.")
            self._prompt = None
            self.hide()
            return

        classes = {
            usertypes.PromptMode.yesno: YesNoPrompt,
            usertypes.PromptMode.text: LineEditPrompt,
            usertypes.PromptMode.user_pwd: AuthenticationPrompt,
            usertypes.PromptMode.download: DownloadFilenamePrompt,
            usertypes.PromptMode.alert: AlertPrompt,
        }
        klass = classes[question.mode]
        prompt = klass(question)

        log.prompt.debug("Displaying prompt {}".format(prompt))
        self._prompt = prompt

        if not question.interrupted:
            # If this question was interrupted, we already connected the signal
            question.aborted.connect(
                lambda: modeman.leave(self._win_id, prompt.KEY_MODE, 'aborted',
                                      maybe=True))
        modeman.enter(self._win_id, prompt.KEY_MODE, 'question asked')

        self.setSizePolicy(prompt.sizePolicy())
        self._layout.addWidget(prompt)
        prompt.show()
        self.show()
        prompt.setFocus()
        self.update_geometry.emit()

    @pyqtSlot(usertypes.KeyMode)
    def _on_prompt_done(self, key_mode):
        """Leave the prompt mode in this window if a question was answered."""
        modeman.leave(self._win_id, key_mode, ':prompt-accept', maybe=True)

    @pyqtSlot(usertypes.KeyMode)
    def _on_global_mode_left(self, mode):
        """Leave prompt/yesno mode in this window if it was left elsewhere.

        This ensures no matter where a prompt was answered, we leave the prompt
        mode and dispose of the prompt object in every window.
        """
        if mode not in [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]:
            return
        modeman.leave(self._win_id, mode, 'left in other window', maybe=True)
        item = self._layout.takeAt(0)
        if item is not None:
            widget = item.widget()
            log.prompt.debug("Deleting prompt {}".format(widget))
            widget.hide()
            widget.deleteLater()

    @cmdutils.register(instance='prompt-container', hide=True, scope='window',
                       modes=[usertypes.KeyMode.prompt,
                              usertypes.KeyMode.yesno])
    def prompt_accept(self, value=None):
        """Accept the current prompt.

        //

        This executes the next action depending on the question mode, e.g. asks
        for the password or leaves the mode.

        Args:
            value: If given, uses this value instead of the entered one.
                   For boolean prompts, "yes"/"no" are accepted as value.
        """
        question = self._prompt.question
        try:
            done = self._prompt.accept(value)
        except Error as e:
            raise cmdexc.CommandError(str(e))
        if done:
            message.global_bridge.prompt_done.emit(self._prompt.KEY_MODE)
            question.done()

    @cmdutils.register(instance='prompt-container', hide=True, scope='window',
                       modes=[usertypes.KeyMode.yesno],
                       deprecated='Use :prompt-accept yes instead!')
    def prompt_yes(self):
        """Answer yes to a yes/no prompt."""
        self.prompt_accept('yes')

    @cmdutils.register(instance='prompt-container', hide=True, scope='window',
                       modes=[usertypes.KeyMode.yesno],
                       deprecated='Use :prompt-accept no instead!')
    def prompt_no(self):
        """Answer no to a yes/no prompt."""
        self.prompt_accept('no')

    @cmdutils.register(instance='prompt-container', hide=True, scope='window',
                       modes=[usertypes.KeyMode.prompt], maxsplit=0)
    def prompt_open_download(self, cmdline: str = None):
        """Immediately open a download.

        If no specific command is given, this will use the system's default
        application to open the file.

        Args:
            cmdline: The command which should be used to open the file. A `{}`
                     is expanded to the temporary file name. If no `{}` is
                     present, the filename is automatically appended to the
                     cmdline.
        """
        try:
            self._prompt.download_open(cmdline)
        except UnsupportedOperationError:
            pass

    @cmdutils.register(instance='prompt-container', hide=True, scope='window',
                       modes=[usertypes.KeyMode.prompt])
    @cmdutils.argument('which', choices=['next', 'prev'])
    def prompt_item_focus(self, which):
        """Shift the focus of the prompt file completion menu to another item.

        Args:
            which: 'next', 'prev'
        """
        try:
            self._prompt.item_focus(which)
        except UnsupportedOperationError:
            pass
Example #35
0
class DatasetsWidget(QWidget):
    tab_active = pyqtSignal()
    go_to_run_tab = pyqtSignal()

    def __init__(self, iface: QgisInterface) -> None:
        super().__init__()
        self.iface = iface

        self.options = get_options()

        self.tab_active.connect(self.on_tab_active)

        self.create_geographical_data_box()
        self.create_meteorological_data_box()

        go_to_run_tab_btn = QPushButton('Continue to Run')
        go_to_run_tab_btn.clicked.connect(self.go_to_run_tab)

        vbox = QVBoxLayout()
        vbox.addWidget(self.gbox_geodata)
        vbox.addWidget(self.gbox_metdata)
        vbox.addWidget(go_to_run_tab_btn)
        self.setLayout(vbox)

        Broadcast.geo_datasets_updated.connect(self.populate_geog_data_tree)
        Broadcast.met_datasets_updated.connect(self.populate_met_data_tree)
        Broadcast.project_updated.connect(self.populate_geog_data_tree)

    @property
    def project(self) -> Project:
        return self._project

    @project.setter
    def project(self, val: Project) -> None:
        ''' Sets the currently active project. See tab_simulation. '''
        self._project = val

        self.populate_geog_data_tree()
        self.populate_met_data_tree()

    def on_tab_active(self):
        self.update_geo_datasets_spec_fields()
        self.set_met_data_current_config_label()

    #region Geographical Data

    def create_geographical_data_box(self) -> None:
        self.gbox_geodata = QGroupBox('Geographical Data')
        vbox_geodata = QVBoxLayout()
        self.gbox_geodata.setLayout(vbox_geodata)

        self.label_geodata_wps_not_setup = QLabel(
            '<html>NOTE: Your project does not yet have a copy of a GEOGRID.TBL file.<br>'
            'This is required for configuring geographical data.<br>'
            'You need to <a href="https://gis4wrf.github.io/configuration/">setup WPS</a> first '
            'so that GIS4WRF can do an automatic<br>copy of that file. When you are done, return here.</html>'
        )
        self.label_geodata_wps_not_setup.setOpenExternalLinks(True)
        self.label_geodata_wps_not_setup.setVisible(False)
        vbox_geodata.addWidget(self.label_geodata_wps_not_setup)

        gbox_avail_datasets = QGroupBox('Available Datasets')
        vbox_geodata.addWidget(gbox_avail_datasets)

        vbox_avail_dataset = QVBoxLayout()
        gbox_avail_datasets.setLayout(vbox_avail_dataset)

        self.tree_geog_data = QTreeWidget()
        self.tree_geog_data.setMinimumHeight(200)
        vbox_avail_dataset.addWidget(self.tree_geog_data)
        self.tree_geog_data.itemDoubleClicked.connect(
            self.on_geog_data_tree_doubleclick)
        self.tree_geog_data.setHeaderItem(
            QTreeWidgetItem(
                ['Group / Variable', 'Resolution', 'Interpolation Method']))

        self.tree_geog_data.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree_geog_data.customContextMenuRequested.connect(
            self.on_tree_geog_data_open_context_menu)

        self.add_geog_dataset_button = QPushButton('Add Dataset to List')
        self.add_geog_dataset_button.clicked.connect(
            self.on_add_geog_dataset_button_clicked)
        vbox_avail_dataset.addWidget(self.add_geog_dataset_button)

        self.create_geog_form()
        vbox_avail_dataset.addWidget(self.geog_dataset_form)

        gbox_datasets_spec = QGroupBox('Selection')
        vbox_geodata.addWidget(gbox_datasets_spec)
        self.vbox_geo_datasets_spec = QVBoxLayout()
        gbox_datasets_spec.setLayout(self.vbox_geo_datasets_spec)

        self.geodata_gboxes = [gbox_avail_datasets, gbox_datasets_spec]

        # selection fields get created in the on_tab_active handler
        self.geo_dataset_spec_inputs = []

    def update_geo_datasets_spec_fields(self) -> None:
        msg_bar = self.iface.messageBar()  # type: QgsMessageBar
        specs = self.project.geo_dataset_specs
        domain_cnt = self.project.domain_count
        field_cnt = self.vbox_geo_datasets_spec.count()

        while field_cnt > domain_cnt:
            layout = self.vbox_geo_datasets_spec.takeAt(field_cnt - 1)
            clear_layout(layout)
            del self.geo_dataset_spec_inputs[-1]
            self.update_project_geo_dataset_specs()
            field_cnt -= 1

        while field_cnt < domain_cnt:

            def create_on_plus_clicked_callback():
                # This code is in this inner function to have a copy of field_cnt,
                # otherwise all on_clicked handlers would refer to field_cnt of the
                # last loop iteration.
                domain_index = field_cnt

                def on_clicked():
                    spec_input = self.geo_dataset_spec_inputs[domain_index]
                    item = self.tree_geog_data.currentItem()
                    if item is None:
                        msg_bar.pushInfo(
                            PLUGIN_NAME,
                            'Select a group in the dataset tree before clicking the + button.'
                        )
                        return
                    if item.childCount() == 0:
                        msg_bar.pushWarning(
                            PLUGIN_NAME,
                            'Select a group in the dataset tree, not a variable.'
                        )
                        return
                    new_group_name = item.data(0, Qt.UserRole)
                    val = spec_input.value()
                    if val:
                        existing_group_names = val.split('+')
                        if new_group_name in existing_group_names:
                            msg_bar.pushWarning(
                                PLUGIN_NAME,
                                'The group you selected is already added for this domain.'
                            )
                            return
                        val += '+'
                    val += new_group_name
                    spec_input.set_value(val)

                return on_clicked

            def create_on_info_clicked_callback():
                # This code is in this inner function to have a copy of field_cnt,
                # otherwise all on_clicked handlers would refer to field_cnt of the
                # last loop iteration.
                domain_index = field_cnt

                def on_clicked():
                    spec_input = self.geo_dataset_spec_inputs[domain_index]
                    group_names = spec_input.value().split('+')

                    # determine resolved datasets for each variable
                    tbl = self.geogrid_tbl
                    resolved_groups = dict(
                    )  # var name -> (group name or None)
                    for variable in sorted(tbl.variables.values(),
                                           key=lambda v: v.name):
                        var_group_names = variable.group_options.keys()
                        found_group_name = None
                        for group_name in group_names:
                            if group_name in var_group_names:
                                found_group_name = group_name
                                break
                        resolved_groups[variable.name] = found_group_name

                    # show in message box
                    title = 'Domain {}'.format(domain_index + 1)
                    text = '<table><tr><td><b>Variable</b></td><td><b>Group</b></td></tr>'
                    for var_name, group_name in resolved_groups.items():
                        if group_name is None:
                            group_name = '<b><i>N/A</i></b>'
                        text += '<tr><td>{}</td><td>{}</td></tr>'.format(
                            var_name, group_name)
                    text += '</table>'
                    QMessageBox.information(self.iface.mainWindow(), title,
                                            text)

                return on_clicked

            hbox_datasets_spec = QHBoxLayout()
            hbox_datasets_spec.addWidget(
                QLabel('Domain: {}'.format(field_cnt + 1)))
            dataset_spec_input = create_lineedit(StringValidator(
                self.is_valid_geo_dataset_spec),
                                                 required=True)
            try:
                dataset_spec_input.set_value(specs[field_cnt])
            except IndexError:
                pass
            dataset_spec_input.textChanged.connect(
                self.update_project_geo_dataset_specs)
            self.geo_dataset_spec_inputs.append(dataset_spec_input)
            hbox_datasets_spec.addWidget(dataset_spec_input)
            plus_btn = QPushButton('+',
                                   clicked=create_on_plus_clicked_callback())
            info_btn = QPushButton('?',
                                   clicked=create_on_info_clicked_callback())
            # TODO make this dynamic, using size policies didn't work
            info_btn.setMaximumWidth(50)
            plus_btn.setMaximumWidth(50)
            hbox_datasets_spec.addWidget(plus_btn)
            hbox_datasets_spec.addWidget(info_btn)
            self.vbox_geo_datasets_spec.addLayout(hbox_datasets_spec)
            field_cnt += 1

    def is_valid_geo_dataset_spec(self, spec: str) -> bool:
        # must only contain geographical dataset group names separated by pluses without duplicates
        tbl = self.geogrid_tbl
        if not tbl:
            return False
        spec_group_names = spec.split('+')
        if len(set(spec_group_names)) != len(spec_group_names):
            return False
        if any(group_name not in tbl.group_names
               for group_name in spec_group_names):
            return False
        return True

    def update_project_geo_dataset_specs(self) -> None:
        self.project.geo_dataset_specs = [
            inp.value() if inp.is_valid() else ''
            for inp in self.geo_dataset_spec_inputs
        ]

    def create_geog_form(self):
        grid = QGridLayout()
        self.geog_dataset_form = QGroupBox('Add Dataset')
        self.geog_dataset_form.setLayout(grid)
        self.geog_dataset_form.hide()

        group_name_validator = StringValidator(
            lambda s: s and re.fullmatch(r'[a-zA-Z0-9_]+', s))
        # TODO fix validator if empty
        interp_validator = StringValidator(lambda s: ' ' not in s)

        self.geog_dataset_form_group_name = add_grid_lineedit(
            grid,
            0,
            'Group Name',
            validator=group_name_validator,
            required=True)
        self.geog_dataset_form_dataset = add_grid_combobox(grid, 1, 'Dataset')
        self.geog_dataset_form_variable = add_grid_combobox(
            grid, 2, 'Variable')
        self.geog_dataset_form_interp = add_grid_combobox(
            grid, 3, 'Interpolation')
        self.geog_dataset_form_custom_interp = add_grid_lineedit(
            grid,
            4,
            'Custom Interpolation',
            validator=interp_validator,
            required=False)

        self.geog_dataset_form_variable.currentIndexChanged.connect(
            self.geog_dataset_form_variable_changed)

        btn_add = QPushButton('Add')
        btn_cancel = QPushButton('Cancel')
        btn_add.clicked.connect(self.on_add_geog_dataset_form_button_clicked)
        btn_cancel.clicked.connect(
            self.on_cancel_geog_dataset_form_button_clicked)

        grid.addWidget(btn_cancel, 5, 0)
        grid.addWidget(btn_add, 5, 1)

    def populate_geog_data_tree(self) -> None:
        tree = self.tree_geog_data
        tree.clear()

        try:
            tbl = self.geogrid_tbl = self.project.read_geogrid_tbl()
        except FileNotFoundError:
            # This happens when WPS is not setup.
            for box in self.geodata_gboxes:
                box.setEnabled(False)
            self.label_geodata_wps_not_setup.setVisible(True)
            return
        if tbl is None:
            return
        for box in self.geodata_gboxes:
            box.setEnabled(True)
        self.label_geodata_wps_not_setup.setVisible(False)
        add_derived_metadata_to_geogrid_tbl(tbl, self.options.geog_dir)

        for group_name in sorted(tbl.group_names):
            all_missing = not any(
                group_name in var.group_options
                and not var.group_options[group_name][GeogridTblKeys.MISSING]
                for var in tbl.variables.values())
            group_item = QTreeWidgetItem(self.tree_geog_data)
            group_item.setText(0, group_name)
            group_item.setData(0, Qt.UserRole, group_name)
            if all_missing:
                group_item.setDisabled(True)
                for i in [0, 1, 2]:
                    group_item.setToolTip(
                        i, 'No dataset available in this group')

            for var_name in sorted(tbl.variables.keys()):
                group_options_all = tbl.variables[var_name].group_options
                if group_name not in group_options_all:
                    continue
                group_options = group_options_all[group_name]
                interp = group_options[GeogridTblKeys.INTERP_OPTION]

                # not available when missing on disk
                resolution = group_options.get(GeogridTblKeys.RESOLUTION, '')

                var_item = QTreeWidgetItem(group_item)
                var_item.setText(0, var_name)
                var_item.setText(1, resolution)
                var_item.setText(2, interp)
                var_item.setToolTip(2, interp)
                var_item.setData(0, Qt.UserRole,
                                 group_options[GeogridTblKeys.ABS_PATH])
                if group_options[GeogridTblKeys.MISSING]:
                    var_item.setDisabled(True)
                    for i in [0, 1, 2]:
                        var_item.setToolTip(i, 'Dataset not available')

    def on_geog_data_tree_doubleclick(self, item: QTreeWidgetItem,
                                      column: int) -> None:
        if item.childCount() > 0:
            # clicked on a group
            return
        abs_path = item.data(0, Qt.UserRole)
        if not os.path.exists(abs_path):
            # dataset doesn't exist
            return
        load_wps_binary_layer(abs_path)

    def on_tree_geog_data_open_context_menu(self, position) -> None:
        item = self.tree_geog_data.currentItem()
        if item is None:
            return

        menu = QMenu()

        if item.parent() is None:
            remove_group_action = menu.addAction('Remove Group')
            remove_group_action.triggered.connect(
                self.on_tree_geog_data_remove_group_clicked)
        else:
            view_dataset_action = menu.addAction('View Dataset')
            view_dataset_action.triggered.connect(
                lambda: self.on_geog_data_tree_doubleclick(item, 0))

        menu.exec_(self.tree_geog_data.viewport().mapToGlobal(position))

    def on_tree_geog_data_remove_group_clicked(self):
        item = self.tree_geog_data.currentItem()
        group_name = item.data(0, Qt.UserRole)

        reply = QMessageBox.question(
            self.iface.mainWindow(), 'Remove Group',
            'Are you sure you want to remove the group "{}"?'.format(
                group_name), QMessageBox.Yes, QMessageBox.No)
        if reply == QMessageBox.No:
            return

        self.geogrid_tbl.remove(group_name)
        self.project.write_geogrid_tbl(self.geogrid_tbl)

        self.populate_geog_data_tree()

    def on_add_geog_dataset_button_clicked(self):
        self.add_geog_dataset_button.hide()
        self.geog_dataset_form.show()

        self.geog_dataset_form_dataset.clear()
        self.geog_dataset_form_dataset.addItem('-')
        for root, subdirs, _ in os.walk(self.options.geog_dir):
            for subdir in subdirs:
                path = os.path.join(root, subdir)
                try:
                    rel_path = os.path.relpath(path, self.options.geog_dir)
                except ValueError:
                    if platform.system() == 'Windows':
                        # Can happen with folder names that are reserved on Windows, like "con".
                        # Those are silently ignored.
                        pass
                    else:
                        # Should never happen, but who knows.
                        raise
                else:
                    self.geog_dataset_form_dataset.addItem(rel_path, path)

        var_names = sorted(self.geogrid_tbl.variables.keys())
        self.geog_dataset_form_variable.clear()
        self.geog_dataset_form_variable.addItem('-')
        for var_name in var_names:
            self.geog_dataset_form_variable.addItem(var_name, var_name)

        # interpolation method dropdown is populated in change callback of variable dropdown
        self.geog_dataset_form_interp.clear()
        self.geog_dataset_form_interp.addItem('-')

    def geog_dataset_form_variable_changed(self, index: int):
        var_name = self.geog_dataset_form_variable.currentData()
        if var_name is None:
            return

        interp_options = set(
            group_options[GeogridTblKeys.INTERP_OPTION] for group_options in
            self.geogrid_tbl.variables[var_name].group_options.values())

        self.geog_dataset_form_interp.clear()
        self.geog_dataset_form_interp.addItem('-')
        for interp in sorted(interp_options):
            self.geog_dataset_form_interp.addItem(interp, interp)

    def on_add_geog_dataset_form_button_clicked(self):
        dataset_path = self.geog_dataset_form_dataset.currentData()
        var_name = self.geog_dataset_form_variable.currentData()
        existing_interp = self.geog_dataset_form_interp.currentData()
        group_name = self.geog_dataset_form_group_name
        custom_interp = self.geog_dataset_form_custom_interp

        msg_bar = self.iface.messageBar()  # type: QgsMessageBar

        if not dataset_path:
            msg_bar.pushCritical(PLUGIN_NAME, 'Dataset not selected.')
            return

        if not var_name:
            msg_bar.pushCritical(PLUGIN_NAME, 'Variable not selected.')
            return

        if not group_name.is_valid():
            msg_bar.pushCritical(PLUGIN_NAME, 'Invalid group name.')
            return

        if not custom_interp.is_valid():
            msg_bar.pushCritical(PLUGIN_NAME, 'Invalid custom interpolation.')
            return

        if not xor(bool(custom_interp.value()), bool(existing_interp)):
            msg_bar.pushCritical(
                PLUGIN_NAME,
                'Must have either interpolation or custom interpolation.')
            return

        interp = custom_interp.value()
        if not interp:
            interp = existing_interp

        if var_name == 'LANDUSEF':
            landmask_water = read_wps_binary_index_file(
                dataset_path).landmask_water
        else:
            landmask_water = None

        self.geogrid_tbl.add(group_name.value(), var_name, dataset_path,
                             self.options.geog_dir, interp, landmask_water)
        self.project.write_geogrid_tbl(self.geogrid_tbl)

        self.populate_geog_data_tree()

        self.add_geog_dataset_button.show()
        self.geog_dataset_form.hide()

    def on_cancel_geog_dataset_form_button_clicked(self) -> None:
        self.add_geog_dataset_button.show()
        self.geog_dataset_form.hide()

    #endregion Geographical Data

    #region Meteorological Data

    def create_meteorological_data_box(self) -> None:
        self.gbox_metdata = QGroupBox('Meteorological Data')
        vbox_metdata = QVBoxLayout()
        self.gbox_metdata.setLayout(vbox_metdata)

        gbox_avail_metdata = QGroupBox('Available Datasets')
        vbox_metdata.addWidget(gbox_avail_metdata)

        vbox_avail_metdata = QVBoxLayout()
        gbox_avail_metdata.setLayout(vbox_avail_metdata)

        self.tree_met_data = QTreeWidget()
        self.tree_met_data.setMinimumHeight(200)
        vbox_avail_metdata.addWidget(self.tree_met_data)
        self.tree_met_data.setHeaderItem(
            QTreeWidgetItem(['Product / Time Range']))
        self.tree_met_data.setSelectionMode(
            QAbstractItemView.ContiguousSelection)

        self.tree_met_data.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree_met_data.customContextMenuRequested.connect(
            self.on_tree_met_data_open_context_menu)

        gbox_datasets_spec = QGroupBox('Selection')
        vbox_metdata.addWidget(gbox_datasets_spec)

        vbox_datasets_spec = QVBoxLayout()
        gbox_datasets_spec.setLayout(vbox_datasets_spec)

        selection_button = QPushButton('Use Dataset Selection from List')
        selection_button.clicked.connect(
            self.on_met_data_selection_button_clicked)
        vbox_datasets_spec.addWidget(selection_button)

        custom_button = QPushButton('Use Custom Dataset')
        custom_button.clicked.connect(self.on_met_data_custom_button_clicked)
        vbox_datasets_spec.addWidget(custom_button)

        hbox_current_config = QHBoxLayout()
        vbox_datasets_spec.addLayout(hbox_current_config)
        hbox_current_config.addWidget(QLabel('Current Configuration: '))
        self.met_data_current_config_label = QLabel()
        hbox_current_config.addWidget(self.met_data_current_config_label)
        self.set_met_data_current_config_label()
        hbox_current_config.insertStretch(-1)

    def set_met_data_current_config_label(self) -> None:
        try:
            spec = self.project.met_dataset_spec
        except (KeyError, AttributeError):
            config = None
        else:
            time_range = [
                d.strftime('%Y-%m-%d %H:%M') for d in spec['time_range']
            ]
            dataset = spec['dataset']
            product = spec['product']
            if dataset:
                config = '{} / {}\n{} -\n{}'.format(spec['dataset'],
                                                    spec['product'],
                                                    *time_range)
            else:
                config = 'Custom dataset / {} GRIB files\n{} -\n{}'.format(
                    len(spec['paths']), *time_range)

        lbl = self.met_data_current_config_label
        if not config:
            lbl.setText('Undefined')
            lbl.setStyleSheet(self.undef_label_style)
        else:
            lbl.setText(config)
            lbl.setStyleSheet('')

    def on_met_data_selection_button_clicked(self) -> None:
        msg_bar = self.iface.messageBar()  # type: QgsMessageBar

        items = self.tree_met_data.selectedItems()

        if not items:
            msg_bar.pushInfo(
                PLUGIN_NAME,
                'Select a time range or a sequence of time steps in the dataset tree before clicking this button.'
            )
            return

        datas = [item.data(0, Qt.UserRole) for item in items]
        if any(data is None for data in datas):
            msg_bar.pushWarning(
                PLUGIN_NAME,
                'Do not select dataset or product entries, only time ranges.')
            return

        is_file = [os.path.isfile(data) for data in datas]
        is_dir = [not f for f in is_file]
        if not all(is_file) and not all(is_dir):
            msg_bar.pushWarning(
                PLUGIN_NAME,
                'Select either a time range or a sequence of time steps.')
            return

        if is_file[0]:
            if len(datas) == 1:
                msg_bar.pushWarning(PLUGIN_NAME,
                                    'Select at least two time steps.')
                return
            file_paths = datas
            time_range_folder = os.path.dirname(file_paths[0])
            meta_all, meta_files = read_grib_files_metadata(file_paths)
        else:
            assert len(datas) == 1
            time_range_folder = datas[0]
            meta_all, meta_files = read_grib_folder_metadata(time_range_folder)

        product_folder = os.path.dirname(time_range_folder)
        dataset_folder = os.path.dirname(product_folder)

        self.project.met_dataset_spec = {
            'paths': [meta.path for meta in meta_files],
            'dataset': os.path.basename(dataset_folder),
            'product': os.path.basename(product_folder),
            'time_range': meta_all.time_range,
            'interval_seconds': meta_all.interval_seconds
        }
        self.set_met_data_current_config_label()

    def on_met_data_custom_button_clicked(self) -> None:
        try:
            spec = self.project.met_dataset_spec
            if spec['dataset']:
                spec = None  # not a custom dataset
        except KeyError:
            # met data not configured yet
            spec = None

        dialog = CustomMetDatasetDialog(self.options.ungrib_vtable_dir, spec)
        if not dialog.exec_():
            return

        vtable_path = dialog.vtable_path
        # only store absolute path if not a standard WPS vtable
        if Path(vtable_path).parent == Path(self.options.ungrib_vtable_dir):
            vtable_path = Path(vtable_path).name

        self.project.met_dataset_spec = {
            'paths': dialog.paths,
            'base_folder': dialog.base_folder,
            'vtable': vtable_path,
            'time_range': [dialog.start_date, dialog.end_date],
            'interval_seconds': dialog.interval_seconds
        }
        self.set_met_data_current_config_label()

    def on_tree_met_data_open_context_menu(self, position) -> None:
        item = self.tree_met_data.currentItem()
        if item is None:
            return
        data = item.data(0, Qt.UserRole)
        if data is None or not os.path.isdir(data):
            return

        menu = QMenu()

        view_vars_action = menu.addAction('View Variables')
        view_vars_action.triggered.connect(
            self.on_tree_met_data_view_variables_clicked)

        menu.exec_(self.tree_met_data.viewport().mapToGlobal(position))

    def on_tree_met_data_view_variables_clicked(self) -> None:
        item = self.tree_met_data.currentItem()
        dataset_folder = item.data(0, Qt.UserRole)

        meta, _ = read_grib_folder_metadata(dataset_folder)

        title = 'Variables for {} ({})'.format(item.parent().text(0),
                                               item.text(0))
        text = '<table>'
        for var_name, var_label in sorted(meta.variables.items()):
            text += '<tr><td>{}</td><td>{}</td></tr>'.format(
                var_name, var_label)
        text += '</table>'
        QMessageBox.information(self.iface.mainWindow(), title, text)

    def populate_met_data_tree(self) -> None:
        tree = self.tree_met_data
        tree.clear()

        root_dir = self.options.met_dir
        # TODO is this the right place to check?
        if not os.path.exists(root_dir):
            return
        for dataset_name in os.listdir(root_dir):
            dataset_path = os.path.join(root_dir, dataset_name)
            if not os.path.isdir(dataset_path):
                continue

            long_name = met_datasets.get(dataset_name)
            if long_name:
                label = '{}: {}'.format(dataset_name, long_name)
            else:
                label = dataset_name

            dataset_item = QTreeWidgetItem(tree)
            dataset_item.setText(0, label)
            if long_name:
                dataset_item.setToolTip(0, 'Dataset: ' + long_name)
            dataset_item.setExpanded(True)

            for product_name in os.listdir(dataset_path):
                product_folder = os.path.join(dataset_path, product_name)
                if not os.path.isdir(product_folder):
                    continue

                product_item = QTreeWidgetItem(dataset_item)
                product_item.setText(0, product_name)
                product_item.setToolTip(0, 'Product: ' + product_name)
                product_item.setExpanded(True)

                for time_range_name in os.listdir(product_folder):
                    time_range_folder = os.path.join(product_folder,
                                                     time_range_name)
                    if not os.path.isdir(time_range_folder):
                        continue

                    folder_meta, file_metas = read_grib_folder_metadata(
                        time_range_folder)
                    if not file_metas:
                        continue

                    # TODO disable item and subitems if bbox does not fully cover the outer-most domain

                    time_range = '{} - {}'.format(
                        *map(lambda d: d.strftime('%Y-%m-%d %H:%M'),
                             folder_meta.time_range))

                    time_range_item = QTreeWidgetItem(product_item)
                    time_range_item.setText(0, time_range)
                    time_range_item.setToolTip(0, time_range_folder)
                    time_range_item.setData(0, Qt.UserRole, time_range_folder)

                    for file_meta in file_metas:
                        if file_meta.time_range[0] == file_meta.time_range[1]:
                            time = file_meta.time_range[0].strftime(
                                '%Y-%m-%d %H:%M')
                        else:
                            time = '{} - {}'.format(
                                *map(lambda d: d.strftime('%Y-%m-%d %H:%M'),
                                     file_meta.time_range))

                        file_item = QTreeWidgetItem(time_range_item)
                        file_item.setText(0, time)
                        file_item.setToolTip(0, file_meta.path)
                        file_item.setData(0, Qt.UserRole, file_meta.path)

    #endregion Meteorological Data

    undef_label_style = ("""
        QLabel {
            font-weight: bold;
            color: #ff0000;
        }
    """)
Example #36
0
class Window(QWidget):
    reminderevent = QtCore.pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.w = None
        self.title = 'Reminder'
        self.left = 500
        self.top = 200
        self.width_ = 600
        self.height = 800
        self.setStyleSheet("background-color: black;\n"
                           "padding:0px;\n"
                           "spacing:0px;\n")

        self.InitWindow()
        self.NoBoderStyleSheet = ("border :0px;\n")
        layout = self.LoadLayout()
        addReminder = QPushButton('Add')
        addReminder.setStyleSheet(
            "QPushButton{background-color: #52057b;\n color: White;\n border: 1px solid #52057b;\n border-radius:25px;\n padding:10px;\nspacing :10px; }"
            "QPushButton::hover{background-color : #31034a;}\n")
        addReminder.setMinimumHeight(50)
        addReminder.setFont(QFont('Open Sans', 15))
        addReminder.clicked.connect(self.showAddReminderWindow)
        layout.addWidget(addReminder)
        self.setLayout(layout)
        self.reminderevent.connect(self.showAlertWindow)
        t = threading.Thread(target=self.showAlert)
        t.deamon = True
        t.start()

    def mousePressEvent(self, event):
        if event.button() == 1:
            self.isMouseButtonDown = True
            self.oldPos = event.globalPos()

    def mouseReleaseEvent(self, event):
        self.isMouseButtonDown = False

    def mouseMoveEvent(self, event):
        if self.isMouseButtonDown == True:
            delta = (event.globalPos() - self.oldPos)
            #print(delta)
            self.move(self.x() + delta.x(), self.y() + delta.y())
            self.oldPos = event.globalPos()

    def showAlertWindow(self, amsg):
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Critical)
        msg.setText("Pending Task")
        msg.setInformativeText(amsg)
        msg.setWindowTitle("Task Alert")
        msg.exec_()

    def showAlert(self):
        file = FileUtil()
        today = datetime.today()
        time = datetime.now()
        systime = time.strftime("%H:%M")
        print(systime)
        sysdate = today.strftime("%d-%m-%Y")
        reminders, count = FileUtil.loadRemindersFromFile(file)
        for reminder in reminders:
            r = reminder.split(';')
            if r[1] == sysdate:
                if r[2] == systime:
                    self.reminderevent.emit(r[0])

        Time.sleep(60)

    def showAddReminderWindow(self, checked):
        if self.w is None:
            self.w = AddReminderWindow()

        #self.w.setWindowModality(Qt.WindowModal)
        self.w.show()
        self.w.Update.connect(self.UpdateReminders)
        #self.UpdateReminders()

    def Close(self):
        self.close()
        self.w.close()

    def DoneWithReminder(self, button):
        index = self.reminderContainer.indexOf(button.parent())
        print(index)
        file = FileUtil()
        FileUtil.deleteReminder(self, index)
        button.parent().deleteLater()

    def upperFrame(self):
        frame = QHBoxLayout()
        frame.setContentsMargins(20, 0, 0, 0)
        Title = QLabel(self.title)
        Title.setFont(QFont('Open Sans', 15))
        Title.setStyleSheet('color:white;\n')
        frame.addWidget(Title)
        frame.setSpacing(0)
        Close = QPushButton('')
        Close.setMinimumHeight(45)
        Close.setMinimumWidth(45)
        Close.setStyleSheet(
            "QPushButton{background-color: black;\n color: White;\n border: 1px solid black;\n border-radius:25px;\n padding:10px;\n image: url(X.png);\n}"
            "QPushButton::hover{background-color : #31034a;}\n")
        Close.clicked.connect(lambda: self.close())

        Minimize = QPushButton('')
        Minimize.setStyleSheet(
            "QPushButton{background-color: black;\n color: White;\n border: 1px solid black;\n border-radius:25px;\n padding:10px;\n image: url(Min.png);\n}"
            "QPushButton::hover{background-color : #31034a;}\n")
        Minimize.clicked.connect(lambda: self.showMinimized())

        Minimize.setMinimumHeight(45)
        Minimize.setMinimumWidth(45)
        frame.addStretch()
        frame.addWidget(Minimize)
        frame.addWidget(Close)
        tempBox = QGroupBox()
        tempBox.setMaximumHeight(45)
        tempBox.setStyleSheet("background-color: black;\n"
                              "padding:0px;\n" + self.NoBoderStyleSheet)
        tempBox.setLayout(frame)
        return tempBox

    def LoadLayout(self):
        ScrollBoxStyleSheet = ("""
        QScrollBar:vertical {              
        border: 0px solid #999999;
        background:white;
        width:5px;    
        margin: 0px 0px 0px 0px;
    }
    QScrollBar::handle:vertical {
        background-color: gray ;
        min-height: 0px;
        border-radius:2px;
    }
    QScrollBar::add-line:vertical {
        background-color: white;
        height: 0px;
        subcontrol-position: bottom;
        subcontrol-origin: margin;
    }
    QScrollBar::sub-line:vertical {
        background-color:white;
        height: 0 px;
        subcontrol-position: top;
        subcontrol-origin: margin;
    } 
    QScrollArea
       {
        border :0px;
       }""")
        root = QStackedWidget(self)
        reminders, count = FileUtil.loadRemindersFromFile(self)

        self.reminderContainer = QVBoxLayout()
        scroll = QScrollArea()
        scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        scroll.setWidgetResizable(True)
        scroll.setStyleSheet(ScrollBoxStyleSheet)
        reminderGroupBox = QGroupBox()
        self.reminderContainer.addStretch()
        reminderGroupBox.setLayout(self.reminderContainer)
        scroll.setWidget(reminderGroupBox)
        if (count != 0):
            for reminder in reminders:
                self.reminderContainer.addWidget(self.reminderUI(reminder))
        root.setStyleSheet("padding:0px;\n" + self.NoBoderStyleSheet)
        #root.addWidget(reminderGroupBox)
        #rootBox = QGroupBox()
        #rootBox.setLayout(scroll)
        root.addWidget(scroll)
        templayout = QGridLayout()
        templayout.setContentsMargins(0, 0, 0, 10)
        templayout.setSpacing(0)
        templayout.addWidget(self.upperFrame())
        templayout.addWidget(root)

        return templayout

    def UpdateReminders(self):
        while self.reminderContainer.count():
            child = self.reminderContainer.takeAt(0)
            if child.widget():
                child.widget().deleteLater()

        self.reminderContainer.addStretch()
        reminders, count = FileUtil.loadRemindersFromFile(self)
        for reminder in reminders:
            self.reminderContainer.addWidget(self.reminderUI(reminder))

    def reminderUI(self, reminder):
        reminderList = reminder.split(';', 4)
        reminderTitle = QLabel(reminderList[0])
        reminderDate = QLabel(reminderList[1])
        reminderStartTime = QLabel(reminderList[2])
        reminderEndTime = QLabel(reminderList[3])

        reminderTitle.setFont(QFont('Open Sans', 15))
        reminderDate.setFont(QFont('Open Sans', 15))
        reminderStartTime.setFont(QFont('Open Sans', 15))
        reminderEndTime.setFont(QFont('Open Sans', 15))

        reminderBox = QVBoxLayout()
        reminderBox.addWidget(reminderTitle)
        reminderBox.addWidget(reminderDate)
        reminderBox.addWidget(reminderStartTime)
        reminderBox.addWidget(reminderEndTime)
        reminderBox2 = QHBoxLayout()
        doneButton = QPushButton('Done')
        doneButton.setStyleSheet("background-color: White;\n"
                                 "border: 1px solid white;\n"
                                 "border-radius:25px;\n"
                                 "padding:10px;\n"
                                 "color: Black;\n")
        doneButton.setMinimumHeight(50)
        doneButton.setMaximumWidth(100)
        doneButton.clicked.connect(lambda: self.DoneWithReminder(doneButton))
        temp = QGroupBox()
        temp.setStyleSheet(self.NoBoderStyleSheet)
        temp.setLayout(reminderBox)
        reminderBox2.addWidget(temp)
        reminderBox2.addWidget(doneButton)
        temp2 = QGroupBox()
        temp2.setMaximumHeight(150)
        temp2.setStyleSheet('border-radius:25px;\n'
                            "background-color: #a40eda;\n"
                            "border: 1px solid #a40eda;\n"
                            "color: White;\n")
        temp2.setLayout(reminderBox2)
        return temp2

    def InitWindow(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width_, self.height)
        flags = QtCore.Qt.WindowFlags(QtCore.Qt.FramelessWindowHint)
        self.setWindowFlags(flags)
        self.show()
class MainView(QWidget):
    """
    Represents the main content of the application (containing the source list
    and main context view).
    """

    CSS = '''
    #view_holder {
        background-color: #fff;
    }
    '''

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

        self.setStyleSheet(self.CSS)

        self.layout = QHBoxLayout(self)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(0)
        self.setLayout(self.layout)

        self.source_list = SourceList()
        self.source_list.itemSelectionChanged.connect(self.on_source_changed)

        self.view_layout = QVBoxLayout()
        self.view_layout.setContentsMargins(0, 0, 0, 0)
        self.view_holder = QWidget()
        self.view_holder.setObjectName('view_holder')  # Set css id
        self.view_holder.setLayout(self.view_layout)

        self.layout.addWidget(self.source_list)
        self.layout.addWidget(self.view_holder)

    def setup(self, controller):
        """
        Pass through the controller object to this widget.
        """
        self.controller = controller
        self.source_list.setup(controller)

    def show_sources(self, sources: List[Source]):
        """
        Update the left hand sources list in the UI with the passed in list of
        sources.
        """
        self.source_list.update(sources)

    def on_source_changed(self):
        """
        Show conversation for the currently-selected source if it hasn't been deleted. If the
        current source no longer exists, clear the conversation for that source.
        """
        source = self.source_list.get_current_source()

        if source:
            conversation_wrapper = SourceConversationWrapper(
                source, self.controller)
            self.set_conversation(conversation_wrapper)
        else:
            self.clear_conversation()

    def set_conversation(self, widget):
        """
        Update the view holder to contain the referenced widget.
        """
        old_widget = self.view_layout.takeAt(0)

        if old_widget:
            old_widget.widget().hide()

        self.view_layout.addWidget(widget)
        widget.show()

    def clear_conversation(self):
        while self.view_layout.count():
            child = self.view_layout.takeAt(0)
            if child.widget():
                child.widget().deleteLater()