File indexing completed on 2024-04-28 16:54:26
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 QPixmap image(item->image()); 0079 if (image.isNull()) { 0080 // Squeeze text strings so that do not take up the entire screen (or more) 0081 QString text = m_proxy_for_menu->fontMetrics().elidedText(Utils::simplifiedText(item->text(), 1000), Qt::ElideRight, m_menu_width); 0082 text.replace(QLatin1Char('&'), QLatin1String("&&")); 0083 action->setText(text); 0084 } else { 0085 action->setIcon(QIcon(image)); 0086 } 0087 0088 action->setData(item->uuid()); 0089 0090 // 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. 0091 QAction *before = index < m_proxy_for_menu->actions().count() ? m_proxy_for_menu->actions().at(index) : nullptr; 0092 // insert the new action to the m_proxy_for_menu 0093 m_proxy_for_menu->insertAction(before, action); 0094 0095 // Determine height of a menu item. 0096 QStyleOptionMenuItem style_options; 0097 // It would be much easier to use QMenu::initStyleOptions. But that is protected, so until we have a better 0098 // excuse to subclass that, I'd rather implement this manually. 0099 // Note 2 properties, tabwidth and maxIconWidth, are not available from the public interface, so those are left out (probably not 0100 // important for height. Also, Exclusive checkType is disregarded as I don't think we will ever use it) 0101 style_options.initFrom(m_proxy_for_menu); 0102 style_options.checkType = action->isCheckable() ? QStyleOptionMenuItem::NonExclusive : QStyleOptionMenuItem::NotCheckable; 0103 style_options.checked = action->isChecked(); 0104 style_options.font = action->font(); 0105 style_options.icon = action->icon(); 0106 style_options.menuHasCheckableItems = true; 0107 style_options.menuRect = m_proxy_for_menu->rect(); 0108 style_options.text = action->text(); 0109 0110 int font_height = QFontMetrics(m_proxy_for_menu->fontMetrics()).height(); 0111 0112 int itemheight = m_proxy_for_menu->style()->sizeFromContents(QStyle::CT_MenuItem, &style_options, QSize(0, font_height), m_proxy_for_menu).height(); 0113 // Subtract the used height 0114 remainingHeight -= itemheight; 0115 } 0116 0117 int PopupProxy::insertFromSpill(int index) 0118 { 0119 const History *history = parent()->history(); 0120 // This menu is going to be filled, so we don't need the aboutToShow() 0121 // signal anymore 0122 disconnect(m_proxy_for_menu, nullptr, this, nullptr); 0123 0124 // Insert history items into the current m_proxy_for_menu, 0125 // discarding any that doesn't match the current filter. 0126 // stop when the total number of items equal m_itemsPerMenu; 0127 int count = 0; 0128 int remainingHeight = m_menu_height - m_proxy_for_menu->sizeHint().height(); 0129 auto item = history->find(m_spill_uuid); 0130 if (!item) { 0131 return count; 0132 } 0133 do { 0134 if (m_filter.match(item->text()).hasMatch()) { 0135 tryInsertItem(item.data(), remainingHeight, index++); 0136 count++; 0137 } 0138 item = history->find(item->next_uuid()); 0139 } while (item && history->first() != item && remainingHeight >= 0); 0140 m_spill_uuid = item->uuid(); 0141 0142 // If there is more items in the history, insert a new "More..." menu and 0143 // make *this a proxy for that menu ('s content). 0144 if (history->first() && m_spill_uuid != history->first()->uuid()) { 0145 QMenu *moreMenu = new QMenu(i18n("&More"), m_proxy_for_menu); 0146 connect(moreMenu, &QMenu::aboutToShow, this, &PopupProxy::slotAboutToShow); 0147 QAction *before = index < m_proxy_for_menu->actions().count() ? m_proxy_for_menu->actions().at(index) : nullptr; 0148 m_proxy_for_menu->insertMenu(before, moreMenu); 0149 m_proxy_for_menu = moreMenu; 0150 } 0151 0152 // Return the number of items inserted. 0153 return count; 0154 }