From: APTX Date: Mon, 5 Nov 2012 17:21:47 +0000 (+0100) Subject: Init X-Git-Url: https://gitweb.aptx.org/?a=commitdiff_plain;h=2cb4e88793432e861c9a4efa7cd22b800c07c543;p=launcher.git Init --- 2cb4e88793432e861c9a4efa7cd22b800c07c543 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f6642b --- /dev/null +++ b/.gitignore @@ -0,0 +1,76 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +.qmake.cache +tags +.DS_Store +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp + +# qtcreator generated files +*.pro.user +*.pro.user.* +*.autosave + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.exp + +# MinGW generated files +*.Debug +*.Release + +# Directories to ignore +# --------------------- + +build +debug +release +lib/qtsingleapplication/lib +lib/qtsingleapplication/examples +lib/qtsingleapplication/doc +.tmp +qtc-gdbmacros + +# Binaries +# -------- +build/*.dll +build/*.lib +build/*.exe +build/*.so* + + diff --git a/launcher.pro b/launcher.pro new file mode 100644 index 0000000..f70f582 --- /dev/null +++ b/launcher.pro @@ -0,0 +1,6 @@ +TEMPLATE = subdirs +CONFIG += ordered + +SUBDIRS += pluginapi \ + launcher \ + plugins diff --git a/launcher/icon.png b/launcher/icon.png new file mode 100644 index 0000000..a6b97e6 Binary files /dev/null and b/launcher/icon.png differ diff --git a/launcher/inputwidget.cpp b/launcher/inputwidget.cpp new file mode 100644 index 0000000..01d6f94 --- /dev/null +++ b/launcher/inputwidget.cpp @@ -0,0 +1,12 @@ +#include "inputwidget.h" + +InputWidget::InputWidget(QWidget *parent) : + QLineEdit(parent) +{ + +} + +QSize InputWidget::minimumSizeHint() +{ + return QSize(400, 50); +} diff --git a/launcher/inputwidget.h b/launcher/inputwidget.h new file mode 100644 index 0000000..36ce4ad --- /dev/null +++ b/launcher/inputwidget.h @@ -0,0 +1,20 @@ +#ifndef INPUTWIDGET_H +#define INPUTWIDGET_H + +#include + +class InputWidget : public QLineEdit +{ + Q_OBJECT +public: + explicit InputWidget(QWidget *parent = 0); + +signals: + +public slots: +// void input +protected: + QSize minimumSizeHint(); +}; + +#endif // INPUTWIDGET_H diff --git a/launcher/launcher.cpp b/launcher/launcher.cpp new file mode 100644 index 0000000..1e2004d --- /dev/null +++ b/launcher/launcher.cpp @@ -0,0 +1,147 @@ +#include "launcher.h" + +#include +#include +#include +#include +#include + +#include "mainwindow.h" +#include "tray.h" +#include "plugin.h" +#include "launcherpluginbase.h" +#include "resultmodel.h" + +#include + +Launcher::Launcher(int argc, char **argv) : + QApplication(argc, argv) +{ + m_instance = this; + m_resultModel = new ResultModel(this); + m_mainWindow = new MainWindow; + m_tray = new Tray; + + connect(this, SIGNAL(aboutToQuit()), m_tray, SLOT(hide())); + + qRegisterMetaType("Reply"); + loadPlugins(); + + setWindowIcon(QIcon(":/icon.png")); + m_tray->setIcon(windowIcon()); + + showAction = new QAction("Show", this); + showAction->setShortcut(QKeySequence("Alt+Space")); + connect(showAction, SIGNAL(triggered()), m_mainWindow, SLOT(activate())); + + showShortcut = new QxtGlobalShortcut(m_mainWindow); + connect(showShortcut, SIGNAL(activated()), showAction, SLOT(trigger())); + showShortcut->setShortcut(showAction->shortcut()); + + quitAction = new QAction("&Quit", this); + connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit())); + + + QMenu *menu = new QMenu; + menu->addAction(showAction); + menu->addSeparator(); + menu->addAction(quitAction); + m_tray->setContextMenu(menu); + + connect(m_mainWindow, SIGNAL(commandChanged(QString)), this, SLOT(queryPlugins(QString))); + connect(m_mainWindow, SIGNAL(commandChanged(QString)), m_resultModel, SLOT(commandChanged(QString))); + m_tray->show(); + +} + +Launcher::~Launcher() +{ + unloadPlugins(); + m_instance = 0; +} + +MainWindow *Launcher::mainWindow() const +{ + return m_mainWindow; +} + +Tray *Launcher::tray() const +{ + return m_tray; +} + +ResultModel *Launcher::resultModel() const +{ + return m_resultModel; +} + +void Launcher::queryPlugins(const QString &cmd) +{ + if (cmd.isEmpty()) + return; + + foreach (Plugin *p, plugins) + { + p->processCommand(cmd); + } +} + +void Launcher::updateResults() +{ + /* + Plugin *p = qobject_cast(sender()); + + foreach(const Result &r, p->latestResult().results) + { + qDebug() << r.match << r.description; + } + */ +} + +void Launcher::loadPlugins() +{ + QDir dir(QDir::current()); + QStringList nameFilter; +#if defined(Q_OS_WIN) + nameFilter << "*_plugin.dll"; +#elif defined(Q_OS_UNIX) + nameFilter << "*_plugin.so"; +#endif + + QFileInfoList pluginFiles = dir.entryInfoList(nameFilter, QDir::Files); + + foreach (const QFileInfo &file, pluginFiles) + { + qDebug() << "Loading plugin" << file.fileName(); + Plugin *plugin = new Plugin(file.absoluteFilePath()); + connect(plugin, SIGNAL(resultRecieved()), this, SLOT(updateResults())); + connect(plugin, SIGNAL(resultRecieved()), m_resultModel, SLOT(updateResults())); + if (!plugin->load()) + { + qDebug() << "Load failed"; + continue; + } + + plugins.append(plugin); + + } +} + +void Launcher::unloadPlugins() +{ + while (plugins.count()) + { + Plugin *p = plugins.takeLast(); + p->unload(); + p->deleteLater(); + } + plugins.clear(); +} + +Launcher *Launcher::instance() +{ + return m_instance; +} + + +Launcher *Launcher::m_instance = 0; diff --git a/launcher/launcher.h b/launcher/launcher.h new file mode 100644 index 0000000..8142350 --- /dev/null +++ b/launcher/launcher.h @@ -0,0 +1,56 @@ +#ifndef LAUNCHER_H +#define LAUNCHER_H + +#include +#include "reply.h" + +class MainWindow; +class Tray; +class QAction; +class QxtGlobalShortcut; + +class Plugin; +class ResultModel; + +class Launcher : public QApplication +{ + Q_OBJECT +public: + explicit Launcher(int argc, char **argv); + ~Launcher(); + + MainWindow *mainWindow() const; + Tray *tray() const; + ResultModel *resultModel() const; + + +public slots: + void queryPlugins(const QString &cmd); + void updateResults(); + +private: + void loadPlugins(); + void unloadPlugins(); + + MainWindow *m_mainWindow; + Tray *m_tray; + + QAction *showAction; + QAction *quitAction; + QxtGlobalShortcut *showShortcut; + + QList plugins; + + ResultModel *m_resultModel; + + static Launcher *m_instance; +public: + static Launcher *instance(); +}; + +#if defined(qApp) +# undef qApp +#endif +#define qApp Launcher::instance() + +#endif // LAUNCHER_H diff --git a/launcher/launcher.pro b/launcher/launcher.pro new file mode 100644 index 0000000..4fd4a0a --- /dev/null +++ b/launcher/launcher.pro @@ -0,0 +1,51 @@ +QT += core gui declarative +CONFIG += qxt +QXT *= gui + +DESTDIR = ../build + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = launcher +TEMPLATE = app + + +SOURCES += main.cpp \ + mainwindow.cpp \ + inputwidget.cpp \ + launcher.cpp \ + tray.cpp + +HEADERS += mainwindow.h \ + inputwidget.h \ + launcher.h \ + tray.h + +RESOURCES += \ + resources.qrc + +include(../pluginapi/pluginapi.pri) + +HEADERS += \ + pluginthread.h + +SOURCES += \ + pluginthread.cpp + +OTHER_FILES += \ + qml/view.qml + +OTHER_FILES += \ + qml/Components/ResultDelegate.qml + +HEADERS += \ + resultmodel.h + +SOURCES += \ + resultmodel.cpp + +HEADERS += \ + plugin.h + +SOURCES += \ + plugin.cpp diff --git a/launcher/main.cpp b/launcher/main.cpp new file mode 100644 index 0000000..e817df9 --- /dev/null +++ b/launcher/main.cpp @@ -0,0 +1,9 @@ +#include "mainwindow.h" +#include "launcher.h" + +int main(int argc, char *argv[]) +{ + Launcher a(argc, argv); + + return a.exec(); +} diff --git a/launcher/mainwindow.cpp b/launcher/mainwindow.cpp new file mode 100644 index 0000000..7cde7a2 --- /dev/null +++ b/launcher/mainwindow.cpp @@ -0,0 +1,148 @@ +#include "mainwindow.h" +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "launcher.h" +#include "resultmodel.h" +#include "inputwidget.h" + +#include + +MainWindow::MainWindow(QWidget *parent) + : QWidget(parent) +{ + setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowStaysOnTopHint); + setAttribute(Qt::WA_TranslucentBackground); + desktopWidget = new QDesktopWidget(); + connect(desktopWidget, SIGNAL(resized(int)), this, SLOT(position())); + setupUi(); +} + +MainWindow::~MainWindow() +{ + delete desktopWidget; +} + +void MainWindow::keyPressEvent(QKeyEvent *e) +{ + switch (e->key()) + { + case Qt::Key_Escape: + hide(); + e->accept(); + break; + case Qt::Key_Down: + qApp->resultModel()->setSelectedIndex(qApp->resultModel()->selectedIndex() + 1); + e->accept(); + break; + case Qt::Key_Up: + qApp->resultModel()->setSelectedIndex(qApp->resultModel()->selectedIndex() - 1); + e->accept(); + break; + } +} + +void MainWindow::resizeEvent(QResizeEvent *e) +{ + QWidget::resizeEvent(e); + position(); +} + +bool MainWindow::event(QEvent *e) +{ + if (e->type() == QEvent::WindowDeactivate) + hide(); + + return QWidget::event(e); +} + +void MainWindow::position() +{ + QRect r = desktopWidget->screenGeometry(); + move(r.center() - QPoint(width() / 2, height() / 2)); +} + +void MainWindow::setupUi() +{ + inputWidget = new InputWidget(this); + connect(inputWidget, SIGNAL(returnPressed()), this, SLOT(handleReturn())); + connect(inputWidget, SIGNAL(textEdited(QString)), this, SIGNAL(commandChanged(QString))); + + view = new QDeclarativeView(this); + view->setMinimumHeight(60*5); + + QDeclarativeContext *ctxt = view->rootContext(); + ctxt->setContextProperty("resultModel", qApp->resultModel()); + ctxt->setContextProperty("mainWindow", this); + + view->setSource(QUrl("qrc:/qml/view.qml")); + view->setResizeMode(QDeclarativeView::SizeRootObjectToView); + + + view->setAttribute(Qt::WA_OpaquePaintEvent); + view->setAttribute(Qt::WA_NoSystemBackground); + view->viewport()->setAttribute(Qt::WA_OpaquePaintEvent); + view->viewport()->setAttribute(Qt::WA_NoSystemBackground); + + QVBoxLayout *l = new QVBoxLayout(); + l->setContentsMargins(0, 0, 0, 0); + l->addWidget(inputWidget); + l->addWidget(view); + setLayout(l); + resize(700, height()); +} + +void MainWindow::activate() +{ + show(); + raise(); + activateWindow(); + inputWidget->setFocus(); +} + +void MainWindow::handleReturn() +{ + QString command = inputWidget->text(); + + if (command == "qqq") + { + qApp->quit(); + hide(); + return; + } + + hide(); + + if (command.isEmpty()) + return; + + Result r = qApp->resultModel()->selectedResult(); + + if (r.command.isEmpty()) + { + qDebug() << "empty command"; + return; + } + + bool success = false; + switch (r.commandType) + { + case RunCommand: + success = QProcess::startDetached(r.command, r.args); + break; + case OpenCommand: +qDebug() << "opening" << ("file://" + r.command); + success = QDesktopServices::openUrl(QUrl::fromUserInput(r.command)); + break; + } + qDebug() << "did it work?" << success; + inputWidget->clear(); + qApp->resultModel()->clear(); +} diff --git a/launcher/mainwindow.h b/launcher/mainwindow.h new file mode 100644 index 0000000..dae0d2d --- /dev/null +++ b/launcher/mainwindow.h @@ -0,0 +1,42 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + + +class QDesktopWidget; +class QDeclarativeView; +class InputWidget; + +class MainWindow : public QWidget +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = 0); + ~MainWindow(); + +public slots: + void activate(); + void handleReturn(); + +protected: + void keyPressEvent(QKeyEvent *e); + void resizeEvent(QResizeEvent *e); + bool event(QEvent *); + +protected slots: + void position(); + +signals: + void commandChanged(const QString &cmd); + +private: + void setupUi(); + + QDesktopWidget *desktopWidget; + InputWidget *inputWidget; + QDeclarativeView *view; +}; + +#endif // MAINWINDOW_H diff --git a/launcher/plugin.cpp b/launcher/plugin.cpp new file mode 100644 index 0000000..c6c1488 --- /dev/null +++ b/launcher/plugin.cpp @@ -0,0 +1,84 @@ +#include "plugin.h" + +#include "pluginthread.h" +#include "launcherpluginbase.h" + +Plugin::Plugin(const QString &file, QObject *parent) : QObject(parent), + m_pluginThread(new PluginThread(file, this)), executing(false), m_status(Unloaded) +{ + unload(); +} + +bool Plugin::isCurrentResult() const +{ + return !executing && m_lastProcessedCommand == m_latestCommand; +} + +Plugin::Status Plugin::status() const +{ + return m_status; +} + +Reply Plugin::latestResult() const +{ + return m_latestResult; +} + +Reply Plugin::previousResult() const +{ + return m_previousResult; +} + +LauncherPluginBase *Plugin::pluginInstance() +{ + return m_pluginThread->plugin(); +} + +bool Plugin::load() +{ + if (m_status != Unloaded) + return false; + + bool r = m_pluginThread->startThread(); + connect(m_pluginThread->plugin(), SIGNAL(replyReady(Reply)), this, SLOT(handleReply(Reply)), Qt::QueuedConnection); + m_status = r ? Loaded : Error; + + return r; +} + +void Plugin::unload() +{ + if (m_status != Loaded) + return; + m_pluginThread->stopThread(); + m_status = Unloaded; +} + +void Plugin::processCommand(const QString &cmd) +{ + if (m_lastProcessedCommand == cmd) + return; + + m_latestCommand = cmd; + + if (executing) + return; + executing = true; + + m_lastProcessedCommand = cmd; + + QMetaObject::invokeMethod(m_pluginThread->plugin(), "processCommand", Qt::QueuedConnection, + Q_ARG(QString, cmd)); +} + +void Plugin::handleReply(const Reply &reply) +{ + m_previousResult = m_latestResult; + m_latestResult = reply; + executing = false; + + if (m_lastProcessedCommand != m_latestCommand) + processCommand(m_latestCommand); + + emit resultRecieved(); +} diff --git a/launcher/plugin.h b/launcher/plugin.h new file mode 100644 index 0000000..bfcafd7 --- /dev/null +++ b/launcher/plugin.h @@ -0,0 +1,55 @@ +#ifndef PLUGIN_H +#define PLUGIN_H + +#include + +#include "reply.h" + +class PluginThread; +class LauncherPluginBase; + +class Plugin : public QObject +{ + Q_OBJECT +public: + enum Status { + Unloaded, + Loaded, + Error + }; + + explicit Plugin(const QString &file, QObject *parent = 0); + + bool isCurrentResult() const; + Status status() const; + Reply latestResult() const; + Reply previousResult() const; + + LauncherPluginBase *pluginInstance(); + +signals: + void resultRecieved(); + +public slots: + bool load(); + void unload(); + + void processCommand(const QString &cmd); + +private slots: + void handleReply(const Reply &reply); + +private: + bool executing; + Status m_status; + + QString m_lastProcessedCommand; + QString m_latestCommand; + + Reply m_previousResult; + Reply m_latestResult; + + PluginThread *m_pluginThread; +}; + +#endif // PLUGIN_H diff --git a/launcher/pluginthread.cpp b/launcher/pluginthread.cpp new file mode 100644 index 0000000..0e0e7cd --- /dev/null +++ b/launcher/pluginthread.cpp @@ -0,0 +1,63 @@ +#include "pluginthread.h" + +#include +#include "launcherpluginbase.h" + +#include + +PluginThread::PluginThread(const QString &file, QObject *parent) : QThread(parent), + loaded(false), m_file(file), m_pluginLoader(0), m_plugin(0), m_waitStarted(0) +{ +} + +bool PluginThread::startThread() +{ + start(); + m_waitStarted.acquire(); + return loaded; +} + +void PluginThread::stopThread() +{ + quit(); + m_waitStarted.acquire(); +} + +LauncherPluginBase *PluginThread::plugin() +{ + return m_plugin; +} + +void PluginThread::run() +{ + m_pluginLoader = new QPluginLoader(m_file); + Q_ASSERT(m_pluginLoader->thread() == this); + + if (!m_pluginLoader->load()) + { + qDebug() << "Load error:" << m_pluginLoader->errorString(); + m_waitStarted.release(); + delete m_pluginLoader; + return; + } + qDebug() << "loaded!"; + loaded = true; + + m_plugin = qobject_cast(m_pluginLoader->instance()); + m_waitStarted.release(); + m_plugin->init(); + + exec(); + + m_plugin->deinit(); + bool s = m_pluginLoader->unload(); + m_plugin = 0; + loaded = false; + + delete m_pluginLoader; + + qDebug() << "Unload succeeded?" << s; + + m_waitStarted.release(); +} + diff --git a/launcher/pluginthread.h b/launcher/pluginthread.h new file mode 100644 index 0000000..cc8707a --- /dev/null +++ b/launcher/pluginthread.h @@ -0,0 +1,32 @@ +#ifndef PLUGINTHREAD_H +#define PLUGINTHREAD_H + +#include +#include + +class QPluginLoader; +class LauncherPluginBase; + +class PluginThread : public QThread +{ + Q_OBJECT +public: + explicit PluginThread(const QString &file, QObject *parent = 0); + + bool startThread(); + void stopThread(); + LauncherPluginBase *plugin(); + +protected: + void run(); + + +private: + bool loaded; + QString m_file; + QPluginLoader *m_pluginLoader; + LauncherPluginBase *m_plugin; + QSemaphore m_waitStarted; +}; + +#endif // PLUGINTHREAD_H diff --git a/launcher/qml/Components/ResultDelegate.qml b/launcher/qml/Components/ResultDelegate.qml new file mode 100644 index 0000000..c8b4c35 --- /dev/null +++ b/launcher/qml/Components/ResultDelegate.qml @@ -0,0 +1,103 @@ +import QtQuick 1.0 + +Component { + Item { + id: wrapper + + property bool open: false + + height: 50 + width: listView.width - 10 + anchors.horizontalCenter: parent.horizontalCenter + + MouseArea { + id: mouseArea + hoverEnabled: true + anchors.fill: parent + + onClicked: { + resultModel.selectedIndex = index + mainWindow.handleReturn() + } + } + + Rectangle { + id: bg + x: 2 + y: 2 + + width: parent.width - 2 * x + height: parent.height - 2 * y + + radius: 10 + + border.width: resultModel.selectedIndex == index || mouseArea.containsMouse ? 2 : 0 + border.color: resultModel.selectedIndex == index ? "red" : mouseArea.containsMouse ? "yellow" : "#000000" + opacity: 0.90 + color: isCurrent ? "steelblue" : "grey" + + Item { + anchors.leftMargin: 10 + anchors.rightMargin: 10 + anchors.fill: parent + + Text { + id: pluginNameText + font.italic: true + text: pluginName + anchors.right: parent.right + } + + Text { + id: resultMatchText + anchors.centerIn: parent + text: resultMatch + } + + Text { + id: resultDescritpionText + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + elide: Text.ElideRight + text: resultDescription + } + } + } + states: [ + State { + when: mainWindow.selectedIndex == index + PropertyChanges { + target: wrapper + height: 100 + } + } + ] + + transitions: [ + Transition { + PropertyAnimation { + properties: "x,y,height,width,opacity"; easing.type: Easing.InOutQuad; duration: 300 + } + } + ] + + ListView.onAdd: SequentialAnimation { + PropertyAction { target: wrapper; property: "opacity"; value: 0 } + PropertyAction { target: wrapper; property: "height"; value: 0 } + ParallelAnimation { + NumberAnimation { target: wrapper; property: "height"; to: 50; duration: 500; easing.type: Easing.InOutQuad } + NumberAnimation { target: wrapper; property: "opacity"; to: 1; duration: 500; easing.type: Easing.InOutQuad } + } + } + + ListView.onRemove: SequentialAnimation { + PropertyAction { target: wrapper; property: "ListView.delayRemove"; value: true } + ParallelAnimation { + NumberAnimation { target: wrapper; property: "height"; to: 0; duration: 500; easing.type: Easing.InOutQuad } + NumberAnimation { target: wrapper; property: "opacity"; to: 0; duration: 500; easing.type: Easing.InOutQuad } + } + PropertyAction { target: wrapper; property: "ListView.delayRemove"; value: false } + } + } +} diff --git a/launcher/qml/view.qml b/launcher/qml/view.qml new file mode 100644 index 0000000..1888735 --- /dev/null +++ b/launcher/qml/view.qml @@ -0,0 +1,31 @@ +import QtQuick 1.0 +import "Components" 1.0 as Components + +Item { + id: main + + signal resultSelected + +// opacity: 0.1 + + ListModel { + id: dummyModel + ListElement + { + pluginName: "foo" + resultMatch: "foo" + } + } + + ListView { + id: listView + + anchors.fill: parent + model: resultModel + delegate: Components.ResultDelegate {} + footer: Item { + width: listView.width + height: listView.height / 2 + } + } +} diff --git a/launcher/resources.qrc b/launcher/resources.qrc new file mode 100644 index 0000000..3b0fd8c --- /dev/null +++ b/launcher/resources.qrc @@ -0,0 +1,7 @@ + + + icon.png + qml/view.qml + qml/Components/ResultDelegate.qml + + diff --git a/launcher/resultmodel.cpp b/launcher/resultmodel.cpp new file mode 100644 index 0000000..3b19689 --- /dev/null +++ b/launcher/resultmodel.cpp @@ -0,0 +1,230 @@ +#include "resultmodel.h" + +#include "plugin.h" +#include "launcherpluginbase.h" + +#include + +ResultModel::ResultModel(QObject *parent) : + QAbstractListModel(parent), rowCountDelta(0), emptyCommand(true) +{ + QHash roles; + roles[PluginName] = "pluginName"; + roles[ResultMatch] = "resultMatch"; + roles[ResultDescription] = "resultDescription"; + roles[ResultCommand] = "resultCommand"; + roles[IsCurrent] = "isCurrent"; + setRoleNames(roles); +} + +ResultModel::~ResultModel() +{ + qDeleteAll(results); +} + +int ResultModel::selectedIndex() const +{ + return m_selectedIndex; +} + +Result ResultModel::selectedResult() const +{ + qDebug() << "selectedResult" << m_selectedIndex; + if (m_selectedIndex > rowCount()) + return Result(); + + return results[m_selectedIndex]->result; +} + +void ResultModel::setSelectedIndex(int idx) +{ + if (!rowCount() || idx >= rowCount()) + idx = 0; + if (idx < 0) + idx = rowCount() - 1; + + if (m_selectedIndex != idx) + { + m_selectedIndex = idx; + emit selectedIndexChanged(idx); + } +} + +void ResultModel::clear() +{ + beginRemoveRows(QModelIndex(), 0, rowCount()); + pluginOrder.clear(); + qDeleteAll(results); + results.clear(); + endRemoveRows(); +} + +QVariant ResultModel::data(const QModelIndex &index, int role) const +{ + if (index.row() < 0) + return QVariant("negative index"); + + PluginResult *pluginResult = results[index.row()]; + switch (role) + { + case Qt::DisplayRole: + case PluginName: + return pluginResult->plugin->pluginInstance()->name(); + break; + case ResultMatch: + return pluginResult->result.match; + case ResultDescription: + return pluginResult->result.description; + break; + case ResultCommand: + return pluginResult->result.command; + break; + case IsCurrent: + return pluginResult->isCurrent; + break; + default: + return QVariant(); + break; + } + return QVariant(); +} + +int ResultModel::rowCount(const QModelIndex &) const +{ + return results.count(); +} + +void ResultModel::commandChanged(const QString &cmd) +{ + emptyCommand = cmd.isEmpty(); + + if (emptyCommand) + { + clear(); + return; + } + + for (int i = 0; i < results.count(); ++i) + results[i]->isCurrent = false; + + emit dataChanged(index(0, 0), index(rowCount() - 1, 0)); +} + +void ResultModel::updateResults() +{ + if (emptyCommand) + return; + + int oldCount = rowCount(); + + Plugin *plugin = qobject_cast(sender()); + int idx = pluginOrder.indexOf(plugin); + + if (idx == -1) + { + if (!plugin->latestResult().results.count()) + return; + pluginOrder << plugin; + } + + beginResetModel(); + + qDeleteAll(results); + results.clear(); + + for (int i = 0; i < pluginOrder.count(); ++i) + { + Plugin *p = pluginOrder[i]; + + for (int j = 0; j < p->latestResult().results.count(); ++j) + { + Result r = p->latestResult().results[j]; + PluginResult *pr = new PluginResult; + pr->isCurrent = true; + pr->plugin = p; + pr->result = r; + results << pr; + } + } + endResetModel(); + +/* Plugin *plugin = qobject_cast(sender()); + auto it = pluginOrder.find(plugin); + int pluginIdx; + int resultIdx; + int oldResultCount; + int newResultCount; + + if (it == resultMap.end()) + { + if (!plugin->latestResult().results.count()) + return; + + pluginOrder.append(plugin); + + pluginIdx = pluginOrder.count(); + + PluginResult *pr = new PluginResult; + pr->plugin = plugin; + + results.append(pr); + resultMap.insert(plugin, pr); + + pluginIdx = results.count() - 1; + resultIdx = rowCount(); + oldResultCount = 0; + } + + if (it != resultMap.end()) + { + pluginIdx = results.indexOf(it.value()); + resultIdx = 0; + for (int i = 0; i < pluginIdx; ++i) + resultIdx += results[i]->count(); + + oldResultCount = plugin->previousResult().results.count(); + } + + newResultCount = plugin->latestResult().results.count(); + + results[pluginIdx]->isCurrent = plugin->isCurrentResult(); + + if (oldResultCount == newResultCount) + { +qDebug() << "Updating rows" << newResultCount; + + if (newResultCount) + updateResultsForRange(resultIdx, newResultCount); + } + else if(oldResultCount < newResultCount) + { +qDebug() << "Adding rows"; + if (oldResultCount) + updateResultsForRange(resultIdx, oldResultCount); +// rowCountDelta = -(newResultCount - oldResultCount); + beginInsertRows(QModelIndex(), resultIdx + oldResultCount, resultIdx + newResultCount - 1); + rowCountDelta = 0; + endInsertRows(); + } + else + { +qDebug() << "Removing rows"; +// rowCountDelta = oldResultCount - newResultCount; + beginRemoveRows(QModelIndex(), resultIdx + newResultCount, resultIdx + oldResultCount - 1); + rowCountDelta = 0; + endRemoveRows(); + if (newResultCount) + updateResultsForRange(resultIdx, newResultCount); + } +*/ + if (oldCount != rowCount()) + setSelectedIndex(0); +} + +void ResultModel::updateResultsForRange(int start, int end) +{ +qDebug() << "Data changed" << start << (start+end-1); + QModelIndex sidx = index(start, 0); + QModelIndex eidx = index(start + end - 1, 0); + emit dataChanged(sidx, eidx); +} diff --git a/launcher/resultmodel.h b/launcher/resultmodel.h new file mode 100644 index 0000000..00a2639 --- /dev/null +++ b/launcher/resultmodel.h @@ -0,0 +1,62 @@ +#ifndef RESULTMODEL_H +#define RESULTMODEL_H + +#include +#include "reply.h" + +class Plugin; + +class ResultModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int selectedIndex READ selectedIndex WRITE setSelectedIndex NOTIFY selectedIndexChanged()) +public: + struct PluginResult { + bool isCurrent; + Plugin *plugin; + Result result; + }; + + enum Roles + { + PluginName = Qt::UserRole + 1, + ResultMatch, + ResultDescription, + ResultCommand, + IsCurrent + }; + + explicit ResultModel(QObject *parent = 0); + ~ResultModel(); + + QVariant data(const QModelIndex &index, int role) const; + + int rowCount(const QModelIndex & = QModelIndex()) const; + + int selectedIndex() const; + Result selectedResult() const; + +public slots: + void commandChanged(const QString &cmd); + void updateResults(); + + void setSelectedIndex(int idx); + + void clear(); + +signals: + void selectedIndexChanged(int idx); + +private: + void updateResultsForRange(int start, int end); + + QList pluginOrder; + QList results; + int rowCountDelta; + + int m_selectedIndex; + + bool emptyCommand; +}; + +#endif // RESULTMODEL_H diff --git a/launcher/tray.cpp b/launcher/tray.cpp new file mode 100644 index 0000000..cff1444 --- /dev/null +++ b/launcher/tray.cpp @@ -0,0 +1,8 @@ +#include "tray.h" + +#include + +Tray::Tray(QObject *parent) : + QSystemTrayIcon(parent) +{ +} diff --git a/launcher/tray.h b/launcher/tray.h new file mode 100644 index 0000000..a261203 --- /dev/null +++ b/launcher/tray.h @@ -0,0 +1,18 @@ +#ifndef TRAY_H +#define TRAY_H + +#include + +class Tray : public QSystemTrayIcon +{ + Q_OBJECT +public: + explicit Tray(QObject *parent = 0); + +signals: + +public slots: + +}; + +#endif // TRAY_H diff --git a/pluginapi/launcherpluginbase.cpp b/pluginapi/launcherpluginbase.cpp new file mode 100644 index 0000000..3b86aba --- /dev/null +++ b/pluginapi/launcherpluginbase.cpp @@ -0,0 +1,35 @@ +#include "launcherpluginbase.h" + +LauncherPluginBase::LauncherPluginBase(QObject *parent) : + QObject(parent) +{ +} + +LauncherPluginBase *LauncherPluginBase::plugin() +{ + return this; +} + +QStringList LauncherPluginBase::supportedPrefixes() const +{ + return QStringList(); +} + +bool LauncherPluginBase::handleNonPrefixedCommands() const +{ + return true; +} + +bool LauncherPluginBase::init() +{ + return true; +} + +void LauncherPluginBase::deinit() +{ +} + +void LauncherPluginBase::processCommand(const QString &cmd) +{ + handleCommand(cmd); +} diff --git a/pluginapi/launcherpluginbase.h b/pluginapi/launcherpluginbase.h new file mode 100644 index 0000000..52a60f6 --- /dev/null +++ b/pluginapi/launcherpluginbase.h @@ -0,0 +1,36 @@ +#ifndef LOADERPLUGINBASE_H +#define LOADERPLUGINBASE_H + +#include "pluginapi_global.h" +#include +#include "launcherplugininterface.h" + +#include "reply.h" + +class PLUGINAPISHARED_EXPORT LauncherPluginBase : public QObject, public LauncherPluginInterface +{ + Q_OBJECT + Q_INTERFACES(LauncherPluginInterface) +public: + explicit LauncherPluginBase(QObject *parent = 0); + + + LauncherPluginBase *plugin(); + + QStringList supportedPrefixes() const; + bool handleNonPrefixedCommands() const; + + bool init(); + void deinit(); + + virtual void handleCommand(const QString &cmd) = 0; + +public slots: + void processCommand(const QString &cmd); + +signals: + void replyReady(const Reply &match); + +}; + +#endif // LOADERPLUGINBASE_H diff --git a/pluginapi/launcherplugininterface.h b/pluginapi/launcherplugininterface.h new file mode 100644 index 0000000..356d7bb --- /dev/null +++ b/pluginapi/launcherplugininterface.h @@ -0,0 +1,27 @@ +#ifndef LAUNCHERPLUGIN_H +#define LAUNCHERPLUGIN_H + +#include "pluginapi_global.h" +#include + +class LauncherPluginBase; + +class PLUGINAPISHARED_EXPORT LauncherPluginInterface +{ +public: + virtual ~LauncherPluginInterface() {} + + virtual LauncherPluginBase *plugin() = 0; + + virtual QString name() const = 0; + + virtual QStringList supportedPrefixes() const = 0; + virtual bool handleNonPrefixedCommands() const = 0; + + virtual bool init() = 0; + virtual void deinit() = 0; +}; + +Q_DECLARE_INTERFACE(LauncherPluginInterface, "org.aptx.launcher.pluginapi/1.0") + +#endif // LAUNCHERPLUGIN_H diff --git a/pluginapi/loaderplugin.cpp b/pluginapi/loaderplugin.cpp new file mode 100644 index 0000000..63b2ce1 --- /dev/null +++ b/pluginapi/loaderplugin.cpp @@ -0,0 +1,6 @@ +#include "loaderplugin.h" + + +LoaderPluginInterface::LoaderPluginInterface() +{ +} diff --git a/pluginapi/pluginapi.pri b/pluginapi/pluginapi.pri new file mode 100644 index 0000000..98eac58 --- /dev/null +++ b/pluginapi/pluginapi.pri @@ -0,0 +1,4 @@ +INCLUDEPATH += $$PWD +DEPENDPATH += $$PWD +LIBS += -lpluginapi +LIBS += -L$$PWD/../build diff --git a/pluginapi/pluginapi.pro b/pluginapi/pluginapi.pro new file mode 100644 index 0000000..d4d1332 --- /dev/null +++ b/pluginapi/pluginapi.pro @@ -0,0 +1,18 @@ +QT -= gui + +DESTDIR = ../build +TARGET = pluginapi +TEMPLATE = lib + +DEFINES += PLUGINAPI_LIBRARY + +HEADERS += launcherplugininterface.h \ + pluginapi_global.h \ + launcherpluginbase.h \ + reply.h + + + +SOURCES += \ + launcherpluginbase.cpp \ + reply.cpp diff --git a/pluginapi/pluginapi_global.h b/pluginapi/pluginapi_global.h new file mode 100644 index 0000000..23ccb8c --- /dev/null +++ b/pluginapi/pluginapi_global.h @@ -0,0 +1,12 @@ +#ifndef PLUGINAPI_GLOBAL_H +#define PLUGINAPI_GLOBAL_H + +#include + +#if defined(PLUGINAPI_LIBRARY) +# define PLUGINAPISHARED_EXPORT Q_DECL_EXPORT +#else +# define PLUGINAPISHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // PLUGINAPI_GLOBAL_H diff --git a/pluginapi/reply.cpp b/pluginapi/reply.cpp new file mode 100644 index 0000000..1215555 --- /dev/null +++ b/pluginapi/reply.cpp @@ -0,0 +1,25 @@ +#include "reply.h" + +Result::Result() +{ + commandType = RunCommand; +} + +Result::Result(const QString &match_, const QString &description_, CommandType commandType_, + const QString &command_, const QStringList &args_) : + match(match_), description(description_), commandType(commandType_), + command(command_), args(args_) +{ +} + +Reply::Reply(const QVector &results_) : + results(results_) +{ +} + +void Reply::addResult(const Result &result) +{ + results.append(result); +} + + diff --git a/pluginapi/reply.h b/pluginapi/reply.h new file mode 100644 index 0000000..dc924c0 --- /dev/null +++ b/pluginapi/reply.h @@ -0,0 +1,37 @@ +#ifndef REPLY_H +#define REPLY_H + +#include "pluginapi_global.h" +#include +#include +#include + +enum CommandType { + RunCommand, + OpenCommand +}; + +struct PLUGINAPISHARED_EXPORT Result { + QString match; + QString description; + + CommandType commandType; + QString command; + QStringList args; + + explicit Result(); + Result(const QString &match_, const QString &description_ = QString(), + CommandType commandType_ = RunCommand, const QString &command_ = QString(), const QStringList &args_ = QStringList()); +}; + +struct PLUGINAPISHARED_EXPORT Reply { + QVector results; + + Reply(const QVector &results_ = QVector()); + void addResult(const Result &result); +}; + +Q_DECLARE_METATYPE(Reply) +Q_DECLARE_METATYPE(Reply*) + +#endif // MATCH_H diff --git a/plugins/localmylist/localmylist.pro b/plugins/localmylist/localmylist.pro new file mode 100644 index 0000000..ac0be1e --- /dev/null +++ b/plugins/localmylist/localmylist.pro @@ -0,0 +1,18 @@ +QT -= gui +QT += sql + +DESTDIR = ../../build +CONFIG += plugin +TARGET = localmylist_plugin +TEMPLATE = lib + +DEFINES += LOCALMYLISTPLUGIN_LIBRARY + +SOURCES += localmylistplugin.cpp + +HEADERS += localmylistplugin.h\ + localmylistplugin_global.h + +include(../../pluginapi/pluginapi.pri) + +LIBS += -llocalmylist diff --git a/plugins/localmylist/localmylistplugin.cpp b/plugins/localmylist/localmylistplugin.cpp new file mode 100644 index 0000000..d2aed82 --- /dev/null +++ b/plugins/localmylist/localmylistplugin.cpp @@ -0,0 +1,93 @@ +#include "localmylistplugin.h" + +#include + +#include + +using namespace LocalMyList; + +LocalMyListPlugin::LocalMyListPlugin(QObject *parent) : LauncherPluginBase(parent) +{ +} + +bool LocalMyListPlugin::init() +{ + MyList::MANUAL_CLEANUP = true; + + LocalMyList::instance()->loadLocalSettings(); + if (!LocalMyList::instance()->database()->connect()) + { + qDebug() << "Could not connect to database."; + return false; + } + return true; +} + +void LocalMyListPlugin::deinit() +{ + MyList::destroy(); +} + +QString LocalMyListPlugin::name() const +{ + return "LocalMyList"; +} + +void LocalMyListPlugin::handleCommand(const QString &cmd) +{ + Reply re; + + QSqlQuery &q = LocalMyList::instance()->database()->prepare( + "SELECT DISTINCT a.aid, b.title AS MainTitle, a.title AS searchTitle FROM anime_title a" + " LEFT JOIN anime_title b on b.aid = a.aid " +// " LEFT JOIN episode e ON e.aid = a.aid " +// " LEFT JOIN file f ON f.aid = a.aid " +// " LEFT JOIN file_location fl ON fl.fid = f.fid " + " WHERE lower(a.title) LIKE :title " + " AND b.type = 1 " + " ORDER BY a.title ASC, a.aid ASC " + ); + + q.bindValue(":title", cmd); + if (!LocalMyList::instance()->database()->exec(q)) + { + emit replyReady(re); + return; + } + addResults(re, q); + + q.bindValue(":title", cmd + "%"); + if (!LocalMyList::instance()->database()->exec(q)) + { + emit replyReady(re); + return; + } + addResults(re, q); + + q.bindValue(":title", "%" + cmd + "%"); + if (!LocalMyList::instance()->database()->exec(q)) + { + emit replyReady(re); + return; + } + addResults(re, q); + + emit replyReady(re); +} + +void LocalMyListPlugin::addResults(Reply &re, QSqlQuery &q) +{ + while(q.next()) + { + Result r; + r.match = q.value(2).toString(); + r.description = QString("Main title: %1").arg(q.value(1).toString()); +// r.commandType = OpenCommand; +// r.command = ofd.path; + re.addResult(r); + } + q.finish(); +} + +#include +Q_EXPORT_PLUGIN2(localmylist_plugin, LocalMyListPlugin) diff --git a/plugins/localmylist/localmylistplugin.h b/plugins/localmylist/localmylistplugin.h new file mode 100644 index 0000000..4bc2b39 --- /dev/null +++ b/plugins/localmylist/localmylistplugin.h @@ -0,0 +1,24 @@ +#ifndef LOCALMYLISTPLUGIN_H +#define LOCALMYLISTPLUGIN_H + +#include "localmylistplugin_global.h" +#include + +#include + +class LOCALMYLISTPLUGINSHARED_EXPORT LocalMyListPlugin : public LauncherPluginBase +{ +public: + LocalMyListPlugin(QObject *parent = 0); + + bool init(); + void deinit(); + + QString name() const; + void handleCommand(const QString &cmd); + +private: + void addResults(Reply &re, QSqlQuery &q); +}; + +#endif // LOCALMYLIST_H diff --git a/plugins/localmylist/localmylistplugin_global.h b/plugins/localmylist/localmylistplugin_global.h new file mode 100644 index 0000000..384c1ed --- /dev/null +++ b/plugins/localmylist/localmylistplugin_global.h @@ -0,0 +1,12 @@ +#ifndef LOCALMYLISTPLUGIN_GLOBAL_H +#define LOCALMYLISTPLUGIN_GLOBAL_H + +#include + +#if defined(LOCALMYLISTPLUGIN_LIBRARY) +# define LOCALMYLISTPLUGINSHARED_EXPORT Q_DECL_EXPORT +#else +# define LOCALMYLISTPLUGINSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // LOCALMYLISTPLUGIN_GLOBAL_H diff --git a/plugins/plugins.pro b/plugins/plugins.pro new file mode 100644 index 0000000..2097898 --- /dev/null +++ b/plugins/plugins.pro @@ -0,0 +1,5 @@ +TEMPLATE = subdirs +CONFIG += ordered + +SUBDIRS += test \ + localmylist diff --git a/plugins/test/test.cpp b/plugins/test/test.cpp new file mode 100644 index 0000000..e1a5b6f --- /dev/null +++ b/plugins/test/test.cpp @@ -0,0 +1,35 @@ +#include "test.h" + +Test::Test(QObject *parent) : LauncherPluginBase(parent) +{ +} + +QString Test::name() const +{ + return "Test"; +} + +void Test::handleCommand(const QString &cmd) +{ + Reply re; + if (cmd.isEmpty()) + { + emit replyReady(re); + return; + } + + int z=0; + for(int i =0; i<100000000;++i) z+=cmd.length(); + + Result r(cmd, "Echo for " + cmd + " z=" + QString::number(z), RunCommand, "cmd", QStringList("dir")); + + re.addResult(r); + r.match += " 1"; + re.addResult(r); + r.match += " 2"; + re.addResult(r); + emit replyReady(re); +} + +#include +Q_EXPORT_PLUGIN2(test_plugin, Test) diff --git a/plugins/test/test.h b/plugins/test/test.h new file mode 100644 index 0000000..2368856 --- /dev/null +++ b/plugins/test/test.h @@ -0,0 +1,17 @@ +#ifndef TEST_H +#define TEST_H + +#include "test_global.h" +#include + + +class TESTSHARED_EXPORT Test : public LauncherPluginBase +{ +public: + Test(QObject *parent = 0); + + QString name() const; + void handleCommand(const QString &cmd); +}; + +#endif // TEST_H diff --git a/plugins/test/test.pro b/plugins/test/test.pro new file mode 100644 index 0000000..144986d --- /dev/null +++ b/plugins/test/test.pro @@ -0,0 +1,24 @@ +QT -= gui + +DESTDIR = ../../build +CONFIG += plugin +TARGET = test_plugin +TEMPLATE = lib + +DEFINES += TEST_LIBRARY + +SOURCES += test.cpp + +HEADERS += test.h\ + test_global.h + +unix:!symbian { + maemo5 { + target.path = /opt/usr/lib + } else { + target.path = /usr/lib + } + INSTALLS += target +} + +include(../../pluginapi/pluginapi.pri) diff --git a/plugins/test/test_global.h b/plugins/test/test_global.h new file mode 100644 index 0000000..d1f81ca --- /dev/null +++ b/plugins/test/test_global.h @@ -0,0 +1,12 @@ +#ifndef TEST_GLOBAL_H +#define TEST_GLOBAL_H + +#include + +#if defined(TEST_LIBRARY) +# define TESTSHARED_EXPORT Q_DECL_EXPORT +#else +# define TESTSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // TEST_GLOBAL_H