@ -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 | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |