Commit f22115b1 authored by Philipp Erhardt's avatar Philipp Erhardt
Browse files

Implement text selection

Single, double and triple right click start the selection. The selected
text is copied to the X11 selection buffer.

Refactored mouse click handling for this feature.
parent 5d8cc487
......@@ -13,11 +13,11 @@ QMAKE_CXXFLAGS_DEBUG += -DDEBUG
# Input
HEADERS += src/layout/layout.h src/layout/presentationlayout.h src/layout/gridlayout.h src/layout/presenterlayout.h \
src/viewer.h src/canvas.h src/resourcemanager.h src/grid.h src/search.h src/gotoline.h src/config.h src/download.h src/util.h src/kpage.h src/worker.h src/beamerwindow.h src/toc.h src/splitter.h
src/viewer.h src/canvas.h src/resourcemanager.h src/grid.h src/search.h src/gotoline.h src/config.h src/download.h src/util.h src/kpage.h src/worker.h src/beamerwindow.h src/toc.h src/splitter.h src/selection.h
SOURCES += src/main.cpp \
src/layout/layout.cpp src/layout/presentationlayout.cpp src/layout/gridlayout.cpp src/layout/presenterlayout.cpp \
src/viewer.cpp src/canvas.cpp src/resourcemanager.cpp src/grid.cpp src/search.cpp src/gotoline.cpp src/config.cpp src/download.cpp src/util.cpp src/kpage.cpp src/worker.cpp src/beamerwindow.cpp src/toc.cpp src/splitter.cpp
src/viewer.cpp src/canvas.cpp src/resourcemanager.cpp src/grid.cpp src/search.cpp src/gotoline.cpp src/config.cpp src/download.cpp src/util.cpp src/kpage.cpp src/worker.cpp src/beamerwindow.cpp src/toc.cpp src/splitter.cpp src/selection.cpp
unix:LIBS += -lpoppler-qt4
documentation.target = doc/katarakt.1
......
......@@ -68,11 +68,13 @@ void BeamerWindow::mouseReleaseEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton) {
if (mx_down == event->x() && my_down == event->y()) {
int page = layout->get_page();
if (layout->click_mouse(mx_down, my_down)) {
pair<int, QPointF> location = layout->get_location_at(mx_down, my_down);
if (layout->activate_link(location.first, location.second.x(), location.second.y())) {
if (viewer->get_canvas()->get_layout()->scroll_page(layout->get_page(), false)) {
viewer->get_canvas()->update();
}
viewer->get_res()->store_jump(page); // store old position if a clicked link moved the view
update();
}
}
}
......
......@@ -5,6 +5,7 @@
#include <QPainter>
#include <QApplication>
#include <QDesktopWidget>
#include <QTimer>
#include <iostream>
#include "canvas.h"
#include "viewer.h"
......@@ -25,6 +26,7 @@ using namespace std;
Canvas::Canvas(Viewer *v, QWidget *parent) :
QWidget(parent),
viewer(v),
triple_click_possible(false),
draw_overlay(true),
valid(true) {
setFocusPolicy(Qt::StrongFocus);
......@@ -146,11 +148,20 @@ void Canvas::paintEvent(QPaintEvent * /*event*/) {
}
void Canvas::mousePressEvent(QMouseEvent *event) {
// TODO make mouse buttons configurable
if (event->button() == Qt::LeftButton) {
mx = event->x();
my = event->y();
mx_down = mx;
my_down = my;
} else if (event->button() == Qt::RightButton) {
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);
}
update();
}
}
......@@ -158,11 +169,14 @@ void Canvas::mouseReleaseEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton) {
if (mx_down == event->x() && my_down == event->y()) {
int page = cur_layout->get_page();
if (cur_layout->click_mouse(mx_down, my_down)) {
pair<int, QPointF> location = cur_layout->get_location_at(mx_down, my_down);
if (cur_layout->activate_link(location.first, location.second.x(), location.second.y())) {
viewer->get_res()->store_jump(page); // store old position if a clicked link moved the view
update();
}
}
} else if (event->button() == Qt::RightButton) {
cur_layout->copy_selection_text();
}
}
......@@ -173,6 +187,34 @@ void Canvas::mouseMoveEvent(QMouseEvent *event) {
}
mx = event->x();
my = event->y();
} else if (event->buttons() & Qt::RightButton) {
if (cur_layout->select(event->x(), event->y(), Selection::End)) {
update();
}
// 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) {
if (cur_layout->scroll_smooth(min(margin - event->x(), margin) * 2, 0)) {
update();
}
}
if (event->x() > width() - margin) {
if (cur_layout->scroll_smooth(max(width() - event->x() - margin, -margin) * 2, 0)) {
update();
}
}
if (event->y() < margin) {
if (cur_layout->scroll_smooth(0, min(margin - event->y(), margin) * 2)) {
update();
}
}
if (event->y() > height() - margin) {
if (cur_layout->scroll_smooth(0, max(height() - event->y() - margin, -margin) * 2)) {
update();
}
}
}
}
......@@ -211,6 +253,14 @@ void Canvas::mouseDoubleClickEvent(QMouseEvent * event) {
if (event->button() == Qt::LeftButton) {
cur_layout->goto_page_at(event->x(), event->y());
update();
} else if (event->button() == Qt::RightButton) {
// enable triple click, disable after timeout
triple_click_possible = true;
QTimer::singleShot(QApplication::doubleClickInterval(), this, SLOT(disable_triple_click()));
if (cur_layout->select(event->x(), event->y(), Selection::StartWord)) {
update();
}
}
}
......@@ -269,6 +319,10 @@ void Canvas::focus_goto() {
goto_line->move(0, height() - goto_line->height()); // TODO this is only necessary because goto_line->height() is wrong in the beginning
}
void Canvas::disable_triple_click() {
triple_click_possible = false;
}
void Canvas::set_search_visible(bool visible) {
cur_layout->set_search_visible(visible);
......
......@@ -7,6 +7,7 @@
#include <QWheelEvent>
#include <QResizeEvent>
#include <QList>
#include <QTimer>
#include <sys/socket.h>
......@@ -54,6 +55,8 @@ private slots:
void toggle_overlay();
void focus_goto();
void disable_triple_click();
private:
void setup_keys(QWidget *base);
......@@ -67,6 +70,8 @@ private:
int mx, my;
int mx_down, my_down;
bool triple_click_possible;
QTimer scroll_timer;
bool draw_overlay;
......
#include "kpage.h"
#include <QList>
#include "selection.h"
using namespace std;
KPage::KPage() :
links(NULL),
inverted_colors(false) {
inverted_colors(false),
text(NULL) {
for (int i = 0; i < 3; i++) {
status[i] = 0;
rotation[i] = 0;
......@@ -18,6 +21,12 @@ KPage::~KPage() {
}
}
delete links;
if (text != NULL) {
Q_FOREACH(SelectionLine *line, *text) {
delete line;
}
}
delete text;
}
const QImage *KPage::get_image(int index) const {
......@@ -42,6 +51,10 @@ char KPage::get_rotation(int index) const {
return rotation[index];
}
const QList<SelectionLine *> *KPage::get_text() const {
return text;
}
//QString KPage::get_label() const {
// return label;
//}
......
......@@ -6,6 +6,9 @@
#include <poppler/qt4/poppler-qt4.h>
class SelectionLine;
class KPage {
private:
KPage();
......@@ -15,6 +18,7 @@ public:
const QImage *get_image(int index = 0) const;
int get_width(int index = 0) const;
char get_rotation(int index = 0) const;
const QList<SelectionLine *> *get_text() const;
// QString get_label() const;
private:
......@@ -28,6 +32,7 @@ private:
int status[3];
char rotation[3];
bool inverted_colors; // img[]s and thumb must be consistent
QList<SelectionLine *> *text;
friend class Worker;
friend class ResourceManager;
......
#include <iostream>
#include <QImage>
#include <QApplication>
#include "gridlayout.h"
#include "../util.h"
#include "layout.h"
......@@ -349,28 +350,14 @@ void GridLayout::render(QPainter *painter) {
}
// draw search rects
QPoint offset(wpos + center_x, hpos + center_y);
if (search_visible) {
painter->setPen(QColor(0, 0, 0));
painter->setBrush(QColor(255, 0, 0, 64));
float w = res->get_page_width(last_page);
float h = res->get_page_height(last_page);
const map<int,QList<QRectF> *> *hits = viewer->get_search_bar()->get_hits();
map<int,QList<QRectF> *>::const_iterator it = hits->find(last_page);
if (it != hits->end()) {
for (QList<QRectF>::iterator i2 = it->second->begin(); i2 != it->second->end(); ++i2) {
if (i2 == hit_it) {
painter->setBrush(QColor(0, 255, 0, 64));
}
QRectF rot = rotate_rect(*i2, w, h, res->get_rotation());
painter->drawRect(transform_rect(rot, size, wpos + center_x, hpos + center_y));
if (i2 == hit_it) {
painter->setBrush(QColor(255, 0, 0, 64));
}
}
}
render_search_rects(painter, last_page, offset, size);
}
// draw text selection
render_selection(painter, last_page, offset, size);
wpos += grid_width + useless_gap;
cur_col++;
}
......@@ -513,7 +500,7 @@ QPoint GridLayout::get_target_page_distance(int target_page) const {
return QPoint(wpos + center_x, hpos + center_y);
}
pair<int,QPointF> GridLayout::get_page_at(int mx, int my) {
pair<int, QPointF> GridLayout::get_location_at(int mx, int my) {
// find vertical page
int cur_page = page;
int grid_height;
......@@ -567,13 +554,7 @@ pair<int,QPointF> GridLayout::get_page_at(int mx, int my) {
int page = cur_page + cur_col - grid->get_offset();
return make_pair(page, QPointF(x,y));
}
bool GridLayout::click_mouse(int mx, int my) {
pair<int,QPointF> page = get_page_at(mx, my);
return activate_link(page.first, page.second.x(), page.second.y());
return make_pair(page, QPointF(x, y));
}
bool GridLayout::goto_link_destination(const Poppler::LinkDestination &link) {
......@@ -595,7 +576,7 @@ bool GridLayout::goto_link_destination(const Poppler::LinkDestination &link) {
}
bool GridLayout::goto_page_at(int mx, int my) {
pair<int,QPointF> page = get_page_at(mx, my);
pair<int,QPointF> page = get_location_at(mx, my);
set_columns(1, false);
scroll_page(page.first, false);
......
......@@ -29,7 +29,7 @@ public:
bool advance_hit(bool forward = true);
bool advance_invisible_hit(bool forward = true);
bool click_mouse(int mx, int my);
std::pair<int, QPointF> get_location_at(int pixel_x, int pixel_y);
bool goto_link_destination(const Poppler::LinkDestination &link);
bool goto_page_at(int mx, int my);
......@@ -43,7 +43,6 @@ private:
bool view_point(const QPoint &p);
QRect get_target_rect(int target_page, QRectF target_rect) const;
QPoint get_target_page_distance(int target_page) const;
std::pair<int,QPointF> get_page_at(int x, int y);
Grid *grid;
......
......@@ -2,6 +2,8 @@
#include <QImage>
#include <QDesktopServices>
#include <QUrl>
#include <QClipboard>
#include <QApplication>
#include "layout.h"
#include "../viewer.h"
#include "../resourcemanager.h"
......@@ -9,6 +11,7 @@
#include "../search.h"
#include "../config.h"
#include "../beamerwindow.h"
#include "../util.h"
using namespace std;
......@@ -46,6 +49,8 @@ void Layout::activate(const Layout *old_layout) {
search_visible = old_layout->search_visible;
hit_page = old_layout->hit_page;
hit_it = old_layout->hit_it;
selection = old_layout->selection;
}
void Layout::rebuild(bool clamp) {
......@@ -184,10 +189,6 @@ bool Layout::advance_hit(bool forward) {
return true;
}
bool Layout::click_mouse(int /*mx*/, int /*my*/) {
return false;
}
bool Layout::goto_link_destination(const Poppler::LinkDestination &link) {
return scroll_page(link.pageNumber() - 1, false);
}
......@@ -200,6 +201,93 @@ bool Layout::get_search_visible() const {
return search_visible;
}
bool Layout::select(int px, int py, enum Selection::Mode mode) {
pair<int, QPointF> loc = get_location_at(px, py);
loc.second.rx() *= res->get_page_width(loc.first, false);
loc.second.ry() *= res->get_page_height(loc.first, false);
const QList<SelectionLine *> *text = res->get_text(loc.first);
selection.set_cursor(text, loc, mode);
return true; // TODO visible? change?
}
void Layout::copy_selection_text() {
QString text;
if (selection.is_active()) {
Cursor from = selection.get_cursor(true);
Cursor to = selection.get_cursor(false);
for (int i = from.page; i <= to.page; i++) {
text += selection.get_selection_text(i, res->get_text(i));
}
}
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(text, QClipboard::Selection);
// TODO copy to clipboard on Ctrl+C
}
void Layout::clear_selection() {
selection.deactivate();
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText("", QClipboard::Selection);
}
void Layout::render_search_rects(QPainter *painter, int cur_page, QPoint offset, float size) {
painter->setPen(QColor(0, 0, 0));
painter->setBrush(QColor(255, 0, 0, 64));
float w = res->get_page_width(cur_page);
float h = res->get_page_height(cur_page);
const map<int,QList<QRectF> *> *hits = viewer->get_search_bar()->get_hits();
map<int,QList<QRectF> *>::const_iterator it = hits->find(cur_page);
if (it != hits->end()) {
for (QList<QRectF>::iterator i2 = it->second->begin(); i2 != it->second->end(); ++i2) {
if (i2 == hit_it) {
painter->setBrush(QColor(0, 255, 0, 64));
}
QRectF rot = rotate_rect(*i2, w, h, res->get_rotation());
painter->drawRect(transform_rect_expand(rot, size, offset.x(), offset.y()));
if (i2 == hit_it) {
painter->setBrush(QColor(255, 0, 0, 64));
}
}
}
}
void Layout::render_selection(QPainter *painter, int cur_page, QPoint offset, float size) {
float w = res->get_page_width(cur_page);
float h = res->get_page_height(cur_page);
painter->setPen(QPen(Qt::NoPen));
QColor color = QApplication::palette().highlight().color();
color.setAlpha(96);
painter->setBrush(color);
const QList<SelectionLine *> *text = res->get_text(cur_page);
if (text != NULL && text->size() != 0 && selection.is_active()) {
Cursor from = selection.get_cursor(true);
Cursor to = selection.get_cursor(false);
if (from.page <= cur_page && to.page >= cur_page) {
if (from.page < cur_page) {
from.line = 0;
}
if (to.page > cur_page) {
to.line = text->size() - 1;
}
for (int i = from.line; i <= to.line; i++) {
QRectF rect = text->at(i)->get_bbox();
if (from.page == cur_page && from.line == i) {
rect.setLeft(from.x);
}
if (to.page == cur_page && to.line == i) {
rect.setRight(to.x);
}
QRectF bb = rotate_rect(rect, w, h, res->get_rotation());;
painter->drawRect(transform_rect(bb, size, offset.x(), offset.y()));
}
}
}
}
bool Layout::activate_link(int page, float x, float y) {
// find matching box
const QList<Poppler::Link *> *links = res->get_links(page);
......
......@@ -5,6 +5,7 @@
#include <QList>
#include <poppler/qt4/poppler-qt4.h>
#include <map>
#include "../selection.h"
class Viewer;
......@@ -38,16 +39,23 @@ public:
virtual bool advance_hit(bool forward = true);
virtual bool advance_invisible_hit(bool forward = true) = 0;
virtual bool click_mouse(int mx, int my);
virtual std::pair<int, QPointF> get_location_at(int px, int py) = 0;
virtual bool goto_link_destination(const Poppler::LinkDestination &link);
virtual bool goto_page_at(int mx, int my);
virtual bool activate_link(int page, float x, float y);
virtual bool get_search_visible() const;
virtual bool page_visible(int p) const = 0;
bool select(int px, int py, enum Selection::Mode mode);
void copy_selection_text();
void clear_selection();
protected:
void render_search_rects(QPainter *painter, int cur_page, QPoint offset, float size);
void render_selection(QPainter *painter, int cur_page, QPoint offset, float size);
virtual bool view_hit() = 0;
virtual bool activate_link(int page, float x, float y);
Viewer *viewer;
ResourceManager *res;
......@@ -67,6 +75,8 @@ protected:
float zoom_factor;
int prefetch_count;
float search_padding;
MouseSelection selection;
};
......
#include <iostream>
#include <QImage>
#include <QApplication>
#include <set>
#include "presentationlayout.h"
#include "layout.h"
#include "../util.h"
......@@ -80,29 +82,14 @@ void PresentationLayout::render(QPainter *painter) {
}
// draw search rects
float factor = p.width() / res->get_page_width(page);
if (search_visible) {
painter->setPen(QColor(0, 0, 0));
painter->setBrush(QColor(255, 0, 0, 64));
float w = res->get_page_width(page);
float h = res->get_page_height(page);
float factor = p.width() / w;
const map<int,QList<QRectF> *> *hits = viewer->get_search_bar()->get_hits();
map<int,QList<QRectF> *>::const_iterator it = hits->find(page);
if (it != hits->end()) {
for (QList<QRectF>::iterator i2 = it->second->begin(); i2 != it->second->end(); ++i2) {
if (i2 == hit_it) {
painter->setBrush(QColor(0, 255, 0, 64));
}
QRectF rot = rotate_rect(*i2, w, h, res->get_rotation());
painter->drawRect(transform_rect(rot, factor, p.x(), p.y()));
if (i2 == hit_it) {
painter->setBrush(QColor(255, 0, 0, 64));
}
}
}
render_search_rects(painter, page, p.topLeft(), factor);
}
// draw text selection
render_selection(painter, page, p.topLeft(), factor);
// draw goto link rects
/* const list<Poppler::LinkGoto *> *l = res->get_links(page);
if (l != NULL) {
......@@ -164,12 +151,12 @@ bool PresentationLayout::view_hit() {
return scroll_page(hit_page, false);
}
bool PresentationLayout::click_mouse(int mx, int my) {
pair<int, QPointF> PresentationLayout::get_location_at(int px, int py) {
const QRect p = calculate_placement(page);
// transform mouse coordinates
float x = (mx - p.x()) / (float) p.width();
float y = (my - p.y()) / (float) p.height();
float x = (px - p.x()) / (float) p.width();
float y = (py - p.y()) / (float) p.height();
// apply rotation
int rotation = res->get_rotation();
......@@ -186,7 +173,7 @@ bool PresentationLayout::click_mouse(int mx, int my) {
x = 1 - tmp;
}
return activate_link(page, x, y);
return make_pair(page, QPointF(x, y));
}
bool PresentationLayout::page_visible(int p) const {
......
......@@ -17,7 +17,7 @@ public:
bool advance_hit(bool forward = true);
bool advance_invisible_hit(bool forward = true);
bool click_mouse(int mx, int my);
std::pair<int, QPointF> get_location_at(int px, int py);
bool page_visible(int p) const;
......
#include "presenterlayout.h"
#include <QApplication>
#include "../resourcemanager.h"
#include "../util.h"
#include "../kpage.h"
......@@ -169,30 +170,17 @@ void PresenterLayout::render(QPainter *painter) {
}
}
// draw search rects
for (int i = 0; i < 2; i++) {
// draw search rects
QPoint offset(center_x[i], center_y[i]);
float factor = page_width[i] / res->get_page_width(page + i);
if (search_visible) {
painter->setPen(QColor(0, 0, 0));
painter->setBrush(QColor(255, 0, 0, 64));
float w_ = res->get_page_width(page + i);
float h_ = res->get_page_height(page + i);
float factor = page_width[i] / w_;
const map<int,QList<QRectF> *> *hits = viewer->get_search_bar()->get_hits();
map<int,QList<QRectF> *>::const_iterator it = hits->find(page + i);
if (it != hits->end()) {
for (QList<QRectF>::iterator i2 = it->second->begin(); i2 != it->second->end(); ++i2) {
if (i2 == hit_it) {
painter->setBrush(QColor(0, 255, 0, 64));
}
QRectF rot = rotate_rect(*i2, w_, h_, res->get_rotation());
painter->drawRect(transform_rect(rot, factor, center_x[i], center_y[i]));
if (