Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download

open-axiom repository from github

24005 views
1
// Copyright (C) 2011-2013, Gabriel Dos Reis.
2
// All rights reserved.
3
// Written by Gabriel Dos Reis.
4
//
5
// Redistribution and use in source and binary forms, with or without
6
// modification, are permitted provided that the following conditions are
7
// met:
8
//
9
// - Redistributions of source code must retain the above copyright
10
// notice, this list of conditions and the following disclaimer.
11
//
12
// - Redistributions in binary form must reproduce the above copyright
13
// notice, this list of conditions and the following disclaimer in
14
// the documentation and/or other materials provided with the
15
// distribution.
16
//
17
// - Neither the name of OpenAxiom. nor the names of its contributors
18
// may be used to endorse or promote products derived from this
19
// software without specific prior written permission.
20
//
21
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
22
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
23
// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
25
// OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
29
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
33
#include <cmath>
34
#include <string>
35
#include <sstream>
36
#include <iostream>
37
38
#include <QScrollBar>
39
#include <QApplication>
40
#include "conversation.h"
41
#include "debate.h"
42
43
namespace OpenAxiom {
44
// Largest width and line spacing, in pixel, of the font metrics
45
// associated with `w'.
46
static QSize font_units(const QWidget* w) {
47
const QFontMetrics fm = w->fontMetrics();
48
const auto h = fm.lineSpacing();
49
if (auto w = fm.maxWidth())
50
return { w, h };
51
return { fm.width('W'), h };
52
}
53
54
// Return true if the QString `s' is morally an empty string.
55
// QT makes a difference between a null string and an empty string.
56
// That distinction is largely pedantic and without difference
57
// for most of our practical purposes.
58
static bool
59
empty_string(const QString& s) {
60
return s.isNull() or s.isEmpty();
61
}
62
63
// Return a resonable margin for this frame.
64
static int our_margin(const QFrame* f) {
65
return 2 + f->frameWidth();
66
}
67
68
// --------------------
69
// -- OutputTextArea --
70
// --------------------
71
OutputTextArea::OutputTextArea(QWidget* p)
72
: Base(p), cur(document()) {
73
get_cursor().movePosition(QTextCursor::End);
74
setReadOnly(true); // this is a output only area.
75
setLineWrapMode(NoWrap); // for the time being, mess with nothing.
76
setFont(p->font());
77
setViewportMargins(0, 0, 0, 0);
78
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
79
// We do not want to see scroll bars. Usually disallowing vertical
80
// scroll bars and allocating enough horizontal space is sufficient
81
// to ensure that we don't see any horizontal scrollbar.
82
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
83
setLineWidth(1);
84
}
85
86
// This overriding implementation is so that we can control the
87
// amount of vertical space in the read-only editor viewport allocated
88
// for the display of output text. In particular we do not want
89
// scrollbars.
90
QSize
91
OutputTextArea::sizeHint() const {
92
const QSize s = font_units(this);
93
return QSize(width(), (1 + document()->lineCount()) * s.height());
94
}
95
96
void OutputTextArea::add_paragraph(const QString& s) {
97
if (not document()->isEmpty())
98
get_cursor().insertBlock();
99
get_cursor().insertText(s);
100
QSize sz = sizeHint();
101
sz.setWidth(parentWidget()->width() - our_margin(this));
102
resize(sz);
103
show();
104
updateGeometry();
105
}
106
107
void OutputTextArea::add_text(const QString& s) {
108
setPlainText(toPlainText() + s);
109
QSize sz = sizeHint();
110
const int w = parentWidget()->width() - 2 * frameWidth();
111
if (w > sz.width())
112
sz.setWidth(w);
113
resize(sz);
114
show();
115
updateGeometry();
116
}
117
118
OutputTextArea&
119
OutputTextArea::insert_block(const QString& s) {
120
if (not document()->isEmpty())
121
get_cursor().insertBlock();
122
get_cursor().insertText(s);
123
resize(sizeHint());
124
updateGeometry();
125
return *this;
126
}
127
128
// --------------
129
// -- Question --
130
// --------------
131
Question::Question(Exchange* e) : QLineEdit(e) {
132
setBackgroundRole(QPalette::AlternateBase);
133
setFrame(true);
134
}
135
136
void Question::focusInEvent(QFocusEvent* e) {
137
setFrame(true);
138
update();
139
QLineEdit::focusInEvent(e);
140
}
141
142
void Question::enterEvent(QEvent* e) {
143
setFrame(true);
144
update();
145
QLineEdit::enterEvent(e);
146
}
147
148
// ------------
149
// -- Answer --
150
// ------------
151
Answer::Answer(Exchange* e) : OutputTextArea(e) {
152
setFrameStyle(StyledPanel | Raised);
153
}
154
155
// --------------
156
// -- Exchange --
157
// --------------
158
// Amount of pixel spacing between the query and reply areas.
159
const int spacing = 2;
160
161
// Return a monospace font
162
static QFont monospace_font() {
163
QFont f("Monaco", 11);
164
f.setStyleHint(QFont::TypeWriter);
165
return f;
166
}
167
168
// The layout within an exchange is as follows:
169
// -- input area (an editor) with its own decoation accounted for.
170
// -- an optional spacing
171
// -- an output area with its own decoration accounted for.
172
QSize Exchange::sizeHint() const {
173
const int m = our_margin(this);
174
QSize sz = question()->size() + QSize(2 * m, 2 * m);
175
if (not answer()->isHidden())
176
sz.rheight() += answer()->height() + spacing;
177
return sz;
178
}
179
180
Server* Exchange::server() const {
181
return win->debate()->server();
182
}
183
184
// Dress the query area with initial properties.
185
static void
186
prepare_query_widget(Conversation* conv, Exchange* e) {
187
Question* q = e->question();
188
q->setFrame(false);
189
q->setFont(conv->font());
190
const int m = our_margin(e);
191
q->setGeometry(m, m, conv->width() - 2 * m, q->height());
192
}
193
194
// Dress the reply aread with initial properties.
195
// Place the reply widget right below the frame containing
196
// the query widget; make both of the same width, of course.
197
static void
198
prepare_reply_widget(Conversation* conv, Exchange* e) {
199
Answer* a = e->answer();
200
Question* q = e->question();
201
const QPoint pt = e->question()->geometry().bottomLeft();
202
const int m = our_margin(a);
203
a->setGeometry(pt.x(), pt.y() + spacing,
204
conv->width() - 2 * m, q->height());
205
a->setBackgroundRole(q->backgroundRole());
206
a->hide(); // nothing to show yet
207
}
208
209
static void
210
finish_exchange_make_up(Conversation* conv, Exchange* e) {
211
e->setAutoFillBackground(true);
212
e->move(conv->bottom_left());
213
}
214
215
Exchange::Exchange(Conversation* conv, int n)
216
: QFrame(conv), win(conv), no(n), query(this), reply(this) {
217
setLineWidth(1);
218
setFont(conv->font());
219
prepare_query_widget(conv, this);
220
prepare_reply_widget(conv, this);
221
finish_exchange_make_up(conv, this);
222
connect(question(), SIGNAL(returnPressed()),
223
this, SLOT(reply_to_query()));
224
}
225
226
static void ensure_visibility(Debate* debate, Exchange* e) {
227
const int y = e->y() + e->height();
228
QScrollBar* vbar = debate->verticalScrollBar();
229
const int value = vbar->value();
230
int new_value = y - vbar->pageStep();
231
if (y < value)
232
vbar->setValue(std::max(new_value, 0));
233
else if (new_value > value)
234
vbar->setValue(std::min(new_value, vbar->maximum()));
235
e->question()->setFocus(Qt::OtherFocusReason);
236
}
237
238
void
239
Exchange::reply_to_query() {
240
QString input = question()->text().trimmed();
241
if (empty_string(input))
242
return;
243
question()->setReadOnly(true); // Make query area read only.
244
question()->clearFocus();
245
question()->setFocusPolicy(Qt::NoFocus);
246
server()->input(input);
247
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
248
}
249
250
void Exchange::resizeEvent(QResizeEvent* e) {
251
QFrame::resizeEvent(e);
252
const int w = width() - 2 * our_margin(this);
253
if (w > question()->width()) {
254
question()->resize(w, question()->height());
255
answer()->resize(w, answer()->height());
256
}
257
}
258
259
// ------------
260
// -- Banner --
261
// ------------
262
Banner::Banner(Conversation* conv) : Base(conv) {
263
setFrameStyle(StyledPanel | Raised);
264
setBackgroundRole(QPalette::Base);
265
}
266
267
// ------------------
268
// -- Conversation --
269
// -------------------
270
271
// Default number of characters per question line.
272
const int columns = 80;
273
const int lines = 40;
274
275
static QSize
276
minimum_preferred_size(const Conversation* conv) {
277
const QSize s = font_units(conv);
278
return QSize(columns * s.width(), lines * s.height());
279
}
280
281
// Set a minimum preferred widget size, so no layout manager
282
// messes with it. Indicate we can make use of more space.
283
Conversation::Conversation(Debate* d)
284
: QWidget(d),
285
win(d),
286
greatings(this),
287
cur_ex(),
288
cur_out(&greatings),
289
rx("\\(\\d+\\)\\s->"),
290
tx("\\sType: ") {
291
setFont(monospace_font());
292
setBackgroundRole(QPalette::Base);
293
greatings.setFont(font());
294
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
295
}
296
297
Conversation::~Conversation() {
298
for (int i = children.size() -1 ; i >= 0; --i)
299
delete children[i];
300
}
301
302
QPoint Conversation::bottom_left() const {
303
if (length() == 0)
304
return greatings.geometry().bottomLeft();
305
return children.back()->geometry().bottomLeft();
306
}
307
308
static QSize
309
round_up_height(const QSize& sz, int height) {
310
if (height < 1)
311
height = 1;
312
const int n = (sz.height() + height) / height;
313
return QSize(sz.width(), n * height);
314
}
315
316
QSize Conversation::sizeHint() const {
317
const int n = length();
318
if (n == 0)
319
return minimum_preferred_size(this);
320
const int view_height = debate()->viewport()->height();
321
QSize sz = greatings.size();
322
for (int i = 0; i < n; ++i)
323
sz.rheight() += children[i]->height();
324
return round_up_height(sz, view_height);
325
}
326
327
void Conversation::resizeEvent(QResizeEvent* e) {
328
QWidget::resizeEvent(e);
329
setMinimumSize(size());
330
const QSize sz = size();
331
if (e->oldSize() == sz)
332
return;
333
greatings.resize(sz.width(), greatings.height());
334
for (int i = 0; i < length(); ++i) {
335
Exchange* e = children[i];
336
e->resize(sz.width(), e->height());
337
}
338
}
339
340
void Conversation::paintEvent(QPaintEvent* e) {
341
QWidget::paintEvent(e);
342
if (length() == 0)
343
greatings.update();
344
}
345
346
Exchange*
347
Conversation::new_topic() {
348
Exchange* w = new Exchange(this, length() + 1);
349
w->show();
350
children.push_back(w);
351
adjustSize();
352
updateGeometry();
353
cur_out = w->answer();
354
return cur_ex = w;
355
}
356
357
Exchange*
358
Conversation::next(Exchange* w) {
359
if (w == 0 or w->number() == length())
360
return new_topic();
361
return cur_ex = children[w->number()];
362
}
363
364
static QTextCharFormat
365
get_type_format(OutputTextArea* area) {
366
auto format = area->get_cursor().charFormat();
367
format.setFontWeight(QFont::Bold);
368
format.setToolTip("domain of result");
369
return format;
370
}
371
372
static void
373
display_type(OutputTextArea* area, QString& text, int n) {
374
area->insert_block(QString(n, ' '));
375
area->get_cursor().insertText(text.mid(n), get_type_format(area));
376
area->resize(area->sizeHint());
377
area->updateGeometry();
378
}
379
380
void
381
Conversation::read_reply() {
382
auto data = debate()->server()->readAll();
383
QStringList strs = QString::fromLocal8Bit(data).split('\n');
384
QString prompt;
385
for (auto& s : strs) {
386
if (rx.indexIn(s) != -1) {
387
prompt = s;
388
continue;
389
}
390
auto tpos = tx.indexIn(s);
391
if (tpos != -1)
392
display_type(cur_out, s, tpos + tx.matchedLength());
393
else
394
cur_out->add_paragraph(s);
395
}
396
if (length() == 0) {
397
if (not empty_string(prompt))
398
ensure_visibility(debate(), new_topic());
399
}
400
else {
401
exchange()->adjustSize();
402
exchange()->update();
403
exchange()->updateGeometry();
404
if (empty_string(prompt))
405
ensure_visibility(debate(), exchange());
406
else {
407
ensure_visibility(debate(), next(exchange()));
408
QApplication::restoreOverrideCursor();
409
}
410
}
411
}
412
}
413
414