class PluginController(QFrame): def __init__(self, plugins, highlighted_plugins=None, parent=None): """ :type plugins: list of Plugin :type highlighted_plugins: list of Plugin """ super().__init__(parent) self.ui = Ui_FramePlugins() self.ui.setupUi(self) self.model = PluginListModel(plugins, highlighted_plugins=highlighted_plugins) self.ui.listViewPlugins.setModel(self.model) self.settings_layout = QVBoxLayout() self.ui.groupBoxSettings.setLayout(self.settings_layout) self.create_connects() def create_connects(self): self.ui.listViewPlugins.selectionModel().selectionChanged.connect( self.handle_list_selection_changed) def save_enabled_states(self): for plugin in self.model.plugins: constants.SETTINGS.setValue(plugin.name, plugin.enabled) @pyqtSlot() def handle_list_selection_changed(self): i = self.ui.listViewPlugins.currentIndex().row() self.ui.txtEditPluginDescription.setText( self.model.plugins[i].description) self.model.plugins[i].load_settings_frame() if self.settings_layout.count() > 0: self.settings_layout.takeAt(0).widget().setParent(None) self.settings_layout.addWidget(self.model.plugins[i].settings_frame)
class MainView(QWidget): def __init__(self): super().__init__() # Set id and styles self.setObjectName('MainView') # Set layout layout = QHBoxLayout(self) self.setLayout(layout) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) # Create SourceList widget self.source_list = SourceList() self.source_list.itemSelectionChanged.connect(self.on_source_changed) # Create widgets self.view_holder = QWidget() self.view_holder.setObjectName('MainView_view_holder') self.view_layout = QVBoxLayout() self.view_holder.setLayout(self.view_layout) # Add widgets to layout layout.addWidget(self.source_list) layout.addWidget(self.view_holder) def on_source_changed(self): self.view_layout.takeAt(0) self.view_layout.addWidget(SourceConversationWrapper())
class EventListWidget(QFrame): """ Class for the event list of the application. This class lists the next occurring events in the calendars. """ def __init__(self, parent, logger): self.logger = logger self.logger.info('Creating Event List Widget') super().__init__(parent) self.setFrameStyle(QFrame.Box) self.setLineWidth(2) self.event_counter = 0 self.events = [] self.layout = QVBoxLayout(self) def updateEvents(self, events): """ Function for updating Event List Widget with events """ self.logger.info('Updating all events') self.clearChildren() self.events = events for e in self.events: self.logger.debug(e) self.events.sort(key=lambda x: x.start_date) counter = 0 for event in self.events: e_widget = EventWidget(self, event) self.layout.addWidget(e_widget) counter += 1 if counter == MAX_WIDGET_AMOUT: break def clearChildren(self): """ Function for clearing all child objects """ while self.layout.takeAt(0): child = self.layout.takeAt(0) del child self.events.clear() def getEventDescription(self): """ Function for getting a single event for the event description. This event will be one of the first 5 events """ if len(self.events) <= self.event_counter: return None event = self.events[self.event_counter] self.event_counter = (self.event_counter + 1) % MAX_WIDGET_AMOUT return event
def initialize_context(self): reloading_layout = self.context_group.layout() if reloading_layout is None: reloading_layout = QVBoxLayout() self.context_group.setLayout(reloading_layout) else: while not reloading_layout.isEmpty(): # Our first item is always the button item = reloading_layout.takeAt(0) self.output_grid_layout.removeItem(item) if item.widget(): item.widget().deleteLater() self.name_label = QLabel() button_layout = QGridLayout() var_button = QPushButton("Local values") var_button.clicked.connect(self.show_locals) button_layout.addWidget(var_button, 0, 0) func_button = QPushButton("Functions") func_button.clicked.connect(self.show_local_funcs) button_layout.addWidget(func_button, 0, 1) out_button = QPushButton("Outputs") out_button.clicked.connect(self.show_outs) button_layout.addWidget(out_button, 1, 0) del_button = QPushButton("Delete") del_button.clicked.connect(self.delete_organ) button_layout.addWidget(del_button, 1, 1) reloading_layout.addWidget(self.name_label) reloading_layout.addLayout(button_layout)
class LocalButtonsConf(OptionsDialogGroupBox): def __init__(self, name, main): self.main = main OptionsDialogGroupBox.__init__(self, name, main) self.buttonBox = QGroupBox("Pins") self.buttonBoxLayout = QVBoxLayout() self.buttonBox.setLayout(self.buttonBoxLayout) def initUI(self): vbox = QVBoxLayout() self.polBox = QCheckBox("Invert") vbox.addWidget(self.polBox) self.buttongroup = QButtonGroup() self.buttongroup.setExclusive(False) vbox.addWidget(self.buttonBox) self.setLayout(vbox) def initButtons(self, num): #delete buttons self.num = num # Remove buttons for i in range(self.buttonBoxLayout.count()): b = self.buttonBoxLayout.takeAt(0) self.buttonBoxLayout.removeItem(b) b.widget().deleteLater() for b in self.buttongroup.buttons(): self.buttongroup.removeButton(b) self.buttonBox.update() for i in range(self.num): cb = QCheckBox(str(i + 1)) self.buttongroup.addButton(cb, i) self.buttonBoxLayout.addWidget(cb) def localcb(mask): for i in range(self.num): self.buttongroup.button(i).setChecked(mask & (1 << i)) self.main.comms.serialGetAsync("local_btnmask?", localcb, int) def apply(self): mask = 0 for i in range(self.num): if (self.buttongroup.button(i).isChecked()): mask |= 1 << i self.main.comms.serialWrite("local_btnmask=" + str(mask)) self.main.comms.serialWrite("local_btnpol=" + ("1" if self.polBox.isChecked() else "0")) def readValues(self): self.main.comms.serialGetAsync("local_btnpins?", self.initButtons, int) self.main.comms.serialGetAsync("local_btnpol?", self.polBox.setChecked, int)
class AnalogInputConf(OptionsDialogGroupBox): analogbtns = QButtonGroup() axes = 0 def __init__(self, name, main): self.main = main OptionsDialogGroupBox.__init__(self, name, main) self.analogbtns.setExclusive(False) self.buttonBox = QGroupBox("Pins") self.buttonBoxLayout = QVBoxLayout() self.buttonBox.setLayout(self.buttonBoxLayout) def initUI(self): layout = QVBoxLayout() self.autorangeBox = QCheckBox("Autorange") layout.addWidget(self.autorangeBox) layout.addWidget(self.buttonBox) self.setLayout(layout) def readValues(self): self.main.comms.serialGetAsync("local_ain_num?", self.createAinButtons, int) self.main.comms.serialGetAsync("local_ain_acal?", self.autorangeBox.setChecked, int) def createAinButtons(self, axes): self.axes = axes # remove buttons for i in range(self.buttonBoxLayout.count()): b = self.buttonBoxLayout.takeAt(0) self.buttonBoxLayout.removeItem(b) b.widget().deleteLater() for b in self.analogbtns.buttons(): self.analogbtns.removeButton(b) # add buttons for i in range(axes): btn = QCheckBox(str(i + 1), self) self.analogbtns.addButton(btn, i) self.buttonBoxLayout.addWidget(btn) def f(axismask): for i in range(self.axes): self.analogbtns.button(i).setChecked(axismask & (1 << i)) self.main.comms.serialGetAsync("local_ain_mask?", f, int) def apply(self): mask = 0 for i in range(self.axes): if (self.analogbtns.button(i).isChecked()): mask |= 1 << i self.main.comms.serialWrite("local_ain_mask=" + str(mask)) self.main.comms.serialWrite("local_ain_acal=" + ( "1" if self.autorangeBox.isChecked() else "0"))
class QScrollableBox(QWidget): def __init__(self, parent): super(QScrollableBox, self).__init__(parent) mainLayout = QVBoxLayout() mainLayout.setContentsMargins(0, 0, 0, 0) self.scrollArea = QScrollArea(self) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) self.scrollArea.setSizePolicy(sizePolicy) self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.scrollArea.setWidgetResizable(True) mainLayout.addWidget(self.scrollArea) self.setLayout(mainLayout) scrollContents = QWidget() self.m_layout = QVBoxLayout() self.m_layout.setContentsMargins(0, 0, 0, 0) self.m_layout.setSizeConstraint(QLayout.SetNoConstraint) scrollContents.setLayout(self.m_layout) self.scrollArea.setWidget(scrollContents) def addWidget(self, w): if not w: return count = self.m_layout.count() if count > 1: self.m_layout.removeItem(self.m_layout.itemAt(count - 1)) self.m_layout.addWidget(w) w.show() self.m_layout.addStretch() self.scrollArea.update() def removeWidget(self, w): self.m_layout.removeWidget(w) self.scrollArea.update() def insertWidget(self, i, w): self.m_layout.insertWidget(i, w) self.scrollArea.update() def clearWidgets(self): item = self.m_layout.takeAt(0) while item != None: item.widget().deleteLater() self.m_layout.removeItem(item) del item self.scrollArea.update() def indexOf(self, w): return self.m_layout.indexOf(w)
class MainView(QWidget): """ Represents the main content of the application (containing the source list and main context view). """ def __init__(self, parent): super().__init__(parent) self.layout = QHBoxLayout(self) self.setLayout(self.layout) left_column = QWidget(parent=self) left_layout = QVBoxLayout() left_column.setLayout(left_layout) self.status = QLabel(_('Waiting to Synchronize')) self.error_status = QLabel('') self.error_status.setObjectName('error_label') left_layout.addWidget(self.status) left_layout.addWidget(self.error_status) filter_widget = QWidget() filter_layout = QHBoxLayout() filter_widget.setLayout(filter_layout) filter_label = QLabel(_('Filter: ')) self.filter_term = QLineEdit() self.source_list = SourceList(left_column) filter_layout.addWidget(filter_label) filter_layout.addWidget(self.filter_term) left_layout.addWidget(filter_widget) left_layout.addWidget(self.source_list) self.layout.addWidget(left_column, 2) self.view_holder = QWidget() self.view_layout = QVBoxLayout() self.view_holder.setLayout(self.view_layout) self.layout.addWidget(self.view_holder, 6) def setup(self, controller): """ Pass through the controller object to this widget. """ self.controller = controller self.source_list.setup(controller) def update_error_status(self, error=None): self.error_status.setText(error) def update_view(self, widget): """ Update the view holder to contain the referenced widget. """ old_widget = self.view_layout.takeAt(0) if old_widget: old_widget.widget().setVisible(False) widget.setVisible(True) self.view_layout.addWidget(widget)
class PDFViewer(QScrollArea): def __init__(self, parent): super().__init__(parent) self.scrollAreaContents = QWidget() self.scrollAreaLayout = QVBoxLayout() self.setWidget(self.scrollAreaContents) self.setWidgetResizable(True) self.scrollAreaContents.setLayout(self.scrollAreaLayout) self.pixmapList = [] def resizeEvent(self, event): pass # do something about this later def update(self, pdf): self.render(pdf) self.clear() self.show() def render(self, pdf): """ Update the preview shown by rendering a new PDF and drawing it to the scroll area. """ self.pixmapList = [] pdfView = fitz.Document(stream=pdf, filetype='pdf') # render at 4x resolution and scale for page in pdfView: self.pixmapList.append(page.getPixmap(matrix=fitz.Matrix(4, 4), alpha=False)) def clear(self): while self.scrollAreaLayout.count(): item = self.scrollAreaLayout.takeAt(0) w = item.widget() if w: w.deleteLater() def show(self): for p in self.pixmapList: label = QLabel(parent=self.scrollAreaContents) label.setAlignment(Qt.AlignHCenter) qtimg = QImage(p.samples, p.width, p.height, p.stride, QImage.Format_RGB888) # -45 because of various margins... value obtained by trial and error. label.setPixmap(QPixmap.fromImage(qtimg).scaled(self.width()-45, self.height()*2, Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation)) self.scrollAreaLayout.addWidget(label) # necessary on Mojave with PyInstaller (or previous contents will be shown) self.repaint()
class ScrollBox(QVBoxLayout): def __init__(self, screen): if screen in window.screens: self.screen = screen super(ScrollBox, self).__init__(window.screens[screen]) else: self.screen = None self.sw = QWidget() self.layout = QVBoxLayout() for i in range(20): childLayout = QHBoxLayout() # childLayout.addWidget(tpb) c = QComboBox() c.addItems(["1", "2", "3"]) childLayout.addWidget(c) childLayout.addWidget(QPushButton("+")) childLayout.addWidget(QPushButton("-")) childLayout.addWidget(QPushButton("Edit")) self.layout.addLayout(childLayout) self.sw.setLayout(self.layout) self.scrollbox = QScrollArea() self.scrollbox.setWidget(self.sw) self.scrollbox.setWidgetResizable(True) self.scrollbox.setFixedHeight(400) self.addWidget(self.scrollbox) def updateContent(self): for i in reversed(range(self.layout.count())): # print(i) self.layout.takeAt(i).deleteLater()
class PluginFrame(QFrame): def __init__(self, plugins, highlighted_plugins=None, parent=None): """ :type plugins: list of Plugin :type highlighted_plugins: list of Plugin """ super().__init__(parent) self.ui = Ui_FramePlugins() self.ui.setupUi(self) self.model = PluginListModel(plugins, highlighted_plugins=highlighted_plugins) self.ui.listViewPlugins.setModel(self.model) self.settings_layout = QVBoxLayout() self.ui.groupBoxSettings.setLayout(self.settings_layout) self.create_connects() try: self.restoreGeometry( constants.SETTINGS.value("{}/geometry".format( self.__class__.__name__))) except TypeError: pass def create_connects(self): self.ui.listViewPlugins.selectionModel().selectionChanged.connect( self.on_list_selection_changed) for plugin in self.model.plugins: if hasattr(plugin, "show_proto_sniff_dialog_clicked"): plugin.show_proto_sniff_dialog_clicked.connect( self.parent().parent().show_proto_sniff_dialog) def save_enabled_states(self): for plugin in self.model.plugins: constants.SETTINGS.setValue(plugin.name, plugin.enabled) @pyqtSlot() def on_list_selection_changed(self): i = self.ui.listViewPlugins.currentIndex().row() self.ui.txtEditPluginDescription.setText( self.model.plugins[i].description) if self.settings_layout.count() > 0: widget = self.settings_layout.takeAt(0).widget() self.settings_layout.removeWidget(widget) widget.setParent(None) self.settings_layout.addWidget(self.model.plugins[i].settings_frame)
class PluginFrame(QFrame): def __init__(self, plugins, highlighted_plugins=None, parent=None): """ :type plugins: list of Plugin :type highlighted_plugins: list of Plugin """ super().__init__(parent) self.ui = Ui_FramePlugins() self.ui.setupUi(self) self.model = PluginListModel(plugins, highlighted_plugins=highlighted_plugins) self.ui.listViewPlugins.setModel(self.model) self.settings_layout = QVBoxLayout() self.ui.groupBoxSettings.setLayout(self.settings_layout) self.create_connects() try: self.restoreGeometry(constants.SETTINGS.value("{}/geometry".format(self.__class__.__name__))) except TypeError: pass def create_connects(self): self.ui.listViewPlugins.selectionModel().selectionChanged.connect(self.on_list_selection_changed) for plugin in self.model.plugins: if hasattr(plugin, "show_proto_sniff_dialog_clicked"): plugin.show_proto_sniff_dialog_clicked.connect(self.parent().parent().show_proto_sniff_dialog) def save_enabled_states(self): for plugin in self.model.plugins: constants.SETTINGS.setValue(plugin.name, plugin.enabled) @pyqtSlot() def on_list_selection_changed(self): i = self.ui.listViewPlugins.currentIndex().row() self.ui.txtEditPluginDescription.setText(self.model.plugins[i].description) if self.settings_layout.count() > 0: widget = self.settings_layout.takeAt(0).widget() self.settings_layout.removeWidget(widget) widget.setParent(None) self.settings_layout.addWidget(self.model.plugins[i].settings_frame)
class AttributeEditor(QScrollArea): def __init__(self, obj, title: str = 'Object', parent=None): super().__init__(parent) self.myObj = obj self.myTitle = title self.myDict = {title: obj} self.initUi() def initUi(self): self.myWidget = QWidget(self) self.myLayout = QVBoxLayout() self.myLayout.setContentsMargins(5, 5, 5, 5) self.myWidget.setLayout(self.myLayout) self.setWidget(self.myWidget) self.setWidgetResizable(True) def updateUi(self): while self.myLayout.count(): item = self.myLayout.takeAt(0) widget = item.widget() if widget is not None: widget.deleteLater() else: self.myLayout.removeItem(item) widget = AWFactory.createWidget(self.myDict, self.myTitle, self, self.dataChanged.emit) self.myLayout.addWidget(widget) self.myLayout.addStretch(0) dataChanged = pyqtSignal() def iterObj(self): if isinstance(self.myObj, dict): for name in self.myObj: yield (name) elif '__dict__' in dir(self.myObj): for name in vars(self.myObj): if name[0] != '_': yield (name) else: raise TypeError('Unsupported object with type ' + type(self.myObj))
class TagListWidget(QWidget): # TagLists are widgets which display all of the tags a user has added to an Image. def __init__(self, parent, tags=None): # Parent is a QWidget, tags is a list of strings super().__init__(parent) self.setFixedWidth(parent.width() / 8) ####### LAYOUT INITIALIZATION ####### # Widget needs a layout to be able to contain tags self.setLayout(QVBoxLayout()) # Need a container to restrict the size of the tag layout (layouts don't have width) tag_container = QWidget(self) tag_container.setFixedWidth(parent.width() / 8) # Need to be able to access the layout later to manipulate tags self.tag_layout = QVBoxLayout() self.tag_layout.setAlignment(Qt.AlignTop) tag_container.setLayout(self.tag_layout) # Add tags to widget as qlabels if tags: for tag in tags: t = QLabel(tag, self) self.tag_layout.addWidget(t) self.layout().addWidget(tag_container) def addTag(self, tag): t = QLabel(tag, self) self.tag_layout.addWidget(t) def updateTags(self, new_tags=None): while self.tag_layout.itemAt(0): tag = self.tag_layout.takeAt(0).widget() tag.deleteLater() for tag in new_tags: t = QLabel(tag, self) self.tag_layout.addWidget(t)
class MainView(QWidget): """ Represents the main content of the application (containing the source list and main context view). """ def __init__(self, parent): super().__init__(parent) self.layout = QHBoxLayout(self) self.setLayout(self.layout) left_column = QWidget(parent=self) left_layout = QVBoxLayout() left_column.setLayout(left_layout) self.status = QLabel(_('Waiting to Synchronize')) left_layout.addWidget(self.status) filter_widget = QWidget() filter_layout = QHBoxLayout() filter_widget.setLayout(filter_layout) filter_label = QLabel(_('Filter: ')) self.filter_term = QLineEdit() self.source_list = SourceList(left_column) filter_layout.addWidget(filter_label) filter_layout.addWidget(self.filter_term) left_layout.addWidget(filter_widget) left_layout.addWidget(self.source_list) self.layout.addWidget(left_column, 2) self.view_holder = QWidget() self.view_layout = QVBoxLayout() self.view_holder.setLayout(self.view_layout) self.layout.addWidget(self.view_holder, 6) def update_view(self, widget): """ Update the view holder to contain the referenced widget. """ old_widget = self.view_layout.takeAt(0) if old_widget: old_widget.widget().setVisible(False) widget.setVisible(True) self.view_layout.addWidget(widget)
class MyApp(QMainWindow): def __init__(self): super().__init__() self.add_btn = QPushButton("Add") self.add_btn.clicked.connect(self.click_add_btn) self.remove_btn = QPushButton("Remove") self.remove_btn.clicked.connect(self.click_remove_btn) self.scroll_area = QScrollArea() self.scroll_area.setWidgetResizable(True) self.init_ui() def init_ui(self): self.main_layout = QVBoxLayout() self.main_layout.addWidget(self.add_btn) self.main_layout.addWidget(self.remove_btn) self.main_layout.addWidget(TestUI()) self.centralWidget = QWidget() self.centralWidget.setLayout(self.main_layout) self.setCentralWidget(self.centralWidget) self.show() def click_add_btn(self): self.main_layout.addWidget(TestUI()) self.setFixedSize( self.main_layout.sizeHint() + PyQt5.QtCore.QSize(0, TestUI().frameGeometry().height()) ) def click_remove_btn(self): if self.main_layout.count() > 3: child = self.main_layout.takeAt(self.main_layout.count() - 1) child.widget().deleteLater() print(self.main_layout.count()) print(self.main_layout.sizeHint()) self.setFixedSize(self.main_layout.sizeHint())
class r2sGUI(QWidget): def __init__(self, sp_agent, reddit, redraw_func): super().__init__() self.sp_agent = sp_agent self.reddit = reddit self.redraw_func = redraw_func self.mvb = QVBoxLayout() self.subreddit_count = 0 self.config = None self.settings_file = f"{self.sp_agent.username}.ini" self.time_dictionary = self.create_time_dictionary() self.initialize_UI() def initialize_UI(self): config = configparser.ConfigParser() if os.path.isfile(self.settings_file): config.read(self.settings_file) self.config = config # should probably check that config file is valid (not corrupted) # load subreddits if not config.sections(): self.display_no_reddits() else: for section in config.sections(): subreddit_box = self.load_subreddit(config[section]) subreddit_box.delete_button.hide() # hide for now self.mvb.addWidget(subreddit_box) self.subreddit_count += 1 # add button add_widget = uic.loadUi("addsubreddit.ui") add_subreddit_button = add_widget.add_subreddit_button add_subreddit_button.clicked.connect(self.create_new_subreddit) self.mvb.addWidget(add_widget) add_widget.hide() # hide for now self.setLayout(self.mvb) self.setWindowTitle("reddit2Spotify") def resize_after_subreddit_count_change(self): # height = self.mvb.sizeHint().height() # width = self.mvb.sizeHint().width() # self.resize(height + 1, width + 1) # attempt to update label # self.resize(height, width) pass def display_no_reddits(self): # no_subreddits_m means no_subreddits_message username = self.sp_agent.username no_subreddits_m = f"No subreddits added for {username}." no_subreddits = QLabel(no_subreddits_m) no_subreddits.setObjectName("no_subreddits_label") self.mvb.addWidget(no_subreddits) def load_subreddit(self, section): # load ui from qtdesigner-made widget ss = uic.loadUi("singlesubreddit.ui") # subreddit name ss.subreddit_entry_textbox.setText(section["name"]) # sorting method dropdown # only show time sorting if "top" is selected def time_lambda(): self.allow_time(ss.sorting_method_combo, [ss.pre_time_label, ss.time_combo]) ss.sorting_method_combo.currentIndexChanged.connect(time_lambda) index = ss.sorting_method_combo.findText(section["sorting method"], QtCore.Qt.MatchFixedString) if index >= 0: ss.sorting_method_combo.setCurrentIndex(index) # flair text flair_placeholder_text = "Case sensitive. Leave blank for all posts." ss.post_flair_textbox.setPlaceholderText(flair_placeholder_text) ss.post_flair_textbox.setText(section["flair text"]) # new and existing playlist radio buttons def playlist_radio_toggled(): self.playlist_radio_toggled(ss) ss.new_pl_radio.toggled.connect(playlist_radio_toggled) ss.existing_pl_radio.toggled.connect(playlist_radio_toggled) pl_type = section["playlist type"] ss.new_pl_radio.setChecked(pl_type == "new") ss.existing_pl_radio.setChecked(pl_type == "existing") # save button # def save_button_clicked(): self.save_subreddit(ss) # ss.save_button.clicked.connect(save_button_clicked) # delete button def delete_button_clicked(): self.delete_subreddit(ss) ss.delete_button.clicked.connect(delete_button_clicked) # run button def run_button_clicked(): self.run_now(ss) ss.run_now_button.clicked.connect(run_button_clicked) # schedule button def schedule_button_clicked(): self.schedule(ss) ss.schedule_button.clicked.connect(schedule_button_clicked) ss.console.text_changed.connect(self.redraw_func) return ss @QtCore.pyqtSlot(object, list) def allow_time(self, sorting, to_be_hidden): index = sorting.currentIndex() if index == 0 or index == 4: # time_hb.show() for widget in to_be_hidden: widget.show() else: # time.setEnabled(False) for widget in to_be_hidden: widget.hide() @QtCore.pyqtSlot(QWidget) def playlist_radio_toggled(self, ss): if ss.new_pl_radio.isChecked(): # new # hide existing playlist stuff ss.existing_pl_id_label.hide() ss.existing_pl_id_textbox.hide() # show new playlist stuff ss.new_pl_name_label.show() ss.new_pl_name_textbox.show() ss.new_pl_desc_label.show() ss.new_pl_desc_textbox.show() else: # existing # show existing playlist stuff ss.existing_pl_id_label.show() ss.existing_pl_id_textbox.show() # hide new playlist stuff ss.new_pl_name_label.hide() ss.new_pl_name_textbox.hide() ss.new_pl_desc_label.hide() ss.new_pl_desc_textbox.hide() def create_new_subreddit(self): print("Creating new subreddit...") # remove add button add_button = self.mvb.takeAt(self.mvb.count() - 1).widget() # remove "no subreddit" label if there are currently no subreddits if self.subreddit_count == 0: no_subreddit_label = self.mvb.takeAt(self.mvb.count() - 1).widget() sip.delete(no_subreddit_label) # add new subreddit new_subreddit = self.load_subreddit(self.config["DEFAULT"]) self.mvb.addWidget(new_subreddit) self.subreddit_count += 1 # add add button back self.mvb.addWidget(add_button) self.resize_after_subreddit_count_change() @QtCore.pyqtSlot(QWidget) def save_subreddit(self, ss): section = ss.subreddit_entry_textbox.text().lower() if not section: # no subreddit name return cf = self.config cf.read(self.settings_file) try: cf.add_section(section) except configparser.DuplicateSectionError: pass cf[section]["name"] = section cf[section]["sorting method"] = ss.sorting_method_combo.currentText() # saves top time period even if not sorting by top cf[section]["top time period"] = ss.time_combo.currentText() cf[section]["flair text"] = ss.post_flair_textbox.text() pl_type = "new" if ss.new_pl_radio.isChecked() else "existing" cf[section]["playlist type"] = pl_type with open(self.settings_file, 'w') as configfile: cf.write(configfile) @QtCore.pyqtSlot(QWidget) def delete_subreddit(self, ss): print("Deleting subreddit...") self.mvb.removeWidget(ss) self.remove_subreddit_from_settings(ss) sip.delete(ss) # honestly have no clue what this is but it works self.subreddit_count -= 1 # add "no subreddits" label if we just deleted the last subreddit if self.subreddit_count == 0: add_button = self.mvb.takeAt(self.mvb.count() - 1).widget() self.display_no_reddits() self.mvb.addWidget(add_button) self.resize_after_subreddit_count_change() self.resize_after_subreddit_count_change() def remove_subreddit_from_settings(self, ss): section = ss.subreddit_entry_textbox.text().lower() cf = self.config cf.read(self.settings_file) deleted = cf.remove_section(section) if not deleted: print(f"Error deleting section {section}.", file=sys.stderr) with open(self.settings_file, 'w') as configfile: cf.write(configfile) def run_now(self, ss): print("Running subreddit settings now...") subreddit_name = ss.subreddit_entry_textbox.text() if not subreddit_name: # first one doesn't update UI, do it twice # not a great solution, but works for now ss.console.change_text("Error: No subreddit name", 1) ss.console.change_text("Error: No subreddit name", 1) self.resize_after_subreddit_count_change() return subreddit = self.reddit.subreddit(subreddit_name) self.save_subreddit(ss) playlist_id = None if ss.new_pl_radio.isChecked(): # new pl_name = ss.new_pl_name_textbox.text() if not pl_name: # no name given for new playlist pl_name = "r2s playlist for " + self.sp_agent.username pl_desc = ss.new_pl_desc_textbox.text() playlist = self.sp_agent.create_playlist(pl_name, pl_desc) playlist_id = playlist["id"] else: # existing playlist playlist_id = ss.existing_pl_id_textbox.text() print("Playlist ID:", playlist_id) fetching_message = f"Fetching posts..." ss.console.change_text(fetching_message, 0) sorting_method = ss.sorting_method_combo.currentText() time = self.time_dictionary[ss.time_combo.currentText()] posts = self.get_posts(subreddit, sorting_method, time) for post in posts: matches_flair_text = True flair = ss.post_flair_textbox.text() if flair: if post.link_flair_text != flair: matches_flair_text = False is_spotify = ("spotify" in post.url) is_track = ("track" in post.url) if matches_flair_text and is_spotify and is_track: valid_part = post.url.split('?')[0] ss.console.change_text(f"Adding {valid_part}", 0) self.sp_agent.add_songs_to_playlist([valid_part], playlist_id) # same as above, doing it twice so it updates UI ss.console.change_text("Ready", 0) ss.console.change_text("Ready", 0) def create_time_dictionary(self): return { "the past hour": "hour", "the past 24 hours": "day", "the past week": "week", "the past month": "month", "the past year": "year", "all time": "all" } # I feel like this function should exist in another module def get_posts(self, subreddit, sorting_method, time): if sorting_method == "top": return subreddit.top(time) elif sorting_method == "hot": return subreddit.hot() elif sorting_method == "new": return subreddit.new() elif sorting_method == "rising": return subreddit.rising() elif sorting_method == "controversial": return subreddit.controversial(time) else: print("Error: Invalid sorting method.", file=sys.stderr) pass def schedule(self, ss): print("Scheduling subreddit settings now...")
class FrameCheckOption(QWidget): def __init__(self, parent: QObject, controller: TouchManagerController, model: TouchManagerModel): super(QWidget, self).__init__() self.parent = parent self.model = model self.controller = controller self.main_lay = QVBoxLayout() self.bottom_lay = QVBoxLayout() self.scroll_wid = QWidget() self.scrollable = QScrollArea() self.lay = QVBoxLayout() self.lbls = [] self.lblsColors = [] self.lblImageColors = [] self.rBtns = [] self.btnAddCoord = QPushButton() self.aroundLbl = QLabel() self.cBoxAround = QComboBox() self.initMainUI() self.initUI({ 'coordinates': [[0.5, 0.5]], 'values': [[255, 255, 255]], 'around': 5, 'currentScreenColors': [[255, 255, 255]] }) self.initConnectors() def initMainUI(self): self.setLayout(self.main_lay) self.aroundLbl.setText("Around factor:") self.cBoxAround.addItems(str(i) for i in range(self.model.MAX_AROUND)) self.cBoxAround.setFixedHeight(20) self.cBoxAround.setMaximumWidth(100) self.cBoxAround.currentIndexChanged.connect( self.controller.requestChangeAround) self.btnAddCoord.setText("add coordinate") self.btnAddCoord.clicked.connect( self.controller.requestFrameCheckCoordAdd) self.lay.setAlignment(Qt.AlignTop) self.scroll_wid.setLayout(self.lay) self.scrollable.setWidgetResizable(True) self.scrollable.setWidget(self.scroll_wid) self.scrollable.setContentsMargins(0, 0, 0, 0) self.main_lay.addWidget(self.scrollable) self.bottom_lay.addWidget(self.btnAddCoord) h_lay = QHBoxLayout() h_lay.addWidget(self.aroundLbl) h_lay.addWidget(self.cBoxAround) self.bottom_lay.addLayout(h_lay) self.main_lay.addLayout(self.bottom_lay) def _clearLayout(self): self.lbls.clear() self.lbls = [] self.rBtns.clear() self.rBtns = [] self.lblsColors.clear() self.lblsColors = [] self.lblImageColors.clear() self.lblImageColors = [] for i in reversed(range(self.lay.count())): row_lay = self.lay.takeAt(i) for j in reversed(range(row_lay.count())): row_lay.itemAt(j).widget().setParent(None) row_lay.setParent(None) def _setAroundSafe(self, around): self.cBoxAround.blockSignals(True) self.cBoxAround.setCurrentIndex(around) self.cBoxAround.blockSignals(False) def initUI(self, newData: dict): if 'around' in newData: self._setAroundSafe(newData['around']) coords_num = len(newData['coordinates']) l = len(newData['currentScreenColors']) w, h = self.controller.current_image_size for i in range(coords_num): lay_row = QHBoxLayout() coord = newData['coordinates'][i] x, y = int((coord[0] * w)), int((coord[1] * h)) lbl_point = QLabel("%15s" % ("C%d: %4d , %4d" % (i, x, y))) lbl_point.setFixedWidth(80) lay_row.addWidget(lbl_point) # lay_row.addWidget(QLabel("X: %4d" % (coord[0] * w))) # lay_row.addWidget(QLabel("Y: %4d" % (coord[1] * h))) colors = newData['values'][i] lblColor = QLabel("") lblColor.setStyleSheet("background-color: rgb({},{},{});".format( colors[0], colors[1], colors[2])) lblColor.mousePressEvent = (partial(self.onManualChoose, i)) lblColor.setToolTip("Target value RGB=(%d, %d, %d)" % (colors[0], colors[1], colors[2])) lblColor.setMaximumWidth(40) self.lblsColors.append(lblColor) btnSet = QPushButton("set->") btnSet.setMaximumWidth(45) btnSet.clicked.connect( partial( self.controller.requestSetCurrentColorToFrameCheckColor, i)) lblimgColor = QLabel("") lblimgColor.setMaximumWidth(20) color_ = newData['currentScreenColors'][i] lblimgColor.setStyleSheet( "background-color: rgb({},{},{});".format( color_[0], color_[1], color_[2])) lblimgColor.setToolTip("Current screenshot RGB=(%d, %d, %d)" % (color_[0], color_[1], color_[2])) lblimgColor.setToolTipDuration(20 * 1000) self.lblImageColors.append(lblimgColor) lay_row.addWidget(lblimgColor) lay_row.addWidget(btnSet) lay_row.addWidget(lblColor) rbtn = QRadioButton() self.rBtns.append(rbtn) rbtn.setText("") rbtn.setFixedWidth(20) rbtn.toggled.connect( partial(self.controller.onCoordinateSelected, i)) lay_row.addWidget(rbtn) self.lay.addLayout(lay_row) if len(self.rBtns) > 0: self.rBtns[self.controller.selectedCoordinateIndex].blockSignals( True) self.rBtns[self.controller.selectedCoordinateIndex].setChecked( True) self.rBtns[self.controller.selectedCoordinateIndex].blockSignals( False) def onManualChoose(self, i, event): self.controller.rquestFrameCheckCoordinateColorManualChange(i) def updateCurrentColors(self, colors_img): num = min(len(colors_img), len(self.lblImageColors)) for i in range(num): color = colors_img[i] self.lblImageColors[i].setStyleSheet( "background-color: rgb({},{},{});".format( color[0], color[1], color[2])) def initConnectors(self): self.controller.onCurrentScreenColorsChanged.connect( self.updateCurrentColors) return # for i, rbtn in enumerate(self.rBtns): # self.rbtn.toggled.connect(partial(self.controller.onCoordinateSelected, i)) def changeData(self, new_data): self._clearLayout() self.initUI(new_data) if 'around' in new_data: if self.cBoxAround.currentIndex != new_data['around']: self._setAroundSafe(new_data['around']) def deleteLater(self): self.controller.onCurrentScreenColorsChanged.disconnect( self.updateCurrentColors) super(FrameCheckOption, self).deleteLater()
class RmsFrame(QFrame): def __init__(self, cu): super().__init__() self.cu = cu self.session = RaceSession() self.resetRMS() self.buildframe() self.driverBtn = {} self.driverObj = {} self.lapcount = {} self.totalTime = {} self.laptime = {} self.bestlaptime = {} self.fuelbar = {} self.pits = {} # QBAcolor = QByteArray() # QBAcolor.append('color') # self.animation = anim = QPropertyAnimation(self, QBAcolor, self) # anim.setDuration(250) # anim.setLoopCount(2) # anim.setStartValue(QColor(230,230, 0)) # anim.setEndValue(QColor(0, 0, 0)) # anim.setKeyValueAt(0.5, QColor(150,100,0)) def buildframe(self): self.vLayout = QVBoxLayout(self) self.hBtnLayout = QHBoxLayout() self.vLayout.addLayout(self.hBtnLayout) # Add driver to grid self.addDriverBtn = QPushButton('(A)dd Driver') self.addDriverKey = QShortcut(QKeySequence("a"), self) self.hBtnLayout.addWidget(self.addDriverBtn) self.addDriverBtn.clicked.connect(self.addDriver) self.addDriverKey.activated.connect(self.addDriver) # Assign Controller self.assignCtrlBtn = QPushButton('Assign Controller') self.hBtnLayout.addWidget(self.assignCtrlBtn) self.assignCtrlBtn.clicked.connect(self.openCtrlDialog) # Setup a race self.setupRace = QPushButton('Setup a Race') self.hBtnLayout.addWidget(self.setupRace) self.setupRace.clicked.connect(self.openRaceDlg) # Code cars self.codeBtn = QPushButton('(C)ode') self.codeKey = QShortcut(QKeySequence("c"), self) self.hBtnLayout.addWidget(self.codeBtn) self.codeBtn.clicked.connect(self.pressCode) self.codeKey.activated.connect(self.pressCode) # Start pace car self.paceBtn = QPushButton('(P)ace') self.paceKey = QShortcut(QKeySequence("p"), self) self.hBtnLayout.addWidget(self.paceBtn) self.paceBtn.clicked.connect(self.setPace) self.paceKey.activated.connect(self.setPace) # set Speed self.setSpeedBtn = QPushButton('Set (S)peed') self.setSpeedKey = QShortcut(QKeySequence("s"), self) self.hBtnLayout.addWidget(self.setSpeedBtn) self.setSpeedBtn.clicked.connect(self.setSpeed) self.setSpeedKey.activated.connect(self.setSpeed) # set Brakes self.setBrakeBtn = QPushButton('Set (B)rake') self.setBrakeKey = QShortcut(QKeySequence("b"), self) self.hBtnLayout.addWidget(self.setBrakeBtn) self.setBrakeBtn.clicked.connect(self.setBrake) self.setBrakeKey.activated.connect(self.setBrake) # Set Fuel self.setFuelBtn = QPushButton('Set (F)uel') self.setFuelKey = QShortcut(QKeySequence("f"), self) self.hBtnLayout.addWidget(self.setFuelBtn) self.setFuelBtn.clicked.connect(self.setFuel) self.setFuelKey.activated.connect(self.setFuel) # Reset CU self.resetBtn = QPushButton('(R)eset') self.resetKey = QShortcut(QKeySequence("r"), self) self.hBtnLayout.addWidget(self.resetBtn) self.resetBtn.clicked.connect(self.resetRMS) self.resetKey.activated.connect(self.resetRMS) # Start/Pause Race Enter self.startRaceBtn = QPushButton( 'Start Race or Enter changed Settings (Spacebar)') self.startRaceBtn.clicked.connect(self.racestart) self.spacekey = QShortcut(QKeySequence("Space"), self) self.spacekey.activated.connect(self.racestart) self.hStartBtnLayout = QHBoxLayout() self.hStartBtnLayout.addStretch(1) self.hStartBtnLayout.addWidget(self.startRaceBtn) self.hStartBtnLayout.setAlignment(self.startRaceBtn, Qt.AlignHCenter) self.hStartBtnLayout.addStretch(1) self.pitLaneStatus = QLabel() self.hStartBtnLayout.addWidget(QLabel('Pitlane')) self.hStartBtnLayout.addWidget(self.pitLaneStatus) self.fuelmode = QLabel() self.hStartBtnLayout.addWidget(QLabel('Fuel Mode')) self.hStartBtnLayout.addWidget(self.fuelmode) self.lapCounter = QLabel() self.hStartBtnLayout.addWidget(QLabel('Lap Counter')) self.hStartBtnLayout.addWidget(self.lapCounter) self.vLayout.addLayout(self.hStartBtnLayout) self.vLayout.setAlignment(self.hStartBtnLayout, Qt.AlignTop) # self.sepline = QFrame() # self.sepline.setFrameShape(QFrame.HLine) # self.sepline.setFrameShadow(QFrame.Sunken) # self.vLayout.addWidget(self.sepline) # self.vLayout.setAlignment(self.sepline, Qt.AlignTop) # Driver Grid self.vLayout.addLayout(self.buildGrid()) # Session Info self.racemode = QLabel('No Race Started') self.racemode.setAlignment(Qt.AlignCenter) self.racemode.setStyleSheet( "QLabel{ border-radius: 10px; background-color: grey; center; color: blue; font: 30pt}" ) self.vLayout.addWidget(self.racemode) self.vLayout.setAlignment(self.racemode, Qt.AlignBottom) def buildGrid(self): self.mainLayout = QGridLayout() self.mainLayout.setSpacing(10) self.mainLayout.setHorizontalSpacing(10) self.headerFont = QFont() self.headerFont.setPointSize(14) self.headerFont.setBold(True) self.labelArr = [ 'Pos', 'Driver', 'Total', 'Laps', 'Laptime', 'Best Lap', 'Fuel', 'Pits' ] for index, label in enumerate(self.labelArr): self.headerLabel = QLabel(label) self.headerLabel.setFont(self.headerFont) self.mainLayout.addWidget(self.headerLabel, 0, index, Qt.AlignHCenter) self.mainLayout.setColumnStretch(1, 1) self.mainLayout.setColumnStretch(2, 1) self.mainLayout.setColumnStretch(3, 2) self.mainLayout.setColumnStretch(4, 3) self.mainLayout.setColumnStretch(5, 3) self.mainLayout.setColumnStretch(6, 2) self.mainLayout.setColumnStretch(7, 1) return self.mainLayout def openCtrlDialog(self): self.ctrlDialog = CtrlDialog(self.driverArr) if self.ctrlDialog.exec_(): self.driverArr = self.ctrlDialog.newDriverArr def openRaceDlg(self): self.setupRaceDlg = RaceModeDialog() self.session.session = None self.session.type = None if self.setupRaceDlg.exec_(): for driver in self.driverArr: driver.bestLapTime = None driver.time = None driver.lapcount = 0 driver.pitcount = 0 self.session.setRace(self.setupRaceDlg.getRaceModeInfo()) self.racemode.setText(self.session.session + ' ' + str(self.session.amount) + ' ' + self.session.type) self.clearCU() self.cu.start() else: self.setupRaceDlg.close() def getColor(self): if self.mainLayout.itemAtPosition(1, 1): return self.mainLayout.itemAtPosition(1, 1).widget().palette().text() else: return None def setColor(self, color): PBwidget = self.mainLayout.itemAtPosition(1, 1).widget() palette = PBwidget.palette() palette.setColor(PBwidget.foregroundRole(), color) PBwidget.setFlat(True) PBwidget.setAutoFillBackground(True) PBwidget.setPalette(palette) color = pyqtProperty(QColor, getColor, setColor) def addDriver(self): driverRow = self.mainLayout.rowCount() if driverRow > 8: return driver = self.driverArr[driverRow - 1] self.posFont = QFont() self.posFont.setPointSize(35) self.posFont.setBold(True) self.driverPos = QLabel(str(driverRow)) self.driverPos.setStyleSheet( "QLabel{ border-radius: 10px; border-color: black; border: 5px solid black; background-color: white}" ) self.driverPos.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Expanding) self.driverPos.setFont(self.posFont) self.mainLayout.addWidget(self.driverPos, driverRow, 0) self.driverBtn[driverRow] = driver.getNameBtn() self.driverBtn[driverRow].setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.mainLayout.addWidget(self.driverBtn[driverRow], driverRow, 1) self.driverObj[driverRow] = driver self.driverBtn[driverRow].clicked.connect(lambda: self.changeDriver( self.driverBtn[driverRow], self.driverObj[driverRow])) self.lapcount[driverRow] = driver.getLapCountLCD() self.lapcount[driverRow].setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.mainLayout.addWidget(self.lapcount[driverRow], driverRow, 3) self.totalFont = QFont() self.totalFont.setPointSize(25) self.totalFont.setBold(True) self.totalTime[driverRow] = QLabel('00:00') self.totalTime[driverRow].setStyleSheet( "QLabel{ border-radius: 10px; border-color: black; border: 5px solid black; background-color: white}" ) self.totalTime[driverRow].setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.totalTime[driverRow].setFont(self.totalFont) self.mainLayout.addWidget(self.totalTime[driverRow], driverRow, 2) self.laptime[driverRow] = driver.getLapLCD() self.laptime[driverRow].setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.mainLayout.addWidget(self.laptime[driverRow], driverRow, 4) self.bestlaptime[driverRow] = driver.getBestLapLCD() self.bestlaptime[driverRow].setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.mainLayout.addWidget(self.bestlaptime[driverRow], driverRow, 5) self.fuelbar[driverRow] = driver.getFuelBar() self.fuelbar[driverRow].setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) self.mainLayout.addWidget(self.fuelbar[driverRow], driverRow, 6) self.pits[driverRow] = driver.getPits() self.pits[driverRow].setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.mainLayout.addWidget(self.pits[driverRow], driverRow, 7) def racestart(self): self.cu.start() # self.mainLayout.itemAtPosition(1, 5).widget().setPits('Pit') # self.mainLayout.itemAtPosition(1, 5).widget().setPits('Track') def resetRMS(self): if hasattr(self, 'driverArr'): for driverObj in self.driverArr: driverObj.deleteLater() self.start = None self.driverArr = [RmsDriver(num) for num in range(1, 9)] self.clearCU() if hasattr(self, 'mainLayout'): while True: widgetToRemove = self.mainLayout.takeAt(0) if widgetToRemove == None: break widgetToRemove.widget().deleteLater() racemode = self.vLayout.takeAt(3) mainItem = self.vLayout.takeAt(2) self.vLayout.removeItem(racemode) self.vLayout.removeItem(mainItem) mainItem.deleteLater() self.vLayout.addLayout(self.buildGrid()) self.vLayout.addWidget(self.racemode) def clearCU(self): # discard remaining timer messages status = self.cu.request() while not isinstance(status, ControlUnit.Status): status = self.cu.request() self.status = status # reset cu timer self.cu.reset() def pressCode(self): print('press Code') self.cu.request(b'T8') def setFuel(self): print('set fuel') self.cu.request(b'T7') def setPace(self): print('pace car') self.cu.request(b'T1') def setSpeed(self): print('set speed') self.cu.request(b'T5') def setBrake(self): print('set brake') self.cu.request(b'T6') def changeDriver(self, driverButton, driverObj): self.driverChangeText = QInputDialog.getText( self, 'Change driver name', 'Driver Name', 0, driverButton.text().split('\n')[0]) if self.driverChangeText[1] == True: driverButton.setText(self.driverChangeText[0] + '\n' + 'Ctrl: ' + str(driverObj.CtrlNum)) driverObj.name = self.driverChangeText[0] def updateDisplay(self, binMode): if binMode != None: if binMode[2] == '1': self.fuelmode.setText('Real') elif binMode[3] == '1': self.fuelmode.setText('On') elif binMode[3] == '0': self.fuelmode.setText('Off') if binMode[1] == '1': self.pitLaneStatus.setText('Exists') else: self.pitLaneStatus.setText('Missing') if binMode[0] == '1': self.lapCounter.setText('Exists') else: self.lapCounter.setText('Missing') driversInPlay = [driver for driver in self.driverArr if driver.time] if len(driversInPlay) + 1 > self.mainLayout.rowCount(): self.addDriver() for pos, driver in enumerate(sorted(driversInPlay, key=posgetter), start=1): if pos == 1: if hasattr(self, 'leader') and self.leader != driver: print('pos change') # self.animation.start() self.leader = driver t = formattime(driver.time - self.start, True) elif driver.lapcount == self.leader.lapcount: t = '+%ss' % formattime(driver.time - self.leader.time) else: gap = self.leader.lapcount - driver.lapcount t = '+%d Lap%s' % (gap, 's' if gap != 1 else '') self.driverBtn[pos].setText(driver.name + '\n' + 'Ctrl: ' + str(driver.CtrlNum)) self.totalTime[pos].setText(t) self.lapcount[pos].display(driver.lapcount) self.laptime[pos].display(formattime(driver.lapTime)) self.bestlaptime[pos].display(formattime(driver.bestLapTime)) self.fuelbar[pos].setValue(driver.fuellevel) if driver.fuellevel > 0: self.fuelbar[pos].setStyleSheet( "QProgressBar{ color: white; background-color: black; border: 5px solid black; border-radius: 10px; text-align: center}\ QProgressBar::chunk { background: qlineargradient(x1: 1, y1: 0.5, x2: 0, y2: 0.5, stop: 0 #00AA00, stop: " + str(0.92 - (1 / (driver.fuellevel))) + " #22FF22, stop: " + str(0.921 - (1 / (driver.fuellevel))) + " #22FF22, stop: " + str(1.001 - (1 / (driver.fuellevel))) + " red, stop: 1 #550000); }") self.pits[pos].display(driver.pitcount) if hasattr(self, 'leader') and self.session.session != None: if self.session.type != None: self.racemode.setText(self.session.session + ' ' + str(self.session.amount) + ' ' + self.session.type) if self.session.type == 'Laps': if self.leader.lapcount > self.session.amount: self.racestart() self.session.saveSessionData(driversInPlay) self.clearCU() self.session.sessionOver() elif self.session.type == 'Timed': if self.leader.time - self.start > self.session.amount * 60000: self.racestart() self.session.saveSessionData(driversInPlay) self.clearCU() self.session.sessionOver() elif self.session.type == None: self.session.session = None self.showLeaderboard() def showLeaderboard(self): self.leaderBoard = LBDialog(self.session.leaderboard) self.leaderBoard.show()
class AutoEditor(TableInfoChanger): """Ух, шайтан класс, в которых запихнули кишки из PathEditor, который основан на ViewShower, со всеми вытекающими для совместимости""" combo_update = ViewInfoChanger.combo_update def __init__(self, header, info, parent): super().__init__(header, info, parent) self.combo_change_idx = {"Водитель": {}} self.slave_drivers_layout = QVBoxLayout() push_btn = self.main_layout.takeAt(self.main_layout.count() - 1).widget() self.auto_id = info[0] add_btn = QPushButton("Добавить") add_btn.clicked.connect(lambda e: self.add_cell(-1)) self.main_layout.addWidget(add_btn) way = self.db.execute( f"SELECT CONCAT(`Фамилия`,' ', `Имя`,' ',`Отчество`), `назначение автомобиля водителю`.`Табельный номер` FROM `назначение автомобиля водителю` join `водитель` on `водитель`.`Табельный номер` = `назначение автомобиля водителю`.`Табельный номер` where `Номер автомобиля` = '{self.auto_id}'" ) for pos, point in enumerate(way, start=-1): auto_info = str(point[0]) self.combo_change_idx["Водитель"][auto_info] = point[1] self.add_cell(pos, auto_info) self.main_layout.addLayout(self.slave_drivers_layout) self.main_layout.addWidget(push_btn) def add_cell(self, pos: int, txt=""): """Вставляет ячейку ниже активирующей кнопки для вставки на уровне надо передать ::pos:: = -1""" cell = QHBoxLayout() edi = QLineEdit() edi.setText(txt) add_btn = QPushButton("Добавить") del_btn = QPushButton("Удалить") cmb = QComboBox() cmb.addItem(txt) edi.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) cmb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) add_btn.clicked.connect(lambda e, c=cell: self.add_cell(c.pos)) del_btn.clicked.connect(lambda e, c=cell: self.del_cell(c.pos)) edi.editingFinished.connect( lambda c=cmb, t=edi.text: self.combo_update("Водитель", c, t() )) # le-kostyl cell.pos = pos cell.addWidget(edi) cell.addWidget(cmb) cell.addWidget(add_btn) cell.addWidget(del_btn) for i in range(pos + 1, self.slave_drivers_layout.count()): cell_to_move = self.slave_drivers_layout.itemAt(i) cell_to_move.pos += 1 cell.pos += 1 # для вставки ниже активированной кнопки self.slave_drivers_layout.insertLayout(cell.pos, cell) def del_cell(self, pos): cell: QVBoxLayout cell = self.slave_drivers_layout.takeAt(pos) for i in range(cell.count()): w = cell.takeAt(0).widget() w.deleteLater() cell.deleteLater() for i in range(pos, self.slave_drivers_layout.count()): cell_to_move = self.slave_drivers_layout.itemAt(i) cell_to_move.pos -= 1 def push_point_changes(self): params = [] for i in range(self.slave_drivers_layout.count()): cell = self.slave_drivers_layout.itemAt(i) w = cell.itemAt(1).widget() driver = w.currentText() if driver: driver_id = self.combo_change_idx["Водитель"][driver] params.append((driver_id, self.auto_id)) query = f" insert into `назначение автомобиля водителю` values(%s, %s)" self.db.execute( "delete from `назначение автомобиля водителю` where `Номер автомобиля` = %s", (self.auto_id, )) self.db.executemany(query, params) self.db.commit() def push_changes(self): self.push_point_changes() super().push_changes()
class ConversationView(QWidget): """ Renders a conversation. """ def __init__(self, source_db_object: Source, sdc_home: str, controller: Client, parent=None): super().__init__(parent) self.source = source_db_object self.sdc_home = sdc_home self.controller = controller self.setStyleSheet("background-color: #fff;") self.container = QWidget() self.conversation_layout = QVBoxLayout() self.container.setLayout(self.conversation_layout) self.container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.scroll = QScrollArea() self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scroll.setWidget(self.container) self.scroll.setWidgetResizable(True) # Completely unintuitive way to ensure the view remains scrolled to the # bottom. sb = self.scroll.verticalScrollBar() sb.rangeChanged.connect(self.move_to_bottom) main_layout = QVBoxLayout() main_layout.addWidget(self.scroll) self.setLayout(main_layout) self.update_conversation(self.source.collection) def update_conversation(self, collection: list) -> None: # clear all old items while True: w = self.conversation_layout.takeAt(0) if w: # pragma: no cover del w else: break # add new items for conversation_item in collection: if conversation_item.filename.endswith('msg.gpg'): self.add_item_content_or(self.add_message, conversation_item, "<Message not yet downloaded>") elif conversation_item.filename.endswith('reply.gpg'): self.add_item_content_or(self.add_reply, conversation_item, "<Reply not yet downloaded>") else: self.add_file(self.source, conversation_item) def add_item_content_or(self, adder, item, default): """ Private helper function to add correct message to conversation widgets """ if item.is_downloaded is False: adder(item.uuid, default) else: adder(item.uuid, get_data(self.sdc_home, item.filename)) def add_file(self, source_db_object, submission_db_object): """ Add a file from the source. """ self.conversation_layout.addWidget( FileWidget(source_db_object, submission_db_object, self.controller)) def move_to_bottom(self, min_val, max_val): """ Handler called when a new item is added to the conversation. Ensures it's scrolled to the bottom and thus visible. """ self.scroll.verticalScrollBar().setValue(max_val) def add_message(self, message_id: str, message: str) -> None: """ Add a message from the source. """ self.conversation_layout.addWidget( MessageWidget(message_id, message, self.controller.message_sync.message_downloaded)) def add_reply(self, message_id: str, reply: str, files=None) -> None: """ Add a reply from a journalist. """ self.conversation_layout.addWidget( ReplyWidget(message_id, reply, self.controller.reply_sync.reply_downloaded))
class courseTab(QWidget): def __init__(self,parent_): super(courseTab,self).__init__(parent_) self.parent_=parent_ self.obj=parent_.obj self.initUI() def initUI(self): self.btn = [] self.lbl = [] self.cFrames =[] self.marker=0 self.d_=False if len(self.obj.courses) is 0: self.lbl.append(QLabel(self)) self.lbl[self.marker].setText('Your credentials have been verified but we couldn\'t configure you up. Wait for auto-update') self.lbl[self.marker].setObjectName("slbl") self.cFrames.append(errorFrame(self.lbl[self.marker])) for i1 in range(self.marker,len(self.obj.courses)): self.lbl.append(QLabel(self)) self.lbl[i1].setText(self.obj.courses[i1].c_name.upper()) self.lbl[i1].setObjectName("lbl") self.btn.append(QPushButton('FILES')) self.btn[i1].setObjectName("btn") self.btn[i1].id=i1 self.btn[i1].clicked.connect(partial(self.createItemTab,self.btn[i1].id)) for i2 in range(self.marker,len(self.obj.courses)): self.cFrames.append(courseFrames(self.lbl[i2],self.btn[i2])) self.marker=len(self.obj.courses) self.widget = QWidget(self) self.vbox = QVBoxLayout() self.vbox.setContentsMargins(0,0,0,0) self.vbox.setSpacing(3) for index, frame in enumerate(self.cFrames): if index%2==0: frame.setObjectName('cFrameEven') else: frame.setObjectName('cFrameOdd') self.vbox.addWidget(frame) if len(self.cFrames)<4: self.dframe = QFrame() self.dframe.setObjectName('nFrameDummy') self.vbox.addWidget(self.dframe) self.d_=True self.widget.setLayout(self.vbox) self.scroll = QScrollArea(self) self.scroll.setWidget(self.widget) self.scroll.setWidgetResizable(True) self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) vbox1 = QVBoxLayout() vbox1.setContentsMargins(0,0,0,0) vbox1.setSpacing(0) vbox1.addWidget(self.scroll) self.setLayout(vbox1) def createItemTab(self,id_): self.parent_.callItemTab(id_) def updater(self): if self.obj.dummy_courses is '': return else: if self.d_==True: child = self.vbox.takeAt(len(self.cFrames)) if child.widget() is not None: child.widget().deleteLater() elif child.layout() is not None: clearLayout(child.layout()) self.dframe.deleteLater() self.d_=False if self.marker==0: self.cFrames[0].deleteLater() self.lbl[0].deleteLater() child = self.vbox.takeAt(0) if child.widget() is not None: child.widget().deleteLater() elif child.layout() is not None: clearLayout(child.layout()) self.lbl.pop(0) self.cFrames.pop(0) for i1 in range(self.marker,len(self.obj.courses)): self.lbl.append(QLabel(self)) self.lbl[i1].setText(self.obj.courses[i1].c_name.upper()) self.lbl[i1].setObjectName("lbl") self.btn.append(QPushButton('FILES')) self.btn[i1].setObjectName("btn") self.btn[i1].id=i1 self.btn[i1].clicked.connect(partial(self.createItemTab,self.btn[i1].id)) for i2 in range(self.marker,len(self.obj.courses)): self.cFrames.append(courseFrames(self.lbl[i2],self.btn[i2])) for i3 in range (self.marker,len(self.cFrames)): if i3%2==0: self.cFrames[i3].setObjectName('cFrameEven') else: self.cFrames[i3].setObjectName('cFrameEven') self.vbox.addWidget(self.cFrames[i3]) if len(self.cFrames)<4: self.dframe = QFrame() self.dframe.setObjectName('nFrameDummy') self.vbox.addWidget(self.dframe) self.d_=True self.marker=self.marker+len(self.obj.dummy_courses)
class E5TextEditSearchWidget(QWidget): """ Class implementing a horizontal search widget for QTextEdit. """ def __init__(self, parent=None, widthForHeight=True): """ Constructor @param parent reference to the parent widget @type QWidget @param widthForHeight flag indicating to prefer width for height. If this parameter is False, some widgets are shown in a third line. @type bool """ super(E5TextEditSearchWidget, self).__init__(parent) self.__setupUi(widthForHeight) self.__textedit = None self.__texteditType = "" self.__findBackwards = True self.__defaultBaseColor = ( self.findtextCombo.lineEdit().palette().color(QPalette.Base) ) self.__defaultTextColor = ( self.findtextCombo.lineEdit().palette().color(QPalette.Text) ) self.findHistory = [] self.findtextCombo.setCompleter(None) self.findtextCombo.lineEdit().returnPressed.connect( self.__findByReturnPressed) self.__setSearchButtons(False) self.infoLabel.hide() self.setFocusProxy(self.findtextCombo) def __setupUi(self, widthForHeight): """ Private method to generate the UI. @param widthForHeight flag indicating to prefer width for height @type bool """ self.setObjectName("E5TextEditSearchWidget") self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setContentsMargins(0, 0, 0, 0) # row 1 of widgets self.horizontalLayout1 = QHBoxLayout() self.horizontalLayout1.setObjectName("horizontalLayout1") self.label = QLabel(self) self.label.setObjectName("label") self.label.setText(self.tr("Find:")) self.horizontalLayout1.addWidget(self.label) self.findtextCombo = E5ClearableComboBox(self) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.findtextCombo.sizePolicy().hasHeightForWidth()) self.findtextCombo.setSizePolicy(sizePolicy) self.findtextCombo.setMinimumSize(QSize(100, 0)) self.findtextCombo.setEditable(True) self.findtextCombo.setInsertPolicy(QComboBox.InsertAtTop) self.findtextCombo.setDuplicatesEnabled(False) self.findtextCombo.setObjectName("findtextCombo") self.horizontalLayout1.addWidget(self.findtextCombo) # row 2 (maybe) of widgets self.horizontalLayout2 = QHBoxLayout() self.horizontalLayout2.setObjectName("horizontalLayout2") self.caseCheckBox = QCheckBox(self) self.caseCheckBox.setObjectName("caseCheckBox") self.caseCheckBox.setText(self.tr("Match case")) self.horizontalLayout2.addWidget(self.caseCheckBox) self.wordCheckBox = QCheckBox(self) self.wordCheckBox.setObjectName("wordCheckBox") self.wordCheckBox.setText(self.tr("Whole word")) self.horizontalLayout2.addWidget(self.wordCheckBox) # layout for the navigation buttons self.horizontalLayout3 = QHBoxLayout() self.horizontalLayout3.setSpacing(0) self.horizontalLayout3.setObjectName("horizontalLayout3") self.findPrevButton = QToolButton(self) self.findPrevButton.setObjectName("findPrevButton") self.findPrevButton.setToolTip(self.tr( "Press to find the previous occurrence")) self.findPrevButton.setIcon(UI.PixmapCache.getIcon("1leftarrow.png")) self.horizontalLayout3.addWidget(self.findPrevButton) self.findNextButton = QToolButton(self) self.findNextButton.setObjectName("findNextButton") self.findNextButton.setToolTip(self.tr( "Press to find the next occurrence")) self.findNextButton.setIcon(UI.PixmapCache.getIcon("1rightarrow.png")) self.horizontalLayout3.addWidget(self.findNextButton) self.horizontalLayout2.addLayout(self.horizontalLayout3) # info label (in row 2 or 3) self.infoLabel = QLabel(self) self.infoLabel.setText("") self.infoLabel.setObjectName("infoLabel") # place everything together self.verticalLayout.addLayout(self.horizontalLayout1) self.__addWidthForHeightLayout(widthForHeight) self.verticalLayout.addWidget(self.infoLabel) QMetaObject.connectSlotsByName(self) self.setTabOrder(self.findtextCombo, self.caseCheckBox) self.setTabOrder(self.caseCheckBox, self.wordCheckBox) self.setTabOrder(self.wordCheckBox, self.findPrevButton) self.setTabOrder(self.findPrevButton, self.findNextButton) def setWidthForHeight(self, widthForHeight): """ Public method to set the 'width for height'. @param widthForHeight flag indicating to prefer width @type bool """ if self.__widthForHeight: self.horizontalLayout1.takeAt(self.__widthForHeightLayoutIndex) else: self.verticalLayout.takeAt(self.__widthForHeightLayoutIndex) self.__addWidthForHeightLayout(widthForHeight) def __addWidthForHeightLayout(self, widthForHeight): """ Private method to set the middle part of the layout. @param widthForHeight flag indicating to prefer width @type bool """ if widthForHeight: self.horizontalLayout1.addLayout(self.horizontalLayout2) self.__widthForHeightLayoutIndex = 2 else: self.verticalLayout.insertLayout(1, self.horizontalLayout2) self.__widthForHeightLayoutIndex = 1 self.__widthForHeight = widthForHeight def attachTextEdit(self, textedit, editType="QTextEdit"): """ Public method to attach a QTextEdit widget. @param textedit reference to the edit widget to be attached @type QTextEdit, QWebEngineView or QWebView @param editType type of the attached edit widget @type str (one of "QTextEdit", "QWebEngineView" or "QWebView") """ assert editType in ["QTextEdit", "QWebEngineView", "QWebView"] self.__textedit = textedit self.__texteditType = editType self.wordCheckBox.setVisible(editType == "QTextEdit") def keyPressEvent(self, event): """ Protected slot to handle key press events. @param event reference to the key press event (QKeyEvent) """ if self.__textedit and event.key() == Qt.Key_Escape: self.__textedit.setFocus(Qt.ActiveWindowFocusReason) event.accept() @pyqtSlot(str) def on_findtextCombo_editTextChanged(self, txt): """ Private slot to enable/disable the find buttons. @param txt text of the combobox (string) """ self.__setSearchButtons(txt != "") self.infoLabel.hide() self.__setFindtextComboBackground(False) def __setSearchButtons(self, enabled): """ Private slot to set the state of the search buttons. @param enabled flag indicating the state (boolean) """ self.findPrevButton.setEnabled(enabled) self.findNextButton.setEnabled(enabled) def __findByReturnPressed(self): """ Private slot to handle the returnPressed signal of the findtext combobox. """ self.__find(self.__findBackwards) @pyqtSlot() def on_findPrevButton_clicked(self): """ Private slot to find the previous occurrence. """ self.__find(True) @pyqtSlot() def on_findNextButton_clicked(self): """ Private slot to find the next occurrence. """ self.__find(False) def __find(self, backwards): """ Private method to search the associated text edit. @param backwards flag indicating a backwards search (boolean) """ if not self.__textedit: return self.infoLabel.clear() self.infoLabel.hide() self.__setFindtextComboBackground(False) txt = self.findtextCombo.currentText() if not txt: return self.__findBackwards = backwards # This moves any previous occurrence of this statement to the head # of the list and updates the combobox if txt in self.findHistory: self.findHistory.remove(txt) self.findHistory.insert(0, txt) self.findtextCombo.clear() self.findtextCombo.addItems(self.findHistory) if self.__texteditType == "QTextEdit": ok = self.__findPrevNextQTextEdit(backwards) self.__findNextPrevCallback(ok) elif self.__texteditType == "QWebEngineView": self.__findPrevNextQWebEngineView(backwards) def __findPrevNextQTextEdit(self, backwards): """ Private method to to search the associated edit widget of type QTextEdit. @param backwards flag indicating a backwards search @type bool @return flag indicating the search result @rtype bool """ if backwards: flags = QTextDocument.FindFlags(QTextDocument.FindBackward) else: flags = QTextDocument.FindFlags() if self.caseCheckBox.isChecked(): flags |= QTextDocument.FindCaseSensitively if self.wordCheckBox.isChecked(): flags |= QTextDocument.FindWholeWords ok = self.__textedit.find(self.findtextCombo.currentText(), flags) if not ok: # wrap around once cursor = self.__textedit.textCursor() if backwards: moveOp = QTextCursor.End # move to end of document else: moveOp = QTextCursor.Start # move to start of document cursor.movePosition(moveOp) self.__textedit.setTextCursor(cursor) ok = self.__textedit.find(self.findtextCombo.currentText(), flags) return ok def __findPrevNextQWebEngineView(self, backwards): """ Private method to to search the associated edit widget of type QWebEngineView. @param backwards flag indicating a backwards search @type bool """ from PyQt5.QtWebEngineWidgets import QWebEnginePage findFlags = QWebEnginePage.FindFlags() if self.caseCheckBox.isChecked(): findFlags |= QWebEnginePage.FindCaseSensitively if backwards: findFlags |= QWebEnginePage.FindBackward self.__textedit.findText(self.findtextCombo.currentText(), findFlags, self.__findNextPrevCallback) def __findNextPrevCallback(self, found): """ Private method to process the result of the last search. @param found flag indicating if the last search succeeded @type bool """ if not found: txt = self.findtextCombo.currentText() self.infoLabel.setText( self.tr("'{0}' was not found.").format(txt)) self.infoLabel.show() self.__setFindtextComboBackground(True) def __setFindtextComboBackground(self, error): """ Private slot to change the findtext combo background to indicate errors. @param error flag indicating an error condition (boolean) """ le = self.findtextCombo.lineEdit() p = le.palette() if error: p.setBrush(QPalette.Base, QBrush(QColor("#FF6666"))) p.setBrush(QPalette.Text, QBrush(QColor("#000000"))) else: p.setBrush(QPalette.Base, self.__defaultBaseColor) p.setBrush(QPalette.Text, self.__defaultTextColor) le.setPalette(p) le.update()
class PromptContainer(QWidget): """Container for prompts to be shown above the statusbar. This is a per-window object, however each window shows the same prompt. Attributes: _layout: The layout used to show prompts in. _win_id: The window ID this object is associated with. Signals: update_geometry: Emitted when the geometry should be updated. """ STYLESHEET = """ QWidget#PromptContainer { {% if conf.statusbar.position == 'top' %} border-bottom-left-radius: {{ conf.prompt.radius }}px; border-bottom-right-radius: {{ conf.prompt.radius }}px; {% else %} border-top-left-radius: {{ conf.prompt.radius }}px; border-top-right-radius: {{ conf.prompt.radius }}px; {% endif %} } QWidget { font: {{ conf.fonts.prompts }}; color: {{ conf.colors.prompts.fg }}; background-color: {{ conf.colors.prompts.bg }}; } QLineEdit { border: {{ conf.colors.prompts.border }}; } QTreeView { selection-background-color: {{ conf.colors.prompts.selected.bg }}; border: {{ conf.colors.prompts.border }}; } QTreeView::branch { background-color: {{ conf.colors.prompts.bg }}; } QTreeView::item:selected, QTreeView::item:selected:hover, QTreeView::branch:selected { background-color: {{ conf.colors.prompts.selected.bg }}; } """ update_geometry = pyqtSignal() def __init__(self, win_id, parent=None): super().__init__(parent) self._layout = QVBoxLayout(self) self._layout.setContentsMargins(10, 10, 10, 10) self._win_id = win_id self._prompt: Optional[_BasePrompt] = None self.setObjectName('PromptContainer') self.setAttribute(Qt.WA_StyledBackground, True) stylesheet.set_register(self) message.global_bridge.prompt_done.connect(self._on_prompt_done) prompt_queue.show_prompts.connect(self._on_show_prompts) message.global_bridge.mode_left.connect(self._on_global_mode_left) def __repr__(self): return utils.get_repr(self, win_id=self._win_id) @pyqtSlot(usertypes.Question) def _on_show_prompts(self, question): """Show a prompt for the given question. Args: question: A Question object or None. """ item = self._layout.takeAt(0) if item is not None: widget = item.widget() log.prompt.debug("Deleting old prompt {}".format(widget)) widget.hide() widget.deleteLater() if question is None: log.prompt.debug("No prompts left, hiding prompt container.") self._prompt = None self.hide() return classes = { usertypes.PromptMode.yesno: YesNoPrompt, usertypes.PromptMode.text: LineEditPrompt, usertypes.PromptMode.user_pwd: AuthenticationPrompt, usertypes.PromptMode.download: DownloadFilenamePrompt, usertypes.PromptMode.alert: AlertPrompt, } klass = classes[question.mode] prompt = klass(question) log.prompt.debug("Displaying prompt {}".format(prompt)) self._prompt = prompt # If this question was interrupted, we already connected the signal if not question.interrupted: question.aborted.connect( functools.partial(self._on_aborted, prompt.KEY_MODE)) modeman.enter(self._win_id, prompt.KEY_MODE, 'question asked') self.setSizePolicy(prompt.sizePolicy()) self._layout.addWidget(prompt) prompt.show() self.show() prompt.setFocus() self.update_geometry.emit() @pyqtSlot() def _on_aborted(self, key_mode): """Leave KEY_MODE whenever a prompt is aborted.""" try: modeman.leave(self._win_id, key_mode, 'aborted', maybe=True) except objreg.RegistryUnavailableError: # window was deleted: ignore pass @pyqtSlot(usertypes.KeyMode) def _on_prompt_done(self, key_mode): """Leave the prompt mode in this window if a question was answered.""" modeman.leave(self._win_id, key_mode, ':prompt-accept', maybe=True) @pyqtSlot(usertypes.KeyMode) def _on_global_mode_left(self, mode): """Leave prompt/yesno mode in this window if it was left elsewhere. This ensures no matter where a prompt was answered, we leave the prompt mode and dispose of the prompt object in every window. """ if mode not in [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]: return modeman.leave(self._win_id, mode, 'left in other window', maybe=True) item = self._layout.takeAt(0) if item is not None: widget = item.widget() log.prompt.debug("Deleting prompt {}".format(widget)) widget.hide() widget.deleteLater() @cmdutils.register(instance='prompt-container', scope='window', modes=[usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]) def prompt_accept(self, value=None, *, save=False): """Accept the current prompt. // This executes the next action depending on the question mode, e.g. asks for the password or leaves the mode. Args: value: If given, uses this value instead of the entered one. For boolean prompts, "yes"/"no" are accepted as value. save: Save the value to the config. """ assert self._prompt is not None question = self._prompt.question try: done = self._prompt.accept(value, save=save) except Error as e: raise cmdutils.CommandError(str(e)) if done: message.global_bridge.prompt_done.emit(self._prompt.KEY_MODE) question.done() @cmdutils.register(instance='prompt-container', scope='window', modes=[usertypes.KeyMode.prompt], maxsplit=0) def prompt_open_download(self, cmdline: str = None, pdfjs: bool = False) -> None: """Immediately open a download. If no specific command is given, this will use the system's default application to open the file. Args: cmdline: The command which should be used to open the file. A `{}` is expanded to the temporary file name. If no `{}` is present, the filename is automatically appended to the cmdline. pdfjs: Open the download via PDF.js. """ assert self._prompt is not None try: self._prompt.download_open(cmdline, pdfjs=pdfjs) except UnsupportedOperationError: pass @cmdutils.register(instance='prompt-container', scope='window', modes=[usertypes.KeyMode.prompt]) @cmdutils.argument('which', choices=['next', 'prev']) def prompt_item_focus(self, which): """Shift the focus of the prompt file completion menu to another item. Args: which: 'next', 'prev' """ assert self._prompt is not None try: self._prompt.item_focus(which) except UnsupportedOperationError: pass @cmdutils.register( instance='prompt-container', scope='window', modes=[usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]) def prompt_yank(self, sel=False): """Yank URL to clipboard or primary selection. Args: sel: Use the primary selection instead of the clipboard. """ assert self._prompt is not None question = self._prompt.question if question.url is None: message.error('No URL found.') return if sel and utils.supports_selection(): target = 'primary selection' else: sel = False target = 'clipboard' utils.set_clipboard(question.url, sel) message.info("Yanked to {}: {}".format(target, question.url))
class ChapterAddWidget(QWidget): CHAPTERS = pyqtSignal(dict) def __init__(self, gallery, parent=None): super().__init__(parent) self.setWindowFlags(Qt.Window) self.current_chapters = len(gallery.chapters) self.added_chaps = 0 layout = QFormLayout() self.setLayout(layout) lbl = QLabel('{} by {}'.format(gallery.title, gallery.artist)) layout.addRow('Gallery:', lbl) layout.addRow('Current chapters:', QLabel('{}'.format(self.current_chapters))) new_btn = QPushButton('New') new_btn.clicked.connect(self.add_new_chapter) new_btn.adjustSize() add_btn = QPushButton('Finish') add_btn.clicked.connect(self.finish) add_btn.adjustSize() new_l = QHBoxLayout() new_l.addWidget(add_btn, alignment=Qt.AlignLeft) new_l.addWidget(new_btn, alignment=Qt.AlignRight) layout.addRow(new_l) frame = QFrame() frame.setFrameShape(frame.StyledPanel) layout.addRow(frame) self.chapter_l = QVBoxLayout() frame.setLayout(self.chapter_l) new_btn.click() self.setMaximumHeight(550) self.setFixedWidth(500) if parent: self.move(parent.window().frameGeometry().topLeft() + parent.window().rect().center() - self.rect().center()) else: frect = self.frameGeometry() frect.moveCenter(QDesktopWidget().availableGeometry().center()) self.move(frect.topLeft()) self.setWindowTitle('Add Chapters') def add_new_chapter(self): chap_layout = QHBoxLayout() self.added_chaps += 1 curr_chap = self.current_chapters+self.added_chaps chp_numb = QSpinBox(self) chp_numb.setMinimum(1) chp_numb.setValue(curr_chap) curr_chap_lbl = QLabel('Chapter {}'.format(curr_chap)) def ch_lbl(n): curr_chap_lbl.setText('Chapter {}'.format(n)) chp_numb.valueChanged[int].connect(ch_lbl) chp_path = PathLineEdit() chp_path.folder = True chp_path.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) chp_path.setPlaceholderText('Right/Left-click to open folder explorer.'+ ' Leave empty to not add.') chap_layout.addWidget(chp_path, 3) chap_layout.addWidget(chp_numb, 0) self.chapter_l.addWidget(curr_chap_lbl, alignment=Qt.AlignLeft) self.chapter_l.addLayout(chap_layout) def finish(self): chapters = {} widgets = [] x = True while x: x = self.chapter_l.takeAt(0) if x: widgets.append(x) for l in range(1, len(widgets), 1): layout = widgets[l] try: line_edit = layout.itemAt(0).widget() spin_box = layout.itemAt(1).widget() except AttributeError: continue p = line_edit.text() c = spin_box.value() - 1 # because of 0-based index if os.path.exists(p): chapters[c] = p self.CHAPTERS.emit(chapters) self.close()
class DangerConfEditor(ViewInfoChanger): def __init__(self, header, info, parent: ViewShower): super().__init__(header, info, parent) self.way_layout = QVBoxLayout() push_btn = self.main_layout.takeAt(self.main_layout.count() - 1).widget() self.worker_id = info[0] self.slave_combo_config = { "название_параметра": ("параметры", "*", "название_параметра", "код_параметра") } self.combo_change_idx["название_параметра"] = {} add_btn = QPushButton("Добавить") add_btn.clicked.connect(lambda e: self.add_cell(-1)) self.main_layout.addWidget(add_btn) way = self.db.execute( f"SELECT код_параметра, название_параметра, нижний_допустимый_порог, верхний_допустимый_порог FROM `подчинённые_допустимые_значения_параметров_view` where `код_категории_вредности` = {self.worker_id}" ) for pos, point in enumerate(way, start=-1): param_name = str(point[1]) self.combo_change_idx["название_параметра"][param_name] = point[0] self.add_cell(pos, param_name, point[2], point[3]) self.main_layout.addLayout(self.way_layout) self.main_layout.addWidget(push_btn) def add_cell(self, pos: int, txt="", dnw_val="", up_val=""): """Вставляет ячейку ниже активирующей кнопки для вставки на уровне надо передать ::pos:: = -1""" cell = QHBoxLayout() edi = QLineEdit() edi.setText(txt) dwn_val_edt = QLineEdit() dwn_val_edt.setText(str(dnw_val)) up_val_edt = QLineEdit(str(up_val)) add_btn = QPushButton("Добавить") del_btn = QPushButton("Удалить") cmb = QComboBox() cmb.addItem(txt) edi.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) cmb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) dwn_val_edt.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) up_val_edt.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) add_btn.clicked.connect(lambda e, c=cell: self.add_cell(c.pos)) del_btn.clicked.connect(lambda e, c=cell: self.del_cell(c.pos)) edi.editingFinished.connect( lambda c=cmb, t=edi.text: self.slave_combo_update( "название_параметра", c, t())) # le-kostyl cell.pos = pos cell.addWidget(edi) cell.addWidget(cmb) cell.addWidget(dwn_val_edt) cell.addWidget(up_val_edt) cell.addWidget(add_btn) cell.addWidget(del_btn) for i in range(pos + 1, self.way_layout.count()): cell_to_move = self.way_layout.itemAt(i) cell_to_move.pos += 1 cell.pos += 1 # для вставки ниже активированной кнопки self.way_layout.insertLayout(cell.pos, cell) def slave_combo_update(self, c_name: str, c: QComboBox, text: str): # grand le-kostyl tmp = self.combo_config self.combo_config = self.slave_combo_config self.combo_update(c_name, c, text) self.combo_config = tmp def del_cell(self, pos): cell: QVBoxLayout cell = self.way_layout.takeAt(pos) for i in range(cell.count()): w = cell.takeAt(0).widget() w.deleteLater() cell.deleteLater() for i in range(pos, self.way_layout.count()): cell_to_move = self.way_layout.itemAt(i) cell_to_move.pos -= 1 def push_slave_changes(self): params = [] kakoito_set = set() try: for i in range(self.way_layout.count()): cell = self.way_layout.itemAt(i) w = cell.itemAt(1).widget() param = w.currentText() if param: if param in kakoito_set: raise KeyError kakoito_set.add(param) param_id = self.combo_change_idx["название_параметра"][ param] dwn_w = cell.itemAt(2).widget() up_w = cell.itemAt(3).widget() dwn_val = dwn_w.text() up_val = up_w.text() if not dwn_val: dwn_val = 0 if not up_val: up_val = 0 params.append((self.worker_id, param_id, dwn_val, up_val)) query = f" insert into `допустимые_значения_параметров` values(%s, %s, %s, %s)" self.db.execute( "delete from `допустимые_значения_параметров` where код_категории_вредности = %s", (self.worker_id, )) self.db.executemany(query, params) self.db.commit() except KeyError as er: from PyQt5.QtWidgets import QErrorMessage error_widget = QErrorMessage() error_widget.showMessage(f"Дубликат параметра") error_widget.exec() def push_changes(self): self.push_slave_changes() super().push_changes()
class ApikeysManagerWindow(QWidget): def __init__(self, parent: QWidget = None): super(ApikeysManagerWindow, self).__init__(parent=parent) self._logger = get_logger(__name__) self._logger.debug('Constructed window!') self.mainwindow = None self.emcore = get_core_instance() self.setMinimumSize(500, 200) self.icon = QIcon('img/pyevemon.png') self.setWindowIcon(self.icon) self.setWindowTitle(self.tr('API Keys Manager')) self._layout = QVBoxLayout() self.setLayout(self._layout) # labels self._lbl_apikeys = QLabel(self.tr('API keys:'), self) # buttons self._btn_add_apikey = QPushButton(self.tr('Add API key...'), self) self._btn_add_apikey.clicked.connect(self.on_click_add_apikey) # layouts self._layout_top1 = QHBoxLayout() self._layout_top1.addWidget(self._lbl_apikeys) self._layout_top1.addStretch() self._layout_top1.addWidget(self._btn_add_apikey) self._layout.addLayout(self._layout_top1, 0) self._layout.setSizeConstraint(QLayout.SetMinimumSize) self.load_apikeys() self.show() # void QWidget::closeEvent(QCloseEvent * event) def closeEvent(self, close_event: QCloseEvent): self._logger.debug('ApikeysManagerWindow.closeEvent()') self.mainwindow.apikeysmgrw = None close_event.accept() def load_apikeys(self): apikeys = self.emcore.savedata.get_apikeys() for apikey in apikeys: apikey_widget = SingleApiKeyWidget(self) apikey_widget.set_from_apikey(apikey) apikey_widget.editClicked.connect(self.on_click_edit_apikey) apikey_widget.removeClicked.connect(self.on_click_remove_apikey) self._layout.addWidget(apikey_widget, 0) # self._layout.addStretch() def reload_apikeys(self): # clear existing layout lc = self._layout.count() i = lc - 1 # iterate from the end # at index 0 there is a layout_top1 # we need items from index 1 and to the end while i > 0: layoutItem = self._layout.takeAt(i) if layoutItem is not None: if layoutItem.widget() is not None: # from Qt docs: Note: While the functions layout() and spacerItem() # perform casts, this function returns another object: QLayout and # QSpacerItem inherit QLayoutItem, while QWidget does not. # so, we must delete widget separately. widget = layoutItem.widget() if widget is not None: widget.hide() widget.setParent(None) # from stackoverflow :/ sip.delete( widget) # calls destructor for wrapped C++ object del widget sip.delete(layoutItem) del layoutItem i -= 1 self.load_apikeys() def start_add_or_edit_apikey(self, keyid: str = None): apikey = None if keyid is not None: apikey = self.emcore.savedata.get_apikey_by_keyid(keyid) self._logger.debug( 'Found existing apikey with primary key = {}'.format( apikey.id)) self._logger.debug(' number of chars: {}'.format( len(apikey.apikey_characters))) dlg = AddEditApikeyDialog(self, apikey) exec_res = dlg.exec_() if exec_res == QDialog.Rejected: return # apply added/edited apikey self.emcore.savedata.store_apikey(dlg.get_apikey(), check_existing=False) self.reload_apikeys() def on_click_add_apikey(self): self._logger.debug('click') self.start_add_or_edit_apikey() def on_click_edit_apikey(self, keyid: str): self._logger.debug('Start edit, keyid = {}'.format(keyid)) self.start_add_or_edit_apikey(keyid) def on_click_remove_apikey(self, keyid: str): answer = QMessageBox.question( self, self.tr('Confirmation'), self.tr('Are you sure you want to delete API Key?')) if answer != QMessageBox.Yes: return self._logger.debug('keyid = {}'.format(keyid)) self.emcore.savedata.remove_apikey_by_keyid(keyid) self.reload_apikeys()
class notifyTab(QWidget): def __init__(self,parent_): super(notifyTab,self).__init__(parent_) self.parent_=parent_ self.obj=parent_.obj self.initUI() def initUI(self): self.btn = [] self.notif_lbl = [] self.tag_lbl = [] self.nFrames =[] self.d_ = False for i in range(0,len(self.obj.notif)): pixmap=QPixmap(self.obj.tagDict[self.obj.notif[i].tag]) pixmap = pixmap.scaled(45, 45) self.tag_lbl.append(QLabel(self)) self.tag_lbl[i].setPixmap(pixmap) for i1 in range(0,len(self.obj.notif)): self.notif_lbl.append(QLabel(self)) self.notif_lbl[i1].setScaledContents(False) self.notif_lbl[i1].setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.notif_lbl[i1].setText(self.obj.notif[i1].notif_text) self.notif_lbl[i1].setObjectName("nlbl") self.btn.append(QPushButton('Get Link')) self.btn[i1].setObjectName("btn") for i2 in range(0,len(self.obj.notif)): self.nFrames.append(notifyFrames(self.notif_lbl[i2],self.tag_lbl[i2],self.btn[i2])) tag = self.obj.notif[i2].tag if tag ==2 or tag==3 or tag==4: self.nFrames[i2].setObjectName('nFrameOdd') else: self.nFrames[i2].setObjectName('nFrameEven') self.widget = QWidget(self) self.vbox = QVBoxLayout() for index, frame in enumerate(self.nFrames): self.vbox.addWidget(frame) if len(self.nFrames)<4: self.dframe = QFrame() self.dframe.setObjectName('nFrameDummy') self.vbox.addWidget(self.dframe) self.d_ = True self.vbox.setContentsMargins(0,0,0,0) self.vbox.setSpacing(3) self.widget.setLayout(self.vbox) self.scroll = QScrollArea(self) self.scroll.setWidget(self.widget) self.scroll.setWidgetResizable(True) self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) vbox1 = QVBoxLayout() vbox1.setContentsMargins(0,0,0,0) vbox1.setSpacing(0) vbox1.addWidget(self.scroll) self.setLayout(vbox1) def updater(self): marker=len(self.nFrames) marker1 = len(self.nFrames) for i in range(0,len(self.obj.notif)): pixmap=QPixmap(self.obj.tagDict[self.obj.notif[i].tag]) pixmap = pixmap.scaled(45, 45) self.tag_lbl.append(QLabel(self)) self.tag_lbl[marker].setPixmap(pixmap) marker=marker+1 marker=marker1 for i1 in range(0,len(self.obj.notif)): self.notif_lbl.append(QLabel(self)) self.notif_lbl[marker].setScaledContents(False) self.notif_lbl[marker].setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.notif_lbl[marker].setText(self.obj.notif[i1].notif_text) self.notif_lbl[marker].setObjectName("nlbl") self.btn.append(QPushButton('Get Link')) self.btn[marker].setObjectName("btn") marker+=1 marker=marker1 for i2 in range(0,len(self.obj.notif)): self.nFrames.append(notifyFrames(self.notif_lbl[marker],self.tag_lbl[marker],self.btn[marker])) marker+=1 marker=marker1 for i3 in range(len(self.nFrames)-1,marker-1,-1): self.vbox.insertWidget(0,self.nFrames[i3]) tag = self.obj.notif[i3-marker].tag if tag ==2 or tag==3 or tag==4: self.nFrames[i3].setObjectName('nFrameOdd') self.nFrames[i3].setStyleSheet(' #nFrameOdd{background-color:#F5F5DC;min-height:110px;max-height:110px;min-width:805px;}') else: self.nFrames[i3].setObjectName('nFrameEven') self.nFrames[i3].setStyleSheet(' #nFrameEven{background-color: rgb(255, 250, 175);min-height:110px;max-height:110px;min-width:805px;}') if len(self.nFrames)>=4 and self.d_==True: child = self.vbox.takeAt(len(self.nFrames)) if child.widget() is not None: child.widget().deleteLater() elif child.layout() is not None: clearLayout(child.layout()) self.dframe.deleteLater() self.d_=False
class PromptContainer(QWidget): """Container for prompts to be shown above the statusbar. This is a per-window object, however each window shows the same prompt. Attributes: _layout: The layout used to show prompts in. _win_id: The window ID this object is associated with. Signals: update_geometry: Emitted when the geometry should be updated. """ STYLESHEET = """ {% set prompt_radius = config.get('ui', 'prompt-radius') %} QWidget#PromptContainer { {% if config.get('ui', 'status-position') == 'top' %} border-bottom-left-radius: {{ prompt_radius }}px; border-bottom-right-radius: {{ prompt_radius }}px; {% else %} border-top-left-radius: {{ prompt_radius }}px; border-top-right-radius: {{ prompt_radius }}px; {% endif %} } QWidget { font: {{ font['prompts'] }}; color: {{ color['prompts.fg'] }}; background-color: {{ color['prompts.bg'] }}; } QTreeView { selection-background-color: {{ color['prompts.selected.bg'] }}; } QTreeView::item:selected, QTreeView::item:selected:hover { background-color: {{ color['prompts.selected.bg'] }}; } """ update_geometry = pyqtSignal() def __init__(self, win_id, parent=None): super().__init__(parent) self._layout = QVBoxLayout(self) self._layout.setContentsMargins(10, 10, 10, 10) self._win_id = win_id self._prompt = None self.setObjectName('PromptContainer') self.setAttribute(Qt.WA_StyledBackground, True) style.set_register_stylesheet(self) message.global_bridge.prompt_done.connect(self._on_prompt_done) prompt_queue.show_prompts.connect(self._on_show_prompts) message.global_bridge.mode_left.connect(self._on_global_mode_left) def __repr__(self): return utils.get_repr(self, win_id=self._win_id) @pyqtSlot(usertypes.Question) def _on_show_prompts(self, question): """Show a prompt for the given question. Args: question: A Question object or None. """ item = self._layout.takeAt(0) if item is not None: widget = item.widget() log.prompt.debug("Deleting old prompt {}".format(widget)) widget.hide() widget.deleteLater() if question is None: log.prompt.debug("No prompts left, hiding prompt container.") self._prompt = None self.hide() return classes = { usertypes.PromptMode.yesno: YesNoPrompt, usertypes.PromptMode.text: LineEditPrompt, usertypes.PromptMode.user_pwd: AuthenticationPrompt, usertypes.PromptMode.download: DownloadFilenamePrompt, usertypes.PromptMode.alert: AlertPrompt, } klass = classes[question.mode] prompt = klass(question) log.prompt.debug("Displaying prompt {}".format(prompt)) self._prompt = prompt if not question.interrupted: # If this question was interrupted, we already connected the signal question.aborted.connect( lambda: modeman.maybe_leave(self._win_id, prompt.KEY_MODE, 'aborted')) modeman.enter(self._win_id, prompt.KEY_MODE, 'question asked') self.setSizePolicy(prompt.sizePolicy()) self._layout.addWidget(prompt) prompt.show() self.show() prompt.setFocus() self.update_geometry.emit() @pyqtSlot(usertypes.KeyMode) def _on_prompt_done(self, key_mode): """Leave the prompt mode in this window if a question was answered.""" modeman.maybe_leave(self._win_id, key_mode, ':prompt-accept') @pyqtSlot(usertypes.KeyMode) def _on_global_mode_left(self, mode): """Leave prompt/yesno mode in this window if it was left elsewhere. This ensures no matter where a prompt was answered, we leave the prompt mode and dispose of the prompt object in every window. """ if mode not in [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]: return modeman.maybe_leave(self._win_id, mode, 'left in other window') item = self._layout.takeAt(0) if item is not None: widget = item.widget() log.prompt.debug("Deleting prompt {}".format(widget)) widget.hide() widget.deleteLater() @cmdutils.register(instance='prompt-container', hide=True, scope='window', modes=[usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]) def prompt_accept(self, value=None): """Accept the current prompt. // This executes the next action depending on the question mode, e.g. asks for the password or leaves the mode. Args: value: If given, uses this value instead of the entered one. For boolean prompts, "yes"/"no" are accepted as value. """ question = self._prompt.question try: done = self._prompt.accept(value) except Error as e: raise cmdexc.CommandError(str(e)) if done: message.global_bridge.prompt_done.emit(self._prompt.KEY_MODE) question.done() @cmdutils.register(instance='prompt-container', hide=True, scope='window', modes=[usertypes.KeyMode.yesno], deprecated='Use :prompt-accept yes instead!') def prompt_yes(self): """Answer yes to a yes/no prompt.""" self.prompt_accept('yes') @cmdutils.register(instance='prompt-container', hide=True, scope='window', modes=[usertypes.KeyMode.yesno], deprecated='Use :prompt-accept no instead!') def prompt_no(self): """Answer no to a yes/no prompt.""" self.prompt_accept('no') @cmdutils.register(instance='prompt-container', hide=True, scope='window', modes=[usertypes.KeyMode.prompt], maxsplit=0) def prompt_open_download(self, cmdline: str = None): """Immediately open a download. If no specific command is given, this will use the system's default application to open the file. Args: cmdline: The command which should be used to open the file. A `{}` is expanded to the temporary file name. If no `{}` is present, the filename is automatically appended to the cmdline. """ try: self._prompt.download_open(cmdline) except UnsupportedOperationError: pass @cmdutils.register(instance='prompt-container', hide=True, scope='window', modes=[usertypes.KeyMode.prompt]) @cmdutils.argument('which', choices=['next', 'prev']) def prompt_item_focus(self, which): """Shift the focus of the prompt file completion menu to another item. Args: which: 'next', 'prev' """ try: self._prompt.item_focus(which) except UnsupportedOperationError: pass
class itemTab(QWidget): def __init__(self,parent_,id_): super(itemTab,self).__init__(parent_) self.parent_=parent_ self.obj = parent_.obj self.id_=id_ self.initUI() def initUI(self): self.obtn = {} self.sbtn = {} self.lbl = [] self.gbtn = [] self.iFrames =[] self.tag_lbl=[] self.d_ =False self.x = 0 self.y=0 self.backBtn = QPushButton(QIcon(':/Assets/close2.png'),'Close') self.backBtn.setObjectName('backBtn') self.backBtn.clicked.connect(partial(self.parent_.closeItemTab,self.id_)) self.clbl = QLabel(self) self.clbl.setText(self.obj.courses[self.id_].c_name.upper()) self.clbl.setObjectName('hlbl') a = ["assignment","exam","quiz"] for iz in range(0,len(self.obj.courses[self.id_].items)): if self.obj.courses[self.id_].items[iz].saved==2: pixmap=QPixmap(self.obj.tagDict[3]) else: str_= self.obj.courses[self.id_].items[iz].i_name.lower() if any(x in str_ for x in a): pixmap=QPixmap(self.obj.tagDict[4]) else: pixmap=QPixmap(self.obj.tagDict[self.obj.courses[self.id_].items[iz].type+2]) pixmap = pixmap.scaled(45, 45) self.tag_lbl.append(QLabel(self)) self.tag_lbl[iz].setPixmap(pixmap) for i1 in range(0,len(self.obj.courses[self.id_].items)): self.lbl.append(ExtendedQLabel(self,i1)) cname = self.shortenTabName(self.obj.courses[self.id_].items[i1].i_name) self.lbl[i1].setText(cname) self.lbl[i1].setObjectName("ilbl") self.sbtn[i1] = QPushButton('Save') self.sbtn[i1].setObjectName("sbtn") self.gbtn.append(QPushButton(QIcon(':/Assets/link.png'),'')) self.gbtn[i1].setObjectName("linkBtn") self.gbtn[i1].id_ = i1 self.gbtn[i1].clicked.connect(partial(self.copyLink,self.gbtn[i1].id_)) self.sbtn[i1].clicked.connect(partial(self.saveItem,self.gbtn[i1].id_)) if self.obj.courses[self.id_].items[i1].saved==1: self.obtn[i1] = QPushButton('Open') if self.obj.courses[self.id_].items[i1].dwnld == 1: self.obtn[i1].setObjectName("obtn") else: self.obtn[i1].setObjectName("rbtn") self.obtn[i1].clicked.connect(partial(self.openItem,self.gbtn[i1].id_)) self.iFrames.append(topFrame(self.backBtn,self.clbl)) for i2 in range(0,len(self.obj.courses[self.id_].items)): if self.obj.courses[self.id_].items[i2].saved==1: self.iFrames.append(itemFrames(self.lbl[i2],self.gbtn[i2],self.sbtn[i2],self.obtn[i2],self.tag_lbl[i2])) elif self.obj.courses[self.id_].items[i2].saved==0: self.iFrames.append(itemFramesNew(self.lbl[i2],self.gbtn[i2],self.sbtn[i2],self.tag_lbl[i2])) else: self.iFrames.append(itemFramesForum(self.lbl[i2],self.gbtn[i2],self.tag_lbl[i2])) self.widget = QWidget(self) self.vbox = QVBoxLayout() self.vbox.setSpacing(3) for index, frame in enumerate(self.iFrames): if index%2==0: frame.setObjectName('nFrameEven') else: frame.setObjectName('nFrameEven') self.vbox.addWidget(frame) self.vbox.setContentsMargins(0,0,0,0) if len(self.iFrames)<4: self.dframe = QFrame() self.dframe.setObjectName('nFrameDummy') self.vbox.addWidget(self.dframe) self.d_=True self.widget.setLayout(self.vbox) self.scroll = QScrollArea(self) self.scroll.setWidget(self.widget) self.scroll.setWidgetResizable(True) self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) vbox1 = QVBoxLayout() vbox1.setContentsMargins(0,0,0,0) vbox1.setSpacing(0) vbox1.addWidget(self.scroll) self.setLayout(vbox1) def shortenTabName(self,cname): list1 =[] for i, ch in enumerate(cname): list1.append(ch) if len(list1)==40: if list1[i]== ' ': list1.pop(i) list1.append('...') break return ''.join(list1) def updater(self): self.x=+1 if self.obj.courses[self.id_].dummy_items is '': return else: if self.d_==True: child = self.vbox.takeAt(len(self.iFrames)) if child.widget() is not None: child.widget().deleteLater() elif child.layout() is not None: clearLayout(child.layout()) self.dframe.deleteLater() self.d_=False marker = len(self.iFrames)-1 a = ["assignment","exam","quiz"] for iz in range(marker,len(self.obj.courses[self.id_].items)): if self.obj.courses[self.id_].items[iz].saved==2: pixmap=QPixmap(self.obj.tagDict[3]) else: str_= self.obj.courses[self.id_].items[iz].i_name.lower() if any(x in str_ for x in a): pixmap=QPixmap(self.obj.tagDict[4]) else: pixmap=QPixmap(self.obj.tagDict[self.obj.courses[self.id_].items[iz].type+2]) pixmap = pixmap.scaled(45, 45) self.tag_lbl.append(QLabel(self)) self.tag_lbl[iz].setPixmap(pixmap) for i1 in range(marker,len(self.obj.courses[self.id_].items)): self.lbl.append(ExtendedQLabel(self,i1)) cname = self.shortenTabName(self.obj.courses[self.id_].items[i1].i_name) self.lbl[i1].setText(cname) self.lbl[i1].setObjectName("ilbl") self.sbtn[i1] = QPushButton('Save') self.sbtn[i1].setObjectName("sbtn") self.gbtn.append(QPushButton(QIcon(':/Assets/link.png'),'')) self.gbtn[i1].setObjectName("linkBtn") self.gbtn[i1].id_ = i1 self.gbtn[i1].clicked.connect(partial(self.copyLink,self.gbtn[i1].id_)) self.sbtn[i1].clicked.connect(partial(self.saveItem,self.gbtn[i1].id_)) if self.obj.courses[self.id_].items[i1].saved==1: self.obtn[i1] = QPushButton('Open') if self.obj.courses[self.id_].items[i1].dwnld == 1: self.obtn[i1].setObjectName("obtn") else: self.obtn[i1].setObjectName("rbtn") self.obtn[i1].clicked.connect(partial(self.openItem,self.gbtn[i1].id_)) for i2 in range(marker,len(self.obj.courses[self.id_].items)): if self.obj.courses[self.id_].items[i2].saved==1: self.iFrames.append(itemFrames(self.lbl[i2],self.gbtn[i2],self.sbtn[i2],self.obtn[i2],self.tag_lbl[i2])) elif self.obj.courses[self.id_].items[i2].saved==0: self.iFrames.append(itemFramesNew(self.lbl[i2],self.gbtn[i2],self.sbtn[i2],self.tag_lbl[i2])) else: self.iFrames.append(itemFramesForum(self.lbl[i2],self.gbtn[i2],self.tag_lbl[i2])) for i3 in range (marker+1,len(self.iFrames)): if i3%2==0: self.iFrames[i3].setObjectName('nFrameEven') else: self.iFrames[i3].setObjectName('nFrameEven') self.vbox.addWidget(self.iFrames[i3]) y=0 if len(self.iFrames)<4: self.dframe = QFrame() self.dframe.setObjectName('nFrameDummy') self.vbox.addWidget(self.dframe) self.d_=True def saveItem(self,id_): fileName = QFileDialog.getOpenFileName(self, 'Select Your File') if fileName[0] is '': return else: self.obtn[id_] = QPushButton('Open') self.obtn[id_].setObjectName("rbtn") self.obtn[id_].clicked.connect(partial(self.openItem,self.gbtn[id_].id_)) self.obj.courses[self.id_].items[id_].olink = fileName[0] self.obj.courses[self.id_].items[id_].saved=1 self.iFrames[id_+1].grid.addWidget(self.obtn[id_],1,3) self.parent_.parent_.itemsWriter(fileName[0],self.id_,id_) def openItem(self,id_): fileName = self.obj.courses[self.id_].items[id_].olink if sys.platform == "win32": if os.path.exists(fileName): if self.obj.courses[self.id_].items[id_].type==1 and self.obj.courses[self.id_].items[id_].dwnld==1 : self.parent_.openTextBrowser(fileName,self.obj.courses[self.id_].c_name,self.obj.courses[self.id_].items[id_].i_name) else: os.startfile(fileName) else: reply = QMessageBox.information(self,'Moodly',"This file no longer exists. Kindly save a new link.", QMessageBox.Ok) if reply == QMessageBox.Ok: pass else: if os.path.exists(fileName): opener ="open" if sys.platform == "darwin" else "xdg-open" subprocess.call([opener, fileName]) else: reply = QMessageBox.information(self,'Moodly',"This file no longer exists. Kindly save a new link.", QMessageBox.Ok) if reply == QMessageBox.Ok: pass def copyLink(self,id_): cb = QApplication.clipboard() cb.clear(mode=cb.Clipboard) cb.setText(self.obj.courses[self.id_].items[id_].glink, mode=cb.Clipboard) reply = QMessageBox.information(self,'Moodly',"The link has been copied", QMessageBox.Ok) if reply == QMessageBox.Ok: pass
class MainWindow(QMainWindow): def __init__(self, client_socket, process_events_method): super().__init__() self.username = config.user["username"] self.client_socket = client_socket self.client_socket.recv_message.connect(self.recvMessage) self.process_events_method = process_events_method self.chats = {} self.send_on_enter = True self.initMenubar() self.initUI() for chat in config.chats: self.createChat(chat, config.chats[chat]["participants"]) def initMenubar(self): self.createChatAction = QAction("&Create Chat", self) #self.exitAction.setShortcut("Ctrl+Q") self.createChatAction.triggered.connect(self.createChat) self.addFriendAction = QAction("&Add Friend", self) self.addFriendAction.triggered.connect(self.addFriend) self.menubar = self.menuBar() self.chatMenu = self.menubar.addMenu("&Chat") self.chatMenu.addAction(self.createChatAction) self.friendMenu = self.menubar.addMenu("&Friend") self.friendMenu.addAction(self.addFriendAction) def initUI(self): self.content = QWidget() self.hbox = QHBoxLayout(self.content) self.setCentralWidget(self.content) self.friend_list = QListWidget() self.friend_list.itemClicked.connect(self.friendClicked) self.message_scroll = QScrollArea() self.message_scroll.setWidgetResizable(True) #TODO have a setting to disable this self.message_scroll.verticalScrollBar().rangeChanged.connect(self.scrollBottom) self.message_input = MessageInput() self.message_input.sendMessage.connect(self.sendMessage) self.message_split = QSplitter(Qt.Vertical) self.message_split.addWidget(self.message_scroll) self.message_split.addWidget(self.message_input) self.main_split = QSplitter(Qt.Horizontal) self.main_split.addWidget(self.friend_list) self.main_split.addWidget(self.message_split) self.hbox.addWidget(self.main_split) self.show() def addFriend(self, username=None): if type(username) is bool: add_friend_dialog = AddFriendDialog(self.client_socket) add_friend_dialog.exec_() if add_friend_dialog.selected_user == None: return username = add_friend_dialog.selected_user self.chats[username] = {"participants":[username], "messages":[]} #TODO we should probably sanatize these to prevent directory manipulation friend = QListWidgetItem(QIcon(config.ICON_DIR + username + ".png"), username) self.friend_list.addItem(friend) def createChat(self, chat_name=None, participants=None): if type(chat_name) is bool: create_chat_dialog = CreateChatDialog() create_chat_dialog.exec_() if not create_chat_dialog.created_chat: return chat_name = create_chat_dialog.chat_name participants = create_chat_dialog.participants self.chats[chat_name] = {"participants":participants, "messages":[]} self.friend_list.addItem(QListWidgetItem(chat_name)) def friendClicked(self, item): self.loadMessages(str(item.text())) def loadMessages(self, chat): #self.clearMessages() #TODO make the message history look pretty #TODO consider storing a message history for each chat and switch between when needed #TODO create a chat class and store the chat name as well as the participants there #TODO index message histories by chat name self.message_history = QVBoxLayout() self.message_history.setSpacing(0) self.message_history.setContentsMargins(0,0,0,0) self.message_history.insertStretch(-1, 1) self.message_history_container = QWidget() self.message_history_container.setLayout(self.message_history) self.message_scroll.setWidget(self.message_history_container) for message in self.chats[chat]["messages"]: self.drawMessage(message) def clearMessages(self): while not self.message_history.isEmpty(): self.message_history.takeAt(0) def sendMessage(self): message = self.message_input.toPlainText().strip() chat = self.friend_list.selectedItems()[0].text() self.client_socket.sendMessage(message, chat, self.chats[chat]["participants"]) print("Sending: %s to %s" % (message, chat)) self.message_input.setText("") self.recvMessage(chat, {"sender":self.username, "message":message}) def recvMessage(self, chat, message): self.chats[chat]["messages"].append(message) current_chat = self.friend_list.selectedItems()[0].text() if current_chat == chat: self.drawMessage(message) def drawMessage(self, message): #TODO add a timestamp to messages new_message = QLabel(message["sender"] + ':' + message["message"]) self.message_history.addWidget(new_message) def scrollBottom(self): self.message_scroll.verticalScrollBar().setValue(self.message_scroll.verticalScrollBar().maximum()) def disconnect(self): self.client_socket.disconnect()
class PathEditor(ViewInfoChanger): def __init__(self, header, info, parent: ViewShower): super().__init__(header, info, parent) self.way_layout = QVBoxLayout() push_btn = self.main_layout.takeAt(self.main_layout.count() - 1).widget() self.path_id = info[0] add_btn = QPushButton("Добавить") add_btn.clicked.connect(lambda e: self.add_cell(-1)) self.main_layout.addWidget(add_btn) way = self.db.execute( f"SELECT Станция, `Код станции` FROM `состав_маршрута_view` where `Номер маршрута` = {self.path_id}" ) for pos, point in enumerate(way, start=-1): station_info = str(point[0]) self.combo_change_idx["Станция отправления"][station_info] = point[ 1] self.add_cell(pos, station_info) self.main_layout.addLayout(self.way_layout) self.main_layout.addWidget(push_btn) def add_cell(self, pos: int, txt=""): """Вставляет ячейку ниже активирующей кнопки для вставки на уровне надо передать ::pos:: = -1""" cell = QHBoxLayout() edi = QLineEdit() edi.setText(txt) add_btn = QPushButton("Добавить") down_btn = QPushButton("Вниз") up_btn = QPushButton("Вверх") del_btn = QPushButton("Удалить") cmb = QComboBox() cmb.addItem(txt) edi.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) cmb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) up_btn.clicked.connect(lambda e, p=pos, c=cell: self.move_cell_up(c)) down_btn.clicked.connect( lambda e, p=pos, c=cell: self.move_cell_down(c)) add_btn.clicked.connect(lambda e, c=cell: self.add_cell(c.pos)) del_btn.clicked.connect(lambda e, c=cell: self.del_cell(c.pos)) edi.editingFinished.connect( lambda c=cmb, t=edi.text: self.combo_update( "Станция отправления", c, t())) # le-kostyl cell.pos = pos cell.addWidget(edi) cell.addWidget(cmb) cell.addWidget(add_btn) cell.addWidget(down_btn) cell.addWidget(up_btn) cell.addWidget(del_btn) for i in range(pos + 1, self.way_layout.count()): cell_to_move = self.way_layout.itemAt(i) cell_to_move.pos += 1 cell.pos += 1 # для вставки ниже активированной кнопки self.way_layout.insertLayout(cell.pos, cell) def del_cell(self, pos): cell: QVBoxLayout cell = self.way_layout.takeAt(pos) for i in range(cell.count()): w = cell.takeAt(0).widget() w.deleteLater() cell.deleteLater() for i in range(pos, self.way_layout.count()): cell_to_move = self.way_layout.itemAt(i) cell_to_move.pos -= 1 def move_cell_up(self, cell): if cell.pos > 0: old_cell = self.way_layout.itemAt(cell.pos - 1) self.way_layout.takeAt(cell.pos) self.way_layout.insertLayout(cell.pos - 1, cell) old_cell.pos += 1 cell.pos -= 1 def move_cell_down(self, cell): if cell.pos < self.way_layout.count() - 1: old_cell = self.way_layout.itemAt(cell.pos + 1) self.way_layout.takeAt(cell.pos) self.way_layout.insertLayout(cell.pos + 1, cell) old_cell.pos -= 1 cell.pos += 1 def push_point_changes(self): params = [] station_ord_numb_in_path = 0 for i in range(self.way_layout.count()): cell = self.way_layout.itemAt(i) w = cell.itemAt(1).widget() station = w.currentText() if station: station_id = self.combo_change_idx["Станция отправления"][ station] params.append( (self.path_id, station_ord_numb_in_path, station_id)) station_ord_numb_in_path += 1 query = f" insert into `состав маршрута` values(%s, %s, %s)" self.db.execute( "delete from `состав маршрута` where `Номер маршрута` = %s", (self.path_id, )) self.db.executemany(query, params) self.db.commit() def push_changes(self): self.push_point_changes() super().push_changes()
class MainWindow(QWidget): ## # \brief Initialization Function def __init__(self): super(MainWindow, self).__init__() #Default variables self.valid = False #Field to determine if the value is valid self.selectorLayout = None #Layout used for selecting a specific source self.sources = ["none", "text", "file", "database"] self.source = {"type": None} self.dests = ["console", "file"] self.dest = {"type": "console"} self.sourceValue = None self.sourceSchema = None #Determine screen settings geo = self.frameGeometry() self.width = QDesktopWidget().availableGeometry().width() self.height = QDesktopWidget().availableGeometry().height() #Define window par meters self.resize(self.width * .5, self.height * .5) self.setWindowTitle("Aqueti Schema Editor") self.show() #create Layouts in UI self.titleLayout = QHBoxLayout() self.mainLayout = QVBoxLayout() self.sourceLayout = QHBoxLayout() self.destLayout = QHBoxLayout() self.valueLayout = QVBoxLayout() self.buttonLayout = QHBoxLayout() #Create frames self.sourceFrame = QFrame() self.destFrame = QFrame() self.valueFrame = QFrame() self.sourceFrame.setFrameStyle(QFrame.Box) self.valueFrame.setFrameStyle(QFrame.Box) self.destFrame.setFrameStyle(QFrame.Box) self.sourceFrame.setLayout(self.sourceLayout) self.destFrame.setLayout(self.destLayout) self.valueFrame.setLayout(self.valueLayout) self.valueFrame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) #Create Scoll Area for valueFrame self.valueScrollArea = QScrollArea() self.valueScrollArea.updateGeometry() self.valueScrollArea.setWidget(self.valueFrame) self.valueScrollArea.setWidgetResizable(True) #Create title title = QLabel() title.setText("Aqueti Schema Editor") self.titleLayout.addWidget(title) #Add persistent source items sourceTitle = QLabel() sourceTitle.setText("Source:") self.sourceCombo = QComboBox() self.sourceCombo.addItems(self.sources) self.sourceCombo.currentTextChanged.connect( lambda: self.sourceChangeCallback()) selectSourceButton = QPushButton("Load") self.sourceLayout.addWidget(sourceTitle) self.sourceLayout.addWidget(self.sourceCombo) self.sourceMetaLayout = QHBoxLayout() self.sourceMetaLayout.setSizeConstraint(QHBoxLayout.SetMinimumSize) self.sourceLayout.addLayout(self.sourceMetaLayout) self.sourceLayout.addWidget(selectSourceButton) #Add persistent dest destTitle = QLabel() destTitle.setText("Dest:") self.destCombo = QComboBox() self.destCombo.addItems(self.dests) self.destCombo.currentTextChanged.connect( lambda: self.destChangeCallback()) selectDestButton = QPushButton("Load") self.destLayout.addWidget(destTitle) self.destLayout.addWidget(self.destCombo) self.destMetaLayout = QHBoxLayout() self.destMetaLayout.setSizeConstraint(QHBoxLayout.SetMinimumSize) self.destLayout.addLayout(self.destMetaLayout) self.destLayout.addWidget(selectDestButton) #Add Submit Button self.submitButton = QPushButton("Submit") self.submitButton.clicked.connect(lambda: self.submitCallback()) self.buttonLayout.addWidget(self.submitButton) #Add cancel Button cancelButton = QPushButton("Cancel") cancelButton.clicked.connect(lambda: self.cancelCallback()) self.buttonLayout.addWidget(cancelButton) #Add Layouts and draw self.mainLayout.addLayout(self.titleLayout) self.mainLayout.addWidget(self.sourceFrame) self.mainLayout.addWidget(self.destFrame) # self.mainLayout.addWidget( self.valueFrame ) self.mainLayout.addWidget(self.valueScrollArea) # self.mainLayout.addStretch(1) self.mainLayout.addLayout(self.buttonLayout) self.draw() ## # \brief updates the source Layout def updateSourceLayout(self): #Remove current layout information #Remove all widgets from the current layout while self.sourceMetaLayout.count(): item = self.sourceMetaLayout.takeAt(0) self.sourceMetaLayout.removeItem(item) widget = item.widget() if widget is not None: widget.deleteLater() try: item.deleteLater() except: pass #Find what our current source is and set the appropriate index index = 0 for i in range(0, self.sourceCombo.count()): if self.sourceCombo.itemText(i) == self.source["type"]: index = i self.sourceCombo.setCurrentIndex(index) #Add fields based on source type if self.source["type"] == "file": #Add filename fileLabel = QLabel() fileLabel.setText("file: ") try: name = self.source["filename"] except: name = "" self.sourceFilenameBox = QLineEdit() self.sourceFilenameBox.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) self.sourceFilenameBox.setText(name) # self.sourceFilenameBox.readOnly = True # self.sourceFilenameBox.sizeHint() # self.sourceFilenameBox.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.sourceMetaLayout.addWidget(fileLabel) self.sourceMetaLayout.addWidget(self.sourceFilenameBox) # #Add a submitSource Button # selectSourceButton = QPushButton("Load") # selectSourceButton.clicked.connect( lambda: self.sourceChangeCallback()) # self.sourceLayout.addWidget(selectSourceButton) # self.sourceLayout.addStretch(1) ## # \brief updates the destination layout # def updateDestLayout(self): #Remove current layout information #Remove all widgets from the current layout while self.destMetaLayout.count(): item = self.destMetaLayout.takeAt(0) self.destMetaLayout.removeItem(item) widget = item.widget() if widget is not None: widget.deleteLater() try: item.deleteLater() except: pass """ ############################################# # Layout to select a destination ############################################# destTitle = QLabel() destTitle.setText("Dest:") self.destCombo = QComboBox() self.destCombo.addItems(self.dests) """ #Find what our current dest is and set the appropriate index index = 0 for i in range(0, self.destCombo.count()): if self.destCombo.itemText(i) == self.dest["type"]: index = i self.destCombo.setCurrentIndex(index) self.destCombo.currentTextChanged.connect( lambda: self.destChangeCallback()) # self.destLayout.addWidget(destTitle) # self.destLayout.addWidget(self.destCombo) # self.destLayout.addStretch(1) #### # Fill in details base on dest tpye #### if self.dest["type"] == "console": pass elif self.dest["type"] == "file": fileLabel = QLabel() fileLabel.setText("file: ") try: name = self.dest["filename"] except: name = "" self.fileNameBox = QLineEdit() self.fileNameBox.setText(name) # self.destMetaLayout.addWidget(fileLabel) self.destMetaLayout.addWidget(self.fileNameBox) ## # \brief function that is called when the source is changed # def destChangeCallback(self): print("Changing dest") newType = self.destCombo.itemText(self.destCombo.currentIndex()) print("New Type: " + str(newType)) if newType != self.dest["type"]: self.dest = {} self.dest["type"] = newType if self.dest["type"] == "console": pass elif self.dest["type"] == "file": options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog destName, _ = QFileDialog.getSaveFileName( self, "QFileDialog.getSaveFileName()", "", "All Files (*);;JSON Files (*.json)", options=options) self.dest["filename"] = str(destName) else: print("Unsupported Type") self.draw() ## # \brief Update the value layout def updateValueLayout(self): #Remove all widgets from the current layout while self.valueLayout.count(): item = self.valueLayout.takeAt(0) self.valueLayout.removeItem(item) widget = item.widget() if widget is not None: widget.deleteLater() try: item.deleteLater() except: pass #If we have data, let's display it if self.sourceSchema != None: valueTitle = QLabel() valueTitle.setText("Schema") self.schemaWidget = SmartWidget().init("Schema", self.sourceValue, self.sourceSchema, showSchema=False) self.valueLayout.addWidget(self.schemaWidget.frame) #Disable the submit button if we don't have a schema if self.sourceSchema == None: self.submitButton.setEnabled(False) else: self.submitButton.setEnabled(True) self.setLayout(self.mainLayout) ## # \brief redraws all dynamic layouts def draw(self): self.updateDestLayout() self.updateSourceLayout() self.updateValueLayout() ## # \brief callback for when the source type changes # def sourceChangeCallback(self): #SDF Add popup to notify of schema loss #Clear the schema to disable the submit button self.sourceSchema = None self.source["type"] = self.sourceCombo.itemText( self.sourceCombo.currentIndex()) if self.source["type"] == "none": self.sourceSchema = {"bsonType": "object"} #If we are a file read the file contents as the value elif self.source["type"] == "file": options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog sourceName, _ = QFileDialog.getOpenFileName( self, "QFileDialog.getOpenFileName()", "", "All Files (*);;JSON Files (*.json)", options=options) self.source["filename"] = str(sourceName) print("Loading: " + str(self.source["filename"])) with open(self.source["filename"]) as json_file: self.sourceSchema = json.load(json_file) print("Loaded Schema:" + str(json.dumps(self.sourceSchema, indent=4))) self.updateSourceLayout() self.updateValueLayout() ## #\brief callback to get result from SmartWidget # # This function assumes that the schema is done. It will produce a popup # asking where and how to save the data # def submitCallback(self): schema = self.schemaWidget.getSchema() if self.dest["type"] == "console": print() print("Schema: (" + str(time.time()) + ")") print(json.dumps(schema, indent=4)) elif self.dest["type"] == "file": print("Writing to: " + str(self.dest["filename"])) with open(self.dest["filename"], 'w') as outfile: json.dump(schema, outfile, indent=4) else: print("Source type: " + str(self.dest["type"]) + " is not currently supported") self.close() #Use save pop-up to save data #self.saveWindow = SaveDataWindow(self.source, schema, self.saveCallback ) print(str(time.time()) + "- schema:") print(str(schema)) ## # \brief Function called after data is saved # def saveCallback(self, success): print("Data Result: " + str(success)) ## # \brief Cancels the change and exits # def cancelCallback(self): print("Exited. No changes were saved") sys.exit(1)
class PromptContainer(QWidget): """Container for prompts to be shown above the statusbar. This is a per-window object, however each window shows the same prompt. Attributes: _layout: The layout used to show prompts in. _win_id: The window ID this object is associated with. Signals: update_geometry: Emitted when the geometry should be updated. """ STYLESHEET = """ {% set prompt_radius = config.get('ui', 'prompt-radius') %} QWidget#PromptContainer { {% if config.get('ui', 'status-position') == 'top' %} border-bottom-left-radius: {{ prompt_radius }}px; border-bottom-right-radius: {{ prompt_radius }}px; {% else %} border-top-left-radius: {{ prompt_radius }}px; border-top-right-radius: {{ prompt_radius }}px; {% endif %} } QWidget { font: {{ font['prompts'] }}; color: {{ color['prompts.fg'] }}; background-color: {{ color['prompts.bg'] }}; } QTreeView { selection-background-color: {{ color['prompts.selected.bg'] }}; } QTreeView::item:selected, QTreeView::item:selected:hover { background-color: {{ color['prompts.selected.bg'] }}; } """ update_geometry = pyqtSignal() def __init__(self, win_id, parent=None): super().__init__(parent) self._layout = QVBoxLayout(self) self._layout.setContentsMargins(10, 10, 10, 10) self._win_id = win_id self._prompt = None self.setObjectName('PromptContainer') self.setAttribute(Qt.WA_StyledBackground, True) style.set_register_stylesheet(self) message.global_bridge.prompt_done.connect(self._on_prompt_done) prompt_queue.show_prompts.connect(self._on_show_prompts) message.global_bridge.mode_left.connect(self._on_global_mode_left) def __repr__(self): return utils.get_repr(self, win_id=self._win_id) @pyqtSlot(usertypes.Question) def _on_show_prompts(self, question): """Show a prompt for the given question. Args: question: A Question object or None. """ item = self._layout.takeAt(0) if item is not None: widget = item.widget() log.prompt.debug("Deleting old prompt {}".format(widget)) widget.hide() widget.deleteLater() if question is None: log.prompt.debug("No prompts left, hiding prompt container.") self._prompt = None self.hide() return classes = { usertypes.PromptMode.yesno: YesNoPrompt, usertypes.PromptMode.text: LineEditPrompt, usertypes.PromptMode.user_pwd: AuthenticationPrompt, usertypes.PromptMode.download: DownloadFilenamePrompt, usertypes.PromptMode.alert: AlertPrompt, } klass = classes[question.mode] prompt = klass(question) log.prompt.debug("Displaying prompt {}".format(prompt)) self._prompt = prompt if not question.interrupted: # If this question was interrupted, we already connected the signal question.aborted.connect( lambda: modeman.leave(self._win_id, prompt.KEY_MODE, 'aborted', maybe=True)) modeman.enter(self._win_id, prompt.KEY_MODE, 'question asked') self.setSizePolicy(prompt.sizePolicy()) self._layout.addWidget(prompt) prompt.show() self.show() prompt.setFocus() self.update_geometry.emit() @pyqtSlot(usertypes.KeyMode) def _on_prompt_done(self, key_mode): """Leave the prompt mode in this window if a question was answered.""" modeman.leave(self._win_id, key_mode, ':prompt-accept', maybe=True) @pyqtSlot(usertypes.KeyMode) def _on_global_mode_left(self, mode): """Leave prompt/yesno mode in this window if it was left elsewhere. This ensures no matter where a prompt was answered, we leave the prompt mode and dispose of the prompt object in every window. """ if mode not in [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]: return modeman.leave(self._win_id, mode, 'left in other window', maybe=True) item = self._layout.takeAt(0) if item is not None: widget = item.widget() log.prompt.debug("Deleting prompt {}".format(widget)) widget.hide() widget.deleteLater() @cmdutils.register(instance='prompt-container', hide=True, scope='window', modes=[usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]) def prompt_accept(self, value=None): """Accept the current prompt. // This executes the next action depending on the question mode, e.g. asks for the password or leaves the mode. Args: value: If given, uses this value instead of the entered one. For boolean prompts, "yes"/"no" are accepted as value. """ question = self._prompt.question try: done = self._prompt.accept(value) except Error as e: raise cmdexc.CommandError(str(e)) if done: message.global_bridge.prompt_done.emit(self._prompt.KEY_MODE) question.done() @cmdutils.register(instance='prompt-container', hide=True, scope='window', modes=[usertypes.KeyMode.yesno], deprecated='Use :prompt-accept yes instead!') def prompt_yes(self): """Answer yes to a yes/no prompt.""" self.prompt_accept('yes') @cmdutils.register(instance='prompt-container', hide=True, scope='window', modes=[usertypes.KeyMode.yesno], deprecated='Use :prompt-accept no instead!') def prompt_no(self): """Answer no to a yes/no prompt.""" self.prompt_accept('no') @cmdutils.register(instance='prompt-container', hide=True, scope='window', modes=[usertypes.KeyMode.prompt], maxsplit=0) def prompt_open_download(self, cmdline: str = None): """Immediately open a download. If no specific command is given, this will use the system's default application to open the file. Args: cmdline: The command which should be used to open the file. A `{}` is expanded to the temporary file name. If no `{}` is present, the filename is automatically appended to the cmdline. """ try: self._prompt.download_open(cmdline) except UnsupportedOperationError: pass @cmdutils.register(instance='prompt-container', hide=True, scope='window', modes=[usertypes.KeyMode.prompt]) @cmdutils.argument('which', choices=['next', 'prev']) def prompt_item_focus(self, which): """Shift the focus of the prompt file completion menu to another item. Args: which: 'next', 'prev' """ try: self._prompt.item_focus(which) except UnsupportedOperationError: pass
class DatasetsWidget(QWidget): tab_active = pyqtSignal() go_to_run_tab = pyqtSignal() def __init__(self, iface: QgisInterface) -> None: super().__init__() self.iface = iface self.options = get_options() self.tab_active.connect(self.on_tab_active) self.create_geographical_data_box() self.create_meteorological_data_box() go_to_run_tab_btn = QPushButton('Continue to Run') go_to_run_tab_btn.clicked.connect(self.go_to_run_tab) vbox = QVBoxLayout() vbox.addWidget(self.gbox_geodata) vbox.addWidget(self.gbox_metdata) vbox.addWidget(go_to_run_tab_btn) self.setLayout(vbox) Broadcast.geo_datasets_updated.connect(self.populate_geog_data_tree) Broadcast.met_datasets_updated.connect(self.populate_met_data_tree) Broadcast.project_updated.connect(self.populate_geog_data_tree) @property def project(self) -> Project: return self._project @project.setter def project(self, val: Project) -> None: ''' Sets the currently active project. See tab_simulation. ''' self._project = val self.populate_geog_data_tree() self.populate_met_data_tree() def on_tab_active(self): self.update_geo_datasets_spec_fields() self.set_met_data_current_config_label() #region Geographical Data def create_geographical_data_box(self) -> None: self.gbox_geodata = QGroupBox('Geographical Data') vbox_geodata = QVBoxLayout() self.gbox_geodata.setLayout(vbox_geodata) self.label_geodata_wps_not_setup = QLabel( '<html>NOTE: Your project does not yet have a copy of a GEOGRID.TBL file.<br>' 'This is required for configuring geographical data.<br>' 'You need to <a href="https://gis4wrf.github.io/configuration/">setup WPS</a> first ' 'so that GIS4WRF can do an automatic<br>copy of that file. When you are done, return here.</html>' ) self.label_geodata_wps_not_setup.setOpenExternalLinks(True) self.label_geodata_wps_not_setup.setVisible(False) vbox_geodata.addWidget(self.label_geodata_wps_not_setup) gbox_avail_datasets = QGroupBox('Available Datasets') vbox_geodata.addWidget(gbox_avail_datasets) vbox_avail_dataset = QVBoxLayout() gbox_avail_datasets.setLayout(vbox_avail_dataset) self.tree_geog_data = QTreeWidget() self.tree_geog_data.setMinimumHeight(200) vbox_avail_dataset.addWidget(self.tree_geog_data) self.tree_geog_data.itemDoubleClicked.connect( self.on_geog_data_tree_doubleclick) self.tree_geog_data.setHeaderItem( QTreeWidgetItem( ['Group / Variable', 'Resolution', 'Interpolation Method'])) self.tree_geog_data.setContextMenuPolicy(Qt.CustomContextMenu) self.tree_geog_data.customContextMenuRequested.connect( self.on_tree_geog_data_open_context_menu) self.add_geog_dataset_button = QPushButton('Add Dataset to List') self.add_geog_dataset_button.clicked.connect( self.on_add_geog_dataset_button_clicked) vbox_avail_dataset.addWidget(self.add_geog_dataset_button) self.create_geog_form() vbox_avail_dataset.addWidget(self.geog_dataset_form) gbox_datasets_spec = QGroupBox('Selection') vbox_geodata.addWidget(gbox_datasets_spec) self.vbox_geo_datasets_spec = QVBoxLayout() gbox_datasets_spec.setLayout(self.vbox_geo_datasets_spec) self.geodata_gboxes = [gbox_avail_datasets, gbox_datasets_spec] # selection fields get created in the on_tab_active handler self.geo_dataset_spec_inputs = [] def update_geo_datasets_spec_fields(self) -> None: msg_bar = self.iface.messageBar() # type: QgsMessageBar specs = self.project.geo_dataset_specs domain_cnt = self.project.domain_count field_cnt = self.vbox_geo_datasets_spec.count() while field_cnt > domain_cnt: layout = self.vbox_geo_datasets_spec.takeAt(field_cnt - 1) clear_layout(layout) del self.geo_dataset_spec_inputs[-1] self.update_project_geo_dataset_specs() field_cnt -= 1 while field_cnt < domain_cnt: def create_on_plus_clicked_callback(): # This code is in this inner function to have a copy of field_cnt, # otherwise all on_clicked handlers would refer to field_cnt of the # last loop iteration. domain_index = field_cnt def on_clicked(): spec_input = self.geo_dataset_spec_inputs[domain_index] item = self.tree_geog_data.currentItem() if item is None: msg_bar.pushInfo( PLUGIN_NAME, 'Select a group in the dataset tree before clicking the + button.' ) return if item.childCount() == 0: msg_bar.pushWarning( PLUGIN_NAME, 'Select a group in the dataset tree, not a variable.' ) return new_group_name = item.data(0, Qt.UserRole) val = spec_input.value() if val: existing_group_names = val.split('+') if new_group_name in existing_group_names: msg_bar.pushWarning( PLUGIN_NAME, 'The group you selected is already added for this domain.' ) return val += '+' val += new_group_name spec_input.set_value(val) return on_clicked def create_on_info_clicked_callback(): # This code is in this inner function to have a copy of field_cnt, # otherwise all on_clicked handlers would refer to field_cnt of the # last loop iteration. domain_index = field_cnt def on_clicked(): spec_input = self.geo_dataset_spec_inputs[domain_index] group_names = spec_input.value().split('+') # determine resolved datasets for each variable tbl = self.geogrid_tbl resolved_groups = dict( ) # var name -> (group name or None) for variable in sorted(tbl.variables.values(), key=lambda v: v.name): var_group_names = variable.group_options.keys() found_group_name = None for group_name in group_names: if group_name in var_group_names: found_group_name = group_name break resolved_groups[variable.name] = found_group_name # show in message box title = 'Domain {}'.format(domain_index + 1) text = '<table><tr><td><b>Variable</b></td><td><b>Group</b></td></tr>' for var_name, group_name in resolved_groups.items(): if group_name is None: group_name = '<b><i>N/A</i></b>' text += '<tr><td>{}</td><td>{}</td></tr>'.format( var_name, group_name) text += '</table>' QMessageBox.information(self.iface.mainWindow(), title, text) return on_clicked hbox_datasets_spec = QHBoxLayout() hbox_datasets_spec.addWidget( QLabel('Domain: {}'.format(field_cnt + 1))) dataset_spec_input = create_lineedit(StringValidator( self.is_valid_geo_dataset_spec), required=True) try: dataset_spec_input.set_value(specs[field_cnt]) except IndexError: pass dataset_spec_input.textChanged.connect( self.update_project_geo_dataset_specs) self.geo_dataset_spec_inputs.append(dataset_spec_input) hbox_datasets_spec.addWidget(dataset_spec_input) plus_btn = QPushButton('+', clicked=create_on_plus_clicked_callback()) info_btn = QPushButton('?', clicked=create_on_info_clicked_callback()) # TODO make this dynamic, using size policies didn't work info_btn.setMaximumWidth(50) plus_btn.setMaximumWidth(50) hbox_datasets_spec.addWidget(plus_btn) hbox_datasets_spec.addWidget(info_btn) self.vbox_geo_datasets_spec.addLayout(hbox_datasets_spec) field_cnt += 1 def is_valid_geo_dataset_spec(self, spec: str) -> bool: # must only contain geographical dataset group names separated by pluses without duplicates tbl = self.geogrid_tbl if not tbl: return False spec_group_names = spec.split('+') if len(set(spec_group_names)) != len(spec_group_names): return False if any(group_name not in tbl.group_names for group_name in spec_group_names): return False return True def update_project_geo_dataset_specs(self) -> None: self.project.geo_dataset_specs = [ inp.value() if inp.is_valid() else '' for inp in self.geo_dataset_spec_inputs ] def create_geog_form(self): grid = QGridLayout() self.geog_dataset_form = QGroupBox('Add Dataset') self.geog_dataset_form.setLayout(grid) self.geog_dataset_form.hide() group_name_validator = StringValidator( lambda s: s and re.fullmatch(r'[a-zA-Z0-9_]+', s)) # TODO fix validator if empty interp_validator = StringValidator(lambda s: ' ' not in s) self.geog_dataset_form_group_name = add_grid_lineedit( grid, 0, 'Group Name', validator=group_name_validator, required=True) self.geog_dataset_form_dataset = add_grid_combobox(grid, 1, 'Dataset') self.geog_dataset_form_variable = add_grid_combobox( grid, 2, 'Variable') self.geog_dataset_form_interp = add_grid_combobox( grid, 3, 'Interpolation') self.geog_dataset_form_custom_interp = add_grid_lineedit( grid, 4, 'Custom Interpolation', validator=interp_validator, required=False) self.geog_dataset_form_variable.currentIndexChanged.connect( self.geog_dataset_form_variable_changed) btn_add = QPushButton('Add') btn_cancel = QPushButton('Cancel') btn_add.clicked.connect(self.on_add_geog_dataset_form_button_clicked) btn_cancel.clicked.connect( self.on_cancel_geog_dataset_form_button_clicked) grid.addWidget(btn_cancel, 5, 0) grid.addWidget(btn_add, 5, 1) def populate_geog_data_tree(self) -> None: tree = self.tree_geog_data tree.clear() try: tbl = self.geogrid_tbl = self.project.read_geogrid_tbl() except FileNotFoundError: # This happens when WPS is not setup. for box in self.geodata_gboxes: box.setEnabled(False) self.label_geodata_wps_not_setup.setVisible(True) return if tbl is None: return for box in self.geodata_gboxes: box.setEnabled(True) self.label_geodata_wps_not_setup.setVisible(False) add_derived_metadata_to_geogrid_tbl(tbl, self.options.geog_dir) for group_name in sorted(tbl.group_names): all_missing = not any( group_name in var.group_options and not var.group_options[group_name][GeogridTblKeys.MISSING] for var in tbl.variables.values()) group_item = QTreeWidgetItem(self.tree_geog_data) group_item.setText(0, group_name) group_item.setData(0, Qt.UserRole, group_name) if all_missing: group_item.setDisabled(True) for i in [0, 1, 2]: group_item.setToolTip( i, 'No dataset available in this group') for var_name in sorted(tbl.variables.keys()): group_options_all = tbl.variables[var_name].group_options if group_name not in group_options_all: continue group_options = group_options_all[group_name] interp = group_options[GeogridTblKeys.INTERP_OPTION] # not available when missing on disk resolution = group_options.get(GeogridTblKeys.RESOLUTION, '') var_item = QTreeWidgetItem(group_item) var_item.setText(0, var_name) var_item.setText(1, resolution) var_item.setText(2, interp) var_item.setToolTip(2, interp) var_item.setData(0, Qt.UserRole, group_options[GeogridTblKeys.ABS_PATH]) if group_options[GeogridTblKeys.MISSING]: var_item.setDisabled(True) for i in [0, 1, 2]: var_item.setToolTip(i, 'Dataset not available') def on_geog_data_tree_doubleclick(self, item: QTreeWidgetItem, column: int) -> None: if item.childCount() > 0: # clicked on a group return abs_path = item.data(0, Qt.UserRole) if not os.path.exists(abs_path): # dataset doesn't exist return load_wps_binary_layer(abs_path) def on_tree_geog_data_open_context_menu(self, position) -> None: item = self.tree_geog_data.currentItem() if item is None: return menu = QMenu() if item.parent() is None: remove_group_action = menu.addAction('Remove Group') remove_group_action.triggered.connect( self.on_tree_geog_data_remove_group_clicked) else: view_dataset_action = menu.addAction('View Dataset') view_dataset_action.triggered.connect( lambda: self.on_geog_data_tree_doubleclick(item, 0)) menu.exec_(self.tree_geog_data.viewport().mapToGlobal(position)) def on_tree_geog_data_remove_group_clicked(self): item = self.tree_geog_data.currentItem() group_name = item.data(0, Qt.UserRole) reply = QMessageBox.question( self.iface.mainWindow(), 'Remove Group', 'Are you sure you want to remove the group "{}"?'.format( group_name), QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.No: return self.geogrid_tbl.remove(group_name) self.project.write_geogrid_tbl(self.geogrid_tbl) self.populate_geog_data_tree() def on_add_geog_dataset_button_clicked(self): self.add_geog_dataset_button.hide() self.geog_dataset_form.show() self.geog_dataset_form_dataset.clear() self.geog_dataset_form_dataset.addItem('-') for root, subdirs, _ in os.walk(self.options.geog_dir): for subdir in subdirs: path = os.path.join(root, subdir) try: rel_path = os.path.relpath(path, self.options.geog_dir) except ValueError: if platform.system() == 'Windows': # Can happen with folder names that are reserved on Windows, like "con". # Those are silently ignored. pass else: # Should never happen, but who knows. raise else: self.geog_dataset_form_dataset.addItem(rel_path, path) var_names = sorted(self.geogrid_tbl.variables.keys()) self.geog_dataset_form_variable.clear() self.geog_dataset_form_variable.addItem('-') for var_name in var_names: self.geog_dataset_form_variable.addItem(var_name, var_name) # interpolation method dropdown is populated in change callback of variable dropdown self.geog_dataset_form_interp.clear() self.geog_dataset_form_interp.addItem('-') def geog_dataset_form_variable_changed(self, index: int): var_name = self.geog_dataset_form_variable.currentData() if var_name is None: return interp_options = set( group_options[GeogridTblKeys.INTERP_OPTION] for group_options in self.geogrid_tbl.variables[var_name].group_options.values()) self.geog_dataset_form_interp.clear() self.geog_dataset_form_interp.addItem('-') for interp in sorted(interp_options): self.geog_dataset_form_interp.addItem(interp, interp) def on_add_geog_dataset_form_button_clicked(self): dataset_path = self.geog_dataset_form_dataset.currentData() var_name = self.geog_dataset_form_variable.currentData() existing_interp = self.geog_dataset_form_interp.currentData() group_name = self.geog_dataset_form_group_name custom_interp = self.geog_dataset_form_custom_interp msg_bar = self.iface.messageBar() # type: QgsMessageBar if not dataset_path: msg_bar.pushCritical(PLUGIN_NAME, 'Dataset not selected.') return if not var_name: msg_bar.pushCritical(PLUGIN_NAME, 'Variable not selected.') return if not group_name.is_valid(): msg_bar.pushCritical(PLUGIN_NAME, 'Invalid group name.') return if not custom_interp.is_valid(): msg_bar.pushCritical(PLUGIN_NAME, 'Invalid custom interpolation.') return if not xor(bool(custom_interp.value()), bool(existing_interp)): msg_bar.pushCritical( PLUGIN_NAME, 'Must have either interpolation or custom interpolation.') return interp = custom_interp.value() if not interp: interp = existing_interp if var_name == 'LANDUSEF': landmask_water = read_wps_binary_index_file( dataset_path).landmask_water else: landmask_water = None self.geogrid_tbl.add(group_name.value(), var_name, dataset_path, self.options.geog_dir, interp, landmask_water) self.project.write_geogrid_tbl(self.geogrid_tbl) self.populate_geog_data_tree() self.add_geog_dataset_button.show() self.geog_dataset_form.hide() def on_cancel_geog_dataset_form_button_clicked(self) -> None: self.add_geog_dataset_button.show() self.geog_dataset_form.hide() #endregion Geographical Data #region Meteorological Data def create_meteorological_data_box(self) -> None: self.gbox_metdata = QGroupBox('Meteorological Data') vbox_metdata = QVBoxLayout() self.gbox_metdata.setLayout(vbox_metdata) gbox_avail_metdata = QGroupBox('Available Datasets') vbox_metdata.addWidget(gbox_avail_metdata) vbox_avail_metdata = QVBoxLayout() gbox_avail_metdata.setLayout(vbox_avail_metdata) self.tree_met_data = QTreeWidget() self.tree_met_data.setMinimumHeight(200) vbox_avail_metdata.addWidget(self.tree_met_data) self.tree_met_data.setHeaderItem( QTreeWidgetItem(['Product / Time Range'])) self.tree_met_data.setSelectionMode( QAbstractItemView.ContiguousSelection) self.tree_met_data.setContextMenuPolicy(Qt.CustomContextMenu) self.tree_met_data.customContextMenuRequested.connect( self.on_tree_met_data_open_context_menu) gbox_datasets_spec = QGroupBox('Selection') vbox_metdata.addWidget(gbox_datasets_spec) vbox_datasets_spec = QVBoxLayout() gbox_datasets_spec.setLayout(vbox_datasets_spec) selection_button = QPushButton('Use Dataset Selection from List') selection_button.clicked.connect( self.on_met_data_selection_button_clicked) vbox_datasets_spec.addWidget(selection_button) custom_button = QPushButton('Use Custom Dataset') custom_button.clicked.connect(self.on_met_data_custom_button_clicked) vbox_datasets_spec.addWidget(custom_button) hbox_current_config = QHBoxLayout() vbox_datasets_spec.addLayout(hbox_current_config) hbox_current_config.addWidget(QLabel('Current Configuration: ')) self.met_data_current_config_label = QLabel() hbox_current_config.addWidget(self.met_data_current_config_label) self.set_met_data_current_config_label() hbox_current_config.insertStretch(-1) def set_met_data_current_config_label(self) -> None: try: spec = self.project.met_dataset_spec except (KeyError, AttributeError): config = None else: time_range = [ d.strftime('%Y-%m-%d %H:%M') for d in spec['time_range'] ] dataset = spec['dataset'] product = spec['product'] if dataset: config = '{} / {}\n{} -\n{}'.format(spec['dataset'], spec['product'], *time_range) else: config = 'Custom dataset / {} GRIB files\n{} -\n{}'.format( len(spec['paths']), *time_range) lbl = self.met_data_current_config_label if not config: lbl.setText('Undefined') lbl.setStyleSheet(self.undef_label_style) else: lbl.setText(config) lbl.setStyleSheet('') def on_met_data_selection_button_clicked(self) -> None: msg_bar = self.iface.messageBar() # type: QgsMessageBar items = self.tree_met_data.selectedItems() if not items: msg_bar.pushInfo( PLUGIN_NAME, 'Select a time range or a sequence of time steps in the dataset tree before clicking this button.' ) return datas = [item.data(0, Qt.UserRole) for item in items] if any(data is None for data in datas): msg_bar.pushWarning( PLUGIN_NAME, 'Do not select dataset or product entries, only time ranges.') return is_file = [os.path.isfile(data) for data in datas] is_dir = [not f for f in is_file] if not all(is_file) and not all(is_dir): msg_bar.pushWarning( PLUGIN_NAME, 'Select either a time range or a sequence of time steps.') return if is_file[0]: if len(datas) == 1: msg_bar.pushWarning(PLUGIN_NAME, 'Select at least two time steps.') return file_paths = datas time_range_folder = os.path.dirname(file_paths[0]) meta_all, meta_files = read_grib_files_metadata(file_paths) else: assert len(datas) == 1 time_range_folder = datas[0] meta_all, meta_files = read_grib_folder_metadata(time_range_folder) product_folder = os.path.dirname(time_range_folder) dataset_folder = os.path.dirname(product_folder) self.project.met_dataset_spec = { 'paths': [meta.path for meta in meta_files], 'dataset': os.path.basename(dataset_folder), 'product': os.path.basename(product_folder), 'time_range': meta_all.time_range, 'interval_seconds': meta_all.interval_seconds } self.set_met_data_current_config_label() def on_met_data_custom_button_clicked(self) -> None: try: spec = self.project.met_dataset_spec if spec['dataset']: spec = None # not a custom dataset except KeyError: # met data not configured yet spec = None dialog = CustomMetDatasetDialog(self.options.ungrib_vtable_dir, spec) if not dialog.exec_(): return vtable_path = dialog.vtable_path # only store absolute path if not a standard WPS vtable if Path(vtable_path).parent == Path(self.options.ungrib_vtable_dir): vtable_path = Path(vtable_path).name self.project.met_dataset_spec = { 'paths': dialog.paths, 'base_folder': dialog.base_folder, 'vtable': vtable_path, 'time_range': [dialog.start_date, dialog.end_date], 'interval_seconds': dialog.interval_seconds } self.set_met_data_current_config_label() def on_tree_met_data_open_context_menu(self, position) -> None: item = self.tree_met_data.currentItem() if item is None: return data = item.data(0, Qt.UserRole) if data is None or not os.path.isdir(data): return menu = QMenu() view_vars_action = menu.addAction('View Variables') view_vars_action.triggered.connect( self.on_tree_met_data_view_variables_clicked) menu.exec_(self.tree_met_data.viewport().mapToGlobal(position)) def on_tree_met_data_view_variables_clicked(self) -> None: item = self.tree_met_data.currentItem() dataset_folder = item.data(0, Qt.UserRole) meta, _ = read_grib_folder_metadata(dataset_folder) title = 'Variables for {} ({})'.format(item.parent().text(0), item.text(0)) text = '<table>' for var_name, var_label in sorted(meta.variables.items()): text += '<tr><td>{}</td><td>{}</td></tr>'.format( var_name, var_label) text += '</table>' QMessageBox.information(self.iface.mainWindow(), title, text) def populate_met_data_tree(self) -> None: tree = self.tree_met_data tree.clear() root_dir = self.options.met_dir # TODO is this the right place to check? if not os.path.exists(root_dir): return for dataset_name in os.listdir(root_dir): dataset_path = os.path.join(root_dir, dataset_name) if not os.path.isdir(dataset_path): continue long_name = met_datasets.get(dataset_name) if long_name: label = '{}: {}'.format(dataset_name, long_name) else: label = dataset_name dataset_item = QTreeWidgetItem(tree) dataset_item.setText(0, label) if long_name: dataset_item.setToolTip(0, 'Dataset: ' + long_name) dataset_item.setExpanded(True) for product_name in os.listdir(dataset_path): product_folder = os.path.join(dataset_path, product_name) if not os.path.isdir(product_folder): continue product_item = QTreeWidgetItem(dataset_item) product_item.setText(0, product_name) product_item.setToolTip(0, 'Product: ' + product_name) product_item.setExpanded(True) for time_range_name in os.listdir(product_folder): time_range_folder = os.path.join(product_folder, time_range_name) if not os.path.isdir(time_range_folder): continue folder_meta, file_metas = read_grib_folder_metadata( time_range_folder) if not file_metas: continue # TODO disable item and subitems if bbox does not fully cover the outer-most domain time_range = '{} - {}'.format( *map(lambda d: d.strftime('%Y-%m-%d %H:%M'), folder_meta.time_range)) time_range_item = QTreeWidgetItem(product_item) time_range_item.setText(0, time_range) time_range_item.setToolTip(0, time_range_folder) time_range_item.setData(0, Qt.UserRole, time_range_folder) for file_meta in file_metas: if file_meta.time_range[0] == file_meta.time_range[1]: time = file_meta.time_range[0].strftime( '%Y-%m-%d %H:%M') else: time = '{} - {}'.format( *map(lambda d: d.strftime('%Y-%m-%d %H:%M'), file_meta.time_range)) file_item = QTreeWidgetItem(time_range_item) file_item.setText(0, time) file_item.setToolTip(0, file_meta.path) file_item.setData(0, Qt.UserRole, file_meta.path) #endregion Meteorological Data undef_label_style = (""" QLabel { font-weight: bold; color: #ff0000; } """)
class Window(QWidget): reminderevent = QtCore.pyqtSignal(str) def __init__(self): super().__init__() self.w = None self.title = 'Reminder' self.left = 500 self.top = 200 self.width_ = 600 self.height = 800 self.setStyleSheet("background-color: black;\n" "padding:0px;\n" "spacing:0px;\n") self.InitWindow() self.NoBoderStyleSheet = ("border :0px;\n") layout = self.LoadLayout() addReminder = QPushButton('Add') addReminder.setStyleSheet( "QPushButton{background-color: #52057b;\n color: White;\n border: 1px solid #52057b;\n border-radius:25px;\n padding:10px;\nspacing :10px; }" "QPushButton::hover{background-color : #31034a;}\n") addReminder.setMinimumHeight(50) addReminder.setFont(QFont('Open Sans', 15)) addReminder.clicked.connect(self.showAddReminderWindow) layout.addWidget(addReminder) self.setLayout(layout) self.reminderevent.connect(self.showAlertWindow) t = threading.Thread(target=self.showAlert) t.deamon = True t.start() def mousePressEvent(self, event): if event.button() == 1: self.isMouseButtonDown = True self.oldPos = event.globalPos() def mouseReleaseEvent(self, event): self.isMouseButtonDown = False def mouseMoveEvent(self, event): if self.isMouseButtonDown == True: delta = (event.globalPos() - self.oldPos) #print(delta) self.move(self.x() + delta.x(), self.y() + delta.y()) self.oldPos = event.globalPos() def showAlertWindow(self, amsg): msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText("Pending Task") msg.setInformativeText(amsg) msg.setWindowTitle("Task Alert") msg.exec_() def showAlert(self): file = FileUtil() today = datetime.today() time = datetime.now() systime = time.strftime("%H:%M") print(systime) sysdate = today.strftime("%d-%m-%Y") reminders, count = FileUtil.loadRemindersFromFile(file) for reminder in reminders: r = reminder.split(';') if r[1] == sysdate: if r[2] == systime: self.reminderevent.emit(r[0]) Time.sleep(60) def showAddReminderWindow(self, checked): if self.w is None: self.w = AddReminderWindow() #self.w.setWindowModality(Qt.WindowModal) self.w.show() self.w.Update.connect(self.UpdateReminders) #self.UpdateReminders() def Close(self): self.close() self.w.close() def DoneWithReminder(self, button): index = self.reminderContainer.indexOf(button.parent()) print(index) file = FileUtil() FileUtil.deleteReminder(self, index) button.parent().deleteLater() def upperFrame(self): frame = QHBoxLayout() frame.setContentsMargins(20, 0, 0, 0) Title = QLabel(self.title) Title.setFont(QFont('Open Sans', 15)) Title.setStyleSheet('color:white;\n') frame.addWidget(Title) frame.setSpacing(0) Close = QPushButton('') Close.setMinimumHeight(45) Close.setMinimumWidth(45) Close.setStyleSheet( "QPushButton{background-color: black;\n color: White;\n border: 1px solid black;\n border-radius:25px;\n padding:10px;\n image: url(X.png);\n}" "QPushButton::hover{background-color : #31034a;}\n") Close.clicked.connect(lambda: self.close()) Minimize = QPushButton('') Minimize.setStyleSheet( "QPushButton{background-color: black;\n color: White;\n border: 1px solid black;\n border-radius:25px;\n padding:10px;\n image: url(Min.png);\n}" "QPushButton::hover{background-color : #31034a;}\n") Minimize.clicked.connect(lambda: self.showMinimized()) Minimize.setMinimumHeight(45) Minimize.setMinimumWidth(45) frame.addStretch() frame.addWidget(Minimize) frame.addWidget(Close) tempBox = QGroupBox() tempBox.setMaximumHeight(45) tempBox.setStyleSheet("background-color: black;\n" "padding:0px;\n" + self.NoBoderStyleSheet) tempBox.setLayout(frame) return tempBox def LoadLayout(self): ScrollBoxStyleSheet = (""" QScrollBar:vertical { border: 0px solid #999999; background:white; width:5px; margin: 0px 0px 0px 0px; } QScrollBar::handle:vertical { background-color: gray ; min-height: 0px; border-radius:2px; } QScrollBar::add-line:vertical { background-color: white; height: 0px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar::sub-line:vertical { background-color:white; height: 0 px; subcontrol-position: top; subcontrol-origin: margin; } QScrollArea { border :0px; }""") root = QStackedWidget(self) reminders, count = FileUtil.loadRemindersFromFile(self) self.reminderContainer = QVBoxLayout() scroll = QScrollArea() scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) scroll.setWidgetResizable(True) scroll.setStyleSheet(ScrollBoxStyleSheet) reminderGroupBox = QGroupBox() self.reminderContainer.addStretch() reminderGroupBox.setLayout(self.reminderContainer) scroll.setWidget(reminderGroupBox) if (count != 0): for reminder in reminders: self.reminderContainer.addWidget(self.reminderUI(reminder)) root.setStyleSheet("padding:0px;\n" + self.NoBoderStyleSheet) #root.addWidget(reminderGroupBox) #rootBox = QGroupBox() #rootBox.setLayout(scroll) root.addWidget(scroll) templayout = QGridLayout() templayout.setContentsMargins(0, 0, 0, 10) templayout.setSpacing(0) templayout.addWidget(self.upperFrame()) templayout.addWidget(root) return templayout def UpdateReminders(self): while self.reminderContainer.count(): child = self.reminderContainer.takeAt(0) if child.widget(): child.widget().deleteLater() self.reminderContainer.addStretch() reminders, count = FileUtil.loadRemindersFromFile(self) for reminder in reminders: self.reminderContainer.addWidget(self.reminderUI(reminder)) def reminderUI(self, reminder): reminderList = reminder.split(';', 4) reminderTitle = QLabel(reminderList[0]) reminderDate = QLabel(reminderList[1]) reminderStartTime = QLabel(reminderList[2]) reminderEndTime = QLabel(reminderList[3]) reminderTitle.setFont(QFont('Open Sans', 15)) reminderDate.setFont(QFont('Open Sans', 15)) reminderStartTime.setFont(QFont('Open Sans', 15)) reminderEndTime.setFont(QFont('Open Sans', 15)) reminderBox = QVBoxLayout() reminderBox.addWidget(reminderTitle) reminderBox.addWidget(reminderDate) reminderBox.addWidget(reminderStartTime) reminderBox.addWidget(reminderEndTime) reminderBox2 = QHBoxLayout() doneButton = QPushButton('Done') doneButton.setStyleSheet("background-color: White;\n" "border: 1px solid white;\n" "border-radius:25px;\n" "padding:10px;\n" "color: Black;\n") doneButton.setMinimumHeight(50) doneButton.setMaximumWidth(100) doneButton.clicked.connect(lambda: self.DoneWithReminder(doneButton)) temp = QGroupBox() temp.setStyleSheet(self.NoBoderStyleSheet) temp.setLayout(reminderBox) reminderBox2.addWidget(temp) reminderBox2.addWidget(doneButton) temp2 = QGroupBox() temp2.setMaximumHeight(150) temp2.setStyleSheet('border-radius:25px;\n' "background-color: #a40eda;\n" "border: 1px solid #a40eda;\n" "color: White;\n") temp2.setLayout(reminderBox2) return temp2 def InitWindow(self): self.setWindowTitle(self.title) self.setGeometry(self.left, self.top, self.width_, self.height) flags = QtCore.Qt.WindowFlags(QtCore.Qt.FramelessWindowHint) self.setWindowFlags(flags) self.show()
class MainView(QWidget): """ Represents the main content of the application (containing the source list and main context view). """ CSS = ''' #view_holder { background-color: #fff; } ''' def __init__(self, parent): super().__init__(parent) self.setStyleSheet(self.CSS) self.layout = QHBoxLayout(self) self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(0) self.setLayout(self.layout) self.source_list = SourceList() self.source_list.itemSelectionChanged.connect(self.on_source_changed) self.view_layout = QVBoxLayout() self.view_layout.setContentsMargins(0, 0, 0, 0) self.view_holder = QWidget() self.view_holder.setObjectName('view_holder') # Set css id self.view_holder.setLayout(self.view_layout) self.layout.addWidget(self.source_list) self.layout.addWidget(self.view_holder) def setup(self, controller): """ Pass through the controller object to this widget. """ self.controller = controller self.source_list.setup(controller) def show_sources(self, sources: List[Source]): """ Update the left hand sources list in the UI with the passed in list of sources. """ self.source_list.update(sources) def on_source_changed(self): """ Show conversation for the currently-selected source if it hasn't been deleted. If the current source no longer exists, clear the conversation for that source. """ source = self.source_list.get_current_source() if source: conversation_wrapper = SourceConversationWrapper( source, self.controller) self.set_conversation(conversation_wrapper) else: self.clear_conversation() def set_conversation(self, widget): """ Update the view holder to contain the referenced widget. """ old_widget = self.view_layout.takeAt(0) if old_widget: old_widget.widget().hide() self.view_layout.addWidget(widget) widget.show() def clear_conversation(self): while self.view_layout.count(): child = self.view_layout.takeAt(0) if child.widget(): child.widget().deleteLater()