from __future__ import annotations
import inspect
from enum import Enum
from typing import Any, TYPE_CHECKING, Iterable
import warnings
from docstring_parser import parse
from qtpy.QtWidgets import QApplication, QMessageBox
if TYPE_CHECKING:
from ..gui import BaseGui
__all__ = [
"MessageBoxMode",
"show_messagebox",
"open_url",
"screen_center",
"to_clipboard",
"iter_members",
"extract_tooltip",
"get_signature",
]
[docs]def iter_members(cls: type, exclude_prefix: str = "__") -> Iterable[tuple[str, Any]]:
"""
Iterate over all the members in the order of source code line number.
This function is identical to inspect.getmembers except for the order
of the results. We have to sort the name in the order of line number.
"""
mro = (cls,) + inspect.getmro(cls)
processed = set()
names: list[str] = list(cls.__dict__.keys())
try:
for base in reversed(mro):
for k in base.__dict__.keys():
if k not in names:
names.append(k)
except AttributeError:
pass
for key in names:
try:
value = getattr(cls, key)
# handle the duplicate key
if key in processed:
raise AttributeError
except AttributeError:
for base in mro:
if key in base.__dict__:
value = base.__dict__[key]
break
else:
continue
if not key.startswith(exclude_prefix):
yield key, value
processed.add(key)
[docs]def get_signature(func):
"""Similar to ``inspect.signature`` but safely returns ``Signature``."""
if hasattr(func, "__signature__"):
sig = func.__signature__
else:
sig = inspect.signature(func)
return sig
[docs]class MessageBoxMode(Enum):
ERROR = "error"
WARNING = "warn"
INFO = "info"
QUESTION = "question"
ABOUT = "about"
_QMESSAGE_MODES = {
MessageBoxMode.ERROR: QMessageBox.critical,
MessageBoxMode.WARNING: QMessageBox.warning,
MessageBoxMode.INFO: QMessageBox.information,
MessageBoxMode.QUESTION: QMessageBox.question,
MessageBoxMode.ABOUT: QMessageBox.about,
}
[docs]def show_messagebox(
mode: str | MessageBoxMode = MessageBoxMode.INFO,
title: str = None,
text: str = None,
parent=None,
) -> bool:
"""
Freeze the GUI and open a messagebox dialog.
Parameters
----------
mode : str or MessageBoxMode, default is MessageBoxMode.INFO
Mode of message box. Must be "error", "warn", "info", "question" or "about".
title : str, optional
Title of messagebox.
text : str, optional
Text in messagebox.
parent : QWidget, optional
Parent widget.
Returns
-------
bool
If "OK" or "Yes" is clicked, return True. Otherwise return False.
"""
show_dialog = _QMESSAGE_MODES[MessageBoxMode(mode)]
result = show_dialog(parent, title, text)
return result in (QMessageBox.Ok, QMessageBox.Yes)
[docs]def open_url(link: str) -> None:
"""
Open the link with the default browser.
Parameters
----------
link : str
Link to the home page.
"""
from qtpy.QtGui import QDesktopServices
from qtpy.QtCore import QUrl
QDesktopServices.openUrl(QUrl(link))
[docs]def to_clipboard(obj: Any) -> None:
"""
Copy an object of any type to the clipboard.
You can copy text, ndarray as an image or data frame as a table data.
Parameters
----------
obj : Any
Object to be copied.
"""
from qtpy.QtGui import QGuiApplication, QImage, qRgb
import numpy as np
import pandas as pd
clipboard = QGuiApplication.clipboard()
if isinstance(obj, str):
clipboard.setText(obj)
elif isinstance(obj, np.ndarray):
if obj.dtype != np.uint8:
raise ValueError(f"Cannot copy an array of dtype {obj.dtype} to clipboard.")
# See https://gist.github.com/smex/5287589
qimg_format = QImage.Format_RGB888 if obj.ndim == 3 else QImage.Format_Indexed8
*_, h, w = obj.shape
qimg = QImage(obj.data, w, h, obj.strides[0], qimg_format)
gray_color_table = [qRgb(i, i, i) for i in range(256)]
qimg.setColorTable(gray_color_table)
clipboard.setImage(qimg)
elif isinstance(obj, pd.DataFrame):
clipboard.setText(obj.to_csv(sep="\t"))
else:
clipboard.setText(str(obj))
[docs]def screen_center():
"""Get the center coordinate of the screen."""
return QApplication.desktop().screen().rect().center()
def screen_scale() -> float:
"""Get the scale of main screen."""
from qtpy.QtGui import QGuiApplication
screen = QGuiApplication.screens()[0]
return screen.devicePixelRatio()
def show_tree(ui: BaseGui) -> str:
return _get_tree(ui)
def _get_tree(ui: BaseGui, depth: int = 0):
pref = "\t" * depth
children_str_list: list[str] = []
for i, child in enumerate(ui.__magicclass_children__):
text = _get_tree(child, depth=depth + 1)
children_str_list.append(pref + f"\t{i:>3}: {text}")
if children_str_list:
children_str = "\n".join(children_str_list)
out = f"'{ui.name}'\n{children_str}"
else:
out = f"'{ui.name}'"
return out
def rst_to_html(rst: str, unescape: bool = True) -> str:
"""Convert rST string into HTML."""
from docutils.examples import html_body
try:
body: bytes = html_body(rst, input_encoding="utf-8", output_encoding="utf-8")
html = body.decode(encoding="utf-8")
if unescape:
from xml.sax.saxutils import unescape as _unescape
html = _unescape(html)
except Exception as e:
warnings.warn(
f"Could not convert string into HTML due to {type(e).__name__}: {e}",
UserWarning,
)
html = rst
return html