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