Skip to content

Commit

Permalink
Shell: Scrolling in the log history
Browse files Browse the repository at this point in the history
  • Loading branch information
skyjake committed Feb 5, 2013
1 parent 937a4c4 commit 1e34f1e
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 9 deletions.
119 changes: 114 additions & 5 deletions doomsday/tools/shell/shell-text/src/logwidget.cpp
Expand Up @@ -20,6 +20,7 @@
#include <de/MonospaceLogSinkFormatter>
#include <de/Lockable>
#include <de/LogBuffer>
#include <de/shell/KeyEvent>
#include <de/shell/TextRootWidget>
#include <QList>

Expand Down Expand Up @@ -68,6 +69,14 @@ class Sink : public LogSink, public Lockable
return *_entries[index];
}

void remove(int pos, int n = 1)
{
while(n-- > 0)
{
delete _entries.takeAt(pos);
}
}

private:
LogWidget &_widget;
QList<LogEntry *> _entries;
Expand All @@ -79,8 +88,14 @@ struct LogWidget::Instance
MonospaceLogSinkFormatter formatter;
int cacheWidth;
QList<TextCanvas *> cache; ///< Indices match entry indices in sink.
int maxEntries;
int visibleOffset;

Instance(LogWidget &inst) : sink(inst), cacheWidth(0)
Instance(LogWidget &inst)
: sink(inst),
cacheWidth(0),
maxEntries(1000),
visibleOffset(0)
{}

~Instance()
Expand All @@ -93,6 +108,41 @@ struct LogWidget::Instance
foreach(TextCanvas *cv, cache) delete cv;
cache.clear();
}

void prune()
{
// Remove old entries if there are too many.
int excess = sink.entryCount() - maxEntries;
if(excess > 0)
{
sink.remove(0, excess);
}
while(excess-- > 0 && !cache.isEmpty())
{
delete cache.takeFirst();
}
}

int totalHeight()
{
int total = 0;
for(int idx = sink.entryCount() - 1; idx >= 0; --idx)
{
total += cache[idx]->size().y;
}
return total;
}

int maxVisibleOffset(int visibleHeight)
{
// Determine total height of all entries.
return de::max(0, totalHeight() - visibleHeight);
}

void clampVisibleOffset(int visibleHeight)
{
visibleOffset = de::min(visibleOffset, maxVisibleOffset(visibleHeight));
}
};

LogWidget::LogWidget(String const &name) : TextWidget(name), d(new Instance(*this))
Expand Down Expand Up @@ -139,24 +189,83 @@ void LogWidget::draw()
TextCanvas *buf = new TextCanvas(Vector2i(pos.width(), lines.size()));
d->cache.append(buf);

TextCanvas::Char::Attribs attribs = (entry.flags() & LogEntry::Remote?
TextCanvas::Char::DefaultAttributes : TextCanvas::Char::Bold);

// Draw the text.
for(int i = 0; i < lines.size(); ++i)
{
buf->drawText(Vector2i(0, i), lines[i]);
buf->drawText(Vector2i(0, i), lines[i], attribs);
}

// Adjust visible offset.
if(d->visibleOffset > 0) d->visibleOffset += lines.size();
}

DENG2_ASSERT(d->cache.size() == d->sink.entryCount());

d->clampVisibleOffset(buf.height());

// Draw in reverse, as much as we need.
int yBottom = pos.size().y;
int yBottom = buf.height() + d->visibleOffset;

for(int idx = d->sink.entryCount() - 1; yBottom > 0 && idx >= 0; --idx)
{
TextCanvas *canvas = d->cache[idx];
yBottom -= canvas->size().y;
buf.draw(*canvas, Vector2i(0, yBottom));
if(yBottom < buf.height())
{
buf.draw(*canvas, Vector2i(0, yBottom));
}
}

d->sink.unlock();
// Draw the scroll indicator.
if(d->visibleOffset > 0)
{
int const indHeight = de::clamp(2, de::floor(float(buf.height() * buf.height()) /
float(d->totalHeight())), buf.height() / 2);
float const indPos = float(d->visibleOffset) / float(d->maxVisibleOffset(buf.height()));
int const avail = buf.height() - indHeight;
for(int i = 0; i < indHeight; ++i)
{
buf.put(Vector2i(buf.width() - 1, i + avail - indPos * avail),
TextCanvas::Char(':', TextCanvas::Char::Reverse));
}
}

targetCanvas().draw(buf, pos.topLeft);

d->prune();
d->sink.unlock();
}

bool LogWidget::handleEvent(Event const *event)
{
if(event->type() != Event::KeyPress) return false;

KeyEvent const *ev = static_cast<KeyEvent const *>(event);

switch(ev->key())
{
case Qt::Key_PageUp:
d->visibleOffset += 3;
redraw();
return true;

case Qt::Key_PageDown:
d->visibleOffset = de::max(0, d->visibleOffset - 3);
redraw();
return true;

default:
break;
}

return TextWidget::handleEvent(event);
}

void LogWidget::scrollToBottom()
{
d->visibleOffset = 0;
redraw();
}
9 changes: 9 additions & 0 deletions doomsday/tools/shell/shell-text/src/logwidget.h
Expand Up @@ -24,6 +24,8 @@

class LogWidget : public de::shell::TextWidget
{
Q_OBJECT

public:
LogWidget(de::String const &name = "");
virtual ~LogWidget();
Expand All @@ -35,6 +37,13 @@ class LogWidget : public de::shell::TextWidget
de::LogSink &logSink();

void draw();
bool handleEvent(de::Event const *event);

public slots:
/**
* Moves the scroll offset of the widget to the bottom of the history.
*/
void scrollToBottom();

private:
struct Instance;
Expand Down
9 changes: 5 additions & 4 deletions doomsday/tools/shell/shell-text/src/shellapp.cpp
Expand Up @@ -72,8 +72,8 @@ struct ShellApp::Instance

menuLabel->addAction(new Action(KeyEvent(Qt::Key_F9), &self, SLOT(openMenu())));
menuLabel->addAction(new Action(KeyEvent(Qt::Key_Z, KeyEvent::Control), &self, SLOT(openMenu())));
menuLabel->addAction(new Action(KeyEvent(Qt::Key_X, KeyEvent::Control), &self, SLOT(openMenu())));
menuLabel->addAction(new Action(KeyEvent(Qt::Key_C, KeyEvent::Control), &self, SLOT(openMenu())));
menuLabel->addAction(new Action(KeyEvent(Qt::Key_X, KeyEvent::Control), &self, SLOT(quit())));

// Expanding command line widget.
cli = new CommandLineWidget;
Expand All @@ -92,6 +92,8 @@ struct ShellApp::Instance
.setInput(Rule::Top, root.viewTop())
.setInput(Rule::Bottom, cli->rule().top());

log->addAction(new Action(KeyEvent(Qt::Key_F5), log, SLOT(scrollToBottom())));

// Main menu.
menu = new MenuWidget(MenuWidget::Popup);
menu->appendItem(new Action(tr("Connect to..."),
Expand All @@ -100,10 +102,9 @@ struct ShellApp::Instance
menu->appendSeparator();
menu->appendItem(new Action(tr("Start local server"), &self, SLOT(askToStartLocalServer())));
menu->appendSeparator();
menu->appendItem(new Action(tr("Scroll to bottom"), log, SLOT(scrollToBottom())), "F5");
menu->appendItem(new Action(tr("About"), &self, SLOT(showAbout())));
menu->appendItem(new Action(tr("Quit Shell"),
KeyEvent(Qt::Key_X, KeyEvent::Control),
&self, SLOT(quit())), "Ctrl-X");
menu->appendItem(new Action(tr("Quit Shell"), &self, SLOT(quit())), "Ctrl-X");
menu->rule()
.setInput(Rule::Bottom, menuLabel->rule().top())
.setInput(Rule::Left, menuLabel->rule().left());
Expand Down

0 comments on commit 1e34f1e

Please sign in to comment.