WSL/SLF GitLab Repository

PreviewEdit.cc 7.66 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/****************************************************************************
** This file is based on the line numbering example in Qt's documentation at
** https://doc.qt.io/qt-5/qtwidgets-widgets-PreviewEdit-example.html.
** It is lincensed under a BSD license (see original text below).
** Changes by M. Bavay, 2020-03-20 for WSL/SLF
**/

/**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** BSD License Usage
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in
**     the documentation and/or other materials provided with the
**     distribution.
**   * Neither the name of The Qt Company Ltd nor the names of its
**     contributors may be used to endorse or promote products derived
**     from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
*/

#include "PreviewEdit.h"
#include "src/main/colors.h"

45
#include <QDir>
46
#include <QFontMetrics>
47
48
#include <QMainWindow>
#include <QtWidgets>
49

50
PreviewEdit::PreviewEdit(const bool& monospace) : QPlainTextEdit()
51
{
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
	QList<QKeySequence> shortcuts;
	QStringList descriptions;

	/* set some keyboard shortcuts as tooltip */
	shortcuts << QKeySequence::Cut; descriptions << tr("Cut (whole line on empty selection)");
	shortcuts << QKeySequence::DeleteEndOfLine; descriptions << tr("Delete to the end of the line");
	shortcuts << QKeySequence::Undo; descriptions << tr("Undo");
	shortcuts << QKeySequence::Redo; descriptions << tr("Redo");
	shortcuts << QKeySequence::AddTab; descriptions << tr("Add tab");
	shortcuts << QKeySequence::NextChild; descriptions << tr("Move to next tab");
	shortcuts << QKeySequence::PreviousChild; descriptions << tr("Move to previous tab");
	shortcuts << QKeySequence::Find; descriptions << tr("Find text");
	shortcuts << QKeySequence::SelectNextWord; descriptions << tr("Select next word");
	QString shortcut_help( tr("Some supported editor shortcuts:\n\n") );
	for (int ii = 0; ii < shortcuts.length(); ++ii) {
		shortcut_help.append(shortcuts.at(ii).toString() +
			QString( "\t" ).repeated(3) + descriptions.at(ii) + "\n"); //TODO: the tabs don't align well?
	}
	shortcut_help.replace("Backtab", "Tab"); //bit of an unusual name
	shortcut_help.chop(1); //trailing '\n'
	this->setToolTip(shortcut_help);

	/* add line number side panel */
75
	if (monospace) setMonospacedFont();
76
	sidePanel = new PreviewSidePanel(this);
77
78
	sidePanel->setStyleSheet("QWidget {background-color: " + colors::getQColor("syntax_background").name() + "; color: " +
	    colors::getQColor("sl_base01").name() + "; font-style: italic; font-size: 9pt}");
79
	updateSidePanelWidth();
80

81
82
	connect(this, &PreviewEdit::blockCountChanged, this, &PreviewEdit::updateSidePanelWidth);
	connect(this, &PreviewEdit::updateRequest, this, &PreviewEdit::updateSidePanel);
83
84
}

85
86
87
88
89
90
/**
 * @brief Try to set a monospaced font for files that are space indented.
 */
void PreviewEdit::setMonospacedFont()
{
	QFont mono_font( QFontDatabase::systemFont(QFontDatabase::FixedFont) ); //system recommendation
91
	mono_font.setPointSize( this->font().pointSize() );
92
93
	this->setFont(mono_font);
}
94
95
96

int PreviewEdit::getSidePanelWidth()
{
97
98
99
	//since log10 returns 0 for numbers <10, add 1. For a nicer look, we add 0.5 char's width
	const double digits = floor(log10(qMax(1, blockCount()))) + 1.5;
	const int width = static_cast<int>(fontMetrics().boundingRect(QLatin1Char('0')).width() * digits);
100
101
102
	return width;
}

103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/**
 * @brief Retrieve full file path of opened content.
 * @return Path and name of currently opened file.
 */
