@ -0,0 +1,75 @@ | |||
# This file is used to ignore files which are generated | |||
# ---------------------------------------------------------------------------- | |||
*~ | |||
*.autosave | |||
*.a | |||
*.core | |||
*.moc | |||
*.o | |||
*.obj | |||
*.orig | |||
*.rej | |||
*.so | |||
*.so.* | |||
*_pch.h.cpp | |||
*_resource.rc | |||
*.qm | |||
.#* | |||
*.*# | |||
core | |||
!core/ | |||
tags | |||
.DS_Store | |||
.directory | |||
*.debug | |||
Makefile* | |||
*.prl | |||
*.app | |||
moc_*.cpp | |||
ui_*.h | |||
qrc_*.cpp | |||
Thumbs.db | |||
*.res | |||
*.rc | |||
/.qmake.cache | |||
/.qmake.stash | |||
# qtcreator generated files | |||
*.pro.user* | |||
# xemacs temporary files | |||
*.flc | |||
# Vim temporary files | |||
.*.swp | |||
# Visual Studio generated files | |||
*.ib_pdb_index | |||
*.idb | |||
*.ilk | |||
*.pdb | |||
*.sln | |||
*.suo | |||
*.vcproj | |||
*vcproj.*.*.user | |||
*.ncb | |||
*.sdf | |||
*.opensdf | |||
*.vcxproj | |||
*vcxproj.* | |||
# MinGW generated files | |||
*.Debug | |||
*.Release | |||
# Python byte code | |||
*.pyc | |||
# Binaries | |||
# -------- | |||
*.dll | |||
*.exe | |||
build | |||
.vscode |
@ -0,0 +1,54 @@ | |||
cmake_minimum_required(VERSION 3.14) | |||
project(reactionmanager VERSION 0.1 LANGUAGES CXX) | |||
set(CMAKE_INCLUDE_CURRENT_DIR ON) | |||
set(CMAKE_AUTOUIC ON) | |||
set(CMAKE_AUTOMOC ON) | |||
set(CMAKE_AUTORCC ON) | |||
set(CMAKE_CXX_STANDARD 11) | |||
set(CMAKE_CXX_STANDARD_REQUIRED ON) | |||
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Quick REQUIRED) | |||
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Quick REQUIRED) | |||
set(PROJECT_SOURCES | |||
src/main.cpp | |||
src/clipboard.hpp | |||
qml.qrc | |||
) | |||
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) | |||
qt_add_executable(reactionmanager | |||
MANUAL_FINALIZATION | |||
${PROJECT_SOURCES} | |||
) | |||
else() | |||
if(ANDROID) | |||
add_library(reactionmanager SHARED | |||
${PROJECT_SOURCES} | |||
) | |||
else() | |||
add_executable(reactionmanager | |||
${PROJECT_SOURCES} | |||
) | |||
endif() | |||
endif() | |||
target_compile_definitions(reactionmanager | |||
PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>) | |||
target_link_libraries(reactionmanager | |||
PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Quick) | |||
set_target_properties(reactionmanager PROPERTIES | |||
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com | |||
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} | |||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} | |||
) | |||
if(QT_VERSION_MAJOR EQUAL 6) | |||
qt_import_qml_plugins(reactionmanager) | |||
qt_finalize_executable(reactionmanager) | |||
endif() |
@ -0,0 +1,22 @@ | |||
reactionmanager | |||
=============== | |||
don't use this lol | |||
Building | |||
-------- | |||
```sh | |||
mkdir -p build; cd build | |||
cmake -DCMAKE_BUILD_TYPE=Release .. | |||
make | |||
``` | |||
Usage | |||
----- | |||
``` | |||
reactionmanager path/to/directory | |||
``` | |||
all of the files need to be in the same level of the directory hierarchy so if you like to organise things you're screwed |
@ -0,0 +1,6 @@ | |||
<RCC> | |||
<qresource prefix="/"> | |||
<file>src/main.qml</file> | |||
<file>src/widgets/Reaction.qml</file> | |||
</qresource> | |||
</RCC> |
@ -0,0 +1,35 @@ | |||
#pragma once | |||
#include <QClipboard> | |||
#include <QObject> | |||
#include <QImage> | |||
#include <QUrl> | |||
#include <QProcess> | |||
#include <iostream> | |||
#include <QGuiApplication> | |||
class Clipboard : public QObject { | |||
Q_OBJECT | |||
public: | |||
Clipboard(QGuiApplication *owner, const char *app) : _app(app), _owner(owner) { } | |||
virtual ~Clipboard() {}; | |||
Q_INVOKABLE void setClipboard(QUrl str) { | |||
QProcess proc; | |||
proc.setProgram(QString(_app)); | |||
QStringList args{"clipboard", str.path()}; | |||
proc.setArguments(args); | |||
qint64 pid; | |||
proc.startDetached(&pid); | |||
std::cout << "clipboard owner " << _app << " started: " << pid << std::endl; | |||
_owner->quit(); | |||
} | |||
private: | |||
const char *_app; | |||
QGuiApplication *_owner; | |||
}; |
@ -0,0 +1,53 @@ | |||
#include <QGuiApplication> | |||
#include <QQmlApplicationEngine> | |||
#include <qqmlcontext.h> | |||
#include <QClipboard> | |||
#include "sys/stat.h" | |||
#include "clipboard.hpp" | |||
int selection(int argc, char *argv[]) { | |||
QGuiApplication app(argc, argv); | |||
QQmlApplicationEngine engine; | |||
const QUrl url(QStringLiteral("qrc:/src/main.qml")); | |||
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, | |||
&app, [url](QObject *obj, const QUrl &objUrl) { | |||
if (!obj && url == objUrl) | |||
QCoreApplication::exit(-1); | |||
}, Qt::QueuedConnection); | |||
engine.load(url); | |||
engine.rootContext()->setContextProperty("clipboard", new Clipboard(&app, argv[0])); | |||
return app.exec(); | |||
} | |||
int clipboard(int argc, char *argv[]) { | |||
QGuiApplication app(argc, argv); | |||
QClipboard *clip = QGuiApplication::clipboard(); | |||
struct stat buf; | |||
if (stat(argv[2], &buf) == -1) { | |||
perror("stat"); | |||
exit(1); | |||
} | |||
QString path(argv[2]); | |||
QImage image(path); | |||
clip->setImage(image); | |||
clip->connect(clip, &QClipboard::dataChanged, [&]() { | |||
app.quit(); | |||
}); | |||
return app.exec(); | |||
} | |||
int main(int argc, char *argv[]) { | |||
if (argc >= 3 && strcmp(argv[1], "clipboard") == 0) { | |||
return clipboard(argc, argv); | |||
} else { | |||
return selection(argc, argv); | |||
} | |||
} |
@ -0,0 +1,144 @@ | |||
import QtQuick 2.15 | |||
import QtQuick.Window 2.15 | |||
import QtQuick.Controls 2.15 | |||
import QtQuick.Layouts 1.3 | |||
import Qt.labs.folderlistmodel 2.15 | |||
import QtQml.Models 2.15 | |||
import "qrc:/src/widgets" | |||
Window { | |||
width: 700 | |||
height: 500 | |||
visible: true | |||
title: qsTr("reaction image manager") | |||
flags: Qt.Dialog | |||
id: window; | |||
Component { | |||
id: delegate | |||
Item { | |||
width: imageList.cellWidth | |||
height: imageList.cellHeight | |||
clip: true | |||
Column { | |||
anchors.fill: parent | |||
Reaction { | |||
anchors.horizontalCenter: parent.horizontalCenter | |||
path: fileUrl | |||
width: Math.min(parent.width, parent.height) - label.height | |||
height: Math.min(parent.width, parent.height) - label.height | |||
} | |||
Text { | |||
id: label | |||
anchors.horizontalCenter: parent.horizontalCenter | |||
text: fileName | |||
} | |||
} | |||
} | |||
} | |||
Column { | |||
anchors.fill: parent | |||
TextField { | |||
id: text | |||
placeholderText: qsTr("Search") | |||
focus: true | |||
height: 40 | |||
width: parent.width | |||
onTextChanged: () => { | |||
backingModel.nameFilters = makeFilter(text.text) | |||
imageList.currentIndex = 0 | |||
imageList.moveCurrentIndexRight() | |||
} | |||
Keys.onPressed: { | |||
if (event.key == Qt.Key_Tab || event.key == Qt.Key_Right) { | |||
event.accepted = true | |||
imageList.moveCurrentIndexRight() | |||
} else if (event.key == Qt.Key_Up) { | |||
event.accepted = true | |||
imageList.moveCurrentIndexUp() | |||
} else if (event.key == Qt.Key_Down) { | |||
event.accepted = true | |||
imageList.moveCurrentIndexDown() | |||
} else if (event.key == Qt.Key_Left) { | |||
event.accepted = true | |||
imageList.moveCurrentIndexLeft() | |||
} else if (event.key == Qt.Key_Escape) { | |||
Qt.quit() | |||
} | |||
} | |||
onAccepted: { | |||
clipboard.setClipboard(backingModel.get(imageList.currentIndex, 'fileUrl') || backingModel.get(0, 'fileUrl')); | |||
} | |||
} | |||
Row { | |||
width: parent.width | |||
height: parent.height - 40 | |||
Rectangle { | |||
width: 2 * (parent.width / 3) | |||
height: parent.height | |||
GridView { | |||
id: imageList | |||
anchors.fill: parent | |||
clip: true | |||
keyNavigationWraps: true | |||
flickableDirection: Flickable.AutoFlickDirection | |||
cellWidth: parent.width / 3; | |||
cellHeight: imageList.cellWidth | |||
snapMode: GridView.SnapToRow | |||
model: FolderListModel { | |||
id: backingModel | |||
nameFilters: makeFilter("") | |||
folder: "file:" + Qt.application.arguments[1] | |||
} | |||
delegate: delegate | |||
highlight: Rectangle { | |||
color: "lightsteelblue" | |||
radius: 5 | |||
} | |||
} | |||
} | |||
Reaction { | |||
width: parent.width / 3 | |||
height: parent.height | |||
path: backingModel.get(imageList.currentIndex, 'fileUrl') || backingModel.get(0, 'fileUrl') | |||
} | |||
} | |||
} | |||
function makeFilter(text) { | |||
if (text.indexOf("*") !== -1) { | |||
return [text] | |||
} | |||
return [ | |||
"*" + text + "*.png", | |||
"*" + text + "*.gif", | |||
"*" + text + "*.jpg", | |||
"*" + text + "*.jpeg", | |||
] | |||
} | |||
} |
@ -0,0 +1,39 @@ | |||
import QtQuick 2.15 | |||
Item { | |||
id: container | |||
property alias path: image.source | |||
MouseArea { | |||
id: mouseArea | |||
width: container.width; height: container.height | |||
anchors.fill: container; | |||
drag.target: image; | |||
AnimatedImage { | |||
id: image | |||
anchors.fill: parent | |||
fillMode: Image.PreserveAspectFit | |||
Drag.active: mouseArea.drag.active | |||
Drag.hotSpot.x: 0 | |||
Drag.hotSpot.y: 0 | |||
Drag.mimeData: { | |||
"text/uri-list": container.path | |||
} | |||
Drag.dragType: Drag.Automatic | |||
Drag.onDragFinished: { | |||
Qt.quit() | |||
} | |||
onStatusChanged: { | |||
if (image.status === Image.Failed) { | |||
container.visible = false | |||
} | |||
} | |||
} | |||
} | |||
} |