canvas.cpp 11.8 KB
Newer Older
Philipp Erhardt's avatar
Philipp Erhardt committed
1
2
3
4
5
#include <QAction>
#include <QStringListIterator>
#include <QKeySequence>
#include <QString>
#include <QPainter>
6
#include <QApplication>
7
#include <QDesktopWidget>
Philipp Erhardt's avatar
Philipp Erhardt committed
8
#include <QTimer>
9
#include <QLabel>
Philipp Erhardt's avatar
Philipp Erhardt committed
10
#include <iostream>
11
12
#include "canvas.h"
#include "viewer.h"
13
14
15
#include "layout/layout.h"
#include "layout/presentationlayout.h"
#include "layout/gridlayout.h"
16
#include "layout/presenterlayout.h"
Philipp Erhardt's avatar
Philipp Erhardt committed
17
#include "resourcemanager.h"
Philipp Erhardt's avatar
Philipp Erhardt committed
18
#include "search.h"
19
#include "gotoline.h"
Philipp Erhardt's avatar
Philipp Erhardt committed
20
#include "config.h"
21
#include "beamerwindow.h"
Philipp Erhardt's avatar
Philipp Erhardt committed
22
#include "util.h"
23
24
25
26

using namespace std;


Philipp Erhardt's avatar
Philipp Erhardt committed
27
Canvas::Canvas(Viewer *v, QWidget *parent) :
28
		QWidget(parent),
Philipp Erhardt's avatar
Philipp Erhardt committed
29
		viewer(v),
Philipp Erhardt's avatar
Philipp Erhardt committed
30
		triple_click_possible(false),
Philipp Erhardt's avatar
Philipp Erhardt committed
31
		valid(true) {
32
33
	setFocusPolicy(Qt::StrongFocus);

Philipp Erhardt's avatar
Philipp Erhardt committed
34
35
	// load config options
	CFG *config = CFG::get_instance();
36

37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
	{
		bool ok;
		unsigned int color = config->get_value("background_color").toString().toUInt(&ok, 16);
		if (ok) {
			background.setRgba(color);
		} else {
			cerr << "failed to parse background_color" << endl;
		}
	}
	{
		bool ok;
		unsigned int color = config->get_value("background_color_fullscreen").toString().toUInt(&ok, 16);
		if (ok) {
			background_fullscreen.setRgba(color);
		} else {
			cerr << "failed to parse background_color_fullscreen" << endl;
		}
	}
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
	mouse_wheel_factor = config->get_value("mouse_wheel_factor").toInt();

	switch (config->get_value("click_link_button").toInt()) {
		case 1: click_link_button = Qt::LeftButton; break;
		case 2: click_link_button = Qt::RightButton; break;
		case 3: click_link_button = Qt::MidButton; break;
		case 4: click_link_button = Qt::XButton1; break;
		case 5: click_link_button = Qt::XButton2; break;
		default: click_link_button = Qt::NoButton;
	}

	switch (config->get_value("drag_view_button").toInt()) {
		case 1: drag_view_button = Qt::LeftButton; break;
		case 2: drag_view_button = Qt::RightButton; break;
		case 3: drag_view_button = Qt::MidButton; break;
		case 4: drag_view_button = Qt::XButton1; break;
		case 5: drag_view_button = Qt::XButton2; break;
		default: drag_view_button = Qt::NoButton;
	}

	switch (config->get_value("select_text_button").toInt()) {
		case 1: select_text_button = Qt::LeftButton; break;
		case 2: select_text_button = Qt::RightButton; break;
		case 3: select_text_button = Qt::MidButton; break;
		case 4: select_text_button = Qt::XButton1; break;
		case 5: select_text_button = Qt::XButton2; break;
		default: select_text_button = Qt::NoButton;
	}
83

Philipp Erhardt's avatar
Philipp Erhardt committed
84
85
86
	presentation_layout = new PresentationLayout(viewer, 0);
	grid_layout = new GridLayout(viewer, 0);
	presenter_layout = new PresenterLayout(viewer, 1); // TODO add config string
87

88
89
	QString default_layout = config->get_value("default_layout").toString();
	if (default_layout == "grid") {
90
		cur_layout = grid_layout;
91
92
	} else if (default_layout == "presenter") {
		cur_layout = presenter_layout;
93
	} else { // "presentation" and everything else
94
		cur_layout = presentation_layout;
95
96
	}

Philipp Erhardt's avatar
Philipp Erhardt committed
97
	// apply start option
98
99
100
	if (viewer->get_res()->is_valid()) {
		cur_layout->scroll_page(config->get_tmp_value("start_page").toInt(), false);
	}
Philipp Erhardt's avatar
Philipp Erhardt committed
101

102
	setup_keys(this);
103

104
105
106
107
108
109
	if (drag_view_button == Qt::LeftButton) {
		setCursor(Qt::OpenHandCursor);
	} else {
		setCursor(Qt::IBeamCursor);
	}

110
111
112
	// prints the string representation of a key
//	cerr << QKeySequence(Qt::Key_Equal).toString().toUtf8().constData() << endl;

113
	goto_line = new GotoLine(viewer->get_res()->get_page_count(), this);
114
115
	goto_line->move(0, height() - goto_line->height()); // TODO goto_line->height() reports the wrong size
	goto_line->hide();
116
	connect(goto_line, SIGNAL(returnPressed()), this, SLOT(goto_page()), Qt::UniqueConnection);
117

118
119
120
121
	page_overlay = new QLabel(this);
	page_overlay->setAutoFillBackground(true);
	page_overlay->show();

122
123
124
125
126
	// setup beamer
	BeamerWindow *beamer = viewer->get_beamer();
	setup_keys(beamer);
	if (cur_layout == presenter_layout) {
		beamer->show();
127
		viewer->show_progress(true);
128
129
	} else {
		beamer->hide();
130
		viewer->show_progress(false);
131
	}
132
133
134
}

Canvas::~Canvas() {
135
	delete page_overlay;
136
	delete goto_line;
137
138
	delete presentation_layout;
	delete grid_layout;
139
	delete presenter_layout;
140
141
}

Philipp Erhardt's avatar
Philipp Erhardt committed
142
143
144
145
bool Canvas::is_valid() const {
	return valid;
}

146
void Canvas::reload(bool clamp) {
147
	cur_layout->rebuild(clamp);
148
	goto_line->set_page_count(viewer->get_res()->get_page_count());
Philipp Erhardt's avatar
Philipp Erhardt committed
149
	update();
150
151
}

152
void Canvas::setup_keys(QWidget *base) {
Philipp Erhardt's avatar
Philipp Erhardt committed
153
154
155
156
157
	add_action(base, "set_presentation_layout", SLOT(set_presentation_layout()), this);
	add_action(base, "set_grid_layout", SLOT(set_grid_layout()), this);
	add_action(base, "set_presenter_layout", SLOT(set_presenter_layout()), this);
	add_action(base, "toggle_overlay", SLOT(toggle_overlay()), this);
	add_action(base, "focus_goto", SLOT(focus_goto()), this);
158
	add_action(base, "swap_selection_and_panning_buttons", SLOT(swap_selection_and_panning_buttons()), this);
159
160
}

161
Layout *Canvas::get_layout() const {
162
	return cur_layout;
163
164
}

165
void Canvas::update_page_overlay() {
166
	QString overlay_text = CFG::get_instance()->get_value("page_overlay_text").toString()
167
168
169
170
171
172
173
		.arg(cur_layout->get_page() + 1)
		.arg(viewer->get_res()->get_page_count());
	page_overlay->setText(overlay_text);
	page_overlay->adjustSize();
	page_overlay->move(width() - page_overlay->width(), height() - page_overlay->height());
}

174
void Canvas::paintEvent(QPaintEvent * /*event*/) {
175
176
177
#ifdef DEBUG
	cerr << "redraw" << endl;
#endif
178
	QPainter painter(this);
179
	if (viewer->isFullScreen()) {
180
		painter.fillRect(rect(), background_fullscreen);
181
	} else {
182
		painter.fillRect(rect(), background);
183
	}
184
	cur_layout->render(&painter);
185
186
187
}

void Canvas::mousePressEvent(QMouseEvent *event) {
188
189
	if ((click_link_button != Qt::NoButton && event->button() == click_link_button)
			|| (drag_view_button != Qt::NoButton && event->button() == drag_view_button)) {
190
191
		mx = event->x();
		my = event->y();
Philipp Erhardt's avatar
Philipp Erhardt committed
192
193
		mx_down = mx;
		my_down = my;
194
	}
195
196
197
198
199
	if (drag_view_button != Qt::NoButton && event->button() == drag_view_button) {
		if (cursor().shape() != Qt::PointingHandCursor) { // TODO
			setCursor(Qt::ClosedHandCursor);
		}
	}
200
	if (select_text_button != Qt::NoButton && event->button() == select_text_button) {
Philipp Erhardt's avatar
Philipp Erhardt committed
201
202
203
204
205
206
		if (triple_click_possible) {
			cur_layout->select(event->x(), event->y(), Selection::StartLine);
			triple_click_possible = false;
		} else {
			cur_layout->select(event->x(), event->y(), Selection::Start);
		}
207
208
209
210

		if (cursor().shape() != Qt::PointingHandCursor) { // TODO
			setCursor(Qt::IBeamCursor);
		}
Philipp Erhardt's avatar
Philipp Erhardt committed
211
212
213
214
	}
}

