1
0
mirror of https://github.com/Swordfish90/cool-retro-term.git synced 2025-01-31 10:11:20 +00:00

689 lines
16 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 "screen_data.h"
#include "block.h"
#include "cursor.h"
#include "text.h"
#include "scrollback.h"
#include "controll_chars.h"
#include "character_sets.h"
#include <QtCore/QTimer>
#include <QtCore/QSocketNotifier>
#include <QtGui/QGuiApplication>
#include <QtCore/QDebug>
#include <float.h>
Screen::Screen(QObject *parent)
: QObject(parent)
, m_palette(new ColorPalette(this))
, m_parser(this)
, m_timer_event_id(0)
, m_width(1)
, m_height(0)
, m_primary_data(new ScreenData(0, this))
, m_alternate_data(new ScreenData(0, this))
, m_current_data(m_primary_data)
, m_old_current_data(m_primary_data)
, m_selection_valid(false)
, m_selection_moved(0)
, m_flash(false)
, m_cursor_changed(false)
, m_application_cursor_key_mode(false)
, m_fast_scroll(true)
, m_default_background(m_palette->normalColor(ColorPalette::DefaultBackground))
{
Cursor *cursor = new Cursor(this);
m_cursor_stack << cursor;
m_new_cursors << cursor;
connect(m_primary_data, SIGNAL(contentHeightChanged()), this, SIGNAL(contentHeightChanged()));
connect(m_palette, SIGNAL(changed()), this, SLOT(paletteChanged()));
setHeight(25);
setWidth(80);
connect(&m_pty, &YatPty::readyRead, this, &Screen::readData);
connect(&m_pty, SIGNAL(hangupReceived()),qGuiApp, SLOT(quit()));
}
Screen::~Screen()
{
for(int i = 0; i < m_to_delete.size(); i++) {
delete m_to_delete.at(i);
}
//m_to_delete.clear();
delete m_primary_data;
delete m_alternate_data;
}
QColor Screen::defaultForgroundColor() const
{
return m_palette->normalColor(ColorPalette::DefaultForground);
}
QColor Screen::defaultBackgroundColor() const
{
return m_palette->normalColor(ColorPalette::DefaultBackground);
}
void Screen::emitRequestHeight(int newHeight)
{
emit requestHeightChange(newHeight);
}
void Screen::setHeight(int height)
{
if (height == m_height)
return;
emit heightAboutToChange(height, currentCursor()->new_y(), currentScreenData()->scrollback()->height());
m_height = height;
m_pty.setHeight(height, height * 10);
emit heightChanged();
}
int Screen::height() const
{
return m_height;
}
int Screen::contentHeight() const
{
return currentScreenData()->contentHeight();
}
void Screen::emitRequestWidth(int newWidth)
{
emit requestWidthChange(newWidth);
}
void Screen::setWidth(int width)
{
if (width == m_width)
return;
emit widthAboutToChange(width);
m_width = width;
m_pty.setWidth(width, width * 10);
emit widthChanged();
}
int Screen::width() const
{
return m_width;
}
void Screen::useAlternateScreenBuffer()
{
if (m_current_data == m_primary_data) {
disconnect(m_primary_data, SIGNAL(contentHeightChanged()), this, SIGNAL(contentHeightChanged()));
m_current_data = m_alternate_data;
m_current_data->clear();
connect(m_alternate_data, SIGNAL(contentHeightChanged()), this, SIGNAL(contentHeightChanged()));
emit contentHeightChanged();
}
}
void Screen::useNormalScreenBuffer()
{
if (m_current_data == m_alternate_data) {
disconnect(m_alternate_data, SIGNAL(contentHeightChanged()), this, SIGNAL(contentHeightChanged()));
m_current_data = m_primary_data;
connect(m_primary_data, SIGNAL(contentHeightChanged()), this, SIGNAL(contentHeightChanged()));
emit contentHeightChanged();
}
}
TextStyle Screen::defaultTextStyle() const
{
TextStyle style;
style.style = TextStyle::Normal;
style.forground = ColorPalette::DefaultForground;
style.background = ColorPalette::DefaultBackground;
return style;
}
void Screen::saveCursor()
{
Cursor *new_cursor = new Cursor(this);
if (m_cursor_stack.size())
m_cursor_stack.last()->setVisible(false);
m_cursor_stack << new_cursor;
m_new_cursors << new_cursor;
}
void Screen::restoreCursor()
{
if (m_cursor_stack.size() <= 1)
return;
m_delete_cursors.append(m_cursor_stack.takeLast());
m_cursor_stack.last()->setVisible(true);
}
void Screen::clearScreen()
{
currentScreenData()->clear();
}
ColorPalette *Screen::colorPalette() const
{
return m_palette;
}
void Screen::fill(const QChar character)
{
currentScreenData()->fill(character);
}
void Screen::clear()
{
fill(QChar(' '));
}
void Screen::setFastScroll(bool fast)
{
m_fast_scroll = fast;
}
bool Screen::fastScroll() const
{
return m_fast_scroll;
}
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
{
//currentScreenData()->sendSelectionToClipboard(m_selection_start, m_selection_end, QClipboard::Clipboard);
}
void Screen::sendSelectionToSelection() const
{
//currentScreenData()->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)
{
Q_UNUSED(clicked);
//int start, end;
//currentScreenData()->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;
}
void Screen::printScreen() const
{
currentScreenData()->printStyleInformation();
qDebug() << "Total height: " << currentScreenData()->contentHeight();
}
void Screen::scheduleEventDispatch()
{
if (!m_timer_event_id) {
m_timer_event_id = startTimer(1);
m_time_since_initiated.restart();
}
m_time_since_parsed.restart();
}
void Screen::dispatchChanges()
{
if (m_old_current_data != m_current_data) {
m_old_current_data->releaseTextObjects();
m_old_current_data = m_current_data;
}
currentScreenData()->dispatchLineEvents();
emit dispatchTextSegmentChanges();
static int max_to_delete_size = 0;
if (max_to_delete_size < m_to_delete.size()) {
max_to_delete_size = m_to_delete.size();
qDebug() << "TO DELETE SIZE :" << max_to_delete_size;
}
if (m_flash) {
m_flash = false;
emit flash();
}
for (int i = 0; i < m_delete_cursors.size(); i++) {
int new_index = m_new_cursors.indexOf(m_delete_cursors.at(i));
if (new_index >= 0)
m_new_cursors.remove(new_index);
delete m_delete_cursors.at(i);
}
m_delete_cursors.clear();
for (int i = 0; i < m_new_cursors.size(); i++) {
emit cursorCreated(m_new_cursors.at(i));
}
m_new_cursors.clear();
for (int i = 0; i < m_cursor_stack.size(); i++) {
m_cursor_stack[i]->dispatchEvents();
}
if (m_selection_valid && m_selection_moved) {
if (m_selection_start.y() < 0 ||
m_selection_end.y() >= height()) {
setSelectionEnabled(false);
} else {
emit selectionAreaStartChanged();
emit selectionAreaEndChanged();
}
}
}
void Screen::sendPrimaryDA()
{
m_pty.write(QByteArrayLiteral("\033[?6c"));
}
void Screen::sendSecondaryDA()
{
m_pty.write(QByteArrayLiteral("\033[>1;95;0c"));
}
void Screen::setApplicationCursorKeysMode(bool enable)
{
m_application_cursor_key_mode = enable;
}
bool Screen::applicationCursorKeyMode() const
{
return m_application_cursor_key_mode;
}
void Screen::ensureVisiblePages(int top_line)
{
currentScreenData()->ensureVisiblePages(top_line);
}
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)
{
// if (key == Qt::Key_Control)
// printScreen();
/// UGH, this function 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;
}
Text *Screen::createTextSegment(const TextStyleLine &style_line)
{
Q_UNUSED(style_line);
Text *to_return;
if (m_to_delete.size()) {
to_return = m_to_delete.takeLast();
to_return->setVisible(true);
} else {
to_return = new Text(this);
emit textCreated(to_return);
}
return to_return;
}
void Screen::releaseTextSegment(Text *text)
{
m_to_delete.append(text);
}
void Screen::readData(const QByteArray &data)
{
m_parser.addData(data);
scheduleEventDispatch();
}
void Screen::paletteChanged()
{
QColor new_default = m_palette->normalColor(ColorPalette::DefaultBackground);
if (new_default != m_default_background) {
m_default_background = new_default;
emit defaultBackgroundColorChanged();
}
}
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 || m_time_since_initiated.elapsed() > 8)) {
killTimer(m_timer_event_id);
m_timer_event_id = 0;
dispatchChanges();
}
}