First working attempt of search results view scaling

Scales the "view" instance that holds search results to the size of the
accompanying "slow chart" for which the search pane is a "sidepane".
A lot of mucking about was required due to resizing of the view
seemingly feeding back into window resizing and further implementing the
sizing logic such that the parent `QSplitter` can be resized as the
user's whim as well.

Details,
- add a `CompleterView._init: bool` which is set once (and only once)
  after startup where the first display of the current symbol/feed is
  shown allowing and a single *width* padding applied once at startup
  to ensure we don't have an awkward line to the right of the longest
  result.
- in `.resize_to_results()` only apply a minimum height to the view
  using `.setMinimumHeight()` with a down-scaled (`0.91` for now) height
  value from input.
- re-implement `CompleterView.show_matches()` to accept and optional
  width, heigh tuple and when not supplied pull the slow chart's
  dimensions and pass as input to the resize method.
- Make `SearchWidget` x dim sizing policy "fixed".
- register the `SearchWidget` for resize events with god.
- add `.show_only_cache_entries()` for easy results clearing.
- add `.space_dims()` to retrieve slow linked-charts dimensions.
- implement `SearchWidget.on_resize()` which is the caller of all the
  previously mentioned resizing routines.
- do resizing and cache entry showing on search loop startup and be sure
  to clear to cache when the user selects a symbol-feed with Enter.
history_view
Tyler Goodlet 2022-09-09 21:46:57 -04:00
parent 1e81feee46
commit d11dc787a1
1 changed files with 145 additions and 88 deletions

View File

@ -138,6 +138,7 @@ class CompleterView(QTreeView):
model.setHorizontalHeaderLabels(labels) model.setHorizontalHeaderLabels(labels)
self._font_size: int = 0 # pixels self._font_size: int = 0 # pixels
self._init: bool = False
async def on_pressed(self, idx: QModelIndex) -> None: async def on_pressed(self, idx: QModelIndex) -> None:
''' '''
@ -145,7 +146,7 @@ class CompleterView(QTreeView):
''' '''
search = self.parent() search = self.parent()
await search.chart_current_item(clear_to_cache=False) await search.chart_current_item()
search.focus() search.focus()
def set_font_size(self, size: int = 18): def set_font_size(self, size: int = 18):
@ -157,56 +158,58 @@ class CompleterView(QTreeView):
self.setStyleSheet(f"font: {size}px") self.setStyleSheet(f"font: {size}px")
# def resizeEvent(self, event: 'QEvent') -> None: def resize_to_results(
# event.accept() self,
# super().resizeEvent(event) w: Optional[float] = 0,
h: Optional[float] = None,
def on_resize(self) -> None: ) -> None:
'''
Resize relay event from god.
'''
self.resize_to_results()
def resize_to_results(self):
model = self.model() model = self.model()
cols = model.columnCount() cols = model.columnCount()
# rows = model.rowCount() # rows = model.rowCount()
cidx = self.selectionModel().currentIndex()
row_h = self.rowHeight(cidx)
# print(f'row_h: {row_h}')
col_w_tot = 0 col_w_tot = 0
for i in range(cols): for i in range(cols):
# only slap in a rows's height's worth
# of padding once at startup.. no idea
if (
not self._init
and row_h
):
col_w_tot = row_h
self._init = True
self.resizeColumnToContents(i) self.resizeColumnToContents(i)
col_w_tot += self.columnWidth(i) col_w_tot += self.columnWidth(i)
win = self.window() # TODO: probably make this more general / less hacky we should
win_h = win.height() # figure out the exact number of rows to allow inclusive of
edit_h = self.parent().bar.height() # search bar and header "rows", in pixel terms. Eventually when
sb_h = win.statusBar().height() # 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.
# TODO: probably make this more general / less hacky if h:
# we should figure out the exact number of rows to allow h: int = round(h)
# inclusive of search bar and header "rows", in pixel terms. # mn = row_h * 2
# Eventually when we have an "info" widget below the results we # self.setMinimumHeight(mn)
# will want space for it and likely terminating the results-view # abs_mx = h - row_h
# space **exactly on a row** would be ideal. # print(f'set min {abs_mx}')
# if row_px > 0: # self.setFixedHeight(round(0.9 * h))
# rows = ceil(window_h / row_px) - 4 self.setMinimumHeight(round(0.91 * h))
# 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 # self.setMaximumHeight(0.9 * abs_mx)
# event will perpetually trigger as the window causes some kind # 6 result row slots and 3 rows for sections and headers
# of recompute of callbacks.. so we have to ensure it's limited. # mn = (6 + 3) * row_h
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 # dyncamically size to width of longest result seen
# TODO: should we always dynamically scale to longest result? curr_w = self.width()
if self.width() < col_w_tot: if curr_w < col_w_tot:
self.setFixedWidth(col_w_tot) self.setMinimumWidth(col_w_tot)
self.update() self.update()
@ -376,8 +379,6 @@ class CompleterView(QTreeView):
else: else:
model.setItem(idx.row(), 1, QStandardItem()) model.setItem(idx.row(), 1, QStandardItem())
self.resize_to_results()
return idx return idx
else: else:
return None return None
@ -445,9 +446,22 @@ class CompleterView(QTreeView):
self.show_matches() self.show_matches()
def show_matches(self) -> None: def show_matches(
self,
wh: Optional[tuple[float, float]] = None,
) -> None:
if wh:
self.resize_to_results(*wh)
else:
# case where it's just an update from results and *NOT*
# a resize of some higher level parent-container widget.
search = self.parent()
w, h = search.space_dims()
self.resize_to_results(w=w)
self.show() self.show()
self.resize_to_results()
class SearchBar(Edit): class SearchBar(Edit):
@ -467,18 +481,15 @@ class SearchBar(Edit):
self.godwidget = godwidget self.godwidget = godwidget
super().__init__(parent, **kwargs) super().__init__(parent, **kwargs)
self.view: CompleterView = view self.view: CompleterView = view
godwidget._widgets[view.mode_name] = view
def show(self) -> None:
super().show()
self.view.show_matches()
def unfocus(self) -> None: def unfocus(self) -> None:
self.parent().hide() self.parent().hide()
self.clearFocus() self.clearFocus()
def hide(self) -> None:
if self.view: if self.view:
self.view.hide() self.view.hide()
super().hide()
class SearchWidget(QtWidgets.QWidget): class SearchWidget(QtWidgets.QWidget):
@ -497,15 +508,19 @@ class SearchWidget(QtWidgets.QWidget):
parent=None, parent=None,
) -> None: ) -> None:
super().__init__(parent or godwidget) super().__init__(parent)
# size it as we specify # size it as we specify
self.setSizePolicy( self.setSizePolicy(
# QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Fixed,
# QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed,
QtWidgets.QSizePolicy.Expanding,
) )
self.godwidget = godwidget self.godwidget = godwidget
godwidget.reg_for_resize(self)
self._last_h: float = 0
self.vbox = QtWidgets.QVBoxLayout(self) self.vbox = QtWidgets.QVBoxLayout(self)
self.vbox.setContentsMargins(0, 4, 4, 0) self.vbox.setContentsMargins(0, 4, 4, 0)
@ -555,26 +570,24 @@ class SearchWidget(QtWidgets.QWidget):
self.vbox.setAlignment(self.view, Qt.AlignTop | Qt.AlignLeft) self.vbox.setAlignment(self.view, Qt.AlignTop | Qt.AlignLeft)
def focus(self) -> None: def focus(self) -> None:
godw = self.godwidget
if self.view.model().rowCount(QModelIndex()) == 0:
# fill cache list if nothing existing
self.view.set_section_entries(
'cache',
list(reversed(godw._chart_cache)),
clear_all=True,
)
hist_linked = godw.hist_linked
hist_chart = hist_linked.chart
if hist_chart:
rt_linked = godw.rt_linked
hist_chart.qframe.set_sidepane(self)
hist_linked.resize_sidepanes(from_linked=rt_linked)
self.show() self.show()
self.bar.focus() self.bar.focus()
def show_only_cache_entries(self) -> None:
'''
Clear the search results view and show only cached (aka recently
loaded with active data) feeds in the results section.
'''
godw = self.godwidget
print('showing cache only')
self.view.set_section_entries(
'cache',
list(reversed(godw._chart_cache)),
# remove all other completion results except for cache
clear_all=True,
)
def get_current_item(self) -> Optional[tuple[str, str]]: def get_current_item(self) -> Optional[tuple[str, str]]:
'''Return the current completer tree selection as '''Return the current completer tree selection as
a tuple ``(parent: str, child: str)`` if valid, else ``None``. a tuple ``(parent: str, child: str)`` if valid, else ``None``.
@ -624,11 +637,11 @@ class SearchWidget(QtWidgets.QWidget):
return None return None
provider, symbol = value provider, symbol = value
chart = self.godwidget godw = self.godwidget
log.info(f'Requesting symbol: {symbol}.{provider}') log.info(f'Requesting symbol: {symbol}.{provider}')
await chart.load_symbol( await godw.load_symbol(
provider, provider,
symbol, symbol,
'info', 'info',
@ -645,25 +658,60 @@ class SearchWidget(QtWidgets.QWidget):
# Re-order the symbol cache on the chart to display in # Re-order the symbol cache on the chart to display in
# LIFO order. this is normally only done internally by # LIFO order. this is normally only done internally by
# the chart on new symbols being loaded into memory # the chart on new symbols being loaded into memory
chart.set_chart_symbol( godw.set_chart_symbol(
fqsn, ( fqsn, (
chart.linkedsplits, godw.hist_linked,
self.godwidget.hist_linked, godw.rt_linked,
) )
) )
self.show_only_cache_entries()
self.view.set_section_entries( # self.focus()
'cache',
values=list(reversed(chart._chart_cache)),
# remove all other completion results except for cache
clear_all=True,
)
self.focus()
self.bar.focus() self.bar.focus()
return fqsn return fqsn
def space_dims(self) -> tuple[float, float]:
'''
Compute and return the "available space dimentions" for this
search widget in terms of px space for results by return the
pair of width and height.
'''
# XXX: dun need dis rite?
# win = self.window()
# win_h = win.height()
# sb_h = win.statusBar().height()
godw = self.godwidget
hl = godw.hist_linked
edit_h = self.bar.height()
h = hl.height() - edit_h
w = hl.width()
return w, h
def on_resize(self) -> None:
'''
Resize relay event from god, resize all child widgets.
Right now this is just view to contents and/or the fast chart
height.
'''
# 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.
w, h = self.space_dims()
if (
not self._last_h
or self._last_h != h
):
# print(
# f'w: {w}\n'
# f'h: {h}\n'
# f'._last_h: {self._last_h}\n'
# )
self._last_h = h
self.bar.view.show_matches(wh=(w, h))
_search_active: trio.Event = trio.Event() _search_active: trio.Event = trio.Event()
_search_enabled: bool = False _search_enabled: bool = False
@ -747,6 +795,10 @@ async def fill_results(
matches = defaultdict(list) matches = defaultdict(list)
has_results: defaultdict[str, set[str]] = defaultdict(set) has_results: defaultdict[str, set[str]] = defaultdict(set)
# show cached feed list at startup
search.show_only_cache_entries()
search.on_resize()
while True: while True:
await _search_active.wait() await _search_active.wait()
period = None period = None
@ -859,8 +911,7 @@ async def handle_keyboard_input(
godwidget = search.godwidget godwidget = search.godwidget
view = bar.view view = bar.view
view.set_font_size(bar.dpi_font.px_size) view.set_font_size(bar.dpi_font.px_size)
send, recv = trio.open_memory_channel(616)
send, recv = trio.open_memory_channel(16)
async with trio.open_nursery() as n: async with trio.open_nursery() as n:
@ -875,6 +926,10 @@ async def handle_keyboard_input(
) )
) )
bar.focus()
search.show_only_cache_entries()
await trio.sleep(0)
async for kbmsg in recv_chan: async for kbmsg in recv_chan:
event, etype, key, mods, txt = kbmsg.to_tuple() event, etype, key, mods, txt = kbmsg.to_tuple()
@ -885,10 +940,11 @@ async def handle_keyboard_input(
ctl = True ctl = True
if key in (Qt.Key_Enter, Qt.Key_Return): if key in (Qt.Key_Enter, Qt.Key_Return):
await search.chart_current_item(clear_to_cache=True)
_search_enabled = False _search_enabled = False
continue await search.chart_current_item(clear_to_cache=True)
view.show_matches()
search.show_only_cache_entries()
search.focus()
elif not ctl and not bar.text(): elif not ctl and not bar.text():
# if nothing in search text show the cache # if nothing in search text show the cache
@ -905,7 +961,7 @@ async def handle_keyboard_input(
Qt.Key_Space, # i feel like this is the "native" one Qt.Key_Space, # i feel like this is the "native" one
Qt.Key_Alt, Qt.Key_Alt,
}: }:
#bar.unfocus() bar.unfocus()
# kill the search and focus back on main chart # kill the search and focus back on main chart
if godwidget: if godwidget:
@ -953,9 +1009,10 @@ async def handle_keyboard_input(
if item: if item:
parent_item = item.parent() parent_item = item.parent()
# if we're in the cache section and thus the next
# selection is a cache item, switch and show it
# immediately since it should be very fast.
if parent_item and parent_item.text() == 'cache': if parent_item and parent_item.text() == 'cache':
# if it's a cache item, switch and show it immediately
await search.chart_current_item(clear_to_cache=False) await search.chart_current_item(clear_to_cache=False)
elif not ctl: elif not ctl: