1
0
mirror of https://github.com/Swordfish90/cool-retro-term.git synced 2025-02-07 13:41:27 +00:00
2013-11-23 13:59:05 +01:00

959 lines
23 KiB
C++

/**************************************************************************************************
* Copyright (c) 2012 Jørgen Lind
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
* OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
***************************************************************************************************/
#include "screen.h"
#include "line.h"
#include "controll_chars.h"
#include <QtCore/QTimer>
#include <QtCore/QSocketNotifier>
#include <QtGui/QGuiApplication>
#include <QtQuick/QQuickItem>
#include <QtQuick/QQuickView>
#include <QtQml/QQmlComponent>
#include <QtCore/QDebug>
#include <float.h>
Screen::Screen(QObject *parent)
: QObject(parent)
, m_parser(this)
, m_timer_event_id(0)
, m_cursor_visible(true)
, m_cursor_visible_changed(false)
, m_cursor_blinking(true)
, m_cursor_blinking_changed(false)
, m_insert_mode(Replace)
, m_selection_valid(false)
, m_selection_moved(0)
, m_flash(false)
, m_cursor_changed(false)
, m_reset(false)
, m_application_cursor_key_mode(false)
{
connect(&m_pty, &YatPty::readyRead, this, &Screen::readData);
m_screen_stack.reserve(2);
m_cursor_stack << QPoint(0,0);
m_current_text_style.style = TextStyle::Normal;
m_current_text_style.forground = ColorPalette::DefaultForground;
m_current_text_style.background = ColorPalette::DefaultBackground;
connect(&m_pty, SIGNAL(hangupReceived()),qGuiApp, SLOT(quit()));
}
Screen::~Screen()
{
for (int i = 0; i < m_screen_stack.size(); i++) {
delete m_screen_stack.at(i);
}
}
QColor Screen::screenBackground()
{
return QColor(Qt::black);
}
QColor Screen::defaultForgroundColor() const
{
return m_palette.normalColor(ColorPalette::DefaultForground);
}
QColor Screen::defaultBackgroundColor() const
{
return QColor(Qt::transparent);
}
void Screen::setHeight(int height)
{
if (!m_screen_stack.size()) {
m_screen_stack << new ScreenData(this);
}
ScreenData *data = current_screen_data();
int size_difference = data->height() - height;
if (!size_difference)
return;
data->setHeight(height);
if (size_difference > 0) {
if (current_cursor_y() > 0)
current_cursor_pos().ry()--;
}
m_pty.setHeight(height, height * 10);
dispatchChanges();
}
void Screen::setWidth(int width)
{
if (!m_screen_stack.size())
m_screen_stack << new ScreenData(this);
current_screen_data()->setWidth(width);
m_pty.setWidth(width, width * 10);
}
int Screen::width() const
{
return m_pty.size().width();
}
void Screen::saveScreenData()
{
ScreenData *new_data = new ScreenData(this);
QSize pty_size = m_pty.size();
new_data->setHeight(pty_size.height());
new_data->setWidth(pty_size.width());
for (int i = 0; i < new_data->height(); i++) {
current_screen_data()->at(i)->setVisible(false);
}
m_screen_stack << new_data;
setSelectionEnabled(false);
}
void Screen::restoreScreenData()
{
ScreenData *data = current_screen_data();
m_screen_stack.remove(m_screen_stack.size()-1);
delete data;
QSize pty_size = m_pty.size();
current_screen_data()->setHeight(pty_size.height());
current_screen_data()->setWidth(pty_size.width());
for (int i = 0; i < current_screen_data()->height(); i++) {
current_screen_data()->at(i)->setVisible(true);
}
setSelectionEnabled(false);
}
int Screen::height() const
{
return current_screen_data()->height();
}
void Screen::setInsertMode(InsertMode mode)
{
m_insert_mode = mode;
}
void Screen::setTextStyle(TextStyle::Style style, bool add)
{
if (add) {
m_current_text_style.style |= style;
} else {
m_current_text_style.style &= !style;
}
}
void Screen::resetStyle()
{
m_current_text_style.background = ColorPalette::DefaultBackground;
m_current_text_style.forground = ColorPalette::DefaultForground;
m_current_text_style.style = TextStyle::Normal;
}
TextStyle Screen::currentTextStyle() const
{
return m_current_text_style;
}
TextStyle Screen::defaultTextStyle() const
{
TextStyle style;
style.style = TextStyle::Normal;
style.forground = ColorPalette::DefaultForground;
style.background = ColorPalette::DefaultBackground;
return style;
}
QPoint Screen::cursorPosition() const
{
return QPoint(current_cursor_x(),current_cursor_y());
}
void Screen::moveCursorHome()
{
current_cursor_pos().setX(0);
m_cursor_changed = true;
}
void Screen::moveCursorTop()
{
current_cursor_pos().setY(0);
m_cursor_changed = true;
}
void Screen::moveCursorUp(int n_positions)
{
if (!current_cursor_pos().y())
return;
if (n_positions <= current_cursor_pos().y()) {
current_cursor_pos().ry() -= n_positions;
} else {
current_cursor_pos().ry() = 0;
}
m_cursor_changed = true;
}
void Screen::moveCursorDown()
{
current_cursor_pos().ry() += 1;
m_cursor_changed = true;
}
void Screen::moveCursorLeft()
{
current_cursor_pos().rx() -= 1;
m_cursor_changed = true;
}
void Screen::moveCursorRight(int n_positions)
{
current_cursor_pos().rx() += n_positions;
m_cursor_changed = true;
}
void Screen::moveCursor(int x, int y)
{
if (x != 0)
x--;
if (y != 0)
y--;
current_cursor_pos().setX(x);
int height = this->height();
if (y >= height) {
current_cursor_pos().setY(height-1);
} else {
current_cursor_pos().setY(y);
}
m_cursor_changed = true;
}
void Screen::moveCursorToLine(int line)
{
current_cursor_pos().setY(line-1);
m_cursor_changed = true;
}
void Screen::moveCursorToCharacter(int character)
{
current_cursor_pos().setX(character-1);
m_cursor_changed = true;
}
void Screen::deleteCharacters(int characters)
{
switch (m_insert_mode) {
case Insert:
current_screen_data()->clearCharacters(current_cursor_y(), current_cursor_x(), current_cursor_x() + characters -1);
break;
case Replace:
current_screen_data()->deleteCharacters(current_cursor_y(), current_cursor_x(), current_cursor_x() + characters -1);
break;
default:
break;
}
}
void Screen::setCursorVisible(bool visible)
{
m_cursor_visible = visible;
m_cursor_visible_changed = true;
}
bool Screen::cursorVisible()
{
return m_cursor_visible;
}
void Screen::setCursorBlinking(bool blinking)
{
m_cursor_blinking = blinking;
m_cursor_blinking_changed = true;
}
bool Screen::cursorBlinking()
{
return m_cursor_blinking;
}
void Screen::saveCursor()
{
QPoint point = current_cursor_pos();
m_cursor_stack << point;
}
void Screen::restoreCursor()
{
if (m_cursor_stack.size() <= 1)
return;
m_cursor_stack.remove(m_screen_stack.size()-1);
}
void Screen::replaceAtCursor(const QString &text)
{
if (m_selection_valid ) {
if (current_cursor_y() >= m_selection_start.y() && current_cursor_y() <= m_selection_end.y())
//don't need to schedule as event since it will only happen once
setSelectionEnabled(false);
}
if (current_cursor_x() + text.size() <= width()) {
Line *line = current_screen_data()->at(current_cursor_y());
line->replaceAtPos(current_cursor_x(), text, m_current_text_style);
current_cursor_pos().rx() += text.size();
} else {
for (int i = 0; i < text.size();) {
if (current_cursor_x() == width()) {
current_cursor_pos().setX(0);
lineFeed();
}
QString toLine = text.mid(i,current_screen_data()->width() - current_cursor_x());
Line *line = current_screen_data()->at(current_cursor_y());
line->replaceAtPos(current_cursor_x(),toLine, m_current_text_style);
i+= toLine.size();
current_cursor_pos().rx() += toLine.size();
}
}
m_cursor_changed = true;
}
void Screen::insertEmptyCharsAtCursor(int len)
{
if (m_selection_valid) {
if (current_cursor_y() >= m_selection_start.y() && current_cursor_y() <= m_selection_end.y())
//don't need to schedule as event since it will only happen once
setSelectionEnabled(false);
}
Line *line = current_screen_data()->at(current_cursor_y());
QString empty(len, QChar(' '));
line->insertAtPos(current_cursor_x(), empty, defaultTextStyle());
}
void Screen::backspace()
{
current_cursor_pos().rx()--;
m_cursor_changed = true;
}
void Screen::eraseLine()
{
current_screen_data()->clearLine(current_cursor_y());
}
void Screen::eraseFromCursorPositionToEndOfLine()
{
current_screen_data()->clearToEndOfLine(current_cursor_y(), current_cursor_x());
}
void Screen::eraseFromCursorPosition(int n_chars)
{
current_screen_data()->clearCharacters(current_cursor_y(), current_cursor_x(), current_cursor_x() + n_chars - 1);
}
void Screen::eraseFromCurrentLineToEndOfScreen()
{
current_screen_data()->clearToEndOfScreen(current_cursor_y());
}
void Screen::eraseFromCurrentLineToBeginningOfScreen()
{
current_screen_data()->clearToBeginningOfScreen(current_cursor_y());
}
void Screen::eraseToCursorPosition()
{
qDebug() << "eraseToCursorPosition NOT IMPLEMENTED!";
}
void Screen::eraseScreen()
{
current_screen_data()->clear();
}
void Screen::setTextStyleColor(ushort color)
{
Q_ASSERT(color >= 30 && color < 50);
if (color < 38) {
m_current_text_style.forground = ColorPalette::Color(color - 30);
} else if (color == 39) {
m_current_text_style.forground = ColorPalette::DefaultForground;
} else if (color >= 40 && color < 48) {
m_current_text_style.background = ColorPalette::Color(color - 40);
} else if (color == 49) {
m_current_text_style.background = ColorPalette::DefaultBackground;
} else {
qDebug() << "Failed to set color";
}
}
const ColorPalette *Screen::colorPalette() const
{
return &m_palette;
}
void Screen::lineFeed()
{
int cursor_y = current_cursor_y();
if(cursor_y == current_screen_data()->scrollAreaEnd()) {
m_selection_start.ry()--;
m_selection_end.ry()--;
m_selection_moved = true;
moveLine(current_screen_data()->scrollAreaStart(),cursor_y);
} else {
current_cursor_pos().ry()++;
m_cursor_changed = true;
}
}
void Screen::reverseLineFeed()
{
int cursor_y = current_cursor_y();
if (cursor_y == current_screen_data()->scrollAreaStart()) {
m_selection_start.ry()++;
m_selection_end.ry()++;
m_selection_moved = true;
moveLine(current_screen_data()->scrollAreaEnd(), cursor_y);
} else {
current_cursor_pos().ry()--;
m_cursor_changed = true;
}
}
void Screen::insertLines(int count)
{
for (int i = 0; i < count; i++) {
moveLine(current_screen_data()->scrollAreaEnd(),current_cursor_y());
}
}
void Screen::deleteLines(int count)
{
for (int i = 0; i < count; i++) {
moveLine(current_cursor_y(),current_screen_data()->scrollAreaEnd());
}
}
void Screen::setScrollArea(int from, int to)
{
from--;
to--;
current_screen_data()->setScrollArea(from,to);
}
QPointF Screen::selectionAreaStart() const
{
return m_selection_start;
}
void Screen::setSelectionAreaStart(const QPointF &start)
{
bool emitChanged = m_selection_start != start;
m_selection_start = start;
setSelectionValidity();
if (emitChanged)
emit selectionAreaStartChanged();
}
QPointF Screen::selectionAreaEnd() const
{
return m_selection_end;
}
void Screen::setSelectionAreaEnd(const QPointF &end)
{
bool emitChanged = m_selection_end != end;
m_selection_end = end;
setSelectionValidity();
if (emitChanged)
emit selectionAreaEndChanged();
}
bool Screen::selectionEnabled() const
{
return m_selection_valid;
}
void Screen::setSelectionEnabled(bool enabled)
{
bool emitchanged = m_selection_valid != enabled;
m_selection_valid = enabled;
if (emitchanged)
emit selectionEnabledChanged();
}
void Screen::sendSelectionToClipboard() const
{
current_screen_data()->sendSelectionToClipboard(m_selection_start, m_selection_end, QClipboard::Clipboard);
}
void Screen::sendSelectionToSelection() const
{
current_screen_data()->sendSelectionToClipboard(m_selection_start, m_selection_end, QClipboard::Selection);
}
void Screen::pasteFromSelection()
{
m_pty.write(QGuiApplication::clipboard()->text(QClipboard::Selection).toUtf8());
}
void Screen::pasteFromClipboard()
{
m_pty.write(QGuiApplication::clipboard()->text(QClipboard::Clipboard).toUtf8());
}
void Screen::doubleClicked(const QPointF &clicked)
{
int start, end;
current_screen_data()->getDoubleClickSelectionArea(clicked, &start, &end);
setSelectionAreaStart(QPointF(start,clicked.y()));
setSelectionAreaEnd(QPointF(end,clicked.y()));
}
void Screen::setTitle(const QString &title)
{
m_title = title;
emit screenTitleChanged();
}
QString Screen::title() const
{
return m_title;
}
void Screen::scheduleFlash()
{
m_flash = true;
}
Line *Screen::at(int i) const
{
return current_screen_data()->at(i);
}
void Screen::printScreen() const
{
current_screen_data()->printStyleInformation();
}
void Screen::dispatchChanges()
{
if (m_reset) {
emit reset();
m_update_actions.clear();
m_reset = false;
} else {
qint16 begin_move = -1;
qint16 end_move = -1;
for (int i = 0; i < m_update_actions.size(); i++) {
UpdateAction action = m_update_actions.at(i);
switch(action.action) {
case UpdateAction::MoveLine: {
if (begin_move < 0) {
begin_move = qMin(action.to_line, action.from_line);
end_move = qMax(action.to_line, action.from_line);
} else
if (action.from_line > action.to_line) {
begin_move = qMin(action.to_line, begin_move);
end_move = qMax(action.from_line, end_move);
} else {
begin_move = qMin(action.from_line, begin_move);
end_move = qMax(action.to_line, end_move);
}
}
break;
default:
qDebug() << "unhandeled UpdatAction in TerminalScreen";
break;
}
}
if (begin_move >= 0) {
current_screen_data()->updateIndexes(begin_move, end_move);
}
current_screen_data()->dispatchLineEvents();
emit dispatchTextSegmentChanges();
}
if (m_flash) {
m_flash = false;
emit flash();
}
if (m_cursor_changed) {
m_cursor_changed = false;
emit cursorPositionChanged(current_cursor_x(), current_cursor_y());
}
if (m_cursor_visible_changed) {
m_cursor_visible_changed = false;
emit cursorVisibleChanged();
}
if (m_cursor_blinking_changed) {
m_cursor_blinking_changed = false;
emit cursorBlinkingChanged();
}
if (m_selection_valid && m_selection_moved) {
if (m_selection_start.y() < 0 ||
m_selection_end.y() >= current_screen_data()->height()) {
setSelectionEnabled(false);
} else {
emit selectionAreaStartChanged();
emit selectionAreaEndChanged();
}
}
m_update_actions.clear();
}
void Screen::sendPrimaryDA()
{
m_pty.write(QByteArrayLiteral("\033[?6c"));
}
void Screen::sendSecondaryDA()
{
m_pty.write(QByteArrayLiteral("\033[>1;95;0c"));
}
void Screen::setCharacterMap(const QString &string)
{
m_character_map = string;
}
QString Screen::characterMap() const
{
return m_character_map;
}
void Screen::setApplicationCursorKeysMode(bool enable)
{
m_application_cursor_key_mode = enable;
}
bool Screen::applicationCursorKeyMode() const
{
return m_application_cursor_key_mode;
}
static bool hasControll(Qt::KeyboardModifiers modifiers)
{
#ifdef Q_OS_MAC
return modifiers & Qt::MetaModifier;
#else
return modifiers & Qt::ControlModifier;
#endif
}
static bool hasMeta(Qt::KeyboardModifiers modifiers)
{
#ifdef Q_OS_MAC
return modifiers & Qt::ControlModifier;
#else
return modifiers & Qt::MetaModifier;
#endif
}
void Screen::sendKey(const QString &text, Qt::Key key, Qt::KeyboardModifiers modifiers)
{
/// UGH, this functions should be re-written
char escape = '\0';
char control = '\0';
char code = '\0';
QVector<ushort> parameters;
bool found = true;
switch(key) {
case Qt::Key_Up:
escape = C0::ESC;
if (m_application_cursor_key_mode)
control = C1_7bit::SS3;
else
control = C1_7bit::CSI;
code = 'A';
break;
case Qt::Key_Right:
escape = C0::ESC;
if (m_application_cursor_key_mode)
control = C1_7bit::SS3;
else
control = C1_7bit::CSI;
code = 'C';
break;
case Qt::Key_Down:
escape = C0::ESC;
if (m_application_cursor_key_mode)
control = C1_7bit::SS3;
else
control = C1_7bit::CSI;
code = 'B';
break;
case Qt::Key_Left:
escape = C0::ESC;
if (m_application_cursor_key_mode)
control = C1_7bit::SS3;
else
control = C1_7bit::CSI;
code = 'D';
break;
case Qt::Key_Insert:
escape = C0::ESC;
control = C1_7bit::CSI;
parameters.append(2);
code = '~';
break;
case Qt::Key_Delete:
escape = C0::ESC;
control = C1_7bit::CSI;
parameters.append(3);
code = '~';
break;
case Qt::Key_Home:
escape = C0::ESC;
control = C1_7bit::CSI;
parameters.append(1);
code = '~';
break;
case Qt::Key_End:
escape = C0::ESC;
control = C1_7bit::CSI;
parameters.append(4);
code = '~';
break;
case Qt::Key_PageUp:
escape = C0::ESC;
control = C1_7bit::CSI;
parameters.append(5);
code = '~';
break;
case Qt::Key_PageDown:
escape = C0::ESC;
control = C1_7bit::CSI;
parameters.append(6);
code = '~';
break;
case Qt::Key_F1:
case Qt::Key_F2:
case Qt::Key_F3:
case Qt::Key_F4:
if (m_application_cursor_key_mode) {
parameters.append((key & 0xff) - 37);
escape = C0::ESC;
control = C1_7bit::CSI;
code = '~';
}
break;
case Qt::Key_F5:
case Qt::Key_F6:
case Qt::Key_F7:
case Qt::Key_F8:
case Qt::Key_F9:
case Qt::Key_F10:
case Qt::Key_F11:
case Qt::Key_F12:
if (m_application_cursor_key_mode) {
parameters.append((key & 0xff) - 36);
escape = C0::ESC;
control = C1_7bit::CSI;
code = '~';
}
break;
case Qt::Key_Control:
case Qt::Key_Shift:
case Qt::Key_Alt:
case Qt::Key_AltGr:
return;
break;
default:
found = false;
}
if (found) {
int term_mods = 0;
if (modifiers & Qt::ShiftModifier)
term_mods |= 1;
if (modifiers & Qt::AltModifier)
term_mods |= 2;
if (modifiers & Qt::ControlModifier)
term_mods |= 4;
QByteArray toPty;
if (term_mods) {
term_mods++;
parameters.append(term_mods);
}
if (escape)
toPty.append(escape);
if (control)
toPty.append(control);
if (parameters.size()) {
for (int i = 0; i < parameters.size(); i++) {
if (i)
toPty.append(';');
toPty.append(QByteArray::number(parameters.at(i)));
}
}
if (code)
toPty.append(code);
m_pty.write(toPty);
} else {
QString verifiedText = text.simplified();
if (verifiedText.isEmpty()) {
switch (key) {
case Qt::Key_Return:
case Qt::Key_Enter:
verifiedText = "\r";
break;
case Qt::Key_Backspace:
verifiedText = "\010";
break;
case Qt::Key_Tab:
verifiedText = "\t";
break;
case Qt::Key_Control:
case Qt::Key_Meta:
case Qt::Key_Alt:
case Qt::Key_Shift:
return;
case Qt::Key_Space:
verifiedText = " ";
break;
default:
return;
}
}
QByteArray to_pty;
QByteArray key_text;
if (hasControll(modifiers)) {
char key_char = verifiedText.toLocal8Bit().at(0);
key_text.append(key_char & 0x1F);
} else {
key_text = verifiedText.toUtf8();
}
if (modifiers & Qt::AltModifier) {
to_pty.append(C0::ESC);
}
if (hasMeta(modifiers)) {
to_pty.append(C0::ESC);
to_pty.append('@');
to_pty.append(FinalBytesNoIntermediate::Reserved3);
}
to_pty.append(key_text);
m_pty.write(to_pty);
}
}
YatPty *Screen::pty()
{
return &m_pty;
}
Line *Screen::line_at_cursor() const
{
return current_screen_data()->at(current_cursor_y());
}
void Screen::readData(const QByteArray &data)
{
m_parser.addData(data);
if (!m_timer_event_id)
m_timer_event_id = startTimer(3);
m_time_since_parsed.restart();
}
void Screen::moveLine(qint16 from, qint16 to)
{
current_screen_data()->moveLine(from,to);
scheduleMoveSignal(from,to);
}
void Screen::scheduleMoveSignal(qint16 from, qint16 to)
{
if (m_update_actions.size() &&
m_update_actions.last().action == UpdateAction::MoveLine &&
m_update_actions.last().from_line == from &&
m_update_actions.last().to_line == to) {
m_update_actions.last().count++;
} else {
m_update_actions << UpdateAction(UpdateAction::MoveLine, from, to, 1);
}
}
void Screen::setSelectionValidity()
{
if (m_selection_end.y() > m_selection_start.y() ||
(m_selection_end.y() == m_selection_start.y() &&
m_selection_end.x() > m_selection_start.x())) {
setSelectionEnabled(true);
} else {
setSelectionEnabled(false);
}
}
void Screen::timerEvent(QTimerEvent *)
{
if (m_timer_event_id && m_time_since_parsed.elapsed() > 3) {
killTimer(m_timer_event_id);
m_timer_event_id = 0;
dispatchChanges();
}
}