void Canvas::mouseReleaseEvent(QMouseEvent *event) {
215
216
	if (event->button() == Qt::LeftButton && event->modifiers() == Qt::ControlModifier) {
		// emit synctex signal
217
218
219
220
		pair<int, QPointF> location = cur_layout->get_location_at(event->x(), event->y());
		// scale from [0,1] to points
		location.second.rx() *= viewer->get_res()->get_page_width(location.first, false);
		location.second.ry() *= viewer->get_res()->get_page_height(location.first, false);
221

222
		emit synchronize_editor(location.first, (int) ROUND(location.second.x()), (int) ROUND(location.second.y()));
223
	} else if (click_link_button != Qt::NoButton && event->button() == click_link_button) {
Philipp Erhardt's avatar
Philipp Erhardt committed
224
		if (mx_down == event->x() && my_down == event->y()) {
Philipp Erhardt's avatar
Philipp Erhardt committed
225
			pair<int, QPointF> location = cur_layout->get_location_at(mx_down, my_down);
Philipp Erhardt's avatar
Philipp Erhardt committed
226
			cur_layout->activate_link(location.first, location.second.x(), location.second.y());
Philipp Erhardt's avatar
Philipp Erhardt committed
227
		}
228
	}
229
230
231
232
233
234
235

	if (drag_view_button == Qt::LeftButton) {
		setCursor(Qt::OpenHandCursor);
	} else {
		setCursor(Qt::IBeamCursor);
	}

236
	if (select_text_button != Qt::NoButton && event->button() == select_text_button) {
Philipp Erhardt's avatar
Philipp Erhardt committed
237
		cur_layout->copy_selection_text();
238
239
240
241
	}
}

void Canvas::mouseMoveEvent(QMouseEvent *event) {
242
	if (drag_view_button != Qt::NoButton && event->buttons() & drag_view_button) {
Philipp Erhardt's avatar
Philipp Erhardt committed
243
		cur_layout->scroll_smooth(event->x() - mx, event->y() - my);
244
245
		mx = event->x();
		my = event->y();
246
247
	}
	if (select_text_button != Qt::NoButton && event->buttons() & select_text_button) {
Philipp Erhardt's avatar
Philipp Erhardt committed
248
		cur_layout->select(event->x(), event->y(), Selection::End);
Philipp Erhardt's avatar
Philipp Erhardt committed
249
250
251
252
253

		// scrolling by dragging the selection
		// TODO only scrolls when the mouse is moved
		int margin = min(10, min(width() / 10, height() / 10));
		if (event->x() < margin) {
Philipp Erhardt's avatar
Philipp Erhardt committed
254
			cur_layout->scroll_smooth(min(margin - event->x(), margin) * 2, 0);
Philipp Erhardt's avatar
Philipp Erhardt committed
255
256
		}
		if (event->x() > width() - margin) {
Philipp Erhardt's avatar
Philipp Erhardt committed
257
			cur_layout->scroll_smooth(max(width() - event->x() - margin, -margin) * 2, 0);
Philipp Erhardt's avatar
Philipp Erhardt committed
258
259
		}
		if (event->y() < margin) {
Philipp Erhardt's avatar
Philipp Erhardt committed
260
			cur_layout->scroll_smooth(0, min(margin - event->y(), margin) * 2);
Philipp Erhardt's avatar
Philipp Erhardt committed
261
262
		}
		if (event->y() > height() - margin) {
Philipp Erhardt's avatar
Philipp Erhardt committed
263
			cur_layout->scroll_smooth(0, max(height() - event->y() - margin, -margin) * 2);
Philipp Erhardt's avatar
Philipp Erhardt committed
264
		}
265
266
267
268
269
	}
}

void Canvas::wheelEvent(QWheelEvent *event) {
	int d = event->delta();
270
271
272
273
274
	switch (QApplication::keyboardModifiers()) {
		// scroll
		case Qt::NoModifier:
			if (event->orientation() == Qt::Vertical) {
				if (cur_layout->supports_smooth_scrolling()) {
Philipp Erhardt's avatar
Philipp Erhardt committed
275
					cur_layout->scroll_smooth(0, d);
276
				} else {
Philipp Erhardt's avatar
Philipp Erhardt committed
277
					cur_layout->scroll_page(-d / mouse_wheel_factor);
278
279
				}
			} else {
Philipp Erhardt's avatar
Philipp Erhardt committed
280
				cur_layout->scroll_smooth(d, 0);
281
			}
282
283
284
285
			break;

		// zoom
		case Qt::ControlModifier:
Philipp Erhardt's avatar
Philipp Erhardt committed
286
			cur_layout->set_zoom(d / mouse_wheel_factor);
287
			break;
288
289
290
	}
}

