Revised Tagging interface (#23)

main
Joel Smith 2020-07-14 00:43:10 -07:00 committed by GitHub
parent cc17b794ee
commit 7b5d4baffd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1113 additions and 343 deletions

View File

@ -26,9 +26,12 @@ SOURCES += \
src/components/error_view/errorview.cpp \
src/components/evidence_editor/evidenceeditor.cpp \
src/components/evidencepreview.cpp \
src/components/flow_layout/flowlayout.cpp \
src/components/loading/qprogressindicator.cpp \
src/components/loading_button/loadingbutton.cpp \
src/components/tag_editor/tageditor.cpp \
src/components/tagging/tageditor.cpp \
src/components/tagging/tagview.cpp \
src/components/tagging/tagwidget.cpp \
src/db/databaseconnection.cpp \
src/forms/evidence_filter/evidencefilter.cpp \
src/forms/evidence_filter/evidencefilterform.cpp \
@ -56,9 +59,13 @@ HEADERS += \
src/components/evidence_editor/evidenceeditor.h \
src/components/evidence_editor/saveevidenceresponse.h \
src/components/evidencepreview.h \
src/components/flow_layout/flowlayout.h \
src/components/loading/qprogressindicator.h \
src/components/loading_button/loadingbutton.h \
src/components/tag_editor/tageditor.h \
src/components/tagging/tageditor.h \
src/components/tagging/tagginglineediteventfilter.h \
src/components/tagging/tagview.h \
src/components/tagging/tagwidget.h \
src/db/databaseconnection.h \
src/exceptions/databaseerr.h \
src/exceptions/fileerror.h \

View File

@ -7,6 +7,8 @@
#include <QSettings>
#include <QString>
#include "models/tag.h"
// AppSettings is a singleton construct for accessing the application's settings. This is different
// from configuration, as it represents the application's state, rather than how the application
// communicates.
@ -29,6 +31,7 @@ class AppSettings : public QObject {
const char *opSlugSetting = "operation/slug";
const char *opNameSetting = "operation/name";
const char *lastUsedTagsSetting = "gather/tags";
AppSettings() : QObject(nullptr) {}
@ -51,5 +54,13 @@ class AppSettings : public QObject {
}
QString operationSlug() { return settings.value(opSlugSetting).toString(); }
QString operationName() { return settings.value(opNameSetting).toString(); }
void setLastUsedTags(std::vector<model::Tag> lastTags) {
settings.setValue(lastUsedTagsSetting, QVariant::fromValue(lastTags));
}
std::vector<model::Tag> getLastUsedTags() {
auto val = settings.value(lastUsedTagsSetting);
return qvariant_cast<std::vector<model::Tag>>(val);
}
};
#endif // APPSETTINGS_H

View File

@ -91,7 +91,7 @@ model::Evidence EvidenceEditor::encodeEvidence() {
void EvidenceEditor::setEnabled(bool enable) {
// if the product is enabled, then we can edit, hence it's not readonly
descriptionTextBox->setReadOnly(!enable);
tagEditor->setEnabled(enable);
tagEditor->setReadonly(!enable);
if (loadedPreview != nullptr) {
loadedPreview->setReadonly(!enable);
}
@ -124,13 +124,7 @@ void EvidenceEditor::loadData() {
loadedPreview->setReadonly(readonly);
// get all remote tags (for op)
std::vector<qint64> initialTagIDs;
initialTagIDs.reserve(originalEvidenceData.tags.size());
for (const model::Tag &tag : originalEvidenceData.tags) {
initialTagIDs.push_back(tag.serverTagId);
}
tagEditor->loadTags(operationSlug, initialTagIDs);
tagEditor->loadTags(operationSlug, originalEvidenceData.tags);
}
catch (QSqlError &e) {
loadedPreview = new ErrorView("Unable to load evidence: " + e.text(), this);
@ -157,12 +151,7 @@ void EvidenceEditor::clearEditor() {
}
void EvidenceEditor::onTagsLoaded(bool success) {
if (!success) {
tagEditor->setEnabled(false);
}
else {
tagEditor->setEnabled(!readonly);
}
tagEditor->setReadonly(!success || readonly);
emit onWidgetReady();
}

View File

@ -9,14 +9,15 @@
#include <QString>
#include <QTextEdit>
#include <QWidget>
#include <QSplitter>
#include "components/evidencepreview.h"
#include "components/tag_editor/tageditor.h"
#include "db/databaseconnection.h"
#include "deleteevidenceresponse.h"
#include "saveevidenceresponse.h"
#include <QSplitter>
#include "components/tagging/tageditor.h"
class EvidenceEditor : public QWidget {
Q_OBJECT

View File

@ -0,0 +1,179 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "flowlayout.h"
#include <QtWidgets>
// Local Edits:
// 1. Formatting
FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing)
: QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing)
{
setContentsMargins(margin, margin, margin, margin);
}
FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing)
: m_hSpace(hSpacing), m_vSpace(vSpacing)
{
setContentsMargins(margin, margin, margin, margin);
}
FlowLayout::~FlowLayout() {
QLayoutItem *item;
while ((item = takeAt(0)))
delete item;
}
void FlowLayout::addItem(QLayoutItem *item) {
itemList.append(item);
}
int FlowLayout::horizontalSpacing() const {
return (m_hSpace >= 0) ? m_hSpace : smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
}
int FlowLayout::verticalSpacing() const {
return (m_vSpace >= 0) ? m_vSpace : smartSpacing(QStyle::PM_LayoutVerticalSpacing);
}
int FlowLayout::count() const {
return itemList.size();
}
QLayoutItem *FlowLayout::itemAt(int index) const {
return itemList.value(index);
}
QLayoutItem *FlowLayout::takeAt(int index) {
if (index >= 0 && index < itemList.size()) {
return itemList.takeAt(index);
}
return nullptr;
}
Qt::Orientations FlowLayout::expandingDirections() const { return 0; }
bool FlowLayout::hasHeightForWidth() const { return true; }
int FlowLayout::heightForWidth(int width) const {
return doLayout(QRect(0, 0, width, 0), true);
}
void FlowLayout::setGeometry(const QRect &rect) {
QLayout::setGeometry(rect);
doLayout(rect, false);
}
QSize FlowLayout::sizeHint() const {
return minimumSize();
}
QSize FlowLayout::minimumSize() const {
QSize size;
for (const QLayoutItem *item : qAsConst(itemList))
size = size.expandedTo(item->minimumSize());
const QMargins margins = contentsMargins();
size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom());
return size;
}
int FlowLayout::doLayout(const QRect &rect, bool testOnly) const {
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom);
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
int x = effectiveRect.x();
int y = effectiveRect.y();
int lineHeight = 0;
for (QLayoutItem *item : qAsConst(itemList)) {
const QWidget *wid = item->widget();
int spaceX = horizontalSpacing();
if (spaceX == -1) {
spaceX = wid->style()->layoutSpacing(
QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
}
int spaceY = verticalSpacing();
if (spaceY == -1) {
spaceY = wid->style()->layoutSpacing(
QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
}
int nextX = x + item->sizeHint().width() + spaceX;
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
x = effectiveRect.x();
y = y + lineHeight + spaceY;
nextX = x + item->sizeHint().width() + spaceX;
lineHeight = 0;
}
if (!testOnly) {
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
}
x = nextX;
lineHeight = qMax(lineHeight, item->sizeHint().height());
}
return y + lineHeight - rect.y() + bottom;
}
int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const {
QObject *parent = this->parent();
if (!parent) {
return -1;
}
else if (parent->isWidgetType()) {
auto pw = static_cast<QWidget *>(parent);
return pw->style()->pixelMetric(pm, nullptr, pw);
}
return static_cast<QLayout *>(parent)->spacing();
}

View File

@ -0,0 +1,87 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef FLOWLAYOUT_H
#define FLOWLAYOUT_H
#include <QLayout>
#include <QRect>
#include <QStyle>
class FlowLayout : public QLayout
{
public:
explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1);
explicit FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1);
~FlowLayout();
void addItem(QLayoutItem *item) override;
int horizontalSpacing() const;
int verticalSpacing() const;
Qt::Orientations expandingDirections() const override;
bool hasHeightForWidth() const override;
int heightForWidth(int) const override;
int count() const override;
QLayoutItem *itemAt(int index) const override;
QSize minimumSize() const override;
void setGeometry(const QRect &rect) override;
QSize sizeHint() const override;
QLayoutItem *takeAt(int index) override;
private:
int doLayout(const QRect &rect, bool testOnly) const;
int smartSpacing(QStyle::PixelMetric pm) const;
QList<QLayoutItem *> itemList;
int m_hSpace;
int m_vSpace;
};
#endif // FLOWLAYOUT_H

