diff --git a/piker/brokers/ib.py b/piker/brokers/ib.py index 4139d60d..788bc829 100644 --- a/piker/brokers/ib.py +++ b/piker/brokers/ib.py @@ -1204,6 +1204,11 @@ async def backfill_bars( https://github.com/pikers/piker/issues/128 """ + if platform.system() == 'Windows': + log.warning( + 'Decreasing history query count to 4 since, windows...') + count = 4 + out, fails = await get_bars(sym) if out is None: diff --git a/piker/clearing/_ems.py b/piker/clearing/_ems.py index 3756daf4..1567f795 100644 --- a/piker/clearing/_ems.py +++ b/piker/clearing/_ems.py @@ -1054,7 +1054,7 @@ async def _emsd_main( # signal to client that we're started and deliver # all known pps and accounts for this ``brokerd``. - await ems_ctx.started((pp_msgs, relay.accounts)) + await ems_ctx.started((pp_msgs, list(relay.accounts))) # establish 2-way stream with requesting order-client and # begin handling inbound order requests and updates diff --git a/piker/config.py b/piker/config.py index 70d9c9c5..93a47378 100644 --- a/piker/config.py +++ b/piker/config.py @@ -60,7 +60,7 @@ def repodir(): """ dirpath = os.path.abspath( # we're 3 levels down in **this** module file - dirname(dirname(dirname(os.path.realpath(__file__)))) + dirname(dirname(os.path.realpath(__file__))) ) return dirpath @@ -73,7 +73,7 @@ def load( path = path or get_broker_conf_path() if not os.path.isfile(path): shutil.copyfile( - os.path.join(repodir(), 'data/brokers.toml'), + os.path.join(repodir(), 'config', 'brokers.toml'), path, ) diff --git a/piker/ui/_chart.py b/piker/ui/_chart.py index a9350d97..6048ca42 100644 --- a/piker/ui/_chart.py +++ b/piker/ui/_chart.py @@ -112,6 +112,9 @@ class GodWidget(QWidget): # 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) @@ -259,7 +262,16 @@ class GodWidget(QWidget): Where we do UX magic to make things not suck B) ''' - log.debug('god widget resize') + 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 ChartnPane(QFrame): diff --git a/piker/ui/_exec.py b/piker/ui/_exec.py index 284ddd9b..7b69acef 100644 --- a/piker/ui/_exec.py +++ b/piker/ui/_exec.py @@ -61,7 +61,9 @@ _do_overrides() # XXX: pretty sure none of this shit works on linux as per: # https://bugreports.qt.io/browse/QTBUG-53022 # it seems to work on windows.. no idea wtf is up. +is_windows = False if platform.system() == "Windows": + is_windows = True # Proper high DPI scaling is available in Qt >= 5.6.0. This attibute # must be set before creating the application @@ -182,6 +184,8 @@ def run_qtractor( window.main_widget = main_widget window.setCentralWidget(instance) + if is_windows: + window.configure_to_desktop() # actually render to screen window.show() diff --git a/piker/ui/_search.py b/piker/ui/_search.py index f1fc1f4e..8cac6b1a 100644 --- a/piker/ui/_search.py +++ b/piker/ui/_search.py @@ -49,7 +49,6 @@ from PyQt5 import QtCore from PyQt5 import QtWidgets from PyQt5.QtCore import ( Qt, - # QSize, QModelIndex, QItemSelectionModel, ) @@ -126,6 +125,10 @@ class CompleterView(QTreeView): # self.setSizeAdjustPolicy(QAbstractScrollArea.AdjustIgnored) # ux settings + self.setSizePolicy( + QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Expanding, + ) self.setItemsExpandable(True) self.setExpandsOnDoubleClick(False) self.setAnimated(False) @@ -153,23 +156,58 @@ class CompleterView(QTreeView): self.setStyleSheet(f"font: {size}px") - def resize(self): + # def resizeEvent(self, event: 'QEvent') -> None: + # event.accept() + # super().resizeEvent(event) + + def on_resize(self) -> None: + ''' + Resize relay event from god. + + ''' + self.resize_to_results() + + def resize_to_results(self): model = self.model() cols = model.columnCount() + # rows = model.rowCount() + col_w_tot = 0 for i in range(cols): self.resizeColumnToContents(i) + col_w_tot += self.columnWidth(i) - # inclusive of search bar and header "rows" in pixel terms - rows = 100 - # max_rows = 8 # 6 + search and headers - row_px = self.rowHeight(self.currentIndex()) - # print(f'font_h: {font_h}\n px_height: {px_height}') + win = self.window() + win_h = win.height() + edit_h = self.parent().bar.height() + sb_h = win.statusBar().height() # TODO: probably make this more general / less hacky - self.setMinimumSize(self.width(), rows * row_px) - self.setMaximumSize(self.width() + 10, rows * row_px) - self.setFixedWidth(333) + # we should figure out the exact number of rows to allow + # inclusive of search bar and header "rows", in pixel terms. + # Eventually when we have an "info" widget below the results we + # will want space for it and likely terminating the results-view + # space **exactly on a row** would be ideal. + # if row_px > 0: + # rows = ceil(window_h / row_px) - 4 + # else: + # rows = 16 + # self.setFixedHeight(rows * row_px) + # self.resize(self.width(), rows * row_px) + + # NOTE: if the heigh set here is **too large** then the resize + # event will perpetually trigger as the window causes some kind + # of recompute of callbacks.. so we have to ensure it's limited. + h = win_h - (edit_h + 1.666*sb_h) + assert h > 0 + self.setFixedHeight(round(h)) + + # size to width of longest result seen thus far + # TODO: should we always dynamically scale to longest result? + if self.width() < col_w_tot: + self.setFixedWidth(col_w_tot) + + self.update() def is_selecting_d1(self) -> bool: cidx = self.selectionModel().currentIndex() @@ -218,7 +256,8 @@ class CompleterView(QTreeView): idx: QModelIndex, ) -> QStandardItem: - '''Select and return the item at index ``idx``. + ''' + Select and return the item at index ``idx``. ''' sel = self.selectionModel() @@ -233,7 +272,8 @@ class CompleterView(QTreeView): return model.itemFromIndex(idx) def select_first(self) -> QStandardItem: - '''Select the first depth >= 2 entry from the completer tree and + ''' + Select the first depth >= 2 entry from the completer tree and return it's item. ''' @@ -296,7 +336,8 @@ class CompleterView(QTreeView): section: str, ) -> Optional[QModelIndex]: - '''Find the *first* depth = 1 section matching ``section`` in + ''' + Find the *first* depth = 1 section matching ``section`` in the tree and return its index. ''' @@ -334,7 +375,7 @@ class CompleterView(QTreeView): else: model.setItem(idx.row(), 1, QStandardItem()) - self.resize() + self.resize_to_results() return idx else: @@ -405,7 +446,7 @@ class CompleterView(QTreeView): def show_matches(self) -> None: self.show() - self.resize() + self.resize_to_results() class SearchBar(Edit): @@ -425,6 +466,7 @@ class SearchBar(Edit): self.godwidget = godwidget super().__init__(parent, **kwargs) self.view: CompleterView = view + godwidget._widgets[view.mode_name] = view def show(self) -> None: super().show() @@ -459,7 +501,7 @@ class SearchWidget(QtWidgets.QWidget): # size it as we specify self.setSizePolicy( QtWidgets.QSizePolicy.Fixed, - QtWidgets.QSizePolicy.Fixed, + QtWidgets.QSizePolicy.Expanding, ) self.godwidget = godwidget diff --git a/piker/ui/_style.py b/piker/ui/_style.py index b51331ab..c60cb077 100644 --- a/piker/ui/_style.py +++ b/piker/ui/_style.py @@ -22,6 +22,7 @@ import math import pyqtgraph as pg from PyQt5 import QtCore, QtGui +from PyQt5.QtCore import Qt, QCoreApplication from qdarkstyle import DarkPalette from ..log import get_logger @@ -121,6 +122,9 @@ class DpiAwareFont: dpi = mn_dpi mult = 1.0 + + # No implicit DPI scaling was done by the DE so let's engage + # some hackery ad-hoc scaling shiat. # dpi is likely somewhat scaled down so use slightly larger font size if scale >= 1.1 and self._font_size: @@ -131,9 +135,19 @@ class DpiAwareFont: if scale >= 1.5: mult = 1.375 - # TODO: this multiplier should probably be determined from - # relative aspect ratios or something? - inches *= mult + # TODO: this multiplier should probably be determined from + # relative aspect ratios or something? + inches *= mult + + # XXX: if additionally we detect a known DE scaling factor we + # also scale *up* our font size on top of the existing + # heuristical (aka no clue why it works) scaling from the block + # above XD + if ( + hasattr(Qt, 'AA_EnableHighDpiScaling') + and QCoreApplication.testAttribute(Qt.AA_EnableHighDpiScaling) + ): + inches *= round(scale) # TODO: we might want to fiddle with incrementing font size by # +1 for the edge cases above. it seems doing it via scaling is diff --git a/piker/ui/_window.py b/piker/ui/_window.py index 6a0c1d8b..46c35e52 100644 --- a/piker/ui/_window.py +++ b/piker/ui/_window.py @@ -153,8 +153,7 @@ class MainWindow(QtGui.QMainWindow): # XXX: for tiling wms this should scale # with the alloted window size. # TODO: detect for tiling and if untrue set some size? - # size = (300, 500) - size = (0, 0) + size = (300, 500) title = 'piker chart (ur symbol is loading bby)' @@ -165,6 +164,7 @@ class MainWindow(QtGui.QMainWindow): self._status_bar: QStatusBar = None self._status_label: QLabel = None + self._size: Optional[tuple[int, int]] = None @property def mode_label(self) -> QtGui.QLabel: @@ -269,6 +269,30 @@ class MainWindow(QtGui.QMainWindow): assert screen, "Wow Qt is dumb as shit and has no screen..." return screen + def configure_to_desktop( + self, + size: Optional[tuple[int, int]] = None, + + ) -> None: + ''' + Explicitly size the window dimensions (for stacked window + managers). + + For tina systems (like windoze) try to do a sane window size on + startup. + + ''' + # https://stackoverflow.com/a/18975846 + if not size and not self._size: + app = QtGui.QApplication.instance() + geo = self.current_screen().geometry() + h, w = geo.height(), geo.width() + self.setMaximumSize(w, h) + # use approx 1/3 of the area of the screen by default + self._size = round(w * .666), round(h * .666) + + self.resize(*size or self._size) + # singleton app per actor _qt_win: QtGui.QMainWindow = None diff --git a/piker/ui/order_mode.py b/piker/ui/order_mode.py index 755e72f3..437e4ca5 100644 --- a/piker/ui/order_mode.py +++ b/piker/ui/order_mode.py @@ -22,6 +22,7 @@ from contextlib import asynccontextmanager from dataclasses import dataclass, field from functools import partial from pprint import pformat +import platform import time from typing import Optional, Dict, Callable, Any import uuid @@ -429,6 +430,9 @@ class OrderMode: # TODO: make this not trash. # XXX: linux only for now + if platform.system() == "Windows": + return + result = await trio.run_process( [ 'notify-send',