diff --git a/src/lib/bookmarks/bookmarkstoolbarbutton.cpp b/src/lib/bookmarks/bookmarkstoolbarbutton.cpp index 4dc078c8..bfd12532 100644 --- a/src/lib/bookmarks/bookmarkstoolbarbutton.cpp +++ b/src/lib/bookmarks/bookmarkstoolbarbutton.cpp @@ -1,440 +1,445 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2014-2018 David Rosca * * This program 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 3 of the License, or * (at your option) any later version. * * This program 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 this program. If not, see . * ============================================================ */ #include "bookmarkstoolbarbutton.h" #include "bookmarkstools.h" #include "bookmarksmodel.h" #include "bookmarkitem.h" #include "bookmarks.h" #include "mainapplication.h" #include "enhancedmenu.h" #include #include #include #include #include #include +#include #define MAX_WIDTH 150 #define SEPARATOR_WIDTH 8 #define PADDING 5 BookmarksToolbarButton::BookmarksToolbarButton(BookmarkItem* bookmark, QWidget* parent) : QPushButton(parent) , m_bookmark(bookmark) , m_window(0) , m_showOnlyIcon(false) { init(); if (m_bookmark->isFolder()) { setAcceptDrops(true); } } BookmarkItem* BookmarksToolbarButton::bookmark() const { return m_bookmark; } void BookmarksToolbarButton::setMainWindow(BrowserWindow* window) { m_window = window; } bool BookmarksToolbarButton::showOnlyIcon() const { return m_showOnlyIcon; } void BookmarksToolbarButton::setShowOnlyIcon(bool show) { m_showOnlyIcon = show; updateGeometry(); update(); } bool BookmarksToolbarButton::showOnlyText() const { return m_showOnlyText; } void BookmarksToolbarButton::setShowOnlyText(bool show) { m_showOnlyText = show; updateGeometry(); update(); } QSize BookmarksToolbarButton::sizeHint() const { int width = PADDING * 2; if (!m_showOnlyText) { width += 16; } if (m_bookmark->isSeparator()) { width = SEPARATOR_WIDTH; } else if (!m_showOnlyIcon) { +#if QTGUI_VERSION >= QT_VERSION_CHECK(5, 11, 0) + width += PADDING * 2 + fontMetrics().horizontalAdvance(m_bookmark->title()); +#else width += PADDING * 2 + fontMetrics().width(m_bookmark->title()); +#endif if (menu()) { width += PADDING + 8; } } QSize s = QPushButton::sizeHint(); s.setWidth(qMin(width, MAX_WIDTH)); return s; } QSize BookmarksToolbarButton::minimumSizeHint() const { int width = PADDING * 2; if (!m_showOnlyText) { width += 16; } if (m_bookmark->isSeparator()) { width = SEPARATOR_WIDTH; } else if (!m_showOnlyIcon && menu()) { width += PADDING + 8; } QSize s = QPushButton::minimumSizeHint(); s.setWidth(width); return s; } void BookmarksToolbarButton::createMenu() { if (!menu()->isEmpty()) { return; } Menu* m = qobject_cast(menu()); Q_ASSERT(m); BookmarksTools::addFolderContentsToMenu(this, m, m_bookmark); } void BookmarksToolbarButton::menuAboutToShow() { Q_ASSERT(qobject_cast(sender())); Menu *menu = static_cast(sender()); const auto menuActions = menu->actions(); for (QAction *action : menuActions) { BookmarkItem *item = static_cast(action->data().value()); if (item && item->type() == BookmarkItem::Url && action->icon().isNull()) { action->setIcon(item->icon()); } } } void BookmarksToolbarButton::menuMiddleClicked(Menu* menu) { BookmarkItem* item = static_cast(menu->menuAction()->data().value()); Q_ASSERT(item); openFolder(item); } void BookmarksToolbarButton::bookmarkActivated(BookmarkItem* item) { if (QAction* action = qobject_cast(sender())) { item = static_cast(action->data().value()); } Q_ASSERT(item); openBookmark(item); } void BookmarksToolbarButton::bookmarkCtrlActivated(BookmarkItem* item) { if (QAction* action = qobject_cast(sender())) { item = static_cast(action->data().value()); } Q_ASSERT(item); openBookmarkInNewTab(item); } void BookmarksToolbarButton::bookmarkShiftActivated(BookmarkItem* item) { if (QAction* action = qobject_cast(sender())) { item = static_cast(action->data().value()); } Q_ASSERT(item); openBookmarkInNewWindow(item); } void BookmarksToolbarButton::openFolder(BookmarkItem* item) { Q_ASSERT(item->isFolder()); if (m_window) { BookmarksTools::openFolderInTabs(m_window, item); } } void BookmarksToolbarButton::openBookmark(BookmarkItem* item) { Q_ASSERT(item->isUrl()); if (m_window) { BookmarksTools::openBookmark(m_window, item); } } void BookmarksToolbarButton::openBookmarkInNewTab(BookmarkItem* item) { Q_ASSERT(item->isUrl()); if (m_window) { BookmarksTools::openBookmarkInNewTab(m_window, item); } } void BookmarksToolbarButton::openBookmarkInNewWindow(BookmarkItem* item) { Q_ASSERT(item->isUrl()); BookmarksTools::openBookmarkInNewWindow(item); } void BookmarksToolbarButton::init() { Q_ASSERT(m_bookmark); setFocusPolicy(Qt::NoFocus); setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); setToolTip(createTooltip()); if (m_bookmark->isFolder()) { Menu* m = new Menu(this); setMenu(m); createMenu(); } } QString BookmarksToolbarButton::createTooltip() const { if (!m_bookmark->description().isEmpty()) { if (!m_bookmark->urlString().isEmpty()) { return QString("%1\n%2").arg(m_bookmark->description(), m_bookmark->urlString()); } return m_bookmark->description(); } if (!m_bookmark->title().isEmpty() && !m_bookmark->url().isEmpty()) { return QString("%1\n%2").arg(m_bookmark->title(), m_bookmark->urlString()); } if (!m_bookmark->title().isEmpty()) { return m_bookmark->title(); } return m_bookmark->urlString(); } void BookmarksToolbarButton::enterEvent(QEvent* event) { QPushButton::enterEvent(event); update(); } void BookmarksToolbarButton::leaveEvent(QEvent* event) { QPushButton::leaveEvent(event); update(); } void BookmarksToolbarButton::mousePressEvent(QMouseEvent* event) { if (m_bookmark && m_bookmark->isFolder()) { if (event->buttons() == Qt::LeftButton && event->modifiers() == Qt::ControlModifier) { openFolder(m_bookmark); return; } } m_dragStartPosition = event->pos(); QPushButton::mousePressEvent(event); } void BookmarksToolbarButton::mouseReleaseEvent(QMouseEvent* event) { if (m_bookmark && rect().contains(event->pos())) { Qt::MouseButton button = event->button(); Qt::KeyboardModifiers modifiers = event->modifiers(); if (m_bookmark->isUrl()) { if (button == Qt::LeftButton && modifiers == Qt::NoModifier) { bookmarkActivated(m_bookmark); } else if (button == Qt::LeftButton && modifiers == Qt::ShiftModifier) { bookmarkShiftActivated(m_bookmark); } else if (button == Qt::MiddleButton || modifiers == Qt::ControlModifier) { bookmarkCtrlActivated(m_bookmark); } } else if (m_bookmark->isFolder() && button == Qt::MiddleButton) { openFolder(m_bookmark); } } QPushButton::mouseReleaseEvent(event); } void BookmarksToolbarButton::mouseMoveEvent(QMouseEvent *event) { if ((event->pos() - m_dragStartPosition).manhattanLength() < QApplication::startDragDistance()) { QPushButton::mouseMoveEvent(event); return; } setDown(false); QDrag *drag = new QDrag(this); BookmarksButtonMimeData* mime = new BookmarksButtonMimeData; mime->setBookmarkItem(m_bookmark); drag->setMimeData(mime); drag->setPixmap(grab()); drag->exec(); } void BookmarksToolbarButton::paintEvent(QPaintEvent* event) { Q_UNUSED(event) QPainter p(this); // Just draw separator if (m_bookmark->isSeparator()) { QStyleOption opt; opt.initFrom(this); opt.state |= QStyle::State_Horizontal; style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &opt, &p); return; } QStyleOptionButton option; initStyleOption(&option); // We are manually drawing the arrow option.features &= ~QStyleOptionButton::HasMenu; // Draw button base (only under mouse, this is autoraise button) if (isDown() || hitButton(mapFromGlobal(QCursor::pos()))) { option.state |= QStyle::State_AutoRaise | QStyle::State_Raised; style()->drawPrimitive(QStyle::PE_PanelButtonTool, &option, &p, this); } const int shiftX = isDown() ? style()->pixelMetric(QStyle::PM_ButtonShiftHorizontal, &option, this) : 0; const int shiftY = isDown() ? style()->pixelMetric(QStyle::PM_ButtonShiftVertical, &option, this) : 0; const int height = option.rect.height(); const int center = height / 2 + option.rect.top() + shiftY; const int iconSize = 16; const int iconYPos = center - iconSize / 2; int leftPosition = PADDING + shiftX; int rightPosition = option.rect.right() - PADDING; // Draw icon if (!m_showOnlyText) { QRect iconRect(leftPosition, iconYPos, iconSize, iconSize); p.drawPixmap(QStyle::visualRect(option.direction, option.rect, iconRect), m_bookmark->icon().pixmap(iconSize)); leftPosition = iconRect.right() + PADDING; } // Draw menu arrow if (!m_showOnlyIcon && menu()) { const int arrowSize = 8; QStyleOption opt; opt.initFrom(this); const QRect rect = QRect(rightPosition - 8, center - arrowSize / 2, arrowSize, arrowSize); opt.rect = QStyle::visualRect(option.direction, option.rect, rect); opt.state &= ~QStyle::State_MouseOver; style()->drawPrimitive(QStyle::PE_IndicatorArrowDown, &opt, &p, this); rightPosition = rect.left() - PADDING; } // Draw text if (!m_showOnlyIcon) { const int textWidth = rightPosition - leftPosition; const int textYPos = center - fontMetrics().height() / 2; const QString txt = fontMetrics().elidedText(m_bookmark->title(), Qt::ElideRight, textWidth); QRect textRect(leftPosition, textYPos, textWidth, fontMetrics().height()); style()->drawItemText(&p, QStyle::visualRect(option.direction, option.rect, textRect), Qt::TextSingleLine | Qt::AlignCenter, option.palette, true, txt); } } void BookmarksToolbarButton::dragEnterEvent(QDragEnterEvent *event) { const QMimeData* mime = event->mimeData(); if ((mime->hasUrls() && mime->hasText()) || mime->hasFormat(BookmarksButtonMimeData::mimeType())) { event->acceptProposedAction(); setDown(true); return; } QPushButton::dragEnterEvent(event); } void BookmarksToolbarButton::dragLeaveEvent(QDragLeaveEvent *event) { Q_UNUSED(event); setDown(false); } void BookmarksToolbarButton::dropEvent(QDropEvent *event) { setDown(false); const QMimeData* mime = event->mimeData(); if (!mime->hasUrls() && !mime->hasFormat(BookmarksButtonMimeData::mimeType())) { QPushButton::dropEvent(event); return; } BookmarkItem* bookmark = nullptr; if (mime->hasFormat(BookmarksButtonMimeData::mimeType())) { const BookmarksButtonMimeData* bookmarkMime = static_cast(mime); bookmark = bookmarkMime->item(); } else { const QUrl url = mime->urls().at(0); const QString title = mime->hasText() ? mime->text() : url.toEncoded(QUrl::RemoveScheme); bookmark = new BookmarkItem(BookmarkItem::Url); bookmark->setTitle(title); bookmark->setUrl(url); } mApp->bookmarks()->addBookmark(m_bookmark, bookmark); } diff --git a/src/lib/navigation/completer/locationcompleterdelegate.cpp b/src/lib/navigation/completer/locationcompleterdelegate.cpp index 9d2499f9..79d80f33 100644 --- a/src/lib/navigation/completer/locationcompleterdelegate.cpp +++ b/src/lib/navigation/completer/locationcompleterdelegate.cpp @@ -1,364 +1,373 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2010-2018 David Rosca * * This program 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 3 of the License, or * (at your option) any later version. * * This program 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 this program. If not, see . * ============================================================ */ #include "locationcompleterdelegate.h" #include "locationcompletermodel.h" #include "locationbar.h" #include "iconprovider.h" #include "qzsettings.h" #include "mainapplication.h" #include "bookmarkitem.h" #include #include #include #include #include +#include LocationCompleterDelegate::LocationCompleterDelegate(QObject *parent) : QStyledItemDelegate(parent) , m_rowHeight(0) , m_padding(0) { } void LocationCompleterDelegate::paint(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); const QWidget* w = opt.widget; const QStyle* style = w ? w->style() : QApplication::style(); const int height = opt.rect.height(); const int center = height / 2 + opt.rect.top(); // Prepare link font QFont linkFont = opt.font; linkFont.setPointSize(linkFont.pointSize() - 1); const QFontMetrics linkMetrics(linkFont); int leftPosition = m_padding * 2; int rightPosition = opt.rect.right() - m_padding; opt.state |= QStyle::State_Active; const QIcon::Mode iconMode = opt.state & QStyle::State_Selected ? QIcon::Selected : QIcon::Normal; const QPalette::ColorRole colorRole = opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text; const QPalette::ColorRole colorLinkRole = opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Link; #ifdef Q_OS_WIN opt.palette.setColor(QPalette::All, QPalette::HighlightedText, opt.palette.color(QPalette::Active, QPalette::Text)); opt.palette.setColor(QPalette::All, QPalette::Highlight, opt.palette.base().color().darker(108)); #endif QPalette textPalette = opt.palette; textPalette.setCurrentColorGroup(opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled); // Draw background style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, w); const bool isVisitSearchItem = index.data(LocationCompleterModel::VisitSearchItemRole).toBool(); const bool isSearchSuggestion = index.data(LocationCompleterModel::SearchSuggestionRole).toBool(); LocationBar::LoadAction loadAction; bool isWebSearch = isSearchSuggestion; BookmarkItem *bookmark = static_cast(index.data(LocationCompleterModel::BookmarkItemRole).value()); if (isVisitSearchItem) { loadAction = LocationBar::loadAction(index.data(LocationCompleterModel::SearchStringRole).toString()); isWebSearch = loadAction.type == LocationBar::LoadAction::Search; if (!m_forceVisitItem) { bookmark = loadAction.bookmark; } } // Draw icon const int iconSize = 16; const int iconYPos = center - (iconSize / 2); QRect iconRect(leftPosition, iconYPos, iconSize, iconSize); QPixmap pixmap = index.data(Qt::DecorationRole).value().pixmap(iconSize); if (isSearchSuggestion || (isVisitSearchItem && isWebSearch)) { pixmap = QIcon::fromTheme(QSL("edit-find"), QIcon(QSL(":icons/menu/search-icon.svg"))).pixmap(iconSize, iconMode); } if (isVisitSearchItem && bookmark) { pixmap = bookmark->icon().pixmap(iconSize); } else if (loadAction.type == LocationBar::LoadAction::Search) { if (loadAction.searchEngine.name != LocationBar::searchEngine().name) { pixmap = loadAction.searchEngine.icon.pixmap(iconSize); } } painter->drawPixmap(iconRect, pixmap); leftPosition = iconRect.right() + m_padding * 2; // Draw star to bookmark items int starPixmapWidth = 0; if (bookmark) { const QIcon icon = IconProvider::instance()->bookmarkIcon(); const QSize starSize(16, 16); starPixmapWidth = starSize.width(); QPoint pos(rightPosition - starPixmapWidth, center - starSize.height() / 2); QRect starRect(pos, starSize); painter->drawPixmap(starRect, icon.pixmap(starSize, iconMode)); } QString searchText = index.data(LocationCompleterModel::SearchStringRole).toString(); // Draw title leftPosition += 2; QRect titleRect(leftPosition, center - opt.fontMetrics.height() / 2, opt.rect.width() * 0.6, opt.fontMetrics.height()); QString title = index.data(LocationCompleterModel::TitleRole).toString(); painter->setFont(opt.font); if (isVisitSearchItem) { if (bookmark) { title = bookmark->title(); } else { title = index.data(LocationCompleterModel::SearchStringRole).toString(); searchText.clear(); } } leftPosition += viewItemDrawText(painter, &opt, titleRect, title, textPalette.color(colorRole), searchText); leftPosition += m_padding * 2; // Trim link to maximum number of characters that can be visible, otherwise there may be perf issue with huge URLs +#if QTGUI_VERSION >= QT_VERSION_CHECK(5, 11, 0) + const int maxChars = (opt.rect.width() - leftPosition) / opt.fontMetrics.horizontalAdvance(QL1C('i')); +#else const int maxChars = (opt.rect.width() - leftPosition) / opt.fontMetrics.width(QL1C('i')); +#endif QString link; const QByteArray linkArray = index.data(Qt::DisplayRole).toByteArray(); if (!linkArray.startsWith("data") && !linkArray.startsWith("javascript")) { link = QString::fromUtf8(QByteArray::fromPercentEncoding(linkArray)).left(maxChars); } else { link = QString::fromLatin1(linkArray.left(maxChars)); } if (isVisitSearchItem || isSearchSuggestion) { if (!opt.state.testFlag(QStyle::State_Selected) && !opt.state.testFlag(QStyle::State_MouseOver)) { link.clear(); } else if (isVisitSearchItem && (!isWebSearch || m_forceVisitItem)) { link = tr("Visit"); } else { QString searchEngineName = loadAction.searchEngine.name; if (searchEngineName.isEmpty()) { searchEngineName = LocationBar::searchEngine().name; } link = tr("Search with %1").arg(searchEngineName); } } if (bookmark) { link = bookmark->url().toString(); } // Draw separator if (!link.isEmpty()) { QChar separator = QL1C('-'); +#if QTGUI_VERSION >= QT_VERSION_CHECK(5, 11, 0) + QRect separatorRect(leftPosition, center - linkMetrics.height() / 2, linkMetrics.horizontalAdvance(separator), linkMetrics.height()); +#else QRect separatorRect(leftPosition, center - linkMetrics.height() / 2, linkMetrics.width(separator), linkMetrics.height()); +#endif style->drawItemText(painter, separatorRect, Qt::AlignCenter, textPalette, true, separator, colorRole); leftPosition += separatorRect.width() + m_padding * 2; } // Draw link const int leftLinkEdge = leftPosition; const int rightLinkEdge = rightPosition - m_padding - starPixmapWidth; QRect linkRect(leftLinkEdge, center - linkMetrics.height() / 2, rightLinkEdge - leftLinkEdge, linkMetrics.height()); painter->setFont(linkFont); // Draw url (or switch to tab) int tabPos = index.data(LocationCompleterModel::TabPositionTabRole).toInt(); if (qzSettings->showSwitchTab && !m_forceVisitItem && tabPos != -1) { const QIcon tabIcon = QIcon(QSL(":icons/menu/tab.svg")); QRect iconRect(linkRect); iconRect.setX(iconRect.x()); iconRect.setWidth(16); painter->drawPixmap(iconRect, tabIcon.pixmap(iconRect.size(), iconMode)); QRect textRect(linkRect); textRect.setX(textRect.x() + m_padding + 16 + m_padding); viewItemDrawText(painter, &opt, textRect, tr("Switch to tab"), textPalette.color(colorLinkRole)); } else if (isVisitSearchItem || isSearchSuggestion) { viewItemDrawText(painter, &opt, linkRect, link, textPalette.color(colorLinkRole)); } else { viewItemDrawText(painter, &opt, linkRect, link, textPalette.color(colorLinkRole), searchText); } // Draw line at the very bottom of item if the item is not highlighted if (!(opt.state & QStyle::State_Selected)) { QRect lineRect(opt.rect.left(), opt.rect.bottom(), opt.rect.width(), 1); painter->fillRect(lineRect, opt.palette.color(QPalette::AlternateBase)); } } QSize LocationCompleterDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index) if (!m_rowHeight) { QStyleOptionViewItem opt(option); initStyleOption(&opt, index); const QWidget* w = opt.widget; const QStyle* style = w ? w->style() : QApplication::style(); const int padding = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0) + 1; m_padding = padding > 3 ? padding : 3; m_rowHeight = 4 * m_padding + qMax(16, opt.fontMetrics.height()); } return QSize(200, m_rowHeight); } void LocationCompleterDelegate::setForceVisitItem(bool enable) { m_forceVisitItem = enable; } static bool sizeBiggerThan(const QString &s1, const QString &s2) { return s1.size() > s2.size(); } static QSizeF viewItemTextLayout(QTextLayout &textLayout, int lineWidth) { qreal height = 0; qreal widthUsed = 0; textLayout.beginLayout(); QTextLine line = textLayout.createLine(); if (line.isValid()) { line.setLineWidth(lineWidth); line.setPosition(QPointF(0, height)); height += line.height(); widthUsed = qMax(widthUsed, line.naturalTextWidth()); textLayout.endLayout(); } return QSizeF(widthUsed, height); } // most of codes taken from QCommonStylePrivate::viewItemDrawText() // added highlighting and simplified for single-line textlayouts int LocationCompleterDelegate::viewItemDrawText(QPainter *p, const QStyleOptionViewItem *option, const QRect &rect, const QString &text, const QColor &color, const QString &searchText) const { if (text.isEmpty()) { return 0; } const QFontMetrics fontMetrics(p->font()); QString elidedText = fontMetrics.elidedText(text, option->textElideMode, rect.width()); QTextOption textOption; textOption.setWrapMode(QTextOption::NoWrap); textOption.setAlignment(QStyle::visualAlignment(textOption.textDirection(), option->displayAlignment)); QTextLayout textLayout; textLayout.setFont(p->font()); textLayout.setText(elidedText); textLayout.setTextOption(textOption); if (!searchText.isEmpty()) { QList delimiters; QStringList searchStrings = searchText.split(QLatin1Char(' '), QString::SkipEmptyParts); // Look for longer parts first std::sort(searchStrings.begin(), searchStrings.end(), sizeBiggerThan); for (const QString &string : qAsConst(searchStrings)) { int delimiter = text.indexOf(string, 0, Qt::CaseInsensitive); while (delimiter != -1) { int start = delimiter; int end = delimiter + string.length(); bool alreadyContains = false; for (int i = 0; i < delimiters.count(); ++i) { int dStart = delimiters.at(i); int dEnd = delimiters.at(++i); if (dStart <= start && dEnd >= end) { alreadyContains = true; break; } } if (!alreadyContains) { delimiters.append(start); delimiters.append(end); } delimiter = text.indexOf(string, end, Qt::CaseInsensitive); } } // We need to sort delimiters to properly paint all parts that user typed std::sort(delimiters.begin(), delimiters.end()); // If we don't find any match, just paint it without any highlight if (!delimiters.isEmpty() && !(delimiters.count() % 2)) { QList highlightParts; while (!delimiters.isEmpty()) { QTextLayout::FormatRange highlightedPart; int start = delimiters.takeFirst(); int end = delimiters.takeFirst(); highlightedPart.start = start; highlightedPart.length = end - start; highlightedPart.format.setFontWeight(QFont::Bold); highlightedPart.format.setUnderlineStyle(QTextCharFormat::SingleUnderline); highlightParts << highlightedPart; } textLayout.setAdditionalFormats(highlightParts); } } // do layout viewItemTextLayout(textLayout, rect.width()); if (textLayout.lineCount() <= 0) { return 0; } QTextLine textLine = textLayout.lineAt(0); // if elidedText after highlighting is longer // than available width then re-elide it and redo layout int diff = textLine.naturalTextWidth() - rect.width(); if (diff > 0) { elidedText = fontMetrics.elidedText(elidedText, option->textElideMode, rect.width() - diff); textLayout.setText(elidedText); // redo layout viewItemTextLayout(textLayout, rect.width()); if (textLayout.lineCount() <= 0) { return 0; } textLine = textLayout.lineAt(0); } // draw line p->setPen(color); qreal width = qMax(rect.width(), textLayout.lineAt(0).width()); const QRect &layoutRect = QStyle::alignedRect(option->direction, option->displayAlignment, QSize(int(width), int(textLine.height())), rect); const QPointF &position = layoutRect.topLeft(); textLine.draw(p, position); return qMin(rect.width(), textLayout.lineAt(0).naturalTextWidth()); } diff --git a/src/lib/tabwidget/combotabbar.cpp b/src/lib/tabwidget/combotabbar.cpp index 8a309072..155773d3 100644 --- a/src/lib/tabwidget/combotabbar.cpp +++ b/src/lib/tabwidget/combotabbar.cpp @@ -1,1860 +1,1865 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2013-2014 S. Razi Alavizadeh * Copyright (C) 2014-2018 David Rosca * * This program 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 3 of the License, or * (at your option) any later version. * * This program 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 this program. If not, see . * ============================================================ */ #include "combotabbar.h" #include "toolbutton.h" #include "tabicon.h" #include "mainapplication.h" #include "proxystyle.h" #include "qzsettings.h" #include "qztools.h" #include #include #include #include #include #include #include #include #include #include #include #include +#include class QMovableTabWidget : public QWidget { public: QPixmap m_pixmap; }; ComboTabBar::ComboTabBar(QWidget* parent) : QWidget(parent) , m_mainTabBar(0) , m_pinnedTabBar(0) , m_mainBarOverFlowed(false) , m_lastAppliedOverflow(false) , m_usesScrollButtons(false) , m_blockCurrentChangedSignal(false) { QObject::setObjectName(QSL("tabbarwidget")); m_mainTabBar = new TabBarHelper(/*isPinnedTabBar*/ false, this); m_pinnedTabBar = new TabBarHelper(/*isPinnedTabBar*/ true, this); m_mainTabBarWidget = new TabBarScrollWidget(m_mainTabBar, this); m_pinnedTabBarWidget = new TabBarScrollWidget(m_pinnedTabBar, this); m_mainTabBar->setScrollArea(m_mainTabBarWidget->scrollArea()); m_pinnedTabBar->setScrollArea(m_pinnedTabBarWidget->scrollArea()); connect(m_mainTabBarWidget->scrollBar(), &QAbstractSlider::rangeChanged, this, &ComboTabBar::setMinimumWidths); connect(m_mainTabBarWidget->scrollBar(), &QAbstractSlider::valueChanged, this, &ComboTabBar::scrollBarValueChanged); connect(m_pinnedTabBarWidget->scrollBar(), &QAbstractSlider::rangeChanged, this, &ComboTabBar::setMinimumWidths); connect(m_pinnedTabBarWidget->scrollBar(), &QAbstractSlider::valueChanged, this, &ComboTabBar::scrollBarValueChanged); connect(this, SIGNAL(overFlowChanged(bool)), m_mainTabBarWidget, SLOT(overFlowChanged(bool))); m_mainTabBar->setActiveTabBar(true); m_pinnedTabBar->setTabsClosable(false); m_leftLayout = new QHBoxLayout; m_leftLayout->setSpacing(0); m_leftLayout->setContentsMargins(0, 0, 0, 0); m_leftContainer = new QWidget(this); m_leftContainer->setLayout(m_leftLayout); m_rightLayout = new QHBoxLayout; m_rightLayout->setSpacing(0); m_rightLayout->setContentsMargins(0, 0, 0, 0); m_rightContainer = new QWidget(this); m_rightContainer->setLayout(m_rightLayout); m_mainLayout = new QHBoxLayout; m_mainLayout->setSpacing(0); m_mainLayout->setContentsMargins(0, 0, 0, 0); m_mainLayout->addWidget(m_leftContainer); m_mainLayout->addWidget(m_pinnedTabBarWidget); m_mainLayout->addWidget(m_mainTabBarWidget); m_mainLayout->addWidget(m_rightContainer); setLayout(m_mainLayout); connect(m_mainTabBar, &QTabBar::currentChanged, this, &ComboTabBar::slotCurrentChanged); connect(m_mainTabBar, &QTabBar::tabCloseRequested, this, &ComboTabBar::slotTabCloseRequested); connect(m_mainTabBar, &QTabBar::tabMoved, this, &ComboTabBar::slotTabMoved); connect(m_pinnedTabBar, &QTabBar::currentChanged, this, &ComboTabBar::slotCurrentChanged); connect(m_pinnedTabBar, &QTabBar::tabCloseRequested, this, &ComboTabBar::slotTabCloseRequested); connect(m_pinnedTabBar, &QTabBar::tabMoved, this, &ComboTabBar::slotTabMoved); setAutoFillBackground(false); m_mainTabBar->setAutoFillBackground(false); m_pinnedTabBar->setAutoFillBackground(false); m_mainTabBar->installEventFilter(this); m_pinnedTabBar->installEventFilter(this); m_leftContainer->installEventFilter(this); m_rightContainer->installEventFilter(this); m_mainTabBarWidget->installEventFilter(this); m_pinnedTabBarWidget->installEventFilter(this); } int ComboTabBar::addTab(const QString &text) { return insertTab(-1, text); } int ComboTabBar::addTab(const QIcon &icon, const QString &text) { return insertTab(-1, icon, text); } int ComboTabBar::insertTab(int index, const QString &text) { return insertTab(index, QIcon(), text); } int ComboTabBar::insertTab(int index, const QIcon &icon, const QString &text, bool pinned) { if (pinned) { index = m_pinnedTabBar->insertTab(index, icon, text); } else { index = m_mainTabBar->insertTab(index - pinnedTabsCount(), icon, text); if (tabsClosable()) { QWidget* closeButton = m_mainTabBar->tabButton(index, closeButtonPosition()); if ((closeButton && closeButton->objectName() != QLatin1String("combotabbar_tabs_close_button")) || !closeButton) { // insert our close button insertCloseButton(index + pinnedTabsCount()); if (closeButton) { closeButton->deleteLater(); } } } index += pinnedTabsCount(); } updatePinnedTabBarVisibility(); tabInserted(index); setMinimumWidths(); return index; } void ComboTabBar::removeTab(int index) { if (validIndex(index)) { setUpdatesEnabled(false); localTabBar(index)->removeTab(toLocalIndex(index)); updatePinnedTabBarVisibility(); tabRemoved(index); setMinimumWidths(); setUpdatesEnabled(true); updateTabBars(); } } void ComboTabBar::moveTab(int from, int to) { if (from >= pinnedTabsCount() && to >= pinnedTabsCount()) { m_mainTabBar->moveTab(from - pinnedTabsCount(), to - pinnedTabsCount()); } else if (from < pinnedTabsCount() && to < pinnedTabsCount()) { m_pinnedTabBar->moveTab(from, to); } } bool ComboTabBar::isTabEnabled(int index) const { return localTabBar(index)->isTabEnabled(toLocalIndex(index)); } void ComboTabBar::setTabEnabled(int index, bool enabled) { localTabBar(index)->setTabEnabled(toLocalIndex(index), enabled); } QColor ComboTabBar::tabTextColor(int index) const { return localTabBar(index)->tabTextColor(toLocalIndex(index)); } void ComboTabBar::setTabTextColor(int index, const QColor &color) { localTabBar(index)->setTabTextColor(toLocalIndex(index), color); } QRect ComboTabBar::tabRect(int index) const { return mapFromLocalTabRect(localTabBar(index)->tabRect(toLocalIndex(index)), localTabBar(index)); } QRect ComboTabBar::draggedTabRect() const { const QRect r = m_pinnedTabBar->draggedTabRect(); if (r.isValid()) { return mapFromLocalTabRect(r, m_pinnedTabBar); } return mapFromLocalTabRect(m_mainTabBar->draggedTabRect(), m_mainTabBar); } QPixmap ComboTabBar::tabPixmap(int index) const { return localTabBar(index)->tabPixmap(toLocalIndex(index)); } int ComboTabBar::tabAt(const QPoint &pos) const { QWidget* w = QApplication::widgetAt(mapToGlobal(pos)); if (!qobject_cast(w) && !qobject_cast(w) && !qobject_cast(w)) return -1; if (m_pinnedTabBarWidget->geometry().contains(pos)) { return m_pinnedTabBarWidget->tabAt(m_pinnedTabBarWidget->mapFromParent(pos)); } else if (m_mainTabBarWidget->geometry().contains(pos)) { int index = m_mainTabBarWidget->tabAt(m_mainTabBarWidget->mapFromParent(pos)); if (index != -1) index += pinnedTabsCount(); return index; } return -1; } bool ComboTabBar::emptyArea(const QPoint &pos) const { if (tabAt(pos) != -1) return false; return qobject_cast(QApplication::widgetAt(mapToGlobal(pos))); } int ComboTabBar::mainTabBarCurrentIndex() const { return (m_mainTabBar->currentIndex() == -1 ? -1 : pinnedTabsCount() + m_mainTabBar->currentIndex()); } int ComboTabBar::currentIndex() const { if (m_pinnedTabBar->isActiveTabBar()) { return m_pinnedTabBar->currentIndex(); } else { return (m_mainTabBar->currentIndex() == -1 ? -1 : pinnedTabsCount() + m_mainTabBar->currentIndex()); } } void ComboTabBar::setCurrentIndex(int index) { return localTabBar(index)->setCurrentIndex(toLocalIndex(index)); } void ComboTabBar::slotCurrentChanged(int index) { if (m_blockCurrentChangedSignal) { return; } if (sender() == m_pinnedTabBar) { if (index == -1 && m_mainTabBar->count() > 0) { m_mainTabBar->setActiveTabBar(true); m_pinnedTabBar->setActiveTabBar(false); emit currentChanged(pinnedTabsCount()); } else { m_pinnedTabBar->setActiveTabBar(true); m_mainTabBar->setActiveTabBar(false); emit currentChanged(index); } } else { if (index == -1 && pinnedTabsCount() > 0) { m_pinnedTabBar->setActiveTabBar(true); m_mainTabBar->setActiveTabBar(false); emit currentChanged(pinnedTabsCount() - 1); } else { m_mainTabBar->setActiveTabBar(true); m_pinnedTabBar->setActiveTabBar(false); emit currentChanged(index + pinnedTabsCount()); } } } void ComboTabBar::slotTabCloseRequested(int index) { if (sender() == m_pinnedTabBar) { emit tabCloseRequested(index); } else { emit tabCloseRequested(index + pinnedTabsCount()); } } void ComboTabBar::slotTabMoved(int from, int to) { if (sender() == m_pinnedTabBar) { emit tabMoved(from, to); } else { emit tabMoved(from + pinnedTabsCount(), to + pinnedTabsCount()); } } void ComboTabBar::closeTabFromButton() { QWidget* button = qobject_cast(sender()); int tabToClose = -1; for (int i = 0; i < m_mainTabBar->count(); ++i) { if (m_mainTabBar->tabButton(i, closeButtonPosition()) == button) { tabToClose = i; break; } } if (tabToClose != -1) { emit tabCloseRequested(tabToClose + pinnedTabsCount()); } } void ComboTabBar::updateTabBars() { m_mainTabBar->update(); m_pinnedTabBar->update(); } void ComboTabBar::emitOverFlowChanged() { if (m_mainBarOverFlowed != m_lastAppliedOverflow) { emit overFlowChanged(m_mainBarOverFlowed); m_lastAppliedOverflow = m_mainBarOverFlowed; } } int ComboTabBar::count() const { return pinnedTabsCount() + m_mainTabBar->count(); } void ComboTabBar::setDrawBase(bool drawTheBase) { m_mainTabBar->setDrawBase(drawTheBase); m_pinnedTabBar->setDrawBase(drawTheBase); } bool ComboTabBar::drawBase() const { return m_mainTabBar->drawBase(); } Qt::TextElideMode ComboTabBar::elideMode() const { return m_mainTabBar->elideMode(); } void ComboTabBar::setElideMode(Qt::TextElideMode elide) { m_mainTabBar->setElideMode(elide); m_pinnedTabBar->setElideMode(elide); } QString ComboTabBar::tabText(int index) const { return localTabBar(index)->tabText(toLocalIndex(index)); } void ComboTabBar::setTabText(int index, const QString &text) { localTabBar(index)->setTabText(toLocalIndex(index), text); } void ComboTabBar::setTabToolTip(int index, const QString &tip) { localTabBar(index)->setTabToolTip(toLocalIndex(index), tip); } QString ComboTabBar::tabToolTip(int index) const { return localTabBar(index)->tabToolTip(toLocalIndex(index)); } bool ComboTabBar::tabsClosable() const { return m_mainTabBar->tabsClosable(); } void ComboTabBar::setTabsClosable(bool closable) { if (closable == tabsClosable()) { return; } if (closable) { // insert our close button for (int i = 0; i < m_mainTabBar->count(); ++i) { QWidget* closeButton = m_mainTabBar->tabButton(i, closeButtonPosition()); if (closeButton) { if (closeButton->objectName() == QLatin1String("combotabbar_tabs_close_button")) { continue; } } insertCloseButton(i + pinnedTabsCount()); if (closeButton) { closeButton->deleteLater(); } } } m_mainTabBar->setTabsClosable(closable); } void ComboTabBar::setTabButton(int index, QTabBar::ButtonPosition position, QWidget* widget) { if (widget) widget->setMinimumSize(closeButtonSize()); localTabBar(index)->setTabButton(toLocalIndex(index), position, widget); } QWidget* ComboTabBar::tabButton(int index, QTabBar::ButtonPosition position) const { return localTabBar(index)->tabButton(toLocalIndex(index), position); } QTabBar::SelectionBehavior ComboTabBar::selectionBehaviorOnRemove() const { return m_mainTabBar->selectionBehaviorOnRemove(); } void ComboTabBar::setSelectionBehaviorOnRemove(QTabBar::SelectionBehavior behavior) { m_mainTabBar->setSelectionBehaviorOnRemove(behavior); m_pinnedTabBar->setSelectionBehaviorOnRemove(behavior); } bool ComboTabBar::expanding() const { return m_mainTabBar->expanding(); } void ComboTabBar::setExpanding(bool enabled) { m_mainTabBar->setExpanding(enabled); m_pinnedTabBar->setExpanding(enabled); } bool ComboTabBar::isMovable() const { return m_mainTabBar->isMovable(); } void ComboTabBar::setMovable(bool movable) { m_mainTabBar->setMovable(movable); m_pinnedTabBar->setMovable(movable); } bool ComboTabBar::documentMode() const { return m_mainTabBar->documentMode(); } void ComboTabBar::setDocumentMode(bool set) { m_mainTabBar->setDocumentMode(set); m_pinnedTabBar->setDocumentMode(set); } int ComboTabBar::pinnedTabsCount() const { return m_pinnedTabBar->count(); } int ComboTabBar::normalTabsCount() const { return m_mainTabBar->count(); } bool ComboTabBar::isPinned(int index) const { return index >= 0 && index < pinnedTabsCount(); } void ComboTabBar::setFocusPolicy(Qt::FocusPolicy policy) { QWidget::setFocusPolicy(policy); m_mainTabBar->setFocusPolicy(policy); m_pinnedTabBar->setFocusPolicy(policy); } void ComboTabBar::setObjectName(const QString &name) { m_mainTabBar->setObjectName(name); m_pinnedTabBar->setObjectName(name); } void ComboTabBar::setMouseTracking(bool enable) { m_mainTabBarWidget->scrollArea()->setMouseTracking(enable); m_mainTabBarWidget->setMouseTracking(enable); m_mainTabBar->setMouseTracking(enable); m_pinnedTabBarWidget->scrollArea()->setMouseTracking(enable); m_pinnedTabBarWidget->setMouseTracking(enable); m_pinnedTabBar->setMouseTracking(enable); QWidget::setMouseTracking(enable); } void ComboTabBar::setUpLayout() { int height = qMax(m_mainTabBar->height(), m_pinnedTabBar->height()); if (height < 1) { height = qMax(m_mainTabBar->sizeHint().height(), m_pinnedTabBar->sizeHint().height()); } // We need to setup heights even before m_mainTabBar->height() has correct value // So lets just set minimum 5px height height = qMax(5, height); setFixedHeight(height); m_leftContainer->setFixedHeight(height); m_rightContainer->setFixedHeight(height); m_mainTabBarWidget->setUpLayout(); m_pinnedTabBarWidget->setUpLayout(); setMinimumWidths(); if (isVisible() && height > 5) { // ComboTabBar is now visible, we can sync heights of both tabbars m_mainTabBar->setFixedHeight(height); m_pinnedTabBar->setFixedHeight(height); } } void ComboTabBar::insertCloseButton(int index) { index -= pinnedTabsCount(); if (index < 0) { return; } QAbstractButton* closeButton = new CloseButton(this); closeButton->setFixedSize(closeButtonSize()); closeButton->setToolTip(m_closeButtonsToolTip); connect(closeButton, &QAbstractButton::clicked, this, &ComboTabBar::closeTabFromButton); m_mainTabBar->setTabButton(index, closeButtonPosition(), closeButton); } void ComboTabBar::setCloseButtonsToolTip(const QString &tip) { m_closeButtonsToolTip = tip; } int ComboTabBar::mainTabBarWidth() const { return m_mainTabBar->width(); } int ComboTabBar::pinTabBarWidth() const { return m_pinnedTabBarWidget->isHidden() ? 0 : m_pinnedTabBarWidget->width(); } bool ComboTabBar::event(QEvent *event) { const bool res = QWidget::event(event); switch (event->type()) { case QEvent::ToolTip: if (!isDragInProgress() && !isScrollInProgress()) { int index = tabAt(mapFromGlobal(QCursor::pos())); if (index >= 0) QToolTip::showText(QCursor::pos(), tabToolTip(index)); } break; case QEvent::Resize: ensureVisible(); break; case QEvent::Show: if (!event->spontaneous()) QTimer::singleShot(0, this, &ComboTabBar::setUpLayout); break; case QEvent::Enter: case QEvent::Leave: // Make sure tabs are painted with correct mouseover state QTimer::singleShot(100, this, &ComboTabBar::updateTabBars); break; default: break; } return res; } void ComboTabBar::wheelEvent(QWheelEvent* event) { event->accept(); if (qzSettings->alwaysSwitchTabsWithWheel || (!m_mainTabBarWidget->isOverflowed() && !m_pinnedTabBarWidget->isOverflowed())) { m_wheelHelper.processEvent(event); while (WheelHelper::Direction direction = m_wheelHelper.takeDirection()) { switch (direction) { case WheelHelper::WheelUp: case WheelHelper::WheelLeft: setCurrentNextEnabledIndex(-1); break; case WheelHelper::WheelDown: case WheelHelper::WheelRight: setCurrentNextEnabledIndex(1); break; default: break; } } return; } if (m_mainTabBarWidget->underMouse()) { if (m_mainTabBarWidget->isOverflowed()) { m_mainTabBarWidget->scrollByWheel(event); } else if (m_pinnedTabBarWidget->isOverflowed()) { m_pinnedTabBarWidget->scrollByWheel(event); } } else if (m_pinnedTabBarWidget->underMouse()) { if (m_pinnedTabBarWidget->isOverflowed()) { m_pinnedTabBarWidget->scrollByWheel(event); } else if (m_mainTabBarWidget->isOverflowed()) { m_mainTabBarWidget->scrollByWheel(event); } } } bool ComboTabBar::eventFilter(QObject* obj, QEvent* ev) { if (obj == m_mainTabBar && ev->type() == QEvent::Resize) { QResizeEvent* event = static_cast(ev); if (event->oldSize().height() != event->size().height()) { setUpLayout(); } } // Handle wheel events exclusively in ComboTabBar if (ev->type() == QEvent::Wheel) { wheelEvent(static_cast(ev)); return true; } return QWidget::eventFilter(obj, ev); } void ComboTabBar::paintEvent(QPaintEvent* ev) { Q_UNUSED(ev); // This is needed to apply style sheets QStyleOption option; option.init(this); QPainter p(this); style()->drawPrimitive(QStyle::PE_Widget, &option, &p, this); #ifndef Q_OS_MACOS // Draw tabbar base even on parts of ComboTabBar that are not directly QTabBar QStyleOptionTabBarBase opt; TabBarHelper::initStyleBaseOption(&opt, m_mainTabBar, size()); // Left container opt.rect.setX(m_leftContainer->x()); opt.rect.setWidth(m_leftContainer->width()); style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p); // Right container opt.rect.setX(m_rightContainer->x()); opt.rect.setWidth(m_rightContainer->width()); style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p); if (m_mainBarOverFlowed) { const int scrollButtonWidth = m_mainTabBarWidget->scrollButtonsWidth(); // Left scroll button opt.rect.setX(m_mainTabBarWidget->x()); opt.rect.setWidth(scrollButtonWidth); style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p); // Right scroll button opt.rect.setX(m_mainTabBarWidget->x() + m_mainTabBarWidget->width() - scrollButtonWidth); opt.rect.setWidth(scrollButtonWidth); style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p); } // Draw base even when main tabbar is empty if (normalTabsCount() == 0) { opt.rect.setX(m_mainTabBarWidget->x()); opt.rect.setWidth(m_mainTabBarWidget->width()); style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p); } #endif } int ComboTabBar::comboTabBarPixelMetric(ComboTabBar::SizeType sizeType) const { switch (sizeType) { case ExtraReservedWidth: return 0; case NormalTabMaximumWidth: return 150; case ActiveTabMinimumWidth: case NormalTabMinimumWidth: case OverflowedTabWidth: return 100; case PinnedTabWidth: return 30; default: break; } return -1; } QTabBar::ButtonPosition ComboTabBar::iconButtonPosition() const { return (closeButtonPosition() == QTabBar::RightSide ? QTabBar::LeftSide : QTabBar::RightSide); } QTabBar::ButtonPosition ComboTabBar::closeButtonPosition() const { return (QTabBar::ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, m_mainTabBar); } QSize ComboTabBar::iconButtonSize() const { QSize s = closeButtonSize(); s.setWidth(qMax(16, s.width())); s.setHeight(qMax(16, s.height())); return s; } QSize ComboTabBar::closeButtonSize() const { int width = style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, 0, this); int height = style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, 0, this); return QSize(width, height); } bool ComboTabBar::validIndex(int index) const { return (index >= 0 && index < count()); } void ComboTabBar::setCurrentNextEnabledIndex(int offset) { for (int index = currentIndex() + offset; validIndex(index); index += offset) { if (isTabEnabled(index)) { setCurrentIndex(index); break; } } } bool ComboTabBar::usesScrollButtons() const { return m_mainTabBarWidget->usesScrollButtons(); } void ComboTabBar::setUsesScrollButtons(bool useButtons) { m_mainTabBarWidget->setUsesScrollButtons(useButtons); } void ComboTabBar::showDropIndicator(int index, DropIndicatorPosition position) { clearDropIndicator(); localTabBar(index)->showDropIndicator(toLocalIndex(index), position); } void ComboTabBar::clearDropIndicator() { m_mainTabBar->clearDropIndicator(); m_pinnedTabBar->clearDropIndicator(); } bool ComboTabBar::isDragInProgress() const { return m_mainTabBar->isDragInProgress() || m_pinnedTabBar->isDragInProgress(); } bool ComboTabBar::isScrollInProgress() const { return m_mainTabBarWidget->scrollBar()->isScrolling() || m_pinnedTabBarWidget->scrollBar()->isScrolling(); } bool ComboTabBar::isMainBarOverflowed() const { return m_mainBarOverFlowed; } int ComboTabBar::cornerWidth(Qt::Corner corner) const { if (corner == Qt::TopLeftCorner) { return m_leftContainer->width(); } else if (corner == Qt::TopRightCorner) { return m_rightContainer->width(); } qFatal("ComboTabBar::cornerWidth Only TopLeft and TopRight corners are implemented!"); return -1; } void ComboTabBar::addCornerWidget(QWidget* widget, Qt::Corner corner) { if (corner == Qt::TopLeftCorner) { m_leftLayout->addWidget(widget); } else if (corner == Qt::TopRightCorner) { m_rightLayout->addWidget(widget); } else { qFatal("ComboTabBar::addCornerWidget Only TopLeft and TopRight corners are implemented!"); } } // static int ComboTabBar::slideAnimationDuration() { // taken from qtabbar_p.h return 250; } void ComboTabBar::ensureVisible(int index, int xmargin) { if (index == -1) { index = currentIndex(); } if (index < pinnedTabsCount()) { if (xmargin == -1) { xmargin = qMax(20, comboTabBarPixelMetric(PinnedTabWidth)); } m_pinnedTabBarWidget->ensureVisible(index, xmargin); } else { if (xmargin == -1) { xmargin = comboTabBarPixelMetric(OverflowedTabWidth); } index -= pinnedTabsCount(); m_mainTabBarWidget->ensureVisible(index, xmargin); } } QSize ComboTabBar::tabSizeHint(int index, bool fast) const { Q_UNUSED(fast) return localTabBar(index)->baseClassTabSizeHint(toLocalIndex(index)); } void ComboTabBar::tabInserted(int index) { Q_UNUSED(index) } void ComboTabBar::tabRemoved(int index) { Q_UNUSED(index) } TabBarHelper* ComboTabBar::mainTabBar() const { return m_mainTabBar; } TabBarHelper* ComboTabBar::localTabBar(int index) const { if (index < 0 || index >= pinnedTabsCount()) { return m_mainTabBar; } else { return m_pinnedTabBar; } } int ComboTabBar::toLocalIndex(int globalIndex) const { if (globalIndex < 0) { return -1; } if (globalIndex >= pinnedTabsCount()) { return globalIndex - pinnedTabsCount(); } else { return globalIndex; } } QRect ComboTabBar::mapFromLocalTabRect(const QRect &rect, QWidget *tabBar) const { if (!rect.isValid()) { return rect; } QRect r = rect; if (tabBar == m_mainTabBar) { r.moveLeft(r.x() + mapFromGlobal(m_mainTabBar->mapToGlobal(QPoint(0, 0))).x()); QRect widgetRect = m_mainTabBarWidget->scrollArea()->viewport()->rect(); widgetRect.moveLeft(widgetRect.x() + mapFromGlobal(m_mainTabBarWidget->scrollArea()->viewport()->mapToGlobal(QPoint(0, 0))).x()); r = r.intersected(widgetRect); } else { r.moveLeft(r.x() + mapFromGlobal(m_pinnedTabBar->mapToGlobal(QPoint(0, 0))).x()); QRect widgetRect = m_pinnedTabBarWidget->scrollArea()->viewport()->rect(); widgetRect.moveLeft(widgetRect.x() + mapFromGlobal(m_pinnedTabBarWidget->scrollArea()->viewport()->mapToGlobal(QPoint(0, 0))).x()); r = r.intersected(widgetRect); } return r; } void ComboTabBar::updatePinnedTabBarVisibility() { m_pinnedTabBarWidget->setVisible(pinnedTabsCount() > 0); } void ComboTabBar::setMinimumWidths() { if (!isVisible() || comboTabBarPixelMetric(PinnedTabWidth) < 0) { return; } const int tabBarsSpacing = 3; // To distinguish tabbars int pinnedTabBarWidth = pinnedTabsCount() * comboTabBarPixelMetric(PinnedTabWidth); m_pinnedTabBar->setMinimumWidth(pinnedTabBarWidth); m_pinnedTabBarWidget->setFixedWidth(pinnedTabBarWidth + tabBarsSpacing); // Width that is needed by main tabbar int mainTabBarWidth = comboTabBarPixelMetric(NormalTabMinimumWidth) * (m_mainTabBar->count() - 1) + comboTabBarPixelMetric(ActiveTabMinimumWidth) + comboTabBarPixelMetric(ExtraReservedWidth); // This is the full width that would be needed for the tabbar (including pinned tabbar and corner widgets) int realTabBarWidth = mainTabBarWidth + m_pinnedTabBarWidget->width() + cornerWidth(Qt::TopLeftCorner) + cornerWidth(Qt::TopRightCorner); // Does it fit in our widget? if (realTabBarWidth <= width()) { if (m_mainBarOverFlowed) { m_mainBarOverFlowed = false; QTimer::singleShot(0, this, &ComboTabBar::emitOverFlowChanged); } m_mainTabBar->useFastTabSizeHint(false); m_mainTabBar->setMinimumWidth(mainTabBarWidth); } else { if (!m_mainBarOverFlowed) { m_mainBarOverFlowed = true; QTimer::singleShot(0, this, &ComboTabBar::emitOverFlowChanged); } // All tabs have now same width, we can use fast tabSizeHint m_mainTabBar->useFastTabSizeHint(true); m_mainTabBar->setMinimumWidth(m_mainTabBar->count() * comboTabBarPixelMetric(OverflowedTabWidth)); } } TabBarHelper::TabBarHelper(bool isPinnedTabBar, ComboTabBar* comboTabBar) : QTabBar(comboTabBar) , m_comboTabBar(comboTabBar) , m_scrollArea(0) , m_pressedIndex(-1) , m_dragInProgress(false) , m_activeTabBar(false) , m_isPinnedTabBar(isPinnedTabBar) , m_useFastTabSizeHint(false) { } int TabBarHelper::tabPadding() const { return m_tabPadding; } void TabBarHelper::setTabPadding(int padding) { m_tabPadding = padding; } QColor TabBarHelper::baseColor() const { return m_baseColor; } void TabBarHelper::setBaseColor(const QColor &color) { m_baseColor = color; } void TabBarHelper::setTabButton(int index, QTabBar::ButtonPosition position, QWidget* widget) { QTabBar::setTabButton(index, position, widget); } QSize TabBarHelper::tabSizeHint(int index) const { if (this == m_comboTabBar->mainTabBar()) { index += m_comboTabBar->pinnedTabsCount(); } return m_comboTabBar->tabSizeHint(index, m_useFastTabSizeHint); } QSize TabBarHelper::baseClassTabSizeHint(int index) const { return QTabBar::tabSizeHint(index); } QRect TabBarHelper::draggedTabRect() const { if (!m_dragInProgress) { return QRect(); } QStyleOptionTab tab; initStyleOption(&tab, m_pressedIndex); const int tabDragOffset = dragOffset(&tab, m_pressedIndex); if (tabDragOffset != 0) { tab.rect.moveLeft(tab.rect.x() + tabDragOffset); } return tab.rect; } QPixmap TabBarHelper::tabPixmap(int index) const { QStyleOptionTab tab; initStyleOption(&tab, index); tab.state &= ~QStyle::State_MouseOver; tab.position = QStyleOptionTab::OnlyOneTab; tab.leftButtonSize = QSize(); tab.rightButtonSize = QSize(); QWidget *iconButton = tabButton(index, m_comboTabBar->iconButtonPosition()); QWidget *closeButton = tabButton(index, m_comboTabBar->closeButtonPosition()); if (iconButton) { const QPixmap pix = iconButton->grab(); if (!pix.isNull()) { tab.icon = pix; tab.iconSize = pix.size() / pix.devicePixelRatioF(); } } if (closeButton) { +#if QTGUI_VERSION >= QT_VERSION_CHECK(5, 11, 0) + const int width = tab.fontMetrics.horizontalAdvance(tab.text) + closeButton->width(); +#else const int width = tab.fontMetrics.width(tab.text) + closeButton->width(); +#endif tab.text = tab.fontMetrics.elidedText(tabText(index), Qt::ElideRight, width); } QPixmap out(tab.rect.size() * devicePixelRatioF()); out.setDevicePixelRatio(devicePixelRatioF()); out.fill(Qt::transparent); tab.rect = QRect(QPoint(0, 0), tab.rect.size()); QPainter p(&out); style()->drawControl(QStyle::CE_TabBarTab, &tab, &p, this); p.end(); return out; } bool TabBarHelper::isActiveTabBar() { return m_activeTabBar; } void TabBarHelper::setActiveTabBar(bool activate) { if (m_activeTabBar != activate) { m_activeTabBar = activate; // If the last tab in a tabbar is closed, the selection jumps to the other // tabbar. The stacked widget automatically selects the next tab, which is // either the last tab in pinned tabbar or the first one in main tabbar. if (!m_activeTabBar) { m_comboTabBar->m_blockCurrentChangedSignal = true; setCurrentIndex(m_isPinnedTabBar ? count() - 1 : 0); m_comboTabBar->m_blockCurrentChangedSignal = false; } update(); } } void TabBarHelper::removeTab(int index) { // Removing tab in inactive tabbar will change current index and thus // changing active tabbar, which is really not wanted. // Also removing tab will cause a duplicate call to ComboTabBar::slotCurrentChanged() m_comboTabBar->m_blockCurrentChangedSignal = true; QTabBar::removeTab(index); m_comboTabBar->m_blockCurrentChangedSignal = false; } void TabBarHelper::setScrollArea(QScrollArea* scrollArea) { m_scrollArea = scrollArea; } void TabBarHelper::useFastTabSizeHint(bool enabled) { m_useFastTabSizeHint = enabled; } void TabBarHelper::showDropIndicator(int index, ComboTabBar::DropIndicatorPosition position) { m_dropIndicatorIndex = index; m_dropIndicatorPosition = position; update(); } void TabBarHelper::clearDropIndicator() { m_dropIndicatorIndex = -1; update(); } bool TabBarHelper::isDisplayedOnViewPort(int globalLeft, int globalRight) { bool isVisible = true; if (m_scrollArea) { if (globalRight < m_scrollArea->viewport()->mapToGlobal(QPoint(0, 0)).x() || globalLeft > m_scrollArea->viewport()->mapToGlobal(m_scrollArea->viewport()->rect().topRight()).x() ) { isVisible = false; } } return isVisible; } bool TabBarHelper::isDragInProgress() const { return m_dragInProgress; } void TabBarHelper::setCurrentIndex(int index) { if (index == currentIndex() && !m_activeTabBar) { emit currentChanged(currentIndex()); } QTabBar::setCurrentIndex(index); } bool TabBarHelper::event(QEvent* ev) { switch (ev->type()) { case QEvent::ToolTip: ev->ignore(); return false; default: break; } QTabBar::event(ev); ev->ignore(); return false; } // Hack to get dragOffset from QTabBar internals int TabBarHelper::dragOffset(QStyleOptionTab *option, int tabIndex) const { QRect rect; QWidget *button = tabButton(tabIndex, QTabBar::LeftSide); if (button) { rect = style()->subElementRect(QStyle::SE_TabBarTabLeftButton, option, this); } if (!rect.isValid()) { button = tabButton(tabIndex, QTabBar::RightSide); rect = style()->subElementRect(QStyle::SE_TabBarTabRightButton, option, this); } if (!button || !rect.isValid()) { return 0; } return button->pos().x() - rect.topLeft().x(); } // Taken from qtabbar.cpp void TabBarHelper::initStyleBaseOption(QStyleOptionTabBarBase *optTabBase, QTabBar* tabbar, QSize size) { QStyleOptionTab tabOverlap; tabOverlap.shape = tabbar->shape(); int overlap = tabbar->style()->pixelMetric(QStyle::PM_TabBarBaseOverlap, &tabOverlap, tabbar); QWidget* theParent = tabbar->parentWidget(); optTabBase->init(tabbar); optTabBase->shape = tabbar->shape(); optTabBase->documentMode = tabbar->documentMode(); if (theParent && overlap > 0) { QRect rect; switch (tabOverlap.shape) { case QTabBar::RoundedNorth: case QTabBar::TriangularNorth: rect.setRect(0, size.height() - overlap, size.width(), overlap); break; case QTabBar::RoundedSouth: case QTabBar::TriangularSouth: rect.setRect(0, 0, size.width(), overlap); break; case QTabBar::RoundedEast: case QTabBar::TriangularEast: rect.setRect(0, 0, overlap, size.height()); break; case QTabBar::RoundedWest: case QTabBar::TriangularWest: rect.setRect(size.width() - overlap, 0, overlap, size.height()); break; } optTabBase->rect = rect; } } // Adapted from qtabbar.cpp // Note: doesn't support vertical tabs void TabBarHelper::paintEvent(QPaintEvent *) { QStyleOptionTabBarBase optTabBase; initStyleBaseOption(&optTabBase, this, size()); QStylePainter p(this); int selected = currentIndex(); for (int i = 0; i < count(); ++i) { optTabBase.tabBarRect |= tabRect(i); } if (m_activeTabBar) { optTabBase.selectedTabRect = tabRect(selected); } if (drawBase()) { p.drawPrimitive(QStyle::PE_FrameTabBarBase, optTabBase); } const QPoint cursorPos = QCursor::pos(); int indexUnderMouse = isDisplayedOnViewPort(cursorPos.x(), cursorPos.x()) ? tabAt(mapFromGlobal(cursorPos)) : -1; for (int i = 0; i < count(); ++i) { if (i == selected) { continue; } QStyleOptionTab tab; initStyleOption(&tab, i); const int tabDragOffset = dragOffset(&tab, i); if (tabDragOffset != 0) { tab.rect.moveLeft(tab.rect.x() + tabDragOffset); } // Don't bother drawing a tab if the entire tab is outside of the visible tab bar. if (!isDisplayedOnViewPort(mapToGlobal(tab.rect.topLeft()).x(), mapToGlobal(tab.rect.topRight()).x())) { continue; } if (!m_activeTabBar) { tab.selectedPosition = QStyleOptionTab::NotAdjacent; } if (!(tab.state & QStyle::State_Enabled)) { tab.palette.setCurrentColorGroup(QPalette::Disabled); } // Update mouseover state when scrolling if (!m_dragInProgress && i == indexUnderMouse) { tab.state |= QStyle::State_MouseOver; } else { tab.state &= ~QStyle::State_MouseOver; } p.drawControl(QStyle::CE_TabBarTab, tab); } // Draw the selected tab last to get it "on top" if (selected >= 0) { QStyleOptionTab tab; initStyleOption(&tab, selected); const int tabDragOffset = dragOffset(&tab, selected); if (tabDragOffset != 0) { tab.rect.moveLeft(tab.rect.x() + tabDragOffset); } // Update mouseover state when scrolling if (selected == indexUnderMouse) { tab.state |= QStyle::State_MouseOver; } else { tab.state &= ~QStyle::State_MouseOver; } if (!m_activeTabBar) { // If this is inactive tab, we still need to draw selected tab outside the tabbar // Some themes (eg. Oxygen) draws line under tabs with selected tab // Let's just move it outside rect(), it appears to work QStyleOptionTab tb = tab; tb.rect.moveRight((rect().x() + rect().width()) * 2); p.drawControl(QStyle::CE_TabBarTab, tb); // Draw the tab without selected state tab.state = tab.state & ~QStyle::State_Selected; } if (!m_movingTab || !m_movingTab->isVisible()) { p.drawControl(QStyle::CE_TabBarTab, tab); } else { int taboverlap = style()->pixelMetric(QStyle::PM_TabBarTabOverlap, nullptr, this); m_movingTab->setGeometry(tab.rect.adjusted(-taboverlap, 0, taboverlap, 0)); QRect grabRect = tabRect(selected); grabRect.adjust(-taboverlap, 0, taboverlap, 0); QPixmap grabImage(grabRect.size() * devicePixelRatioF()); grabImage.setDevicePixelRatio(devicePixelRatioF()); grabImage.fill(Qt::transparent); QStylePainter p(&grabImage, this); p.initFrom(this); if (tabDragOffset != 0) { tab.position = QStyleOptionTab::OnlyOneTab; } tab.rect.moveTopLeft(QPoint(taboverlap, 0)); p.drawControl(QStyle::CE_TabBarTab, tab); m_movingTab->m_pixmap = grabImage; m_movingTab->update(); } } // Draw drop indicator if (m_dropIndicatorIndex != -1) { const QRect tr = tabRect(m_dropIndicatorIndex); QRect r; if (m_dropIndicatorPosition == ComboTabBar::BeforeTab) { r = QRect(qMax(0, tr.left() - 1), tr.top(), 3, tr.height()); } else { const int rightOffset = m_dropIndicatorIndex == count() - 1 ? -2 : 0; r = QRect(tr.right() + rightOffset, tr.top(), 3, tr.height()); } QzTools::paintDropIndicator(this, r); } } void TabBarHelper::mousePressEvent(QMouseEvent* event) { event->ignore(); if (event->buttons() == Qt::LeftButton) { m_pressedIndex = tabAt(event->pos()); if (m_pressedIndex != -1) { m_dragStartPosition = event->pos(); // virtualize selecting tab by click if (m_pressedIndex == currentIndex() && !m_activeTabBar) { emit currentChanged(currentIndex()); } } } QTabBar::mousePressEvent(event); } void TabBarHelper::mouseMoveEvent(QMouseEvent *event) { if (!m_dragInProgress && m_pressedIndex != -1) { if ((event->pos() - m_dragStartPosition).manhattanLength() > QApplication::startDragDistance()) { m_dragInProgress = true; } } QTabBar::mouseMoveEvent(event); // Hack to find QMovableTabWidget if (m_dragInProgress && !m_movingTab) { const auto objects = children(); const int taboverlap = style()->pixelMetric(QStyle::PM_TabBarTabOverlap, nullptr, this); QRect grabRect = tabRect(currentIndex()); grabRect.adjust(-taboverlap, 0, taboverlap, 0); for (QObject *object : objects) { QWidget *widget = qobject_cast(object); if (widget && widget->geometry() == grabRect) { m_movingTab = static_cast(widget); break; } } } // Don't allow to move tabs outside of tabbar if (m_dragInProgress && m_movingTab) { // FIXME: This doesn't work at all with RTL... if (isRightToLeft()) { return; } QRect r = tabRect(m_pressedIndex); r.moveLeft(r.x() + (event->pos().x() - m_dragStartPosition.x())); bool sendEvent = false; int diff = r.topRight().x() - tabRect(count() - 1).topRight().x(); if (diff > 0) { sendEvent = true; } else { diff = r.topLeft().x() - tabRect(0).topLeft().x(); if (diff < 0) { sendEvent = true; } } if (sendEvent) { QPoint pos = event->pos(); pos.setX(pos.x() - diff); QMouseEvent ev(event->type(), pos, event->button(), event->buttons(), event->modifiers()); QTabBar::mouseMoveEvent(&ev); } } } void TabBarHelper::mouseReleaseEvent(QMouseEvent* event) { event->ignore(); if (event->button() == Qt::LeftButton) { m_pressedIndex = -1; m_dragInProgress = false; m_dragStartPosition = QPoint(); } QTabBar::mouseReleaseEvent(event); update(); } void TabBarHelper::initStyleOption(QStyleOptionTab* option, int tabIndex) const { QTabBar::initStyleOption(option, tabIndex); // Workaround zero padding when tabs are styled using style sheets if (m_tabPadding) { const QRect textRect = style()->subElementRect(QStyle::SE_TabBarTabText, option, this); const int width = textRect.width() - 2 * m_tabPadding; option->text = option->fontMetrics.elidedText(tabText(tabIndex), elideMode(), width, Qt::TextShowMnemonic); } // Bespin doesn't highlight current tab when there is only one tab in tabbar static int isBespin = -1; if (isBespin == -1) isBespin = mApp->styleName() == QL1S("bespin"); if (!isBespin) return; int index = m_isPinnedTabBar ? tabIndex : m_comboTabBar->pinnedTabsCount() + tabIndex; if (m_comboTabBar->count() > 1) { if (index == 0) option->position = QStyleOptionTab::Beginning; else if (index == m_comboTabBar->count() - 1) option->position = QStyleOptionTab::End; else option->position = QStyleOptionTab::Middle; } else { option->position = QStyleOptionTab::OnlyOneTab; } } TabScrollBar::TabScrollBar(QWidget* parent) : QScrollBar(Qt::Horizontal, parent) { m_animation = new QPropertyAnimation(this, "value", this); } TabScrollBar::~TabScrollBar() { } bool TabScrollBar::isScrolling() const { return m_animation->state() == QPropertyAnimation::Running; } void TabScrollBar::animateToValue(int to, QEasingCurve::Type type) { to = qBound(minimum(), to, maximum()); int length = qAbs(to - value()); int duration = qMin(1500, 200 + length / 2); m_animation->stop(); m_animation->setEasingCurve(type); m_animation->setDuration(duration); m_animation->setStartValue(value()); m_animation->setEndValue(to); m_animation->start(); } TabBarScrollWidget::TabBarScrollWidget(QTabBar* tabBar, QWidget* parent) : QWidget(parent) , m_tabBar(tabBar) , m_usesScrollButtons(false) , m_totalDeltas(0) { m_scrollArea = new QScrollArea(this); m_scrollArea->setFocusPolicy(Qt::NoFocus); m_scrollArea->setFrameStyle(QFrame::NoFrame); m_scrollArea->setWidgetResizable(true); m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_scrollBar = new TabScrollBar(m_scrollArea); m_scrollArea->setHorizontalScrollBar(m_scrollBar); m_scrollArea->setWidget(m_tabBar); m_leftScrollButton = new ToolButton(this); m_leftScrollButton->setFocusPolicy(Qt::NoFocus); m_leftScrollButton->setAutoRaise(true); m_leftScrollButton->setObjectName("tabbar-button-left"); m_leftScrollButton->setAutoRepeat(true); m_leftScrollButton->setAutoRepeatDelay(200); m_leftScrollButton->setAutoRepeatInterval(200); connect(m_leftScrollButton, &QAbstractButton::pressed, this, &TabBarScrollWidget::scrollStart); connect(m_leftScrollButton, &ToolButton::doubleClicked, this, &TabBarScrollWidget::scrollToLeftEdge); connect(m_leftScrollButton, SIGNAL(middleMouseClicked()), this, SLOT(ensureVisible())); m_rightScrollButton = new ToolButton(this); m_rightScrollButton->setFocusPolicy(Qt::NoFocus); m_rightScrollButton->setAutoRaise(true); m_rightScrollButton->setObjectName("tabbar-button-right"); m_rightScrollButton->setAutoRepeat(true); m_rightScrollButton->setAutoRepeatDelay(200); m_rightScrollButton->setAutoRepeatInterval(200); connect(m_rightScrollButton, &QAbstractButton::pressed, this, &TabBarScrollWidget::scrollStart); connect(m_rightScrollButton, &ToolButton::doubleClicked, this, &TabBarScrollWidget::scrollToRightEdge); connect(m_rightScrollButton, SIGNAL(middleMouseClicked()), this, SLOT(ensureVisible())); QHBoxLayout* hLayout = new QHBoxLayout; hLayout->setSpacing(0); hLayout->setContentsMargins(0, 0, 0, 0); hLayout->addWidget(m_leftScrollButton); hLayout->addWidget(m_scrollArea); hLayout->addWidget(m_rightScrollButton); setLayout(hLayout); m_scrollArea->viewport()->setAutoFillBackground(false); connect(m_scrollBar, &QAbstractSlider::valueChanged, this, &TabBarScrollWidget::updateScrollButtonsState); updateScrollButtonsState(); overFlowChanged(false); } QTabBar* TabBarScrollWidget::tabBar() { return m_tabBar; } QScrollArea* TabBarScrollWidget::scrollArea() { return m_scrollArea; } TabScrollBar* TabBarScrollWidget::scrollBar() { return m_scrollBar; } void TabBarScrollWidget::ensureVisible(int index, int xmargin) { if (index == -1) { index = m_tabBar->currentIndex(); } if (index < 0 || index >= m_tabBar->count()) { return; } xmargin = qMin(xmargin, m_scrollArea->viewport()->width() / 2); // Qt Bug? the following lines were taken from QScrollArea::ensureVisible() and // then were fixed. The original version caculates wrong values in RTL layouts. const QRect logicalTabRect = QStyle::visualRect(m_tabBar->layoutDirection(), m_tabBar->rect(), m_tabBar->tabRect(index)); int logicalX = QStyle::visualPos(Qt::LeftToRight, m_scrollArea->viewport()->rect(), logicalTabRect.center()).x(); if (logicalX - xmargin < m_scrollBar->value()) { m_scrollBar->animateToValue(qMax(0, logicalX - xmargin)); } else if (logicalX > m_scrollBar->value() + m_scrollArea->viewport()->width() - xmargin) { m_scrollBar->animateToValue(qMin(logicalX - m_scrollArea->viewport()->width() + xmargin, m_scrollBar->maximum())); } } void TabBarScrollWidget::scrollToLeft(int n, QEasingCurve::Type type) { n = qMax(1, n); m_scrollBar->animateToValue(m_scrollBar->value() - n * m_scrollBar->singleStep(), type); } void TabBarScrollWidget::scrollToRight(int n, QEasingCurve::Type type) { n = qMax(1, n); m_scrollBar->animateToValue(m_scrollBar->value() + n * m_scrollBar->singleStep(), type); } void TabBarScrollWidget::scrollToLeftEdge() { m_scrollBar->animateToValue(m_scrollBar->minimum()); } void TabBarScrollWidget::scrollToRightEdge() { m_scrollBar->animateToValue(m_scrollBar->maximum()); } void TabBarScrollWidget::setUpLayout() { const int height = m_tabBar->height(); setFixedHeight(height); } void TabBarScrollWidget::updateScrollButtonsState() { m_leftScrollButton->setEnabled(m_scrollBar->value() != m_scrollBar->minimum()); m_rightScrollButton->setEnabled(m_scrollBar->value() != m_scrollBar->maximum()); } void TabBarScrollWidget::overFlowChanged(bool overflowed) { bool showScrollButtons = overflowed && m_usesScrollButtons; m_leftScrollButton->setVisible(showScrollButtons); m_rightScrollButton->setVisible(showScrollButtons); } void TabBarScrollWidget::scrollStart() { bool ctrlModifier = QApplication::keyboardModifiers() & Qt::ControlModifier; if (sender() == m_leftScrollButton) { if (ctrlModifier) { scrollToLeftEdge(); } else { scrollToLeft(5, QEasingCurve::Linear); } } else if (sender() == m_rightScrollButton) { if (ctrlModifier) { scrollToRightEdge(); } else { scrollToRight(5, QEasingCurve::Linear); } } } void TabBarScrollWidget::scrollByWheel(QWheelEvent* event) { event->accept(); // Check if direction has changed from last time if (m_totalDeltas * event->delta() < 0) { m_totalDeltas = 0; } m_totalDeltas += event->delta(); // Slower scrolling for horizontal wheel scrolling if (event->orientation() == Qt::Horizontal) { if (event->delta() > 0) { scrollToLeft(); } else if (event->delta() < 0) { scrollToRight(); } return; } // Faster scrolling with control modifier if (event->orientation() == Qt::Vertical && event->modifiers() == Qt::ControlModifier) { if (event->delta() > 0) { scrollToLeft(10); } else if (event->delta() < 0) { scrollToRight(10); } return; } // Fast scrolling with just wheel scroll int factor = qMax(qRound(m_scrollBar->pageStep() / 1.5), m_scrollBar->singleStep()); if ((event->modifiers() & Qt::ControlModifier) || (event->modifiers() & Qt::ShiftModifier)) { factor = m_scrollBar->pageStep(); } int offset = (m_totalDeltas / 120) * factor; if (offset != 0) { if (isRightToLeft()) { m_scrollBar->animateToValue(m_scrollBar->value() + offset); } else { m_scrollBar->animateToValue(m_scrollBar->value() - offset); } m_totalDeltas -= (offset / factor) * 120; } } int TabBarScrollWidget::scrollButtonsWidth() const { // Assumes both buttons have the same width return m_leftScrollButton->width(); } bool TabBarScrollWidget::usesScrollButtons() const { return m_usesScrollButtons; } void TabBarScrollWidget::setUsesScrollButtons(bool useButtons) { if (useButtons != m_usesScrollButtons) { m_usesScrollButtons = useButtons; updateScrollButtonsState(); m_tabBar->setElideMode(m_tabBar->elideMode()); } } bool TabBarScrollWidget::isOverflowed() const { return m_tabBar->count() > 0 && m_scrollBar->minimum() != m_scrollBar->maximum(); } int TabBarScrollWidget::tabAt(const QPoint &pos) const { if (m_leftScrollButton->isVisible() && (m_leftScrollButton->rect().contains(pos) || m_rightScrollButton->rect().contains(pos))) { return -1; } return m_tabBar->tabAt(m_tabBar->mapFromGlobal(mapToGlobal(pos))); } void TabBarScrollWidget::mouseMoveEvent(QMouseEvent* event) { event->ignore(); } void TabBarScrollWidget::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); updateScrollButtonsState(); } CloseButton::CloseButton(QWidget* parent) : QAbstractButton(parent) { setObjectName("combotabbar_tabs_close_button"); setFocusPolicy(Qt::NoFocus); setCursor(Qt::ArrowCursor); resize(sizeHint()); } QSize CloseButton::sizeHint() const { ensurePolished(); int width = style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, nullptr, this); int height = style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, nullptr, this); return QSize(width, height); } void CloseButton::enterEvent(QEvent* event) { if (isEnabled()) { update(); } QAbstractButton::enterEvent(event); } void CloseButton::leaveEvent(QEvent* event) { if (isEnabled()) { update(); } QAbstractButton::leaveEvent(event); } void CloseButton::paintEvent(QPaintEvent*) { QPainter p(this); QStyleOption opt; opt.init(this); opt.state |= QStyle::State_AutoRaise; // update raised state on scrolling bool isUnderMouse = rect().contains(mapFromGlobal(QCursor::pos())); if (isEnabled() && isUnderMouse && !isChecked() && !isDown()) { opt.state |= QStyle::State_Raised; } if (isChecked()) { opt.state |= QStyle::State_On; } if (isDown()) { opt.state |= QStyle::State_Sunken; } if (TabBarHelper* tb = qobject_cast(parent())) { int index = tb->currentIndex(); QTabBar::ButtonPosition closeSide = (QTabBar::ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, tb); if (tb->tabButton(index, closeSide) == this && tb->isActiveTabBar()) { opt.state |= QStyle::State_Selected; } } style()->drawPrimitive(QStyle::PE_IndicatorTabClose, &opt, &p, this); } diff --git a/src/lib/tools/listitemdelegate.cpp b/src/lib/tools/listitemdelegate.cpp index 94dbd6f4..234b5fa8 100644 --- a/src/lib/tools/listitemdelegate.cpp +++ b/src/lib/tools/listitemdelegate.cpp @@ -1,131 +1,135 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2010-2017 David Rosca * * This program 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 3 of the License, or * (at your option) any later version. * * This program 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 this program. If not, see . * ============================================================ */ #include "listitemdelegate.h" #include "mainapplication.h" #include "proxystyle.h" #include #include +#include ListItemDelegate::ListItemDelegate(int iconSize, QWidget* parent) : QStyledItemDelegate(parent) , m_iconSize(iconSize) , m_updateParentHeight(false) , m_uniformItemSizes(false) , m_itemHeight(0) , m_itemWidth(0) , m_padding(0) { } void ListItemDelegate::setUpdateParentHeight(bool update) { m_updateParentHeight = update; } void ListItemDelegate::setUniformItemSizes(bool uniform) { m_uniformItemSizes = uniform; } int ListItemDelegate::itemHeight() const { return m_itemHeight; } void ListItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); const QWidget* w = opt.widget; const QStyle* style = w ? w->style() : QApplication::style(); const Qt::LayoutDirection direction = w ? w->layoutDirection() : QApplication::layoutDirection(); const QPalette::ColorRole colorRole = opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text; QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) { cg = QPalette::Inactive; } #ifdef Q_OS_WIN opt.palette.setColor(QPalette::All, QPalette::HighlightedText, opt.palette.color(QPalette::Active, QPalette::Text)); opt.palette.setColor(QPalette::All, QPalette::Highlight, opt.palette.base().color().darker(108)); #endif QPalette textPalette = opt.palette; textPalette.setCurrentColorGroup(cg); int topPosition = opt.rect.top() + m_padding; // Draw background opt.showDecorationSelected = true; style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, w); // Draw icon QRect iconRect(opt.rect.left() + (opt.rect.width() - m_iconSize) / 2, topPosition, m_iconSize, m_iconSize); QRect visualIconRect = style->visualRect(direction, opt.rect, iconRect); QPixmap pixmap = index.data(Qt::DecorationRole).value().pixmap(m_iconSize); painter->drawPixmap(visualIconRect, pixmap); topPosition += m_iconSize + m_padding; // Draw title const QString title = index.data(Qt::DisplayRole).toString(); const int leftTitleEdge = opt.rect.left() + m_padding; QRect titleRect(leftTitleEdge, topPosition, opt.rect.width() - 2 * m_padding, opt.fontMetrics.height()); QRect visualTitleRect = style->visualRect(direction, opt.rect, titleRect); style->drawItemText(painter, visualTitleRect, Qt::AlignCenter, textPalette, true, title, colorRole); } QSize ListItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!m_itemHeight) { QStyleOptionViewItem opt(option); initStyleOption(&opt, index); const QWidget* w = opt.widget; const QStyle* style = w ? w->style() : QApplication::style(); const int padding = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0) + 1; m_padding = padding > 5 ? padding : 5; m_itemHeight = 3 * m_padding + opt.fontMetrics.height() + m_iconSize; // Update height of parent widget QWidget* p = qobject_cast(parent()); if (p && m_updateParentHeight) { int frameWidth = p->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, p); p->setFixedHeight(m_itemHeight + 2 * frameWidth); } } - +#if QTGUI_VERSION >= QT_VERSION_CHECK(5, 11, 0) + int width = 2 * m_padding + option.fontMetrics.horizontalAdvance(index.data(Qt::DisplayRole).toString()); +#else int width = 2 * m_padding + option.fontMetrics.width(index.data(Qt::DisplayRole).toString()); +#endif width = width > (m_iconSize + 2 * m_padding) ? width : m_iconSize + 2 * m_padding; if (m_uniformItemSizes) { if (width > m_itemWidth) { m_itemWidth = width; } else { width = m_itemWidth; } } return QSize(width, m_itemHeight); } diff --git a/src/lib/tools/qztools.cpp b/src/lib/tools/qztools.cpp index a24f0774..4f05084a 100644 --- a/src/lib/tools/qztools.cpp +++ b/src/lib/tools/qztools.cpp @@ -1,1021 +1,1026 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2010-2018 David Rosca * * This program 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 3 of the License, or * (at your option) any later version. * * This program 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 this program. If not, see . * ============================================================ */ #include "qztools.h" #include "datapaths.h" #include "settings.h" #include "mainapplication.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #ifdef QZ_WS_X11 #include #include #endif #ifdef Q_OS_WIN #include #else #include #endif #ifdef Q_OS_MACOS #include #endif QByteArray QzTools::pixmapToByteArray(const QPixmap &pix) { QByteArray bytes; QBuffer buffer(&bytes); buffer.open(QIODevice::WriteOnly); if (pix.save(&buffer, "PNG")) { return buffer.buffer().toBase64(); } return QByteArray(); } QPixmap QzTools::pixmapFromByteArray(const QByteArray &data) { QPixmap image; QByteArray bArray = QByteArray::fromBase64(data); image.loadFromData(bArray); return image; } QUrl QzTools::pixmapToDataUrl(const QPixmap &pix) { const QString data(pixmapToByteArray(pix)); return data.isEmpty() ? QUrl() : QUrl(QSL("data:image/png;base64,") + data); } QPixmap QzTools::dpiAwarePixmap(const QString &path) { const QIcon icon(path); if (icon.availableSizes().isEmpty()) { return QPixmap(path); } return icon.pixmap(icon.availableSizes().at(0)); } QString QzTools::readAllFileContents(const QString &filename) { return QString::fromUtf8(readAllFileByteContents(filename)); } QByteArray QzTools::readAllFileByteContents(const QString &filename) { QFile file(filename); if (!filename.isEmpty() && file.open(QFile::ReadOnly)) { const QByteArray a = file.readAll(); file.close(); return a; } return QByteArray(); } void QzTools::centerWidgetOnScreen(QWidget* w) { const QRect screen = QApplication::desktop()->screenGeometry(w); const QRect size = w->geometry(); w->move((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2); } // Very, very, very simplified QDialog::adjustPosition from qdialog.cpp void QzTools::centerWidgetToParent(QWidget* w, QWidget* parent) { if (!parent || !w) { return; } QPoint p; parent = parent->window(); QPoint pp = parent->mapToGlobal(QPoint(0, 0)); p = QPoint(pp.x() + parent->width() / 2, pp.y() + parent->height() / 2); p = QPoint(p.x() - w->width() / 2, p.y() - w->height() / 2 - 20); w->move(p); } bool QzTools::removeRecursively(const QString &filePath) { const QFileInfo fileInfo(filePath); if (!fileInfo.exists() && !fileInfo.isSymLink()) { return true; } if (fileInfo.isDir() && !fileInfo.isSymLink()) { QDir dir(filePath); dir = dir.canonicalPath(); if (dir.isRoot() || dir.path() == QDir::home().canonicalPath()) { qCritical() << "Attempt to remove root/home directory" << dir; return false; } const QStringList fileNames = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); for (const QString &fileName : fileNames) { if (!removeRecursively(filePath + QLatin1Char('/') + fileName)) { return false; } } if (!QDir::root().rmdir(dir.path())) { return false; } } else if (!QFile::remove(filePath)) { return false; } return true; } bool QzTools::copyRecursively(const QString &sourcePath, const QString &targetPath) { const QFileInfo srcFileInfo(sourcePath); if (srcFileInfo.isDir() && !srcFileInfo.isSymLink()) { QDir targetDir(targetPath); targetDir.cdUp(); if (!targetDir.mkdir(QFileInfo(targetPath).fileName())) { return false; } const QStringList fileNames = QDir(sourcePath).entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); for (const QString &fileName : fileNames) { const QString newSourcePath = sourcePath + QL1C('/') + fileName; const QString newTargetPath = targetPath + QL1C('/') + fileName; if (!copyRecursively(newSourcePath, newTargetPath)) { return false; } } #ifndef Q_OS_WIN } else if (srcFileInfo.isSymLink()) { const QByteArray pathData = sourcePath.toLocal8Bit(); char buf[1024]; ssize_t len = readlink(pathData.constData(), buf, sizeof(buf) - 1); if (len < 0) { qWarning() << "Error getting symlink path" << pathData; return false; } buf[len] = '\0'; return QFile::link(QString::fromLocal8Bit(buf), targetPath); #endif } else if (!QFile::copy(sourcePath, targetPath)) { return false; } return true; } /* Finds same part of @one in @other from the beginning */ QString QzTools::samePartOfStrings(const QString &one, const QString &other) { int maxSize = qMin(one.size(), other.size()); if (maxSize <= 0) { return QString(); } int i = 0; while (one.at(i) == other.at(i)) { i++; if (i == maxSize) { break; } } return one.left(i); } QString QzTools::urlEncodeQueryString(const QUrl &url) { QString returnString = url.toString(QUrl::RemoveQuery | QUrl::RemoveFragment); if (url.hasQuery()) { returnString += QLatin1Char('?') + url.query(QUrl::FullyEncoded); } if (url.hasFragment()) { returnString += QLatin1Char('#') + url.fragment(QUrl::FullyEncoded); } returnString.replace(QLatin1Char(' '), QLatin1String("%20")); return returnString; } QString QzTools::fromPunycode(const QString &str) { if (!str.startsWith(QL1S("xn--"))) return str; // QUrl::fromAce will only decode domains from idn whitelist const QString decoded = QUrl::fromAce(str.toUtf8() + QByteArray(".org")); return decoded.left(decoded.size() - 4); } QString QzTools::escapeSqlGlobString(QString urlString) { urlString.replace(QL1C('['), QStringLiteral("[[")); urlString.replace(QL1C(']'), QStringLiteral("[]]")); urlString.replace(QStringLiteral("[["), QStringLiteral("[[]")); urlString.replace(QL1C('*'), QStringLiteral("[*]")); urlString.replace(QL1C('?'), QStringLiteral("[?]")); return urlString; } QString QzTools::ensureUniqueFilename(const QString &name, const QString &appendFormat) { Q_ASSERT(appendFormat.contains(QL1S("%1"))); QFileInfo info(name); if (!info.exists()) return name; const QDir dir = info.absoluteDir(); const QString fileName = info.fileName(); int i = 1; while (info.exists()) { QString file = fileName; int index = file.lastIndexOf(QL1C('.')); const QString appendString = appendFormat.arg(i); if (index == -1) file.append(appendString); else file = file.left(index) + appendString + file.mid(index); info.setFile(dir, file); i++; } return info.absoluteFilePath(); } QString QzTools::getFileNameFromUrl(const QUrl &url) { QString fileName = url.toString(QUrl::RemoveFragment | QUrl::RemoveQuery | QUrl::RemoveScheme | QUrl::RemovePort); if (fileName.endsWith(QLatin1Char('/'))) { fileName = fileName.mid(0, fileName.length() - 1); } if (fileName.indexOf(QLatin1Char('/')) != -1) { int pos = fileName.lastIndexOf(QLatin1Char('/')); fileName = fileName.mid(pos); fileName.remove(QLatin1Char('/')); } fileName = filterCharsFromFilename(fileName); if (fileName.isEmpty()) { fileName = filterCharsFromFilename(url.host()); } return fileName; } QString QzTools::filterCharsFromFilename(const QString &name) { QString value = name; value.replace(QLatin1Char('/'), QLatin1Char('-')); value.remove(QLatin1Char('\\')); value.remove(QLatin1Char(':')); value.remove(QLatin1Char('*')); value.remove(QLatin1Char('?')); value.remove(QLatin1Char('"')); value.remove(QLatin1Char('<')); value.remove(QLatin1Char('>')); value.remove(QLatin1Char('|')); return value; } QString QzTools::lastPathForFileDialog(const QString &dialogName, const QString &fallbackPath) { Settings settings; settings.beginGroup("LastFileDialogsPaths"); QString path = settings.value("FileDialogs/" + dialogName).toString(); settings.endGroup(); return path.isEmpty() ? fallbackPath : path; } void QzTools::saveLastPathForFileDialog(const QString &dialogName, const QString &path) { if (path.isEmpty()) { return; } Settings settings; settings.beginGroup("LastFileDialogsPaths"); settings.setValue(dialogName, path); settings.endGroup(); } QString QzTools::alignTextToWidth(const QString &string, const QString &text, const QFontMetrics &metrics, int width) { int pos = 0; QString returnString; while (pos <= string.size()) { QString part = string.mid(pos); QString elidedLine = metrics.elidedText(part, Qt::ElideRight, width); if (elidedLine.isEmpty()) { break; } if (elidedLine.size() != part.size()) { elidedLine = elidedLine.left(elidedLine.size() - 3); } if (!returnString.isEmpty()) { returnString += text; } returnString += elidedLine; pos += elidedLine.size(); } return returnString; } QString QzTools::fileSizeToString(qint64 size) { if (size < 0) { return QObject::tr("Unknown size"); } double _size = size / 1024.0; // KB if (_size < 1000) { return QString::number(_size > 1 ? _size : 1, 'f', 0) + QLatin1Char(' ') + QObject::tr("KB"); } _size /= 1024; // MB if (_size < 1000) { return QString::number(_size, 'f', 1) + QLatin1Char(' ') + QObject::tr("MB"); } _size /= 1024; // GB return QString::number(_size, 'f', 2) + QLatin1Char(' ') + QObject::tr("GB"); } QPixmap QzTools::createPixmapForSite(const QIcon &icon, const QString &title, const QString &url) { const QFontMetrics fontMetrics = QApplication::fontMetrics(); const int padding = 4; +#if QTGUI_VERSION >= QT_VERSION_CHECK(5, 11, 0) + const int maxWidth = fontMetrics.horizontalAdvance(title.length() > url.length() ? title : url) + 3 * padding + 16; +#else const int maxWidth = fontMetrics.width(title.length() > url.length() ? title : url) + 3 * padding + 16; +#endif const int width = qMin(maxWidth, 150); const int height = fontMetrics.height() * 2 + fontMetrics.leading() + 2 * padding; QPixmap pixmap(width * qApp->devicePixelRatio(), height * qApp->devicePixelRatio()); pixmap.setDevicePixelRatio(qApp->devicePixelRatio()); QPainter painter(&pixmap); painter.setRenderHint(QPainter::Antialiasing); // Draw background QPen pen(Qt::black); pen.setWidth(1); painter.setPen(pen); QPainterPath path; path.addRect(QRect(0, 0, width, height)); painter.fillPath(path, Qt::white); painter.drawPath(path); // Draw icon QRect iconRect(padding, 0, 16, height); icon.paint(&painter, iconRect); // Draw title QRect titleRect(iconRect.right() + padding, padding, width - padding - iconRect.right(), fontMetrics.height()); painter.drawText(titleRect, fontMetrics.elidedText(title, Qt::ElideRight, titleRect.width())); // Draw url QRect urlRect(titleRect.x(), titleRect.bottom() + fontMetrics.leading(), titleRect.width(), titleRect.height()); painter.setPen(QApplication::palette().color(QPalette::Link)); painter.drawText(urlRect, fontMetrics.elidedText(url, Qt::ElideRight, urlRect.width())); return pixmap; } QString QzTools::applyDirectionToPage(QString &pageContents) { QString direction = QLatin1String("ltr"); QString right_str = QLatin1String("right"); QString left_str = QLatin1String("left"); if (QApplication::isRightToLeft()) { direction = QLatin1String("rtl"); right_str = QLatin1String("left"); left_str = QLatin1String("right"); } pageContents.replace(QLatin1String("%DIRECTION%"), direction); pageContents.replace(QLatin1String("%RIGHT_STR%"), right_str); pageContents.replace(QLatin1String("%LEFT_STR%"), left_str); return pageContents; } QString QzTools::truncatedText(const QString &text, int size) { if (text.length() > size) { return text.left(size) + QL1S(".."); } return text; } // Thanks to http://www.qtcentre.org/threads/3205-Toplevel-widget-with-rounded-corners?p=17492#post17492 QRegion QzTools::roundedRect(const QRect &rect, int radius) { QRegion region; // middle and borders region += rect.adjusted(radius, 0, -radius, 0); region += rect.adjusted(0, radius, 0, -radius); // top left QRect corner(rect.topLeft(), QSize(radius * 2, radius * 2)); region += QRegion(corner, QRegion::Ellipse); // top right corner.moveTopRight(rect.topRight()); region += QRegion(corner, QRegion::Ellipse); // bottom left corner.moveBottomLeft(rect.bottomLeft()); region += QRegion(corner, QRegion::Ellipse); // bottom right corner.moveBottomRight(rect.bottomRight()); region += QRegion(corner, QRegion::Ellipse); return region; } QIcon QzTools::iconFromFileName(const QString &fileName) { static QHash iconCache; QFileInfo tempInfo(fileName); if (iconCache.contains(tempInfo.suffix())) { return iconCache.value(tempInfo.suffix()); } QFileIconProvider iconProvider; QTemporaryFile tempFile(DataPaths::path(DataPaths::Temp) + "/XXXXXX." + tempInfo.suffix()); tempFile.open(); tempInfo.setFile(tempFile.fileName()); QIcon icon(iconProvider.icon(tempInfo)); iconCache.insert(tempInfo.suffix(), icon); return icon; } QString QzTools::resolveFromPath(const QString &name) { const QString path = qgetenv("PATH").trimmed(); if (path.isEmpty()) { return QString(); } const QStringList dirs = path.split(QLatin1Char(':'), QString::SkipEmptyParts); for (const QString &dir : dirs) { QDir d(dir); if (d.exists(name)) { return d.absoluteFilePath(name); } } return QString(); } // http://stackoverflow.com/questions/1031645/how-to-detect-utf-8-in-plain-c bool QzTools::isUtf8(const char* string) { if (!string) { return 0; } const unsigned char* bytes = (const unsigned char*)string; while (*bytes) { if ((// ASCII bytes[0] == 0x09 || bytes[0] == 0x0A || bytes[0] == 0x0D || (0x20 <= bytes[0] && bytes[0] <= 0x7F) ) ) { bytes += 1; continue; } if ((// non-overlong 2-byte (0xC2 <= bytes[0] && bytes[0] <= 0xDF) && (0x80 <= bytes[1] && bytes[1] <= 0xBF) ) ) { bytes += 2; continue; } if ((// excluding overlongs bytes[0] == 0xE0 && (0xA0 <= bytes[1] && bytes[1] <= 0xBF) && (0x80 <= bytes[2] && bytes[2] <= 0xBF) ) || (// straight 3-byte ((0xE1 <= bytes[0] && bytes[0] <= 0xEC) || bytes[0] == 0xEE || bytes[0] == 0xEF) && (0x80 <= bytes[1] && bytes[1] <= 0xBF) && (0x80 <= bytes[2] && bytes[2] <= 0xBF) ) || (// excluding surrogates bytes[0] == 0xED && (0x80 <= bytes[1] && bytes[1] <= 0x9F) && (0x80 <= bytes[2] && bytes[2] <= 0xBF) ) ) { bytes += 3; continue; } if ((// planes 1-3 bytes[0] == 0xF0 && (0x90 <= bytes[1] && bytes[1] <= 0xBF) && (0x80 <= bytes[2] && bytes[2] <= 0xBF) && (0x80 <= bytes[3] && bytes[3] <= 0xBF) ) || (// planes 4-15 (0xF1 <= bytes[0] && bytes[0] <= 0xF3) && (0x80 <= bytes[1] && bytes[1] <= 0xBF) && (0x80 <= bytes[2] && bytes[2] <= 0xBF) && (0x80 <= bytes[3] && bytes[3] <= 0xBF) ) || (// plane 16 bytes[0] == 0xF4 && (0x80 <= bytes[1] && bytes[1] <= 0x8F) && (0x80 <= bytes[2] && bytes[2] <= 0xBF) && (0x80 <= bytes[3] && bytes[3] <= 0xBF) ) ) { bytes += 4; continue; } return false; } return true; } bool QzTools::containsSpace(const QString &str) { for (const QChar &c : str) { if (c.isSpace()) return true; } return false; } QString QzTools::getExistingDirectory(const QString &name, QWidget* parent, const QString &caption, const QString &dir, QFileDialog::Options options) { Settings settings; settings.beginGroup("FileDialogPaths"); QString lastDir = settings.value(name, dir).toString(); QString path = QFileDialog::getExistingDirectory(parent, caption, lastDir, options); if (!path.isEmpty()) { settings.setValue(name, QFileInfo(path).absolutePath()); } settings.endGroup(); return path; } static QString getFilename(const QString &path) { QFileInfo info(path); if (info.isFile()) { return info.fileName(); } if (info.isDir()) { return QString(); } if (info.dir().exists()) { return info.fileName(); } return QString(); } QString QzTools::getOpenFileName(const QString &name, QWidget* parent, const QString &caption, const QString &dir, const QString &filter, QString* selectedFilter, QFileDialog::Options options) { Settings settings; settings.beginGroup("FileDialogPaths"); QString lastDir = settings.value(name, QString()).toString(); QString fileName = getFilename(dir); if (lastDir.isEmpty()) { lastDir = dir; } else { lastDir.append(QDir::separator() + fileName); } QString path = QFileDialog::getOpenFileName(parent, caption, lastDir, filter, selectedFilter, options); if (!path.isEmpty()) { settings.setValue(name, QFileInfo(path).absolutePath()); } settings.endGroup(); return path; } QStringList QzTools::getOpenFileNames(const QString &name, QWidget* parent, const QString &caption, const QString &dir, const QString &filter, QString* selectedFilter, QFileDialog::Options options) { Settings settings; settings.beginGroup("FileDialogPaths"); QString lastDir = settings.value(name, QString()).toString(); QString fileName = getFilename(dir); if (lastDir.isEmpty()) { lastDir = dir; } else { lastDir.append(QDir::separator() + fileName); } QStringList paths = QFileDialog::getOpenFileNames(parent, caption, lastDir, filter, selectedFilter, options); if (!paths.isEmpty()) { settings.setValue(name, QFileInfo(paths.at(0)).absolutePath()); } settings.endGroup(); return paths; } QString QzTools::getSaveFileName(const QString &name, QWidget* parent, const QString &caption, const QString &dir, const QString &filter, QString* selectedFilter, QFileDialog::Options options) { Settings settings; settings.beginGroup("FileDialogPaths"); QString lastDir = settings.value(name, QString()).toString(); QString fileName = getFilename(dir); if (lastDir.isEmpty()) { lastDir = dir; } else { lastDir.append(QDir::separator() + fileName); } QString path = QFileDialog::getSaveFileName(parent, caption, lastDir, filter, selectedFilter, options); if (!path.isEmpty()) { settings.setValue(name, QFileInfo(path).absolutePath()); } settings.endGroup(); return path; } // Matches domain (assumes both pattern and domain not starting with dot) // pattern = domain to be matched // domain = site domain bool QzTools::matchDomain(const QString &pattern, const QString &domain) { if (pattern == domain) { return true; } if (!domain.endsWith(pattern)) { return false; } int index = domain.indexOf(pattern); return index > 0 && domain[index - 1] == QLatin1Char('.'); } QKeySequence QzTools::actionShortcut(const QKeySequence &shortcut, const QKeySequence &fallBack, const QKeySequence &shortcutRtl, const QKeySequence &fallbackRtl) { if (QApplication::isRightToLeft() && (!shortcutRtl.isEmpty() || !fallbackRtl.isEmpty())) return shortcutRtl.isEmpty() ? fallbackRtl : shortcutRtl; return shortcut.isEmpty() ? fallBack : shortcut; } static inline bool isQuote(const QChar &c) { return (c == QLatin1Char('"') || c == QLatin1Char('\'')); } // Function splits command line into arguments // eg. /usr/bin/foo -o test -b "bar bar" -s="sed sed" // => '/usr/bin/foo' '-o' 'test' '-b' 'bar bar' '-s=sed sed' QStringList QzTools::splitCommandArguments(const QString &command) { QString line = command.trimmed(); if (line.isEmpty()) { return QStringList(); } QChar SPACE(' '); QChar EQUAL('='); QChar BSLASH('\\'); QChar QUOTE('"'); QStringList r; int equalPos = -1; // Position of = in opt="value" int startPos = isQuote(line.at(0)) ? 1 : 0; bool inWord = !isQuote(line.at(0)); bool inQuote = !inWord; if (inQuote) { QUOTE = line.at(0); } const int strlen = line.length(); for (int i = 0; i < strlen; ++i) { const QChar c = line.at(i); if (inQuote && c == QUOTE && i > 0 && line.at(i - 1) != BSLASH) { QString str = line.mid(startPos, i - startPos); if (equalPos > -1) { str.remove(equalPos - startPos + 1, 1); } inQuote = false; if (!str.isEmpty()) { r.append(str); } continue; } else if (!inQuote && isQuote(c)) { inQuote = true; QUOTE = c; if (!inWord) { startPos = i + 1; } else if (i > 0 && line.at(i - 1) == EQUAL) { equalPos = i - 1; } } if (inQuote) { continue; } if (inWord && (c == SPACE || i == strlen - 1)) { int len = (i == strlen - 1) ? -1 : i - startPos; const QString str = line.mid(startPos, len); inWord = false; if (!str.isEmpty()) { r.append(str); } } else if (!inWord && c != SPACE) { inWord = true; startPos = i; } } // Unmatched quote if (inQuote) { return QStringList(); } return r; } bool QzTools::startExternalProcess(const QString &executable, const QString &args) { const QStringList arguments = splitCommandArguments(args); bool success = QProcess::startDetached(executable, arguments); if (!success) { QString info = "
  • %1%2
  • %3%4
