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
parent
1e81feee46
commit
d11dc787a1
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue