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