Example #1
0
    def __init__(self, app_context):
        super(AboutDialog, self).__init__()

        ok_btn = QDialogButtonBox.Ok
        self.button_box = QDialogButtonBox(ok_btn)
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)

        layout = QVBoxLayout()

        title = QLabel("Melbourne")
        font = title.font()
        font.setPointSize(20)
        font.setBold(True)
        title.setFont(font)

        logo = QLabel()
        logo.setPixmap(QPixmap(app_context.get_resource("logo_128.png")))

        layout.addWidget(title)
        layout.addWidget(logo)
        layout.addWidget(QLabel("Version 5.0.1"))
        layout.addWidget(QLabel("Copyright © Aditya Duri"))

        for i in range(0, layout.count()):
            layout.itemAt(i).setAlignment(Qt.AlignHCenter)

        layout.addWidget(self.button_box)
        self.setLayout(layout)
Example #2
0
    def __init__(self):
        super().__init__()

        QBtn = QDialogButtonBox.Ok  # No cancel
        self.buttonBox = QDialogButtonBox(QBtn)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)

        layout = QVBoxLayout()

        title = QLabel("Mozzarella Ashbadger")
        font = title.font()
        font.setPointSize(20)
        title.setFont(font)

        layout.addWidget(title)

        logo = QLabel()
        logo.setPixmap(QPixmap(os.path.join("icons", "ma-icon-128.png")))
        layout.addWidget(logo)

        layout.addWidget(QLabel("Version 23.35.211.233232"))
        layout.addWidget(QLabel("Copyright 2015 Mozzarella Inc."))

        for i in range(0, layout.count()):
            layout.itemAt(i).setAlignment(Qt.AlignHCenter)

        layout.addWidget(self.buttonBox)

        self.setLayout(layout)
Example #3
0
    def __init__(self, *args, **kwargs):
        super(AboutDialog, self).__init__(*args, **kwargs)

        QBtn = QDialogButtonBox.Ok  # No cancel
        self.buttonBox = QDialogButtonBox(QBtn)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)

        layout = QVBoxLayout()

        title = QLabel("MooseAche")
        font = title.font()
        font.setPointSize(20)
        title.setFont(font)

        layout.addWidget(title)

        logo = QLabel()
        logo.setPixmap(QPixmap(os.path.join('images', 'ma-icon-128.png')))
        layout.addWidget(logo)

        layout.addWidget(QLabel("Version 23.35.211.233232"))
        layout.addWidget(QLabel("Copyright 2015 MooseAche Inc."))

        for i in range(0, layout.count()):
            layout.itemAt(i).setAlignment(Qt.AlignHCenter)

        layout.addWidget(self.buttonBox)

        self.setLayout(layout)
Example #4
0
class StockList(Updatable, QGroupBox):
    updateComplete = Signal(bool)  #emit this signal when an update is done.

    def __init__(self, kernel, parent):
        super(StockList, self).__init__()
        self.kernel = kernel
        self.addParent(parent)
        self.addUpdateFunction(self.updateStockWidgets)
        self.addUpdateFunction(self.update)
        self.kernel.getUpdateGraph().addUpdatable(self)
        self.initUI()

    def initUI(self):
        self.createElements()
        self.createLayout()
        self.createActions()

    def createElements(self):
        self.stockWidgets = []
        self.securities = set()
        for security in self.kernel.getCurrentUser().getSecuritiesOwned():
            self.securities.add(security)
            self.stockWidgets.append(Stock(security))

    def createLayout(self):
        self.layout = QVBoxLayout()
        for w in self.stockWidgets:
            self.layout.addWidget(w)
        self.setLayout(self.layout)

    def createActions(self):
        pass

    def updateStockWidgets(self):
        for security in self.kernel.getCurrentUser().getSecuritiesOwned():
            # remove old widgets
            for i in reversed(range(self.layout.count())):
                self.layout.itemAt(i).widget().setParent(None)

            # create new widgets
            if security not in self.securities:
                self.securities.add(security)
                newWidget = Stock(security)
                self.stockWidgets.append(newWidget)
                self.layout.addWidget(newWidget)

    def __str__(self):
        return "StockList widget"

    def runUpdates(self):
        updateStatus = super().runUpdates()
        self.updateComplete.emit(updateStatus)
Example #5
0
class MainWindow(QDialog):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.layout_ = QVBoxLayout()
        self.setLayout(self.layout_)
        # override Escape key behavior
        self.reject = self.reset
        self.entry = None

    def show_(self):
        self.update_title()
        self.adjustSize()
        self.show()

    def reset(self):
        self.clear()
        self.hide()

    def clear(self):
        for i in reversed(range(self.layout_.count())):
            self.layout_.takeAt(i).widget().deleteLater()

    def move_(self, x, y):
        try:
            self.move(x, y)
        except OverflowError:
            pass

    def show_entry(self, callback):
        def on_return():
            entry = self.entry.text()
            self.reset()
            callback(entry)

        self.entry = QLineEdit()
        self.entry.returnPressed.connect(on_return)
        self.layout_.addWidget(self.entry)
        self.entry.setFocus()

    def add_label(self, text, sunken=False, raised=False):
        label = QLabel(text, self)
        if sunken:
            label.setFrameStyle(QFrame.Panel | QFrame.Sunken)
        elif raised:
            label.setFrameStyle(QFrame.Panel | QFrame.Raised)
        label.setLineWidth(2)
        self.layout_.addWidget(label)

    def update_title(self):
        time_ = time.asctime(time.localtime())
        self.setWindowTitle(time_)
Example #6
0
class ButtonList(QGroupBox):
    def __init__(self):
        QGroupBox.__init__(self)
        self.createElements(5)
        self.createLayout()
        self.createActions()

    def createElements(self, numElements):
        self.numElements = numElements

        self.lineEdit = QLineEdit()
        self.add = QPushButton("Add another button")
        self.widgets = []
        for i in range(self.numElements):
            self.widgets.append(MyButton("button {}".format(i + 1)))

    def createLayout(self):
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)
        self.layout.addWidget(self.lineEdit)
        self.layout.addWidget(self.add)
        for i in range(self.numElements):
            self.layout.addWidget(self.widgets[i])

    def createActions(self):
        self.lineEdit.returnPressed.connect(self.update)
        self.add.clicked.connect(self.addButton)

    def addButton(self):
        self.widgets[0] = MyButton("New Button")
        #self.widgets.append(MyButton("New Button"))
        self.layout.addWidget(self.widgets[-1])
        #self.numElements += 1

    def update(self):
        print(self.lineEdit.text())
        n = int(self.lineEdit.text())
        for i in reversed(range(self.layout.count())):
            self.layout.itemAt(i).widget().setParent(None)
        self.createElements(n)
        self.layout.addWidget(self.lineEdit)
        self.layout.addWidget(self.add)
        for i in range(self.numElements):
            self.layout.addWidget(self.widgets[i])
        self.createActions()
Example #7
0
class ManagerWindow(MayaQWidgetDockableMixin, QWidget):
    add_spore_clicked = Signal(str)
    remove_spore_clicked = Signal()
    refresh_spore_clicked = Signal()
    close_event = Signal()

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

        self.setWindowTitle('Spore Manager')
        self.setGeometry(50, 50, 400, 550)
        #  self.setMinimumSize(350, 400)
        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)

        self.items = []  # list of all item widgets

        self.build_ui()
        self.connect_signals()

    def build_ui(self):
        layout = QGridLayout(self)
        layout.setContentsMargins(5, 5, 5, 5)
        self.setLayout(layout)

        self.name_edt = QLineEdit()
        self.name_edt.setPlaceholderText('Create New')
        self.name_edt.setSizePolicy(QSizePolicy.Preferred,
                                    QSizePolicy.Preferred)
        layout.addWidget(self.name_edt, 0, 0, 1, 1)

        self.add_btn = QPushButton()
        self.add_btn.setIcon(QIcon(QPixmap(':/teAdditive.png')))
        self.add_btn.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)
        layout.addWidget(self.add_btn, 0, 1, 1, 1)

        self.refresh_btn = QPushButton()
        self.refresh_btn.setIcon(QIcon(QPixmap(':/teKeyRefresh.png')))
        self.refresh_btn.setSizePolicy(QSizePolicy.Maximum,
                                       QSizePolicy.Preferred)
        layout.addWidget(self.refresh_btn, 0, 2, 1, 1)

        scroll_wdg = QWidget(self)
        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        scroll_area.setStyleSheet(
            "QScrollArea { background-color: rgb(57,57,57);}")
        scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        scroll_area.setWidget(scroll_wdg)

        self.spore_layout = QVBoxLayout()
        self.spore_layout.setContentsMargins(1, 1, 3, 1)
        self.spore_layout.setSpacing(0)
        self.spore_layout.addStretch()
        scroll_wdg.setLayout(self.spore_layout)
        layout.addWidget(scroll_area, 1, 0, 1, 3)

        #  self.frame_lay.addWidget(ItemWidget())
        #  layout.addWidget(btn, 0, 0, 1, 1)

    def connect_signals(self):
        self.name_edt.returnPressed.connect(
            lambda: self.add_spore_clicked.emit(self.name_edt.text()))
        self.add_btn.clicked.connect(
            lambda: self.add_spore_clicked.emit(self.name_edt.text()))
        #  self.remove_btn.clicked.connect(self.remove_spore_clicked.emit)
        self.refresh_btn.clicked.connect(self.refresh_spore_clicked.emit)

    def append_item(self, item):
        self.items.append(item)
        self.spore_layout.insertWidget(0, item)

    def remove_item(self, item):
        pass

    #  def clear_items(self):
    #      for item in self.items:
    #          self.spore_layout.removeWidget(item)
    #          self.items.remove(item)
    #          item.delateLater()
    #          del item
    #
    #      self.spore_layout.update()

    def clear_layout(self):
        """ remove all child widgets and layout """

        self.name_edt.setText('')

        del self.items[:]
        while self.spore_layout.count():
            child = self.spore_layout.takeAt(0)
            if child.widget() is not None:
                child.widget().deleteLater()
            #  elif child.layout() is not None:
            #      self.clear_layout(child.layout())

        self.spore_layout.setSpacing(0)
        self.spore_layout.addStretch()

    def closeEvent(self, event):
        self.close_event.emit()

    def hideEvent(self, event):
        self.close_event.emit()
Example #8
0
class QGroundObjectMenu(QDialog):
    def __init__(self, parent, ground_object: TheaterGroundObject,
                 buildings: [], cp: ControlPoint, game: Game):
        super(QGroundObjectMenu, self).__init__(parent)
        self.setMinimumWidth(350)
        self.ground_object = ground_object
        self.buildings = buildings
        self.cp = cp
        self.game = game
        self.setWindowTitle("Location " + self.ground_object.obj_name)
        self.setWindowIcon(EVENT_ICONS["capture"])
        self.intelBox = QGroupBox("Units :")
        self.buildingBox = QGroupBox("Buildings :")
        self.intelLayout = QGridLayout()
        self.buildingsLayout = QGridLayout()
        self.init_ui()

    def init_ui(self):

        self.mainLayout = QVBoxLayout()
        self.budget = QBudgetBox(self.game)
        self.budget.setGame(self.game)

        self.doLayout()

        if self.ground_object.dcs_identifier == "AA":
            self.mainLayout.addWidget(self.intelBox)
        else:
            self.mainLayout.addWidget(self.buildingBox)
        self.setLayout(self.mainLayout)

    def doLayout(self):
        self.intelBox = QGroupBox("Units :")
        self.intelLayout = QGridLayout()
        i = 0
        for g in self.ground_object.groups:
            if not hasattr(g, "units_losts"):
                g.units_losts = []
            for u in g.units:
                self.intelLayout.addWidget(
                    QLabel("<b>Unit #" + str(u.id) + " - " + str(u.type) +
                           "</b>"), i, 0)
                i = i + 1

            for u in g.units_losts:

                utype = unit_type_of(u)
                if utype in PRICES:
                    price = PRICES[utype]
                else:
                    price = 6

                self.intelLayout.addWidget(
                    QLabel("<b>Unit #" + str(u.id) + " - " + str(u.type) +
                           "</b> [DEAD]"), i, 0)
                if self.cp.captured:
                    repair = QPushButton("Repair [" + str(price) + "M]")
                    repair.setProperty("style", "btn-success")
                    repair.clicked.connect(
                        lambda u=u, g=g, p=price: self.repair_unit(g, u, p))
                    self.intelLayout.addWidget(repair, i, 1)
                i = i + 1

        self.buildingBox = QGroupBox("Buildings :")
        self.buildingsLayout = QGridLayout()
        j = 0
        for i, building in enumerate(self.buildings):
            if building.dcs_identifier not in FORTIFICATION_BUILDINGS:
                self.buildingsLayout.addWidget(
                    QBuildingInfo(building, self.ground_object), j / 3, j % 3)
                j = j + 1

        self.buildingBox.setLayout(self.buildingsLayout)
        self.intelBox.setLayout(self.intelLayout)

    def do_refresh_layout(self):
        try:
            for i in range(self.mainLayout.count()):
                self.mainLayout.removeItem(self.mainLayout.itemAt(i))
            self.doLayout()
            if len(self.ground_object.groups) > 0:
                self.mainLayout.addWidget(self.intelBox)
            else:
                self.mainLayout.addWidget(self.buildingBox)
        except Exception as e:
            print(e)

    def repair_unit(self, group, unit, price):
        if self.game.budget > price:
            self.game.budget -= price
            group.units_losts = [
                u for u in group.units_losts if u.id != unit.id
            ]
            group.units.append(unit)
            GameUpdateSignal.get_instance().updateGame(self.game)

            # Remove destroyed units in the vicinity
            destroyed_units = self.game.get_destroyed_units()
            for d in destroyed_units:
                p = Point(d["x"], d["z"])
                if p.distance_to_point(unit.position) < 15:
                    destroyed_units.remove(d)
                    logging.info("Removed destroyed units " + str(d))
            logging.info("Repaired unit : " + str(unit.id) + " " +
                         str(unit.type))

        self.do_refresh_layout()

    def closeEvent(self, closeEvent: QCloseEvent):
        GameUpdateSignal.get_instance().updateGame(self.game)
Example #9
0
class GroupEcephys(QGroupBox):
    def __init__(self, parent):
        """Groupbox for Ecephys module fields filling form."""
        super().__init__()
        self.setTitle('Ecephys')
        self.group_type = 'Ecephys'
        self.groups_list = []

        self.combo1 = CustomComboBox()
        self.combo1.addItem('-- Add group --')
        self.combo1.addItem('Device')
        self.combo1.addItem('ElectrodeGroup')
        self.combo1.addItem('ElectricalSeries')
        self.combo1.addItem('SpikeEventSeries')
        self.combo1.addItem('EventDetection')
        self.combo1.addItem('EventWaveform')
        self.combo1.addItem('LFP')
        self.combo1.addItem('FilteredEphys')
        self.combo1.addItem('FeatureExtraction')
        self.combo1.addItem('DecompositionSeries')
        self.combo1.setCurrentIndex(0)
        self.combo1.activated.connect(lambda: self.add_group('combo'))
        self.combo2 = CustomComboBox()
        self.combo2.addItem('-- Del group --')
        self.combo2.setCurrentIndex(0)
        self.combo2.activated.connect(lambda: self.del_group('combo'))

        self.vbox1 = QVBoxLayout()
        self.vbox1.addStretch()

        self.grid = QGridLayout()
        self.grid.setColumnStretch(5, 1)
        if parent.show_add_del:
            self.grid.addWidget(self.combo1, 1, 0, 1, 2)
            self.grid.addWidget(self.combo2, 1, 2, 1, 2)
        self.grid.addLayout(self.vbox1, 2, 0, 1, 6)
        self.setLayout(self.grid)

    def add_group(self, group, metadata=None):
        """Adds group form."""
        if metadata is not None:
            group.write_fields(metadata=metadata)
        group.form_name.textChanged.connect(self.refresh_del_combo)
        self.groups_list.append(group)
        nWidgetsVbox = self.vbox1.count()
        self.vbox1.insertWidget(nWidgetsVbox - 1,
                                group)  # insert before the stretch
        self.combo1.setCurrentIndex(0)
        self.combo2.addItem(group.form_name.text())
        self.refresh_children(metadata=metadata)

    def del_group(self, group_name):
        """Deletes group form by name."""
        if group_name == 'combo':
            group_name = str(self.combo2.currentText())
        if group_name != '-- Del group --':
            # Tests if any other group references this one
            if self.is_referenced(grp_unique_name=group_name):
                QMessageBox.warning(
                    self, "Cannot delete subgroup", group_name +
                    " is being referenced by another subgroup(s).\n"
                    "You should remove any references of " + group_name +
                    " before "
                    "deleting it!")
                self.combo2.setCurrentIndex(0)
            else:
                nWidgetsVbox = self.vbox1.count()
                for i in range(nWidgetsVbox):
                    if self.vbox1.itemAt(i) is not None:
                        if hasattr(self.vbox1.itemAt(i).widget(), 'form_name'):
                            if self.vbox1.itemAt(
                                    i).widget().form_name.text() == group_name:
                                self.groups_list.remove(
                                    self.vbox1.itemAt(
                                        i).widget())  # deletes list item
                                self.vbox1.itemAt(i).widget().setParent(
                                    None)  # deletes widget
                                self.combo2.removeItem(
                                    self.combo2.findText(group_name))
                                self.combo2.setCurrentIndex(0)
                                self.refresh_children()

    def is_referenced(self, grp_unique_name):
        """Tests if a group is being referenced any other groups. Returns boolean."""
        nWidgetsVbox = self.vbox1.count()
        for i in range(nWidgetsVbox):
            if self.vbox1.itemAt(i).widget() is not None:
                other_grp = self.vbox1.itemAt(i).widget()
                # check if this subgroup has any ComboBox referencing grp_unique_name
                for ch in other_grp.children():
                    if isinstance(ch, (CustomComboBox, QComboBox)):
                        if ch.currentText() == grp_unique_name:
                            return True
        return False

    def refresh_children(self, metadata=None):
        """Refreshes references with existing objects in child groups."""
        for child in self.groups_list:
            child.refresh_objects_references(metadata=metadata)

    def refresh_del_combo(self):
        """Refreshes del combobox with existing objects names in child groups."""
        self.combo2.clear()
        self.combo2.addItem('-- Del group --')
        for child in self.groups_list:
            self.combo2.addItem(child.form_name.text())
        self.refresh_children()

    def read_fields(self):
        """Reads fields and returns them structured in a dictionary."""
        error = None
        data = {}
        # group_type counts, if there are multiple groups of same type, they are saved in a list
        grp_types = [grp.group_type for grp in self.groups_list]
        grp_type_count = {
            value: len(list(freq))
            for value, freq in groupby(sorted(grp_types))
        }
        # initiate lists as values for groups keys with count > 1
        for k, v in grp_type_count.items():
            if v > 1 or k == 'Device' or k == 'ElectrodeGroup' or k == 'ElectricalSeries':
                data[k] = []
        # iterate over existing groups and copy their metadata
        for grp in self.groups_list:
            if grp_type_count[grp.group_type] > 1 or grp.group_type == 'Device' \
               or grp.group_type == 'ElectrodeGroup' or grp.group_type == 'ElectricalSeries':
                data[grp.group_type].append(grp.read_fields())
            else:
                data[grp.group_type] = grp.read_fields()
        return data, error
Example #10
0
class _ExecuteTab(QTabWidget):
    """Tab used to execute modules or shell commands on the selected bot."""

    def __init__(self, responses_tab, model):
        """
        :type responses_tab: _ResponsesTab
        """
        super(_ExecuteTab, self).__init__()

        self._model = model
        self._current_layout = None
        self._current_bot = None

        self._layout = QGridLayout()
        self._sub_layout = QVBoxLayout()
        self._module_view = ModuleView(responses_tab)

        self._layout.setAlignment(Qt.AlignTop)
        self.setLayout(self._layout)
        self.set_empty_layout()

    def set_current_bot(self, bot):
        """Sets the connected bot this tab will interact with.

        :type bot: Bot
        """
        self._current_bot = bot

    def _clear_layout(self):
        while self._layout.count():
            child = self._layout.takeAt(0)

            if child.widget():
                child.widget().deleteLater()
        while self._sub_layout.count():
            child = self._sub_layout.takeAt(0)

            if child.widget():
                child.widget().deleteLater()

    def set_empty_layout(self):
        """Default layout shown when the user has not yet selected a row."""
        self._current_layout = "Empty"
        self._clear_layout()

        self._layout.addWidget(QLabel("Please select a bot in the table above."), 0, 0)

    def set_module_layout(self, module_name="screenshot"):
        """Sets the layout which can execute modules.

        :type module_name: str
        """
        self._current_layout = "Module"
        self._clear_layout()

        command_type_label = QLabel("Command type: ")
        command_type_combobox = QComboBox()

        command_type_combobox.addItem("Module")
        command_type_combobox.addItem("Shell")

        module_label = QLabel("Module name: ")
        module_combobox = QComboBox()

        for module_name in modules.get_names():
            module_combobox.addItem(module_name)

        module_combobox.currentTextChanged.connect(self._on_module_change)
        command_type_combobox.currentTextChanged.connect(self._on_command_type_change)

        self._layout.setColumnStretch(1, 1)
        self._layout.addWidget(command_type_label, 0, 0)
        self._layout.addWidget(command_type_combobox, 0, 1)
        self._layout.addWidget(module_label, 1, 0)
        self._layout.addWidget(module_combobox, 1, 1)

        # Module layout
        cached_module = modules.get_module(module_name)

        if not cached_module:
            cached_module = modules.load_module(module_name, self._module_view, self._model)

        input_fields = []

        for option_name in cached_module.get_setup_messages():
            input_field = QLineEdit()

            self._sub_layout.addWidget(QLabel(option_name))
            self._sub_layout.addWidget(input_field)
            input_fields.append(input_field)

        run_button = QPushButton("Run")
        run_button.setMaximumWidth(250)
        run_button.setMinimumHeight(25)

        run_button.pressed.connect(lambda: self._on_module_run(module_combobox.currentText(), input_fields))

        self._sub_layout.addWidget(QLabel(""))
        self._sub_layout.addWidget(run_button)
        self._sub_layout.setContentsMargins(0, 15, 0, 0)
        self._layout.addLayout(self._sub_layout, self._layout.rowCount() + 2, 0, 1, 2)

        self._on_module_change(module_combobox.currentText())

    def set_shell_layout(self):
        """Sets the layout which can execute shell commands."""
        self._current_layout = "Shell"
        self._clear_layout()

        command_type_label = QLabel("Command type: ")
        command_type_combobox = QComboBox()

        command_type_combobox.addItem("Shell")
        command_type_combobox.addItem("Module")

        command_label = QLabel("Command:")
        command_input = QLineEdit()

        run_button = QPushButton("Run")
        run_button.setMaximumWidth(250)
        run_button.setMinimumHeight(25)

        command_type_combobox.currentTextChanged.connect(self._on_command_type_change)
        run_button.pressed.connect(lambda: self._on_command_run(command_input))

        self._layout.addWidget(command_type_label, 0, 0)
        self._layout.addWidget(command_type_combobox, 0, 1)
        self._layout.addWidget(command_label, 1, 0)
        self._layout.addWidget(command_input, 1, 1)

        self._sub_layout.addWidget(QLabel(""))
        self._sub_layout.addWidget(run_button)
        self._sub_layout.setContentsMargins(0, 15, 0, 0)
        self._layout.addLayout(self._sub_layout, self._layout.rowCount() + 2, 0, 1, 2)

    def _on_command_type_change(self, text):
        """Handles the command type combobox change event.

        :type text: str
        """
        if text == "Module":
            self.set_module_layout()
        else:
            self.set_shell_layout()

    def _on_module_change(self, module_name):
        """Handles module combobox changes.

        :type module_name: str
        """
        while self._sub_layout.count():
            child = self._sub_layout.takeAt(0)

            if child.widget():
                child.widget().deleteLater()

        cached_module = modules.get_module(module_name)

        if not cached_module:
            cached_module = modules.load_module(module_name, self._module_view, self._model)

        input_fields = []

        for option_name in cached_module.get_setup_messages():
            input_field = QLineEdit()
            input_fields.append(input_field)

            self._sub_layout.addWidget(QLabel(option_name))
            self._sub_layout.addWidget(input_field)

        run_button = QPushButton("Run")
        run_button.setMaximumWidth(250)
        run_button.setMinimumHeight(25)

        run_button.pressed.connect(lambda: self._on_module_run(module_name, input_fields))

        self._sub_layout.addWidget(QLabel(""))
        self._sub_layout.addWidget(run_button)
        self._sub_layout.setContentsMargins(0, 15, 0, 0)

    def display_info(self, text):
        """
        :type text: str
        """
        message_box = QMessageBox()

        message_box.setIcon(QMessageBox.Information)
        message_box.setWindowTitle("Information")
        message_box.setText(text)
        message_box.setStandardButtons(QMessageBox.Ok)
        message_box.exec_()

    def _on_module_run(self, module_name, input_fields):
        """Handles running modules.

        :type module_name: str
        :type input_fields: list
        """
        set_options = []

        for input_field in input_fields:
            set_options.append(input_field.text())

        module = modules.get_module(module_name)

        if not module:
            module = modules.load_module(module_name, self._module_view, self._model)

        successful, options = module.setup(set_options)

        if successful:
            if module_name == "remove_bot":
                code = loaders.get_remove_code(self._current_bot.loader_name)
            elif module_name == "update_bot":
                code = loaders.get_update_code(self._current_bot.loader_name)
            else:
                code = modules.get_code(module_name)

            if not options:
                options = {}

            options["module_name"] = module_name

            self._model.add_command(self._current_bot.uid, Command(
                CommandType.MODULE, code, options
            ))

            self.display_info("Module added to the queue of:\n {}@{}".format(
                self._current_bot.username, self._current_bot.hostname)
            )

    def _on_command_run(self, command_input):
        """Handles running commands.

        :type command_input: QLineEdit
        """
        if command_input.text().strip() == "":
            return

        self._model.add_command(self._current_bot.uid, Command(CommandType.SHELL, command_input.text().encode()))

        command_input.clear()
        self.display_info("Command added to the queue of:\n {}@{}".format(
            self._current_bot.username, self._current_bot.hostname
        ))
Example #11
0
class NodeChoiceWidget(QWidget):
    def __init__(self, flow, nodes):
        super(NodeChoiceWidget, self).__init__()

        self.flow = flow

        self.all_nodes = sort_nodes(nodes)  # copy, no ref

        self.nodes = []

        # 'current_nodes' are the currently selectable ones, they get recreated every time update_view() is called
        self.current_nodes = []
        self.all_current_node_widgets = []
        self.active_node_widget_index = -1
        self.active_node_widget = None

        self.reset_list()

        self.node_widget_index_counter = 0

        self.setMinimumWidth(260)
        self.setFixedHeight(450)

        self.main_layout = QVBoxLayout(self)
        self.main_layout.setAlignment(Qt.AlignTop)
        self.setLayout(self.main_layout)

        # adding all stuff to the layout
        self.search_line_edit = QLineEdit(self)
        self.search_line_edit.setPlaceholderText('search for node...')
        self.search_line_edit.textChanged.connect(self.update_view)
        self.layout().addWidget(self.search_line_edit)

        self.list_scroll_area = QScrollArea(self)
        self.list_scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.list_scroll_area.setHorizontalScrollBarPolicy(
            Qt.ScrollBarAsNeeded)
        self.list_scroll_area.setWidgetResizable(True)

        self.list_scroll_area_widget = QWidget()
        self.list_scroll_area.setWidget(self.list_scroll_area_widget)

        self.list_layout = QVBoxLayout()
        self.list_layout.setAlignment(Qt.AlignTop)
        self.list_scroll_area_widget.setLayout(self.list_layout)

        self.layout().addWidget(self.list_scroll_area)

        self.setFixedHeight(400)

        self.update_view('')

        try:
            f = open('resources/stylesheets/dark_node_choice_widget.txt')
            self.setStyleSheet(f.read())
            f.close()
        except FileNotFoundError:
            pass

        self.search_line_edit.setFocus()

    def mousePressEvent(self, event):
        QWidget.mousePressEvent(self, event)
        event.accept()

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            self.flow.hide_node_choice_widget()

        if event.key() == Qt.Key_Down:
            index = self.active_node_widget_index+1 if \
                self.active_node_widget_index+1 < len(self.all_current_node_widgets) else 0
            self.set_active_node_widget_index(index)
        if event.key() == Qt.Key_Up:
            index = self.active_node_widget_index-1 if \
                self.active_node_widget_index-1 > -1 else len(self.all_current_node_widgets)-1
            self.set_active_node_widget_index(index)

        if event.key() == Qt.Key_Return:
            if len(self.all_current_node_widgets) > 0:
                self.place_node(self.active_node_widget_index)

    def wheelEvent(self, event):
        QWidget.wheelEvent(self, event)
        event.accept()

    def refocus(self):
        self.search_line_edit.setFocus()
        self.search_line_edit.selectAll()

    def update_list(self, nodes):
        self.nodes = sort_nodes(nodes)

    def reset_list(self):
        self.nodes = self.all_nodes

    def update_view(self, text=''):
        text = text.lower()
        for i in reversed(range(self.list_layout.count())):
            self.list_layout.itemAt(i).widget().setParent(None)

        self.current_nodes.clear()
        self.all_current_node_widgets.clear()

        self.node_widget_index_counter = 0

        # select valid elements from text
        # nodes
        nodes_names_dict = {}
        for n in self.nodes:
            nodes_names_dict[n] = n.title.lower()
        sorted_indices = self.get_sorted_dict_matching_search(
            nodes_names_dict, text)
        for n, index in sorted_indices.items():
            self.current_nodes.append(n)

        # nodes
        if len(self.current_nodes) > 0:
            nodes_label = QLabel('Hover for description')
            nodes_label_font = QFont('Poppins')
            nodes_label_font.setPixelSize(15)
            nodes_label.setStyleSheet('color: #9bbf9dff; border: none;')
            nodes_label.setFont(nodes_label_font)
            self.list_layout.addWidget(nodes_label)

            for n in self.current_nodes:
                node_widget = self.create_list_item_widget(n)
                self.list_layout.addWidget(node_widget)
                self.all_current_node_widgets.append(node_widget)

        if len(self.all_current_node_widgets) > 0:
            self.set_active_node_widget_index(0)

        # self.setFixedWidth(self.minimumWidth())
        # print(self.list_scroll_area_widget.width())

    def get_sorted_dict_matching_search(self, items_dict, text):
        indices_dict = {}
        for item, name in items_dict.items(
        ):  # the strings are already lowered here
            Debugger.debug(item, name, text)
            if name.__contains__(text):
                index = name.index(text)
                indices_dict[item] = index
        return {
            k: v
            for k, v in sorted(indices_dict.items(), key=lambda i: i[1])
        }

    def create_list_item_widget(self, node):
        node_widget = NodeWidget(self, node)  # , self.node_images[node])
        node_widget.custom_focused_from_inside.connect(
            self.node_widget_focused_from_inside)
        node_widget.setObjectName('node_widget_' +
                                  str(self.node_widget_index_counter))
        self.node_widget_index_counter += 1
        node_widget.chosen.connect(self.node_widget_chosen)

        return node_widget

    def node_widget_focused_from_inside(self):
        self.set_active_node_widget_index(
            self.all_current_node_widgets.index(self.sender()))

    def set_active_node_widget_index(self, index):
        self.active_node_widget_index = index
        node_widget = self.all_current_node_widgets[index]

        if self.active_node_widget:
            self.active_node_widget.set_custom_focus(False)

        node_widget.set_custom_focus(True)
        self.active_node_widget = node_widget
        self.list_scroll_area.ensureWidgetVisible(self.active_node_widget)

    def node_widget_chosen(
        self
    ):  # gets called when the user clicks on a node widget with the mouse
        self.flow.ignore_mouse_event = True  # otherwise, it will receive a mouse press event

        index = int(
            self.sender().objectName()[self.sender().objectName().rindex('_') +
                                       1:])
        self.place_node(index)

    def place_node(self, index):
        node_index = index
        node = self.current_nodes[node_index]
        self.flow.place_node__cmd(node)

        self.flow.hide_node_choice_widget()
Example #12
0
class PropertyEditor(MayaQWidgetDockableMixin, QWidget):
    """
    This class represents the property editor which displays the selected render setup item's property information.


    Note: The Qt should never called any 'deferred' actions because all the design is based on synchronous notifications
          and any asynchronous events will change the order of execution of these events.
    
          For example when the selection in the Render Setup Window is changed (so the Property Editor must be updated). 
          The delete must be synchronous on the 'unselected' layouts otherwise they will be updated along with selected ones. 
          The two main side effects are that lot of unnecessary processings are triggered (those one the deleted layouts) 
          and the infamous 'C++ already deleted' issue appears because the Data Model & Qt Model objects were deleted 
          but not their corresponding Layout (instance used by the Property Editor to display a render setup object).    
    """

    width = cmds.optionVar(query='workspacesWidePanelInitialWidth')
    PREFERRED_SIZE = QSize(width, 600)
    MINIMUM_SIZE = QSize((width * 0.75), 0)

    def __init__(self, treeView, parent):
        super(PropertyEditor, self).__init__(parent=parent)
        self.needsRebuild = None
        self.itemsToRepopulate = None
        self.rebuildInProgress = None
        self.preferredSize = self.PREFERRED_SIZE
                
        self.treeView = weakref.ref(treeView)
        self.model = weakref.ref(treeView.model())

        self.setWindowTitle(maya.stringTable['y_main.kPropertyEditor' ])
        
        self.scrollArea = PropertyEditorScrollArea(self)
        self.scrollAreaLayout = QVBoxLayout(self)
        self.scrollArea.setLayout(self.scrollAreaLayout)
        self.scrollWidget = QWidget(self)
        self.scrollArea.setWidget(self.scrollWidget)         
        self.scrollArea.setWidgetResizable(True)
        self.scrollWidgetLayout = QVBoxLayout(self)
        self.scrollWidget.setLayout(self.scrollWidgetLayout)

        layout = QVBoxLayout(self)
        layout.setContentsMargins(0,0,0,0)
        layout.addWidget(self.scrollArea, 0)
        self.setLayout(layout)

        self.frameLayouts = []
        
        self.setAcceptDrops(True)
        
        self._registered = False
        self._register()

        renderSetupModel.addObserver(self)

    def __del__(self):
        self._unregister()
        
    def _register(self):
        if not self._registered:
            self.model().itemChanged.connect(self.itemChanged)
            self.rebuildInProgress = False
            self.itemsToRepopulate = [] # List of items waiting to be repopulated
            self.needsRebuild = False

            selectionModel = self.treeView().selectionModel()
            selectionModel.selectionChanged.connect(self.selectionChanged)
            
            self._registered = True
        
    def _unregister(self):
        if self._registered:
            self.model().itemChanged.disconnect()
            self.rebuildInProgress = False
            self.itemsToRepopulate = [] # List of items waiting to be repopulated
            self.needsRebuild = False

            selectionModel = self.treeView().selectionModel()

            # The following more obvious implementation:
            #
            # selectionModel.selectionChanged.disconnect(self.selectionChanged)
            #
            # raises
            #
            # // Error: line 0: RuntimeError: file renderSetup\views\propertyEditor\main.py line 103: Failed to disconnect signal selectionChanged(QItemSelection,QItemSelection). //
            #
            # which comes from PySide2's CPython implementation, in file
            # pysidesignal.cpp, function signalInstanceDisconnect().  The
            # argument slot is not recognized, and the function fails.
            # Use old-style disconnection as a workaround.

            selectionModel.disconnect(
                QtCore.SIGNAL(
                    'selectionChanged(QItemSelection,QItemSelection)'),
                self, QtCore.SLOT(
                    'selectionChanged(QItemSelection,QItemSelection)'))
            
            self._registered = False

    def setSizeHint(self, size):
        self.preferredSize = size

    def sizeHint(self):
        return self.preferredSize

    def minimumSizeHint(self):
        return self.MINIMUM_SIZE

    def aboutToDelete(self):
        """Cleanup method to be called immediately before the object is deleted."""
        self._unregister()
        self._clearWidgets()
        # Qt object can take a long time before actually being destroyed
        # => observation of renderSetupModel may remain active (since self is not dead)
        # => explicitly remove observer to avoid receiving unwanted calls
        renderSetupModel.removeObserver(self)

    # Obsolete interface.
    dispose = aboutToDelete

    def renderSetupAdded(self):
        """ RenderSetup node was created """
        self._register()

    def renderSetupPreDelete(self):
        """ RenderSetup node is about to be deleted """
        # Flush the current content to avoid intermediate refreshes
        self._unregister()
        self._clearWidgets()

    def _clearWidgets(self):
        """ Clears the property editor widgets """
        while self.scrollWidgetLayout.count() > 0:
            layoutItem = self.scrollWidgetLayout.takeAt(0)
            # Note: Not obvious but to enforce the layoutItem delete, the parent should be None. 
            #  I would have expected that the takeAt() call did it by default 
            #  as the layoutItem is not anymore in the layout.
            if isinstance(layoutItem, QWidgetItem):
                layoutItem.widget().setParent(None)
            del layoutItem

        self.frameLayouts = []

    def _addItemEditor(self, propertyEditorItem):
        """
        Adds a property editor item type to the end of the layout, also
        keeps track of the control and frameLayout in case there is a data
        change.
        """
        frameLayout = FrameLayout(propertyEditorItem.item(), self)
        self.scrollWidgetLayout.addWidget(frameLayout)
        frameLayout.addWidget(propertyEditorItem)
        self.frameLayouts.append(frameLayout)
        return propertyEditorItem
        
    def _addLayer(self, currentItem):
        """ Adds a property editor layer to the end of the layout. """
        self._addItemEditor(RenderLayer(currentItem, self))

    def _addCollection(self, currentItem):
        """ Adds a property editor collection to the end of the layout. """
        collection = self._addItemEditor(
            collectionFactory.create(currentItem, self))

    def _addOverride(self, currentItem):
        """ Adds a property editor override to the end of the layout. """
        override = self._addItemEditor(Override(currentItem, self))

    @Slot(QStandardItem)
    def itemChanged(self, item):
        """
        When an item in the model changes, update the control and 
        frameLayout that make use of that item (if one exists).
        """
        if not item.isModelDirty():
            # itemChanged was not triggered due to a change to the model.
            # Nothing to do.
            # This is a workaround (see views/proxy/renderSetup.py (modelChanged() callback))
            return
        if item.data(renderSetupRoles.NODE_REBUILD_UI_WHEN_CHANGED):
            self.triggerRebuild()
        else:
            self.triggerRepopulate(item)

    def _getSortedSelectedIndexes(self):
        """ Unfortunately the selected items that are given to us from Qt are not sorted, we need to do this ourselves. """
        selectionModel = self.treeView().selectionModel()
        selectedIndexes = selectionModel.selectedIndexes()
        rootIndex = self.treeView().rootIndex()
        indexStack = []
        indexStack.append(rootIndex)

        # Pre-order traversal of our tree in order to get the preOrderIndex for each item in the tree.
        # Then a sort is applied by preOrderIndex on the selected items to get the sorted selected indexes.
        count = 0
        while(len(indexStack) > 0):
            index = indexStack.pop()
            if index != self.treeView().rootIndex():
                item = self.model().itemFromIndex(index)
                item.preOrderIndex = count
                count = count + 1
            if index is not None and (index.isValid() or index == self.treeView().rootIndex()):
                numRows = self.model().rowCount(index)
                for i in range(numRows):
                    indexStack.append(self.model().index(numRows - i - 1, 0, index))

        sortedSelectedIndices = []
        for i in range(len(selectedIndexes)):
            item = self.model().itemFromIndex(selectedIndexes[i])
            sortedSelectedIndices.append((selectedIndexes[i], item.preOrderIndex))
        sortedSelectedIndices = sorted(sortedSelectedIndices, key=lambda element: element[1]) # Sort by preOrderIndex
        return sortedSelectedIndices

    @Slot(QItemSelection, QItemSelection)
    def selectionChanged(self, selected, deselected):
        """
        On selection changed we lazily regenerate our collection/override/layer 
        controls.
        """
        self.triggerRebuild()

    def triggerRebuild(self):
        self.needsRebuild = True
        if len(self.itemsToRepopulate) == 0 and not self.rebuildInProgress:
            self.rebuildInProgress = True
            QTimer.singleShot(0, lambda: self.rebuild())

    def rebuild(self):
        """ regenerate our collection/override/layer controls. """
        if not self.needsRebuild:
            # early out if we no longer need to rebuild
            # this can happen because rebuild is asynchronous
            return
        self.scrollArea.setVisible(False)
        self._clearWidgets()
        indexes = self._getSortedSelectedIndexes()
        
        creators = {    renderSetup.RENDER_LAYER_TYPE            : self._addLayer,
                        renderSetup.COLLECTION_TYPE              : self._addCollection,
                        renderSetup.RENDER_SETTINGS_TYPE         : self._addCollection,
                        renderSetup.LIGHTS_TYPE                  : self._addCollection,
                        renderSetup.AOVS_TYPE                    : self._addCollection,
                        renderSetup.AOVS_CHILD_COLLECTION_TYPE   : self._addCollection,
                        renderSetup.LIGHTS_CHILD_COLLECTION_TYPE : self._addCollection,
                        renderSetup.RENDER_OVERRIDE_TYPE         : self._addOverride }
        
        for i in range(0, len(indexes)):
            currentIndex = QPersistentModelIndex(indexes[i][0])
            currentItem = self.model().itemFromIndex(currentIndex)
            creators[currentItem.type()](currentItem)

        self.scrollWidgetLayout.addStretch(1)
        self.rebuildInProgress = False
        self.needsRebuild = False
        self.itemsToRepopulate = []
        self.scrollArea.setVisible(True)

    def triggerRepopulate(self, item):
        if not self.rebuildInProgress and not item in self.itemsToRepopulate:
            self.itemsToRepopulate.append(item)
            QTimer.singleShot(0, partial(self.populateFields, item=item))

    def populateFields(self, item):
        # If we need a rebuild while a populateFields request is made, the rebuild is the priority, so rebuild and return.
        if self.needsRebuild:
            return self.rebuild()
        # If another populateFields caused a rebuild then the item will no longer be in the list so return there is no work to do.
        elif not item in self.itemsToRepopulate:
            return

        PropertyEditor.updaters = \
            { renderSetup.RENDER_LAYER_TYPE            : self._updateItem,
              renderSetup.COLLECTION_TYPE              : self._updateCollection,
              renderSetup.RENDER_SETTINGS_TYPE         : self._updateItem,
              renderSetup.LIGHTS_TYPE                  : self._updateItem,
              renderSetup.AOVS_TYPE                    : self._updateItem,
              renderSetup.AOVS_CHILD_COLLECTION_TYPE   : self._updateItem,
              renderSetup.LIGHTS_CHILD_COLLECTION_TYPE : self._updateItem,
              renderSetup.RENDER_OVERRIDE_TYPE         : self._updateItem }
        
        PropertyEditor.updaters[item.type()](item)
        self.itemsToRepopulate.remove(item)

    def _updateItem(self, item):
        for frameLayout in self.frameLayouts:
            if frameLayout.item() is item:
                frameLayout.update()

    def _updateCollection(self, item):
        for frameLayout in self.frameLayouts:
            if frameLayout.item() is item:
                frameLayout.getWidget(0).populateFields()
        self._updateItem(item)
        
    def highlight(self, names):
        if not isinstance(names, set):
            names = set(names)
        def doHighlight():
            collections = (frameLayout.getWidget(0) for frameLayout in self.frameLayouts \
                if frameLayout.item().type() == renderSetup.COLLECTION_TYPE)
            for col in collections:
                col.highlight(names)
        # triggerRepopulate is delayed => highlight must also be delayed to apply only
        # when repopulate is complete
        QTimer.singleShot(0, doHighlight)
class MainWidget(Widget):
    """
    Klasa główna wyświetlająca wszystkie niezbędne elementy w głównym oknie programu.
    """
    def __init__(self, user):
        super(MainWidget, self).__init__(user)
        self.que = queue.Queue()
        self.page_number = 1
        self.sorted_by = None
        self.sorted_direction = None
        self._txt_change_perm = 'Zmiana hasła'
        self._txt_permission = 'Utwórz konto z uprawnieniami'
        self._txt_bad_data = 'Błędne dane'
        self.page = 0
        self.lbl_page = QLabel()

        # Layouty
        self.layout = QVBoxLayout()
        self.menu_layout = QHBoxLayout()
        self.text_layout = QVBoxLayout()

        # Widgety
        self.btn_home = QPushButton('Strona główna')
        self.btn_my_book = QPushButton('Wypożyczone książki')
        self.btn_reserved = QPushButton('Zarezerwowane książki')
        self.btn_library = QPushButton('Biblioteka')
        self.btn_profile = QPushButton('Profil')
        self.btn_permission = QPushButton(self._txt_permission)
        self.btn_change_passwd = QPushButton(self._txt_change_perm)
        self.btn_set_permission = QPushButton('Uprawnienia')
        self.btn_logout = QPushButton('Wylogowanie')
        self.btn_add_book = QPushButton('Dodaj nową książkę')

        # Ustawienia widgetów
        self.menu_layout.setContentsMargins(-1, -1, -1, 25)
        self.btn_home.clicked.connect(self.on_home_clicked)
        if self.user.get('roleId') > 1:
            self.btn_my_book.clicked.connect(self.on_book_clicked_permission)
        else:
            self.btn_my_book.clicked.connect(self.on_book_clicked)
        self.btn_reserved.clicked.connect(self.on_reserved_clicked)
        self.btn_library.clicked.connect(self.on_library_clicked)
        self.btn_permission.clicked.connect(self.on_permission_clicked)
        if self.user.get('roleId') != 3:
            self.btn_change_passwd.clicked.connect(
                self.on_change_passwd_clicked)
            self.btn_profile.clicked.connect(self.on_profile_clicked)
        else:
            self.btn_profile.setText('Usuwanie profili')
            self.btn_change_passwd.clicked.connect(
                self.on_change_passwd_admin_clicked)
            self.btn_profile.clicked.connect(self.on_profile_admin_clicked)
        self.btn_set_permission.clicked.connect(self.on_set_permission_clicked)
        self.btn_logout.clicked.connect(self.on_logout_clicked)
        self.btn_next_page.clicked.connect(self.next_page)
        self.btn_back_page.clicked.connect(self.back_page)
        self.btn_add_book.clicked.connect(self.add_book)
        self.edit_search.returnPressed.connect(self.search)

        # Przypisanie widgetów do layoutów
        self.menu_layout.addWidget(self.btn_home)
        self.menu_layout.addWidget(self.btn_my_book)
        if self.user.get('roleId') == 1:
            self.menu_layout.addWidget(self.btn_reserved)
        self.menu_layout.addWidget(self.btn_library)
        self.menu_layout.addWidget(self.btn_profile)
        if self.user.get('roleId') == 3:
            self.menu_layout.addWidget(self.btn_permission)
            self.menu_layout.addWidget(self.btn_set_permission)
        self.menu_layout.addWidget(self.btn_change_passwd)
        self.menu_layout.addItem(self.h_spacer)
        self.menu_layout.addWidget(self.btn_logout)
        self.text_layout.addWidget(self.lbl_home)

        self.layout.addLayout(self.menu_layout)
        self.layout.addLayout(self.text_layout)

        self.setLayout(self.layout)

    def clear_layout(self):
        """
        Czyści layout głównego widżetu z pozostałych, niepotrzebnych  widgetów.
        """
        for i in reversed(range(self.text_layout.count())):
            self.text_layout.itemAt(i).widget().setParent(None)

    def on_home_clicked(self):
        """
        Wyświetla widgety strony głównej.
        """
        print("Home")
        self.clear_layout()
        self.text_layout.addWidget(self.lbl_home)

    def on_book_clicked(self, user_id=None):
        """
        Wyświetla wypożyczone książki danego użytkownika.
        :param user_id: int
        """
        print("My books")
        if user_id is None:
            self.clear_layout()
        self.tbl_result.clear()
        self.lbl_title.setText('Wypożyczone książki')
        self.text_layout.addWidget(self.lbl_title)
        if self.get_book_user_id(user_id):
            self.text_layout.addWidget(self.tbl_result)

    def on_reserved_clicked(self, user_id=None):
        """
        Wyświetla zarezerwowane książki danego użytkownika.
        """
        print("Reserved")
        self.clear_layout()
        self.tbl_result.clear()
        self.lbl_title.setText('Zarezerwowane książki')
        self.text_layout.addWidget(self.lbl_title)
        if self.get_reservation_user_id(user_id):
            self.text_layout.addWidget(self.tbl_result)

    def on_book_clicked_permission(self):
        """
        Dla konta z uprawnieniami, wyświetla tabelę użytkowników przed tabelą wypożyczonych książek.
        """
        print('Books permision')
        self.lbl_title.setText('Użytkownicy')
        self.tbl_result.clear()
        self.clear_layout()
        self.text_layout.addWidget(self.lbl_title)
        self.get_users()
        self.text_layout.addWidget(self.tbl_result)

    def next_page(self):
        """
        Wyświetla następną stronę książek w bibliotece.
        """
        self.page_number += 1
        self.lbl_page.setText('Strona {}/{}'.format(self.page_number,
                                                    self.page))
        if self.lbl_title.text() == 'Biblioteka':
            self.get_books(self.page_number,
                           search=self.edit_search.text(),
                           sort_by=self.sorted_by,
                           sort_direction=self.sorted_direction)

    def back_page(self):
        """
        Wyświetla poprzednią stronę książek w bibliotece.
        """
        self.page_number -= 1
        self.lbl_page.setText('Strona {}/{}'.format(self.page_number,
                                                    self.page))
        if self.lbl_title.text() == 'Biblioteka':
            self.get_books(self.page_number,
                           search=self.edit_search.text(),
                           sort_by=self.sorted_by,
                           sort_direction=self.sorted_direction)

    def search(self):
        """
        Wyszukuje odpowiednią frazę w kolumnach Tytuł, Wydawca, Autor, Opis.
        """
        self.page_number = 1
        if self.lbl_title.text() == 'Biblioteka':
            self.get_books(1,
                           search=self.edit_search.text(),
                           sort_direction=self.sorted_direction)

    def sort_direction(self, text):
        """
        Sortuje wyniki wyświetlania zawartości biblioteki rosnąco bądź malejąco.
        :param text: str
        """
        sort_dict = {'Rosnąco': 0, 'Malejąco': 1}
        self.page_number = 1
        self.sorted_direction = sort_dict.get(text)
        if self.lbl_title.text() == 'Biblioteka':
            self.get_books(1,
                           sort_by=self.sorted_by,
                           sort_direction=self.sorted_direction)

    def sort_by(self, text):
        """
        Sortuje wyniki wyświetlania zawartości biblioteki na podstawie Tytułu, Opisu, Kategorii, Wydawcy lub Daty
        wydania.
        :param text: str
        """
        sort_dict = {
            'Sortuj według': None,
            'Tytuł': 'BookName',
            'Opis': 'BookDescription',
            'Kategoria': 'Category',
            'Wydawca': 'PublisherName',
            'Data wydania': 'PublishDate'
        }
        self.page_number = 1
        self.sorted_by = sort_dict.get(text)
        if self.lbl_title.text() == 'Biblioteka':
            self.get_books(1,
                           sort_by=self.sorted_by,
                           sort_direction=self.sorted_direction)

    def on_library_clicked(self):
        """
        Wyświetla książki zawarte w bibliotece.
        """
        print("library")
        self.clear_layout()
        self.lbl_title.setText('Biblioteka')
        self.edit_search.setPlaceholderText('Wyszukaj i wciśnij ENTER')
        self.btn_delete_book.show()
        self.btn_borrow_book.show()
        self.text_layout.addWidget(self.lbl_title)
        self.text_layout.addWidget(self.library)
        self.text_layout.addWidget(self.tbl_result)
        self.text_layout.addWidget(self.btn_add_book)
        self.btn_add_book.setStyleSheet(
            "color: #17a2b8; border-color : #17a2b8")
        self.get_books(self.page_number)

        print(self.page_number, self.page)
        self.lbl_page.setText('Strona {}/{}'.format(self.page_number,
                                                    self.page))
        self.text_layout.addWidget(self.lbl_page)

    def on_profile_clicked(self):
        """
        Wyświetla dane profilu, dostępne do edycji.
        """
        print("profile")
        self.clear_layout()
        self.lbl_title.setText('Ustawienia profilu')
        self.text_layout.addWidget(self.lbl_title)
        self.text_layout.addWidget(self.dialog_profile)
        data = self.get_user_id()
        self.edit_name.setText(data.get('name'))
        self.edit_subname.setText(data.get('surname'))
        self.edit_email.setText(data.get('email'))

    def on_profile_admin_clicked(self):
        """
        Wyświetla dane wszystkich użytkowników.
        """
        self.clear_layout()
        self.lbl_title.setText('Usuwanie profilu')
        self.text_layout.addWidget(self.lbl_title)
        self.get_users()
        self.text_layout.addWidget(self.tbl_result)

    def on_permission_clicked(self):
        """
        Wyświetla możliwość utworzenia konta z uprawnieniami.
        """
        print("permission")
        self.clear_layout()
        self.lbl_title.setText('Utwórz nowe konto z uprawnieniami')
        self.text_layout.addWidget(self.lbl_title)
        self.text_layout.addWidget(self.dialog_permission)

    def on_set_permission_clicked(self):
        """
        Wyświetla możliwość zmiany uprawnień konta.
        """
        print("permission set")
        self.clear_layout()
        self.lbl_title.setText('Uprawnienia')
        self.text_layout.addWidget(self.lbl_title)
        self.get_users()
        self.text_layout.addWidget(self.tbl_result)

    def on_set_permission(self):
        print("permission set")
        self.clear_layout()
        self.lbl_title.setText('Uprawnienia')
        self.text_layout.addWidget(self.lbl_title)
        self.text_layout.addWidget(self.dialog_set_permission)

    def change_permission(self, user_id):
        permission_api = URL + self.get_users_api + '/role/' + str(user_id)
        jsons = {"roleId": int(self._role_id)}

        x = threading.Thread(target=patch_request,
                             args=(permission_api, jsons,
                                   self.user.get('token'), self.que))

        x.start()
        x.join()

        data = self.que.get()
        print(data)
        if data.status_code == 200:
            QMessageBox.information(self, "Zmiana uprawnień",
                                    "Uprawnienia zostały zmienione!")

    def on_change_passwd_clicked(self):
        """
        Wyświetla możliwość zmiany hasła.
        """
        print("Change password")
        self.clear_layout()
        self.lbl_title.setText(self._txt_change_perm)
        self.text_layout.addWidget(self.lbl_title)
        self.text_layout.addWidget(self.dialog_password)
        self.edit_passwd1.setFocus()

    def on_change_passwd_admin_clicked(self):
        """
        Wyświetla użytkowników, dla których hasło zostanie zmienione.
        """
        print("Change admin password")
        self.clear_layout()
        self.lbl_title.setText(self._txt_change_perm)
        self.text_layout.addWidget(self.lbl_title)
        self.get_users()
        self.text_layout.addWidget(self.tbl_result)

    def on_logout_clicked(self):
        """
        Wylogowuje użytkownika z programu.
        """
        print("Logout")
        self.clear_layout()
        self.logout()

    def logout(self):
        """
        Ukrywa główne okno, wyświetla monit o logowanie, po czym podmienia zawartość zmiennej user.
        """
        self.close()
        self.parent().hide()
        window = Login()
        dec = window.exec_()
        if dec == QDialog.Accepted:
            result = json.loads(window.response.text)
            self.user = result
            self.parent().setCentralWidget(MainWidget(result))
            self.parent().show()
            self.on_home_clicked()

    def set_data(self, data):
        """
        Wyświetla dane w tabeli i je formatuje.
        :param data: dict
        """
        self.tbl_result.clear()
        row_count = (len(data))
        column_count = (len(data[0]))
        role_id = {1: 'Użytkownik', 2: 'Pracownik', 3: 'Administrator'}

        self.tbl_result.setColumnCount(column_count)
        self.tbl_result.setRowCount(row_count)

        self.tbl_result.setHorizontalHeaderLabels((list(data[0].keys())))

        if list(data[0].keys())[0] == 'id':
            self.tbl_result.hideColumn(0)
        if 'roleId' in list(data[0].keys(
        )) and self.lbl_title.text() != self._txt_permission:
            self.tbl_result.hideColumn(4)
        else:
            self.tbl_result.showColumn(4)

        for row in range(row_count):
            for column in range(column_count):
                item = (list(data[row].values())[column])
                if column == 4 and self.lbl_title.text(
                ) == self._txt_permission:
                    item = role_id.get(item)
                self.tbl_result.setItem(row, column,
                                        QTableWidgetItem(str(item)))

        self.tbl_result.resizeColumnsToContents()
        self.tbl_result.resizeRowsToContents()

    def get_books(self,
                  page_number,
                  sort_by=None,
                  sort_direction=None,
                  search=None):
        """
        Pobiera dane o zawartości książek w bibliotece z API w wątku.
        :param page_number: int
        :param sort_by: str
        :param sort_direction: int
        :param search: str
        """
        self.page_number = page_number
        url = URL + self.get_books_api + f'?PageNumber={page_number}&PageSize=15'
        if search:
            url += f'&SearchPhrase={search}'
        if sort_by:
            url += f'&SortBy={sort_by}'
        if sort_direction:
            url += f'&SortDirection={sort_direction}'

        x = threading.Thread(target=get_request,
                             args=(url, self.user.get('token'), self.que))

        x.start()
        x.join()

        data = self.que.get()
        self.page = data.get('totalPages')

        if data.get('itemFrom') == 1:
            self.btn_back_page.setEnabled(False)
        else:
            self.btn_back_page.setEnabled(True)
        if page_number < data.get('totalPages'):
            self.btn_next_page.setEnabled(True)
        else:
            self.btn_next_page.setEnabled(False)

        self.set_data(data.get('items'))

    def get_users(self):
        """
        Pobiera dane o użytkownikach z API w wątku.
        """
        x = threading.Thread(target=get_request,
                             args=("".join([URL, self.get_users_api]),
                                   self.user.get('token'), self.que))

        x.start()
        x.join()

        data = self.que.get()
        self.set_data(data)

    def get_user_id(self):
        """
        Pobiera dane o konkretnym użytkowniku w wątku i je zwraca.
        :return: json
        """
        user_id_api = "".join(
            [URL, self.get_users_api, '/',
             str(self.user.get('id'))])
        x = threading.Thread(target=get_request,
                             args=(user_id_api, self.user.get('token'),
                                   self.que))

        x.start()
        x.join()

        data = self.que.get()
        return data

    def get_book_id(self, book_id):
        """
        Pobiera dane o konkretnej książce w wątku i je zwraca.
        :param book_id: int
        :return: json
        """
        self.btn_delete_book.clicked.connect(lambda: self.delete_book(book_id))
        self.btn_borrow_book.clicked.connect(lambda: self.borrow_book_user())
        self._book_id = book_id
        book_id_api = "".join([URL, self.get_books_api, '/', str(book_id)])
        x = threading.Thread(target=get_request,
                             args=(book_id_api, self.user.get('token'),
                                   self.que))

        x.start()
        x.join()

        data = self.que.get()
        return data

    def get_book_user_id(self, user_id=None):
        """
        Pobiera dane na temat wypożyczonych książek przez konkretnego użytkownika.
        :param user_id: int
        :return: boolean
        """
        if not user_id:
            user_id = self.user.get('id')
        book_user_id_api = URL + self.get_books_api + '/' + self.get_users_api[:-1] + '/' + str(
            user_id)
        x = threading.Thread(target=get_request,
                             args=(book_user_id_api, self.user.get('token'),
                                   self.que))

        x.start()
        x.join()

        data = self.que.get()
        if data:
            self.set_data(data)
            return True
        else:
            return False

    def get_reservation_user_id(self, user_id=None):
        """
        Pobiera dane na temat zarezerwowanych książek przez konkretnego użytkownika.
        :param user_id: int
        :return: boolean
        """
        if not user_id:
            user_id = self.user.get('id')
        reservation_user_id_api = URL + self.get_books_api + '/' + self.get_users_api[:-1] + self.reservation_api + str(
            user_id)
        x = threading.Thread(target=get_request,
                             args=(reservation_user_id_api,
                                   self.user.get('token'), self.que))

        x.start()
        x.join()

        data = self.que.get()
        if data:
            self.set_data(data)
            return True
        else:
            return False

    def borrow_book_user(self):
        """
        Wyświetla listę użytkowników, dla których dana książka zostanie wypożyczona.
        """
        self.clear_layout()
        self.lbl_title.setText('Wybierz użytkownika')
        self.text_layout.addWidget(self.lbl_title)
        self.get_users()
        self.text_layout.addWidget(self.tbl_result)

    def reservation_book(self, book_id):
        """
        Dokonuje rezerwacji danej książki w wątku.
        :param book_id: int
        """
        url = URL + self.get_books_api + self.reservation_api + str(book_id)
        x = threading.Thread(target=patch_request,
                             args=(url, {}, self.user.get('token'), self.que))

        x.start()
        x.join()

        data = self.que.get()
        print(data)
        return data

    def borrow_book(self, book_id, user_id):
        """
        Dokonuje wypożyczenia książki użytkownikowi, w wątku.
        :param book_id: int
        :param user_id: int
        """
        url = URL + self.get_books_api + '/borrow/' + str(book_id)
        jsons = {"userId": int(user_id)}

        self.reservation_book(book_id)

        x = threading.Thread(target=patch_request,
                             args=(url, jsons, self.user.get('token'),
                                   self.que))

        x.start()
        x.join()

        data = self.que.get()
        print(data)
        if data:
            QMessageBox.information(self, "Wypożyczono",
                                    "Podana pozycja została wypożyczona!")
            self.on_library_clicked()
        elif data.status_code == 400:
            QMessageBox.warning(self, "Błąd", data.text)
            self.on_library_clicked()

    def back_book(self, book_id):
        """
        Dokonuje zwrotu danej książki w wątku.
        :param book_id: int
        """
        url = URL + self.get_books_api + '/return/' + str(book_id)
        jsons = {}
        x = threading.Thread(target=patch_request,
                             args=(url, jsons, self.user.get('token'),
                                   self.que))

        x.start()
        x.join()

        data = self.que.get()
        print(data)
        print('Zwrócono')
        if data:
            QMessageBox.information(self, "Zwrócono",
                                    "Książka została zwrócona!")
            self.on_home_clicked()
        else:
            self.clear_layout()
            self.lbl_title.setText('Nie masz żadnych wypożyczonych książek')
            self.text_layout.addWidget(self.lbl_title)

    def post_user(self):
        """
        Dokonuje utworzenia nowego użytkownika.
        """
        user_api = "".join([URL, self.get_users_api])
        jsons = {
            "name": self.edit_new_name.text(),
            "surname": self.edit_new_surname.text(),
            "email": self.edit_new_email.text(),
            "password": self.edit_pass2.text(),
            "confirmPassword": self.edit_pass3.text(),
            "roleId": self._role_id
        }

        if self.edit_pass2.text() != self.edit_pass3.text():
            QMessageBox.warning(self, "Błędne hasła",
                                "Hasła nie są takie same.")
            self.edit_pass2.setText('')
            self.edit_pass3.setText('')
            self.edit_pass2.setFocus()
            return
        if self.edit_pass2.text() == '':
            QMessageBox.warning(self, "Błąd", "Hasła nie mogą być puste.")
            self.edit_pass2.setFocus()
            return
        if self.edit_new_email.text() == '':
            QMessageBox.warning(self, "Błąd", "Email nie może być pusty.")
            self.edit_new_email.setFocus()
            return

        x = threading.Thread(target=post_request,
                             args=(user_api, jsons, self.user.get('token'),
                                   self.que))

        x.start()
        x.join()

        data = self.que.get()
        if data.status_code == 201:
            QMessageBox.information(self, "Nowe konto",
                                    'Utworzono nowe konto.')
        print(data)

    def add_book(self):
        """
        Wyświetla widgety odpowiedzialne za dodanie nowej książki do biblioteki.
        """
        print("Add book")
        self.clear_layout()
        self.lbl_title.setText('Dodaj nową książkę')
        self.text_layout.addWidget(self.lbl_title)
        self.btn_delete_book.hide()
        self.btn_borrow_book.hide()
        self.text_layout.addWidget(self.dialog_book)

    def change_passwd(self, user_id=None):
        """
        Umożliwia zmianę hasła danego użytkownika w wątku.
        :param user_id: int
        """
        if user_id is None:
            user_id = self.user.get('id')
        print(user_id)
        token = self.user.get('token')
        url_password = "".join(['users/changePassword/', str(user_id)])

        if self.edit_passwd2.text() == '' or self.edit_passwd3.text() == '':
            QMessageBox.warning(self, "Brak nowego hasła",
                                "Nowe hasło nie może być puste")
            self.edit_passwd2.setText('')
            self.edit_passwd3.setText('')
            self.edit_passwd2.setFocus()
            return

        if self.edit_passwd2.text() != self.edit_passwd3.text():
            QMessageBox.warning(self, self._txt_bad_data,
                                "Podane hasła nie są identyczne!")
            self.edit_passwd2.setText('')
            self.edit_passwd3.setText('')
            self.edit_passwd2.setFocus()
            return

        jsons = {
            "oldPassword": self.edit_passwd1.text(),
            "newPassword": self.edit_passwd2.text(),
            "confirmNewPassword": self.edit_passwd3.text()
        }
        x = threading.Thread(target=patch_request,
                             args=("".join([URL, url_password]), jsons, token,
                                   self.que))

        x.start()
        x.join()

        response = self.que.get()
        if response.status_code == 400:
            print(response.text)
            if response.text == 'Invalid password':
                QMessageBox.warning(self, "Błędne hasło", response.text)
                self.edit_passwd1.setText('')
                self.edit_passwd1.setFocus()
                return
            error = json.loads(response.text)
            err = json.loads(
                json.dumps(error['errors'], indent=4, sort_keys=True))
            print(err)
            for key in err.keys():
                if key == 'NewPassword':
                    QMessageBox.warning(self, "Nowe hasło", err.get(key)[0])

        if response.status_code == 200:
            QMessageBox.information(self, "Hasło zmienione",
                                    "Hasło zostało zmienione!")
            self.edit_passwd1.setText('')
            self.edit_passwd2.setText('')
            self.edit_passwd3.setText('')
            self.on_home_clicked()

    def delete_profile(self, user_id=None):
        """
        Umożliwia usunięcie danego użytkownika z bazy, w wątku.
        :param user_id: int
        """
        token = self.user.get('token')
        _user_id = user_id
        if _user_id is None:
            user_id = self.user.get('id')
        url_profile = "".join([URL, self.get_users_api, '/', str(user_id)])
        button_reply = QMessageBox.question(
            self, 'Usuwanie',
            "Czy na pewnno chcesz usunąć te konto?\nOperacji tej nie można cofnąć.",
            QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if button_reply == QMessageBox.No:
            return

        x = threading.Thread(target=delete_request,
                             args=(url_profile, token, self.que))

        x.start()
        x.join()

        response = self.que.get()
        if response.status_code == 204:
            QMessageBox.information(self, "Usunięto",
                                    "Konto zostało usunięte!")
            if _user_id != self.user.get('id'):
                return
            self.on_logout_clicked()

        if response.status_code == 500:
            QMessageBox.warning(
                self, "Błąd",
                "Nie można usunąć użytkownika, który wypożyczył książki!")

    def delete_book(self, book_id):
        """
        Umożliwia usunięcie danej książki z bazy, w wątku.
        :param book_id: int
        """
        token = self.user.get('token')
        book_id_api = "".join([URL, self.get_books_api, '/', str(book_id)])
        button_reply = QMessageBox.question(
            self, 'Usuwanie',
            "Czy na pewno chcesz usunąć tą książkę?\nOperacji tej nie można cofnąć.",
            QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if button_reply == QMessageBox.No:
            return

        x = threading.Thread(target=delete_request,
                             args=(book_id_api, token, self.que))

        x.start()
        x.join()

        response = self.que.get()
        if response.status_code == 204:
            QMessageBox.information(self, "Usunięto",
                                    "Książka została usunięta!")
            self.on_library_clicked()

    def new_book(self, book_id=None):
        """
        Umożliwia utworzenie nowej książki w bazie, bądź zmianę już istniejącej, w wątku.
        :param book_id: int
        """
        token = self.user.get('token')
        flag_book = False

        if self.edit_isbn.text() == '' or \
                self.edit_book_name.text() == '' or \
                self.edit_author.text() == '' or \
                self.edit_publisher.text() == '' or \
                self.edit_publish_date.text() == '' or \
                self.edit_category.text() == '':
            QMessageBox.warning(self, "Uwaga, błąd danych",
                                "Należy wypełnić wymagane pola.")
            return
        if not self.edit_publish_date.text().isdigit():
            QMessageBox.warning(self, self._txt_bad_data,
                                'Pole "Data wydania" należy wypełnić liczbą.')
            self.edit_publish_date.setFocus()
            return

        jsons = {
            "isbn": self.edit_isbn.text(),
            "bookName": self.edit_book_name.text(),
            "authorName": self.edit_author.text(),
            "publisherName": self.edit_publisher.text(),
            "publishDate": int(self.edit_publish_date.text()),
            "category": self.edit_category.text(),
            "language": self.edit_language_book.text(),
            "bookDescription": self.edit_book_description.toPlainText()
        }

        if not book_id:
            book_id_api = "".join([URL, self.get_books_api])
            x = threading.Thread(target=post_request,
                                 args=(book_id_api, jsons, token, self.que))
            title = 'Dodano'
            descr = 'Dodano nową pozycję do biblioteki.'
            flag_book = True
        if book_id:
            book_id_api = "".join([URL, self.get_books_api, '/', str(book_id)])
            x = threading.Thread(target=put_request,
                                 args=(book_id_api, jsons, token, self.que))
            title = 'Zmieniono'
            descr = 'Dane o książce zostały zaktualizowane.'
            flag_book = True

        if flag_book:
            x.start()
            x.join()

            response = self.que.get()
            print(response)
            if response.status_code == 200 or response.status_code == 201:
                QMessageBox.information(self, title, descr)
                self.on_library_clicked()

    def change_profile(self):
        """
        Umożliwia zmianę danych użytkownika, w wątku.
        """
        token = self.user.get('token')
        url_profile = "".join(
            [URL, self.get_users_api, '/',
             str(self.user.get('id'))])

        name = self.edit_name.text()
        surname = self.edit_subname.text()
        email = self.edit_email.text()

        try:
            validate_email(email)
        except EmailNotValidError as e:
            print(e)
            QMessageBox.warning(self, "Błędny email",
                                "Proszę wpisać poprawny email")
            self.edit_email.setFocus()
            return

        if name == '' or surname == '' or email == '':
            QMessageBox.warning(self, self._txt_bad_data,
                                "Podane dane nie mogą być puste!")
            self.edit_name.setFocus()
            return

        jsons = {"name": name, "surname": surname, "email": email}

        x = threading.Thread(target=put_request,
                             args=(url_profile, jsons, token, self.que))

        x.start()
        x.join()

        response = self.que.get()
        print(response)
        if response.status_code == 200:
            QMessageBox.information(self, "Zmiana danych",
                                    "Dane zostały pomyślnie zapisane!")

    def change_book(self, book_id):
        """
        Uzupełnia dane widgetów o zawartość jsona danej książki.
        :param book_id: int
        """
        print("Change book id")
        self.clear_layout()
        self.lbl_title.setText('Edycja książki')
        self.text_layout.addWidget(self.lbl_title)
        jsons = self.get_book_id(book_id)
        self.edit_isbn.setText(jsons.get('isbn'))
        self.edit_book_name.setText(jsons.get('bookName'))
        self.edit_author.setText(jsons.get('authorName'))
        self.edit_publisher.setText(jsons.get('publisherName'))
        self.edit_publish_date.setText(str(jsons.get('publishDate')))
        self.edit_category.setText(jsons.get('category'))
        self.edit_language_book.setText(jsons.get('language'))
        self.edit_book_description.setPlainText(jsons.get('bookDescription'))
        self.text_layout.addWidget(self.dialog_book)

    def delete_reservation(self, book_id):
        """
        Zwraca zarezerwowaną książkę.
        :param book_id: int
        """
        print("Return book reserved")

        reservation_del_api = URL + self.get_books_api + self.reservation_api + str(
            book_id)
        x = threading.Thread(target=delete_request,
                             args=(reservation_del_api, self.user.get('token'),
                                   self.que))

        x.start()
        x.join()

        data = self.que.get()
        print(data)
        if data:
            QMessageBox.information(
                self, "Pozycja usunięta",
                "Podana pozycja została usunięta z listy zarezerwowanych!")

        self.on_reserved_clicked()
Example #14
0
class WetSand(QWidget):
    def __init__(self):
        super().__init__()

        # Init list
        self.list = QListWidget()
        self.list.addItem(WetListItem("You don't form in the wet sand"))
        self.list.addItem(WetListItem("You don't form at all"))

        # Init buttons
        self.btns = [
            WetButton('johs'),
            WetButton('jon'),
            WetButton('feel'),
            WetButton('chat'),
            WetButton('tony')
        ]

        # Freeze and unfreeze buttons
        self.freeze_btn = QPushButton('Freeze me')
        self.unfreeze_btn = QPushButton('Unfreeze me')

        # Layout stuff
        self.btn_lyt = QVBoxLayout()
        for btn in self.btns:
            self.btn_lyt.addWidget(btn)

        self.top_lyt = QHBoxLayout()
        self.top_lyt.addWidget(self.list)
        self.top_lyt.addLayout(self.btn_lyt)

        self.bottom_lyt = QHBoxLayout()
        self.bottom_lyt.addWidget(self.freeze_btn)
        self.bottom_lyt.addWidget(self.unfreeze_btn)

        self.main_lyt = QVBoxLayout()
        self.main_lyt.addLayout(self.top_lyt)
        self.main_lyt.addLayout(self.bottom_lyt)

        # Signals and slots
        # Use functools.partial to send arguments
        self.freeze_btn.clicked.connect(partial(self.freeze, True))
        self.unfreeze_btn.clicked.connect(partial(self.freeze, False))

        # Don't forget me
        self.setLayout(self.main_lyt)
        self.show()

    @Slot()
    def freeze(self, yes=True):
        # Freeze buttons
        print(yes)
        btns = (self.btn_lyt.itemAt(i).widget()
                for i in range(self.btn_lyt.count()))
        for btn in btns:
            btn.set_clickable(not yes)

        # Freeze list items
        list_items = [self.list.item(i) for i in range(self.list.count())]
        for list_item in list_items:
            list_item.set_selectable(not yes)
Example #15
0
class ButtonList(QWidget):

    # 按钮添加时发送信号
    # QPushButton为触发的按钮
    signalBtnAdded = Signal(QPushButton)
    signalBtnDeleted = Signal(QPushButton)
    signalBtnClicked = Signal(QPushButton)
    """按钮列表,默认有一个添加按钮的按钮"""
    def __init__(self, addStr: str, parent: any = None) -> None:
        '''按钮列表,默认有一个添加按钮的按钮

        Args:
            addStr(str):添加按钮上显示的文字
        '''
        super().__init__(parent=parent)

        self.addIterm = QPushButton(addStr, self)
        self.addIterm.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        # self.addIterm.resize(self.addIterm.sizeHint())
        self.addIterm.clicked.connect(self.addBtn_clicked)

        # 添加按钮布局
        self.layDown = QVBoxLayout()
        self.layDown.addWidget(self.addIterm)
        # 按钮布局,与addIterm所处的布局分开,防止删除按钮时布局的神秘错乱(我太菜了)
        self.layUp = QVBoxLayout()
        # 整体外布局
        lay = QVBoxLayout()
        lay.setMargin(3)  # 设置边距
        lay.addLayout(self.layUp)
        lay.addLayout(self.layDown)
        lay.addStretch(1)  # 添加拉伸因子,防止按钮由于父控件大小被纵向拉伸
        self.setLayout(lay)  # 应用布局

    @Slot()
    def addBtn_clicked(self) -> QPushButton:
        index = self.layUp.count()  # 添加的按钮在布局中的索引位置,起始0
        button = QPushButton('btn{}'.format(index))
        button.clicked.connect(lambda: self.button_clicked(button))

        button.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)

        def on_context_menu(point):
            # 弹出菜单
            menu.exec_(button.mapToGlobal(point))  # 把相对于按钮的位置转为相对于全局的位置

        button.setContextMenuPolicy(Qt.CustomContextMenu)  # 菜单策略,自定义
        button.customContextMenuRequested.connect(on_context_menu)  # 触发信号

        # 设置右击删除菜单
        menu = QMenu(button)
        delQAction = QAction("删除", button)
        delQAction.triggered.connect(lambda: self.deleteButton(button))
        menu.addAction(delQAction)

        self.layUp.insertWidget(index, button)  # 添加按钮到布局中
        self.signalBtnAdded.emit(button)  # 发送添加信号
        return button

    @Slot(QPushButton)
    def deleteButton(self, button: QPushButton):
        self.signalBtnDeleted.emit(button)  # 发送删除信号
        # self.layUp.removeWidget(button)  # 移除控件
        button.deleteLater()  # 删除控件

    @Slot(QPushButton)
    def button_clicked(self, button: QPushButton):
        self.signalBtnClicked.emit(button)  # 发送点击信号
Example #16
0
class Form(QDialog):
    def __init__(self, parent=None):
        super(Form, self).__init__(parent)
        self.setWindowTitle('RetroUFO')

        # Create widgets
        self.chkboxPlatformDetect = QCheckBox('Platform Auto-Detect')
        self.chkboxPlatformDetect.setChecked(True)
        self.chkboxPlatformDetect.stateChanged.connect(self.auto_detect)

        self.cmbboxPlatform = QComboBox()
        self.cmbboxPlatform.setEnabled(False)
        self.cmbboxPlatform.setEditable(False)
        self.cmbboxPlatform.addItem('Linux')
        self.cmbboxPlatform.addItem('macOS')
        self.cmbboxPlatform.addItem('Windows')

        self.cmbboxArchitecture = QComboBox()
        self.cmbboxArchitecture.setEnabled(False)
        self.cmbboxArchitecture.setEditable(False)
        self.cmbboxArchitecture.addItem('x86')
        self.cmbboxArchitecture.addItem('x86_64')

        self.chkboxLocationDetect = QCheckBox('Core Location Auto-Detect')
        self.chkboxLocationDetect.setChecked(True)
        self.chkboxLocationDetect.stateChanged.connect(self.auto_location)

        self.leditCoreLocation = QLineEdit('')
        self.leditCoreLocation.setEnabled(False)

        self.btnCoreLocation = QPushButton('...')
        self.btnCoreLocation.setEnabled(False)
        self.btnCoreLocation.clicked.connect(self.choose_location)

        self.teditLog = QTextEdit()
        self.teditLog.setReadOnly(True)

        self.tcsrLog = QTextCursor(self.teditLog.document())

        self.chkboxKeepDownload = QCheckBox('Keep Downloaded Cores')
        self.chkboxKeepDownload.setChecked(False)

        self.btnGrabCores = QPushButton('Grab Cores')
        self.btnGrabCores.clicked.connect(self.grab_cores)

        # Create layout and add widgets
        self.formLayout = QVBoxLayout()
        self.formLayout.addWidget(self.chkboxPlatformDetect)
        self.formLayout.addWidget(self.cmbboxPlatform)
        self.formLayout.addWidget(self.cmbboxArchitecture)
        self.formLayout.addWidget(self.chkboxLocationDetect)
        self.formLayout.addWidget(self.leditCoreLocation)
        self.formLayout.addWidget(self.btnCoreLocation)
        self.formLayout.addWidget(self.teditLog)
        self.formLayout.addWidget(self.chkboxKeepDownload)
        self.formLayout.addWidget(self.btnGrabCores)

        # Set dialog layout
        self.setLayout(self.formLayout)

    def auto_detect(self):
        if self.chkboxPlatformDetect.isChecked():
            self.cmbboxPlatform.setEnabled(False)
            self.cmbboxArchitecture.setEnabled(False)
        else:
            self.cmbboxPlatform.setEnabled(True)
            self.cmbboxArchitecture.setEnabled(True)

    def auto_location(self):
        if self.chkboxLocationDetect.isChecked():
            self.leditCoreLocation.setEnabled(False)
            self.btnCoreLocation.setEnabled(False)
        else:
            self.leditCoreLocation.setEnabled(True)
            self.btnCoreLocation.setEnabled(True)

    def choose_location(self):
        directory = QFileDialog.getExistingDirectory(self,
                                                     'Choose Target Location',
                                                     os.path.expanduser('~'))

        self.leditCoreLocation.insert(directory)

    def update_log(self, _info):
        self.teditLog.insertPlainText('{}\n'.format(_info))

        # Auto scrolling on log UI
        self.teditLog.moveCursor(QTextCursor.End)

    def lock_ui(self, _lock):
        # Cycle through each widget and disable it except for log UI
        widgets = (self.formLayout.itemAt(i).widget()
                   for i in range(self.formLayout.count()))
        for widget in widgets:
            if isinstance(widget, QTextEdit):
                pass
            else:
                widget.setDisabled(_lock)
                # Have to run these to make sure only the correct things unlock after grab thread
                self.auto_detect()
                self.auto_location()

    def grab_cores(self):
        """ Where the magic happens """
        if not self.chkboxKeepDownload.isChecked():
            self.clean_up()

        target_platform = self.get_platform()
        architecture = self.get_architecture()
        location = self.get_location()

        self.grab = GrabThread(target_platform, architecture, location)
        self.grab.add_to_log.connect(self.update_log)
        self.grab.lock.connect(self.lock_ui)
        self.grab.start()

    def get_platform(self):
        """ Gets the Platform and Architecture if not supplied """

        if not self.chkboxPlatformDetect.isChecked():
            if self.cmbboxPlatform.currentText() == 'macOS':
                return 'apple/osx'  # macOS
            else:
                return self.cmbboxPlatform.currentText().lower()
        else:
            if platform.system() == 'Linux':
                return 'linux'
            elif platform.system() == 'Darwin':  # macOS
                return 'apple/osx'
            elif platform.system(
            ) == 'Windows' or 'MSYS_NT' in platform.system(
            ):  # Checks for MSYS environment as well
                return 'windows'
            else:
                msgBox = QMessageBox.warning(
                    self, 'Error', 'Platform not found or supported!',
                    QMessageBox.Ok)
                msgBox.exec_()

    def get_architecture(self):
        """ Gets the Platform and Architecture if not supplied """

        if '64' in platform.architecture()[0]:
            return 'x86_64'

        elif '32' in platform.architecture()[0]:
            return 'x86'
        else:
            msgBox = QMessageBox.warning(
                self, 'Error', 'Architecture not found or supported',
                QMessageBox.Ok)
            msgBox.exec_()

    def get_location(self):
        if not self.chkboxLocationDetect.isChecked():
            return self.leditCoreLocation.text()
        else:
            return CORE_LOCATION[self.get_platform()]

    def clean_up(self):
        """ Removes all the downloaded files """
        if os.path.isdir('cores'):
            rmtree('cores/')
Example #17
0
class ViewWidget(QWidget):
    exercise_name_label = None
    exercise_name_line = None

    scroll_area = None
    base_widget = None
    exercises_widget = None

    return_button = None

    add_button = None

    def __init__(self):
        QWidget.__init__(self)

        self.file = ""
        self.setup_widget()

    def setup_widget(self):
        self.exercise_name_label = QLabel("Exercise name:", self)
        self.exercise_name_label.move(5, 5)
        self.exercise_name_label.resize(125, 25)

        self.add_button = QPushButton("Add", self)
        self.add_button.resize(75, 25)
        self.add_button.clicked.connect(self.add_line)

        self.exercise_name_line = QLineEdit(self)
        self.exercise_name_line.move(135, 5)
        self.exercise_name_line.resize(125, 25)

        self.scroll_area = QScrollArea(self)
        self.base_widget = QWidget(self)
        self.scroll_area.setWidget(self.base_widget)

        self.exercises_widget = QVBoxLayout()
        self.exercises_widget.setAlignment(Qt.AlignTop)

        self.base_widget.setLayout(self.exercises_widget)

        self.return_button = QPushButton("Return wo save", self)

    def resizeEvent(self, event):
        self.scroll_area.move(5, 35)
        self.scroll_area.resize(self.width() - 165, self.height() - 40)
        self.add_button.move(self.width() - 160 - 75, 5)
        self.return_button.move(self.width() - 155, 5)
        self.return_button.resize(150, 40)

        self.base_widget.resize(self.scroll_area.width() - 25,
                                self.exercises_widget.count() * 25)

    def clear_widget(self):
        while self.exercises_widget.count() > 0:
            self.exercises_widget.takeAt(0)

    def open_exercise_file(self, file: str):
        self.file = file

        with open(self.file, "r") as json_file:
            json_data = json.load(json_file)

            name = json_data['name']

            for data in json_data['exercise']:
                movement = data['name']
                description = data['description']
                time = data['time']

                widget = PanelWidget()
                widget.set_data(movement, description, time)
                widget.remove_signal.connect(self.remove_panel_item)
                widget.move_down_signal.connect(self.move_widget_down)
                widget.move_up_signal.connect(self.move_widget_up)

                self.exercises_widget.addWidget(widget)

            json_file.close()

        self.base_widget.resize(self.scroll_area.width() - 25,
                                self.exercises_widget.count() * 25)

        self.exercise_name_line.setText(name)

    @Slot()
    def add_line(self):
        widget = PanelWidget()
        self.exercises_widget.addWidget(widget)
        self.base_widget.resize(self.scroll_area.width() - 25,
                                self.exercises_widget.count() * 25)

    @Slot(QWidget)
    def move_widget_down(self, widget: QWidget):
        ind = self.exercises_widget.indexOf(widget)
        self.exercises_widget.removeWidget(widget)
        self.exercises_widget.insertWidget((ind + 1), widget)

    @Slot(QWidget)
    def move_widget_up(self, widget: QWidget):
        ind = self.exercises_widget.indexOf(widget)
        self.exercises_widget.removeWidget(widget)
        self.exercises_widget.insertWidget((ind - 1), widget)

    @Slot(QWidget)
    def remove_panel_item(self, widget: QWidget):
        self.exercises_widget.removeWidget(widget)
        self.base_widget.resize(self.scroll_area.width() - 25,
                                self.exercises_widget.count() * 25)
class QGroundObjectMenu(QDialog):

    changed = QtCore.Signal()

    def __init__(self, parent, ground_object: TheaterGroundObject,
                 buildings: [], cp: ControlPoint, game: Game):
        super(QGroundObjectMenu, self).__init__(parent)
        self.setMinimumWidth(350)
        self.ground_object = ground_object
        self.buildings = buildings
        self.cp = cp
        self.game = game
        self.setWindowTitle("Location " + self.ground_object.obj_name)
        self.setWindowIcon(EVENT_ICONS["capture"])
        self.intelBox = QGroupBox("Units :")
        self.buildingBox = QGroupBox("Buildings :")
        self.intelLayout = QGridLayout()
        self.buildingsLayout = QGridLayout()
        self.sell_all_button = None
        self.total_value = 0
        self.init_ui()

    def init_ui(self):

        self.mainLayout = QVBoxLayout()
        self.budget = QBudgetBox(self.game)
        self.budget.setGame(self.game)

        self.doLayout()

        if self.ground_object.dcs_identifier == "AA":
            self.mainLayout.addWidget(self.intelBox)
        else:
            self.mainLayout.addWidget(self.buildingBox)

        self.actionLayout = QHBoxLayout()

        self.sell_all_button = QPushButton("Disband (+" +
                                           str(self.total_value) + "M)")
        self.sell_all_button.clicked.connect(self.sell_all)
        self.sell_all_button.setProperty("style", "btn-danger")

        self.buy_replace = QPushButton("Buy/Replace")
        self.buy_replace.clicked.connect(self.buy_group)
        self.buy_replace.setProperty("style", "btn-success")

        if self.total_value > 0:
            self.actionLayout.addWidget(self.sell_all_button)
        self.actionLayout.addWidget(self.buy_replace)

        if self.cp.captured and self.ground_object.dcs_identifier == "AA":
            self.mainLayout.addLayout(self.actionLayout)
        self.setLayout(self.mainLayout)

    def doLayout(self):

        self.update_total_value()
        self.intelBox = QGroupBox("Units :")
        self.intelLayout = QGridLayout()
        i = 0
        for g in self.ground_object.groups:
            if not hasattr(g, "units_losts"):
                g.units_losts = []
            for u in g.units:
                self.intelLayout.addWidget(
                    QLabel("<b>Unit #" + str(u.id) + " - " + str(u.type) +
                           "</b>"), i, 0)
                i = i + 1

            for u in g.units_losts:

                utype = unit_type_of(u)
                if utype in PRICES:
                    price = PRICES[utype]
                else:
                    price = 6

                self.intelLayout.addWidget(
                    QLabel("<b>Unit #" + str(u.id) + " - " + str(u.type) +
                           "</b> [DEAD]"), i, 0)
                if self.cp.captured:
                    repair = QPushButton("Repair [" + str(price) + "M]")
                    repair.setProperty("style", "btn-success")
                    repair.clicked.connect(
                        lambda u=u, g=g, p=price: self.repair_unit(g, u, p))
                    self.intelLayout.addWidget(repair, i, 1)
                i = i + 1
        stretch = QVBoxLayout()
        stretch.addStretch()
        self.intelLayout.addLayout(stretch, i, 0)

        self.buildingBox = QGroupBox("Buildings :")
        self.buildingsLayout = QGridLayout()
        j = 0
        for i, building in enumerate(self.buildings):
            if building.dcs_identifier not in FORTIFICATION_BUILDINGS:
                self.buildingsLayout.addWidget(
                    QBuildingInfo(building, self.ground_object), j / 3, j % 3)
                j = j + 1

        self.buildingBox.setLayout(self.buildingsLayout)
        self.intelBox.setLayout(self.intelLayout)

    def do_refresh_layout(self):
        try:
            for i in range(self.mainLayout.count()):
                item = self.mainLayout.itemAt(i)
                if item is not None and item.widget() is not None:
                    item.widget().setParent(None)
            self.sell_all_button.setParent(None)
            self.buy_replace.setParent(None)
            self.actionLayout.setParent(None)

            self.doLayout()
            if self.ground_object.dcs_identifier == "AA":
                self.mainLayout.addWidget(self.intelBox)
            else:
                self.mainLayout.addWidget(self.buildingBox)

            self.actionLayout = QHBoxLayout()
            if self.total_value > 0:
                self.actionLayout.addWidget(self.sell_all_button)
            self.actionLayout.addWidget(self.buy_replace)

            if self.cp.captured and self.ground_object.dcs_identifier == "AA":
                self.mainLayout.addLayout(self.actionLayout)

        except Exception as e:
            print(e)
        self.update_total_value()
        self.changed.emit()

    def update_total_value(self):
        total_value = 0
        for group in self.ground_object.groups:
            for u in group.units:
                utype = unit_type_of(u)
                if utype in PRICES:
                    total_value = total_value + PRICES[utype]
                else:
                    total_value = total_value + 1
        if self.sell_all_button is not None:
            self.sell_all_button.setText("Disband (+$" +
                                         str(self.total_value) + "M)")
        self.total_value = total_value

    def repair_unit(self, group, unit, price):
        if self.game.budget > price:
            self.game.budget -= price
            group.units_losts = [
                u for u in group.units_losts if u.id != unit.id
            ]
            group.units.append(unit)
            GameUpdateSignal.get_instance().updateGame(self.game)

            # Remove destroyed units in the vicinity
            destroyed_units = self.game.get_destroyed_units()
            for d in destroyed_units:
                p = Point(d["x"], d["z"])
                if p.distance_to_point(unit.position) < 15:
                    destroyed_units.remove(d)
                    logging.info("Removed destroyed units " + str(d))
            logging.info("Repaired unit : " + str(unit.id) + " " +
                         str(unit.type))

        self.do_refresh_layout()
        self.changed.emit()

    def sell_all(self):
        self.update_total_value()
        self.game.budget = self.game.budget + self.total_value
        self.ground_object.groups = []
        self.do_refresh_layout()
        GameUpdateSignal.get_instance().updateBudget(self.game)

    def buy_group(self):
        self.subwindow = QBuyGroupForGroundObjectDialog(
            self, self.ground_object, self.cp, self.game, self.total_value)
        self.subwindow.changed.connect(self.do_refresh_layout)
        self.subwindow.show()
Example #19
0
class _BuilderTab(QWidget):
    """Handles the creation of launchers."""

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

        self._layout = QVBoxLayout()

        host_label = QLabel("Server host (where EvilOSX will connect to):")
        self._host_field = QLineEdit()

        self._layout.addWidget(host_label)
        self._layout.addWidget(self._host_field)

        port_label = QLabel("Server port:")
        self._port_field = QLineEdit()

        self._layout.addWidget(port_label)
        self._layout.addWidget(self._port_field)

        live_label = QLabel("Where should EvilOSX live? (Leave empty for ~/Library/Containers/.<RANDOM>): ")
        self._live_field = QLineEdit()

        self._layout.addWidget(live_label)
        self._layout.addWidget(self._live_field)

        launcher_label = QLabel("Launcher name:")
        self._launcher_combobox = QComboBox()

        for launcher_name in launchers.get_names():
            self._launcher_combobox.addItem(launcher_name)

        self._layout.addWidget(launcher_label)
        self._layout.addWidget(self._launcher_combobox)

        loader_label = QLabel("Loader name:")
        loader_combobox = QComboBox()
        self._loader_layout = QVBoxLayout()

        for loader_name in loaders.get_names():
            loader_combobox.addItem(loader_name)

        self._layout.addWidget(loader_label)
        self._layout.addWidget(loader_combobox)
        loader_combobox.currentTextChanged.connect(self._set_on_loader_change)

        # Dynamically loaded loader layout
        self._layout.addLayout(self._loader_layout)
        self._set_on_loader_change(loader_combobox.currentText())

        self._layout.setContentsMargins(10, 10, 10, 0)
        self._layout.setAlignment(Qt.AlignTop)
        self.setLayout(self._layout)

    def _set_on_loader_change(self, new_text):
        """Handles the loader combobox change event.

        :type new_text: str
        """
        while self._loader_layout.count():
            child = self._loader_layout.takeAt(0)

            if child.widget():
                child.widget().deleteLater()

        input_fields = []

        for message in loaders.get_option_messages(new_text):
            input_field = QLineEdit()

            self._loader_layout.addWidget(QLabel(message))
            self._loader_layout.addWidget(input_field)
            input_fields.append(input_field)

        create_button = QPushButton("Create launcher")
        create_button.setMaximumWidth(250)
        create_button.setMinimumHeight(30)
        create_button.pressed.connect(lambda: self._on_create_launcher(
            self._host_field.text(), self._port_field.text(), self._live_field.text(),
            new_text, self._launcher_combobox.currentText(), input_fields
        ))

        self._loader_layout.addWidget(QLabel(""))
        self._loader_layout.addWidget(create_button)

    @staticmethod
    def display_error(text):
        """Displays an error message to the user.

        :type text: str
        """
        message = QMessageBox()

        message.setIcon(QMessageBox.Critical)
        message.setWindowTitle("Error")
        message.setText(text)
        message.setStandardButtons(QMessageBox.Ok)
        message.exec_()

    @staticmethod
    def display_info(text):
        """
        :type text: str
        """
        message = QMessageBox()

        message.setIcon(QMessageBox.Information)
        message.setWindowTitle("Information")
        message.setText(text)
        message.setStandardButtons(QMessageBox.Ok)
        message.exec_()

    def _on_create_launcher(self, server_host, server_port, program_directory,
                            loader_name, launcher_name, input_fields):
        """Creates the launcher and outputs it to the builds directory.

        :type server_host: str
        :type server_port: int
        :type program_directory: str
        :type loader_name: str
        :type launcher_name: str
        :type input_fields: list
        """
        if not self._host_field.text():
            self.display_error("Invalid host specified.")
        elif not str(self._port_field.text()).isdigit():
            self.display_error("Invalid port specified.")
        else:
            set_options = []

            for field in input_fields:
                set_options.append(field.text())

            loader_options = loaders.get_options(loader_name, set_options)
            loader_options["program_directory"] = program_directory

            stager = launchers.create_stager(server_host, server_port, loader_options)

            launcher_extension, launcher = launchers.generate(launcher_name, stager)
            launcher_path = path.realpath(path.join(
                path.dirname(__file__), path.pardir, path.pardir, "data", "builds", "Launcher-{}.{}".format(
                    str(uuid4())[:6], launcher_extension
                )))

            with open(launcher_path, "w") as output_file:
                output_file.write(launcher)

            self.display_info("Launcher written to: \n{}".format(launcher_path))
Example #20
0
class MainWindow(QMainWindow):
    def __init__(self, app):
        super(MainWindow, self).__init__()
        self.app = app
        self.central_widget_setup()
        self.default_vbox_setup()
        self.layout.addWidget(PrimaryMonitorSelect(self))
        self.add_application_button_setup()
        self.application_boxes_setup()
        self.application_grid_setup()
        self.application_scroll_setup()
        self.window_setup()

    def window_setup(self):
        self.adjustSize()
        self.setGeometry(
            QStyle.alignedRect(
                Qt.LeftToRight,
                Qt.AlignCenter,
                self.size(),
                QGuiApplication.primaryScreen().availableGeometry(),
            ))
        self.setWindowTitle("Saharah Paper")
        self.setWindowIcon(QIcon(f"{sys.argv[0]}/assets/app_icon.png"))

    def central_widget_setup(self):
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        self.layout = QVBoxLayout()
        self.layout.setAlignment(Qt.AlignCenter | Qt.AlignTop)
        self.centralWidget.setLayout(self.layout)

    def default_vbox_setup(self):
        self.defaultVBox = QHBoxLayout()
        self.defaultVBox.addWidget(WallpaperBox(self, 0, True))
        self.defaultVBox.addWidget(WallpaperBox(self, 1, True))
        self.layout.addLayout(self.defaultVBox)

    def add_application_button_setup(self):
        self.addApplicationButton = AddApplicationButton(self)
        self.layout.addWidget(self.addApplicationButton,
                              alignment=Qt.AlignCenter)

    def application_boxes_setup(self):
        self.applicationBoxes = list()
        for i in range(0, len(applicationSettings.list)):
            self.applicationBoxes.append(WallpaperBox(self, i))

    def application_grid_setup(self):
        self.applicationGrid = QVBoxLayout()
        self.applicationGridContainer = QWidget()
        self.applicationGridContainer.setLayout(self.applicationGrid)
        self.applicationGridContainer.setFixedWidth(1340)
        self.application_grid_arrange()

    def application_grid_arrange(self):
        for i in reversed(range(0, self.applicationGrid.count())):
            layout = self.applicationGrid.itemAt(i).layout()
            for j in reversed(range(0, layout.count())):
                widget = layout.itemAt(j).widget()
                widget.hide()
        for i in range(0, len(self.applicationBoxes)):
            r = math.floor((i) / 4)
            item = self.applicationGrid.itemAt(r)
            if item is None:
                layout = QHBoxLayout()
                layout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
                self.applicationGrid.addLayout(layout)
            else:
                layout = item.layout()
            applicationBox = self.applicationBoxes[i]
            applicationBox.set_index(i)
            applicationBox.show()
            layout.addWidget(applicationBox)
        self.applicationGridContainer.setFixedHeight(
            self.applicationGrid.count() * 250)

    def application_scroll_setup(self):
        self.applicationScroll = QScrollArea()
        self.applicationScroll.setWidget(self.applicationGridContainer)
        self.applicationScroll.setFixedSize(1370, 510)
        self.layout.addWidget(self.applicationScroll)

    def closeEvent(self, event):
        self.app.mainWindow.hide()
        event.accept()
Example #21
0
class Application(QMainWindow):
    def __init__(self,
                 metafile=None,
                 conversion_module=None,
                 source_paths=None,
                 kwargs_fields=None,
                 extension_modules=None,
                 extension_forms=None,
                 nwbfile_loc=None,
                 conversion_class=None,
                 nwbwidgets=True):
        super().__init__()
        # Dictionary storing source files paths
        self.source_paths = source_paths
        # Path to conversion module .py file
        self.conversion_module_path = conversion_module
        # Dictionary storing custom boolean options (to form checkboxes)
        self.kwargs_fields = kwargs_fields
        # Extension modules
        self.extension_modules = extension_modules
        # Updates name_to_gui_class with extension classes
        self.name_to_gui_class = name_to_gui_class
        if extension_forms:
            self.name_to_gui_class.update(extension_forms)
        # Temporary folder path
        self.temp_dir = tempfile.mkdtemp()
        # default nwbfile save location:
        self.nwbfile_loc = nwbfile_loc
        # conversion_class:
        self.conversion_class = conversion_class

        self.resize(1200, 900)
        self.setWindowTitle('NWB:N conversion tools')

        # Initialize GUI elements
        self.init_gui()
        self.init_meta_tab()
        self.load_meta_file(filename=metafile)
        if nwbwidgets:
            self.init_nwb_explorer()
        self.show()

    def init_gui(self):
        """Initiates GUI elements."""
        mainMenu = self.menuBar()

        fileMenu = mainMenu.addMenu('File')
        action_choose_conversion = QAction('Choose conversion module', self)
        fileMenu.addAction(action_choose_conversion)
        action_choose_conversion.triggered.connect(self.load_conversion_module)

        helpMenu = mainMenu.addMenu('Help')
        action_about = QAction('About', self)
        helpMenu.addAction(action_about)
        action_about.triggered.connect(self.about)

        self.tabs = QTabWidget()
        self.setCentralWidget(self.tabs)

    def init_meta_tab(self):
        # Center panels -------------------------------------------------------
        self.groups_list = []

        # Left-side panel: forms
        self.btn_load_meta = QPushButton('Load metafile')
        self.btn_load_meta.setIcon(self.style().standardIcon(
            QStyle.SP_ArrowDown))
        self.btn_load_meta.clicked.connect(
            lambda: self.load_meta_file(filename=None))
        self.btn_load_meta.setToolTip(
            "The YAML file with metadata for this conversion.\n"
            "You can customize the metadata in the forms below.")
        self.btn_save_meta = QPushButton('Save metafile')
        self.btn_save_meta.setIcon(self.style().standardIcon(
            QStyle.SP_DriveFDIcon))
        self.btn_save_meta.clicked.connect(self.save_meta_file)
        self.btn_run_conversion = QPushButton('Run conversion')
        self.btn_run_conversion.setIcon(self.style().standardIcon(
            QStyle.SP_MediaPlay))
        self.btn_run_conversion.clicked.connect(self.run_conversion)
        self.btn_form_editor = QPushButton('Form -> Editor')
        self.btn_form_editor.clicked.connect(self.form_to_editor)

        self.lbl_nwb_file = QLabel('Output nwb file:')
        self.lbl_nwb_file.setToolTip(
            "Path to the NWB file that will be created.")
        self.lin_nwb_file = QLineEdit('')
        if self.nwbfile_loc is not None:
            self.lin_nwb_file = QLineEdit(self.nwbfile_loc)
        self.btn_nwb_file = QPushButton()
        self.btn_nwb_file.setIcon(self.style().standardIcon(
            QStyle.SP_DialogOpenButton))
        self.btn_nwb_file.clicked.connect(self.load_nwb_file)

        l_grid1 = QGridLayout()
        l_grid1.setColumnStretch(3, 1)
        l_grid1.addWidget(self.btn_load_meta, 0, 0, 1, 1)
        l_grid1.addWidget(self.btn_save_meta, 0, 1, 1, 1)
        l_grid1.addWidget(self.btn_run_conversion, 0, 2, 1, 1)
        l_grid1.addWidget(QLabel(), 0, 3, 1, 1)
        l_grid1.addWidget(self.btn_form_editor, 0, 4, 1, 2)
        l_grid1.addWidget(self.lbl_nwb_file, 1, 0, 1, 1)
        l_grid1.addWidget(self.lin_nwb_file, 1, 1, 1, 3)
        l_grid1.addWidget(self.btn_nwb_file, 1, 4, 1, 1)

        # Adds custom files/dir paths fields
        if self.source_paths is None:
            self.lbl_source_file = QLabel('source files:')
            self.lin_source_file = QLineEdit('')
            self.btn_source_file = QPushButton()
            self.btn_source_file.setIcon(self.style().standardIcon(
                QStyle.SP_DialogOpenButton))
            self.btn_source_file.clicked.connect(self.load_source_files)
            l_grid1.addWidget(self.lbl_source_file, 3, 0, 1, 1)
            l_grid1.addWidget(self.lin_source_file, 3, 1, 1, 3)
            l_grid1.addWidget(self.btn_source_file, 3, 4, 1, 1)
        else:
            self.group_source_paths = QGroupBox('Source paths')
            self.grid_source = QGridLayout()
            self.grid_source.setColumnStretch(3, 1)
            ii = -1
            for k, v in self.source_paths.items():
                ii += 1
                lbl_src = QLabel(k + ':')
                setattr(self, 'lbl_src_' + str(ii), lbl_src)
                lin_src = QLineEdit(v['path'])
                setattr(self, 'lin_src_' + str(ii), lin_src)
                btn_src = QPushButton()
                btn_src.setIcon(self.style().standardIcon(
                    QStyle.SP_DialogOpenButton))
                setattr(self, 'btn_src_' + str(ii), btn_src)
                if v['type'] == 'file':
                    btn_src.clicked.connect(
                        (lambda x: lambda: self.load_source_files(x[0], x[1]))(
                            [ii, k]))
                else:
                    btn_src.clicked.connect(
                        (lambda x: lambda: self.load_source_dir(x[0], x[1]))(
                            [ii, k]))
                self.grid_source.addWidget(lbl_src, ii, 0, 1, 1)
                self.grid_source.addWidget(lin_src, ii, 1, 1, 3)
                self.grid_source.addWidget(btn_src, ii, 4, 1, 1)
            self.group_source_paths.setLayout(self.grid_source)
            l_grid1.addWidget(self.group_source_paths, 3, 0, 1, 6)

        # Adds custom kwargs checkboxes
        if self.kwargs_fields:
            self.group_kwargs = QGroupBox('KWARGS')
            self.grid_kwargs = QGridLayout()
            self.grid_kwargs.setColumnStretch(4, 1)
            ii = -1
            for k, v in self.kwargs_fields.items():
                ii += 1
                chk_kwargs = QCheckBox(k)
                chk_kwargs.setChecked(v)
                chk_kwargs.clicked.connect(
                    (lambda x: lambda: self.update_kwargs(x[0], x[1]))([ii,
                                                                        k]))
                setattr(self, 'chk_kwargs_' + str(ii), chk_kwargs)
                self.grid_kwargs.addWidget(chk_kwargs, ii // 4, ii % 4, 1, 1)
            self.group_kwargs.setLayout(self.grid_kwargs)
            l_grid1.addWidget(self.group_kwargs, 4, 0, 1, 6)

        self.l_vbox1 = QVBoxLayout()
        self.l_vbox1.addStretch()
        scroll_aux = QWidget()
        scroll_aux.setLayout(self.l_vbox1)
        l_scroll = QScrollArea()
        l_scroll.setWidget(scroll_aux)
        l_scroll.setWidgetResizable(True)

        self.l_vbox2 = QVBoxLayout()
        self.l_vbox2.addLayout(l_grid1)
        self.l_vbox2.addWidget(l_scroll)

        # Right-side panel
        # Metadata text
        editor_label = QLabel('Metafile preview:')
        r_grid1 = QGridLayout()
        r_grid1.setColumnStretch(1, 1)
        r_grid1.addWidget(editor_label, 0, 0, 1, 1)
        r_grid1.addWidget(QLabel(), 0, 1, 1, 1)
        self.editor = QTextEdit()
        r_vbox1 = QVBoxLayout()
        r_vbox1.addLayout(r_grid1)
        r_vbox1.addWidget(self.editor)

        # Logger
        log_label = QLabel('Log:')
        r_grid2 = QGridLayout()
        r_grid2.setColumnStretch(1, 1)
        r_grid2.addWidget(log_label, 0, 0, 1, 1)
        r_grid2.addWidget(QLabel(), 0, 1, 1, 1)
        self.logger = QTextEdit()
        self.logger.setReadOnly(True)
        r_vbox2 = QVBoxLayout()
        r_vbox2.addLayout(r_grid2)
        r_vbox2.addWidget(self.logger)

        r_vsplitter = QSplitter(QtCore.Qt.Vertical)
        ru_w = QWidget()
        ru_w.setLayout(r_vbox1)
        rb_w = QWidget()
        rb_w.setLayout(r_vbox2)
        r_vsplitter.addWidget(ru_w)
        r_vsplitter.addWidget(rb_w)

        # Metadata/conversion tab Layout
        self.left_w = QWidget()
        self.left_w.setLayout(self.l_vbox2)
        self.splitter = QSplitter(QtCore.Qt.Horizontal)
        self.splitter.addWidget(self.left_w)
        self.splitter.addWidget(r_vsplitter)

        self.metadata_layout = QVBoxLayout()
        self.metadata_layout.addWidget(self.splitter)
        self.tab_metadata = QWidget()
        self.tab_metadata.setLayout(self.metadata_layout)
        self.tabs.addTab(self.tab_metadata, 'Metadata/Conversion')

        # Background color
        p = self.palette()
        p.setColor(self.backgroundRole(), QtCore.Qt.white)
        self.setPalette(p)

    def init_nwb_explorer(self):
        """Initializes NWB file explorer tab"""
        # Layout Widgets
        self.btn_load_nwbexp = QPushButton('Load NWB')
        self.btn_load_nwbexp.setIcon(self.style().standardIcon(
            QStyle.SP_ArrowDown))
        self.btn_load_nwbexp.clicked.connect(self.load_nwb_explorer)
        self.btn_load_nwbexp.setToolTip("Choose NWB file to explore!")
        self.btn_close_nwbexp = QPushButton('Close')
        self.btn_close_nwbexp.setIcon(self.style().standardIcon(
            QStyle.SP_DialogCloseButton))
        self.btn_close_nwbexp.clicked.connect(self.close_nwb_explorer)
        self.btn_close_nwbexp.setToolTip("Close current file view.")
        self.html = QWebEngineView()

        self.grid_widgets = QGridLayout()
        self.grid_widgets.setColumnStretch(2, 1)
        self.grid_widgets.addWidget(self.btn_load_nwbexp, 0, 0, 1, 1)
        self.grid_widgets.addWidget(self.btn_close_nwbexp, 0, 1, 1, 1)
        self.grid_widgets.addWidget(QLabel(), 0, 2, 1, 1)
        self.vbox_widgets = QVBoxLayout()
        self.vbox_widgets.addLayout(self.grid_widgets)
        self.vbox_widgets.addWidget(self.html)

        # Layout Console
        console_label = QLabel('Ipython console:')
        self.explorer_console = ConsoleWidget(par=self)
        self.explorer_console.setToolTip("nwbfile --> NWB file data")

        self.grid_console = QGridLayout()
        self.grid_console.addWidget(console_label, 0, 0, 1, 1)
        self.grid_console.addWidget(self.explorer_console, 1, 0, 1, 1)

        hsplitter = QSplitter(QtCore.Qt.Horizontal)
        left_w = QWidget()
        left_w.setLayout(self.vbox_widgets)
        right_w = QWidget()
        right_w.setLayout(self.grid_console)
        hsplitter.addWidget(left_w)
        hsplitter.addWidget(right_w)

        # Add tab to GUI
        self.tabs.addTab(hsplitter, 'NWB widgets')

    def write_to_logger(self, txt):
        time = datetime.datetime.now().time().strftime("%H:%M:%S")
        full_txt = "[" + time + "]    " + txt
        self.logger.append(full_txt)

    def run_conversion(self):
        """Runs conversion function."""
        self.write_to_logger('Converting data to NWB... please wait.')
        self.toggle_enable_gui(enable=False)
        self.thread = ConversionFunctionThread(self)
        self.thread.finished.connect(
            lambda: self.finish_conversion(error=self.thread.error))
        self.thread.start()

    def finish_conversion(self, error):
        if error:
            self.write_to_logger('ERROR:')
            self.write_to_logger(str(error))
        else:
            self.write_to_logger('Data successfully converted to NWB.')
        self.toggle_enable_gui(enable=True)

    def toggle_enable_gui(self, enable):
        self.editor.setEnabled(enable)
        self.left_w.setEnabled(enable)

    def save_meta_file(self):
        """Saves metadata to .yml file."""
        filename, _ = QFileDialog.getSaveFileName(self, 'Save file', '',
                                                  "(*.yml);;(*.yaml)")
        if filename:
            data = {}
            for grp in self.groups_list:
                info, error = grp.read_fields()
                if error is None:
                    data[grp.group_type] = info
                else:
                    return
            with open(filename, 'w') as f:
                yaml.dump(data, f, default_flow_style=False)

    def read_metadata_from_form(self):
        """Loads metadata from form."""
        metadata = {}
        for grp in self.groups_list:
            info, error = grp.read_fields()
            if error is None:
                metadata[grp.group_type] = info
            else:
                return
        return metadata

    def form_to_editor(self):
        """Loads data from form to editor."""
        metadata = self.read_metadata_from_form()
        txt = yaml.dump(metadata, default_flow_style=False)
        self.editor.setText(txt)

    def update_kwargs(self, ind, key):
        """Updates the boolean values for keyword arguments."""
        chk_kw = getattr(self, 'chk_kwargs_' + str(ind))
        self.kwargs_fields[key] = chk_kw.isChecked()

    def load_source_files(self, ind, key):
        """Browser to source file location."""
        filenames, ftype = QFileDialog.getOpenFileNames(parent=self,
                                                        caption='Open file',
                                                        directory='',
                                                        filter="(*)")
        if len(filenames):
            all_names = ''
            for fname in filenames:
                all_names += fname + ', '
            lin_src = getattr(self, 'lin_src_' + str(ind))
            lin_src.setText(all_names[:-2])
            self.source_paths[key]['path'] = all_names[:-2]

    def load_source_dir(self, ind, key):
        """Browser to source directory location."""
        dirname = QFileDialog.getExistingDirectory(parent=self,
                                                   caption='Source directory',
                                                   directory='')
        if len(dirname):
            lin_src = getattr(self, 'lin_src_' + str(ind))
            lin_src.setText(dirname)
            self.source_paths[key]['path'] = dirname

    def load_meta_file(self, filename=None):
        """
        Opens (or browsers to) a .yml file containing metadata for NWB. Then:
        1. loads the internal variable self.metadata with the content
        2. writes content to editor
        3. updates forms
        """
        if filename is None:
            filename, ftype = QFileDialog.getOpenFileName(
                parent=self,
                caption='Open file',
                directory='',
                filter="(*.yml);;(*.yaml)")
            if ftype != '(*.yml)' or ftype != '(*.yaml)':
                return
        with open(filename) as f:
            self.metadata = yaml.safe_load(f)
        txt = yaml.dump(self.metadata, default_flow_style=False)
        self.editor.setText(txt)
        self.update_forms()

    def load_conversion_module(self):
        """Browser to conversion script file location."""
        filename, ftype = QFileDialog.getOpenFileName(parent=self,
                                                      caption='Open file',
                                                      directory='',
                                                      filter="(*py)")
        if filename != '':
            self.conversion_module_path = filename

    def load_nwb_file(self):
        """Browser to nwb file location."""
        filename, ftype = QFileDialog.getSaveFileName(parent=self,
                                                      caption='Save file',
                                                      directory='',
                                                      filter="(*nwb)")
        if filename is not None:
            self.lin_nwb_file.setText(filename)

    def load_nwb_explorer(self):
        """Browser to nwb file location."""
        filename, ftype = QFileDialog.getOpenFileName(parent=self,
                                                      caption='Load file',
                                                      directory='',
                                                      filter="(*nwb)")
        if filename != '':
            # Opens file on Ipython console
            self.run_console(fname=filename)
            # Opens file on NWBWidgets
            self.run_voila(fname=filename)

    def close_nwb_explorer(self):
        """Close current NWB file view on explorer"""
        if hasattr(self, 'voilathread'):
            # Stop Voila thread
            self.voilathread.stop()
            # Closes nwb file on console
            self.explorer_console._execute('io.close()', True)
            self.explorer_console.clear()

    def run_console(self, fname):
        """Loads NWB file on Ipython console"""
        # Imports extension modules
        imports_text = ""
        if self.extension_modules:
            for k, v in self.extension_modules.items():
                imports_text += "\nfrom " + k + " import " + ", ".join(v)
        code = """
            import pynwb
            import os
            """ + imports_text + """
            fpath = os.path.join(r'""" + str(fname) + """')
            io = pynwb.NWBHDF5IO(fpath, 'r', load_namespaces=True)
            nwbfile = io.read()
            """
        self.explorer_console._execute(code, True)
        self.explorer_console.clear()
        self.explorer_console.print_text('nwbfile --> Loaded NWB file\n')

    def run_voila(self, fname):
        """Set up notebook and run it with a dedicated Voila thread."""
        # Stop any current Voila thread
        self.close_nwb_explorer()
        # Write Figure + ipywidgets to a .ipynb file
        nb = nbf.v4.new_notebook()
        # Imports extension modules
        imports_text = ""
        if self.extension_modules:
            for k, v in self.extension_modules.items():
                imports_text += "\nfrom " + k + " import " + ", ".join(v)
        code = """
            from nwbwidgets import nwb2widget
            import pynwb
            import os
            """ + imports_text + """
            fpath = os.path.join(r'""" + str(fname) + """')
            io = pynwb.NWBHDF5IO(fpath, 'r', load_namespaces=True)
            nwb = io.read()
            nwb2widget(nwb)
            """
        nb['cells'] = [nbf.v4.new_code_cell(code)]
        nbpath = os.path.join(self.temp_dir, Path(fname).stem + '.ipynb')
        nbf.write(nb, nbpath)
        # Run instance of Voila with the just saved .ipynb file
        port = get_free_port()
        self.voilathread = voilaThread(parent=self, port=port, nbpath=nbpath)
        self.voilathread.start()
        # Load Voila instance on GUI
        self.update_html(url='http://localhost:' + str(port))
        # self.parent.write_to_logger(txt=self.name + " ready!")

    def update_html(self, url):
        """Loads temporary HTML file and render it."""
        self.html.load(QtCore.QUrl(url))
        self.html.show()

    def clean_groups(self):
        """Removes all groups widgets."""
        for grp in self.groups_list:
            nWidgetsVbox = self.l_vbox1.count()
            for i in range(nWidgetsVbox):
                if self.l_vbox1.itemAt(i) is not None:
                    if grp == self.l_vbox1.itemAt(i).widget():
                        self.l_vbox1.itemAt(i).widget().setParent(
                            None)  # deletes widget
        self.groups_list = []  # deletes all list items

    def update_forms(self):
        """Updates forms fields with values in metadata."""
        self.clean_groups()
        for grp in self.metadata:
            if grp == 'NWBFile':
                item = GroupNwbfile(parent=self,
                                    metadata=self.metadata['NWBFile'])
                item.write_fields(data=self.metadata['NWBFile'])
                self.groups_list.append(item)
                self.l_vbox1.addWidget(item)
            if grp == 'Subject':
                item = GroupSubject(parent=self)
                item.write_fields(data=self.metadata['Subject'])
                self.groups_list.append(item)
                self.l_vbox1.addWidget(item)
            if grp == 'Ophys':
                item = GroupOphys(self)
                for subgroup in self.metadata[grp]:
                    # if many items of same class, in list
                    if isinstance(self.metadata[grp][subgroup], list):
                        for subsub in self.metadata[grp][subgroup]:
                            item.add_group(
                                group=self.name_to_gui_class[subgroup](
                                    parent=item),
                                metadata=subsub)
                    else:  # if it's just one item of this class
                        item.add_group(group=self.name_to_gui_class[subgroup](
                            parent=item),
                                       metadata=self.metadata[grp][subgroup])
                self.groups_list.append(item)
                self.l_vbox1.addWidget(item)
            if grp == 'Ecephys':
                item = GroupEcephys(self)
                for subgroup in self.metadata[grp]:
                    # if many items of same class, in list
                    if isinstance(self.metadata[grp][subgroup], list):
                        for subsub in self.metadata[grp][subgroup]:
                            item.add_group(
                                group=self.name_to_gui_class[subgroup](
                                    parent=item),
                                metadata=subsub)
                    else:  # if it's just one item of this class
                        item.add_group(group=self.name_to_gui_class[subgroup](
                            parent=item),
                                       metadata=self.metadata[grp][subgroup])
                self.groups_list.append(item)
                self.l_vbox1.addWidget(item)
            if grp == 'Behavior':
                item = GroupBehavior(self)
                for subgroup in self.metadata[grp]:
                    # if many items of same class, in list
                    if isinstance(self.metadata[grp][subgroup], list):
                        for subsub in self.metadata[grp][subgroup]:
                            item.add_group(
                                group=self.name_to_gui_class[subgroup](
                                    parent=item),
                                metadata=subsub)
                    else:  # if it's just one item of this class
                        item.add_group(group=self.name_to_gui_class[subgroup](
                            parent=item),
                                       metadata=self.metadata[grp][subgroup])
                self.groups_list.append(item)
                self.l_vbox1.addWidget(item)
            if grp == 'Ogen':
                item = GroupOgen(self)
                for subgroup in self.metadata[grp]:
                    # if many items of same class, in list
                    if isinstance(self.metadata[grp][subgroup], list):
                        for subsub in self.metadata[grp][subgroup]:
                            item.add_group(
                                group=self.name_to_gui_class[subgroup](
                                    parent=item),
                                metadata=subsub)
                    else:  # if it's just one item of this class
                        item.add_group(group=self.name_to_gui_class[subgroup](
                            parent=item),
                                       metadata=self.metadata[grp][subgroup])
                self.groups_list.append(item)
                self.l_vbox1.addWidget(item)
        nItems = self.l_vbox1.count()
        self.l_vbox1.addStretch(nItems)

    def about(self):
        """About dialog."""
        msg = QMessageBox()
        msg.setWindowTitle("About NWB conversion")
        msg.setIcon(QMessageBox.Information)
        msg.setText("Version: 0.1.0 \n"
                    "GUI for NWB conversion and exploration.\n ")
        msg.setInformativeText(
            "<a href='https://github.com/catalystneuro/nwb-qt-gui'>NWBQtGUI Github page</a>"
        )
        msg.setStandardButtons(QMessageBox.Ok)
        msg.exec_()

    def closeEvent(self, event):
        """Before exiting, executes these actions."""
        # Stop any current Voila thread
        self.close_nwb_explorer()
        # Remove any remaining temporary directory/files
        shutil.rmtree(self.temp_dir, ignore_errors=False, onerror=None)
        event.accept()
class GroupOgen(QGroupBox):
    def __init__(self, parent):
        """Groupbox for Ogen module fields filling form."""
        super().__init__()
        self.setTitle('Ogen')
        self.group_type = 'Ogen'
        self.groups_list = []

        self.vbox1 = QVBoxLayout()
        self.vbox1.addStretch()

        self.grid = QGridLayout()
        self.grid.setColumnStretch(5, 1)
        self.grid.addLayout(self.vbox1, 2, 0, 1, 6)
        self.setLayout(self.grid)

    def add_group(self, group, metadata=None):
        """Adds group form."""
        if metadata is not None:
            group.write_fields(metadata=metadata)
        group.form_name.textChanged.connect(self.refresh_children)
        self.groups_list.append(group)
        nWidgetsVbox = self.vbox1.count()
        self.vbox1.insertWidget(nWidgetsVbox - 1,
                                group)  # insert before the stretch
        self.refresh_children(metadata=metadata)

    def is_referenced(self, grp_unique_name):
        """Tests if a group is being referenced any other groups. Returns boolean."""
        nWidgetsVbox = self.vbox1.count()
        for i in range(nWidgetsVbox):
            if self.vbox1.itemAt(i).widget() is not None:
                other_grp = self.vbox1.itemAt(i).widget()
                # check if this subgroup has any ComboBox referencing grp_unique_name
                for ch in other_grp.children():
                    if isinstance(ch, (CustomComboBox, QComboBox)):
                        if ch.currentText() == grp_unique_name:
                            return True
        return False

    def refresh_children(self, metadata=None):
        """Refreshes references with existing objects in child groups."""
        for child in self.groups_list:
            child.refresh_objects_references(metadata=metadata)

    def read_fields(self):
        """Reads fields and returns them structured in a dictionary."""
        error = None
        data = {}
        # group_type counts, if there are multiple groups of same type, they are saved in a list
        grp_types = [grp.group_type for grp in self.groups_list]
        grp_type_count = {
            value: len(list(freq))
            for value, freq in groupby(sorted(grp_types))
        }
        # initiate lists as values for groups keys with count > 1
        for k, v in grp_type_count.items():
            if v > 1 or k == 'Device' or k == 'OptogeneticStimulusSite' or k == 'OptogeneticSeries':
                data[k] = []
        # iterate over existing groups and copy their metadata
        for grp in self.groups_list:
            if grp_type_count[grp.group_type] > 1 or grp.group_type == 'Device' \
               or grp.group_type == 'OptogeneticStimulusSite' \
               or grp.group_type == 'OptogeneticSeries':
                data[grp.group_type].append(grp.read_fields())
            else:
                data[grp.group_type] = grp.read_fields()
        return data, error
class QGroundObjectMenu(QDialog):
    def __init__(
        self,
        parent,
        ground_object: TheaterGroundObject,
        buildings: Optional[List[TheaterGroundObject]],
        cp: ControlPoint,
        game: Game,
    ):
        super().__init__(parent)
        self.setMinimumWidth(350)
        self.ground_object = ground_object
        if buildings is None:
            self.buildings = []
        else:
            self.buildings = buildings
        self.cp = cp
        self.game = game
        self.setWindowTitle(
            f"Location - {self.ground_object.obj_name} ({self.cp.name})"
        )
        self.setWindowIcon(EVENT_ICONS["capture"])
        self.intelBox = QGroupBox("Units :")
        self.buildingBox = QGroupBox("Buildings :")
        self.intelLayout = QGridLayout()
        self.buildingsLayout = QGridLayout()
        self.sell_all_button = None
        self.total_value = 0
        self.init_ui()

    def init_ui(self):

        self.mainLayout = QVBoxLayout()
        self.budget = QBudgetBox(self.game)
        self.budget.setGame(self.game)

        self.doLayout()

        if isinstance(self.ground_object, BuildingGroundObject):
            self.mainLayout.addWidget(self.buildingBox)
            if self.cp.captured:
                self.mainLayout.addWidget(self.financesBox)
        else:
            self.mainLayout.addWidget(self.intelBox)

        self.actionLayout = QHBoxLayout()

        self.sell_all_button = QPushButton("Disband (+" + str(self.total_value) + "M)")
        self.sell_all_button.clicked.connect(self.sell_all)
        self.sell_all_button.setProperty("style", "btn-danger")

        self.buy_replace = QPushButton("Buy/Replace")
        self.buy_replace.clicked.connect(self.buy_group)
        self.buy_replace.setProperty("style", "btn-success")

        if self.ground_object.purchasable:
            if self.total_value > 0:
                self.actionLayout.addWidget(self.sell_all_button)
            self.actionLayout.addWidget(self.buy_replace)

        if self.cp.captured and self.ground_object.purchasable:
            self.mainLayout.addLayout(self.actionLayout)
        self.setLayout(self.mainLayout)

    def doLayout(self):

        self.update_total_value()
        self.intelBox = QGroupBox("Units :")
        self.intelLayout = QGridLayout()
        i = 0
        for g in self.ground_object.groups:
            if not hasattr(g, "units_losts"):
                g.units_losts = []
            for unit in g.units:
                unit_display_name = unit.type
                dcs_unit_type = vehicles.vehicle_map.get(unit.type)
                if dcs_unit_type is not None:
                    # Hack: Don't know which variant is used.
                    try:
                        unit_display_name = next(
                            GroundUnitType.for_dcs_type(dcs_unit_type)
                        ).name
                    except StopIteration:
                        pass
                self.intelLayout.addWidget(
                    QLabel(
                        "<b>Unit #"
                        + str(unit.id)
                        + " - "
                        + str(unit_display_name)
                        + "</b>"
                    ),
                    i,
                    0,
                )
                i = i + 1

            for unit in g.units_losts:
                dcs_unit_type = vehicles.vehicle_map.get(unit.type)
                if dcs_unit_type is None:
                    continue

                # Hack: Don't know which variant is used.

                try:
                    unit_type = next(GroundUnitType.for_dcs_type(dcs_unit_type))
                    name = unit_type.name
                    price = unit_type.price
                except StopIteration:
                    name = dcs_unit_type.name
                    price = 0

                self.intelLayout.addWidget(
                    QLabel(f"<b>Unit #{unit.id} - {name}</b> [DEAD]"), i, 0
                )
                if self.cp.captured:
                    repair = QPushButton(f"Repair [{price}M]")
                    repair.setProperty("style", "btn-success")
                    repair.clicked.connect(
                        lambda u=unit, g=g, p=unit_type.price: self.repair_unit(g, u, p)
                    )
                    self.intelLayout.addWidget(repair, i, 1)
                i = i + 1
        stretch = QVBoxLayout()
        stretch.addStretch()
        self.intelLayout.addLayout(stretch, i, 0)

        self.buildingBox = QGroupBox("Buildings :")
        self.buildingsLayout = QGridLayout()

        j = 0
        total_income = 0
        received_income = 0
        for i, building in enumerate(self.buildings):
            if building.dcs_identifier not in FORTIFICATION_BUILDINGS:
                self.buildingsLayout.addWidget(
                    QBuildingInfo(building, self.ground_object), j / 3, j % 3
                )
                j = j + 1

            if building.category in REWARDS.keys():
                total_income = total_income + REWARDS[building.category]
                if not building.is_dead:
                    received_income = received_income + REWARDS[building.category]
            else:
                logging.warning(building.category + " not in REWARDS")

        self.financesBox = QGroupBox("Finances: ")
        self.financesBoxLayout = QGridLayout()
        self.financesBoxLayout.addWidget(
            QLabel("Available: " + str(total_income) + "M"), 2, 1
        )
        self.financesBoxLayout.addWidget(
            QLabel("Receiving: " + str(received_income) + "M"), 2, 2
        )

        self.financesBox.setLayout(self.financesBoxLayout)
        self.buildingBox.setLayout(self.buildingsLayout)
        self.intelBox.setLayout(self.intelLayout)

    def do_refresh_layout(self):
        try:
            for i in range(self.mainLayout.count()):
                item = self.mainLayout.itemAt(i)
                if item is not None and item.widget() is not None:
                    item.widget().setParent(None)
            self.sell_all_button.setParent(None)
            self.buy_replace.setParent(None)
            self.actionLayout.setParent(None)

            self.doLayout()
            if isinstance(self.ground_object, BuildingGroundObject):
                self.mainLayout.addWidget(self.buildingBox)
            else:
                self.mainLayout.addWidget(self.intelBox)

            self.actionLayout = QHBoxLayout()
            if self.total_value > 0:
                self.actionLayout.addWidget(self.sell_all_button)
            self.actionLayout.addWidget(self.buy_replace)

            if self.cp.captured and self.ground_object.purchasable:
                self.mainLayout.addLayout(self.actionLayout)
        except Exception as e:
            logging.exception(e)
        self.update_total_value()

    def update_total_value(self):
        total_value = 0
        if not self.ground_object.purchasable:
            return
        for u in self.ground_object.units:
            # Hack: Unknown variant.
            unit_type = next(GroundUnitType.for_dcs_type(vehicles.vehicle_map[u.type]))
            total_value += unit_type.price
        if self.sell_all_button is not None:
            self.sell_all_button.setText("Disband (+$" + str(self.total_value) + "M)")
        self.total_value = total_value

    def repair_unit(self, group, unit, price):
        if self.game.budget > price:
            self.game.budget -= price
            group.units_losts = [u for u in group.units_losts if u.id != unit.id]
            group.units.append(unit)
            GameUpdateSignal.get_instance().updateGame(self.game)

            # Remove destroyed units in the vicinity
            destroyed_units = self.game.get_destroyed_units()
            for d in destroyed_units:
                p = Point(d["x"], d["z"])
                if p.distance_to_point(unit.position) < 15:
                    destroyed_units.remove(d)
                    logging.info("Removed destroyed units " + str(d))
            logging.info("Repaired unit : " + str(unit.id) + " " + str(unit.type))

        self.do_refresh_layout()

    def sell_all(self):
        self.update_total_value()
        self.game.budget = self.game.budget + self.total_value
        self.ground_object.groups = []

        # Replan if the tgo was a target of the redfor
        if any(
            package.target == self.ground_object
            for package in self.game.ato_for(player=False).packages
        ):
            self.game.initialize_turn(for_red=True, for_blue=False)

        self.do_refresh_layout()
        GameUpdateSignal.get_instance().updateGame(self.game)

    def buy_group(self):
        self.subwindow = QBuyGroupForGroundObjectDialog(
            self, self.ground_object, self.cp, self.game, self.total_value
        )
        self.subwindow.show()
class QGroundObjectMenu(QDialog):
    def __init__(
        self,
        parent,
        ground_object: TheaterGroundObject,
        cp: ControlPoint,
        game: Game,
    ):
        super().__init__(parent)
        self.setMinimumWidth(350)
        self.ground_object = ground_object
        self.cp = cp
        self.game = game
        self.setWindowTitle(
            f"Location - {self.ground_object.obj_name} ({self.cp.name})"
        )
        self.setWindowIcon(EVENT_ICONS["capture"])
        self.intelBox = QGroupBox("Units :")
        self.buildingBox = QGroupBox("Buildings :")
        self.orientationBox = QGroupBox("Orientation :")
        self.intelLayout = QGridLayout()
        self.buildingsLayout = QGridLayout()
        self.sell_all_button = None
        self.total_value = 0
        self.init_ui()

    def init_ui(self):

        self.mainLayout = QVBoxLayout()
        self.budget = QBudgetBox(self.game)
        self.budget.setGame(self.game)

        self.doLayout()

        if isinstance(self.ground_object, BuildingGroundObject):
            self.mainLayout.addWidget(self.buildingBox)
            if self.cp.captured:
                self.mainLayout.addWidget(self.financesBox)
        else:
            self.mainLayout.addWidget(self.intelBox)
            self.mainLayout.addWidget(self.orientationBox)

        self.actionLayout = QHBoxLayout()

        self.sell_all_button = QPushButton("Disband (+" + str(self.total_value) + "M)")
        self.sell_all_button.clicked.connect(self.sell_all)
        self.sell_all_button.setProperty("style", "btn-danger")

        self.buy_replace = QPushButton("Buy/Replace")
        self.buy_replace.clicked.connect(self.buy_group)
        self.buy_replace.setProperty("style", "btn-success")

        if self.ground_object.purchasable:
            if self.total_value > 0:
                self.actionLayout.addWidget(self.sell_all_button)
            self.actionLayout.addWidget(self.buy_replace)

        if self.cp.captured and self.ground_object.purchasable:
            self.mainLayout.addLayout(self.actionLayout)
        self.setLayout(self.mainLayout)

    def doLayout(self):

        self.update_total_value()
        self.intelBox = QGroupBox("Units :")
        self.intelLayout = QGridLayout()
        i = 0
        for g in self.ground_object.groups:
            for unit in g.units:
                self.intelLayout.addWidget(
                    QLabel(f"<b>Unit {str(unit.display_name)}</b>"), i, 0
                )

                if not unit.alive and unit.repairable and self.cp.captured:
                    price = unit.unit_type.price if unit.unit_type else 0
                    repair = QPushButton(f"Repair [{price}M]")
                    repair.setProperty("style", "btn-success")
                    repair.clicked.connect(
                        lambda u=unit, p=price: self.repair_unit(u, p)
                    )
                    self.intelLayout.addWidget(repair, i, 1)
                i += 1

        stretch = QVBoxLayout()
        stretch.addStretch()
        self.intelLayout.addLayout(stretch, i, 0)

        self.buildingBox = QGroupBox("Buildings :")
        self.buildingsLayout = QGridLayout()

        j = 0
        total_income = 0
        received_income = 0
        for static in self.ground_object.statics:
            if static not in FORTIFICATION_BUILDINGS:
                self.buildingsLayout.addWidget(
                    QBuildingInfo(static, self.ground_object), j / 3, j % 3
                )
                j = j + 1

            if self.ground_object.category in REWARDS.keys():
                total_income += REWARDS[self.ground_object.category]
                if static.alive:
                    received_income += REWARDS[self.ground_object.category]
            else:
                logging.warning(self.ground_object.category + " not in REWARDS")

        self.financesBox = QGroupBox("Finances: ")
        self.financesBoxLayout = QGridLayout()
        self.financesBoxLayout.addWidget(
            QLabel("Available: " + str(total_income) + "M"), 2, 1
        )
        self.financesBoxLayout.addWidget(
            QLabel("Receiving: " + str(received_income) + "M"), 2, 2
        )

        # Orientation Box
        self.orientationBox = QGroupBox("Orientation :")
        self.orientationBoxLayout = QHBoxLayout()

        heading_image = QLabel()
        heading_image.setPixmap(
            ICONS["heading"].transformed(
                QTransform().rotate(self.ground_object.heading.degrees)
            )
        )
        self.orientationBoxLayout.addWidget(heading_image)
        self.headingLabel = QLabel("Heading:")
        self.orientationBoxLayout.addWidget(self.headingLabel)
        self.headingSelector = QSpinBox()
        self.headingSelector.setEnabled(False)  # Disable for now
        self.headingSelector.setMinimum(0)
        self.headingSelector.setMaximum(360)
        self.headingSelector.setValue(self.ground_object.heading.degrees)
        self.orientationBoxLayout.addWidget(self.headingSelector)
        if self.cp.captured:
            # TODO Let the user choose the heading with the SpinBox
            self.headingSelector.setEnabled(False)
            self.head_to_conflict_button = QPushButton("Head to conflict")
            heading = (
                self.game.theater.heading_to_conflict_from(self.ground_object.position)
                or self.ground_object.heading
            )
            self.head_to_conflict_button.clicked.connect(
                lambda: self.rotate_tgo(heading)
            )
            self.orientationBoxLayout.addWidget(self.head_to_conflict_button)
        else:
            self.headingSelector.setEnabled(False)

        # Set the layouts
        self.financesBox.setLayout(self.financesBoxLayout)
        self.buildingBox.setLayout(self.buildingsLayout)
        self.intelBox.setLayout(self.intelLayout)
        self.orientationBox.setLayout(self.orientationBoxLayout)

    def do_refresh_layout(self):
        try:
            for i in reversed(range(self.mainLayout.count())):
                item = self.mainLayout.itemAt(i)
                if item is not None and item.widget() is not None:
                    item.widget().setParent(None)
            self.sell_all_button.setParent(None)
            self.buy_replace.setParent(None)
            self.actionLayout.setParent(None)

            self.doLayout()
            if isinstance(self.ground_object, BuildingGroundObject):
                self.mainLayout.addWidget(self.buildingBox)
            else:
                self.mainLayout.addWidget(self.intelBox)
                self.mainLayout.addWidget(self.orientationBox)

            self.actionLayout = QHBoxLayout()
            if self.total_value > 0:
                self.actionLayout.addWidget(self.sell_all_button)
            self.actionLayout.addWidget(self.buy_replace)

            if self.cp.captured and self.ground_object.purchasable:
                self.mainLayout.addLayout(self.actionLayout)
        except Exception as e:
            logging.exception(e)
        self.update_total_value()

    def update_total_value(self):
        if not self.ground_object.purchasable:
            return
        self.total_value = self.ground_object.value
        if self.sell_all_button is not None:
            self.sell_all_button.setText("Disband (+$" + str(self.total_value) + "M)")

    def repair_unit(self, unit, price):
        if self.game.blue.budget > price:
            self.game.blue.budget -= price
            unit.alive = True
            GameUpdateSignal.get_instance().updateGame(self.game)

            # Remove destroyed units in the vicinity
            destroyed_units = self.game.get_destroyed_units()
            for d in destroyed_units:
                p = Point(d["x"], d["z"], self.game.theater.terrain)
                if p.distance_to_point(unit.position) < 15:
                    destroyed_units.remove(d)
                    logging.info("Removed destroyed units " + str(d))
            logging.info(f"Repaired unit: {unit.unit_name}")

        self.update_game()

    def rotate_tgo(self, heading: Heading) -> None:
        self.ground_object.rotate(heading)
        self.do_refresh_layout()

    def sell_all(self):
        self.update_total_value()
        self.game.blue.budget += self.total_value
        self.ground_object.groups = []
        self.update_game()

    def buy_group(self) -> None:
        self.subwindow = QGroundObjectBuyMenu(
            self, self.ground_object, self.game, self.total_value
        )
        if self.subwindow.exec_():
            self.update_game()

    def update_game(self) -> None:
        events = GameUpdateEvents()
        events.update_tgo(self.ground_object)
        if any(
            package.target == self.ground_object
            for package in self.game.ato_for(player=False).packages
        ):
            # Replan if the tgo was a target of the redfor
            self.game.initialize_turn(events, for_red=True, for_blue=False)
        EventStream.put_nowait(events)
        GameUpdateSignal.get_instance().updateGame(self.game)
        # Refresh the dialog
        self.do_refresh_layout()