Factor chart selection into widget, cleanups, add resource links
parent
ab3adcee9e
commit
7dfc7f7fa2
|
@ -103,6 +103,18 @@ class CompleterView(QTreeView):
|
||||||
# https://doc.qt.io/qt-5/model-view-programming.html
|
# https://doc.qt.io/qt-5/model-view-programming.html
|
||||||
# - MV tut:
|
# - MV tut:
|
||||||
# https://doc.qt.io/qt-5/modelview.html
|
# https://doc.qt.io/qt-5/modelview.html
|
||||||
|
# - custome header view (for doing stuff like we have in kivy?):
|
||||||
|
# https://doc.qt.io/qt-5/qheaderview.html#moving-header-sections
|
||||||
|
|
||||||
|
# TODO: selection model stuff for eventual aggregate feeds
|
||||||
|
# charting and mgmt;
|
||||||
|
# https://doc.qt.io/qt-5/qabstractitemview.html#setSelectionModel
|
||||||
|
# https://doc.qt.io/qt-5/qitemselectionmodel.html
|
||||||
|
# https://doc.qt.io/qt-5/modelview.html#3-2-working-with-selections
|
||||||
|
# https://doc.qt.io/qt-5/model-view-programming.html#handling-selections-of-items
|
||||||
|
|
||||||
|
# TODO: mouse extended handling:
|
||||||
|
# https://doc.qt.io/qt-5/qabstractitemview.html#entered
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -126,6 +138,8 @@ class CompleterView(QTreeView):
|
||||||
|
|
||||||
# self.setUniformRowHeights(True)
|
# self.setUniformRowHeights(True)
|
||||||
# self.setColumnWidth(0, 3)
|
# self.setColumnWidth(0, 3)
|
||||||
|
# self.setVerticalBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||||
|
# self.setSizeAdjustPolicy(QAbstractScrollArea.AdjustIgnored)
|
||||||
|
|
||||||
# ux settings
|
# ux settings
|
||||||
self.setItemsExpandable(True)
|
self.setItemsExpandable(True)
|
||||||
|
@ -133,15 +147,6 @@ class CompleterView(QTreeView):
|
||||||
self.setAnimated(False)
|
self.setAnimated(False)
|
||||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||||
|
|
||||||
# TODO: this up front?
|
|
||||||
# self.setSelectionModel(
|
|
||||||
# QItemSelectionModel.ClearAndSelect |
|
|
||||||
# QItemSelectionModel.Rows
|
|
||||||
# )
|
|
||||||
|
|
||||||
# self.setVerticalBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
||||||
# self.setSizeAdjustPolicy(QAbstractScrollArea.AdjustIgnored)
|
|
||||||
|
|
||||||
# column headers
|
# column headers
|
||||||
model.setHorizontalHeaderLabels(labels)
|
model.setHorizontalHeaderLabels(labels)
|
||||||
|
|
||||||
|
@ -150,47 +155,9 @@ class CompleterView(QTreeView):
|
||||||
def on_pressed(self, idx: QModelIndex) -> None:
|
def on_pressed(self, idx: QModelIndex) -> None:
|
||||||
|
|
||||||
search = self.parent()
|
search = self.parent()
|
||||||
value = search.get_current_item()
|
search.chart_current_item(clear_to_cache=False)
|
||||||
|
|
||||||
if value is not None:
|
|
||||||
provider, symbol = value
|
|
||||||
chart = search.chart_app
|
|
||||||
|
|
||||||
chart.load_symbol(
|
|
||||||
provider,
|
|
||||||
symbol,
|
|
||||||
'info',
|
|
||||||
)
|
|
||||||
|
|
||||||
# fully qualified symbol name (SNS i guess is what we're
|
|
||||||
# making?)
|
|
||||||
fqsn = '.'.join([symbol, provider]).lower()
|
|
||||||
|
|
||||||
# Re-order the symbol cache on the chart to display in
|
|
||||||
# LIFO order. this is normally only done internally by
|
|
||||||
# the chart on new symbols being loaded into memory
|
|
||||||
chart.set_chart_symbol(fqsn, chart.linkedcharts)
|
|
||||||
|
|
||||||
search.focus()
|
search.focus()
|
||||||
|
|
||||||
# def viewportSizeHint(self) -> QtCore.QSize:
|
|
||||||
# vps = super().viewportSizeHint()
|
|
||||||
# return QSize(vps.width(), _font.px_size * 6 * 2)
|
|
||||||
|
|
||||||
# def sizeHint(self) -> QtCore.QSize:
|
|
||||||
# """Scale completion results up to 6/16 of window.
|
|
||||||
# """
|
|
||||||
# # height = self.window().height() * 1/6
|
|
||||||
# # psh.setHeight(self.dpi_font.px_size * 6)
|
|
||||||
# # print(_font.px_size)
|
|
||||||
# height = _font.px_size * 6 * 2
|
|
||||||
# # the default here is just the vp size without scroll bar
|
|
||||||
# # https://doc.qt.io/qt-5/qabstractscrollarea.html#viewportSizeHint
|
|
||||||
# vps = self.viewportSizeHint()
|
|
||||||
# # print(f'h: {height}\n{vps}')
|
|
||||||
# # psh.setHeight(12)
|
|
||||||
# return QSize(-1, height)
|
|
||||||
|
|
||||||
def set_font_size(self, size: int = 18):
|
def set_font_size(self, size: int = 18):
|
||||||
# dpi_px_size = _font.px_size
|
# dpi_px_size = _font.px_size
|
||||||
# print(size)
|
# print(size)
|
||||||
|
@ -373,22 +340,15 @@ class CompleterView(QTreeView):
|
||||||
# print(f'removing {rows} from {section}')
|
# print(f'removing {rows} from {section}')
|
||||||
assert model.removeRows(0, rows, parent=idx)
|
assert model.removeRows(0, rows, parent=idx)
|
||||||
|
|
||||||
# remove section as well
|
# remove section as well ?
|
||||||
# model.removeRow(i, QModelIndex())
|
# model.removeRow(i, QModelIndex())
|
||||||
|
|
||||||
if status_field is not None:
|
if status_field is not None:
|
||||||
model.setItem(idx.row(), 1, QStandardItem(status_field))
|
model.setItem(idx.row(), 1, QStandardItem(status_field))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
model.setItem(idx.row(), 1, QStandardItem())
|
model.setItem(idx.row(), 1, QStandardItem())
|
||||||
|
|
||||||
# XXX: not idea how to use this
|
|
||||||
# model.setItemData(
|
|
||||||
# idx,
|
|
||||||
# {
|
|
||||||
# 0: 'cache',
|
|
||||||
# 1: 'searching',
|
|
||||||
# }
|
|
||||||
# )
|
|
||||||
self.resize()
|
self.resize()
|
||||||
|
|
||||||
return idx
|
return idx
|
||||||
|
@ -435,6 +395,11 @@ class CompleterView(QTreeView):
|
||||||
|
|
||||||
self.expandAll()
|
self.expandAll()
|
||||||
|
|
||||||
|
# TODO: figure out if we can avoid this line in a better way
|
||||||
|
# such that "re-selection" doesn't happen tree-wise for each new
|
||||||
|
# sub-search:
|
||||||
|
# https://doc.qt.io/qt-5/model-view-programming.html#handling-selections-in-item-views
|
||||||
|
|
||||||
# XXX: THE BELOW LINE MUST BE CALLED.
|
# XXX: THE BELOW LINE MUST BE CALLED.
|
||||||
# this stuff is super finicky and if not done right will cause
|
# this stuff is super finicky and if not done right will cause
|
||||||
# Qt crashes out our buttz. it's required in order to get the
|
# Qt crashes out our buttz. it's required in order to get the
|
||||||
|
@ -462,6 +427,10 @@ class SearchBar(QtWidgets.QLineEdit):
|
||||||
|
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
|
# self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||||
|
# self.customContextMenuRequested.connect(self.show_menu)
|
||||||
|
# self.setStyleSheet(f"font: 18px")
|
||||||
|
|
||||||
self.view: CompleterView = view
|
self.view: CompleterView = view
|
||||||
self.dpi_font = font
|
self.dpi_font = font
|
||||||
self.chart_app = parent_chart
|
self.chart_app = parent_chart
|
||||||
|
@ -477,10 +446,6 @@ class SearchBar(QtWidgets.QLineEdit):
|
||||||
# witty bit of margin
|
# witty bit of margin
|
||||||
self.setTextMargins(2, 2, 2, 2)
|
self.setTextMargins(2, 2, 2, 2)
|
||||||
|
|
||||||
# self.setContextMenuPolicy(Qt.CustomContextMenu)
|
|
||||||
# self.customContextMenuRequested.connect(self.show_menu)
|
|
||||||
# self.setStyleSheet(f"font: 18px")
|
|
||||||
|
|
||||||
def focus(self) -> None:
|
def focus(self) -> None:
|
||||||
self.selectAll()
|
self.selectAll()
|
||||||
self.show()
|
self.show()
|
||||||
|
@ -508,12 +473,18 @@ class SearchBar(QtWidgets.QLineEdit):
|
||||||
|
|
||||||
|
|
||||||
class SearchWidget(QtGui.QWidget):
|
class SearchWidget(QtGui.QWidget):
|
||||||
|
'''Composed widget of ``SearchBar`` + ``CompleterView``.
|
||||||
|
|
||||||
|
Includes helper methods for item management in the sub-widgets.
|
||||||
|
|
||||||
|
'''
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
chart_space: 'ChartSpace', # type: ignore # noqa
|
chart_space: 'ChartSpace', # type: ignore # noqa
|
||||||
columns: List[str] = ['src', 'symbol'],
|
columns: List[str] = ['src', 'symbol'],
|
||||||
parent=None,
|
parent=None,
|
||||||
):
|
|
||||||
|
) -> None:
|
||||||
super().__init__(parent or chart_space)
|
super().__init__(parent or chart_space)
|
||||||
|
|
||||||
# size it as we specify
|
# size it as we specify
|
||||||
|
@ -529,10 +500,11 @@ class SearchWidget(QtGui.QWidget):
|
||||||
self.vbox.setSpacing(4)
|
self.vbox.setSpacing(4)
|
||||||
|
|
||||||
# split layout for the (label:| search bar entry)
|
# split layout for the (label:| search bar entry)
|
||||||
self.bar_hbox = QtGui.QHBoxLayout(self)
|
self.bar_hbox = QtGui.QHBoxLayout()
|
||||||
self.bar_hbox.setContentsMargins(0, 0, 0, 0)
|
self.bar_hbox.setContentsMargins(0, 0, 0, 0)
|
||||||
self.bar_hbox.setSpacing(4)
|
self.bar_hbox.setSpacing(4)
|
||||||
|
|
||||||
|
# add label to left of search bar
|
||||||
self.label = label = QtGui.QLabel(parent=self)
|
self.label = label = QtGui.QLabel(parent=self)
|
||||||
label.setTextFormat(3) # markdown
|
label.setTextFormat(3) # markdown
|
||||||
label.setFont(_font.font)
|
label.setFont(_font.font)
|
||||||
|
@ -546,9 +518,6 @@ class SearchWidget(QtGui.QWidget):
|
||||||
|
|
||||||
self.bar_hbox.addWidget(label)
|
self.bar_hbox.addWidget(label)
|
||||||
|
|
||||||
# https://doc.qt.io/qt-5/qlayout.html#SizeConstraint-enum
|
|
||||||
# self.vbox.setSizeConstraint(QLayout.SetMaximumSize)
|
|
||||||
|
|
||||||
self.view = CompleterView(
|
self.view = CompleterView(
|
||||||
parent=self,
|
parent=self,
|
||||||
labels=columns,
|
labels=columns,
|
||||||
|
@ -560,8 +529,6 @@ class SearchWidget(QtGui.QWidget):
|
||||||
)
|
)
|
||||||
self.bar_hbox.addWidget(self.bar)
|
self.bar_hbox.addWidget(self.bar)
|
||||||
|
|
||||||
# self.vbox.addWidget(self.bar)
|
|
||||||
# self.vbox.setAlignment(self.bar, Qt.AlignTop | Qt.AlignRight)
|
|
||||||
self.vbox.addLayout(self.bar_hbox)
|
self.vbox.addLayout(self.bar_hbox)
|
||||||
|
|
||||||
self.vbox.setAlignment(self.bar, Qt.AlignTop | Qt.AlignRight)
|
self.vbox.setAlignment(self.bar, Qt.AlignTop | Qt.AlignRight)
|
||||||
|
@ -595,6 +562,7 @@ class SearchWidget(QtGui.QWidget):
|
||||||
# to figure out the desired field(s)
|
# to figure out the desired field(s)
|
||||||
# https://doc.qt.io/qt-5/qstandarditemmodel.html#itemFromIndex
|
# https://doc.qt.io/qt-5/qstandarditemmodel.html#itemFromIndex
|
||||||
node = model.itemFromIndex(cidx.siblingAtColumn(1))
|
node = model.itemFromIndex(cidx.siblingAtColumn(1))
|
||||||
|
|
||||||
if node:
|
if node:
|
||||||
symbol = node.text()
|
symbol = node.text()
|
||||||
try:
|
try:
|
||||||
|
@ -612,6 +580,52 @@ class SearchWidget(QtGui.QWidget):
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def chart_current_item(
|
||||||
|
self,
|
||||||
|
clear_to_cache: bool = True,
|
||||||
|
) -> Optional[str]:
|
||||||
|
'''Attempt to load and switch the current selected
|
||||||
|
completion result to the affiliated chart app.
|
||||||
|
|
||||||
|
Return any loaded symbol
|
||||||
|
|
||||||
|
'''
|
||||||
|
value = self.get_current_item()
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
provider, symbol = value
|
||||||
|
chart = self.chart_app
|
||||||
|
|
||||||
|
log.info(f'Requesting symbol: {symbol}.{provider}')
|
||||||
|
|
||||||
|
chart.load_symbol(
|
||||||
|
provider,
|
||||||
|
symbol,
|
||||||
|
'info',
|
||||||
|
)
|
||||||
|
|
||||||
|
# fully qualified symbol name (SNS i guess is what we're
|
||||||
|
# making?)
|
||||||
|
fqsn = '.'.join([symbol, provider]).lower()
|
||||||
|
|
||||||
|
# Re-order the symbol cache on the chart to display in
|
||||||
|
# LIFO order. this is normally only done internally by
|
||||||
|
# the chart on new symbols being loaded into memory
|
||||||
|
chart.set_chart_symbol(fqsn, chart.linkedcharts)
|
||||||
|
|
||||||
|
if clear_to_cache:
|
||||||
|
self.bar.clear()
|
||||||
|
self.view.set_section_entries(
|
||||||
|
'cache',
|
||||||
|
values=list(reversed(chart._chart_cache)),
|
||||||
|
|
||||||
|
# remove all other completion results except for cache
|
||||||
|
clear_all=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
return fqsn
|
||||||
|
|
||||||
|
|
||||||
_search_active: trio.Event = trio.Event()
|
_search_active: trio.Event = trio.Event()
|
||||||
_search_enabled: bool = False
|
_search_enabled: bool = False
|
||||||
|
@ -630,7 +644,9 @@ async def pack_matches(
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
log.info(f'Searching {provider} for "{pattern}"')
|
log.info(f'Searching {provider} for "{pattern}"')
|
||||||
|
|
||||||
if provider != 'cache':
|
if provider != 'cache':
|
||||||
|
# insert provider entries with search status
|
||||||
view.set_section_entries(
|
view.set_section_entries(
|
||||||
section=provider,
|
section=provider,
|
||||||
values=[],
|
values=[],
|
||||||
|
@ -645,13 +661,14 @@ async def pack_matches(
|
||||||
# ensure ^ status is updated
|
# ensure ^ status is updated
|
||||||
results = await search(pattern)
|
results = await search(pattern)
|
||||||
|
|
||||||
if provider != 'cache':
|
if provider != 'cache': # XXX: don't cache the cache results xD
|
||||||
matches[(provider, pattern)] = results
|
matches[(provider, pattern)] = results
|
||||||
|
|
||||||
# print(f'results from {provider}: {results}')
|
# print(f'results from {provider}: {results}')
|
||||||
has_results[pattern].add(provider)
|
has_results[pattern].add(provider)
|
||||||
|
|
||||||
if results:
|
if results:
|
||||||
|
# display completion results
|
||||||
view.set_section_entries(
|
view.set_section_entries(
|
||||||
section=provider,
|
section=provider,
|
||||||
values=results,
|
values=results,
|
||||||
|
@ -661,10 +678,9 @@ async def pack_matches(
|
||||||
async def fill_results(
|
async def fill_results(
|
||||||
|
|
||||||
search: SearchBar,
|
search: SearchBar,
|
||||||
# multisearch: Callable[..., Awaitable],
|
|
||||||
recv_chan: trio.abc.ReceiveChannel,
|
recv_chan: trio.abc.ReceiveChannel,
|
||||||
|
|
||||||
# kb debouncing pauses
|
# kb debouncing pauses (bracket defaults)
|
||||||
min_pause_time: float = 0.0616,
|
min_pause_time: float = 0.0616,
|
||||||
max_pause_time: float = 6/16,
|
max_pause_time: float = 6/16,
|
||||||
|
|
||||||
|
@ -736,7 +752,9 @@ async def fill_results(
|
||||||
# "searching.." statuses on outstanding results providers
|
# "searching.." statuses on outstanding results providers
|
||||||
async with trio.open_nursery() as n:
|
async with trio.open_nursery() as n:
|
||||||
|
|
||||||
for provider, (search, pause) in _searcher_cache.copy().items():
|
for provider, (search, pause) in (
|
||||||
|
_searcher_cache.copy().items()
|
||||||
|
):
|
||||||
|
|
||||||
if provider != 'cache':
|
if provider != 'cache':
|
||||||
view.clear_section(
|
view.clear_section(
|
||||||
|
@ -766,8 +784,8 @@ async def fill_results(
|
||||||
else:
|
else:
|
||||||
view.clear_section(provider)
|
view.clear_section(provider)
|
||||||
|
|
||||||
# if last_patt is None or last_patt != text:
|
if last_patt is None or last_patt != text:
|
||||||
# view.select_first()
|
view.select_first()
|
||||||
|
|
||||||
last_patt = text
|
last_patt = text
|
||||||
bar.show()
|
bar.show()
|
||||||
|
@ -777,7 +795,6 @@ async def handle_keyboard_input(
|
||||||
|
|
||||||
search: SearchWidget,
|
search: SearchWidget,
|
||||||
recv_chan: trio.abc.ReceiveChannel,
|
recv_chan: trio.abc.ReceiveChannel,
|
||||||
keyboard_pause_period: float = 0.0616,
|
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
|
@ -819,36 +836,7 @@ async def handle_keyboard_input(
|
||||||
|
|
||||||
if key in (Qt.Key_Enter, Qt.Key_Return):
|
if key in (Qt.Key_Enter, Qt.Key_Return):
|
||||||
|
|
||||||
value = search.get_current_item()
|
search.chart_current_item(clear_to_cache=True)
|
||||||
if value is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
provider, symbol = value
|
|
||||||
|
|
||||||
log.info(f'Requesting symbol: {symbol}.{provider}')
|
|
||||||
|
|
||||||
chart.load_symbol(
|
|
||||||
provider,
|
|
||||||
symbol,
|
|
||||||
'info',
|
|
||||||
)
|
|
||||||
|
|
||||||
# fully qualified symbol name (SNS i guess is what we're
|
|
||||||
# making?)
|
|
||||||
fqsn = '.'.join([symbol, provider]).lower()
|
|
||||||
|
|
||||||
# Re-order the symbol cache on the chart to display in
|
|
||||||
# LIFO order. this is normally only done internally by
|
|
||||||
# the chart on new symbols being loaded into memory
|
|
||||||
chart.set_chart_symbol(fqsn, chart.linkedcharts)
|
|
||||||
|
|
||||||
search.bar.clear()
|
|
||||||
view.set_section_entries(
|
|
||||||
'cache',
|
|
||||||
values=list(reversed(chart._chart_cache)),
|
|
||||||
clear_all=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
_search_enabled = False
|
_search_enabled = False
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -893,12 +881,12 @@ async def handle_keyboard_input(
|
||||||
view.next_section(direction='up')
|
view.next_section(direction='up')
|
||||||
|
|
||||||
# selection navigation controls
|
# selection navigation controls
|
||||||
elif ctl and key in {
|
elif (ctl and key in {
|
||||||
|
|
||||||
Qt.Key_K,
|
Qt.Key_K,
|
||||||
Qt.Key_J,
|
Qt.Key_J,
|
||||||
|
|
||||||
} or key in {
|
}) or key in {
|
||||||
|
|
||||||
Qt.Key_Up,
|
Qt.Key_Up,
|
||||||
Qt.Key_Down,
|
Qt.Key_Down,
|
||||||
|
|
Loading…
Reference in New Issue