1 /* This file is part of the KDE project
2    Copyright 1999-2006 The KSpread Team <calligra-devel@kde.org>
3 
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library 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 library 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    Library General Public License for more details.
13 
14    You should have received a copy of the GNU Library General Public License
15    along with this library; see the file COPYING.LIB.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17    Boston, MA 02110-1301, USA.
18 */
19 
20 #include "FunctionCompletion.h"
21 
22 // Sheets
23 #include "CellEditor.h"
24 #include "Function.h"
25 #include "FunctionDescription.h"
26 #include "FunctionRepository.h"
27 
28 // Qt
29 #include <QApplication>
30 #include <QDesktopWidget>
31 #include <QKeyEvent>
32 #include <QLabel>
33 #include <QListWidget>
34 #include <QScrollBar>
35 #include <QToolTip>
36 #include <QVBoxLayout>
37 
38 using namespace Calligra::Sheets;
39 
40 class FunctionCompletion::Private
41 {
42 public:
43     CellEditor* editor;
44     QFrame *completionPopup;
45     QListWidget *completionListBox;
46     QLabel* hintLabel;
47 };
48 
FunctionCompletion(CellEditor * editor)49 FunctionCompletion::FunctionCompletion(CellEditor* editor)
50         : QObject(editor)
51         , d(new Private)
52 {
53     d->editor = editor;
54     d->hintLabel = 0;
55 
56     d->completionPopup = new QFrame(editor->topLevelWidget(), Qt::Popup);
57     d->completionPopup->setFrameStyle(QFrame::Box | QFrame::Plain);
58     d->completionPopup->setLineWidth(1);
59     d->completionPopup->installEventFilter(this);
60     d->completionPopup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
61     QVBoxLayout *layout = new QVBoxLayout(d->completionPopup);
62     layout->setMargin(0);
63     layout->setSpacing(0);
64 
65     d->completionListBox = new QListWidget(d->completionPopup);
66     d->completionPopup->setFocusProxy(d->completionListBox);
67     d->completionListBox->setFrameStyle(QFrame::NoFrame);
68 //   d->completionListBox->setVariableWidth( true );
69     d->completionListBox->installEventFilter(this);
70     connect(d->completionListBox, SIGNAL(currentRowChanged(int)), SLOT(itemSelected()));
71     // When items are activated on single click, also change the help page on mouse-over, otherwise there is no (easy) way to get
72     // the help (with the mouse) without inserting the function
73     if (d->completionListBox->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, d->completionListBox)) {
74         connect(d->completionListBox, SIGNAL(itemEntered(QListWidgetItem*)), this, SLOT(itemSelected(QListWidgetItem*)));
75         d->completionListBox->setMouseTracking(true);
76     }
77 
78     connect(d->completionListBox, SIGNAL(itemActivated(QListWidgetItem*)),
79             this, SLOT(doneCompletion()));
80     layout->addWidget(d->completionListBox);
81 
82     d->hintLabel = new QLabel(0, Qt::FramelessWindowHint | Qt::Tool |  Qt::X11BypassWindowManagerHint);
83     d->hintLabel->setFrameStyle(QFrame::Plain | QFrame::Box);
84     d->hintLabel->setPalette(QToolTip::palette());
85     d->hintLabel->setWordWrap(true);
86     d->hintLabel->hide();
87 }
88 
~FunctionCompletion()89 FunctionCompletion::~FunctionCompletion()
90 {
91     delete d->hintLabel;
92     delete d;
93 }
94 
itemSelected(QListWidgetItem * listItem)95 void FunctionCompletion::itemSelected(QListWidgetItem* listItem)
96 {
97     QString item;
98     if (listItem) {
99         item = listItem->text();
100     } else {
101         listItem = d->completionListBox->currentItem();
102         if (listItem) {
103             item = listItem->text();
104         }
105     }
106 
107     Calligra::Sheets::FunctionDescription* desc;
108     desc = Calligra::Sheets::FunctionRepository::self()->functionInfo(item);
109     if (!desc) {
110         d->hintLabel->hide();
111         return;
112     }
113 
114     const QStringList helpTexts = desc->helpText();
115     QString helpText = helpTexts.isEmpty() ? QString() : helpTexts.first();
116     if (helpText.isEmpty()) {
117         d->hintLabel->hide();
118         return;
119     }
120 
121     helpText.append("</qt>").prepend("<qt>");
122     d->hintLabel->setText(helpText);
123     d->hintLabel->adjustSize();
124 
125     // reposition nicely
126     QPoint pos = d->editor->mapToGlobal(QPoint(d->editor->width(), 0));
127     pos.setY(pos.y() - d->hintLabel->height() - 1);
128     d->hintLabel->move(pos);
129     d->hintLabel->show();
130     d->hintLabel->raise();
131 
132     // do not show it forever
133     //QTimer::singleShot( 5000, d->hintLabel, SLOT(hide()) );
134 }
135 
eventFilter(QObject * obj,QEvent * ev)136 bool FunctionCompletion::eventFilter(QObject *obj, QEvent *ev)
137 {
138     if (obj != d->completionPopup && obj != d->completionListBox)
139         return false;
140 
141     if (ev->type() == QEvent::KeyPress) {
142         QKeyEvent *ke = static_cast<QKeyEvent*>(ev);
143         switch (ke->key()) {
144         case Qt::Key_Enter:
145         case Qt::Key_Return:
146             doneCompletion();
147             return true;
148         case Qt::Key_Left:
149         case Qt::Key_Right:
150         case Qt::Key_Up:
151         case Qt::Key_Down:
152         case Qt::Key_Home:
153         case Qt::Key_End:
154         case Qt::Key_PageUp:
155         case Qt::Key_PageDown:
156             return false;
157         default:
158             d->hintLabel->hide();
159             d->completionPopup->close();
160             d->editor->setFocus();
161             QApplication::sendEvent(d->editor, ev);
162             return true;
163         }
164     }
165 
166     if (ev->type() == QEvent::Close) {
167         d->hintLabel->hide();
168     }
169 
170     if (ev->type() == QEvent::MouseButtonDblClick) {
171         doneCompletion();
172         return true;
173     }
174     return false;
175 }
176 
doneCompletion()177 void FunctionCompletion::doneCompletion()
178 {
179     d->hintLabel->hide();
180     d->completionPopup->close();
181     d->editor->setFocus();
182     emit selectedCompletion(d->completionListBox->currentItem()->text());
183 }
184 
showCompletion(const QStringList & choices)185 void FunctionCompletion::showCompletion(const QStringList &choices)
186 {
187     if (!choices.count()) return;
188 
189     d->completionListBox->clear();
190     d->completionListBox->addItems(choices);
191     d->completionListBox->setCurrentItem(0);
192 
193     // size of the pop-up
194     d->completionPopup->setMaximumHeight(100);
195     d->completionPopup->resize(d->completionListBox->sizeHint() +
196                                QSize(d->completionListBox->verticalScrollBar()->width() + 4,
197                                      d->completionListBox->horizontalScrollBar()->height() + 4));
198     int h = d->completionListBox->height();
199     int w = d->completionListBox->width();
200 
201     QPoint pos = d->editor->globalCursorPosition();
202 
203     // if popup is partially invisible, move to other position
204     // FIXME check it if it works in Xinerama multihead
205     int screen_num = QApplication::desktop()->screenNumber(d->completionPopup);
206     QRect screen = QApplication::desktop()->screenGeometry(screen_num);
207     if (pos.y() + h > screen.y() + screen.height())
208         pos.setY(pos.y() - h - d->editor->height());
209     if (pos.x() + w > screen.x() + screen.width())
210         pos.setX(screen.x() + screen.width() - w);
211 
212     d->completionPopup->move(pos);
213     d->completionListBox->setFocus();
214     d->completionPopup->show();
215 }
216