# piker: trading gear for hackers
# Copyright (C) Tyler Goodlet (in stewardship for pikers)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
'''
Text entry "forms" widgets (mostly for configuration and UI user input).
'''
from __future__ import annotations
from contextlib import asynccontextmanager
from functools import partial
from textwrap import dedent
from typing import (
Optional, Any, Callable, Awaitable
)
import trio
from PyQt5 import QtGui
from PyQt5.QtCore import QSize, QModelIndex, Qt, QEvent
from PyQt5.QtWidgets import (
QWidget,
QLabel,
QComboBox,
QLineEdit,
QHBoxLayout,
QVBoxLayout,
QFormLayout,
QProgressBar,
QSizePolicy,
QStyledItemDelegate,
QStyleOptionViewItem,
)
# import pydantic
from ._event import open_handlers
from ._style import hcolor, _font, _font_small, DpiAwareFont
from ._label import FormatLabel
from .. import config
class FontAndChartAwareLineEdit(QLineEdit):
def __init__(
self,
parent: QWidget,
# parent_chart: QWidget, # noqa
font: DpiAwareFont = _font,
width_in_chars: int = None,
) -> None:
# self.setContextMenuPolicy(Qt.CustomContextMenu)
# self.customContextMenuRequested.connect(self.show_menu)
# self.setStyleSheet(f"font: 18px")
self.dpi_font = font
# self.godwidget = parent_chart
if width_in_chars:
self._chars = int(width_in_chars)
else:
# chart count which will be used to calculate
# width of input field.
self._chars: int = 9
super().__init__(parent)
# size it as we specify
# https://doc.qt.io/qt-5/qsizepolicy.html#Policy-enum
self.setSizePolicy(
QSizePolicy.Expanding,
QSizePolicy.Fixed,
)
self.setFont(font.font)
# witty bit of margin
self.setTextMargins(2, 2, 2, 2)
def sizeHint(self) -> QSize:
"""
Scale edit box to size of dpi aware font.
"""
psh = super().sizeHint()
dpi_font = self.dpi_font
psh.setHeight(dpi_font.px_size)
# space for ``._chars: int``
char_w_pxs = dpi_font.boundingRect(self.text()).width()
chars_w = char_w_pxs + 6 # * dpi_font.scale() * self._chars
psh.setWidth(chars_w)
return psh
def set_width_in_chars(
self,
chars: int,
) -> None:
self._chars = chars
self.sizeHint()
self.update()
def focus(self) -> None:
self.selectAll()
self.show()
self.setFocus()
class FontScaledDelegate(QStyledItemDelegate):
'''
Super simple view delegate to render text in the same
font size as the search widget.
'''
def __init__(
self,
parent=None,
font: DpiAwareFont = _font,
) -> None:
super().__init__(parent)
self.dpi_font = font
def sizeHint(
self,
option: QStyleOptionViewItem,
index: QModelIndex,
) -> QSize:
# value = index.data()
# br = self.dpi_font.boundingRect(value)
# w, h = br.width(), br.height()
parent = self.parent()
if getattr(parent, '_max_item_size', None):
return QSize(*self.parent()._max_item_size)
else:
return super().sizeHint(option, index)
# NOTE: hack to display icons on RHS
# TODO: is there a way to set this stype option once?
# def paint(self, painter, option, index):
# # display icons on RHS
# # https://stackoverflow.com/a/39943629
# option.decorationPosition = QtGui.QStyleOptionViewItem.Right
# option.decorationAlignment = Qt.AlignRight | Qt.AlignVCenter
# QStyledItemDelegate.paint(self, painter, option, index)
# NOTE: in theory we can put icons on the RHS side with this hackery:
# https://stackoverflow.com/a/64256969
# class ComboBox(QComboBox):
# def __init__(
# self,
# parent=None,
# ) -> None:
# super().__init__(parent=parent)
# def showPopup(self):
# print('show')
# QComboBox.showPopup(self)
# def hidePopup(self):
# # self.setItemDelegate(FontScaledDelegate(self.parent()))
# print('hide')
# QComboBox.hidePopup(self)
# slew of resources which helped get this where it is:
# https://stackoverflow.com/questions/20648210/qcombobox-adjusttocontents-changing-height
# https://stackoverflow.com/questions/3151798/how-do-i-set-the-qcombobox-width-to-fit-the-largest-item
# https://stackoverflow.com/questions/6337589/qlistwidget-adjust-size-to-content#6370892
# https://stackoverflow.com/questions/25304267/qt-resize-of-qlistview
# https://stackoverflow.com/questions/28227406/how-to-set-qlistview-rows-height-permanently
class FieldsForm(QWidget):
vbox: QVBoxLayout
form: QFormLayout
def __init__(
self,
parent=None,
) -> None:
super().__init__(parent)
# size it as we specify
self.setSizePolicy(
QSizePolicy.Expanding,
QSizePolicy.Expanding,
)
# XXX: not sure why we have to create this here exactly
# (instead of in the pane creation routine) but it's
# here and is managed by downstream layout routines.
# best guess is that you have to create layouts in order
# of hierarchy in order for things to display correctly?
# TODO: we may want to hand this *down* from some "pane manager"
# thing eventually?
self.vbox = QVBoxLayout(self)
# self.vbox.setAlignment(Qt.AlignVCenter)
self.vbox.setAlignment(Qt.AlignBottom)
self.vbox.setContentsMargins(0, 4, 3, 6)
self.vbox.setSpacing(0)
# split layout for the (