QString PreviewEdit::getFullFilePath() const {
	return QDir::toNativeSeparators(file_path_ + "/" + file_name_);
}

/**
 * @brief Remember the currently displayed file's path.
 * @details This function splits its argument up into file path and name, so that
 * any one or both combined can always be retrieved.
 * @param[in] file_path Full path with name and extension to opened file.
 */
void PreviewEdit::setFile(const QString &file_path) {
	const QFileInfo file_info( file_path );
	file_name_ = file_info.fileName();
	if (file_info.exists()) //avoid warning
		file_path_ = file_info.absolutePath();
	else
		file_path_ = QDir::currentPath(); //only a name was given
}

126
127
void PreviewEdit::updateSidePanelWidth()
{
128
129
130
	//we add a small margin between the line number and the line itself
	const int margin_width = fontMetrics().boundingRect(QLatin1Char('0')).width() / 2;
	setViewportMargins(getSidePanelWidth()+margin_width, 0, 0, 0);
131
132
133
134
135
136
137
138
139
140
141
142
143
}

void PreviewEdit::updateSidePanel(const QRect &rect, int dy)
{
	if (dy)
		sidePanel->scroll(0, dy);
	else
		sidePanel->update(0, rect.y(), sidePanel->width(), rect.height());

	if (rect.contains(viewport()->rect()))
		updateSidePanelWidth();
}

144
void PreviewEdit::resizeEvent(QResizeEvent *event)
145
{
146
	QPlainTextEdit::resizeEvent(event);
147
148
149
150
	const QRect cr( contentsRect() );
	sidePanel->setGeometry(QRect(cr.left(), cr.top(), getSidePanelWidth(), cr.height()));
}

151
152
153
154
155
156
157
158
/**
 * @brief Show info when users drag files over the editor.
 * @details We let the text editor handle the drag and drop behaviour. However, we give notice that
 * dropping files in an area outside of the text editor itself will open the files.
 * @param[in] event The drag move event.
 */
void PreviewEdit::dragMoveEvent(QDragMoveEvent *event)
{
159
	const QMainWindow* preview_window( qobject_cast<QMainWindow*>(this->parent()->parent()->parent()) ); //QStackedWidget->QTabWidget->PreviewWindow
160
161
162
163
164
165
166
167
168
169
170
171
172
	if (preview_window) {
		if (event->mimeData()->hasUrls()) {
			preview_window->statusBar()->showMessage(tr("Drop files over the menu or tab titles to open."));
			preview_window->statusBar()->show(); //might be hidden from closing search bar
		}
	}

	event->acceptProposedAction(); //progress as normal
	
	//Note: overriding QDragENTERevent instead causes "QDragManager::drag in possibly invalid state" for no apparent reason,
	//but this here should be good.
}

173
174
175
176
void PreviewEdit::repaintSidePanel(QPaintEvent *event)
{
	QPainter painter(sidePanel);
	QTextBlock block( firstVisibleBlock() );
177
	int lineNumber = block.blockNumber();
Michael Reisecker's avatar
Michael Reisecker committed
178
179
	int top = static_cast<int>(blockBoundingGeometry(block).translated(contentOffset()).top());
	int bottom = top + static_cast<int>(blockBoundingRect(block).height());
180
181
182

	while (block.isValid() && top <= event->rect().bottom()) {
		if (block.isVisible() && bottom >= event->rect().top()) {
183
			const QString number( QString::number(lineNumber + 1) );
184
185
186
187
188
			painter.drawText(0, top, sidePanel->width(), fontMetrics().height(), Qt::AlignRight | Qt::AlignVCenter, number);
		}

		block = block.next();
		top = bottom;
Michael Reisecker's avatar
Michael Reisecker committed
189
		bottom = top + static_cast<int>(blockBoundingRect(block).height());
190
		++lineNumber;
191
192
	}
}