changeset 253:b3549da699a6

*: ooooh! stupid big commit! oops
author Paper <paper@paper.us.eu.org>
date Tue, 06 Feb 2024 16:56:32 -0500
parents a0eeb2cc7e6d
children d14f8e0e40c3
files Makefile.am include/gui/layouts/flow_layout.h include/gui/widgets/anime_button.h include/gui/widgets/elided_label.h include/gui/widgets/text.h src/gui/dialog/information.cc src/gui/layouts/flow_layout.cc src/gui/pages/seasons.cc src/gui/widgets/anime_button.cc src/gui/widgets/anime_info.cc src/gui/widgets/clickable_label.cc src/gui/widgets/elided_label.cc src/gui/widgets/text.cc
diffstat 13 files changed, 535 insertions(+), 90 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.am	Tue Feb 06 02:24:49 2024 -0500
+++ b/Makefile.am	Tue Feb 06 16:56:32 2024 -0500
@@ -133,6 +133,7 @@
 	include/gui/translate/anilist.h		\
 	include/gui/translate/anime.h			\
 	include/gui/translate/config.h		\
+	include/gui/widgets/anime_button.h	\
 	include/gui/widgets/anime_info.h		\
 	include/gui/widgets/clickable_label.h		\
 	include/gui/widgets/graph.h			\
@@ -140,6 +141,8 @@
 	include/gui/widgets/poster.h			\
 	include/gui/widgets/sidebar.h			\
 	include/gui/widgets/text.h		\
+	include/gui/widgets/elided_label.h	\
+	include/gui/layouts/flow_layout.h	\
 	include/gui/locale.h	\
 	include/gui/theme.h			\
 	include/gui/window.h
@@ -188,6 +191,7 @@
 	src/gui/dialog/about.cc		\
 	src/gui/dialog/information.cc		\
 	src/gui/dialog/settings.cc		\
+	src/gui/layouts/flow_layout.cc	\
 	src/gui/pages/anime_list.cc		\
 	src/gui/pages/history.cc		\
 	src/gui/pages/now_playing.cc		\
@@ -198,8 +202,10 @@
 	src/gui/translate/anilist.cc		\
 	src/gui/translate/anime.cc		\
 	src/gui/translate/config.cc		\
+	src/gui/widgets/anime_button.cc	\
 	src/gui/widgets/anime_info.cc		\
 	src/gui/widgets/clickable_label.cc		\
+	src/gui/widgets/elided_label.cc			\
 	src/gui/widgets/optional_date.cc		\
 	src/gui/widgets/poster.cc		\
 	src/gui/widgets/sidebar.cc		\
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/gui/layouts/flow_layout.h	Tue Feb 06 16:56:32 2024 -0500
@@ -0,0 +1,39 @@
+#ifndef __gui__layouts__flow_layout_h
+#define __gui__layouts__flow_layout_h
+
+#include <QLayout>
+#include <QRect>
+#include <QStyle>
+
+class QWidget;
+class QLayoutItem;
+
+class FlowLayout : public QLayout {
+public:
+    explicit FlowLayout(QWidget* parent, int margin = -1, int hSpacing = -1, int vSpacing = -1);
+    explicit FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1);
+    ~FlowLayout();
+
+    void addItem(QLayoutItem* item) override;
+    int horizontalSpacing() const;
+    int verticalSpacing() const;
+    Qt::Orientations expandingDirections() const override;
+    bool hasHeightForWidth() const override;
+    int heightForWidth(int) const override;
+    int count() const override;
+    QLayoutItem* itemAt(int index) const override;
+    QSize minimumSize() const override;
+    void setGeometry(const QRect& rect) override;
+    QSize sizeHint() const override;
+    QLayoutItem* takeAt(int index) override;
+
+private:
+    int doLayout(const QRect& rect, bool testOnly) const;
+    int smartSpacing(QStyle::PixelMetric pm) const;
+
+    QList<QLayoutItem*> item_list;
+    int _horiz_space;
+    int _vert_space;
+};
+
+#endif // __gui__layouts__flow_layout_h
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/gui/widgets/anime_button.h	Tue Feb 06 16:56:32 2024 -0500
@@ -0,0 +1,33 @@
+#ifndef __gui_widgets__anime_button_h
+#define __gui_widgets__anime_button_h
+
+#include <QFrame>
+
+class QWidget;
+
+class Poster;
+class ElidedLabel;
+
+namespace TextWidgets {
+class Line;
+class LabelledParagraph;
+}
+
+namespace Anime {
+class Anime;
+}
+
+class AnimeButton : public QFrame {
+public:
+	AnimeButton(QWidget* parent = nullptr);
+	AnimeButton(const Anime::Anime& anime, QWidget* parent = nullptr);
+	void SetAnime(const Anime::Anime& anime);
+
+protected:
+	Poster* _poster = nullptr;
+	TextWidgets::Line* _title = nullptr;
+	TextWidgets::LabelledParagraph* _info = nullptr;
+	ElidedLabel* _synopsis = nullptr;
+};
+
+#endif // __gui_widgets__anime_button_h
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/gui/widgets/elided_label.h	Tue Feb 06 16:56:32 2024 -0500
@@ -0,0 +1,21 @@
+#ifndef __gui__widgets__elided_label_h
+#define __gui__widgets__elided_label_h
+
+#include <QFrame>
+#include <QString>
+
+class QPaintEvent;
+class QWidget;
+
+class ElidedLabel : public QFrame {
+public:
+	ElidedLabel(const QString& text, QWidget* parent = nullptr);
+	void SetText(const QString& text);
+
+protected:
+	QString content;
+
+	void paintEvent(QPaintEvent* event) override;
+};
+
+#endif // __gui__widgets__elided_label_h
\ No newline at end of file
--- a/include/gui/widgets/text.h	Tue Feb 06 02:24:49 2024 -0500
+++ b/include/gui/widgets/text.h	Tue Feb 06 16:56:32 2024 -0500
@@ -8,7 +8,8 @@
 #include <QWidget>
 
 class QFrame;
-class QLabel;
+
+#include <QLabel>
 
 namespace TextWidgets {
 
@@ -24,23 +25,34 @@
 		QFrame* static_text_line;
 };
 
-class Paragraph : public QPlainTextEdit {
-		Q_OBJECT
+class Paragraph : public QLabel {
+	Q_OBJECT
 
-	public:
-		Paragraph(const QString& text, QWidget* parent = nullptr);
-		void SetText(const QString& text);
-		QSize minimumSizeHint() const override;
-		QSize sizeHint() const override;
+public:
+	Paragraph(const QString& text, QWidget* parent = nullptr);
+	void SetText(const QString& text);
 };
 
-class Line : public QLineEdit {
-		Q_OBJECT
+class LabelledParagraph final : public QWidget {
+	Q_OBJECT
+
+public:
+	LabelledParagraph(const QString& label, const QString& data, QWidget* parent = nullptr);
+	Paragraph* GetLabels();
+	Paragraph* GetParagraph();
 
-	public:
-		Line(QWidget* parent = nullptr);
-		Line(const QString& text, QWidget* parent = nullptr);
-		void SetText(const QString& text);
+private:
+	Paragraph* labels;
+	Paragraph* paragraph;
+};
+
+class Line : public Paragraph {
+	Q_OBJECT
+
+public:
+	Line(QWidget* parent = nullptr);
+	Line(const QString& text, QWidget* parent = nullptr);
+	void SetText(const QString& text);
 };
 
 class Title final : public Line {
@@ -74,8 +86,7 @@
 
 	private:
 		Header* header;
-		Paragraph* labels;
-		Paragraph* paragraph;
+		LabelledParagraph* content;
 };
 
 class SelectableSection final : public QWidget {
--- a/src/gui/dialog/information.cc	Tue Feb 06 02:24:49 2024 -0500
+++ b/src/gui/dialog/information.cc	Tue Feb 06 16:56:32 2024 -0500
@@ -93,7 +93,7 @@
 			{
 				/* Tab widget, contains main info and settings */
 				QTabWidget* tabbed_widget = new QTabWidget(main_widget);
-				tabbed_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+				tabbed_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
 
 				{
 					/* Main information */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gui/layouts/flow_layout.cc	Tue Feb 06 16:56:32 2024 -0500
@@ -0,0 +1,156 @@
+/*
+* Copyright (C) 2016 The Qt Company Ltd.
+* Contact: https://www.qt.io/licensing/
+*
+* This file is part of the QtCore module of the Qt Toolkit.
+*
+* "Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are
+* met:
+*   * Redistributions of source code must retain the above copyright
+*     notice, this list of conditions and the following disclaimer.
+*   * Redistributions in binary form must reproduce the above copyright
+*     notice, this list of conditions and the following disclaimer in
+*     the documentation and/or other materials provided with the
+*     distribution.
+*   * Neither the name of The Qt Company Ltd nor the names of its
+*     contributors may be used to endorse or promote products derived
+*     from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+*/
+#include "gui/layouts/flow_layout.h"
+
+#include <QWidget>
+
+#include <algorithm>
+
+FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing)
+	: QLayout(parent), _horiz_space(hSpacing), _vert_space(vSpacing) {
+	setContentsMargins(margin, margin, margin, margin);
+}
+
+FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing)
+	: _horiz_space(hSpacing), _vert_space(vSpacing) {
+	setContentsMargins(margin, margin, margin, margin);
+}
+
+FlowLayout::~FlowLayout() {
+	while (count())
+		delete takeAt(0);
+}
+
+void FlowLayout::addItem(QLayoutItem *item) {
+	item_list.append(item);
+}
+
+int FlowLayout::horizontalSpacing() const {
+	return (_horiz_space >= 0) ? _horiz_space : smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
+}
+
+int FlowLayout::verticalSpacing() const {
+	return (_vert_space >= 0) ? _vert_space : smartSpacing(QStyle::PM_LayoutVerticalSpacing);
+}
+
+int FlowLayout::count() const {
+	return item_list.size();
+}
+
+QLayoutItem* FlowLayout::itemAt(int index) const {
+	return item_list.value(index);
+}
+
+QLayoutItem* FlowLayout::takeAt(int index) {
+	return (index >= 0 && index < item_list.size()) ? item_list.takeAt(index) : nullptr;
+}
+
+Qt::Orientations FlowLayout::expandingDirections() const
+{
+	return {};
+}
+
+bool FlowLayout::hasHeightForWidth() const {
+	return true;
+}
+
+int FlowLayout::heightForWidth(int width) const {
+	return doLayout(QRect(0, 0, width, 0), true);
+}
+
+void FlowLayout::setGeometry(const QRect &rect) {
+	QLayout::setGeometry(rect);
+	doLayout(rect, false);
+}
+
+QSize FlowLayout::sizeHint() const {
+	return minimumSize();
+}
+
+QSize FlowLayout::minimumSize() const {
+	QSize size;
+	for (QLayoutItem* item : item_list)
+		size = size.expandedTo(item->minimumSize());
+
+	const QMargins margins = contentsMargins();
+	size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom());
+	return size;
+}
+
+int FlowLayout::doLayout(const QRect &rect, bool test) const {
+	int left, top, right, bottom;
+	getContentsMargins(&left, &top, &right, &bottom);
+	QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
+
+	int x = effectiveRect.x();
+	int y = effectiveRect.y();
+	int line_height = 0;
+
+	for (QLayoutItem* item : item_list) {
+		const QWidget* wid = item->widget();
+		int horiz_space = horizontalSpacing();
+		if (horiz_space == -1)
+			horiz_space = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
+
+		int vert_space = verticalSpacing();
+		if (vert_space == -1)
+			vert_space = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
+
+		int next_x = x + item->sizeHint().width() + horiz_space;
+		if ((next_x - horiz_space > effectiveRect.right()) && (line_height > 0)) {
+			x = effectiveRect.x();
+			y = y + line_height + vert_space;
+			next_x = x + item->sizeHint().width() + horiz_space;
+			line_height = 0;
+		}
+
+		if (!test)
+			item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
+
+		x = next_x;
+		line_height = std::max(line_height, item->sizeHint().height());
+	}
+
+	return y + line_height - rect.y() + bottom;
+}
+
+int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const {
+	QObject *parent = this->parent();
+	if (!parent) {
+		return -1;
+	} else if (parent->isWidgetType()) {
+		QWidget *pw = static_cast<QWidget *>(parent);
+		return pw->style()->pixelMetric(pm, nullptr, pw);
+	} else {
+		return static_cast<QLayout *>(parent)->spacing();
+	}
+}
--- a/src/gui/pages/seasons.cc	Tue Feb 06 02:24:49 2024 -0500
+++ b/src/gui/pages/seasons.cc	Tue Feb 06 16:56:32 2024 -0500
@@ -1,4 +1,36 @@
 #include "gui/pages/seasons.h"
 
+#include "core/anime_db.h"
+#include "gui/widgets/anime_button.h"
+#include "gui/layouts/flow_layout.h"
+
+#include <QVBoxLayout>
+
 SeasonsPage::SeasonsPage(QWidget* parent) : QWidget(parent) {
+	FlowLayout* ly = new FlowLayout(this);
+	{
+		AnimeButton* button = new AnimeButton(this);
+		button->SetAnime(Anime::db.items[Anime::db.GetAnimeFromTitle("Another")]);
+		ly->addWidget(button);
+	}
+	{
+		AnimeButton* button = new AnimeButton(this);
+		button->SetAnime(Anime::db.items[Anime::db.GetAnimeFromTitle("Another")]);
+		ly->addWidget(button);
+	}
+	{
+		AnimeButton* button = new AnimeButton(this);
+		button->SetAnime(Anime::db.items[Anime::db.GetAnimeFromTitle("Another")]);
+		ly->addWidget(button);
+	}
+	{
+		AnimeButton* button = new AnimeButton(this);
+		button->SetAnime(Anime::db.items[Anime::db.GetAnimeFromTitle("Another")]);
+		ly->addWidget(button);
+	}
+	{
+		AnimeButton* button = new AnimeButton(this);
+		button->SetAnime(Anime::db.items[Anime::db.GetAnimeFromTitle("Another")]);
+		ly->addWidget(button);
+	}
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gui/widgets/anime_button.cc	Tue Feb 06 16:56:32 2024 -0500
@@ -0,0 +1,84 @@
+#include "gui/widgets/anime_button.h"
+
+#include "core/anime_db.h"
+#include "core/strings.h"
+#include "core/session.h"
+#include "gui/widgets/text.h"
+#include "gui/widgets/elided_label.h"
+#include "gui/widgets/poster.h"
+
+#include <QWidget>
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QDate>
+
+/* This widget is only used on the Seasons page. */
+
+/***********************************\
+*|---------| Title                  *
+*|         |                        *
+*|         | Aired                  *
+*|         | Episodes               *
+*| Poster  | Producers              *
+*|         | Score                  *
+*|         | Popularity             *
+*|         |                        *
+*|_________| Synopsis               *
+\***********************************/
+
+AnimeButton::AnimeButton(QWidget* parent) : QFrame(parent) {
+	setFrameShadow(QFrame::Plain);
+	setFrameShape(QFrame::Box);
+	QHBoxLayout* ly = new QHBoxLayout(this);
+
+	_poster = new Poster(this);
+	_poster->setFixedSize(120, 170);
+	ly->addWidget(_poster);
+
+	{
+		QWidget* misc_section = new QWidget(this);
+		misc_section->setFixedSize(354, 180);
+
+		QVBoxLayout* misc_layout = new QVBoxLayout(misc_section);
+		misc_layout->setContentsMargins(0, 0, 0, 0);
+
+		_title = new TextWidgets::Line("", misc_section);
+		misc_layout->addWidget(_title);
+
+		/* need to make a separate "labelled paragraph" for this */
+		_info = new TextWidgets::LabelledParagraph(tr("Aired:\nEpisodes:\nProducers:\nScore:\nPopularity:"), "\n\n\n\n", misc_section);
+		_info->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
+		misc_layout->addWidget(_info);
+
+		_synopsis = new ElidedLabel("", misc_section);
+		misc_layout->addWidget(_synopsis);
+
+		ly->addWidget(misc_section);
+	}
+}
+
+AnimeButton::AnimeButton(const Anime::Anime& anime, QWidget* parent) : AnimeButton(parent) {
+	SetAnime(anime);
+}
+
+void AnimeButton::SetAnime(const Anime::Anime& anime) {
+	_poster->SetAnime(anime);
+	_title->SetText(Strings::ToQString(anime.GetUserPreferredTitle()));
+
+	{
+		const QLocale& locale = session.config.locale.GetLocale();
+		_info->GetParagraph()->SetText(
+			locale.toString(anime.GetAirDate().GetAsQDate(), "dd MMM yyyy") + "\n" +
+			QString::number(anime.GetEpisodes()) + "\n" +
+			"...\n" +
+			QString::number(anime.GetAudienceScore()) + "%\n" +
+			"..."
+		);
+	}
+
+	{
+		QString synopsis = Strings::ToQString(anime.GetSynopsis());
+		QFontMetrics metrics(_synopsis->font());
+		_synopsis->SetText(Strings::ToQString(anime.GetSynopsis()));
+	}
+}
\ No newline at end of file
--- a/src/gui/widgets/anime_info.cc	Tue Feb 06 02:24:49 2024 -0500
+++ b/src/gui/widgets/anime_info.cc	Tue Feb 06 16:56:32 2024 -0500
@@ -16,8 +16,10 @@
 	layout->addWidget(_details.get());
 
 	_synopsis.reset(new TextWidgets::SelectableSection(tr("Synopsis"), "", this));
-	_synopsis->GetParagraph()->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
+	_synopsis->GetParagraph()->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
 	layout->addWidget(_synopsis.get());
+
+	layout->addStretch();
 }
 
 AnimeInfoWidget::AnimeInfoWidget(const Anime::Anime& anime, QWidget* parent) : AnimeInfoWidget(parent) {
--- a/src/gui/widgets/clickable_label.cc	Tue Feb 06 02:24:49 2024 -0500
+++ b/src/gui/widgets/clickable_label.cc	Tue Feb 06 16:56:32 2024 -0500
@@ -1,6 +1,4 @@
 #include "gui/widgets/clickable_label.h"
-/* NOTE: this can likely be moved to poster.cpp, as
-   it's really the only place this will ever be used */
 
 ClickableLabel::ClickableLabel(QWidget* parent) : QLabel(parent) {
 	setCursor(Qt::PointingHandCursor);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gui/widgets/elided_label.cc	Tue Feb 06 16:56:32 2024 -0500
@@ -0,0 +1,81 @@
+/*
+* Copyright (C) 2016 The Qt Company Ltd.
+* Contact: https://www.qt.io/licensing/
+*
+* This file is part of the QtCore module of the Qt Toolkit.
+*
+* "Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are
+* met:
+*   * Redistributions of source code must retain the above copyright
+*     notice, this list of conditions and the following disclaimer.
+*   * Redistributions in binary form must reproduce the above copyright
+*     notice, this list of conditions and the following disclaimer in
+*     the documentation and/or other materials provided with the
+*     distribution.
+*   * Neither the name of The Qt Company Ltd nor the names of its
+*     contributors may be used to endorse or promote products derived
+*     from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+*/
+
+#include "gui/widgets/elided_label.h"
+
+#include <QPainter>
+#include <QSizePolicy>
+#include <QTextLayout>
+
+ElidedLabel::ElidedLabel(const QString& text, QWidget* parent)
+	: QFrame(parent), content(text) {
+	setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+}
+
+void ElidedLabel::SetText(const QString& text) {
+	content = text;
+	update();
+}
+
+void ElidedLabel::paintEvent(QPaintEvent *event) {
+	QFrame::paintEvent(event);
+
+	QPainter painter(this);
+	QFontMetrics fontMetrics = painter.fontMetrics();
+
+	int line_spacing = fontMetrics.lineSpacing();
+	int y = 0;
+
+	QTextLayout textLayout(content, painter.font());
+	textLayout.beginLayout();
+	for (;;) {
+		QTextLine line = textLayout.createLine();
+
+		if (!line.isValid())
+			break;
+
+		line.setLineWidth(width());
+		int nextLineY = y + line_spacing;
+
+		if (height() >= nextLineY + line_spacing) {
+			line.draw(&painter, QPoint(0, y));
+			y = nextLineY;
+		} else {
+			QString last_line = content.mid(line.textStart());
+			QString elided_last_line = fontMetrics.elidedText(last_line, Qt::ElideRight, width());
+			painter.drawText(QPoint(0, y + fontMetrics.ascent()), elided_last_line);
+			line = textLayout.createLine();
+			break;
+		}
+	}
+	textLayout.endLayout();
+}
--- a/src/gui/widgets/text.cc	Tue Feb 06 02:24:49 2024 -0500
+++ b/src/gui/widgets/text.cc	Tue Feb 06 16:56:32 2024 -0500
@@ -1,5 +1,6 @@
 #include "gui/widgets/text.h"
 #include "core/session.h"
+
 #include <QDebug>
 #include <QFrame>
 #include <QLabel>
@@ -37,66 +38,37 @@
 	updateGeometry();
 }
 
-/* inherits QPlainTextEdit and gives a much more reasonable minimum size */
-Paragraph::Paragraph(const QString& text, QWidget* parent) : QPlainTextEdit(text, parent) {
+/* for now, this is a QLabel with a couple of default settings.
+ *
+ * eventually I'll have to implement this as a QScrollArea, just in case
+ * some random text decides to overflow or something.
+*/
+Paragraph::Paragraph(const QString& text, QWidget* parent) : QLabel(text, parent) {
 	setTextInteractionFlags(Qt::TextBrowserInteraction);
 	setFrameShape(QFrame::NoFrame);
-	setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-	setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-
-	QPalette pal;
-	pal.setColor(QPalette::Base, Qt::transparent);
-	setPalette(pal);
-
-	setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
-}
+	setCursor(Qt::IBeamCursor); /* emulate Taiga */
+	setWordWrap(true);
 
-void Paragraph::SetText(const QString& text) {
-	QTextDocument* document = new QTextDocument(this);
-	document->setDocumentLayout(new QPlainTextDocumentLayout(document));
-	document->setPlainText(text);
-	setDocument(document);
-	updateGeometry();
-}
-
-/* highly based upon... some stackoverflow answer for PyQt */
-QSize Paragraph::minimumSizeHint() const {
-	return QSize(0, 0);
+	setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
 }
 
-QSize Paragraph::sizeHint() const {
-	QTextDocument* doc = document();
-	doc->adjustSize();
-	long h = 0;
-	for (QTextBlock line = doc->begin(); line != doc->end(); line = line.next()) {
-		h += doc->documentLayout()->blockBoundingRect(line).height();
-	}
-	return QSize(doc->size().width(), h);
+/* kept here for legacy reasons, see explanation above */
+void Paragraph::SetText(const QString& text) {
+	setText(text);
 }
 
-/* Equivalent to Paragraph(), but is only capable of showing one line. Only
-   exists because with SelectableSection it will let you go
-   out of bounds and that looks really fugly for most things */
-Line::Line(QWidget* parent) : QLineEdit(parent) {
-	setFrame(false);
-	setReadOnly(true);
-	setCursor(Qt::IBeamCursor);
-
-	QPalette pal;
-	pal.setColor(QPalette::Window, Qt::transparent);
-	pal.setColor(QPalette::Base, Qt::transparent);
-	setPalette(pal);
-
-	setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
+/* Equivalent to Paragraph(), but disables word wrap. */
+Line::Line(QWidget* parent) : Paragraph("", parent) {
+	setWordWrap(false);
 }
 
-Line::Line(const QString& text, QWidget* parent) : Line(parent) {
-	SetText(text);
+Line::Line(const QString& text, QWidget* parent) : Paragraph(text, parent) {
+	setWordWrap(false);
 }
 
+/* legacy function, don't use in new code */
 void Line::SetText(const QString& text) {
 	setText(text);
-	setCursorPosition(0); /* displays left text first */
 }
 
 Title::Title(const QString& title, QWidget* parent) : Line(title, parent) {
@@ -120,7 +92,7 @@
 	paragraph = new Paragraph(data, this);
 	paragraph->setTextInteractionFlags(Qt::NoTextInteraction);
 	paragraph->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents);
-	paragraph->setWordWrapMode(QTextOption::NoWrap);
+	paragraph->setWordWrap(QTextOption::NoWrap);
 
 	content_layout->addWidget(paragraph);
 	content_layout->setSpacing(0);
@@ -141,6 +113,35 @@
 	return paragraph;
 }
 
+LabelledParagraph::LabelledParagraph(const QString& label, const QString& data, QWidget* parent) : QWidget(parent) {
+	QHBoxLayout* ly = new QHBoxLayout(this);
+
+	labels = new Paragraph(label, this);
+	labels->setTextInteractionFlags(Qt::NoTextInteraction);
+	labels->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents);
+	labels->setWordWrap(QTextOption::NoWrap);
+	labels->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
+
+	paragraph = new Paragraph(data, this);
+	paragraph->setTextInteractionFlags(Qt::NoTextInteraction);
+	paragraph->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents);
+	paragraph->setWordWrap(QTextOption::NoWrap);
+	paragraph->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
+
+	ly->addWidget(labels, 0, Qt::AlignTop);
+	ly->addWidget(paragraph, 0, Qt::AlignTop);
+	ly->setSpacing(20);
+	ly->setContentsMargins(0, 0, 0, 0);
+}
+
+Paragraph* LabelledParagraph::GetLabels() {
+	return labels;
+}
+
+Paragraph* LabelledParagraph::GetParagraph() {
+	return paragraph;
+}
+
 LabelledSection::LabelledSection(const QString& title, const QString& label, const QString& data, QWidget* parent) : QWidget(parent) {
 	QVBoxLayout* layout = new QVBoxLayout(this);
 
@@ -148,27 +149,8 @@
 
 	// this is not accessible from the object because there's really
 	// no reason to make it accessible...
-	QWidget* content = new QWidget(this);
+	content = new LabelledParagraph(label, data, this);
 	content->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
-
-	labels = new Paragraph(label, this);
-	labels->setTextInteractionFlags(Qt::NoTextInteraction);
-	labels->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents);
-	labels->setWordWrapMode(QTextOption::NoWrap);
-	labels->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
-
-	paragraph = new Paragraph(data, this);
-	paragraph->setTextInteractionFlags(Qt::NoTextInteraction);
-	paragraph->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents);
-	paragraph->setWordWrapMode(QTextOption::NoWrap);
-	paragraph->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
-
-	QHBoxLayout* content_layout = new QHBoxLayout(content);
-	content_layout->addWidget(labels, 0, Qt::AlignTop);
-	content_layout->addWidget(paragraph, 0, Qt::AlignTop);
-	content_layout->setSpacing(20);
-	content_layout->setContentsMargins(0, 0, 0, 0);
-
 	content->setContentsMargins(12, 0, 0, 0);
 
 	layout->addWidget(header);
@@ -182,11 +164,11 @@
 }
 
 Paragraph* LabelledSection::GetLabels() {
-	return labels;
+	return content->GetLabels();
 }
 
 Paragraph* LabelledSection::GetParagraph() {
-	return paragraph;
+	return content->GetParagraph();
 }
 
 SelectableSection::SelectableSection(const QString& title, const QString& data, QWidget* parent) : QWidget(parent) {