made a drastic simplification pass on the ShareManager, which now only needs a single window except for selecting files using a QFileDialog

This commit is contained in:
mr-alice 2016-10-31 16:28:26 +01:00
parent e8e054eeae
commit 9d586bcfb0
10 changed files with 255 additions and 177 deletions

View File

@ -700,7 +700,7 @@ bool ftServer::getSharedDirectories(std::list<SharedDirInfo> &dirs)
return true;
}
bool ftServer::setSharedDirectories(std::list<SharedDirInfo> &dirs)
bool ftServer::setSharedDirectories(const std::list<SharedDirInfo>& dirs)
{
mFileDatabase->setSharedDirectories(dirs);
return true;

View File

@ -202,7 +202,7 @@ public:
virtual std::string getPartialsDirectory();
virtual bool getSharedDirectories(std::list<SharedDirInfo> &dirs);
virtual bool setSharedDirectories(std::list<SharedDirInfo> &dirs);
virtual bool setSharedDirectories(const std::list<SharedDirInfo> &dirs);
virtual bool addSharedDirectory(const SharedDirInfo& dir);
virtual bool updateShareFlags(const SharedDirInfo& dir); // updates the flags. The directory should already exist !
virtual bool removeSharedDirectory(std::string dir);

View File

@ -101,7 +101,7 @@ struct SharedDirInfo
{
std::string filename ;
std::string virtualname ;
FileStorageFlags shareflags ; // DIR_FLAGS_NETWORK_WIDE_OTHERS | DIR_FLAGS_BROWSABLE_GROUPS | ...
FileStorageFlags shareflags ; // combnation of DIR_FLAGS_ANONYMOUS_DOWNLOAD | DIR_FLAGS_BROWSABLE | ...
std::list<RsNodeGroupId> parent_groups ;
};
@ -217,8 +217,9 @@ class RsFiles
virtual std::string getDownloadDirectory() = 0;
virtual std::string getPartialsDirectory() = 0;
virtual bool getSharedDirectories(std::list<SharedDirInfo> &dirs) = 0;
virtual bool addSharedDirectory(const SharedDirInfo& dir) = 0;
virtual bool getSharedDirectories(std::list<SharedDirInfo>& dirs) = 0;
virtual bool setSharedDirectories(const std::list<SharedDirInfo>& dirs) = 0;
virtual bool addSharedDirectory(const SharedDirInfo& dir) = 0;
virtual bool updateShareFlags(const SharedDirInfo& dir) = 0; // updates the flags. The directory should already exist !
virtual bool removeSharedDirectory(std::string dir) = 0;

View File

@ -34,7 +34,8 @@
#include "ShareManager.h"
#include "ShareDialog.h"
#include "settings/rsharesettings.h"
#include <gui/common/GroupFlagsWidget.h>
#include "gui/common/GroupFlagsWidget.h"
#include "gui/common/GroupSelectionBox.h"
#include "gui/common/GroupDefs.h"
#include "gui/notifyqt.h"
#include "util/QtVersion.h"
@ -65,18 +66,16 @@ ShareManager::ShareManager()
Settings->loadWidgetInformation(this);
connect(ui.addButton, SIGNAL(clicked( bool ) ), this , SLOT( showShareDialog() ) );
connect(ui.editButton, SIGNAL(clicked( bool ) ), this , SLOT( editShareDirectory() ) );
connect(ui.removeButton, SIGNAL(clicked( bool ) ), this , SLOT( removeShareDirectory() ) );
connect(ui.addButton, SIGNAL(clicked( bool ) ), this , SLOT( addShare() ) );
connect(ui.closeButton, SIGNAL(clicked()), this, SLOT(applyAndClose()));
connect(ui.cancelButton, SIGNAL(clicked()), this, SLOT(cancel()));
connect(ui.shareddirList, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(shareddirListCostumPopupMenu(QPoint)));
connect(ui.shareddirList, SIGNAL(currentCellChanged(int,int,int,int)), this, SLOT(shareddirListCurrentCellChanged(int,int,int,int)));
connect(NotifyQt::getInstance(), SIGNAL(groupsChanged(int)), this, SLOT(updateGroups()));
connect(ui.shareddirList, SIGNAL(cellDoubleClicked(int,int)), this, SLOT(doubleClickedCell(int,int)));
ui.editButton->setEnabled(false);
ui.removeButton->setEnabled(false);
connect(NotifyQt::getInstance(), SIGNAL(groupsChanged(int)), this, SLOT(reload()));
QHeaderView* header = ui.shareddirList->horizontalHeader();
QHeaderView_setSectionResizeModeColumn(header, COLUMN_PATH, QHeaderView::Stretch);
@ -90,6 +89,33 @@ ShareManager::ShareManager()
setAttribute(Qt::WA_DeleteOnClose, true);
}
void ShareManager::doubleClickedCell(int row,int column)
{
if(column == COLUMN_PATH)
{
QString dirname = QFileDialog::getExistingDirectory(NULL,tr("Choose directory"),QString(),QFileDialog::DontUseNativeDialog | QFileDialog::ShowDirsOnly);
if(!dirname.isNull())
{
std::string new_name( dirname.toUtf8() );
for(uint32_t i=0;i<mDirInfos.size();++i)
if(mDirInfos[row].filename == new_name)
return ;
mDirInfos[row].filename = new_name ;
load();
}
}
else if(column == COLUMN_GROUPS)
{
std::list<RsNodeGroupId> selected_groups = GroupSelectionDialog::selectGroups(std::list<RsNodeGroupId>()) ;
mDirInfos[row].parent_groups = selected_groups ;
load();
}
}
ShareManager::~ShareManager()
{
_instance = NULL;
@ -97,11 +123,21 @@ ShareManager::~ShareManager()
Settings->saveWidgetInformation(this);
}
void ShareManager::cancel()
{
close();
}
void ShareManager::applyAndClose()
{
// std::cerr << "ShareManager:::close(): updating!" << std::endl;
// This is the only place where we change things.
std::list<SharedDirInfo> infos ;
for(uint32_t i=0;i<mDirInfos.size();++i)
infos.push_back(mDirInfos[i]) ;
rsFiles->setSharedDirectories(infos) ;
updateFlags() ;
close() ;
}
@ -121,6 +157,17 @@ void ShareManager::shareddirListCostumPopupMenu( QPoint /*point*/ )
contextMnu.exec(QCursor::pos());
}
void ShareManager::reload()
{
std::list<SharedDirInfo> dirs ;
rsFiles->getSharedDirectories(dirs) ;
for(std::list<SharedDirInfo>::const_iterator it(dirs.begin());it!=dirs.end();++it)
mDirInfos.push_back(*it) ;
load();
}
/** Loads the settings for this page */
void ShareManager::load()
{
@ -128,44 +175,40 @@ void ShareManager::load()
return ;
isLoading = true;
// std::cerr << "ShareManager:: In load !!!!!" << std::endl ;
std::list<SharedDirInfo>::const_iterator it;
std::list<SharedDirInfo> dirs;
rsFiles->getSharedDirectories(dirs);
/* get a link to the table */
QTableWidget *listWidget = ui.shareddirList;
/* set new row count */
listWidget->setRowCount(dirs.size());
listWidget->setRowCount(mDirInfos.size());
int row=0 ;
for(it = dirs.begin(); it != dirs.end(); ++it,++row)
for(uint32_t row=0;row<mDirInfos.size();++row)
{
listWidget->setItem(row, COLUMN_PATH, new QTableWidgetItem(QString::fromUtf8((*it).filename.c_str())));
listWidget->setItem(row, COLUMN_VIRTUALNAME, new QTableWidgetItem(QString::fromUtf8((*it).virtualname.c_str())));
listWidget->setItem(row, COLUMN_PATH, new QTableWidgetItem(QString::fromUtf8(mDirInfos[row].filename.c_str())));
listWidget->setItem(row, COLUMN_VIRTUALNAME, new QTableWidgetItem(QString::fromUtf8(mDirInfos[row].virtualname.c_str())));
GroupFlagsWidget *widget = new GroupFlagsWidget(NULL,(*it).shareflags);
GroupFlagsWidget *widget = new GroupFlagsWidget(NULL,mDirInfos[row].shareflags);
listWidget->setRowHeight(row, 32 * QFontMetricsF(font()).height()/14.0);
listWidget->setCellWidget(row, COLUMN_SHARE_FLAGS, widget);
listWidget->setRowHeight(row, 32 * QFontMetricsF(font()).height()/14.0);
listWidget->setCellWidget(row, COLUMN_SHARE_FLAGS, widget);
listWidget->setItem(row, COLUMN_GROUPS, new QTableWidgetItem()) ;
listWidget->item(row,COLUMN_GROUPS)->setBackgroundColor(QColor(183,236,181)) ;
listWidget->setItem(row, COLUMN_GROUPS, new QTableWidgetItem()) ;
listWidget->item(row,COLUMN_GROUPS)->setBackgroundColor(QColor(183,236,181)) ;
listWidget->item(row,COLUMN_GROUPS)->setText(getGroupString(mDirInfos[row].parent_groups));
//connect(widget,SIGNAL(flagsChanged(FileStorageFlags)),this,SLOT(updateFlags())) ;
connect(widget,SIGNAL(flagsChanged(FileStorageFlags)),this,SLOT(updateFlags())) ;
listWidget->item(row,COLUMN_PATH)->setToolTip(tr("Double click to change shared directory path")) ;
listWidget->item(row,COLUMN_GROUPS)->setToolTip(tr("Double click to select which groups of friends can see the files")) ;
listWidget->item(row,COLUMN_VIRTUALNAME)->setToolTip(tr("Double click to change the cirtual file name")) ;
}
listWidget->setColumnWidth(COLUMN_SHARE_FLAGS,132 * QFontMetricsF(font()).height()/14.0) ;
//ui.incomingDir->setText(QString::fromStdString(rsFiles->getDownloadDirectory()));
listWidget->setColumnWidth(COLUMN_SHARE_FLAGS,132 * QFontMetricsF(font()).height()/14.0) ;
listWidget->update(); /* update display */
update();
isLoading = false ;
updateGroups();
}
void ShareManager::showYourself()
@ -173,6 +216,7 @@ void ShareManager::showYourself()
if(_instance == NULL)
_instance = new ShareManager() ;
_instance->reload() ;
_instance->show() ;
_instance->activateWindow();
}
@ -184,7 +228,7 @@ void ShareManager::showYourself()
}
if (update_local) {
_instance->load();
_instance->reload();
}
}
@ -193,101 +237,77 @@ void ShareManager::updateFlags()
if(isLoading)
return ;
isLoading = true ; // stops GUI update. Otherwise each call to rsFiles->updateShareFlags() modifies the GUI that we count on to check
// what has changed => fail!
isLoading = true ; // stops GUI update. Otherwise each call to rsFiles->updateShareFlags() modifies the GUI that we count on to check
// what has changed => fail!
// std::cerr << "Updating flags" << std::endl;
for(int row=0;row<ui.shareddirList->rowCount();++row)
{
FileStorageFlags flags = (dynamic_cast<GroupFlagsWidget*>(ui.shareddirList->cellWidget(row,COLUMN_SHARE_FLAGS)))->flags() ;
std::list<SharedDirInfo>::iterator it;
std::list<SharedDirInfo> dirs;
rsFiles->getSharedDirectories(dirs);
std::map<QString, FileStorageFlags> mapped_flags ;
for(int row=0;row<ui.shareddirList->rowCount();++row)
{
QString dirpath = ui.shareddirList->item(row,COLUMN_PATH)->text() ;
FileStorageFlags flags = (dynamic_cast<GroupFlagsWidget*>(ui.shareddirList->cellWidget(row,COLUMN_SHARE_FLAGS)))->flags() ;
mapped_flags[dirpath] = flags ;
// std::cerr << "Getting new flags " << flags << " for path " << dirpath.toStdString() << std::endl;
}
for(std::list<SharedDirInfo>::iterator it(dirs.begin());it!=dirs.end();++it)
{
FileStorageFlags newf = mapped_flags[QString::fromUtf8((*it).filename.c_str())] ;
if( (*it).shareflags != newf )
{
(*it).shareflags = newf ;
rsFiles->updateShareFlags(*it) ; // modifies the flags
// std::cerr << "Updating flags to " << newf << " for dir " << (*it).filename << std::endl ;
}
}
mDirInfos[row].shareflags = flags ;
}
isLoading = false ; // re-enable GUI load
load() ; // update the GUI.
load() ; // update the GUI.
}
void ShareManager::updateGroups()
// void ShareManager::updateFromWidget()
// {
// mDirInfos.clear();
//
// for(uint32_t i=0;i<ui.shareddirList.rows();++i)
// {
// SharedDirInfo sdi ;
// sdi.filename = ui.shareddirList->item(i,COLUMN_PATH)->text().toUtf8() ;
// sdi.virtualname = ui.shareddirList->item(i,COLUMN_VIRTUALNAME)->text().toUtf8() ;
// sdi.shareflags = dynamic_cast<GroupFlagsWidget*>(ui.shareddirList->item(i,COLUMN_SHARE_FLAGS))->flags();
// sdi.parent_groups = std::list<RsNodeGroupId>();//ui.shareddirList->item(i,COLUMN_GROUPS)->text();
// }
// }
QString ShareManager::getGroupString(const std::list<RsNodeGroupId>& groups)
{
if(isLoading)
return ;
int n = 0;
QString group_string ;
// std::cerr << "Updating groups" << std::endl;
std::list<SharedDirInfo>::iterator it;
std::list<SharedDirInfo> dirs;
rsFiles->getSharedDirectories(dirs);
int row=0 ;
for(it = dirs.begin(); it != dirs.end(); ++it,++row)
for (std::list<RsNodeGroupId>::const_iterator it(groups.begin());it!=groups.end();++it,++n)
{
QTableWidgetItem *item = ui.shareddirList->item(row, COLUMN_GROUPS);
if (n>0)
group_string += ", " ;
QString group_string;
int n = 0;
for (std::list<RsNodeGroupId>::const_iterator it2((*it).parent_groups.begin());it2!=(*it).parent_groups.end();++it2,++n)
{
if (n>0)
group_string += ", " ;
RsGroupInfo groupInfo;
rsPeers->getGroupInfo(*it2, groupInfo);
group_string += GroupDefs::name(groupInfo);
}
item->setText(group_string);
RsGroupInfo groupInfo;
rsPeers->getGroupInfo(*it, groupInfo);
group_string += GroupDefs::name(groupInfo);
}
return group_string ;
}
void ShareManager::editShareDirectory()
{
/* id current dir */
int row = ui.shareddirList->currentRow();
QTableWidgetItem *item = ui.shareddirList->item(row, COLUMN_PATH);
if (item) {
std::string filename = item->text().toUtf8().constData();
std::list<SharedDirInfo> dirs;
rsFiles->getSharedDirectories(dirs);
std::list<SharedDirInfo>::const_iterator it;
for (it = dirs.begin(); it != dirs.end(); ++it) {
if (it->filename == filename) {
/* file name found, show dialog */
ShareDialog sharedlg (it->filename, this);
sharedlg.setWindowTitle(tr("Edit Shared Folder"));
sharedlg.exec();
load();
break;
}
}
}
}
// void ShareManager::editShareDirectory()
// {
// /* id current dir */
// int row = ui.shareddirList->currentRow();
// QTableWidgetItem *item = ui.shareddirList->item(row, COLUMN_PATH);
//
// if (item) {
// std::string filename = item->text().toUtf8().constData();
//
// std::list<SharedDirInfo> dirs;
// rsFiles->getSharedDirectories(dirs);
//
// std::list<SharedDirInfo>::const_iterator it;
// for (it = dirs.begin(); it != dirs.end(); ++it) {
// if (it->filename == filename) {
// /* file name found, show dialog */
// ShareDialog sharedlg (it->filename, this);
// sharedlg.setWindowTitle(tr("Edit Shared Folder"));
// sharedlg.exec();
// load();
// break;
// }
// }
// }
// }
void ShareManager::removeShareDirectory()
{
@ -301,7 +321,9 @@ void ShareManager::removeShareDirectory()
{
if ((QMessageBox::question(this, tr("Warning!"),tr("Do you really want to stop sharing this directory ?"),QMessageBox::Yes|QMessageBox::No, QMessageBox::Yes))== QMessageBox::Yes)
{
rsFiles->removeSharedDirectory( qdir->text().toUtf8().constData());
for(uint32_t i=row;i+1<mDirInfos.size();++i)
mDirInfos[i] = mDirInfos[i+1] ;
load();
}
}
@ -315,6 +337,30 @@ void ShareManager::showEvent(QShowEvent *event)
}
}
void ShareManager::addShare()
{
QString fname = QFileDialog::getExistingDirectory(NULL,tr("Choose a directory to share"),QString(),QFileDialog::DontUseNativeDialog | QFileDialog::ShowDirsOnly);
if(fname.isNull())
return;
std::string dir_name ( fname.toUtf8() );
// check that the directory does not already exist
for(uint32_t i=0;i<mDirInfos.size();++i)
if(mDirInfos[i].filename == dir_name)
return ;
mDirInfos.push_back(SharedDirInfo());
mDirInfos.back().filename = dir_name ;
mDirInfos.back().virtualname = std::string();
mDirInfos.back().shareflags = DIR_FLAGS_ANONYMOUS_DOWNLOAD | DIR_FLAGS_ANONYMOUS_SEARCH;
mDirInfos.back().parent_groups.clear();
load();
}
void ShareManager::showShareDialog()
{
ShareDialog sharedlg ("", this);
@ -327,14 +373,6 @@ void ShareManager::shareddirListCurrentCellChanged(int currentRow, int currentCo
Q_UNUSED(currentColumn);
Q_UNUSED(previousRow);
Q_UNUSED(previousColumn);
if (currentRow >= 0) {
ui.editButton->setEnabled(true);
ui.removeButton->setEnabled(true);
} else {
ui.editButton->setEnabled(false);
ui.removeButton->setEnabled(false);
}
}
void ShareManager::dragEnterEvent(QDragEnterEvent *event)

View File

@ -26,6 +26,7 @@
#include <QDialog>
#include <QFileDialog>
#include <retroshare/rsfiles.h>
#include "ui_ShareManager.h"
class ShareManager : public QDialog
@ -55,14 +56,18 @@ private slots:
/** Create the context popup menu and it's submenus */
void shareddirListCurrentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn);
void shareddirListCostumPopupMenu( QPoint point );
void addShare();
void doubleClickedCell(int,int);
void showShareDialog();
void editShareDirectory();
//void editShareDirectory();
void removeShareDirectory();
void updateFlags();
void updateGroups();
void applyAndClose() ;
void applyAndClose() ;
void cancel() ;
void reload() ;
static QString getGroupString(const std::list<RsNodeGroupId>& groups);
private:
static ShareManager *_instance;
bool isLoading;
@ -72,6 +77,8 @@ private:
/** Qt Designer generated object */
Ui::ShareManager ui;
std::vector<SharedDirInfo> mDirInfos ;
};
#endif

View File

@ -18,7 +18,16 @@
<normaloff>:/images/logo/logo_16.png</normaloff>:/images/logo/logo_16.png</iconset>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<property name="margin">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
@ -49,7 +58,7 @@
<property name="topMargin">
<number>6</number>
</property>
<item row="1" column="0" colspan="6">
<item row="1" column="0" colspan="4">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Shared Folder Manager</string>
@ -64,7 +73,7 @@
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
<set>QAbstractItemView::DoubleClicked</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
@ -92,12 +101,12 @@
</attribute>
<column>
<property name="text">
<string>Directory</string>
<string>Shared directory</string>
</property>
</column>
<column>
<property name="text">
<string>Virtual Folder</string>
<string>Visible name</string>
</property>
</column>
<column>
@ -139,7 +148,7 @@
<string>Add a Share Directory</string>
</property>
<property name="text">
<string>Add</string>
<string>Add new</string>
</property>
<property name="iconSize">
<size>
@ -149,35 +158,7 @@
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QPushButton" name="removeButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>200</height>
</size>
</property>
<property name="toolTip">
<string>Stop sharing selected Directory</string>
</property>
<property name="text">
<string>Remove</string>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
</widget>
</item>
<item row="2" column="3">
<item row="2" column="1">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -190,24 +171,14 @@
</property>
</spacer>
</item>
<item row="2" column="4">
<item row="2" column="2">
<widget class="QPushButton" name="closeButton">
<property name="text">
<string>Apply and close</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="editButton">
<property name="toolTip">
<string>Edit selected Shared Directory</string>
</property>
<property name="text">
<string>Edit</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="5">
<item row="0" column="0" colspan="3">
<widget class="StyledLabel" name="labelInstructions">
<property name="palette">
<palette>
@ -290,6 +261,13 @@
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QPushButton" name="cancelButton">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -1,3 +1,5 @@
#include <QLayout>
#include <QDialogButtonBox>
#include <retroshare/rspeers.h>
#include "GroupSelectionBox.h"
#include "GroupDefs.h"
@ -17,7 +19,6 @@ GroupSelectionBox::GroupSelectionBox(QWidget *parent)
// Fill with available groups
fillGroups();
}
void GroupSelectionBox::fillGroups()
{
std::list<RsNodeGroupId> selectedIds;
@ -78,3 +79,39 @@ void GroupSelectionBox::selectedGroupNames(QList<QString> &groupNames) const
}
}
}
std::list<RsNodeGroupId> GroupSelectionDialog::selectGroups(const std::list<RsNodeGroupId>& default_groups)
{
GroupSelectionDialog gsd(NULL) ;
gsd.mBox->setSelectedGroupIds(default_groups) ;
gsd.exec();
std::list<RsNodeGroupId> selected_groups ;
gsd.mBox->selectedGroupIds(selected_groups);
return selected_groups ;
}
GroupSelectionDialog::~GroupSelectionDialog()
{
delete mBox ;
}
GroupSelectionDialog::GroupSelectionDialog(QWidget *parent)
{
mBox = new GroupSelectionBox(this) ;
QLayout *l = new QVBoxLayout ;
setLayout(l) ;
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
l->addWidget(mBox) ;
l->addWidget(buttonBox) ;
l->update() ;
}

View File

@ -1,4 +1,5 @@
#include <QListWidget>
#include <QDialog>
#include <retroshare/rsids.h>
class GroupSelectionBox: public QListWidget
@ -8,6 +9,8 @@ class GroupSelectionBox: public QListWidget
public:
GroupSelectionBox(QWidget *parent);
static void selectGroups(const std::list<RsNodeGroupId>& default_groups) ;
void selectedGroupIds(std::list<RsNodeGroupId> &groupIds) const;
void selectedGroupNames(QList<QString> &groupNames) const;
@ -16,3 +19,17 @@ public:
private slots:
void fillGroups();
};
class GroupSelectionDialog: public QDialog
{
Q_OBJECT
public:
GroupSelectionDialog(QWidget *parent) ;
virtual ~GroupSelectionDialog() ;
static std::list<RsNodeGroupId> selectGroups(const std::list<RsNodeGroupId>& default_groups) ;
private:
GroupSelectionBox *mBox ;
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB