group{ "elm/button/base/gallery_image";
inherit: "elm/button/base/transparent";
parts {
+ rect { "rect.bg";
+ before: "swallow.bg";
+ desc { "default";
+ color: GALLERY_COLOR_IMG_BG;
+ }
+ }
swallow { "swallow.bg";
before: "elm.swallow.content";
- scale;
desc { "default";
}
}
#define GALLERY_COLOR_WHITE 255 255 255 255
+#define GALLERY_COLOR_IMG_BG 64 64 64 255
#define GALLERY_COLOR_IMG_EF_NORMAL 0 0 0 0
#define GALLERY_COLOR_IMG_EF_PRESSED 0 0 0 102
#define GALLERY_COLOR_IMG_EF_DISABLED 0 0 0 77
image: "gallery_thumbnail_select_ring_1x1.png" COMP;
}
group { "elm/layout/gallery_image_grid/hcomb_3x3_even";
+ data.item: "image_min_load_size" 116;
parts {
spacer { "spacer_t";
scale;
}
}
group { "elm/layout/gallery_image_grid/hcomb_3x3_odd";
+ data.item: "image_min_load_size" 116;
parts {
spacer { "spacer_t";
scale;
}
}
group { "elm/layout/gallery_image_grid/linear";
+ data.item: "image_min_load_size" 360;
parts {
spacer { "spacer";
scale;
FLAG_THUMBNAIL = 1,
FLAG_REMOVE = 2,
FLAG_SAVE = 4,
+ FLAG_RESOLUTION = 8,
- FLAGS_FROM_MEDIA_DB = (FLAG_THUMBNAIL | FLAG_REMOVE),
+ FLAGS_FROM_MEDIA_DB = (FLAG_THUMBNAIL | FLAG_REMOVE |
+ FLAG_RESOLUTION),
FLAGS_SIMPLE_FILE = (FLAG_SAVE)
};
const std::string &getId() const;
+ ucl::Result getResolution(int &x, int &y) const;
+
const std::string &getFilePath() const;
ucl::Result getThumbnailPath(ThumbnailPathGetCb cb) const;
MediaItem(int flags, MediaType type);
ucl::Result prepare(media_info_h media);
+ ucl::Result prepareImage(media_info_h media);
ucl::Result prepare(std::string filePath);
private:
ucl::Result initThumbPath(media_info_h media) const;
- void freeMediaInfo() const;
private:
// XXX This proxy is needed to deal with cases when
void completeCb(media_content_error_e error, const char *path);
};
+ private:
+ using AutoMediaInfo = ucl::AutoHandle<
+ media_info_h, int, media_info_destroy>;
+
private:
const int m_flags;
const MediaType m_type;
std::string m_mediaId;
std::string m_filePath;
- mutable media_info_h m_media;
+ int m_resolutionX;
+ int m_resolutionY;
+ mutable AutoMediaInfo m_media;
mutable std::string m_thumbPath;
mutable std::unique_ptr<ThumbCbProxy> m_thumbCbProxy;
bool m_isValid;
#include <media_content.h>
+#include "ucl/misc/AutoHandle.h"
+
#include "../types.h"
namespace gallery {
Builder &setNaviframe(const ucl::NaviframeSRef &navi);
Builder &setMedia(const MediaItemSRef &media);
Builder &setZoomIn(int x, int y);
+ Builder &setImageLoadSize(int size, bool isFull);
Builder &setExitOnZoomOut(bool value);
ViewerPageWRef build(ExitRequestHandler onExitRequest) const;
private:
MediaItemSRef m_media;
int m_zoomInX;
int m_zoomInY;
+ int m_imageLoadSize;
+ bool m_isImageLoadSizeFull;
bool m_exitOnZoomOut;
};
bool exitOnZoomOut);
virtual ~ViewerPage();
- ucl::Result prepare();
+ ucl::Result prepare(int imageLoadSize, bool isImageLoadSizeFull);
void zoomIn(int originX, int originY);
};
struct ItemParams {
- int flags;
std::string imagePath;
std::string bgImagePath;
+ int imageWidth;
+ int imageHeight;
+ int flags;
+ };
+
+ struct ItemInfo {
+ int imageLoadSize;
+ bool isImageLoadSizeFull;
+ bool isImageLoaded;
};
class Unrealizer : ucl::NonCopyable {
ucl::Result isItemRealized(int itemIndex) const;
+ ucl::Result getItemInfo(int itemIndex, ItemInfo &info) const;
+
int getScrolledToItemIndex() const;
ucl::Result scrollToItem(int itemIndex);
ucl::Result bringInItem(int itemIndex);
public:
Builder();
Builder &setHighResImagePath(std::string path);
+ Builder &setLoadSize(int value);
Builder &setForceLoad(bool value);
ImageViewerSRef build(ucl::ElmWidget &parent) const;
private:
std::string m_highResPath;
+ int m_loadSize;
bool m_forceLoad;
};
private:
friend class ucl::RefCountObj<ImageViewer>;
ImageViewer(ucl::RefCountObjBase &rc, Evas_Object *scroller,
- const std::string &highResPath, bool forceLoad);
+ const std::string &highResPath, int loadSize, bool forceLoad);
virtual ~ImageViewer();
- void prepare(const std::string &highResPath, bool forceLoad);
+ void prepare(const std::string &highResPath,
+ int loadSize, bool forceLoad);
void onImagePreloaded(ucl::Widget &widget, void *eventInfo);
void onScrollerResize(ucl::Widget &widget, void *eventInfo);
namespace gallery { namespace { namespace impl {
+ using namespace ucl;
+
const std::string MIME_PREFIX_IMAGE {"image/"};
const std::string MIME_PREFIX_VIDEO {"video/"};
+ using AutoImageMeta = AutoHandle<image_meta_h, int, image_meta_destroy>;
+
MediaType getFileMediaType(const std::string &filePath)
{
const auto ext = util::extractFileExtension(filePath);
MediaItem::MediaItem(const int flags, const MediaType type) :
m_flags(flags),
m_type(type),
- m_media(nullptr),
+ m_resolutionX(0),
+ m_resolutionY(0),
m_isValid(false)
{
}
MediaItem::~MediaItem()
{
cancelThumbnailPathGet();
- freeMediaInfo();
}
MediaItemSRef MediaItem::newInstance(const media_info_h media)
{
media_content_type_e contentType = MEDIA_CONTENT_TYPE_OTHERS;
- const int ret = media_info_get_media_type(media, &contentType);
- if (ret != 0) {
- LOG_RETURN_VALUE(RES_FAIL, {},
- "media_info_get_media_type() failed: %d", ret);
- }
+ FAIL_RETURN_VALUE(util::get(media_info_get_media_type, contentType,
+ media), {}, "media_info_get_media_type() failed!");
auto result = makeShared<MediaItem>(FLAGS_FROM_MEDIA_DB,
toMediaType(contentType));
FAIL_RETURN(initThumbPath(media), "initThumbPath() failed!");
if (isEmpty(m_thumbPath)) {
- const int ret = media_info_clone(&m_media, media);
- if (ret != 0) {
- m_media = nullptr;
- LOG_RETURN(RES_FAIL, "media_info_clone() failed: %d", ret);
- }
+ media_info_h mediaClone = nullptr;
+ FAIL_RETURN(util::call(media_info_clone, &mediaClone, media),
+ "media_info_clone() failed!");
+ m_media = mediaClone;
}
FAIL_RETURN(util::getNz(media_info_get_media_id, m_mediaId, media),
- "media_info_get_media_id() failed!");
+ "media_info_get_media_id() failed!");
FAIL_RETURN(util::getNz(media_info_get_file_path, m_filePath, media),
- "media_info_get_file_path() failed!");
+ "media_info_get_file_path() failed!");
+
+ if (m_type == MediaType::IMAGE) {
+ prepareImage(media);
+ }
m_isValid = true;
return RES_OK;
}
+ Result MediaItem::prepareImage(const media_info_h media)
+ {
+ impl::AutoImageMeta imageMeta;
+
+ FAIL_RETURN(util::getNz(media_info_get_image, imageMeta, media),
+ "media_info_get_image() failed!");
+
+ FAIL_RETURN(util::getNz(image_meta_get_width, m_resolutionX,
+ imageMeta), "image_meta_get_width() failed!");
+
+ FAIL_RETURN(util::getNz(image_meta_get_height, m_resolutionY,
+ imageMeta), "image_meta_get_height() failed!");
+
+ return RES_OK;
+ }
+
Result MediaItem::initThumbPath(const media_info_h media) const
{
FAIL_RETURN(util::get(media_info_get_thumbnail_path, m_thumbPath,
- media), "media_info_get_thumbnail_path() failed!");
+ media), "media_info_get_thumbnail_path() failed!");
return RES_OK;
}
return RES_OK;
}
- void MediaItem::freeMediaInfo() const
- {
- if (m_media) {
- const int ret = media_info_destroy(m_media);
- if (ret != 0) {
- WLOG("media_info_destroy() failed: %d", ret);
- }
- m_media = nullptr;
- }
- }
-
bool MediaItem::isValid() const
{
return m_isValid;
return m_mediaId;
}
+ Result MediaItem::getResolution(int &x, int &y) const
+ {
+ if (!(m_flags & FLAG_RESOLUTION)) {
+ LOG_RETURN(RES_NOT_SUPPORTED, "Operation not supported!");
+ }
+
+ x = m_resolutionX;
+ y = m_resolutionY;
+
+ return RES_OK;
+ }
+
const std::string &MediaItem::getFilePath() const
{
return m_filePath;
LOG_RETURN(RES_FAIL, "media_info_delete_from_db() failed: %d", ret);
}
- freeMediaInfo();
+ m_media = nullptr;
m_isValid = false;
}
{
MutexLock lock(getMediaMutex());
- const int ret = media_info_create_thumbnail(m_media,
- CALLBACK_B(ThumbCbProxy::completeCb), cbProxy.get());
- if (ret != 0) {
- LOG_RETURN(RES_FAIL, "media_info_clone() failed: %d", ret);
- }
+ FAIL_RETURN(util::call(media_info_create_thumbnail, m_media,
+ CALLBACK_B(ThumbCbProxy::completeCb), cbProxy.get()),
+ "media_info_create_thumbnail() failed!");
}
m_thumbCbProxy = std::move(cbProxy);
result = RES_FAIL;
} else {
item->m_thumbPath = path;
- item->freeMediaInfo();
+ item->m_media = nullptr;
}
const auto proxy = std::move(*item->m_thumbCbProxy);
ImageGrid::ItemParams params = {};
params.imagePath = m_media->getFilePath();
params.bgImagePath = path;
+ m_media->getResolution(params.imageWidth, params.imageHeight);
addFlags(params);
m_imageGrid.updateItem(m_index, params);
void PreviewPage::openViewer(const int itemIndex, const int x, const int y)
{
- m_page = ViewerPage::Builder().
- setNaviframe(asShared(getNaviframe())).
- setZoomIn(x, y).
- setMedia(m_items[itemIndex]->getMedia()).
- build(DELEGATE(PreviewPage::onPageExitRequest, this));
+ ViewerPage::Builder builder;
+ builder.setNaviframe(asShared(getNaviframe()));
+ builder.setZoomIn(x, y);
+ builder.setMedia(m_items[itemIndex]->getMedia());
+
+ ImageGrid::ItemInfo info = {};
+ m_imageGrid->getItemInfo(itemIndex, info);
+ if (info.isImageLoaded) {
+ builder.setImageLoadSize(info.imageLoadSize,
+ info.isImageLoadSizeFull);
+ }
+
+ m_page = builder.build(DELEGATE(PreviewPage::onPageExitRequest, this));
}
void PreviewPage::onPageExitRequest(Page &page)
ImageGrid::ItemParams params = {};
params.imagePath = path;
+ m_parent.m_mediaItems[m_index]->getResolution(
+ params.imageWidth, params.imageHeight);
m_parent.m_imageGrid->updateItem(m_index, params);
}
ViewerPage::Builder::Builder() :
m_zoomInX(-1),
m_zoomInY(-1),
+ m_imageLoadSize(-1),
+ m_isImageLoadSizeFull(false),
m_exitOnZoomOut(true)
{
}
return *this;
}
+ ViewerPage::Builder &ViewerPage::Builder::setImageLoadSize(
+ const int size, const bool isFull)
+ {
+ m_imageLoadSize = size;
+ m_isImageLoadSizeFull = isFull;
+ return *this;
+ }
+
ViewerPage::Builder &ViewerPage::Builder::setExitOnZoomOut(const bool value)
{
m_exitOnZoomOut = value;
auto result = makeShared<ViewerPage>(
m_navi, onExitRequest, m_media, m_exitOnZoomOut);
- FAIL_RETURN_VALUE(result->prepare(), {}, "result->prepare() failed!");
+ FAIL_RETURN_VALUE(result->prepare(m_imageLoadSize,
+ m_isImageLoadSizeFull), {}, "result->prepare() failed!");
if ((m_zoomInX >= 0) && (m_zoomInY >= 0)) {
result->zoomIn(m_zoomInX, m_zoomInY);
}
}
- Result ViewerPage::prepare()
+ Result ViewerPage::prepare(const int imageLoadSize,
+ const bool isImageLoadSizeFull)
{
const int mediaFlags = m_media->getFlags();
const bool canSave = (mediaFlags & MediaItem::FLAG_SAVE);
const bool hasThumb = (mediaFlags & MediaItem::FLAG_THUMBNAIL);
+ const bool useThumb = (hasThumb && (imageLoadSize < 0));
+ const bool forceLoad = (!useThumb && isImageLoadSizeFull);
m_imageViewer = ImageViewer::Builder().
setHighResImagePath(m_media->getFilePath()).
- setForceLoad(!hasThumb).
+ setLoadSize(imageLoadSize).
+ setForceLoad(forceLoad).
build(getNaviframe());
if (!m_imageViewer) {
LOG_RETURN(RES_FAIL, "ImageViewer::build() failed!");
m_more->setListener(asWeakThis<IMoreOptionsListener>(this));
}
- if (hasThumb) {
+ if (useThumb) {
FAIL_RETURN(m_media->getThumbnailPath(DELEGATE(
ViewerPage::onThumbnail, this)),
"m_media->getThumbnailPath() failed!");
+ } else if (!forceLoad) {
+ m_imageViewer->setLowResImagePath(m_media->getFilePath());
}
m_imageViewer->addEventHandler(IMAGE_VIEWER_ZOOM_END,
const TString SIGNAL_SELECT_ITEM_FMT {"gallery,select,%d"};
const TString SIGNAL_UNSELECT_ITEM_FMT {"gallery,unselect,%d"};
+ constexpr EdjeDataKey DATA_IMAGE_MIN_LOAD_SIZE {"image_min_load_size"};
+
constexpr EdjeSignal SIGNAL_FORCE_SELECT_MODE
{"gallery,force,select,mode"};
constexpr EdjeSignal SIGNAL_ENABLE_SELECT_MODE
RefCountAware(&rc),
m_imageGrid(imageGrid),
m_btn(elm_button_add(parent)),
- m_image(evas_object_image_filled_add(m_btn.getEvas())),
- m_realizeIndex(-1),
- m_imageLoadSize(0),
- m_wasUpdated(false),
- m_isImageEmpty(false),
- m_isClicksBlocked(false),
- m_isSelected(false)
+ m_image(evas_object_image_filled_add(m_btn.getEvas()))
{
m_btn.setFocusAlowed(false);
m_btn.setStyle(impl::ITEM_BTN_STYLE);
return m_btn;
}
- void setImageLoadSize(const int value)
+ void setImageMinLoadSize(const int value)
{
- m_imageLoadSize = value;
- evas_object_image_load_size_set(m_image,
- m_imageLoadSize, m_imageLoadSize);
+ m_imageMinLoadSize = std::max(value, 1);
}
bool isRealized() const
return (m_realizeIndex >= 0);
}
+ void getInfo(ItemInfo &info) const
+ {
+ int w = m_imageWidth;
+ int h = m_imageHeight;
+ if (!impl::getImageSize(m_image, w, h)) {
+ WLOG("Invalid image size!");
+ }
+
+ info.imageLoadSize = m_imageLoadSize;
+ info.isImageLoadSizeFull = (
+ (w == m_imageWidth) && (h == m_imageHeight));
+ info.isImageLoaded = m_isImageLoaded;
+ }
+
void realize(const int itemIndex)
{
if (isRealized()) {
if (!m_wasUpdated || (params.flags & UF_LOSE_IMAGE)) {
makeTransparent(m_image);
m_isImageEmpty = true;
+ m_isImageLoaded = false;
}
return;
}
makeTransparent(m_image);
m_isImageEmpty = false;
+ m_isImageLoaded = false;
+
+ m_imageWidth = std::max(params.imageWidth, 1);
+ m_imageHeight = std::max(params.imageHeight, 1);
evas_object_image_file_set(m_image,
params.imagePath.c_str(), NULL);
+ updateImageLoadSize();
+
evas_object_image_preload(m_image, EINA_FALSE);
}
- void onImagePreloaded(Widget &widget, void *eventInfo)
+ void updateImageLoadSize()
{
- if (m_isImageEmpty) {
+ const int newLoadSize = ((m_imageWidth > m_imageHeight) ?
+ (m_imageMinLoadSize * m_imageWidth / m_imageHeight) :
+ (m_imageMinLoadSize * m_imageHeight / m_imageWidth));
+ if (newLoadSize == m_imageLoadSize) {
return;
}
- int w = 1;
- int h = 1;
+ int w = m_imageWidth;
+ int h = m_imageHeight;
if (!impl::getImageSize(m_image, w, h)) {
WLOG("Invalid image size!");
}
- m_image.setARHint(WidgetARHint::NEITHER, w, h);
+
+ if (isCurentLoadSizeSatisfying(w, h) ||
+ !isImageRespectsLoadSize(w, h)) {
+ return;
+ }
+
+ m_imageLoadSize = newLoadSize;
+ evas_object_image_load_size_set(m_image,
+ newLoadSize, newLoadSize);
+ }
+
+ bool isCurentLoadSizeSatisfying(
+ const int curImgW, const int curImgH) const
+ {
+ const int kw = ((m_imageWidth > m_imageMinLoadSize) ?
+ (curImgW / m_imageMinLoadSize) : 1);
+ const int kh = ((m_imageHeight > m_imageMinLoadSize) ?
+ (curImgH / m_imageMinLoadSize) : 1);
+ return (((kw == 1) && (kh >= 1)) || ((kh == 1) && (kw >= 1)));
+ }
+
+ bool isImageRespectsLoadSize(
+ const int curImgW, const int curImgH) const
+ {
+ return ((m_imageLoadSize <= 0) ||
+ (curImgW != m_imageWidth) ||
+ (curImgH != m_imageHeight) || (
+ ((curImgW / m_imageLoadSize) <= 1) &&
+ ((curImgH / m_imageLoadSize) <= 1)
+ ));
+ }
+
+ void onImagePreloaded(Widget &widget, void *eventInfo)
+ {
+ if (m_isImageEmpty) {
+ return;
+ }
+
+ m_isImageLoaded = true;
+
+ m_image.setARHint(WidgetARHint::NEITHER,
+ m_imageWidth, m_imageHeight);
makeWhite(m_image);
}
if (!m_bgImage) {
m_bgImage = makeShared<Widget>(
evas_object_image_filled_add(m_btn.getEvas()));
- evas_object_image_load_size_set(*m_bgImage,
- m_imageLoadSize, m_imageLoadSize);
m_btn.setContent(*m_bgImage, impl::BTN_PART_BG);
show(*m_bgImage);
}
evas_object_image_file_set(*m_bgImage,
params.bgImagePath.c_str(), NULL);
- int w = 1;
- int h = 1;
- if (!impl::getImageSize(m_image, w, h) &&
- !impl::getImageSize(*m_bgImage, w, h)) {
- WLOG("Invalid image size!");
- }
- m_bgImage->setARHint(WidgetARHint::NEITHER, w, h);
+ m_bgImage->setARHint(WidgetARHint::NEITHER,
+ m_imageWidth, m_imageHeight);
makeWhite(*m_bgImage);
}
StyledWidget m_image;
WidgetSRef m_bgImage;
TouchParserSRef m_touchParser;
- int m_realizeIndex;
- int m_imageLoadSize;
- bool m_wasUpdated;
- bool m_isImageEmpty;
- bool m_isClicksBlocked;
- bool m_isSelected;
+ int m_realizeIndex = -1;
+ int m_imageLoadSize = 0;
+ int m_imageMinLoadSize = 1;
+ int m_imageWidth = 1;
+ int m_imageHeight = 1;
+ bool m_wasUpdated = false;
+ bool m_isImageEmpty = false;
+ bool m_isImageLoaded = false;
+ bool m_isClicksBlocked = false;
+ bool m_isSelected = false;
};
public:
m_layout(elm_layout_add(imageGrid.m_box), true),
m_isRealized(false)
{
+ int imageMinLoadSize = 0;
+
if (isValid(m_info.slotThemes[isOdd])) {
if (!m_layout.setTheme(m_info.slotThemes[isOdd])) {
ELOG("setTheme() failed!");
edje_object_message_signal_process(
elm_layout_edje_get(m_layout));
}
+ imageMinLoadSize = ELM_SCALE_SIZE(m_layout.getData(
+ impl::DATA_IMAGE_MIN_LOAD_SIZE).asInt());
}
fill(m_layout);
show(m_layout);
const auto partName = impl::SLOT_PART_FMT.format(i);
m_layout.setContent(m_items.back()->getWidget(),
EdjePart(partName.c_str()));
+
+ m_items.back()->setImageMinLoadSize(imageMinLoadSize);
}
}
return std::max((m_info.isHorizontal ? w : h), 1);
}
- void setImageLoadSize(const int value)
- {
- for (auto &item: m_items) {
- item->setImageLoadSize(value);
- }
- }
-
void unrealize()
{
if (!m_isRealized) {
return m_items[itemOffset]->isRealized();
}
+ void getItemInfo(const int itemOffset, ItemInfo &info) const
+ {
+ m_items[itemOffset]->getInfo(info);
+ }
+
private:
void setSelected(const int itemOffset, const bool selected)
{
});
}
+ Result ImageGrid::getItemInfo(const int itemIndex, ItemInfo &info) const
+ {
+ return doWithItem(itemIndex,
+ [&info](Slot &slot, const int itemOffset)
+ {
+ slot.getItemInfo(itemOffset, info);
+ return RES_OK;
+ });
+ }
+
template <class FUNC>
Result ImageGrid::doWithItem(const int itemIndex, FUNC &&func) const
{
setSlotSize(slot->getSize());
}
- slot->setImageLoadSize(m_slotSize);
-
elm_box_pack_before(m_box, slot->getLayout(), m_rect2);
m_slots.emplace_back(std::move(slot));
// ImageViewer::Builder //
ImageViewer::Builder::Builder() :
+ m_loadSize(0),
m_forceLoad(false)
{
}
return *this;
}
+ ImageViewer::Builder &ImageViewer::Builder::setLoadSize(const int value)
+ {
+ m_loadSize = value;
+ return *this;
+ }
+
ImageViewer::Builder &ImageViewer::Builder::setForceLoad(const bool value)
{
m_forceLoad = value;
LOG_RETURN_VALUE(RES_FAIL, {}, "elm_scroller_add() failed!");
}
- return makeShared<ImageViewer>(scroller, m_highResPath, m_forceLoad);
+ return makeShared<ImageViewer>(scroller, m_highResPath,
+ m_loadSize, m_forceLoad);
}
// ImageViewer //
ImageViewer::ImageViewer(RefCountObjBase &rc, Evas_Object *const scroller,
- const std::string &highResPath, const bool forceLoad) :
+ const std::string &highResPath, const int loadSize,
+ const bool forceLoad) :
ElmWidget(&rc, scroller, true),
m_scroller(makeShared<StyledWidget>(scroller)),
m_animator(nullptr),
m_state(State::ZOOMED_OUT)
{
- prepare(highResPath, forceLoad);
+ prepare(highResPath, loadSize, forceLoad);
}
ImageViewer::~ImageViewer()
}
void ImageViewer::prepare(const std::string &highResPath,
- const bool forceLoad)
+ const int loadSize, const bool forceLoad)
{
expandAndFill(*m_scroller);
m_scroller->setStyle(SCROLLER_STYLE);
show(m_grid);
evas_object_grid_pack(m_grid, m_lowResImage, 0, 0, 1, 1);
+ if (loadSize > 0) {
+ evas_object_image_load_size_set(m_lowResImage, loadSize, loadSize);
+ }
show(m_lowResImage);
makeTransparent(m_lowResImage);
evas_object_grid_pack(m_grid, m_highResImage, 0, 0, 1, 1);
if (forceLoad) {
+ if (loadSize > 0) {
+ evas_object_image_load_size_set(m_highResImage,
+ loadSize, loadSize);
+ }
evas_object_image_file_set(m_highResImage,
highResPath.c_str(), nullptr);
show(m_highResImage);