qpdfviewsb/sources/miscellaneous.cpp

992 lines
22 KiB
C++

/*
Copyright 2014 S. Razi Alavizadeh
Copyright 2012-2018 Adam Reichold
Copyright 2018 Pavel Sanda
Copyright 2014 Dorian Scholz
This file is part of qpdfview.
qpdfview is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
qpdfview is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qpdfview. If not, see <http://www.gnu.org/licenses/>.
*/
#include "miscellaneous.h"
#include <QApplication>
#include <QDebug>
#include <QDialog>
#include <QDialogButtonBox>
#include <QLabel>
#include <QMenu>
#include <QMouseEvent>
#include <QProcess>
#include <QScrollBar>
#include <QTimer>
#include <QToolTip>
#include <QVBoxLayout>
#include "searchmodel.h"
namespace qpdfview
{
namespace
{
inline bool isPrintable(const QString& string)
{
foreach(const QChar& character, string)
{
if(!character.isPrint())
{
return false;
}
}
return true;
}
inline QModelIndex firstIndex(const QModelIndexList& indexes)
{
return !indexes.isEmpty() ? indexes.first() : QModelIndex();
}
} // anonymous
GraphicsCompositionModeEffect::GraphicsCompositionModeEffect(QPainter::CompositionMode compositionMode, QObject* parent) : QGraphicsEffect(parent),
m_compositionMode(compositionMode)
{
}
void GraphicsCompositionModeEffect::draw(QPainter* painter)
{
painter->save();
painter->setCompositionMode(m_compositionMode);
drawSource(painter);
painter->restore();
}
ProxyStyle::ProxyStyle() : QProxyStyle(),
m_scrollableMenus(false)
{
}
bool ProxyStyle::scrollableMenus() const
{
return m_scrollableMenus;
}
void ProxyStyle::setScrollableMenus(bool scrollableMenus)
{
m_scrollableMenus = scrollableMenus;
}
int ProxyStyle::styleHint(StyleHint hint, const QStyleOption* option, const QWidget* widget, QStyleHintReturn* returnData) const
{
if(m_scrollableMenus && hint == QStyle::SH_Menu_Scrollable)
{
return 1;
}
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
ToolTipMenu::ToolTipMenu(QWidget* parent) : QMenu(parent)
{
}
ToolTipMenu::ToolTipMenu(const QString& title, QWidget* parent) : QMenu(title, parent)
{
}
bool ToolTipMenu::event(QEvent* event)
{
const QAction* const action = activeAction();
if(event->type() == QEvent::ToolTip && action != 0 && !action->data().isNull())
{
QToolTip::showText(static_cast< QHelpEvent* >(event)->globalPos(), action->toolTip());
}
else
{
QToolTip::hideText();
}
return QMenu::event(event);
}
SearchableMenu::SearchableMenu(const QString& title, QWidget* parent) : ToolTipMenu(title, parent),
m_searchable(false),
m_text()
{
}
bool SearchableMenu::isSearchable() const
{
return m_searchable;
}
void SearchableMenu::setSearchable(bool searchable)
{
m_searchable = searchable;
}
void SearchableMenu::hideEvent(QHideEvent* event)
{
QMenu::hideEvent(event);
if(m_searchable && !event->spontaneous())
{
m_text = QString();
foreach(QAction* action, actions())
{
action->setVisible(true);
}
}
}
void SearchableMenu::keyPressEvent(QKeyEvent* event)
{
if(!m_searchable)
{
QMenu::keyPressEvent(event);
return;
}
const QString text = event->text();
if(!text.isEmpty() && isPrintable(text))
{
m_text.append(text);
}
else if(event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete)
{
m_text.chop(1);
}
else
{
QMenu::keyPressEvent(event);
return;
}
QAction* firstVisibleAction = 0;
foreach(QAction* action, actions())
{
if(action->data().isNull()) // Modify only flagged actions
{
continue;
}
const bool visible = action->text().contains(m_text, Qt::CaseInsensitive);
action->setVisible(visible);
if(visible && firstVisibleAction == 0)
{
firstVisibleAction = action;
}
}
setActiveAction(firstVisibleAction);
QToolTip::showText(mapToGlobal(rect().topLeft()), tr("Search for '%1'...").arg(m_text), this);
}
TabBar::TabBar(QWidget* parent) : QTabBar(parent),
m_dragIndex(-1)
{
}
QSize TabBar::tabSizeHint(int index) const
{
QSize size = QTabBar::tabSizeHint(index);
const TabWidget* tabWidget = qobject_cast< TabWidget* >(parentWidget());
if(tabWidget != 0 && tabWidget->spreadTabs())
{
switch(tabWidget->tabPosition())
{
default:
case QTabWidget::North:
case QTabWidget::South:
size.setWidth(qMax(width() / count(), size.width()));
break;
case QTabWidget::East:
case QTabWidget::West:
size.setHeight(qMax(height() / count(), size.height()));
break;
}
}
return size;
}
void TabBar::mousePressEvent(QMouseEvent* event)
{
if(event->button() == Qt::MidButton)
{
const int index = tabAt(event->pos());
if(index != -1)
{
emit tabCloseRequested(index);
event->accept();
return;
}
}
else if(event->modifiers() == Qt::ShiftModifier && event->button() == Qt::LeftButton)
{
const int index = tabAt(event->pos());
if(index != -1)
{
m_dragIndex = index;
m_dragPos = event->pos();
event->accept();
return;
}
}
QTabBar::mousePressEvent(event);
}
void TabBar::mouseMoveEvent(QMouseEvent* event)
{
QTabBar::mouseMoveEvent(event);
if(m_dragIndex != -1)
{
if((event->pos() - m_dragPos).manhattanLength() >= QApplication::startDragDistance())
{
emit tabDragRequested(m_dragIndex);
m_dragIndex = -1;
}
}
}
void TabBar::mouseReleaseEvent(QMouseEvent* event)
{
QTabBar::mouseReleaseEvent(event);
m_dragIndex = -1;
}
TabWidget::TabWidget(QWidget* parent) : QTabWidget(parent),
m_tabBarPolicy(TabBarAsNeeded),
m_spreadTabs(false)
{
TabBar* tabBar = new TabBar(this);
tabBar->setContextMenuPolicy(Qt::CustomContextMenu);
connect(tabBar, SIGNAL(tabDragRequested(int)), SIGNAL(tabDragRequested(int)));
connect(tabBar, SIGNAL(customContextMenuRequested(QPoint)), SLOT(on_tabBar_customContextMenuRequested(QPoint)));
setTabBar(tabBar);
}
int TabWidget::addTab(QWidget* const widget, const bool nextToCurrent,
const QString& label, const QString& toolTip)
{
const int index = nextToCurrent
? insertTab(currentIndex() + 1, widget, label)
: QTabWidget::addTab(widget, label);
setTabToolTip(index, toolTip);
setCurrentIndex(index);
return index;
}
TabWidget::TabBarPolicy TabWidget::tabBarPolicy() const
{
return m_tabBarPolicy;
}
void TabWidget::setTabBarPolicy(TabWidget::TabBarPolicy tabBarPolicy)
{
m_tabBarPolicy = tabBarPolicy;
switch(m_tabBarPolicy)
{
case TabBarAsNeeded:
tabBar()->setVisible(count() > 1);
break;
case TabBarAlwaysOn:
tabBar()->setVisible(true);
break;
case TabBarAlwaysOff:
tabBar()->setVisible(false);
break;
}
}
bool TabWidget::spreadTabs() const
{
return m_spreadTabs;
}
void TabWidget::setSpreadTabs(bool spreadTabs)
{
if(m_spreadTabs != spreadTabs)
{
m_spreadTabs = spreadTabs;
QResizeEvent resizeEvent(tabBar()->size(), tabBar()->size());
QApplication::sendEvent(tabBar(), &resizeEvent);
}
}
void TabWidget::previousTab()
{
int index = currentIndex() - 1;
if(index < 0)
{
index = count() - 1;
}
setCurrentIndex(index);
}
void TabWidget::nextTab()
{
int index = currentIndex() + 1;
if(index >= count())
{
index = 0;
}
setCurrentIndex(index);
}
void TabWidget::on_tabBar_customContextMenuRequested(QPoint pos)
{
const int index = tabBar()->tabAt(pos);
if(index != -1)
{
emit tabContextMenuRequested(tabBar()->mapToGlobal(pos), tabBar()->tabAt(pos));
}
}
void TabWidget::tabInserted(int index)
{
QTabWidget::tabInserted(index);
if(m_tabBarPolicy == TabBarAsNeeded)
{
tabBar()->setVisible(count() > 1);
}
}
void TabWidget::tabRemoved(int index)
{
QTabWidget::tabRemoved(index);
if(m_tabBarPolicy == TabBarAsNeeded)
{
tabBar()->setVisible(count() > 1);
}
}
TreeView::TreeView(int expansionRole, QWidget* parent) : QTreeView(parent),
m_expansionRole(expansionRole)
{
connect(this, SIGNAL(expanded(QModelIndex)), SLOT(on_expanded(QModelIndex)));
connect(this, SIGNAL(collapsed(QModelIndex)), SLOT(on_collapsed(QModelIndex)));
}
void TreeView::expandAbove(const QModelIndex& child)
{
for(QModelIndex index = child.parent(); index.isValid(); index = index.parent())
{
expand(index);
}
}
void TreeView::expandAll(const QModelIndex& index)
{
if(index.isValid())
{
if(!isExpanded(index))
{
expand(index);
}
for(int row = 0, rowCount = model()->rowCount(index); row < rowCount; ++row)
{
expandAll(index.child(row, 0));
}
}
else
{
QTreeView::expandAll();
}
}
void TreeView::collapseAll(const QModelIndex& index)
{
if(index.isValid())
{
if(isExpanded(index))
{
collapse(index);
}
for(int row = 0, rowCount = model()->rowCount(index); row < rowCount; ++row)
{
collapseAll(index.child(row, 0));
}
}
else
{
QTreeView::collapseAll();
}
}
int TreeView::expandedDepth(const QModelIndex& index)
{
if(index.isValid())
{
if(!isExpanded(index) || !model()->hasChildren(index))
{
return 0;
}
int depth = 0;
for(int row = 0, rowCount = model()->rowCount(index); row < rowCount; ++row)
{
depth = qMax(depth, expandedDepth(index.child(row, 0)));
}
return 1 + depth;
}
else
{
int depth = 0;
for(int row = 0, rowCount = model()->rowCount(); row < rowCount; ++row)
{
depth = qMax(depth, expandedDepth(model()->index(row, 0)));
}
return depth;
}
}
void TreeView::expandToDepth(const QModelIndex& index, int depth)
{
if(index.isValid())
{
if(depth > 0)
{
if(!isExpanded(index))
{
expand(index);
}
}
if(depth > 1)
{
for(int row = 0, rowCount = model()->rowCount(index); row < rowCount; ++row)
{
expandToDepth(index.child(row, 0), depth - 1);
}
}
}
else
{
for(int row = 0, rowCount = model()->rowCount(); row < rowCount; ++row)
{
expandToDepth(model()->index(row, 0), depth);
}
}
}
void TreeView::collapseFromDepth(const QModelIndex& index, int depth)
{
if(index.isValid())
{
if(depth <= 0)
{
if(isExpanded(index))
{
collapse(index);
}
}
for(int row = 0, rowCount = model()->rowCount(index); row < rowCount; ++row)
{
collapseFromDepth(index.child(row, 0), depth - 1);
}
}
else
{
for(int row = 0, rowCount = model()->rowCount(); row < rowCount; ++row)
{
collapseFromDepth(model()->index(row, 0), depth);
}
}
}
void TreeView::restoreExpansion(const QModelIndex& index)
{
if(index.isValid())
{
const bool expanded = index.data(m_expansionRole).toBool();
if(isExpanded(index) != expanded)
{
setExpanded(index, expanded);
}
}
for(int row = 0, rowCount = model()->rowCount(index); row < rowCount; ++row)
{
restoreExpansion(model()->index(row, 0, index));
}
}
void TreeView::keyPressEvent(QKeyEvent* event)
{
const bool verticalKeys = event->key() == Qt::Key_Up || event->key() == Qt::Key_Down;
const bool horizontalKeys = event->key() == Qt::Key_Left || event->key() == Qt::Key_Right;
const QModelIndex selection = firstIndex(selectedIndexes());
// If Shift is pressed, the view is scrolled up or down.
if(event->modifiers().testFlag(Qt::ShiftModifier) && verticalKeys)
{
QScrollBar* scrollBar = verticalScrollBar();
if(event->key() == Qt::Key_Up && scrollBar->value() > scrollBar->minimum())
{
scrollBar->triggerAction(QAbstractSlider::SliderSingleStepSub);
event->accept();
return;
}
else if(event->key() == Qt::Key_Down && scrollBar->value() < scrollBar->maximum())
{
scrollBar->triggerAction(QAbstractSlider::SliderSingleStepAdd);
event->accept();
return;
}
}
// If Control is pressed, all children of the selected item are expanded or collapsed.
if(event->modifiers().testFlag(Qt::ControlModifier) && horizontalKeys)
{
if(event->key() == Qt::Key_Left)
{
collapseAll(selection);
}
else if(event->key() == Qt::Key_Right)
{
expandAll(selection);
}
event->accept();
return;
}
// If Shift is pressed, one level of children of the selected item are expanded or collapsed.
if(event->modifiers().testFlag(Qt::ShiftModifier) && horizontalKeys)
{
const int depth = expandedDepth(selection);
if(event->key() == Qt::Key_Left)
{
collapseFromDepth(selection, depth - 1);
}
else if(event->key() == Qt::Key_Right)
{
expandToDepth(selection, depth + 1);
}
event->accept();
return;
}
QTreeView::keyPressEvent(event);
}
void TreeView::wheelEvent(QWheelEvent* event)
{
const QModelIndex selection = firstIndex(selectedIndexes());
// If Control is pressed, expand or collapse the selected entry.
if(event->modifiers().testFlag(Qt::ControlModifier) && selection.isValid())
{
if(event->delta() > 0)
{
collapse(selection);
}
else
{
expand(selection);
}
// Fall through when Shift is also pressed.
if(!event->modifiers().testFlag(Qt::ShiftModifier))
{
event->accept();
return;
}
}
// If Shift is pressed, move the selected entry up and down.
if(event->modifiers().testFlag(Qt::ShiftModifier) && selection.isValid())
{
QModelIndex sibling;
if(event->delta() > 0)
{
sibling = indexAbove(selection);
}
else
{
sibling = indexBelow(selection);
}
if(sibling.isValid())
{
setCurrentIndex(sibling);
}
event->accept();
return;
}
QTreeView::wheelEvent(event);
}
void TreeView::contextMenuEvent(QContextMenuEvent* event)
{
QTreeView::contextMenuEvent(event);
if(!event->isAccepted())
{
QMenu menu;
const QAction* expandAllAction = menu.addAction(tr("&Expand all"));
const QAction* collapseAllAction = menu.addAction(tr("&Collapse all"));
const QAction* action = menu.exec(event->globalPos());
if(action == expandAllAction)
{
expandAll(indexAt(event->pos()));
}
else if(action == collapseAllAction)
{
collapseAll(indexAt(event->pos()));
}
}
}
void TreeView::on_expanded(const QModelIndex& index)
{
model()->setData(index, true, m_expansionRole);
}
void TreeView::on_collapsed(const QModelIndex& index)
{
model()->setData(index, false, m_expansionRole);
}
LineEdit::LineEdit(QWidget* parent) : QLineEdit(parent)
{
}
void LineEdit::mousePressEvent(QMouseEvent* event)
{
QLineEdit::mousePressEvent(event);
selectAll();
}
ComboBox::ComboBox(QWidget* parent) : QComboBox(parent)
{
setLineEdit(new LineEdit(this));
}
SpinBox::SpinBox(QWidget* parent) : QSpinBox(parent)
{
setLineEdit(new LineEdit(this));
}
void SpinBox::keyPressEvent(QKeyEvent* event)
{
QSpinBox::keyPressEvent(event);
if(event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
{
emit returnPressed();
}
}
MappingSpinBox::MappingSpinBox(TextValueMapper* mapper, QWidget* parent) : SpinBox(parent),
m_mapper(mapper)
{
}
QString MappingSpinBox::textFromValue(int val) const
{
bool ok = false;
QString text = m_mapper->textFromValue(val, ok);
if(!ok)
{
text = SpinBox::textFromValue(val);
}
return text;
}
int MappingSpinBox::valueFromText(const QString& text) const
{
bool ok = false;
int value = m_mapper->valueFromText(text, ok);
if(!ok)
{
value = SpinBox::valueFromText(text);
}
return value;
}
QValidator::State MappingSpinBox::validate(QString& input, int& pos) const
{
Q_UNUSED(input);
Q_UNUSED(pos);
return QValidator::Acceptable;
}
int getMappedNumber(MappingSpinBox::TextValueMapper* mapper,
QWidget* parent, const QString& title, const QString& caption,
int value, int min, int max, bool* ok, Qt::WindowFlags flags)
{
QDialog* dialog = new QDialog(parent, flags | Qt::MSWindowsFixedSizeDialogHint);
dialog->setWindowTitle(title);
QLabel* label = new QLabel(dialog);
label->setText(caption);
MappingSpinBox* mappingSpinBox = new MappingSpinBox(mapper, dialog);
mappingSpinBox->setRange(min, max);
mappingSpinBox->setValue(value);
mappingSpinBox->selectAll();
QDialogButtonBox* dialogButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, dialog);
QObject::connect(dialogButtonBox, SIGNAL(accepted()), dialog, SLOT(accept()));
QObject::connect(dialogButtonBox, SIGNAL(rejected()), dialog, SLOT(reject()));
dialog->setLayout(new QVBoxLayout(dialog));
dialog->layout()->addWidget(label);
dialog->layout()->addWidget(mappingSpinBox);
dialog->layout()->addWidget(dialogButtonBox);
dialog->setFocusProxy(mappingSpinBox);
const int dialogResult = dialog->exec();
const int number = mappingSpinBox->value();
delete dialog;
if(ok)
{
*ok = dialogResult == QDialog::Accepted;
}
return number;
}
ProgressLineEdit::ProgressLineEdit(QWidget* parent) : QLineEdit(parent),
m_progress(0)
{
}
int ProgressLineEdit::progress() const
{
return m_progress;
}
void ProgressLineEdit::setProgress(int progress)
{
if(m_progress != progress && progress >= 0 && progress <= 100)
{
m_progress = progress;
update();
}
}
void ProgressLineEdit::paintEvent(QPaintEvent* event)
{
QLineEdit::paintEvent(event);
QPainter painter(this);
QRect highlightedRect = rect();
highlightedRect.setWidth(m_progress * highlightedRect.width() / 100);
painter.setCompositionMode(QPainter::CompositionMode_Multiply);
painter.fillRect(highlightedRect, palette().highlight());
}
void ProgressLineEdit::keyPressEvent(QKeyEvent* event)
{
QLineEdit::keyPressEvent(event);
if(event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
{
emit returnPressed(event->modifiers());
}
}
SearchLineEdit::SearchLineEdit(QWidget* parent) : ProgressLineEdit(parent)
{
m_timer = new QTimer(this);
m_timer->setInterval(2000);
m_timer->setSingleShot(true);
connect(this, SIGNAL(textEdited(QString)), m_timer, SLOT(start()));
connect(this, SIGNAL(returnPressed(Qt::KeyboardModifiers)), SLOT(on_returnPressed(Qt::KeyboardModifiers)));
connect(m_timer, SIGNAL(timeout()), SLOT(on_timeout()));
}
void SearchLineEdit::startSearch()
{
QTimer::singleShot(0, this, SLOT(on_timeout()));
}
void SearchLineEdit::startTimer()
{
m_timer->start();
}
void SearchLineEdit::stopTimer()
{
m_timer->stop();
}
void SearchLineEdit::on_timeout()
{
emit searchInitiated(text());
}
void SearchLineEdit::on_returnPressed(Qt::KeyboardModifiers modifiers)
{
stopTimer();
emit searchInitiated(text(), modifiers == Qt::ShiftModifier);
}
Splitter::Splitter(Qt::Orientation orientation, QWidget* parent) : QSplitter(orientation, parent),
m_currentIndex(0)
{
connect(qApp, SIGNAL(focusChanged(QWidget*,QWidget*)), this, SLOT(on_focusChanged(QWidget*,QWidget*)));
}
QWidget* Splitter::currentWidget() const
{
return widget(m_currentIndex);
}
void Splitter::setCurrentWidget(QWidget* const currentWidget)
{
for(int index = 0, count = this->count(); index < count; ++index)
{
QWidget* const widget = this->widget(index);
if(currentWidget == widget)
{
if(m_currentIndex != index)
{
m_currentIndex = index;
emit currentWidgetChanged(currentWidget);
}
return;
}
}
}
void Splitter::setUniformSizes()
{
int size;
switch(orientation())
{
default:
case Qt::Horizontal:
size = width();
break;
case Qt::Vertical:
size = height();
break;
}
QList< int > sizes;
for(int index = 0, count = this->count(); index < count; ++index)
{
sizes.append(size / count);
}
setSizes(sizes);
}
void Splitter::on_focusChanged(QWidget* /* old */, QWidget* now)
{
for(QWidget* currentWidget = now; currentWidget != 0; currentWidget = currentWidget->parentWidget())
{
if(currentWidget->parentWidget() == this)
{
setCurrentWidget(currentWidget);
return;
}
}
}
void openInNewWindow(const QString& filePath, int page)
{
QProcess::startDetached(
QApplication::applicationFilePath(),
QStringList() << QString("%2#%1").arg(page).arg(filePath)
);
}
} // qpdfview