Commit 735e1778 authored by Philipp Erhardt's avatar Philipp Erhardt

Implement config file

I use QSettings in ini format. See the qt documentation to learn about
file locations. If there is no config found the default values are used.

Also, fixed memory leaks.
parent 5f4eed24
#include <QAction>
#include <QStringListIterator>
#include <QKeySequence>
#include <QCoreApplication>
#include <QString>
#include <QPainter>
#include <iostream>
#include "canvas.h"
#include "viewer.h"
#include "layout.h"
#include "resourcemanager.h"
#include "search.h"
#include "gotoline.h"
#include "config.h"
using namespace std;
// TODO put in a config source file
#define MOUSE_WHEEL_FACTOR 120 // (qt-)delta for turning the mouse wheel 1 click
#define SMOOTH_SCROLL_DELTA 30 // pixel scroll offset
Canvas::Canvas(Viewer *v, int start_page, QWidget *parent) :
Canvas::Canvas(Viewer *v, QWidget *parent) :
QWidget(parent),
viewer(v),
draw_overlay(true),
......@@ -21,52 +24,42 @@ Canvas::Canvas(Viewer *v, int start_page, QWidget *parent) :
setFocusPolicy(Qt::StrongFocus);
layout = new PresentationLayout(viewer->get_res());
layout->scroll_page(start_page, false); // apply start option
// load config options
CFG *config = CFG::get_instance();
// apply start option
layout->scroll_page(config->get_value("start_page").toInt(), false);
mouse_wheel_factor = config->get_value("mouse_wheel_factor").toInt();
smooth_scroll_delta = config->get_value("smooth_scroll_delta").toInt();
// setup keys
add_action("set_presentation_layout", SLOT(set_presentation_layout()));
add_action("set_grid_layout", SLOT(set_grid_layout()));
add_action("page_down", SLOT(page_down()));
add_action("page_up", SLOT(page_up()));
add_action("page_first", SLOT(page_first()));
add_action("page_last", SLOT(page_last()));
add_action("focus_goto", SLOT(focus_goto()));
add_action("auto_smooth_up", SLOT(auto_smooth_up()));
add_action("auto_smooth_down", SLOT(auto_smooth_down()));
add_action("smooth_left", SLOT(smooth_left()));
add_action("smooth_right", SLOT(smooth_right()));
add_action("zoom_in", SLOT(zoom_in()));
add_action("zoom_out", SLOT(zoom_out()));
add_action("reset_zoom", SLOT(reset_zoom()));
add_action("columns_inc", SLOT(columns_inc()));
add_action("columns_dec", SLOT(columns_dec()));
add_action("toggle_overlay", SLOT(toggle_overlay()));
add_action("quit", SLOT(quit()));
add_action("search", SLOT(search()));
add_action("next_hit", SLOT(next_hit()));
add_action("previous_hit", SLOT(previous_hit()));
add_action("rotate_left", SLOT(rotate_left()));
add_action("rotate_right", SLOT(rotate_right()));
// prints the string representation of a key
// cerr << QKeySequence(Qt::Key_Equal).toString().toUtf8().constData() << endl;
// key -> function mapping
add_sequence("1", &Canvas::set_presentation_layout);
add_sequence("2", &Canvas::set_grid_layout);
add_sequence("Space", &Canvas::page_down);
add_sequence("PgDown", &Canvas::page_down);
add_sequence("Down", &Canvas::page_down);
add_sequence("Backspace", &Canvas::page_up);
add_sequence("PgUp", &Canvas::page_up);
add_sequence("Up", &Canvas::page_up);
add_sequence("G", &Canvas::page_first);
add_sequence("Shift+G", &Canvas::page_last);
add_sequence("Ctrl+G", &Canvas::focus_goto);
add_sequence("K", &Canvas::auto_smooth_up);
add_sequence("J", &Canvas::auto_smooth_down);
add_sequence("H", &Canvas::smooth_left);
add_sequence("L", &Canvas::smooth_right);
add_sequence("+", &Canvas::zoom_in);
add_sequence("=", &Canvas::zoom_in);
add_sequence("-", &Canvas::zoom_out);
add_sequence("Z", &Canvas::reset_zoom);
add_sequence("]", &Canvas::columns_inc);
add_sequence("[", &Canvas::columns_dec);
add_sequence("T", &Canvas::toggle_overlay);
add_sequence("Q", &Canvas::quit);
add_sequence("W,E,E,E", &Canvas::quit); // just messing around :)
add_sequence("/", &Canvas::search);
add_sequence("N", &Canvas::next_hit);
add_sequence("Shift+N", &Canvas::previous_hit);
add_sequence("u", &Canvas::rotate_left);
add_sequence("i", &Canvas::rotate_right);
goto_line = new GotoLine(viewer->get_res()->get_page_count(), this);
goto_line->hide(); // TODO why is it shown by default?
connect(goto_line, SIGNAL(returnPressed()), this, SLOT(goto_page()), Qt::UniqueConnection);
......@@ -89,27 +82,20 @@ void Canvas::reload() {
update();
}
void Canvas::add_sequence(QString key, func_t action) {
QKeySequence s(key);
sequences[s] = action;
grabShortcut(s, Qt::WidgetShortcut);
void Canvas::add_action(const char *action, const char *slot) {
QStringListIterator i(CFG::get_instance()->get_keys(action));
while (i.hasNext()) {
QAction *a = new QAction(this);
a->setShortcut(QKeySequence(i.next()));
addAction(a);
connect(a, SIGNAL(triggered()), this, slot);
}
}
const Layout *Canvas::get_layout() const {
return layout;
}
bool Canvas::event(QEvent *event) {
if (event->type() == QEvent::Shortcut) {
QShortcutEvent *s = static_cast<QShortcutEvent*>(event);
if (sequences.find(s->key()) != sequences.end()) {
(this->*sequences[s->key()])();
}
return true;
}
return QWidget::event(event);
}
void Canvas::paintEvent(QPaintEvent * /*event*/) {
#ifdef DEBUG
cerr << "redraw" << endl;
......@@ -172,7 +158,7 @@ void Canvas::wheelEvent(QWheelEvent *event) {
update();
}
} else {
if (layout->scroll_page(-d / MOUSE_WHEEL_FACTOR)) {
if (layout->scroll_page(-d / mouse_wheel_factor)) {
update();
}
}
......@@ -253,26 +239,26 @@ void Canvas::auto_smooth_down() {
}
void Canvas::smooth_up() {
if (layout->scroll_smooth(0, SMOOTH_SCROLL_DELTA)) {
if (layout->scroll_smooth(0, smooth_scroll_delta)) {
update();
}
}
void Canvas::smooth_down() {
if (layout->scroll_smooth(0, -SMOOTH_SCROLL_DELTA)) {
if (layout->scroll_smooth(0, -smooth_scroll_delta)) {
update();
}
}
void Canvas::smooth_left() {
if (layout->scroll_smooth(SMOOTH_SCROLL_DELTA, 0)) {
if (layout->scroll_smooth(smooth_scroll_delta, 0)) {
update();
}
}
void Canvas::smooth_right() {
if (layout->scroll_smooth(-SMOOTH_SCROLL_DELTA, 0)) {
if (layout->scroll_smooth(-smooth_scroll_delta, 0)) {
update();
}
}
......
......@@ -2,18 +2,10 @@
#define CANVAS_H
#include <QWidget>
#include <QImage>
#include <QPainter>
#include <QPaintEvent>
#include <QString>
//#include <QKeyEvent>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QCoreApplication>
#include <QResizeEvent>
#include <QKeySequence>
#include <iostream>
#include <map>
#include <list>
#include <sys/socket.h>
......@@ -27,10 +19,8 @@ class GotoLine;
class Canvas : public QWidget {
Q_OBJECT
typedef void (Canvas::*func_t)();
public:
Canvas(Viewer *v, int start_page = 0, QWidget *parent = 0);
Canvas(Viewer *v, QWidget *parent = 0);
~Canvas();
bool is_valid() const;
......@@ -40,10 +30,7 @@ public:
protected:
// QT event handling
bool event(QEvent *event);
void paintEvent(QPaintEvent *event);
// void keyPressEvent(QKeyEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
......@@ -58,7 +45,6 @@ private slots:
void page_rendered(int page);
void goto_page();
private:
// primitive actions
void set_presentation_layout();
void set_grid_layout();
......@@ -86,16 +72,14 @@ private:
void rotate_left();
void rotate_right();
void add_sequence(QString key, func_t action);
private:
void add_action(const char *action, const char *slot);
Viewer *viewer;
Layout *layout;
GotoLine *goto_line;
// key sequences
std::map<QKeySequence,func_t> sequences;
int mx, my;
int mx_down, my_down;
......@@ -103,6 +87,9 @@ private:
bool valid;
// config options
int mouse_wheel_factor;
int smooth_scroll_delta;
};
#endif
......
#include "config.h"
#include <QHashIterator>
CFG::CFG() :
settings(QSettings::IniFormat, QSettings::UserScope, "katarakt") {
// TODO warn about invalid user input
settings.beginGroup("Settings");
// canvas options
defaults["mouse_wheel_factor"] = 120; // (qt-)delta for turning the mouse wheel 1 click
defaults["smooth_scroll_delta"] = 30; // pixel scroll offset
// layout options
defaults["useless_gap"] = 2;
defaults["min_page_width"] = 150;
defaults["min_zoom"] = -14;
defaults["max_zoom"] = 30;
defaults["zoom_factor"] = 0.05;
defaults["prefetch_count"] = 3;
defaults["search_padding"] = 0.2; // must be <= 0.5
// resourcemanager options
defaults["smooth_downscaling"] = true; // filter when creating thumbnail image
defaults["thumbnail_size"] = 32;
// search options
defaults["rect_expansion"] = 2;
settings.endGroup();
settings.beginGroup("keys");
// canvas keys
keys["set_presentation_layout"] = QStringList() << "1";
keys["set_grid_layout"] = QStringList() << "2";
keys["page_down"] = QStringList() << "Space" << "PgDown" << "Down";
keys["page_up"] = QStringList() << "Backspace" << "PgUp" << "Up";
keys["page_first"] = QStringList() << "G";
keys["page_last"] = QStringList() << "Shift+G";
keys["focus_goto"] = QStringList() << "Ctrl+G";
keys["auto_smooth_up"] = QStringList() << "K";
keys["auto_smooth_down"] = QStringList() << "J";
keys["smooth_left"] = QStringList() << "H";
keys["smooth_right"] = QStringList() << "L";
keys["zoom_in"] = QStringList() << "=" << "+";
keys["zoom_out"] = QStringList() << "-";
keys["reset_zoom"] = QStringList() << "Z";
keys["columns_inc"] = QStringList() << "]";
keys["columns_dec"] = QStringList() << "[";
keys["toggle_overlay"] = QStringList() << "T";
keys["quit"] = QStringList() << "Q" << "W,E,E,E";
keys["search"] = QStringList() << "/";
keys["next_hit"] = QStringList() << "N";
keys["previous_hit"] = QStringList() << "Shift+N";
keys["rotate_left"] = QStringList() << "U";
keys["rotate_right"] = QStringList() << "I";
// viewer keys
keys["toggle_fullscreen"] = QStringList() << "F";
keys["close_search"] = QStringList() << "Esc";
keys["reload"] = QStringList() << "R";
settings.endGroup();
}
CFG::CFG(const CFG &/*other*/) {
}
CFG &CFG::operator=(const CFG &/*other*/) {
return *this;
}
CFG::~CFG() {
// do not write temporary options to disk
settings.remove("start_page");
settings.remove("fullscreen");
// set_defaults(); // uncomment to create ini file with all the default settings
}
void CFG::set_defaults() {
settings.beginGroup("Settings");
QHashIterator<QString,QVariant> it(defaults);
while (it.hasNext()) {
it.next();
settings.setValue(it.key(), it.value());
}
settings.endGroup();
settings.beginGroup("Keys");
QHashIterator<QString,QStringList> i2(keys);
while (i2.hasNext()) {
i2.next();
settings.setValue(i2.key(), i2.value());
}
settings.endGroup();
}
CFG *CFG::get_instance() {
static CFG instance;
return &instance;
}
QVariant CFG::get_value(const char *key) const {
return settings.value(QString("Settings/") + key, defaults[key]);
}
void CFG::set_value(const char *key, QVariant value) {
settings.setValue(QString("Settings/") + key, value);
}
QStringList CFG::get_keys(const char *action) const {
return settings.value(QString("Keys/") + action, keys[action]).toStringList();
}
#ifndef CONFIG_H
#define CONFIG_H
#include <QSettings>
#include <QHash>
#include <QString>
#include <QVariant>
#include <QStringList>
class CFG {
private:
CFG();
CFG(const CFG &other);
CFG &operator=(const CFG &other);
~CFG();
void set_defaults();
QSettings settings;
QHash<QString,QVariant> defaults;
QHash<QString,QStringList> keys;
public:
static CFG *get_instance();
QVariant get_value(const char *key) const;
void set_value(const char *key, QVariant value);
QStringList get_keys(const char *action) const;
};
#endif
......@@ -7,6 +7,6 @@ CONFIG += qt
QMAKE_CXXFLAGS_DEBUG += -DDEBUG
# Input
HEADERS += layout.h viewer.h canvas.h resourcemanager.h grid.h search.h gotoline.h
SOURCES += main.cpp layout.cpp viewer.cpp canvas.cpp resourcemanager.cpp grid.cpp search.cpp gotoline.cpp
HEADERS += layout.h viewer.h canvas.h resourcemanager.h grid.h search.h gotoline.h config.h
SOURCES += main.cpp layout.cpp viewer.cpp canvas.cpp resourcemanager.cpp grid.cpp search.cpp gotoline.cpp config.cpp
unix:LIBS += -lpoppler-qt4
#include <iostream>
#include <QImage>
#include "layout.h"
#include "resourcemanager.h"
#include "grid.h"
#include "search.h"
#include "config.h"
using namespace std;
// TODO put in a config source file
#define USELESS_GAP 2
#define MIN_PAGE_WIDTH 150
#define MIN_ZOOM -14
#define MAX_ZOOM 30
#define ZOOM_FACTOR 0.05f
#define PREFETCH_COUNT 3
#define SEARCH_PADDING 0.2 // must be <= 0.5
// rounds a float when afterwards cast to int
// seems to fix the mismatch between calculated page height and actual image height
#define ROUND(x) ((x) + 0.5f)
......@@ -25,6 +19,19 @@ using namespace std;
Layout::Layout(ResourceManager *_res, int _page) :
res(_res), page(_page), off_x(0), off_y(0), width(0), height(0),
search_visible(false) {
// load config options
CFG *config = CFG::get_instance();
useless_gap = config->get_value("useless_gap").toInt();
min_page_width = config->get_value("min_page_width").toInt();
min_zoom = config->get_value("min_zoom").toInt();
max_zoom = config->get_value("max_zoom").toInt();
zoom_factor = config->get_value("zoom_factor").toFloat();
prefetch_count = config->get_value("prefetch_count").toInt();
search_padding = config->get_value("search_padding").toFloat();
}
Layout::~Layout() {
clear_hits();
}
int Layout::get_page() const {
......@@ -260,7 +267,7 @@ void PresentationLayout::render(QPainter *painter) {
} */
// prefetch
for (int count = 1; count <= PREFETCH_COUNT; count++) {
for (int count = 1; count <= prefetch_count; count++) {
// after current page
if (res->get_page(page + count, calculate_fit_width(page + count)) != NULL) {
res->unlock_page(page + count);
......@@ -270,7 +277,7 @@ void PresentationLayout::render(QPainter *painter) {
res->unlock_page(page - count);
}
}
res->collect_garbage(page - PREFETCH_COUNT * 3, page + PREFETCH_COUNT * 3);
res->collect_garbage(page - prefetch_count * 3, page + prefetch_count * 3);
}
void PresentationLayout::advance_hit(bool forward) {
......@@ -371,14 +378,14 @@ void GridLayout::set_constants() {
for (int i = 0; i < grid->get_column_count(); i++) {
used += grid->get_width(i);
}
int available = width - USELESS_GAP * (grid->get_column_count() - 1);
if (available < MIN_PAGE_WIDTH * grid->get_column_count()) {
available = MIN_PAGE_WIDTH * grid->get_column_count();
int available = width - useless_gap * (grid->get_column_count() - 1);
if (available < min_page_width * grid->get_column_count()) {
available = min_page_width * grid->get_column_count();
}
size = (float) available / used;
// apply zoom value
size *= (1 + zoom * ZOOM_FACTOR);
size *= (1 + zoom * zoom_factor);
horizontal_page = (page + horizontal_page) % grid->get_column_count();
page = page / grid->get_column_count() * grid->get_column_count();
......@@ -387,13 +394,13 @@ void GridLayout::set_constants() {
for (int i = 0; i < grid->get_row_count(); i++) {
total_height += ROUND(grid->get_height(i * grid->get_column_count()) * size);
}
total_height += USELESS_GAP * (grid->get_row_count() - 1);
total_height += useless_gap * (grid->get_row_count() - 1);
total_width = 0;
for (int i = 0; i < grid->get_column_count(); i++) {
total_width += grid->get_width(i) * size;
}
total_width += USELESS_GAP * (grid->get_column_count() - 1);
total_width += useless_gap * (grid->get_column_count() - 1);
// calculate offset for blocking at the right border
border_page_w = grid->get_column_count();
......@@ -405,7 +412,7 @@ void GridLayout::set_constants() {
border_off_w = width - w;
break;
}
w += USELESS_GAP;
w += useless_gap;
}
// bottom border
border_page_h = grid->get_row_count() * grid->get_column_count();
......@@ -417,7 +424,7 @@ void GridLayout::set_constants() {
border_off_h = height - h;
break;
}
h += USELESS_GAP;
h += useless_gap;
}
// update view
......@@ -438,18 +445,18 @@ void GridLayout::resize(int w, int h) {
}
void GridLayout::set_zoom(int new_zoom, bool relative) {
float old_factor = 1 + zoom * ZOOM_FACTOR;
float old_factor = 1 + zoom * zoom_factor;
if (relative) {
zoom += new_zoom;
} else {
zoom = new_zoom;
}
if (zoom < MIN_ZOOM) {
zoom = MIN_ZOOM;
} else if (zoom > MAX_ZOOM) {
zoom = MAX_ZOOM;
if (zoom < min_zoom) {
zoom = min_zoom;
} else if (zoom > max_zoom) {
zoom = max_zoom;
}
float new_factor = 1 + zoom * ZOOM_FACTOR;
float new_factor = 1 + zoom * zoom_factor;
off_x = (off_x - width / 2) * new_factor / old_factor + width / 2;
off_y = (off_y - height / 2) * new_factor / old_factor + height / 2;
......@@ -483,14 +490,14 @@ bool GridLayout::scroll_smooth(int dx, int dy) {
// page up
while (off_y > 0 &&
(h = ROUND(grid->get_height(page - grid->get_column_count()) * size)) > 0) {
off_y -= h + USELESS_GAP;
off_y -= h + useless_gap;
page -= grid->get_column_count();
}
// page down
while ((h = ROUND(grid->get_height(page) * size)) > 0 &&
page < border_page_h &&
off_y <= -h - USELESS_GAP) {
off_y += h + USELESS_GAP;
off_y <= -h - useless_gap) {
off_y += h + useless_gap;
page += grid->get_column_count();
}
// top and bottom borders
......@@ -512,15 +519,15 @@ bool GridLayout::scroll_smooth(int dx, int dy) {
// page left
while (off_x > 0 &&
(w = grid->get_width(horizontal_page - 1) * size) > 0) {
off_x -= w + USELESS_GAP;
off_x -= w + useless_gap;
horizontal_page--;
}
// page right
while ((w = grid->get_width(horizontal_page) * size) > 0 &&
horizontal_page < border_page_w &&
horizontal_page < grid->get_column_count() - 1 && // only for horizontal
off_x <= -w - USELESS_GAP) {
off_x += w + USELESS_GAP;
off_x <= -w - useless_gap) {
off_x += w + useless_gap;
horizontal_page++;
}
// left and right borders
......@@ -629,22 +636,22 @@ void GridLayout::render(QPainter *painter) {
}
}
wpos += grid_width + USELESS_GAP;
wpos += grid_width + useless_gap;
cur_col++;
}
hpos += grid_height + USELESS_GAP;
hpos += grid_height + useless_gap;
cur_page += grid->get_column_count();
}
last_visible_page = last_page;
res->collect_garbage(page - PREFETCH_COUNT * 3, last_page + PREFETCH_COUNT * 3);
res->collect_garbage(page - prefetch_count * 3, last_page + prefetch_count * 3);
// prefetch
int cur_col = last_page % grid->get_column_count();
cur_page = last_page - cur_col;
int cur_col_first = horizontal_page;
int cur_page_first = page;
for (int count = 0; count < PREFETCH_COUNT; count++) {
for (int count = 0; count < prefetch_count; count++) {
// after last visible page
cur_col++;
if (cur_col >= grid->get_column_count()) {
......@@ -683,18 +690,18 @@ void GridLayout::view_hit() {
int wpos = off_x;
for (int i = 0; i < hit_page % grid->get_column_count(); i++) {
wpos += grid->get_width(i) * size + USELESS_GAP;
wpos += grid->get_width(i) * size + useless_gap;
}
int hpos = off_y;
if (hit_page > page) {
for (int i = page / grid->get_column_count();
i < hit_page / grid->get_column_count(); i++) {
hpos += grid->get_height(i) * size + USELESS_GAP;
hpos += grid->get_height(i) * size + useless_gap;
}
} else {
for (int i = page / grid->get_column_count();
i > hit_page / grid->get_column_count(); i--) {
hpos -= grid->get_height(i) * size + USELESS_GAP;
hpos -= grid->get_height(i) * size + useless_gap;
}
}
// get search rect coordinates relative to the current view
......@@ -702,11 +709,11 @@ void GridLayout::view_hit() {
res->get_page_width(hit_page), res->get_page_height(hit_page),
wpos + center_x, hpos + center_y, res->get_rotation());
// move view horizontally
if (r.width() <= width * (1 - 2 * SEARCH_PADDING)) {
if (r.x() < width * SEARCH_PADDING) {
scroll_smooth(width * SEARCH_PADDING - r.x(), 0);
} else if (r.x() + r.width() > width * (1 - SEARCH_PADDING)) {
scroll_smooth(width * (1 - SEARCH_PADDING) - r.x() - r.width(), 0);
if (r.width() <= width * (