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