Commit 2e0fd299 authored by David Faure's avatar David Faure

Port the dict applet to KF5 and QtQuick 2

Summary:
My wife was missing this applet, after migrating to Plasma 5.
Therefore I had to learn how to write QML-based plasma applets :-)

Test Plan:
* Adding dict applet to desktop, works.
* Looking up definitions, work.
* Clicking on a link, works.
* Hi-dpi support, works.
* Configuration dialog to choose dictionary: done in a followup commit.

Reviewers: #plasma, davidedmundson

Reviewed By: #plasma, davidedmundson

Subscribers: ngraham, anthonyfieroni, broulik, davidedmundson, plasma-devel, #plasma

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D8421
parent 6eca2535
......@@ -7,6 +7,7 @@ add_subdirectory(colorpicker)
add_subdirectory(mediaframe)
add_subdirectory(notes)
add_subdirectory(timer)
add_subdirectory(dict)
find_package(KDEExperimentalPurpose QUIET)
set_package_properties(KDEExperimentalPurpose PROPERTIES DESCRIPTION "EXPERIMENTAL. Support for QuickShare sharing"
......
INCLUDE_DIRECTORIES(
${QT_QTWEBKIT_INCLUDE_DIR})
#######################################################################################
# Package (metadata.desktop and QML)
plasma_install_package(package org.kde.plasma_applet_dict)
#######################################################################################
# Icon for the package
install(FILES accessories-dictionary.svgz
DESTINATION ${DATA_INSTALL_DIR}/icons/hicolor/scalable/apps/)
#######################################################################################
# C++ Plugin
find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED WebEngine)
add_definitions(-DTRANSLATION_DOMAIN="plasma_applet_org.kde.plasma.dict")
set(dict_SRCS
dict.cpp)
plugin/dict_plugin.cpp
plugin/dict_object.cpp
)
kde4_add_plugin(plasma_applet_dict ${dict_SRCS})
target_link_libraries(plasma_applet_dict ${KDE4_PLASMA_LIBS} ${KDE4_KDEUI_LIBS}
add_library(dictplugin SHARED ${dict_SRCS})
target_link_libraries(dictplugin
KF5::Plasma
KF5::IconThemes
KF5::I18n
Qt5::Quick
Qt5::WebEngine
)
install(TARGETS plasma_applet_dict DESTINATION ${PLUGIN_INSTALL_DIR})
install(FILES plasma-dict-default.desktop DESTINATION ${SERVICES_INSTALL_DIR})
install(FILES plugin/qmldir DESTINATION ${QML_INSTALL_DIR}/org/kde/plasma/private/dict)
install(TARGETS dictplugin DESTINATION ${QML_INSTALL_DIR}/org/kde/plasma/private/dict)
kde4_install_icons(${ICON_INSTALL_DIR})
#! /usr/bin/env bash
$XGETTEXT *.cpp -o $podir/plasma_applet_qstardict.pot
$XGETTEXT `find . -name \*.qml -o -name \*.cpp` -o $podir/plasma_applet_org.kde.plasma.dict.pot
TODO for dict applet
Looks
Add clear button
Sliding animations
Configurable dictionaries
Migrate to layouts
/*
* Copyright (C) 2008 Nick Shaforostoff <shaforostoff@kde.ru>
*
* based on work by:
* Copyright (C) 2007 Thomas Georgiou <TAGeorgiou@gmail.com> and Jeff Cooper <weirdsox11@gmail.com>
*
* 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 2 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 <http://www.gnu.org/licenses/>.
*/
#include "dict.h"
#include <QGraphicsLinearLayout>
#include <QTimer>
#include <KDebug>
#include <KIcon>
#include <QTreeView>
#include <QStringListModel>
#include <KColorScheme>
#include <KConfigDialog>
#include <KConfigGroup>
#include <KLineEdit>
#include <KTextBrowser>
#include <Plasma/Animator>
#include <Plasma/IconWidget>
#include <Plasma/LineEdit>
#include <Plasma/TextBrowser>
#include <Plasma/Theme>
#include <Plasma/ToolTipContent>
#include <Plasma/ToolTipManager>
#include <plasma/animations/animation.h>
#define AUTO_DEFINE_TIMEOUT 500
using namespace Plasma;
// Style sheet format
const char* translationCSS =
"dl {color: %1;}\n"
"a{color: %2}\n"
"a:visited{color: %3}\n";
DictApplet::DictApplet(QObject *parent, const QVariantList &args)
: Plasma::PopupApplet(parent, args)
, m_graphicsWidget(0)
, m_wordEdit(0)
{
setPopupIcon(QLatin1String( "accessories-dictionary" ));
setAspectRatioMode(Plasma::IgnoreAspectRatio);
}
void DictApplet::init()
{
const char* dataEngines[]={"dict","qstardict"};
bool engineChoice = dataEngine(QString( QLatin1String( dataEngines[1] ) ) )->isValid();
// bool engineChoice = false; //for testing
m_dataEngine = QLatin1String( dataEngines[int(engineChoice)] );
// tooltip stuff
Plasma::ToolTipContent toolTipData;
toolTipData.setAutohide(true);
toolTipData.setMainText(name());
toolTipData.setImage(KIcon(QLatin1String( "accessories-dictionary" )));
Plasma::ToolTipManager::self()->setContent(this, toolTipData);
// Only register the tooltip in panels
switch (formFactor()) {
case Plasma::Horizontal:
case Plasma::Vertical:
Plasma::ToolTipManager::self()->registerWidget(this);
break;
default:
Plasma::ToolTipManager::self()->unregisterWidget(this);
break;
}
}
DictApplet::~DictApplet()
{
m_defBrowser->deleteLater();
}
QGraphicsWidget *DictApplet::graphicsWidget()
{
if (m_graphicsWidget) {
return m_graphicsWidget;
}
m_wordEdit = new LineEdit(this);
m_wordEdit->nativeWidget()->setClearButtonShown( true );
m_wordEdit->nativeWidget()->setClickMessage(i18n("Enter word to define here"));
m_wordEdit->show();
m_defBrowser = new Plasma::TextBrowser();
m_defBrowser->nativeWidget()->setNotifyClick(true);
connect(m_defBrowser->nativeWidget(),SIGNAL(urlClick(QString)),this,SLOT(linkDefine(QString)));
syncTheme();
connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), SLOT(updateColors()));
m_defBrowser->hide();
// Icon in upper-left corner
m_icon = new Plasma::IconWidget(this);
m_icon->setIcon(QLatin1String( "accessories-dictionary" ));
// Position lineedits
//const int wordEditOffset = 40;
m_icon->setPos(12,3);
//m_wordProxyWidget->setPos(15 + wordEditOffset,7);
//m_wordProxyWidget->show();
// TODO m_wordEdit->setDefaultTextColor(Plasma::Theme::self()->color(Plasma::Theme::TextColor));
// Timer for auto-define
m_timer = new QTimer(this);
m_timer->setInterval(AUTO_DEFINE_TIMEOUT);
m_timer->setSingleShot(true);
connect(m_timer, SIGNAL(timeout()), this, SLOT(define()));
m_horLayout = new QGraphicsLinearLayout(Qt::Horizontal);
m_horLayout->addItem(m_icon);
m_horLayout->addItem(m_wordEdit);
m_horLayout->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
m_layout = new QGraphicsLinearLayout(Qt::Vertical);
m_layout->addItem(m_horLayout);
m_layout->addItem(m_defBrowser);
m_source.clear();
dataEngine(m_dataEngine)->connectSource(m_source, this);
connect(m_wordEdit, SIGNAL(editingFinished()), this, SLOT(define()));
connect(m_wordEdit->nativeWidget(), SIGNAL(textChanged(QString)), this, SLOT(autoDefine(QString)));
dataEngine(m_dataEngine)->connectSource(QLatin1String( "list-dictionaries" ), this);
//connect(m_defEdit, SIGNAL(linkActivated(QString)), this, SLOT(linkDefine(QString)));
// This is the fix for links/selecting text
//QGraphicsItem::GraphicsItemFlags flags = m_defEdit->flags();
//flags ^= QGraphicsItem::ItemIsMovable;
// m_defEdit->setFlags(flags);
/*m_flash = new Plasma::Flash(this);
m_flash->setColor(Qt::gray);
QFont fnt = qApp->font();
fnt.setBold(true);
m_flash->setFont(fnt);
m_flash->setPos(25,-10);
m_flash->resize(QSize(200,20));*/
configChanged();
m_graphicsWidget = new QGraphicsWidget(this);
m_graphicsWidget->setLayout(m_layout);
m_graphicsWidget->setPreferredSize(500, 200);
Animation *zoomAnim =
Plasma::Animator::create(Plasma::Animator::ZoomAnimation);
zoomAnim->setTargetWidget(m_wordEdit);
zoomAnim->setProperty("zoom", 1.0);
zoomAnim->setProperty("duration", 350);
zoomAnim->start(QAbstractAnimation::DeleteWhenStopped);
return m_graphicsWidget;
}
void DictApplet::syncTheme()
{
// Gets the color scheme from default theme
KColorScheme colorScheme(QPalette::Active, KColorScheme::View, Plasma::Theme::defaultTheme()->colorScheme());
m_defBrowser->nativeWidget()->document()->setDefaultStyleSheet(QString(QLatin1String( translationCSS ))
.arg(colorScheme.foreground().color().name())
.arg(colorScheme.foreground(KColorScheme::LinkText).color().name())
.arg(colorScheme.foreground(KColorScheme::VisitedText).color().name()));
}
void DictApplet::configChanged()
{
KConfigGroup cg = config();
m_dicts = cg.readEntry("KnownDictionaries", QStringList());
QStringList activeDictNames = cg.readEntry("ActiveDictionaries", QStringList());
for (QStringList::const_iterator i = m_dicts.constBegin(); i != m_dicts.constEnd(); ++i) {
m_activeDicts[*i]=activeDictNames.contains(*i);
}
}
void DictApplet::linkDefine(const QString &text)
{
//kDebug() <<"ACTIVATED";
m_wordEdit->setText(text);
define();
}
void DictApplet::dataUpdated(const QString& source, const Plasma::DataEngine::Data &data)
{
if (source==QLatin1String( "list-dictionaries" )) {
QStringList newDicts = data[QLatin1String( "dictionaries" )].toStringList();
bool changed = false;
for (QStringList::const_iterator i = newDicts.constBegin(); i != newDicts.constEnd(); ++i) {
if (!m_dicts.contains(*i)) {
m_dicts << *i;
m_activeDicts[*i] = true;
changed = true;
}
}
QStringList::iterator it = m_dicts.begin();
while (it != m_dicts.end()) {
if (!newDicts.contains(*it)) {
it = m_dicts.erase(it);
changed = true;
} else {
++it;
}
}
if (changed) {
configAccepted();
}
}
if (!m_source.isEmpty()) {
m_defBrowser->show();
}
if (data.contains(QLatin1String( "text" ))) {
m_defBrowser->nativeWidget()->setHtml(data[QLatin1String("text")].toString());
}
updateGeometry();
}
void DictApplet::define()
{
if (m_timer->isActive()) {
m_timer->stop();
}
QString newSource = m_wordEdit->text();
QStringList dictsList;
for (QStringList::const_iterator i = m_dicts.constBegin(); i != m_dicts.constEnd(); ++i) {
if (m_activeDicts.contains(*i) && m_activeDicts.value(*i)) {
dictsList << *i;
}
}
if (!newSource.isEmpty() && !dictsList.isEmpty()) {
newSource.prepend(dictsList.join(QLatin1String( "," ) )+QLatin1Char( ':' ));
}
if (newSource == m_source) {
return;
}
dataEngine(m_dataEngine)->disconnectSource(m_source, this);
if (!newSource.isEmpty()) {
//get new definition
//m_flash->flash(i18n("Looking up ") + m_word);
m_source = newSource;
dataEngine(m_dataEngine)->connectSource(m_source, this);
} else {
//make the definition box disappear
m_defBrowser->hide();
}
updateConstraints();
}
void DictApplet::autoDefine(const QString &word)
{
Q_UNUSED(word)
m_timer->start();
}
class CheckableStringListModel: public QStringListModel
{
public:
CheckableStringListModel(QObject* parent, const QStringList& dicts, const QHash<QString,bool>& activeDicts_)
: QStringListModel(parent)
, activeDicts(activeDicts_)
{
setStringList(dicts);
}
QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const
{
Q_UNUSED(section)
Q_UNUSED(orientation)
if (role!=Qt::DisplayRole)
return QVariant();
return i18n("Dictionary");
}
Qt::DropActions supportedDropActions() const {return Qt::MoveAction;}
Qt::ItemFlags flags(const QModelIndex& index) const
{
if (!index.isValid())
return Qt::ItemIsEnabled | Qt::ItemIsDropEnabled;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled;
}
bool setData (const QModelIndex& index, const QVariant& value, int role=Qt::EditRole)
{
if (role==Qt::CheckStateRole)
{
activeDicts[stringList().at(index.row())]=value.toInt()==Qt::Checked;
return true;
}
else
return QStringListModel::setData(index,value,role);
}
QVariant data(const QModelIndex& index, int role=Qt::EditRole) const
{
if (!index.isValid())
return QVariant();
if (role==Qt::CheckStateRole)
return ( activeDicts.contains(stringList().at(index.row()))&&activeDicts.value(stringList().at(index.row())) )?Qt::Checked:Qt::Unchecked;
return QStringListModel::data(index,role);
}
public:
QHash<QString,bool> activeDicts;
};
void DictApplet::createConfigurationInterface(KConfigDialog *parent)
{
if (dataEngine(QLatin1String( "qstardict" ))->isValid()) {
QTreeView* widget=new QTreeView(parent);
widget->setDragEnabled(true);
widget->setAcceptDrops(true);
widget->setDragDropMode(QAbstractItemView::InternalMove);
widget->setDropIndicatorShown(true);
widget->setItemsExpandable(false);
widget->setAllColumnsShowFocus(true);
widget->setRootIsDecorated(false);
delete m_dictsModel.data();
CheckableStringListModel *model = new CheckableStringListModel(parent,m_dicts,m_activeDicts);
m_dictsModel = model;
widget->setModel(model);
parent->addPage(widget, parent->windowTitle(), Applet::icon());
connect(parent, SIGNAL(applyClicked()), this, SLOT(configAccepted()));
connect(parent, SIGNAL(okClicked()), this, SLOT(configAccepted()));
}
}
void DictApplet::popupEvent(bool shown)
{
//kDebug() << shown;
if (shown && m_wordEdit) {
focusEditor();
}
}
void DictApplet::configAccepted()
{
if (m_dictsModel) {
CheckableStringListModel *model = m_dictsModel.data();
m_dicts = model->stringList();
m_activeDicts = model->activeDicts;
}
KConfigGroup cg = config();
cg.writeEntry("KnownDictionaries", m_dicts);
QStringList activeDictNames;
for (QStringList::const_iterator i = m_dicts.constBegin(); i != m_dicts.constEnd(); ++i)
if (m_activeDicts.contains(*i) && m_activeDicts.value(*i))
activeDictNames<<*i;
cg.writeEntry("ActiveDictionaries", activeDictNames);
define();
emit configNeedsSaving();
}
void DictApplet::focusEditor()
{
m_wordEdit->clearFocus();
m_wordEdit->setFocus();
m_wordEdit->nativeWidget()->clearFocus();
m_wordEdit->nativeWidget()->setFocus();
}
/*
* Copyright (C) 2008 Nick Shaforostoff <shaforostoff@kde.ru>
*
* based on work by:
* Copyright (C) 2007 Thomas Georgiou <TAGeorgiou@gmail.com> and Jeff Cooper <weirdsox11@gmail.com>
*
* 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 2 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 <http://www.gnu.org/licenses/>.
*/
#ifndef DICT_H
#define DICT_H
#include <Plasma/PopupApplet>
#include <Plasma/DataEngine>
class QTimer;
class CheckableStringListModel;
class QGraphicsLinearLayout;
namespace Plasma
{
class IconWidget;
class LineEdit;
class TextBrowser;
}
class DictApplet: public Plasma::PopupApplet
{
Q_OBJECT
public:
DictApplet(QObject *parent, const QVariantList &args);
~DictApplet();
void init();
QGraphicsWidget *graphicsWidget();
void setPath(const QString&);
public slots:
void dataUpdated(const QString &name, const Plasma::DataEngine::Data &data);
void autoDefine(const QString &word);
void linkDefine(const QString &word);
void configChanged();
protected slots:
void define();
void configAccepted();
void focusEditor();
void syncTheme();
protected:
void createConfigurationInterface(KConfigDialog *parent);
void popupEvent(bool shown);
private:
QString m_source;
QTimer* m_timer;
QString m_dataEngine;
//QGraphicsPixmapItem *m_graphicsIcon;
QGraphicsWidget *m_graphicsWidget;
QGraphicsLinearLayout *m_layout;
QGraphicsLinearLayout *m_horLayout;
Plasma::LineEdit *m_wordEdit;
//Plasma::Flash *m_flash;
// QTextBrowser* m_defBrowser;
Plasma::TextBrowser* m_defBrowser;
Plasma::IconWidget *m_icon;
// QList< QPair<QString, bool> > m_dicts;
QWeakPointer<CheckableStringListModel> m_dictsModel;
QStringList m_dicts;
QHash<QString,bool> m_activeDicts;
};
K_EXPORT_PLASMA_APPLET(qstardict, DictApplet)
#endif
import QtQuick 2.0
import QtQuick.Layouts 1.1
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.plasma.core 2.0 as PlasmaCore
import QtWebEngine 1.1
import org.kde.plasma.private.dict 1.0
ColumnLayout {
DictObject {
id: dict
onSearchInProgress: web.loadHtml(i18n("Looking up definition..."));
onDefinitionFound: web.loadHtml(html);
}
PlasmaComponents.TextField {
id: input
placeholderText: i18n("Enter word to define here")
implicitWidth: units.gridUnit * 40
Layout.fillWidth: true
onAccepted: {
if (input.text === "") {
web.visible = false;
} else {
web.visible = true;
dict.lookup(input.text);
}
}
}
WebEngineView {
id: web
visible: false
Layout.fillWidth: true
//Layout.fillHeight: true
Layout.preferredHeight: 400
zoomFactor: units.devicePixelRatio
profile: dict.webProfile
}
}
......@@ -116,10 +116,9 @@ Type=Service
Icon=accessories-dictionary
X-KDE-ServiceTypes=Plasma/Applet
X-KDE-Library=plasma_applet_dict
X-KDE-PluginInfo-Author=The Plasma Team
X-KDE-PluginInfo-Email=plasma-devel@kde.org
X-KDE-PluginInfo-Name=dict
X-KDE-PluginInfo-Name=org.kde.plasma_applet_dict
X-KDE-PluginInfo-Version=1.0
X-KDE-PluginInfo-Website=http://plasma.kde.org/
X-KDE-PluginInfo-Category=Language
......@@ -127,5 +126,9 @@ X-KDE-PluginInfo-Depends=
X-KDE-PluginInfo-License=GPL-2.0+
X-KDE-PluginInfo-EnabledByDefault=true
X-Plasma-API=declarativeappletscript
X-Plasma-MainScript=ui/main.qml
X-Plasma-RemoteLocation= </