File indexing completed on 2024-04-28 05:35:29
0001 /* 0002 SPDX-FileCopyrightText: 2004 Esben Mose Hansen <kde@mosehansen.dk> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 #include "popupproxy.h" 0007 0008 #include <QPixmap> 0009 #include <QStyle> 0010 #include <QStyleOption> 0011 0012 #include <KLocalizedString> 0013 0014 #include "historyitem.h" 0015 #include "klipperpopup.h" 0016 #include "utils.h" 0017 0018 PopupProxy::PopupProxy(KlipperPopup *parent, int menu_height, int menu_width) 0019 : QObject(parent) 0020 , m_proxy_for_menu(parent) 0021 , m_spill_uuid() 0022 , m_menu_height(menu_height) 0023 , m_menu_width(menu_width) 0024 { 0025 if (!parent->history()->empty()) { 0026 m_spill_uuid = parent->history()->first()->uuid(); 0027 } 0028 connect(parent->history(), &History::changed, this, &PopupProxy::slotHistoryChanged); 0029 connect(m_proxy_for_menu, SIGNAL(triggered(QAction *)), parent->history(), SLOT(slotMoveToTop(QAction *))); 0030 } 0031 0032 void PopupProxy::slotHistoryChanged() 0033 { 0034 deleteMoreMenus(); 0035 } 0036 0037 void PopupProxy::deleteMoreMenus() 0038 { 0039 const QMenu *myParent = parent(); 0040 if (myParent != m_proxy_for_menu) { 0041 QMenu *delme = m_proxy_for_menu; 0042 m_proxy_for_menu = static_cast<QMenu *>(m_proxy_for_menu->parent()); 0043 while (m_proxy_for_menu != myParent) { 0044 delme = m_proxy_for_menu; 0045 m_proxy_for_menu = static_cast<QMenu *>(m_proxy_for_menu->parent()); 0046 } 0047 // We are called probably from within the menus event-handler (triggered=>slotMoveToTop=>changed=>slotHistoryChanged=>deleteMoreMenus) 0048 // what can result in a crash if we just delete the menu here (#155196 and #165154) So, delay the delete. 0049 delme->deleteLater(); 0050 } 0051 } 0052 0053 int PopupProxy::buildParent(int index, const QRegularExpression &filter) 0054 { 0055 deleteMoreMenus(); 0056 // Start from top of history (again) 0057 m_spill_uuid = parent()->history()->empty() ? QByteArray() : parent()->history()->first()->uuid(); 0058 if (filter.isValid()) { 0059 m_filter = filter; 0060 } 0061 0062 return insertFromSpill(index); 0063 } 0064 0065 KlipperPopup *PopupProxy::parent() 0066 { 0067 return static_cast<KlipperPopup *>(QObject::parent()); 0068 } 0069 0070 void PopupProxy::slotAboutToShow() 0071 { 0072 insertFromSpill(); 0073 } 0074 0075 void PopupProxy::tryInsertItem(HistoryItem const *const item, int &remainingHeight, const int index) 0076 { 0077 QAction *action = new QAction(m_proxy_for_menu); 0078 if (QImage image(item->image()); image.isNull()) { 0079 // Squeeze text strings so that do not take up the entire screen (or more) 0080 QString text = m_proxy_for_menu->fontMetrics().elidedText(Utils::simplifiedText(item->text(), 1000), Qt::ElideRight, m_menu_width); 0081 text.replace(QLatin1Char('&'), QLatin1String("&&")); 0082 action->setText(text); 0083 } else { 0084 action->setIcon(QIcon(QPixmap::fromImage(std::move(image)))); 0085 } 0086 0087 action->setData(item->uuid()); 0088 0089 // if the m_proxy_for_menu is a submenu (aka a "More" menu) then it may the case, that there is no other action in that menu yet. 0090 QAction *before = index < m_proxy_for_menu->actions().count() ? m_proxy_for_menu->actions().at(index) : nullptr; 0091 // insert the new action to the m_proxy_for_menu 0092 m_proxy_for_menu->insertAction(before, action); 0093 0094 // Determine height of a menu item. 0095 QStyleOptionMenuItem style_options; 0096 // It would be much easier to use QMenu::initStyleOptions. But that is protected, so until we have a better 0097 // excuse to subclass that, I'd rather implement this manually. 0098 // Note 2 properties, tabwidth and maxIconWidth, are not available from the public interface, so those are left out (probably not 0099 // important for height. Also, Exclusive checkType is disregarded as I don't think we will ever use it) 0100 style_options.initFrom(m_proxy_for_menu); 0101 style_options.checkType = action->isCheckable() ? QStyleOptionMenuItem::NonExclusive : QStyleOptionMenuItem::NotCheckable; 0102 style_options.checked = action->isChecked(); 0103 style_options.font = action->font(); 0104 style_options.icon = action->icon(); 0105 style_options.menuHasCheckableItems = true; 0106 style_options.menuRect = m_proxy_for_menu->rect(); 0107 style_options.text = action->text(); 0108 0109 int font_height = QFontMetrics(m_proxy_for_menu->fontMetrics()).height(); 0110 0111 int itemheight = m_proxy_for_menu->style()->sizeFromContents(QStyle::CT_MenuItem, &style_options, QSize(0, font_height), m_proxy_for_menu).height(); 0112 // Subtract the used height 0113 remainingHeight -= itemheight; 0114 } 0115 0116 int PopupProxy::insertFromSpill(int index) 0117 { 0118 const History *history = parent()->history(); 0119 // This menu is going to be filled, so we don't need the aboutToShow() 0120 // signal anymore 0121 disconnect(m_proxy_for_menu, nullptr, this, nullptr); 0122 0123 // Insert history items into the current m_proxy_for_menu, 0124 // discarding any that doesn't match the current filter. 0125 // stop when the total number of items equal m_itemsPerMenu; 0126 int count = 0; 0127 int remainingHeight = m_menu_height - m_proxy_for_menu->sizeHint().height(); 0128 auto item = history->find(m_spill_uuid); 0129 if (!item) { 0130 return count; 0131 } 0132 do { 0133 if (m_filter.match(item->text()).hasMatch()) { 0134 tryInsertItem(item.get(), remainingHeight, index++); 0135 count++; 0136 } 0137 item = history->find(item->next_uuid()); 0138 } while (item && history->first() != item && remainingHeight >= 0); 0139 m_spill_uuid = item->uuid(); 0140 0141 // If there is more items in the history, insert a new "More..." menu and 0142 // make *this a proxy for that menu ('s content). 0143 if (history->first() && m_spill_uuid != history->first()->uuid()) { 0144 QMenu *moreMenu = new QMenu(i18n("&More"), m_proxy_for_menu); 0145 connect(moreMenu, &QMenu::aboutToShow, this, &PopupProxy::slotAboutToShow); 0146 QAction *before = index < m_proxy_for_menu->actions().count() ? m_proxy_for_menu->actions().at(index) : nullptr; 0147 m_proxy_for_menu->insertMenu(before, moreMenu); 0148 m_proxy_for_menu = moreMenu; 0149 } 0150 0151 // Return the number of items inserted. 0152 return count; 0153 }