diff options
-rw-r--r-- | src/gui/conversation.C | 192 | ||||
-rw-r--r-- | src/gui/conversation.h | 32 | ||||
-rw-r--r-- | src/gui/debate.C | 6 | ||||
-rw-r--r-- | src/gui/debate.h | 2 | ||||
-rw-r--r-- | src/gui/main-window.C | 6 |
5 files changed, 144 insertions, 94 deletions
diff --git a/src/gui/conversation.C b/src/gui/conversation.C index 1938dbb7..367f6b81 100644 --- a/src/gui/conversation.C +++ b/src/gui/conversation.C @@ -29,6 +29,7 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include <cmath> #include <string> #include <sstream> #include <iostream> @@ -41,9 +42,19 @@ namespace OpenAxiom { // -- Question -- Question::Question(Exchange& e) : QLineEdit(&e) { } + void Question::enterEvent(QEvent* e) { + setFocus(Qt::OtherFocusReason); + QLineEdit::enterEvent(e); + } + // -- Answer -- Answer::Answer(Exchange& e) : QLabel(&e) { } - + + void Answer::enterEvent(QEvent* e) { + static_cast<Exchange*>(parentWidget())->question() + ->setFocus(Qt::OtherFocusReason); + QLabel::enterEvent(e); + } // -- Exchange -- // Amount of pixel spacing between the query and reply areas. @@ -51,77 +62,67 @@ namespace OpenAxiom { // Margin around query and reply areas. const int margin = 2; - - QSize Exchange::sizeHint() const { - QSize sz = question()->frameSize(); - sz.rwidth() += 2 * margin; - sz.rheight() += answer()->frameSize().height() + spacing + 2 * margin; - return sz; - } - - QSize Exchange::minimumSizeHint() const { - QSize sz = question()->frameSize(); - sz.rwidth() += 2 * margin; - if (not answer()->isHidden()) - sz.rheight() += answer()->frameSize().height() + spacing; - sz.rheight() += 2 * margin; - return sz; - } // Return a monospace font - QFont - monospace_font() { + static QFont monospace_font() { QFont f("Courier"); f.setStyleHint(QFont::TypeWriter); return f; } - // Measurement in pixel of the em unit in the given font metrics. - static QSize - em_metrics(const QFontMetrics& fm) { + // Measurement in pixel of the em unit in the given font `f'. + static QSize em_metrics(const QWidget* w) { + const QFontMetrics fm = w->fontMetrics(); return QSize(fm.width(QLatin1Char('m')), fm.height()); } - // Measurement in pixel of the em unit in the given font `f'. - QSize em_metrics(const QFont& f) { - return em_metrics(QFontMetrics(f)); + QSize Exchange::minimumSizeHint() const { + int w = question()->width() + 2 * margin; + int h = question()->height() + 2 * margin; + if (not answer()->isHidden()) + h += answer()->height() + spacing; + return QSize(w, h); + } + + QSize Exchange::sizeHint() const { + return Exchange::minimumSizeHint(); } // Dress the query area with initial properties. static void - prepare_query_widget(QLineEdit* w) { - w->setFrame(false); - w->setFont(monospace_font()); - QSize em = em_metrics(w->fontMetrics()); - w->setGeometry(margin, margin, - question_columns * em.width(), em.height()); + prepare_query_widget(Exchange* e, Question* q) { + q->setFrame(false); + q->setFont(e->font()); + q->setGeometry(margin, margin, + e->width() - 2 * margin, q->fontMetrics().height()); } // Dress the reply aread with initial properties. static void - prepare_reply_widget(QLabel* w, const QRect& below) { - w->setFont(monospace_font()); - w->setGeometry(below.x(), below.height() + spacing, - below.width(), 2 * w->fontMetrics().height()); - w->hide(); // nothing to show yet + prepare_reply_widget(Exchange* e, Answer* a) { + a->setFont(e->font()); + Question* q = e->question(); + a->setGeometry(q->x(), q->height() + spacing, + q->width(), 2 * a->fontMetrics().height()); + a->setBackgroundRole(q->backgroundRole()); + a->hide(); // nothing to show yet + } + + static void prepare_frame(Conversation& conv, Exchange* e) { + e->setFont(conv.font()); + e->setAutoFillBackground(true); + e->setBackgroundRole(e->question()->backgroundRole()); + e->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + e->setLineWidth(1); + QPoint pt = conv.bottom_left(); + e->setGeometry(pt.x(), pt.y(), conv.width(), em_metrics(e).height()); } - // -- Exchange -- Exchange::Exchange(Conversation& conv, int n) : QFrame(&conv), no(n), query(*this), reply(*this) { - // 1. Construct the query area. - prepare_query_widget(question()); - - // 2. Construct the response area. - prepare_reply_widget(answer(), question()->geometry()); - - // 3. Construct the whole frame - QSize qs = question()->frameSize(); - QSize rs = answer()->frameSize(); - resize(qs.width() + 2 * margin, qs.height() + rs.height() + 2 * margin); - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - setLineWidth(1); - // 4. + prepare_frame(conv, this); + prepare_query_widget(this, question()); + prepare_reply_widget(this, answer()); connect(&query, SIGNAL(returnPressed()), this, SLOT(reply_to_query())); } @@ -140,6 +141,23 @@ namespace OpenAxiom { Debate* Exchange::debate() { return conversation()->debate(); } + + static void + ensure_visible_point(Debate* debate, const QPoint& pt) { + QScrollBar* vbar = debate->verticalScrollBar(); + const int y = pt.y(); + const int value = vbar->value(); + const int new_value = y - vbar->pageStep(); + if (y < value) + vbar->setValue(std::max(new_value, 0)); + else if (new_value > value) + vbar->setValue(vbar->maximum()); + } + + static void ensure_visibility(Debate* debate, Exchange* e) { + ensure_visible_point(debate, e->frameGeometry().bottomLeft()); + e->question()->setFocus(Qt::OtherFocusReason); + } void Exchange::reply_to_query() { @@ -149,35 +167,39 @@ namespace OpenAxiom { QString ans = parenthesize(number()) + " " + input; answer()->show(); answer()->setText(ans); - debate()->ensureWidgetVisible(answer()); - debate()->horizontalScrollBar()->setValue(0); - conversation()->next(this)->question() - ->setFocus(Qt::OtherFocusReason); + resize(sizeHint()); + update(); + updateGeometry(); + ensure_visibility(debate(), conversation()->next(this)); + } + + void Exchange::resizeEvent(QResizeEvent* e) { + QFrame::resizeEvent(e); + int w = width() - 2 * margin; + if (w > question()->width()) { + question()->resize(w, question()->height()); + answer()->resize(w, answer()->height()); + } } // ------------------ // -- Conversation -- // ------------------- + + // Default number of characters per question line. + const int question_columns = 80; - static void resize_if_necessary(Conversation& conv) { - QSize sz = conv.sizeHint(); - if (sz.height() > conv.height()) - conv.resize(conv.width(), sz.height()); - } - - static void debug_engine(Conversation& conv) { - std::cerr << "# conversations: " << conv.length() - << std::endl; - QSize sz = conv.sizeHint(); - std::cerr << "size: " << sz.width() << ", " << sz.height() - << std::endl; + static QSize + minimum_preferred_size(const Conversation* conv) { + const QSize em = em_metrics(conv); + return QSize(question_columns * em.width(), 25 * em.height()); } - + Conversation::Conversation(Debate& parent) : QWidget(&parent), group(parent) { + setFont(monospace_font()); + setMinimumSize(minimum_preferred_size(this)); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); - QSize sz = new_topic()->frameSize(); - setMinimumSize(sz.width(), 10 * sz.height()); } Conversation::~Conversation() { @@ -185,6 +207,12 @@ namespace OpenAxiom { delete children[i]; } + QPoint Conversation::bottom_left() const { + if (length() == 0) + return QPoint(0, 0); + return children.back()->geometry().bottomLeft(); + } + QSize Conversation::sizeHint() const { QSize sz(0,0); for (int i = 0; i < length(); ++i) { @@ -193,26 +221,32 @@ namespace OpenAxiom { sz.setWidth(s.width()); sz.rheight() += s.height(); } - return sz; + return minimum_preferred_size(this).expandedTo(sz); + } + + void Conversation::resizeEvent(QResizeEvent* e) { + QWidget::resizeEvent(e); + const int w = width(); + for (int i = 0; i < length(); ++i) { + Exchange* e = children[i]; + e->resize(w, e->height()); + } + // Start the conversation on first exposure. + if (length() == 0) + new_topic(); } Exchange* Conversation::new_topic() { - QPoint loc(0,0); - if (not fresh()) { - Exchange* last = children.back(); - loc = last->geometry().bottomLeft(); - } Exchange* w = new Exchange(*this, length() + 1); w->setVisible(true); - w->move(loc); children.push_back(w); - resize_if_necessary(*this); - debug_engine(*this); + adjustSize(); + update(); + updateGeometry(); return w; } - Exchange* Conversation::next(Exchange* w) { if (w == 0 or w->number() == length()) diff --git a/src/gui/conversation.h b/src/gui/conversation.h index d949e9d6..37d598f2 100644 --- a/src/gui/conversation.h +++ b/src/gui/conversation.h @@ -32,11 +32,13 @@ #ifndef OPENAXIOM_CONVERSATION_INCLUDED #define OPENAXIOM_CONVERSATION_INCLUDED +#include <vector> #include <QFrame> #include <QLineEdit> #include <QLabel> #include <QFont> -#include <vector> +#include <QEvent> +#include <QResizeEvent> namespace OpenAxiom { // A conversation is a set of exchanges. An exchange is a question @@ -52,16 +54,21 @@ namespace OpenAxiom { class Question : public QLineEdit { public: explicit Question(Exchange&); + + protected: + // Automatically grab focus when mouse moves into this widget + void enterEvent(QEvent*); }; class Answer : public QLabel { public: explicit Answer(Exchange&); + + protected: + // Automatically transfers focus to the associated query widget. + void enterEvent(QEvent*); }; - // -- Elemental conversation widget - // -- A basic interaction consists of a query, a reply, and the - // -- the type of the reply. class Exchange : public QFrame { Q_OBJECT; public: @@ -87,7 +94,7 @@ namespace OpenAxiom { QSize minimumSizeHint() const; protected: - // void resizeEvent(QResizeEvent*); + void resizeEvent(QResizeEvent*); private: const int no; @@ -117,6 +124,10 @@ namespace OpenAxiom { // Return the `i'-th conversation in this set, if any. Exchange* operator[](int) const; + // Return the bottom left corner of the rectangle enclosing the + // the set of exchanges in this conversation. + QPoint bottom_left() const; + // Start a new conversation topic. Exchange* new_topic(); @@ -125,23 +136,20 @@ namespace OpenAxiom { QSize sizeHint() const; // Return the parent engine widget. - Debate* debate() { return &group; } + Debate* debate() const { return const_cast<Debate*>(&group); } public slots: // Return the topic following a given topic in this set of conversations Exchange* next(Exchange*); + protected: + void resizeEvent(QResizeEvent*); + private: typedef std::vector<Exchange*> Children; Debate& group; Children children; }; - - // Default number of characters per question line. - const int question_columns = 80; - - QFont monospace_font(); - QSize em_metrics(const QFont&); } #endif // OPENAXIOM_CONVERSATION_INCLUDED diff --git a/src/gui/debate.C b/src/gui/debate.C index a4f289c1..bb70120c 100644 --- a/src/gui/debate.C +++ b/src/gui/debate.C @@ -38,7 +38,13 @@ namespace OpenAxiom { : QScrollArea(parent), conv(*this) { setViewportMargins(0, 0, 0, 0); setWidget(&conv); + viewport()->setAutoFillBackground(true); + viewport()->setBackgroundRole(conv.backgroundRole()); + QSize sz = conv.sizeHint(); + horizontalScrollBar()->setRange(0, sz.width()); + verticalScrollBar()->setRange(0, sz.height()); } Debate::~Debate() { } + } diff --git a/src/gui/debate.h b/src/gui/debate.h index 86016909..236967b3 100644 --- a/src/gui/debate.h +++ b/src/gui/debate.h @@ -44,6 +44,8 @@ namespace OpenAxiom { explicit Debate(QWidget*); ~Debate(); + Conversation* exchanges() { return &conv; } + private: Conversation conv; }; diff --git a/src/gui/main-window.C b/src/gui/main-window.C index 74e75350..b4097197 100644 --- a/src/gui/main-window.C +++ b/src/gui/main-window.C @@ -31,16 +31,16 @@ #include <QMenuBar> #include <QAction> -#include <QScrollArea> #include "debate.h" #include "main-window.h" namespace OpenAxiom { - + MainWindow::MainWindow() : tabs(this) { setCentralWidget(&tabs); - tabs.addTab(new Debate(&tabs), "Main Frame"); + Debate* debate = new Debate(&tabs); + tabs.addTab(debate, "Main Frame"); QMenu* file = menuBar()->addMenu(tr("&File")); QAction* action = new QAction(tr("Quit"), this); file->addAction(action); |