"""Round 72: LinTunes draws its own title bars. On Wayland Qt's ``adwaita`` decoration plugin paints the title bar pure white from hardcoded constants, so it can't follow the palette. Every window goes frameless and gets ``gui/titlebar.TitleBar`` instead, in the palette's Window color. """ import pytest from PyQt6.QtCore import QPoint, QSize, Qt from PyQt6.QtGui import QPalette from PyQt6.QtWidgets import ( QApplication, QDialog, QLabel, QMainWindow, QMenu, QMessageBox, QVBoxLayout, QWidget, ) from lintunes.gui import titlebar from lintunes.gui.art_window import ArtWindow from lintunes.gui.titlebar import ( OPT_OUT, TitleBar, cursor_for, edges_at, parse_button_layout, title_bar, ) @pytest.fixture def installed(qapp, monkeypatch): monkeypatch.setattr(titlebar, "_layout_cache", parse_button_layout("close,minimize:appmenu")) installer = titlebar.install(qapp) yield installer qapp.removeEventFilter(installer) def _dialog(title="Preferences"): dialog = QDialog() dialog.setWindowTitle(title) layout = QVBoxLayout(dialog) layout.addWidget(QLabel("A label in a dialog")) return dialog def test_dialog_gets_frameless_bar_and_keeps_its_content_size(installed): dialog = _dialog() dialog.resize(400, 200) dialog.show() bar = title_bar(dialog) assert isinstance(bar, TitleBar) assert dialog.windowFlags() & Qt.WindowType.FramelessWindowHint height = bar.bar_height() assert dialog.contentsMargins().top() == height # The bar used to sit outside the window; the contents keep their room. assert dialog.size() == QSize(400, 200 + height) assert bar.geometry().topLeft() == QPoint(0, 0) assert bar.width() == dialog.width() dialog.close() def test_title_follows_window_title(installed): dialog = _dialog("Get Info") dialog.show() bar = title_bar(dialog) assert bar.title() == "Get Info" dialog.setWindowTitle("Get Info — Reel One[*]") dialog.setWindowModified(True) assert bar.title() == "Get Info — Reel One*" dialog.close() def test_buttons_follow_gnome_layout(installed): dialog = _dialog() dialog.resize(400, 200) dialog.show() bar = title_bar(dialog) kinds = [b.kind for b in bar._left] assert kinds == ["close", "minimize"] assert bar._right == [] assert bar._left[0].x() < bar._left[1].x() < bar.width() // 2 dialog.close() def test_main_window_message_box_and_art_window_get_one(installed): window = QMainWindow() window.menuBar().addMenu("File") window.setCentralWidget(QLabel("hello")) window.show() assert title_bar(window) is not None # The menu bar sits below the bar, not under it. assert window.menuBar().y() >= title_bar(window).height() box = QMessageBox(QMessageBox.Icon.Question, "Delete?", "Really?", QMessageBox.StandardButton.Yes, window) box.show() assert title_bar(box) is not None box.close() art = ArtWindow() art.show() assert title_bar(art) is not None assert art.contentsRect().top() == title_bar(art).height() art.close() window.close() def test_popups_and_opted_out_windows_are_left_alone(installed): menu = QMenu() menu.addAction("x") menu.popup(QPoint(0, 0)) assert title_bar(menu) is None menu.close() plain = QWidget() plain.setProperty(OPT_OUT, True) plain.show() assert title_bar(plain) is None assert not plain.windowFlags() & Qt.WindowType.FramelessWindowHint plain.close() def test_fixed_size_dialog_has_no_maximize_and_stays_fixed(installed, monkeypatch): monkeypatch.setattr(titlebar, "_layout_cache", parse_button_layout(":minimize,maximize,close")) dialog = _dialog() dialog.setFixedSize(300, 120) dialog.show() bar = title_bar(dialog) assert [b.kind for b in bar._right] == ["minimize", "maximize", "close"] maximize = bar._right[1] assert not maximize.isVisibleTo(dialog) assert dialog.minimumSize() == dialog.maximumSize() \ == QSize(300, 120 + bar.bar_height()) assert not titlebar.is_resizable(dialog) # No resize from the top strip either. assert not bar._top_edges(QPoint(1, 1)) dialog.close() def test_parse_button_layout(): assert parse_button_layout("'close,minimize:appmenu'") == ( ["close", "minimize"], []) assert parse_button_layout(":minimize,maximize,close") == ( [], ["minimize", "maximize", "close"]) assert parse_button_layout("appmenu:close") == ([], ["close"]) def test_edges_at_sides_and_corners(): size = QSize(400, 300) E = Qt.Edge assert not edges_at(QPoint(200, 150), size) assert edges_at(QPoint(0, 150), size) == E.LeftEdge assert edges_at(QPoint(399, 150), size) == E.RightEdge assert edges_at(QPoint(200, 299), size) == E.BottomEdge assert edges_at(QPoint(200, 0), size) == E.TopEdge assert edges_at(QPoint(0, 0), size) == E.LeftEdge | E.TopEdge # Along an edge, near a corner, the corner is caught. assert edges_at(QPoint(5, 299), size) == E.LeftEdge | E.BottomEdge assert edges_at(QPoint(399, 290), size) == E.RightEdge | E.BottomEdge assert cursor_for(E.LeftEdge | E.TopEdge) == Qt.CursorShape.SizeFDiagCursor assert cursor_for(E.RightEdge | E.TopEdge) == Qt.CursorShape.SizeBDiagCursor assert cursor_for(E.BottomEdge) == Qt.CursorShape.SizeVerCursor def test_bar_paints_in_the_background_preference(installed, qapp, tmp_path): from lintunes import theme from lintunes.preferences import Preferences saved = (QPalette(qapp.palette()), qapp.font(), qapp.styleSheet()) prefs = Preferences(tmp_path) prefs.set("color_background", 224) try: theme.apply_theme(qapp, prefs) dialog = _dialog() dialog.resize(300, 150) dialog.show() image = title_bar(dialog).grab().toImage() assert image.pixelColor(image.width() // 2, 3).name() == "#e0e0e0" dialog.close() finally: qapp.setPalette(saved[0]) qapp.setFont(saved[1]) qapp.setStyleSheet(saved[2]) def test_maximized_window_hides_its_edges(installed): dialog = _dialog() dialog.resize(300, 150) dialog.show() bar = title_bar(dialog) assert all(g.isVisibleTo(dialog) for g in bar._grips) dialog.setWindowState(Qt.WindowState.WindowMaximized) QApplication.processEvents() assert not any(g.isVisibleTo(dialog) for g in bar._grips) dialog.close()