Skip to content

Commit

Permalink
Pin/unpin items
Browse files Browse the repository at this point in the history
Fixes #545
Fixes #609
Fixes #652
  • Loading branch information
hluk committed Feb 27, 2017
1 parent 75da732 commit 609d85b
Show file tree
Hide file tree
Showing 11 changed files with 783 additions and 0 deletions.
1 change: 1 addition & 0 deletions plugins/CMakeLists.txt
Expand Up @@ -58,6 +58,7 @@ add_subdirectory("itemencrypted")
add_subdirectory("itemfakevim")
add_subdirectory("itemimage")
add_subdirectory("itemnotes")
add_subdirectory("itempinned")
add_subdirectory("itemtags")
add_subdirectory("itemtext")
add_subdirectory("itemsync")
Expand Down
8 changes: 8 additions & 0 deletions plugins/itempinned/CMakeLists.txt
@@ -0,0 +1,8 @@
set(copyq_plugin_itempinned_SOURCES
../../src/common/common.cpp
../../src/common/log.cpp
../../src/common/mimetypes.cpp
)

copyq_add_plugin(itempinned)

329 changes: 329 additions & 0 deletions plugins/itempinned/itempinned.cpp
@@ -0,0 +1,329 @@
/*
Copyright (c) 2014, Lukas Holecek <hluk@email.cz>
This file is part of CopyQ.
CopyQ is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CopyQ is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
*/

#include "itempinned.h"
#include "ui_itempinnedsettings.h"

#include "common/common.h"
#include "common/command.h"
#include "common/contenttype.h"

#ifdef HAS_TESTS
# include "tests/itempinnedtests.h"
#endif

#include <QApplication>
#include <QBoxLayout>
#include <QMessageBox>
#include <QModelIndex>

#include <algorithm>

namespace {

const char mimePinned[] = "application/x-copyq-item-pinned";

int pinnedRow(const QModelIndex &index)
{
bool ok;
const auto dataMap = index.data(contentType::data).toMap();
const auto pinnedRow = dataMap.value(mimePinned).toInt(&ok);
return ok ? pinnedRow : -1;
}

bool isPinned(const QModelIndex &index)
{
return pinnedRow(index) >= 0;
}

Command dummyPinCommand()
{
Command c;
c.icon = QString(QChar(IconThumbTack));
c.inMenu = true;
c.shortcuts = QStringList()
<< ItemPinnedLoader::tr("Ctrl+Shift+P", "Shortcut to pin and unpin items");
return c;
}

} // namespace

ItemPinned::ItemPinned(ItemWidget *childItem, int pinnedRow)
: QWidget( childItem->widget()->parentWidget() )
, ItemWidget(this)
, m_border(new QWidget(this))
, m_childItem(childItem)
, m_pinnedRow(pinnedRow)
{
m_childItem->widget()->setObjectName("item_child");
m_childItem->widget()->setParent(this);

m_border->setFixedWidth( pointsToPixels(8) );
m_border->setStyleSheet("background: rgba(0,0,0,0.2)");

QBoxLayout *layout;
layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing( pointsToPixels(5) );

layout->addWidget(m_childItem->widget());
layout->addWidget(m_border);
}

void ItemPinned::highlight(const QRegExp &re, const QFont &highlightFont, const QPalette &highlightPalette)
{
m_childItem->setHighlight(re, highlightFont, highlightPalette);
}

QWidget *ItemPinned::createEditor(QWidget *parent) const
{
return m_childItem->createEditor(parent);
}

void ItemPinned::setEditorData(QWidget *editor, const QModelIndex &index) const
{
return m_childItem->setEditorData(editor, index);
}

void ItemPinned::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
return m_childItem->setModelData(editor, model, index);
}

bool ItemPinned::hasChanges(QWidget *editor) const
{
return m_childItem->hasChanges(editor);
}

QObject *ItemPinned::createExternalEditor(const QModelIndex &index, QWidget *parent) const
{
return m_childItem->createExternalEditor(index, parent);
}

void ItemPinned::updateSize(const QSize &maximumSize, int idealWidth)
{
const int childItemWidth = idealWidth - m_border->width() - layout()->spacing();
m_childItem->updateSize(maximumSize, childItemWidth);
}

ItemPinnedSaver::ItemPinnedSaver(QAbstractItemModel *model, QVariantMap &settings, const ItemSaverPtr &saver)
: m_model(model)
, m_settings(settings)
, m_saver(saver)
{
connect( model, SIGNAL(rowsInserted(QModelIndex, int, int)),
SLOT(onRowsInserted(QModelIndex, int, int)) );
connect( model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
SLOT(onRowsRemoved(QModelIndex,int,int)) );
}

bool ItemPinnedSaver::saveItems(const QAbstractItemModel &model, QIODevice *file)
{
return m_saver->saveItems(model, file);
}

bool ItemPinnedSaver::canRemoveItems(const QList<QModelIndex> &indexList, QString *error)
{
const bool containsPinnedItems = std::any_of(
std::begin(indexList), std::end(indexList), isPinned);

if (!containsPinnedItems)
return m_saver->canRemoveItems(indexList, error);

if (error) {
*error = "Removing pinned items is not allowed (plugin.itempinned.unpin() items first)";
return false;
}

QMessageBox::information(
QApplication::activeWindow(),
ItemPinnedLoader::tr("Cannot Remove Pinned Items"),
ItemPinnedLoader::tr("Unpin items first to remove them.") );
return false;
}

bool ItemPinnedSaver::canMoveItems(const QList<QModelIndex> &indexList)
{
return m_saver->canMoveItems(indexList);
}

void ItemPinnedSaver::itemsRemovedByUser(const QList<QModelIndex> &indexList)
{
m_saver->itemsRemovedByUser(indexList);
}

QVariantMap ItemPinnedSaver::copyItem(const QAbstractItemModel &model, const QVariantMap &itemData)
{
return m_saver->copyItem(model, itemData);
}

void ItemPinnedSaver::onRowsInserted(const QModelIndex &, int start, int end)
{
if (!m_model)
return;

// Shift rows below inserted up.
const int rowCount = end - start + 1;
for (int row = end + 1; row < m_model->rowCount(); ++row) {
const auto index = m_model->index(row, 0);
if ( isPinned(index) )
moveRow(row, row - rowCount);
}
}

void ItemPinnedSaver::onRowsRemoved(const QModelIndex &, int start, int end)
{
if (!m_model)
return;

// Shift rows below removed down.
const int rowCount = end - start + 1;
for (int row = m_model->rowCount() - 1; row >= end; --row) {
const auto index = m_model->index(row, 0);
if ( isPinned(index) )
moveRow(row, row + rowCount + 1);
}
}

void ItemPinnedSaver::moveRow(int from, int to)
{
#if QT_VERSION < 0x050000
QMetaObject::invokeMethod(m_model, "moveRow", Q_ARG(int, from), Q_ARG(int, to));
#else
m_model->moveRow(QModelIndex(), from, QModelIndex(), to);
#endif
}

ItemPinnedLoader::ItemPinnedLoader()
{
}

ItemPinnedLoader::~ItemPinnedLoader()
{
}

QStringList ItemPinnedLoader::formatsToSave() const
{
return QStringList() << mimePinned;
}

QVariantMap ItemPinnedLoader::applySettings()
{
return m_settings;
}

QWidget *ItemPinnedLoader::createSettingsWidget(QWidget *parent)
{
ui.reset(new Ui::ItemPinnedSettings);
QWidget *w = new QWidget(parent);
ui->setupUi(w);

return w;
}

ItemWidget *ItemPinnedLoader::transform(ItemWidget *itemWidget, const QModelIndex &index)
{
const auto row = pinnedRow(index);
if (row < 0)
return nullptr;

return new ItemPinned(itemWidget, row);
}

ItemSaverPtr ItemPinnedLoader::transformSaver(const ItemSaverPtr &saver, QAbstractItemModel *model)
{
return std::make_shared<ItemPinnedSaver>(model, m_settings, saver);
}

QObject *ItemPinnedLoader::tests(const TestInterfacePtr &test) const
{
#ifdef HAS_TESTS
QObject *tests = new ItemPinnedTests(test);
return tests;
#else
Q_UNUSED(test);
return nullptr;
#endif
}

QString ItemPinnedLoader::script() const
{
return "plugins." + id() + R"SCRIPT( = {
mime: ')SCRIPT" + QString(mimePinned) + R"SCRIPT(',
_pin: function(pin, args) {
var rows = Array.prototype.slice.call(args, 0)
if (rows.length == 0)
rows = selecteditems()
for (var i in rows) {
var row = rows[i]
change(row, this.mime, pin ? str(row) : '')
}
},
isPinned: function(rows) {
var rows = Array.prototype.slice.call(arguments, 0)
if (rows.length == 0)
rows = selecteditems()
for (var i in rows) {
var pinnedRowText = str(read(this.mime, rows[i]))
pinnedRow = parseInt(pinnedRowText)
if (pinnedRow >= 0)
return true
}
return false
},
pin: function() {
this._pin(true, arguments)
},
unpin: function() {
this._pin(false, arguments)
},
})SCRIPT";
}

QList<Command> ItemPinnedLoader::commands() const
{
QList<Command> commands;

Command c;

c = dummyPinCommand();
c.name = tr("Pin");
c.matchCmd = "copyq: plugins.itempinned.isPinned() && fail()";
c.cmd = "copyq: plugins.itempinned.pin()";
commands.append(c);

c = dummyPinCommand();
c.name = tr("Unpin");
c.matchCmd = "copyq: plugins.itempinned.isPinned() || fail()";
c.cmd = "copyq: plugins.itempinned.unpin()";
commands.append(c);

return commands;
}

Q_EXPORT_PLUGIN2(itempinned, ItemPinnedLoader)

0 comments on commit 609d85b

Please sign in to comment.