1/* This file is part of the KDE project
2 Copyright (C) 2004 Esben Mose Hansen <kde@mosehansen.dk>
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; see the file COPYING. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
18*/
19#include "popupproxy.h"
20
21#include <QtCore/QRegExp>
22#include <QtGui/QStyle>
23#include <QtGui/QPixmap>
24#include <QtGui/QImage>
25#include <QtGui/QStyleOption>
26
27#include <KMenu>
28#include <KLocale>
29#include <KDebug>
30
31#include "historyitem.h"
32#include "history.h"
33#include "klipperpopup.h"
34
35
36PopupProxy::PopupProxy( KlipperPopup* parent, int menu_height, int menu_width )
37 : QObject( parent ),
38 m_proxy_for_menu( parent ),
39 m_spill_uuid(),
40 m_menu_height( menu_height ),
41 m_menu_width( menu_width )
42{
43 if (!parent->history()->empty()) {
44 m_spill_uuid = parent->history()->first()->uuid();
45 }
46 connect( parent->history(), SIGNAL(changed()), SLOT(slotHistoryChanged()) );
47 connect(m_proxy_for_menu, SIGNAL(triggered(QAction*)), parent->history(), SLOT(slotMoveToTop(QAction*)));
48}
49
50void PopupProxy::slotHistoryChanged() {
51 deleteMoreMenus();
52
53}
54
55void PopupProxy::deleteMoreMenus() {
56 const KMenu* myParent = parent();
57 if ( myParent != m_proxy_for_menu ) {
58 KMenu* delme = m_proxy_for_menu;
59 m_proxy_for_menu = static_cast<KMenu*>( m_proxy_for_menu->parent() );
60 while ( m_proxy_for_menu != myParent ) {
61 delme = m_proxy_for_menu;
62 m_proxy_for_menu = static_cast<KMenu*>( m_proxy_for_menu->parent() );
63 }
64 // We are called probably from within the menus event-handler (triggered=>slotMoveToTop=>changed=>slotHistoryChanged=>deleteMoreMenus)
65 // what can result in a crash if we just delete the menu here (#155196 and #165154) So, delay the delete.
66 delme->deleteLater();
67 }
68}
69
70int PopupProxy::buildParent( int index, const QRegExp& filter ) {
71 deleteMoreMenus();
72 // Start from top of history (again)
73 m_spill_uuid = parent()->history()->empty() ? QByteArray() : parent()->history()->first()->uuid();
74 if ( filter.isValid() ) {
75 m_filter = filter;
76 }
77
78 return insertFromSpill( index );
79
80}
81
82KlipperPopup* PopupProxy::parent() {
83 return static_cast<KlipperPopup*>( QObject::parent() );
84}
85
86void PopupProxy::slotAboutToShow() {
87 insertFromSpill();
88}
89
90void PopupProxy::tryInsertItem( HistoryItem const * const item,
91 int& remainingHeight,
92 const int index )
93{
94 QAction *action = new QAction(m_proxy_for_menu);
95 QPixmap image( item->image() );
96 if ( image.isNull() ) {
97 // Squeeze text strings so that do not take up the entire screen (or more)
98 QString text = m_proxy_for_menu->fontMetrics().elidedText( item->text().simplified(), Qt::ElideMiddle, m_menu_width );
99 text.replace( '&', "&&" );
100 action->setText(text);
101 } else {
102#if 0 // not used because QAction#setIcon does not respect this size; it does scale anyway. TODO: find a way to set a bigger image
103 const QSize max_size( m_menu_width,m_menu_height/4 );
104 if ( image.height() > max_size.height() || image.width() > max_size.width() ) {
105 image = image.scaled( max_size, Qt::KeepAspectRatio, Qt::SmoothTransformation );
106 }
107#endif
108 action->setIcon(QIcon(image));
109 }
110
111 action->setData(item->uuid());
112
113 // 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.
114 QAction *before = index < m_proxy_for_menu->actions().count() ? m_proxy_for_menu->actions().at(index) : 0;
115 // insert the new action to the m_proxy_for_menu
116 m_proxy_for_menu->insertAction(before, action);
117
118 // Determine height of a menu item.
119 QStyleOptionMenuItem style_options;
120 // It would be much easier to use QMenu::initStyleOptions. But that is protected, so until we have a better
121 // excuse to subclass that, I'd rather implement this manually.
122 // Note 2 properties, tabwidth and maxIconWidth, are not available from the public interface, so those are left out (probably not
123 // important for height. Also, Exlsive checkType is disregarded as I don't think we will ever use it)
124 style_options.initFrom(m_proxy_for_menu);
125 style_options.checkType = action->isCheckable() ? QStyleOptionMenuItem::NonExclusive : QStyleOptionMenuItem::NotCheckable;
126 style_options.checked = action->isChecked();
127 style_options.font = action->font();
128 style_options.icon = action->icon();
129 style_options.menuHasCheckableItems = true;
130 style_options.menuRect = m_proxy_for_menu->rect();
131 style_options.text = action->text();
132
133 int font_height = QFontMetrics(m_proxy_for_menu->fontMetrics()).height();
134
135 int itemheight = m_proxy_for_menu->style()->sizeFromContents(QStyle::CT_MenuItem,
136 &style_options,
137 QSize( 0, font_height ),
138 m_proxy_for_menu).height();
139 // Subtract the used height
140 remainingHeight -= itemheight;
141}
142
143int PopupProxy::insertFromSpill( int index ) {
144
145 const History* history = parent()->history();
146 // This menu is going to be filled, so we don't need the aboutToShow()
147 // signal anymore
148 disconnect( m_proxy_for_menu, 0, this, 0 );
149
150 // Insert history items into the current m_proxy_for_menu,
151 // discarding any that doesn't match the current filter.
152 // stop when the total number of items equal m_itemsPerMenu;
153 int count = 0;
154 int remainingHeight = m_menu_height - m_proxy_for_menu->sizeHint().height();
155 const HistoryItem* item = history->find(m_spill_uuid);
156 if (!item) {
157 return count;
158 }
159 do {
160 if ( m_filter.indexIn( item->text() ) != -1) {
161 tryInsertItem( item, remainingHeight, index++ );
162 count++;
163 }
164 item = history->find(item->next_uuid());
165 } while ( item && history->first() != item && remainingHeight >= 0);
166 m_spill_uuid = item->uuid();
167
168 // If there is more items in the history, insert a new "More..." menu and
169 // make *this a proxy for that menu ('s content).
170 if (history->first() && m_spill_uuid != history->first()->uuid()) {
171 KMenu* moreMenu = new KMenu(i18n("&More"), m_proxy_for_menu);
172 connect(moreMenu, SIGNAL(aboutToShow()), SLOT(slotAboutToShow()));
173 QAction *before = index < m_proxy_for_menu->actions().count() ? m_proxy_for_menu->actions().at(index) : 0;
174 m_proxy_for_menu->insertMenu(before, moreMenu);
175 m_proxy_for_menu = moreMenu;
176 }
177
178 // Return the number of items inserted.
179 return count;
180
181}
182#include "popupproxy.moc"
183