1 #include <QAction>
2 #include <QStringListIterator>
3 #include <QKeySequence>
4 #include <QString>
5 #include <QPainter>
6 #include <QApplication>
7 #include <QDesktopWidget>
8 #include <QTimer>
9 #include <QLabel>
10 #include <iostream>
11 #include "canvas.h"
12 #include "viewer.h"
13 #include "layout/layout.h"
14 #include "layout/singlelayout.h"
15 #include "layout/gridlayout.h"
16 #include "layout/presenterlayout.h"
17 #include "resourcemanager.h"
18 #include "search.h"
19 #include "gotoline.h"
20 #include "config.h"
21 #include "beamerwindow.h"
22 #include "util.h"
23 
24 using namespace std;
25 
26 
Canvas(Viewer * v,QWidget * parent)27 Canvas::Canvas(Viewer *v, QWidget *parent) :
28 		QWidget(parent),
29 		viewer(v),
30 		triple_click_possible(false),
31 		last_cursor(Qt::BlankCursor),
32 		valid(true) {
33 	setFocusPolicy(Qt::StrongFocus);
34 
35 	// load config options
36 	CFG *config = CFG::get_instance();
37 
38 	{
39 		bool ok;
40 		unsigned int color = config->get_value("Settings/background_color").toString().toUInt(&ok, 16);
41 		if (ok) {
42 			background.setRgba(color);
43 		} else {
44 			cerr << "failed to parse background_color" << endl;
45 		}
46 	}
47 	{
48 		bool ok;
49 		unsigned int color = config->get_value("Settings/background_color_fullscreen").toString().toUInt(&ok, 16);
50 		if (ok) {
51 			background_fullscreen.setRgba(color);
52 		} else {
53 			cerr << "failed to parse background_color_fullscreen" << endl;
54 		}
55 	}
56 	mouse_wheel_factor = config->get_value("Settings/mouse_wheel_factor").toInt();
57 
58 	switch (config->get_value("Settings/click_link_button").toInt()) {
59 		case 1: click_link_button = Qt::LeftButton; break;
60 		case 2: click_link_button = Qt::RightButton; break;
61 		case 3: click_link_button = Qt::MidButton; break;
62 		case 4: click_link_button = Qt::XButton1; break;
63 		case 5: click_link_button = Qt::XButton2; break;
64 		default: click_link_button = Qt::NoButton;
65 	}
66 
67 	switch (config->get_value("Settings/drag_view_button").toInt()) {
68 		case 1: drag_view_button = Qt::LeftButton; break;
69 		case 2: drag_view_button = Qt::RightButton; break;
70 		case 3: drag_view_button = Qt::MidButton; break;
71 		case 4: drag_view_button = Qt::XButton1; break;
72 		case 5: drag_view_button = Qt::XButton2; break;
73 		default: drag_view_button = Qt::NoButton;
74 	}
75 
76 	switch (config->get_value("Settings/select_text_button").toInt()) {
77 		case 1: select_text_button = Qt::LeftButton; break;
78 		case 2: select_text_button = Qt::RightButton; break;
79 		case 3: select_text_button = Qt::MidButton; break;
80 		case 4: select_text_button = Qt::XButton1; break;
81 		case 5: select_text_button = Qt::XButton2; break;
82 		default: select_text_button = Qt::NoButton;
83 	}
84 
85 	single_layout = new SingleLayout(viewer, 0);
86 	grid_layout = new GridLayout(viewer, 0);
87 	presenter_layout = new PresenterLayout(viewer, 1);
88 
89 	QString default_layout = config->get_value("Settings/default_layout").toString();
90 	if (default_layout == QString::fromUtf8("grid")) {
91 		cur_layout = grid_layout;
92 	} else if (default_layout == QString::fromUtf8("presenter")) {
93 		cur_layout = presenter_layout;
94 	} else { // "single" and everything else
95 		cur_layout = single_layout;
96 	}
97 
98 	setup_keys(this);
99 
100 	if (drag_view_button == Qt::LeftButton) {
101 		setCursor(Qt::OpenHandCursor);
102 	} else {
103 		setCursor(Qt::IBeamCursor);
104 	}
105 	last_cursor = Qt::BlankCursor;
106 
107 	// prints the string representation of a key
108 //	cerr << QKeySequence(Qt::Key_Equal).toString().toUtf8().constData() << endl;
109 
110 	goto_line = new GotoLine(viewer->get_res()->get_page_count(), this);
111 	goto_line->move(0, height() - goto_line->height()); // TODO goto_line->height() reports the wrong size
112 	goto_line->hide();
113 	connect(goto_line, SIGNAL(returnPressed()), this, SLOT(goto_page()), Qt::UniqueConnection);
114 
115 	hide_mouse_timeout = config->get_value("Settings/hide_mouse_timeout").toInt();
116 	setMouseTracking(true);
117 	hide_mouse_timer.setSingleShot(true);
118 	connect(&hide_mouse_timer, SIGNAL(timeout()), this, SLOT(hide_mouse_pointer()), Qt::UniqueConnection);
119 
120 	page_overlay = new QLabel(this);
121 	page_overlay->setMargin(1);
122 	page_overlay->setAutoFillBackground(true);
123 	page_overlay->show();
124 
125 	// setup beamer
126 	BeamerWindow *beamer = viewer->get_beamer();
127 	setup_keys(beamer);
128 	if (cur_layout == presenter_layout) {
129 		beamer->show();
130 		viewer->show_progress(true);
131 	} else {
132 		beamer->hide();
133 		viewer->show_progress(false);
134 	}
135 }
136 
~Canvas()137 Canvas::~Canvas() {
138 	delete page_overlay;
139 	delete goto_line;
140 	delete single_layout;
141 	delete grid_layout;
142 	delete presenter_layout;
143 }
144 
is_valid() const145 bool Canvas::is_valid() const {
146 	return valid;
147 }
148 
reload(bool clamp)149 void Canvas::reload(bool clamp) {
150 	cur_layout->rebuild(clamp);
151 	goto_line->set_page_count(viewer->get_res()->get_page_count());
152 	update();
153 }
154 
setup_keys(QWidget * base)155 void Canvas::setup_keys(QWidget *base) {
156 	add_action(base, "Keys/goto_page", SLOT(focus_goto()), this);
157 
158 	add_action(base, "Keys/set_single_layout", SLOT(set_single_layout()), this);
159 	add_action(base, "Keys/set_grid_layout", SLOT(set_grid_layout()), this);
160 	add_action(base, "Keys/set_presenter_layout", SLOT(set_presenter_layout()), this);
161 
162 	add_action(base, "Keys/toggle_overlay", SLOT(toggle_overlay()), this);
163 	add_action(base, "Keys/swap_selection_and_panning_buttons", SLOT(swap_selection_and_panning_buttons()), this);
164 }
165 
get_layout() const166 Layout *Canvas::get_layout() const {
167 	return cur_layout;
168 }
169 
update_page_overlay()170 void Canvas::update_page_overlay() {
171 	QString frozen_text;
172 	if (viewer->get_beamer()->is_frozen()) {
173 		frozen_text = QString::fromUtf8("Frozen: %1, ").arg(viewer->get_beamer()->get_layout()->get_page() + 1);
174 	}
175 	QString overlay_text = CFG::get_instance()->get_value("Settings/page_overlay_text").toString()
176 		.arg(cur_layout->get_page() + 1)
177 		.arg(viewer->get_res()->get_page_count());
178 	page_overlay->setText(frozen_text + overlay_text);
179 	page_overlay->adjustSize();
180 	page_overlay->move(width() - page_overlay->width(), height() - page_overlay->height());
181 }
182 
paintEvent(QPaintEvent *)183 void Canvas::paintEvent(QPaintEvent * /*event*/) {
184 #ifdef DEBUG
185 	cerr << "redraw" << endl;
186 #endif
187 	QPainter painter(this);
188 	if (viewer->isFullScreen()) {
189 		painter.fillRect(rect(), background_fullscreen);
190 	} else {
191 		painter.fillRect(rect(), background);
192 	}
193 	cur_layout->render(&painter);
194 }
195 
mousePressEvent(QMouseEvent * event)196 void Canvas::mousePressEvent(QMouseEvent *event) {
197 	if ((click_link_button != Qt::NoButton && event->button() == click_link_button)
198 			|| (drag_view_button != Qt::NoButton && event->button() == drag_view_button)) {
199 		mx = event->x();
200 		my = event->y();
201 		mx_down = mx;
202 		my_down = my;
203 	}
204 	if (drag_view_button != Qt::NoButton && event->button() == drag_view_button) {
205 		if (cursor().shape() != Qt::PointingHandCursor) { // TODO
206 			setCursor(Qt::ClosedHandCursor);
207 			last_cursor = Qt::BlankCursor;
208 		}
209 	}
210 	if (select_text_button != Qt::NoButton && event->button() == select_text_button) {
211 		if (triple_click_possible) {
212 			cur_layout->select(event->x(), event->y(), Selection::StartLine);
213 			triple_click_possible = false;
214 		} else {
215 			cur_layout->select(event->x(), event->y(), Selection::Start);
216 		}
217 
218 		if (cursor().shape() != Qt::PointingHandCursor) { // TODO
219 			setCursor(Qt::IBeamCursor);
220 			last_cursor = Qt::BlankCursor;
221 		}
222 	}
223 }
224 
mouseReleaseEvent(QMouseEvent * event)225 void Canvas::mouseReleaseEvent(QMouseEvent *event) {
226 	if (event->button() == Qt::LeftButton && event->modifiers() == Qt::ControlModifier) {
227 		// emit synctex signal
228 		pair<int, QPointF> location = cur_layout->get_location_at(event->x(), event->y());
229 		// scale from [0,1] to points
230 		location.second.rx() *= viewer->get_res()->get_page_width(location.first, false);
231 		location.second.ry() *= viewer->get_res()->get_page_height(location.first, false);
232 
233 		emit synchronize_editor(location.first, (int) ROUND(location.second.x()), (int) ROUND(location.second.y()));
234 	} else if (click_link_button != Qt::NoButton && event->button() == click_link_button) {
235 		if (mx_down == event->x() && my_down == event->y()) {
236 			pair<int, QPointF> location = cur_layout->get_location_at(mx_down, my_down);
237 			cur_layout->activate_link(location.first, location.second.x(), location.second.y());
238 		}
239 	}
240 
241 	if (drag_view_button == Qt::LeftButton) {
242 		setCursor(Qt::OpenHandCursor);
243 	} else {
244 		setCursor(Qt::IBeamCursor);
245 	}
246 	last_cursor = Qt::BlankCursor;
247 
248 	if (select_text_button != Qt::NoButton && event->button() == select_text_button) {
249 		cur_layout->copy_selection_text();
250 	}
251 
252 	// auto-hide mouse pointer
253 	if (event->buttons() == 0) {
254 		if (hide_mouse_timeout > 0) {
255 			hide_mouse_timer.start(hide_mouse_timeout);
256 		}
257 	} else {
258 		hide_mouse_timer.stop();
259 	}
260 }
261 
mouseMoveEvent(QMouseEvent * event)262 void Canvas::mouseMoveEvent(QMouseEvent *event) {
263 	if (drag_view_button != Qt::NoButton && event->buttons() & drag_view_button) {
264 		cur_layout->scroll_smooth(event->x() - mx, event->y() - my);
265 		mx = event->x();
266 		my = event->y();
267 
268         // wrap mouse around when dragging at the border
269         if (mx <= 0) {
270             mx = width() - 2;
271             QCursor c = cursor();
272             c.setPos(mapToGlobal(QPoint(mx, my)));
273             setCursor(c);
274         }
275         if (mx >= width() - 1) {
276             mx = 1;
277             QCursor c = cursor();
278             c.setPos(mapToGlobal(QPoint(mx, my)));
279             setCursor(c);
280         }
281 
282         if (my <= 0) {
283             my = height() - 2;
284             QCursor c = cursor();
285             c.setPos(mapToGlobal(QPoint(mx, my)));
286             setCursor(c);
287         }
288         if (my >= height() - 1) {
289             my = 1;
290             QCursor c = cursor();
291             c.setPos(mapToGlobal(QPoint(mx, my)));
292             setCursor(c);
293         }
294 	}
295 	if (select_text_button != Qt::NoButton && event->buttons() & select_text_button) {
296 		cur_layout->select(event->x(), event->y(), Selection::End);
297 
298 		// scrolling by dragging the selection
299 		// TODO only scrolls when the mouse is moved
300 		int margin = min(10, min(width() / 10, height() / 10));
301 		if (event->x() < margin) {
302 			cur_layout->scroll_smooth(min(margin - event->x(), margin) * 2, 0);
303 		}
304 		if (event->x() > width() - margin) {
305 			cur_layout->scroll_smooth(max(width() - event->x() - margin, -margin) * 2, 0);
306 		}
307 		if (event->y() < margin) {
308 			cur_layout->scroll_smooth(0, min(margin - event->y(), margin) * 2);
309 		}
310 		if (event->y() > height() - margin) {
311 			cur_layout->scroll_smooth(0, max(height() - event->y() - margin, -margin) * 2);
312 		}
313 	}
314 
315 	// auto-show mouse pointer
316 	if (last_cursor != Qt::BlankCursor) {
317 		setCursor(last_cursor);
318 		last_cursor = Qt::BlankCursor;
319 	}
320 	// auto-hide mouse pointer
321 	if (event->buttons() == 0) {
322 		if (hide_mouse_timeout > 0) {
323 			hide_mouse_timer.start(hide_mouse_timeout);
324 		}
325 	} else {
326 		hide_mouse_timer.stop();
327 	}
328 }
329 
wheelEvent(QWheelEvent * event)330 void Canvas::wheelEvent(QWheelEvent *event) {
331 	int d = event->delta();
332 	switch (QApplication::keyboardModifiers()) {
333 		// scroll
334 		case Qt::NoModifier:
335 			if (event->orientation() == Qt::Vertical) {
336 				if (cur_layout->supports_smooth_scrolling()) {
337 					cur_layout->scroll_smooth(0, d);
338 				} else {
339 					cur_layout->scroll_page(-d / mouse_wheel_factor);
340 				}
341 			} else {
342 				cur_layout->scroll_smooth(d, 0);
343 			}
344 			break;
345 
346 		// zoom
347 		case Qt::ControlModifier:
348 			cur_layout->set_zoom(d / mouse_wheel_factor);
349 			break;
350 	}
351 }
352 
mouseDoubleClickEvent(QMouseEvent * event)353 void Canvas::mouseDoubleClickEvent(QMouseEvent * event) {
354 	if (drag_view_button != Qt::NoButton && event->button() == drag_view_button) {
355 		cur_layout->goto_page_at(event->x(), event->y());
356 	}
357 	if (select_text_button != Qt::NoButton && event->button() == select_text_button) {
358 		// enable triple click, disable after timeout
359 		triple_click_possible = true;
360 		QTimer::singleShot(QApplication::doubleClickInterval(), this, SLOT(disable_triple_click()));
361 
362 		cur_layout->select(event->x(), event->y(), Selection::StartWord);
363 	}
364 }
365 
resizeEvent(QResizeEvent * event)366 void Canvas::resizeEvent(QResizeEvent *event) {
367 	cur_layout->resize(event->size().width(), event->size().height());
368 	goto_line->move(0, height() - goto_line->height());
369 	page_overlay->move(width() - page_overlay->width(), height() - page_overlay->height());
370 }
371 
372 // primitive actions
set_single_layout()373 void Canvas::set_single_layout() {
374 	single_layout->activate(cur_layout);
375 	cur_layout = single_layout;
376 	update();
377 	viewer->get_beamer()->hide();
378 	viewer->show_progress(false);
379 	viewer->activateWindow();
380 }
381 
set_grid_layout()382 void Canvas::set_grid_layout() {
383 	grid_layout->activate(cur_layout);
384 	grid_layout->rebuild();
385 	cur_layout = grid_layout;
386 	update();
387 	viewer->get_beamer()->hide();
388 	viewer->show_progress(false);
389 	viewer->activateWindow();
390 }
391 
set_presenter_layout()392 void Canvas::set_presenter_layout() {
393 	presenter_layout->activate(cur_layout);
394 	presenter_layout->rebuild();
395 	cur_layout = presenter_layout;
396 	update();
397 
398 	// TODO move beamer to second screen
399 //	QDesktopWidget *desktop = QApplication::desktop();
400 //	if (desktop->screenCount() > 1) {
401 //		int primary_screen = desktop->primaryScreen();
402 //		int cur_screen = desktop->screenNumber(viewer);
403 //		cout << "primary: " << primary_screen << ", current: " << cur_screen << endl;
404 //	}
405 	if (!viewer->get_beamer()->is_frozen()) {
406 		viewer->get_beamer()->get_layout()->scroll_page(cur_layout->get_page(), false);
407 	}
408 	viewer->get_beamer()->show();
409 	viewer->show_progress(true);
410 }
411 
toggle_overlay()412 void Canvas::toggle_overlay() {
413 	page_overlay->setVisible(!page_overlay->isVisible());
414 }
415 
focus_goto()416 void Canvas::focus_goto() {
417 	goto_line->activateWindow();
418 	goto_line->show();
419 	goto_line->setFocus();
420 	goto_line->setText(QString::number(cur_layout->get_page() + 1));
421 	goto_line->selectAll();
422 	goto_line->move(0, height() - goto_line->height()); // TODO this is only necessary because goto_line->height() is wrong in the beginning
423 }
424 
disable_triple_click()425 void Canvas::disable_triple_click() {
426 	triple_click_possible = false;
427 }
428 
hide_mouse_pointer()429 void Canvas::hide_mouse_pointer() {
430 	last_cursor = cursor().shape();
431 	setCursor(Qt::BlankCursor);
432 }
433 
swap_selection_and_panning_buttons()434 void Canvas::swap_selection_and_panning_buttons() {
435 	Qt::MouseButton tmp = drag_view_button;
436 	drag_view_button = select_text_button;
437 	select_text_button = tmp;
438 
439 	if (drag_view_button == Qt::LeftButton) {
440 		setCursor(Qt::OpenHandCursor);
441 	} else {
442 		setCursor(Qt::IBeamCursor);
443 	}
444 	last_cursor = Qt::BlankCursor;
445 }
446 
447 
set_search_visible(bool visible)448 void Canvas::set_search_visible(bool visible) {
449 	cur_layout->set_search_visible(visible);
450 	update();
451 }
452 
page_rendered(int page)453 void Canvas::page_rendered(int page) {
454 	if (cur_layout->page_visible(page)) {
455 		update();
456 	}
457 }
458 
goto_page()459 void Canvas::goto_page() {
460 	int page = goto_line->text().toInt() - 1;
461 	goto_line->hide();
462 	setFocus(Qt::OtherFocusReason);
463 	cur_layout->scroll_page_top_jump(page, false);
464 }
465 
466