291
void Canvas::mouseDoubleClickEvent(QMouseEvent * event) {
292
	if (click_link_button != Qt::NoButton && event->button() == drag_view_button) {
293
		cur_layout->goto_page_at(event->x(), event->y());
294
295
	}
	if (select_text_button != Qt::NoButton && event->button() == select_text_button) {
Philipp Erhardt's avatar
Philipp Erhardt committed
296
297
298
299
		// enable triple click, disable after timeout
		triple_click_possible = true;
		QTimer::singleShot(QApplication::doubleClickInterval(), this, SLOT(disable_triple_click()));

Philipp Erhardt's avatar
Philipp Erhardt committed
300
		cur_layout->select(event->x(), event->y(), Selection::StartWord);
301
302
303
	}
}

304
void Canvas::resizeEvent(QResizeEvent *event) {
305
	cur_layout->resize(event->size().width(), event->size().height());
306
	goto_line->move(0, height() - goto_line->height());
307
	page_overlay->move(width() - page_overlay->width(), height() - page_overlay->height());
308
309
310
311
}

// primitive actions
void Canvas::set_presentation_layout() {
312
313
	presentation_layout->activate(cur_layout);
	cur_layout = presentation_layout;
314
	update();
315
	viewer->get_beamer()->hide();
316
	viewer->show_progress(false);
317
	viewer->activateWindow();
318
319
320
}

void Canvas::set_grid_layout() {
321
322
323
	grid_layout->activate(cur_layout);
	grid_layout->rebuild();
	cur_layout = grid_layout;
324
	update();
325
	viewer->get_beamer()->hide();
326
	viewer->show_progress(false);
327
	viewer->activateWindow();
328
329
}

330
331
332
333
334
335
void Canvas::set_presenter_layout() {
	presenter_layout->activate(cur_layout);
	presenter_layout->rebuild();
	cur_layout = presenter_layout;
	update();

336
337
338
339
340
341
342
	// TODO move beamer to second screen
//	QDesktopWidget *desktop = QApplication::desktop();
//	if (desktop->screenCount() > 1) {
//		int primary_screen = desktop->primaryScreen();
//		int cur_screen = desktop->screenNumber(viewer);
//		cout << "primary: " << primary_screen << ", current: " << cur_screen << endl;
//	}
Philipp Erhardt's avatar
Philipp Erhardt committed
343
	viewer->get_beamer()->get_layout()->scroll_page(cur_layout->get_page(), false);
344
	viewer->get_beamer()->show();
345
	viewer->show_progress(true);
346
}
347
348

void Canvas::toggle_overlay() {
349
	page_overlay->setVisible(!page_overlay->isVisible());
350
351
}

352
void Canvas::focus_goto() {
353
354
	goto_line->activateWindow();
	goto_line->show();
355
	goto_line->setFocus();
356
	goto_line->setText(QString::number(cur_layout->get_page() + 1));
357
	goto_line->selectAll();
358
	goto_line->move(0, height() - goto_line->height()); // TODO this is only necessary because goto_line->height() is wrong in the beginning
359
360
}

Philipp Erhardt's avatar
Philipp Erhardt committed
361
362
363
364
void Canvas::disable_triple_click() {
	triple_click_possible = false;
}

365
366
367
368
369
370
371
372
373
374
375
376
void Canvas::swap_selection_and_panning_buttons() {
	Qt::MouseButton tmp = drag_view_button;
	drag_view_button = select_text_button;
	select_text_button = tmp;

	if (drag_view_button == Qt::LeftButton) {
		setCursor(Qt::OpenHandCursor);
	} else {
		setCursor(Qt::IBeamCursor);
	}
}

377

378
void Canvas::set_search_visible(bool visible) {
379
	cur_layout->set_search_visible(visible);
380
381
382
	update();
}

Philipp Erhardt's avatar
Philipp Erhardt committed
383
void Canvas::page_rendered(int page) {
384
	if (cur_layout->page_visible(page)) {
Philipp Erhardt's avatar
Philipp Erhardt committed
385
386
		update();
	}
387
388
}

389
390
391
void Canvas::goto_page() {
	int page = goto_line->text().toInt() - 1;
	goto_line->hide();
392
393
	setFocus(Qt::OtherFocusReason);
	cur_layout->scroll_page_top_jump(page, false);
394
395
}