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 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 RentingList(QWidget): def __init__(self, parent): super(RentingList, self).__init__() self.parent = parent self.setMaximumWidth(1000) self.current_theme = self.parent.current_theme self.current_font = self.parent.current_font self.current_sf = self.parent.current_sf if (self.current_theme == 'Dark'): self.darkTheme() elif (self.current_theme == 'Light'): self.lightTheme() else: self.classicTheme() self.requests = [] self.layout = QVBoxLayout() self.layout.setContentsMargins(30, 30, 30, 30) self.layout.setSpacing(10) self.layout.setAlignment(QtCore.Qt.AlignTop) self.setLayout(self.layout) def addRequests(self): self.requests = [] while (not self.layout.isEmpty()): self.layout.removeWidget(self.layout.itemAt(0).widget()) stats = self.parent.parent.sender.get_job_statuses() for job in stats: request = RentingRequest(self, job) self.requests.append(request) self.layout.addWidget(request, alignment=QtCore.Qt.AlignTop) if (len(self.requests) == 0): request = EmptyRequest(self) request.requestLabel.setText('You have no renting requests') self.requests.append(request) self.layout.addWidget(request, alignment=QtCore.Qt.AlignTop) return 0 return len(self.requests) def darkTheme(self): self.setStyleSheet('background: rgb(69, 69, 69);\n' 'color: white;\n' 'border: 0px solid white;\n' 'margin: 0px;\n') def lightTheme(self): self.setStyleSheet('background: rgb(204, 204, 204);\n' 'color: white;\n' 'border: 0px solid black;\n') def classicTheme(self): self.setStyleSheet('background: rgb(0, 23, 37);\n' 'color: white;\n' 'border: 0px solid white;\n')
class GodWidget(QWidget): ''' "Our lord and savior, the holy child of window-shua, there is no widget above thee." - 6|6 The highest level composed widget which contains layouts for organizing charts as well as other sub-widgets used to control or modify them. ''' def __init__( self, parent=None, ) -> None: super().__init__(parent) self.hbox = QHBoxLayout(self) self.hbox.setContentsMargins(0, 0, 0, 0) self.hbox.setSpacing(6) self.hbox.setAlignment(Qt.AlignTop) self.vbox = QVBoxLayout() self.vbox.setContentsMargins(0, 0, 0, 0) self.vbox.setSpacing(2) self.vbox.setAlignment(Qt.AlignTop) self.hbox.addLayout(self.vbox) # self.toolbar_layout = QHBoxLayout() # self.toolbar_layout.setContentsMargins(0, 0, 0, 0) # self.vbox.addLayout(self.toolbar_layout) # self.init_timeframes_ui() # self.init_strategy_ui() # self.vbox.addLayout(self.hbox) self._chart_cache: dict[str, LinkedSplits] = {} self.linkedsplits: Optional[LinkedSplits] = None # assigned in the startup func `_async_main()` self._root_n: trio.Nursery = None self._widgets: dict[str, QWidget] = {} self._resizing: bool = False # def init_timeframes_ui(self): # self.tf_layout = QHBoxLayout() # self.tf_layout.setSpacing(0) # self.tf_layout.setContentsMargins(0, 12, 0, 0) # time_frames = ('1M', '5M', '15M', '30M', '1H', '1D', '1W', 'MN') # btn_prefix = 'TF' # for tf in time_frames: # btn_name = ''.join([btn_prefix, tf]) # btn = QtWidgets.QPushButton(tf) # # TODO: # btn.setEnabled(False) # setattr(self, btn_name, btn) # self.tf_layout.addWidget(btn) # self.toolbar_layout.addLayout(self.tf_layout) # XXX: strat loader/saver that we don't need yet. # def init_strategy_ui(self): # self.strategy_box = StrategyBoxWidget(self) # self.toolbar_layout.addWidget(self.strategy_box) def set_chart_symbol( self, symbol_key: str, # of form <fqsn>.<providername> linkedsplits: LinkedSplits, # type: ignore ) -> None: # re-sort org cache symbol list in LIFO order cache = self._chart_cache cache.pop(symbol_key, None) cache[symbol_key] = linkedsplits def get_chart_symbol( self, symbol_key: str, ) -> LinkedSplits: # type: ignore return self._chart_cache.get(symbol_key) async def load_symbol( self, providername: str, symbol_key: str, loglevel: str, reset: bool = False, ) -> trio.Event: ''' Load a new contract into the charting app. Expects a ``numpy`` structured array containing all the ohlcv fields. ''' # our symbol key style is always lower case symbol_key = symbol_key.lower() # fully qualified symbol name (SNS i guess is what we're making?) fqsn = '.'.join([symbol_key, providername]) linkedsplits = self.get_chart_symbol(fqsn) order_mode_started = trio.Event() if not self.vbox.isEmpty(): # XXX: this is CRITICAL especially with pixel buffer caching self.linkedsplits.hide() self.linkedsplits.unfocus() # XXX: pretty sure we don't need this # remove any existing plots? # XXX: ahh we might want to support cache unloading.. # self.vbox.removeWidget(self.linkedsplits) # switching to a new viewable chart if linkedsplits is None or reset: from ._display import display_symbol_data # we must load a fresh linked charts set linkedsplits = LinkedSplits(self) # spawn new task to start up and update new sub-chart instances self._root_n.start_soon( display_symbol_data, self, providername, symbol_key, loglevel, order_mode_started, ) self.set_chart_symbol(fqsn, linkedsplits) self.vbox.addWidget(linkedsplits) linkedsplits.show() linkedsplits.focus() await trio.sleep(0) else: # symbol is already loaded and ems ready order_mode_started.set() # TODO: # - we'll probably want per-instrument/provider state here? # change the order config form over to the new chart # XXX: since the pp config is a singleton widget we have to # also switch it over to the new chart's interal-layout # self.linkedsplits.chart.qframe.hbox.removeWidget(self.pp_pane) chart = linkedsplits.chart # chart is already in memory so just focus it linkedsplits.show() linkedsplits.focus() linkedsplits.graphics_cycle() await trio.sleep(0) # resume feeds *after* rendering chart view asap chart.resume_all_feeds() # TODO: we need a check to see if the chart # last had the xlast in view, if so then shift so it's # still in view, if the user was viewing history then # do nothing yah? chart.default_view() self.linkedsplits = linkedsplits symbol = linkedsplits.symbol if symbol is not None: self.window.setWindowTitle( f'{symbol.front_fqsn()} ' f'tick:{symbol.tick_size}' ) return order_mode_started def focus(self) -> None: ''' Focus the top level widget which in turn focusses the chart ala "view mode". ''' # go back to view-mode focus (aka chart focus) self.clearFocus() self.linkedsplits.chart.setFocus() def resizeEvent(self, event: QtCore.QEvent) -> None: ''' Top level god widget resize handler. Where we do UX magic to make things not suck B) ''' if self._resizing: return self._resizing = True log.info('God widget resize') for name, widget in self._widgets.items(): widget.on_resize() self._resizing = False
class LeasingList(QWidget): def __init__(self, parent): super(LeasingList, self).__init__() self.parent = parent self.setMaximumWidth(1000) self.current_theme = self.parent.current_theme self.current_font = self.parent.current_font self.current_sf = self.parent.current_sf if (self.current_theme == 'Dark'): self.darkTheme() elif (self.current_theme == 'Light'): self.lightTheme() else: self.classicTheme() self.requests = [] self.layout = QVBoxLayout() self.layout.setContentsMargins(30, 30, 30, 50) self.layout.setSpacing(10) self.layout.setAlignment(QtCore.Qt.AlignTop) self.setLayout(self.layout) def addRequests(self): self.requests = [] while (not self.layout.isEmpty()): self.layout.removeWidget() requests = self.parent.parent.receiver.get_job_notifications() for r in requests: request = LeasingRequest(self) request.orderId = r[0] request.renterUserName = r[1] request.jobId = r[2] request.jobDesc = r[3] request.jobMode = r[4] request.status = r[5] request.price = r[6] request.setRenter(request.renterUserName) self.requests.append(request) self.layout.addWidget(request) if (len(self.requests) == 0): request = EmptyRequest(self) request.requestLabel.setText('You have no leasing requests') self.requests.append(request) self.layout.addWidget(request, alignment=QtCore.Qt.AlignTop) return 0 return len(self.requests) def darkTheme(self): self.setStyleSheet('background: rgb(69, 69, 69);\n' 'color: white;\n' 'border: 0px solid white;\n') def lightTheme(self): self.setStyleSheet('background: rgb(204, 204, 204);\n' 'color: white;\n' 'border: 0px solid black;\n') def classicTheme(self): self.setStyleSheet('background: rgb(0, 23, 37);\n' 'color: white;\n' 'border: 0px solid white;\n')