"; info = info.arg(QObject::tr("Executable: "), executable, QObject::tr("Arguments: "), arguments.join(QLatin1Char(' '))); QMessageBox::critical(0, QObject::tr("Cannot start external program"), QObject::tr("Cannot start external program! %1").arg(info)); } return success; } void QzTools::setWmClass(const QString &name, const QWidget* widget) { #ifdef QZ_WS_X11 if (QGuiApplication::platformName() != QL1S("xcb")) return; const QByteArray nameData = name.toUtf8(); const QByteArray classData = mApp->wmClass().isEmpty() ? QByteArrayLiteral("Falkon") : mApp->wmClass(); uint32_t class_len = nameData.length() + 1 + classData.length() + 1; char *class_hint = (char*) malloc(class_len); qstrcpy(class_hint, nameData.constData()); qstrcpy(class_hint + nameData.length() + 1, classData.constData()); xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, widget->winId(), XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, class_len, class_hint); free(class_hint); #else Q_UNUSED(name) Q_UNUSED(widget) #endif } QString QzTools::operatingSystem() { #ifdef Q_OS_MACOS QString str = "Mac OS X"; SInt32 majorVersion; SInt32 minorVersion; if (Gestalt(gestaltSystemVersionMajor, &majorVersion) == noErr && Gestalt(gestaltSystemVersionMinor, &minorVersion) == noErr) { str.append(QString(" %1.%2").arg(majorVersion).arg(minorVersion)); } return str; #endif #ifdef Q_OS_LINUX return "Linux"; #endif #ifdef Q_OS_BSD4 return "BSD 4.4"; #endif #ifdef Q_OS_BSDI return "BSD/OS"; #endif #ifdef Q_OS_FREEBSD return "FreeBSD"; #endif #ifdef Q_OS_HPUX return "HP-UX"; #endif #ifdef Q_OS_HURD return "GNU Hurd"; #endif #ifdef Q_OS_LYNX return "LynxOS"; #endif #ifdef Q_OS_NETBSD return "NetBSD"; #endif #ifdef Q_OS_OS2 return "OS/2"; #endif #ifdef Q_OS_OPENBSD return "OpenBSD"; #endif #ifdef Q_OS_OSF return "HP Tru64 UNIX"; #endif #ifdef Q_OS_SOLARIS return "Sun Solaris"; #endif #ifdef Q_OS_UNIXWARE return "UnixWare 7 / Open UNIX 8"; #endif #ifdef Q_OS_UNIX return "Unix"; #endif #ifdef Q_OS_HAIKU return "Haiku"; #endif #ifdef Q_OS_WIN32 QString str = "Windows"; switch (QSysInfo::windowsVersion()) { case QSysInfo::WV_NT: str.append(" NT"); break; case QSysInfo::WV_2000: str.append(" 2000"); break; case QSysInfo::WV_XP: str.append(" XP"); break; case QSysInfo::WV_2003: str.append(" XP Pro x64"); break; case QSysInfo::WV_VISTA: str.append(" Vista"); break; case QSysInfo::WV_WINDOWS7: str.append(" 7"); break; case QSysInfo::WV_WINDOWS8: str.append(" 8"); break; case QSysInfo::WV_WINDOWS8_1: str.append(" 8.1"); break; case QSysInfo::WV_WINDOWS10: str.append(" 10"); break; default: break; } return str; #endif } QString QzTools::cpuArchitecture() { return QSysInfo::currentCpuArchitecture(); } QString QzTools::operatingSystemLong() { const QString arch = cpuArchitecture(); if (arch.isEmpty()) return QzTools::operatingSystem(); return QzTools::operatingSystem() + QSL(" ") + arch; } void QzTools::paintDropIndicator(QWidget *widget, const QRect &r) { // Modified code from KFilePlacesView QColor color = widget->palette().brush(QPalette::Normal, QPalette::Highlight).color(); const int x = (r.left() + r.right()) / 2; const int thickness = qRound(r.width() / 2.0); int alpha = 255; const int alphaDec = alpha / (thickness + 1); QStylePainter p(widget); for (int i = 0; i < thickness; i++) { color.setAlpha(alpha); alpha -= alphaDec; p.setPen(color); p.drawLine(x - i, r.top(), x - i, r.bottom()); p.drawLine(x + i, r.top(), x + i, r.bottom()); } } diff --git a/src/plugins/GreaseMonkey/settings/gm_settingslistdelegate.cpp b/src/plugins/GreaseMonkey/settings/gm_settingslistdelegate.cpp index a7decfda..bbe56457 100644 --- a/src/plugins/GreaseMonkey/settings/gm_settingslistdelegate.cpp +++ b/src/plugins/GreaseMonkey/settings/gm_settingslistdelegate.cpp @@ -1,173 +1,178 @@ /* ============================================================ * GreaseMonkey plugin for Falkon * Copyright (C) 2012-2017 David Rosca * * This program 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 3 of the License, or * (at your option) any later version. * * This program 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 this program. If not, see . * ============================================================ */ #include "gm_settingslistdelegate.h" #include "gm_script.h" #include "iconprovider.h" #include #include #include +#include GM_SettingsListDelegate::GM_SettingsListDelegate(QObject* parent) : QStyledItemDelegate(parent) , m_rowHeight(0) , m_padding(0) { m_removePixmap = IconProvider::standardIcon(QStyle::SP_DialogCloseButton).pixmap(16); m_updateIcon = IconProvider::standardIcon(QStyle::SP_BrowserReload); } int GM_SettingsListDelegate::padding() const { return m_padding; } void GM_SettingsListDelegate::paint(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { GM_Script *script = static_cast(index.data(Qt::UserRole + 10).value()); if (!script) return; QStyleOptionViewItem opt = option; initStyleOption(&opt, index); const QWidget* w = opt.widget; const QStyle* style = w ? w->style() : QApplication::style(); const int height = opt.rect.height(); const int center = height / 2 + opt.rect.top(); // forced to LTR, see issue#967 painter->setLayoutDirection(Qt::LeftToRight); // Prepare title font QFont titleFont = opt.font; titleFont.setBold(true); titleFont.setPointSize(titleFont.pointSize() + 1); const QFontMetrics titleMetrics(titleFont); const QPalette::ColorRole colorRole = opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text; QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) { cg = QPalette::Inactive; } #ifdef Q_OS_WIN opt.palette.setColor(QPalette::All, QPalette::HighlightedText, opt.palette.color(QPalette::Active, QPalette::Text)); opt.palette.setColor(QPalette::All, QPalette::Highlight, opt.palette.base().color().darker(108)); #endif QPalette textPalette = opt.palette; textPalette.setCurrentColorGroup(cg); int leftPosition = m_padding; int rightPosition = opt.rect.right() - m_padding - 16; // 16 for remove button if (!script->downloadUrl().isEmpty()) rightPosition -= m_padding + 16; // 16 for update button // Draw background style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, w); // Draw checkbox const int checkboxSize = 18; const int checkboxYPos = center - (checkboxSize / 2); QStyleOptionViewItem opt2 = opt; opt2.checkState == Qt::Checked ? opt2.state |= QStyle::State_On : opt2.state |= QStyle::State_Off; QRect styleCheckBoxRect = style->subElementRect(QStyle::SE_ViewItemCheckIndicator, &opt2, w); opt2.rect = QRect(leftPosition, checkboxYPos, styleCheckBoxRect.width(), styleCheckBoxRect.height()); style->drawPrimitive(QStyle::PE_IndicatorViewItemCheck, &opt2, painter, w); leftPosition = opt2.rect.right() + m_padding; // Draw icon const int iconSize = 32; const int iconYPos = center - (iconSize / 2); QRect iconRect(leftPosition, iconYPos, iconSize, iconSize); QPixmap pixmap = index.data(Qt::DecorationRole).value().pixmap(iconSize); if (!pixmap.isNull()) { painter->drawPixmap(iconRect, pixmap); leftPosition = iconRect.right() + m_padding; } else { leftPosition += m_padding; } // Draw script name const QString name = index.data(Qt::DisplayRole).toString(); const int leftTitleEdge = leftPosition + 2; const int rightTitleEdge = rightPosition - m_padding; +#if QTGUI_VERSION >= QT_VERSION_CHECK(5, 11, 0) + const int leftPosForVersion = titleMetrics.horizontalAdvance(name) + m_padding; +#else const int leftPosForVersion = titleMetrics.width(name) + m_padding; +#endif QRect nameRect(leftTitleEdge, opt.rect.top() + m_padding, rightTitleEdge - leftTitleEdge, titleMetrics.height()); painter->setFont(titleFont); style->drawItemText(painter, nameRect, Qt::AlignLeft, textPalette, true, name, colorRole); // Draw version QRect versionRect(nameRect.x() + leftPosForVersion, nameRect.y(), rightTitleEdge - leftPosForVersion, titleMetrics.height()); QFont versionFont = titleFont; versionFont.setBold(false); painter->setFont(versionFont); style->drawItemText(painter, versionRect, Qt::AlignLeft, textPalette, true, script->version(), colorRole); // Draw description const int infoYPos = nameRect.bottom() + opt.fontMetrics.leading(); QRect infoRect(nameRect.x(), infoYPos, nameRect.width(), opt.fontMetrics.height()); const QString info = opt.fontMetrics.elidedText(script->description(), Qt::ElideRight, infoRect.width()); painter->setFont(opt.font); style->drawItemText(painter, infoRect, Qt::TextSingleLine | Qt::AlignLeft, textPalette, true, info, colorRole); // Draw update button if (!script->downloadUrl().isEmpty()) { const int updateIconSize = 16; const int updateIconYPos = center - (updateIconSize / 2); const QPixmap updatePixmap = m_updateIcon.pixmap(16, script->isUpdating() ? QIcon::Disabled : QIcon::Normal); QRect updateIconRect(rightPosition, updateIconYPos, updateIconSize, updateIconSize); painter->drawPixmap(updateIconRect, updatePixmap); rightPosition += m_padding + 16; } // Draw remove button const int removeIconSize = 16; const int removeIconYPos = center - (removeIconSize / 2); QRect removeIconRect(rightPosition, removeIconYPos, removeIconSize, removeIconSize); painter->drawPixmap(removeIconRect, m_removePixmap); } QSize GM_SettingsListDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index) if (!m_rowHeight) { QStyleOptionViewItem opt(option); initStyleOption(&opt, index); const QWidget* w = opt.widget; const QStyle* style = w ? w->style() : QApplication::style(); const int padding = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0) + 1; QFont titleFont = opt.font; titleFont.setBold(true); titleFont.setPointSize(titleFont.pointSize() + 1); m_padding = padding > 5 ? padding : 5; const QFontMetrics titleMetrics(titleFont); m_rowHeight = 2 * m_padding + opt.fontMetrics.leading() + opt.fontMetrics.height() + titleMetrics.height(); } return QSize(200, m_rowHeight); }