View File

@ -1,240 +0,0 @@
#include "tageditor.h"
#include <QRandomGenerator>
#include "dtos/tag.h"
#include "helpers/netman.h"
#include "helpers/stopreply.h"
TagEditor::TagEditor(QWidget *parent) : QWidget(parent) {
buildUi();
couldNotCreateTagMsg = new QErrorMessage(this);
wireUi();
}
TagEditor::~TagEditor() {
delete _withTagsLabel;
delete createTagTextBox;
delete includedTagsListBox;
delete allTagsListBox;
delete includeTagButton;
delete excludeTagButton;
delete couldNotCreateTagMsg;
delete createTagButton;
delete gridLayout;
stopReply(&getTagsReply);
stopReply(&createTagReply);
}
void TagEditor::buildUi() {
gridLayout = new QGridLayout(this);
gridLayout->setMargin(0);
_withTagsLabel = new QLabel("With Tags...", this);
includedTagsListBox = new QListWidget(this);
allTagsListBox = new QListWidget(this);
includeTagButton = new QPushButton("<<", this);
excludeTagButton = new QPushButton(">>", this);
createTagTextBox = new QLineEdit();
createTagButton = new LoadingButton("Create Tag", this);
minorErrorLabel = new QLabel(this);
// Layout
/* 0 1 2 3
+----------+-----------+----------+---------+
0 | With Tags label |
+----------+-----------+----------+---------+
1 | Include | << btn | All |
+ +-----------+ + +
2 | Tag List | >> Btn | Tags List |
+----------+-----------+----------+---------+
3 | err Lab | <empty> | Add TB | add btn |
+----------+-----------+----------+---------+
*/
// row 0
gridLayout->addWidget(_withTagsLabel, 0, 0, 1, gridLayout->columnCount());
// row 1
gridLayout->addWidget(includedTagsListBox, 1, 0, 2, 1);
gridLayout->addWidget(includeTagButton, 1, 1);
gridLayout->addWidget(allTagsListBox, 1, 2, 2, 2);
// row 2
gridLayout->addWidget(excludeTagButton, 2, 1);
// row 3
gridLayout->addWidget(minorErrorLabel, 3, 0);
gridLayout->addWidget(createTagTextBox, 3, 2);
gridLayout->addWidget(createTagButton, 3, 3);
this->setLayout(gridLayout);
}
void TagEditor::wireUi() {
auto btnClicked = &QPushButton::clicked;
connect(createTagButton, btnClicked, this, &TagEditor::createTagButtonClicked);
connect(includeTagButton, btnClicked, this, &TagEditor::includeTagButtonClicked);
connect(excludeTagButton, btnClicked, this, &TagEditor::excludeTagButtonClicked);
connect(createTagTextBox, &QLineEdit::returnPressed, this, &TagEditor::createTagButtonClicked);
allTagsListBox->setSortingEnabled(true);
}
void TagEditor::clear() {
stopReply(&getTagsReply);
stopReply(&createTagReply);
createTagTextBox->setText("");
allTagsListBox->clear();
includedTagsListBox->clear();
minorErrorLabel->setText("");
}
void TagEditor::setEnabled(bool enable) {
createTagButton->setEnabled(enable);
includeTagButton->setEnabled(enable);
excludeTagButton->setEnabled(enable);
createTagTextBox->setEnabled(enable);
}
void TagEditor::loadTags(const QString& operationSlug, std::vector<qint64> initialTagIDs) {
this->operationSlug = operationSlug;
includedTagIds.clear();
for (auto tagID : initialTagIDs) {
includedTagIds.insert(tagID);
}
getTagsReply = NetMan::getInstance().getOperationTags(operationSlug);
connect(getTagsReply, &QNetworkReply::finished, this, &TagEditor::onGetTagsComplete);
}
void TagEditor::refreshTagBoxes() {
allTagsListBox->clear();
includedTagsListBox->clear();
for (const auto &itr : knownTags) {
QString tagText = itr.first;
qint64 tagId = itr.second;
int itemCount = includedTagIds.count(tagId);
if (itemCount == 1) {
includedTagsListBox->addItem(tagText);
}
else {
allTagsListBox->addItem(tagText);
}
}
}
std::vector<model::Tag> TagEditor::getIncludedTags() {
std::vector<model::Tag> rtn;
rtn.reserve(includedTagIds.size());
// Construct a reverse map to find tag names.
// slightly inefficient way to do this, but much easier to code against.
std::unordered_map<qint64, QString> reversedMap;
for (const auto &entry : knownTags) {
reversedMap.insert(std::pair<qint64, QString>(entry.second, entry.first));
}
for (const qint64 &tagID : includedTagIds) {
try {
auto tagName = reversedMap.at(tagID);
model::Tag tag(tagID, tagName);
rtn.push_back(tag);
}
catch (std::out_of_range &e) {
} // drop any tag ids we can't find (doesn't exist on the server, and will fail anyway)
}
return rtn;
}
void TagEditor::createTagButtonClicked() {
auto newText = createTagTextBox->text().trimmed();
if (newText == "") {
return;
}
createTagButton->startAnimation();
createTagButton->setEnabled(false);
dto::Tag newTag(newText, randomColor());
createTagReply = NetMan::getInstance().createTag(newTag, operationSlug);
connect(createTagReply, &QNetworkReply::finished, this, &TagEditor::onCreateTagComplete);
}
void TagEditor::includeTagButtonClicked() {
auto items = allTagsListBox->selectedItems();
for (auto item : items) {
includedTagsListBox->addItem(item->text());
allTagsListBox->takeItem(allTagsListBox->row(item));
includedTagIds.insert(knownTags[item->text()]);
}
}
void TagEditor::excludeTagButtonClicked() {
auto items = includedTagsListBox->selectedItems();
for (auto item : items) {
allTagsListBox->addItem(item->text());
includedTagsListBox->takeItem(includedTagsListBox->row(item));
includedTagIds.erase(knownTags[item->text()]);
}
}
void TagEditor::onGetTagsComplete() {
bool isValid;
auto data = NetMan::extractResponse(getTagsReply, isValid);
if (isValid) {
std::vector<dto::Tag> tags = dto::Tag::parseDataAsList(data);
knownTags.clear();
for (const dto::Tag &tag : tags) {
knownTags.insert(std::pair<QString, qint64>(tag.name, tag.id));
}
refreshTagBoxes();
}
else {
minorErrorLabel->setText(tr("Unable to fetch tags. Please check your connection."));
includeTagButton->setEnabled(false);
}
disconnect(getTagsReply, &QNetworkReply::finished, this, &TagEditor::onGetTagsComplete);
tidyReply(&getTagsReply);
emit tagsLoaded(isValid);
}
void TagEditor::onCreateTagComplete() {
bool isValid;
auto data = NetMan::extractResponse(createTagReply, isValid);
if (isValid) {
auto newTag = dto::Tag::parseData(data);
knownTags.insert(std::pair<QString, qint64>(newTag.name, newTag.id));
includedTagIds.insert(newTag.id);
createTagTextBox->setText("");
refreshTagBoxes();
}
else {
couldNotCreateTagMsg->showMessage(
"Could not create tag. Please check your connection and try again.");
}
disconnect(createTagReply, &QNetworkReply::finished, this, &TagEditor::onCreateTagComplete);
tidyReply(&createTagReply);
createTagButton->stopAnimation();
createTagButton->setEnabled(true);
}
QString TagEditor::randomColor() {
// Note: this should match the frontend's color palette (naming)
static std::vector<QString> colors = {
"blue", "yellow", "green", "indigo", "orange",
"lightBlue", "lightYellow", "lightGreen", "lightIndigo", "lightOrange",
"pink", "red", "teal", "vermilion", "violet",
"lightPink", "lightRed", "lightTeal", "lightVermilion", "lightViolet"};
auto index = QRandomGenerator::global()->bounded(int(colors.size()));
return colors.at(index);
}

View File

@ -1,70 +0,0 @@
#ifndef TAGEDITOR_H
#define TAGEDITOR_H
#include <QErrorMessage>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QListWidget>
#include <QNetworkReply>
#include <QPushButton>
#include <QWidget>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "components/loading_button/loadingbutton.h"
#include "models/tag.h"
class TagEditor : public QWidget {
Q_OBJECT
public:
explicit TagEditor(QWidget* parent = nullptr);
~TagEditor();
public:
void setEnabled(bool enable);
void loadTags(const QString& operationSlug, std::vector<qint64> initialTagIDs);
void clear();
std::vector<model::Tag> getIncludedTags();
signals:
void tagsLoaded(bool success);
private:
void buildUi();
void wireUi();
void refreshTagBoxes();
QString randomColor();
private slots:
void createTagButtonClicked();
void includeTagButtonClicked();
void excludeTagButtonClicked();
void onGetTagsComplete();
void onCreateTagComplete();
private:
QString operationSlug;
std::unordered_map<QString, qint64> knownTags;
std::unordered_set<qint64> includedTagIds;
// ui Components
QGridLayout* gridLayout;
QLabel* _withTagsLabel;
LoadingButton* createTagButton;
QLineEdit* createTagTextBox;
QListWidget* includedTagsListBox;
QListWidget* allTagsListBox;
QPushButton* includeTagButton;
QPushButton* excludeTagButton;
QLabel* minorErrorLabel;
QNetworkReply* getTagsReply = nullptr;
QNetworkReply* createTagReply = nullptr;
QErrorMessage* couldNotCreateTagMsg = nullptr;
};
#endif // TAGEDITOR_H

View File

@ -0,0 +1,228 @@
#include "components/tagging/tageditor.h"
#include <QAbstractItemView>
#include <QLineEdit>
#include <QStringListModel>
#include <QTimer>
#include <algorithm>
#include "helpers/netman.h"
#include "helpers/stopreply.h"
TagEditor::TagEditor(QWidget *parent) : QWidget(parent) {
buildUi();
wireUi();
}
TagEditor::~TagEditor() {
delete couldNotCreateTagMsg;
delete loading;
delete tagCompleteTextBox;
delete errorLabel;
delete tagView;
delete gridLayout;
delete completer;
stopReply(&getTagsReply);
stopReply(&createTagReply);
}
void TagEditor::buildUi() {
gridLayout = new QGridLayout(this);
gridLayout->setMargin(0);
couldNotCreateTagMsg = new QErrorMessage(this);
tagView = new TagView(this);
errorLabel = new QLabel(this);
loading = new QProgressIndicator(this);
completer = new QCompleter(this);
completer->setCompletionMode(QCompleter::PopupCompletion);
completer->setFilterMode(Qt::MatchContains);
completer->setCaseSensitivity(Qt::CaseInsensitive);
tagCompleteTextBox = new QLineEdit(this);
tagCompleteTextBox->setPlaceholderText("Add Tags...");
tagCompleteTextBox->installEventFilter(&filter);
tagCompleteTextBox->setCompleter(completer);
tagCompleteTextBox->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed));
// Layout
/* 0 1 2
+----------------+-------------+--------------+
0 | |
| Flow Tag List |
| |
+----------------+-------------+--------------+
1 | errLbl/Loading | <None> | [Add Tag TB] |
+----------------+-------------+--------------+
*/
// row 0
gridLayout->addWidget(tagView, 0, 0, 1, 3);
// row 1
gridLayout->addWidget(errorLabel, 1, 0);
gridLayout->addWidget(loading, 1, 0);
gridLayout->addWidget(tagCompleteTextBox, 1, 2);
this->setLayout(gridLayout);
}
void TagEditor::wireUi() {
connect(tagCompleteTextBox, &QLineEdit::returnPressed, this, &TagEditor::tagEditReturnPressed);
connect(&filter, &TaggingLineEditEventFilter::upPressed, this, &TagEditor::showCompleter);
connect(&filter, &TaggingLineEditEventFilter::downPressed, this, &TagEditor::showCompleter);
connect(&filter, &TaggingLineEditEventFilter::completePressed, this, &TagEditor::showCompleter);
connect(&filter, &TaggingLineEditEventFilter::leftMouseClickPressed, this, &TagEditor::showCompleter);
connect(completer, QOverload<const QString &>::of(&QCompleter::activated), this,
&TagEditor::completerActivated);
connect(tagCompleteTextBox, &QLineEdit::textChanged, [this](const QString &text) {
if (text.isEmpty()) {
tagCompleteTextBox->completer()->setCompletionPrefix("");
}
});
}
void TagEditor::completerActivated(const QString &text) {
tagTextEntered(text);
QTimer::singleShot(0, tagCompleteTextBox, &QLineEdit::clear);
}
void TagEditor::tagEditReturnPressed() {
if (completer->popup()->isVisible()) {
return;
}
tagTextEntered(tagCompleteTextBox->text().trimmed());
}
void TagEditor::tagTextEntered(QString text) {
if (text.isEmpty()) {
return;
}
auto foundTag = tagMap.find(standardizeTagKey(text));
if (foundTag == tagMap.end()) {
createTag(text);
}
else {
dto::Tag data = foundTag->second;
tagView->contains(data) ? tagView->remove(data) : tagView->addTag(data);
}
tagCompleteTextBox->setText("");
tagCompleteTextBox->completer()->setCompletionPrefix("");
}
void TagEditor::updateCompleterModel() {
tagNames.sort(Qt::CaseInsensitive);
// no need to delete previous model -- handled by qcompleter
completer->setModel(new QStringListModel(tagNames, completer));
completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
}
void TagEditor::clear() {
stopReply(&getTagsReply);
stopReply(&createTagReply);
tagCompleteTextBox->clear();
errorLabel->setText("");
tagView->clear();
}
void TagEditor::loadTags(const QString &operationSlug, std::vector<model::Tag> initialTags) {
this->operationSlug = operationSlug;
this->initialTags = initialTags;
getTagsReply = NetMan::getInstance().getOperationTags(operationSlug);
connect(getTagsReply, &QNetworkReply::finished, this, &TagEditor::onGetTagsComplete);
}
void TagEditor::onGetTagsComplete() {
bool isValid;
auto data = NetMan::extractResponse(getTagsReply, isValid);
tagMap.clear();
tagNames.clear();
if (isValid) {
std::vector<dto::Tag> tags = dto::Tag::parseDataAsList(data);
for (auto tag : tags) {
addTag(tag);
auto itr = std::find_if(initialTags.begin(), initialTags.end(), [tag](model::Tag modelTag) {
return modelTag.serverTagId == tag.id;
});
if (itr != initialTags.end()) {
tagView->addTag(tag);
}
}
updateCompleterModel();
}
else {
errorLabel->setText(
tr("Unable to fetch tags."
" Please check your connection."
" (Tags names and colors may be incorrect)"));
tagCompleteTextBox->setEnabled(false);
for (auto tag : initialTags) {
tagView->addTag(dto::Tag::fromModelTag(tag, TagWidget::randomColor()));
}
}
disconnect(getTagsReply, &QNetworkReply::finished, this, &TagEditor::onGetTagsComplete);
tidyReply(&getTagsReply);
emit tagsLoaded(isValid);
}
void TagEditor::createTag(QString tagName) {
auto newText = tagName.trimmed();
if (newText == "") {
return;
}
errorLabel->setText("");
loading->startAnimation();
tagCompleteTextBox->setEnabled(false);
dto::Tag newTag(newText, TagWidget::randomColor());
createTagReply = NetMan::getInstance().createTag(newTag, operationSlug);
connect(createTagReply, &QNetworkReply::finished, this, &TagEditor::onCreateTagComplete);
}
void TagEditor::onCreateTagComplete() {
bool isValid;
auto data = NetMan::extractResponse(createTagReply, isValid);
if (isValid) {
auto newTag = dto::Tag::parseData(data);
addTag(newTag);
tagView->addTag(newTag);
updateCompleterModel();
}
else {
couldNotCreateTagMsg->showMessage(
"Could not create tag."
" Please check your connection and try again.");
}
disconnect(createTagReply, &QNetworkReply::finished, this, &TagEditor::onCreateTagComplete);
tidyReply(&createTagReply);
loading->stopAnimation();
tagCompleteTextBox->setEnabled(true);
tagCompleteTextBox->setFocus();
}
void TagEditor::addTag(dto::Tag tag) {
tagNames << tag.name;
tagMap.emplace(standardizeTagKey(tag.name), tag);
}
QString TagEditor::standardizeTagKey(const QString& tagName) {
return tagName.trimmed().toLower();
}
void TagEditor::setReadonly(bool readonly) {
tagCompleteTextBox->setEnabled(!readonly);
tagCompleteTextBox->setVisible(!readonly);
tagView->setReadonly(readonly);
}

View File

@ -0,0 +1,74 @@
#ifndef TAGEDITOR_H
#define TAGEDITOR_H
#include <QCompleter>
#include <QErrorMessage>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QNetworkReply>
#include <QPushButton>
#include <QWidget>
#include "components/loading/qprogressindicator.h"
#include "components/loading_button/loadingbutton.h"
#include "components/tagging/tagview.h"
#include "components/tagging/tagwidget.h"
#include "components/tagging/tagginglineediteventfilter.h"
#include "models/tag.h"
class TagEditor : public QWidget {
Q_OBJECT
public:
explicit TagEditor(QWidget* parent = nullptr);
~TagEditor();
private:
void buildUi();
void wireUi();
void createTag(QString tagName);
void updateCompleterModel();
void tagTextEntered(QString text);
inline void showCompleter() { completer->complete(); }
void addTag(dto::Tag tag);
QString standardizeTagKey(const QString &tagName);
private slots:
void onGetTagsComplete();
void onCreateTagComplete();
void tagEditReturnPressed();
void completerActivated(const QString& text);
public:
void clear();
void setReadonly(bool readonly);
void loadTags(const QString& operationSlug, std::vector<model::Tag> initialTagIDs);
inline std::vector<model::Tag> getIncludedTags() { return tagView->getIncludedTags(); }
signals:
void tagsLoaded(bool isValid);
private:
QString operationSlug = "";
std::vector<model::Tag> initialTags;
QNetworkReply* getTagsReply = nullptr;
QNetworkReply* createTagReply = nullptr;
QErrorMessage* couldNotCreateTagMsg = nullptr;
TaggingLineEditEventFilter filter;
QCompleter* completer;
QStringList tagNames;
std::unordered_map<QString, dto::Tag> tagMap;
// Ui Elements
QGridLayout* gridLayout = nullptr;
QLineEdit* tagCompleteTextBox = nullptr;
QProgressIndicator* loading = nullptr;
QLabel* errorLabel = nullptr;
TagView* tagView = nullptr;
};
#endif // TAGEDITOR_H

View File

@ -0,0 +1,69 @@
#ifndef UPDOWNKEYFILTER_H
#define UPDOWNKEYFILTER_H
#include <QEvent>
#include <QKeyEvent>
#include <QKeySequence>
#include <QWidget>
#include <iostream>
class TaggingLineEditEventFilter : public QObject {
Q_OBJECT
public:
explicit TaggingLineEditEventFilter(QObject *parent = nullptr) : QObject(parent) {}
signals:
void upPressed();
void downPressed();
void completePressed();
void leftMouseClickPressed();
private:
bool matchesKey(QKeyEvent *ke, QKeySequence keyCombo) {
// with help from https://forum.qt.io/topic/73408/qt-reading-key-sequences-from-key-event/3
QString modifier;
QString key;
if (ke->modifiers() & Qt::ShiftModifier) modifier += "Shift+";
if (ke->modifiers() & Qt::ControlModifier) modifier += "Ctrl+";
if (ke->modifiers() & Qt::AltModifier) modifier += "Alt+";
if (ke->modifiers() & Qt::MetaModifier) modifier += "Meta+";
key = QKeySequence(ke->key()).toString();
QKeySequence ks(modifier + key);
return ks[0] == keyCombo[0];
}
protected:
bool eventFilter(QObject *object, QEvent *event) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *ke = static_cast<QKeyEvent *>(event);
if (matchesKey(ke, QKeySequence(Qt::CTRL + Qt::Key_Space))) {
emit completePressed();
return true;
}
if (ke->key() == Qt::Key_Up) {
emit upPressed();
return true;
}
if (ke->key() == Qt::Key_Down) {
emit downPressed();
return true;
}
}
else if (event->type() == QEvent::MouseButtonRelease) {
auto mouseEvt = static_cast<QMouseEvent *>(event);
if (mouseEvt->button() == Qt::LeftButton) {
emit leftMouseClickPressed();
return true;
}
}
return QObject::eventFilter(object, event);
}
};
#endif // UPDOWNKEYFILTER_H

View File

@ -0,0 +1,97 @@
#include "tagview.h"
#include <iostream>
TagView::TagView(QWidget *parent) : QWidget(parent) {
buildUi();
wireUi();
}
TagView::~TagView() {
clear();
delete layout;
delete tagGroupBox;
delete mainLayout;
}
void TagView::buildUi() {
mainLayout = new QHBoxLayout(this);
mainLayout->setMargin(0);
tagGroupBox = new QGroupBox("Tags", this);
layout = new FlowLayout();
tagGroupBox->setLayout(layout);
mainLayout->addWidget(tagGroupBox);
setLayout(mainLayout);
}
void TagView::wireUi() {
}
void TagView::addTag(dto::Tag tag) {
TagWidget* widget = new TagWidget(tag, readonly, this);
includedTags.push_back(widget);
layout->addWidget(widget);
connect(widget, &TagWidget::removePressed, [this, widget](){
removeWidget(widget);
});
}
bool TagView::contains(dto::Tag tag) {
for (const auto &widget : includedTags) {
if (widget->getTag().id == tag.id) {
return true;
}
}
return false;
}
void TagView::removeWidget(TagWidget* tagWidget) {
// remove from view
tagWidget->hide();
layout->removeWidget(tagWidget);
// remove from includedTags
auto itr = std::find(includedTags.begin(), includedTags.end(), tagWidget);
if(itr != includedTags.cend()) {
auto last = includedTags.end()-1;
std::iter_swap(itr, last);
includedTags.pop_back();
}
delete tagWidget;
}
void TagView::remove(dto::Tag tag) {
auto itr = std::find_if(includedTags.begin(), includedTags.end(), [tag](TagWidget* item){
return item->getTag().id == tag.id;
});
removeWidget(includedTags.at(itr - includedTags.begin()));
}
void TagView::clear() {
for(auto widget : includedTags) {
layout->removeWidget(widget);
delete widget;
}
includedTags.clear();
}
std::vector<model::Tag> TagView::getIncludedTags() {
std::vector<model::Tag> rtn;
for (const auto &widget : includedTags) {
dto::Tag tag = widget->getTag();
rtn.push_back(model::Tag(tag.id, tag.name));
}
return rtn;
}
void TagView::setReadonly(bool readonly) {
for(auto widget : includedTags) {
widget->setReadOnly(readonly);
}
}

View File

@ -0,0 +1,44 @@
#ifndef TAGVIEW_H
#define TAGVIEW_H
#include <QObject>
#include <QWidget>
#include <QGroupBox>
#include "components/tagging/tagwidget.h"
#include "components/flow_layout/flowlayout.h"
#include "models/tag.h"
class TagView : public QWidget
{
Q_OBJECT
public:
explicit TagView(QWidget *parent = nullptr);
~TagView();
private:
void buildUi();
void wireUi();
private slots:
void removeWidget(TagWidget* tag);
public:
void addTag(dto::Tag tag);
std::vector<model::Tag> getIncludedTags();
void setReadonly(bool readonly);
bool contains(dto::Tag tag);
void remove(dto::Tag tag);
void clear();
private:
bool readonly = false;
// UI Components
QHBoxLayout* mainLayout = nullptr;
FlowLayout* layout = nullptr;
QGroupBox* tagGroupBox = nullptr;
std::vector<TagWidget*> includedTags;
};
#endif // TAGVIEW_H

View File

@ -0,0 +1,108 @@
#include "tagwidget.h"
#include <QPainter>
#include <QMouseEvent>
#include <iostream>
TagWidget::TagWidget(dto::Tag tag, bool readonly, QWidget *parent) : QLabel(parent) {
this->tag = tag;
this->readonly = readonly;
buildUi();
}
void TagWidget::buildUi() {
buildTag();
}
void TagWidget::wireUi(){
}
void TagWidget::setReadOnly(bool readonly) {
this->readonly = readonly;
buildTag();
}
void TagWidget::mouseReleaseEvent(QMouseEvent* evt) {
const int x = evt->x();
const int y = evt->y();
if (removeArea.contains(x, y)) {
emit removePressed();
}
else if (labelArea.contains(x, y)) {
emit labelPressed();
}
}
void TagWidget::buildTag() {
QFont labelFont;
#ifdef Q_OS_MACOS
labelFont = QFont("Sans", 14);
#else
labelFont = QFont("Sans", 12);
#endif
// Calculate the positions of everything
QFontMetrics metric(labelFont);
QSize labelSize = metric.size(Qt::TextSingleLine, tag.name);
QSize removeSize = metric.size(Qt::TextSingleLine, removeSymbol);
int labelWidth = labelSize.width();
int innerTagHeight = std::max(labelSize.height(), removeSize.height()); //tag height without the buffer
int tagHeightBuffer = 12; // space around the top/bottom (real size is half as much)
int tagWidthBuffer = 12; // space around the outer left/right edges (real size is half as much)
int innerBuffer = 6; // space between each segment
int labelLeftOffset = tagWidthBuffer/2;
int labelTopOffset = ((innerTagHeight - labelSize.height())/2) + (tagHeightBuffer/2);
int removeLeftOffset = labelLeftOffset + labelWidth + innerBuffer;
int removeTopOffset = ((innerTagHeight - removeSize.height())/2) + (tagHeightBuffer/2);
int fullTagWidth = labelWidth + tagWidthBuffer;
int fullTagHeight = innerTagHeight + tagHeightBuffer;
// set bounds for mouse release event
labelArea = QRectF(0, 0, removeLeftOffset, fullTagHeight);
removeArea = QRectF(-1, -1, 0, 0); // set to dummy value in case we don't have a remove area
if (!readonly) {
fullTagWidth += removeSize.width() + innerBuffer;
removeArea = QRectF(removeLeftOffset, 0, fullTagWidth - removeLeftOffset, fullTagHeight);
}
const qreal dpr = this->devicePixelRatio();
// prep the image
QPixmap pixmap = QPixmap(fullTagWidth * dpr, fullTagHeight * dpr);
pixmap.setDevicePixelRatio(dpr);
pixmap.fill(Qt::transparent);
QColor bgColor = colorMap[tag.colorName];
QPainter painter(&pixmap);
// these actually are used and removing them makes the edges/text slightly less sharp
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
// draw container
painter.setBrush(bgColor);
painter.setPen(Qt::NoPen);
painter.drawRoundedRect(QRectF(0, 0, (pixmap.width() / dpr)-1, (pixmap.height() / dpr)-1), 6, 6);
// set up font drawing
auto fontColor = fontColorForBgColor(bgColor);
painter.setFont(labelFont);
painter.setPen(fontColor);
// draw label
painter.drawText(QRectF(QPointF(labelLeftOffset, labelTopOffset), labelSize), Qt::AlignCenter, tag.name);
// draw remove (if needed)
if(!readonly) {
painter.drawText(QRectF(QPointF(removeLeftOffset, removeTopOffset), removeSize), Qt::AlignCenter, removeSymbol);
}
painter.end();
setPixmap(pixmap);
}

View File

@ -0,0 +1,92 @@
#ifndef TAGWIDGET_H
#define TAGWIDGET_H
#include <QImage>
#include <QLabel>
#include <QWidget>
#include <QRandomGenerator>
#include <unordered_map>
#include "dtos/tag.h"
static std::unordered_map<QString, QColor> colorMap{
// matches colors defined on front end
{"blue", QColor(0x0E5A8A)},
{"yellow", QColor(0xA67908)},
{"green", QColor(0x0A6640)},
{"indigo", QColor(0x5642A6)},
{"orange", QColor(0xA66321)},
{"pink", QColor(0xA82255)},
{"red", QColor(0xA82A2A)},
{"teal", QColor(0x008075)},
{"vermilion", QColor(0x9E2B0E)},
{"violet", QColor(0x5C255C)},
{"lightBlue", QColor(0x48AFF0)},
{"lightYellow", QColor(0xFFC940)},
{"lightGreen", QColor(0x3DCC91)},
{"lightIndigo", QColor(0xAD99FF)},
{"lightOrange", QColor(0xFFB366)},
{"lightPink", QColor(0xFF66A1)},
{"lightRed", QColor(0xFF7373)},
{"lightTeal", QColor(0x2EE6D6)},
{"lightVermilion", QColor(0xFF6E4A)},
{"lightViolet", QColor(0xC274C2)},
};
static std::vector<QString> allColorsNames = []()->std::vector<QString>{
std::vector<QString> colors;
for (auto kv : colorMap) {
colors.push_back(kv.first);
}
return colors;
}();
class TagWidget : public QLabel {
Q_OBJECT
public:
explicit TagWidget(dto::Tag tag, bool readonly, QWidget* parent = nullptr);
~TagWidget() = default;
private:
void buildUi();
void wireUi();
void buildTag();
void setImage(QImage img);
protected:
void mouseReleaseEvent(QMouseEvent* ev) override;
public:
inline dto::Tag getTag(){return tag;};
void setReadOnly(bool readonly);
inline bool isReadOnly(){return readonly;}
static QString randomColor() {
// Note: this should match the frontend's color palette (naming)
auto index = QRandomGenerator::global()->bounded(int(allColorsNames.size()));
return allColorsNames.at(index);
}
static QColor fontColorForBgColor(QColor bg) {
long yiq = ((bg.red() * 299) + (bg.green() * 587) + (bg.blue() * 114)) / 1000;
return (yiq < 128 ? Qt::white : Qt::black);
}
signals:
void removePressed();
void labelPressed();
private:
bool readonly = false;
dto::Tag tag;
const QString removeSymbol = QString::fromUtf8("\u2718");
QRectF labelArea;
QRectF removeArea;
int tagWidth;
int tagHeight;
};
#endif // TAGWIDGET_H

View File

@ -8,20 +8,21 @@
#include <vector>
#include "helpers/jsonhelpers.h"
#include "models/tag.h"
namespace dto {
class Tag {
public:
Tag() {}
Tag() = default;
~Tag() = default;
Tag(const Tag &) = default;
Tag(QString name, QString colorName) {
this->name = name;
this->colorName = colorName;
}
qint64 id;
QString colorName;
QString name;
public:
static Tag parseData(QByteArray data) { return parseJSONItem<Tag>(data, Tag::fromJson); }
static std::vector<Tag> parseDataAsList(QByteArray data) {
@ -35,6 +36,13 @@ class Tag {
return QJsonDocument(obj).toJson();
}
static Tag fromModelTag(model::Tag tag, QString colorName) {
Tag t;
t.name = tag.tagName;
t.colorName = colorName;
return t;
}
private:
// provides a Tag from a given QJsonObject
static Tag fromJson(QJsonObject obj) {
@ -45,7 +53,13 @@ class Tag {
return t;
}
public:
qint64 id;
QString colorName;
QString name;
};
} // namespace dto
Q_DECLARE_METATYPE(dto::Tag);
#endif // DTO_TAG_H

View File

@ -6,6 +6,7 @@
#include <QAction>
#include <QDialog>
#include <QLineEdit>
#include <QMenu>
#include <QNetworkReply>
#include <QTableWidget>

View File

@ -3,8 +3,8 @@
#include "getinfo.h"
#include <QMessageBox>
#include <QKeySequence>
#include <QMessageBox>
#include "appsettings.h"
#include "components/evidence_editor/evidenceeditor.h"
@ -54,6 +54,12 @@ void GetInfo::wireUi() {
connect(closeWindowAction, &QAction::triggered, this, &GetInfo::deleteButtonClicked);
}
void GetInfo::showEvent(QShowEvent* evt) {
QDialog::showEvent(evt);
setFocus(); // giving the form focus, to prevent retaining focus on the submit button when
// closing the window
}
bool GetInfo::saveData() {
auto saveResponse = evidenceEditor->saveEvidence();
if (!saveResponse.actionSucceeded) {
@ -139,6 +145,7 @@ void GetInfo::onUploadComplete() {
else {
try {
db->updateEvidenceSubmitted(this->evidenceID);
emit evidenceSubmitted(db->getEvidenceDetails(this->evidenceID));
this->close();
}
catch (QSqlError& e) {

View File

@ -30,12 +30,18 @@ class GetInfo : public QDialog {
bool saveData();
void setActionButtonsEnabled(bool enabled);
void showEvent(QShowEvent *evt) override;
private slots:
void submitButtonClicked();
void deleteButtonClicked();
void onUploadComplete();
public:
signals:
void evidenceSubmitted(model::Evidence evidence);
private:
Ui::GetInfo *ui;
DatabaseConnection *db;

View File

@ -19,6 +19,38 @@ void handleCLI(std::vector<std::string> args);
#include "exceptions/fileerror.h"
#include "traymanager.h"
QDataStream& operator<<(QDataStream& out, const model::Tag& v) {
out << v.tagName << v.id << v.serverTagId;
return out;
}
QDataStream& operator>>(QDataStream& in, model::Tag& v) {
in >> v.tagName;
in >> v.id;
in >> v.serverTagId;
return in;
}
QDataStream& operator<<(QDataStream& out, const std::vector<model::Tag>& v) {
out << int(v.size());
for (auto tag : v) {
out << tag;
}
return out;
}
QDataStream& operator>>(QDataStream& in, std::vector<model::Tag>& v) {
int qty;
in >> qty;
v.reserve(qty);
for(int i = 0; i < qty; i++) {
model::Tag t;
in >> t;
v.push_back(t);
}
return in;
}
int main(int argc, char* argv[]) {
Q_INIT_RESOURCE(res_icons);
Q_INIT_RESOURCE(res_migrations);
@ -58,6 +90,8 @@ int main(int argc, char* argv[]) {
int rtn;
try {
qRegisterMetaTypeStreamOperators<model::Tag>("Tag");
qRegisterMetaTypeStreamOperators<std::vector<model::Tag>>("TagVector");
QApplication app(argc, argv);
if (!QSystemTrayIcon::isSystemTrayAvailable()) {

View File

@ -5,20 +5,29 @@
#define MODEL_TAG_H
#include <QString>
#include <QVariant>
#include <QDataStream>
namespace model {
class Tag {
public:
Tag() = default;
~Tag() = default;
Tag(const Tag &) = default;
Tag(qint64 id, qint64 tagId, QString name) : Tag(tagId, name) { this->id = id; }
Tag(qint64 tagId, QString name) {
this->serverTagId = tagId;
this->tagName = name;
}
public:
qint64 id;
qint64 serverTagId;
QString tagName;
};
} // namespace model
Q_DECLARE_METATYPE(model::Tag);
Q_DECLARE_METATYPE(std::vector<model::Tag>);
#endif // MODEL_TAG_H

View File

@ -175,15 +175,36 @@ void TrayManager::createActions() {
chooseOpSubmenu->addSeparator();
}
void TrayManager::spawnGetInfoWindow(qint64 evidenceID) {
auto getInfoWindow = new GetInfo(db, evidenceID, this);
connect(getInfoWindow, &GetInfo::evidenceSubmitted, [](model::Evidence evi){
AppSettings::getInstance().setLastUsedTags(evi.tags);
});
getInfoWindow->show();
}
qint64 TrayManager::createNewEvidence(QString filepath, QString evidenceType) {
AppSettings& inst = AppSettings::getInstance();
auto evidenceID = db->createEvidence(filepath, inst.operationSlug(), evidenceType);
auto tags = inst.getLastUsedTags();
if (tags.size() > 0) {
db->setEvidenceTags(tags, evidenceID);
}
return evidenceID;
}
void TrayManager::onCodeblockCapture() {
QString clipboardContent = ClipboardHelper::readPlaintext();
if (clipboardContent != "") {
Codeblock evidence(clipboardContent);
Codeblock::saveCodeblock(evidence);
auto evidenceID = db->createEvidence(evidence.filePath(),
AppSettings::getInstance().operationSlug(), "codeblock");
auto getInfoWindow = new GetInfo(db, evidenceID, this);
getInfoWindow->show();
try {
auto evidenceID = createNewEvidence(evidence.filePath(), "codeblock");
spawnGetInfoWindow(evidenceID);
}
catch (QSqlError& e) {
std::cout << "could not write to the database: " << e.text().toStdString() << std::endl;
}
}
}
@ -207,11 +228,9 @@ void TrayManager::createTrayMenu() {
}
void TrayManager::onScreenshotCaptured(const QString& path) {
std::cout << "Captured screenshot to file: " << path.toStdString() << std::endl;
try {
auto evidenceID = db->createEvidence(path, AppSettings::getInstance().operationSlug(), "image");
auto getInfoWindow = new GetInfo(db, evidenceID, this);
getInfoWindow->show();
auto evidenceID = createNewEvidence(path, "image");
spawnGetInfoWindow(evidenceID);
}
catch (QSqlError& e) {
std::cout << "could not write to the database: " << e.text().toStdString() << std::endl;
@ -245,6 +264,7 @@ void TrayManager::onOperationListUpdated(bool success,
}
connect(newAction, &QAction::triggered, [this, newAction, op] {
AppSettings::getInstance().setLastUsedTags(std::vector<model::Tag>{}); // clear last used tags
AppSettings::getInstance().setOperationDetails(op.slug, op.name);
if (selectedAction != nullptr) {
selectedAction->setChecked(false);

View File

@ -54,7 +54,10 @@ class TrayManager : public QDialog {
void createActions();
void createTrayMenu();
void wireUi();
qint64 createNewEvidence(QString filepath, QString evidenceType);
void spawnGetInfoWindow(qint64 evidenceID);
private:
QAction *quitAction;
QAction *showSettingsAction;
QAction *currentOperationMenuAction;