/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
// property used to specify the QtAccessibleWidget* that should be used as the // custom accessible interface for an object constchar* const PROPERTY_ACCESSIBLE = "accessible-interface";
if (m_xAccessible.is())
{ try
{
xAc = m_xAccessible->getAccessibleContext();
} catch (css::lang::DisposedException /*ex*/)
{
SAL_WARN("vcl.qt", "Accessible context disposed already");
} // sometimes getAccessibleContext throws also RuntimeException if context is no longer alive catch (css::uno::RuntimeException /*ex*/)
{ // so let's catch it here, cuz otherwise soffice falls flat on its face // with FatalError and nothing else
SAL_WARN("vcl.qt", "Accessible context no longer alive");
}
}
QWindow* QtAccessibleWidget::window() const
{ if (m_rObject.isWidgetType())
{
QWidget& rWidget = static_cast<QWidget&>(m_rObject);
QWidget* pWindow = rWidget.window(); if (pWindow) return pWindow->windowHandle();
}
QAccessibleInterface* pParent = parent(); if (pParent) return pParent->window();
return nullptr;
}
int QtAccessibleWidget::childCount() const
{
Reference<XAccessibleContext> xAc = getAccessibleContextImpl(); if (!xAc.is()) return 0;
sal_Int64 nChildCount = xAc->getAccessibleChildCount(); if (nChildCount > std::numeric_limits<int>::max())
{
SAL_WARN("vcl.qt", "QtAccessibleWidget::childCount: Child count exceeds maximum int value, " "returning max int.");
nChildCount = std::numeric_limits<int>::max();
}
return nChildCount;
}
int QtAccessibleWidget::indexOfChild(const QAccessibleInterface* pChild) const
{ const QtAccessibleWidget* pAccessibleWidget = dynamic_cast<const QtAccessibleWidget*>(pChild); if (!pAccessibleWidget)
{
SAL_WARN( "vcl.qt", "QtAccessibleWidget::indexOfChild called with child that is no QtAccessibleWidget"); return -1;
}
Reference<XAccessibleContext> xContext = pAccessibleWidget->getAccessibleContextImpl(); if (!xContext.is()) return -1;
sal_Int64 nChildIndex = xContext->getAccessibleIndexInParent(); if (nChildIndex > std::numeric_limits<int>::max())
{ // use -2 when the child index is too large to fit into 32 bit to neither use the // valid index of another child nor -1, which would e.g. make the Orca screen reader // interpret the object as being a zombie
SAL_WARN("vcl.qt", "QtAccessibleWidget::indexOfChild: Child index exceeds maximum int value, " "returning -2.");
nChildIndex = -2;
} return nChildIndex;
}
namespace
{
sal_Int16 lcl_matchQtTextBoundaryType(QAccessible::TextBoundaryType boundaryType)
{ switch (boundaryType)
{ case QAccessible::CharBoundary: return css::accessibility::AccessibleTextType::CHARACTER; case QAccessible::WordBoundary: return css::accessibility::AccessibleTextType::WORD; case QAccessible::SentenceBoundary: return css::accessibility::AccessibleTextType::SENTENCE; case QAccessible::ParagraphBoundary: return css::accessibility::AccessibleTextType::PARAGRAPH; case QAccessible::LineBoundary: return css::accessibility::AccessibleTextType::LINE; case QAccessible::NoBoundary: // assert here, better handle it directly at call site
assert(false
&& "No match for QAccessible::NoBoundary, handle it separately at call site."); break; default: break;
}
QAccessible::Relation lcl_matchUnoRelation(AccessibleRelationType eRelationType)
{ // Qt semantics is the other way around switch (eRelationType)
{ #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) case AccessibleRelationType_CONTENT_FLOWS_FROM: return QAccessible::FlowsTo; case AccessibleRelationType_CONTENT_FLOWS_TO: return QAccessible::FlowsFrom; #endif case AccessibleRelationType_CONTROLLED_BY: return QAccessible::Controller; case AccessibleRelationType_CONTROLLER_FOR: return QAccessible::Controlled; #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) case AccessibleRelationType_DESCRIBED_BY: return QAccessible::DescriptionFor; #endif case AccessibleRelationType_LABELED_BY: return QAccessible::Label; case AccessibleRelationType_LABEL_FOR: return QAccessible::Labelled; case AccessibleRelationType_INVALID: case AccessibleRelationType_MEMBER_OF: case AccessibleRelationType_SUB_WINDOW_OF: case AccessibleRelationType_NODE_CHILD_OF: default:
SAL_WARN("vcl.qt", "Unmatched relation: " << static_cast<int>(eRelationType)); return {};
}
}
void lcl_appendRelation(QVector<QPair<QAccessibleInterface*, QAccessible::Relation>>* relations,
AccessibleRelation aRelation, QAccessible::Relation match)
{
QAccessible::Relation aQRelation = lcl_matchUnoRelation(aRelation.RelationType); // skip in case there's no Qt relation matching the filter if (!(aQRelation & match)) return;
if (xAc->getAccessibleParent().is()) return QAccessible::queryAccessibleInterface(
QtAccessibleRegistry::getQObject(xAc->getAccessibleParent()));
// go via the QObject hierarchy; a11y objects like the application object // and native weld::Widgets are handled solely by Qt and have // no LO-internal a11y objects associated with them
QObject* pObj = object(); if (pObj && pObj->parent()) return QAccessible::queryAccessibleInterface(pObj->parent());
// return app as parent for top-level objects return QAccessible::queryAccessibleInterface(qApp);
}
switch (text)
{ case QAccessible::Name: return toQString(xAc->getAccessibleName()); case QAccessible::Description: case QAccessible::DebugDescription: return toQString(xAc->getAccessibleDescription()); #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) case QAccessible::Identifier:
{
Reference<XAccessibleContext2> xContext(getAccessibleContextImpl(), UNO_QUERY); if (!xContext.is()) return QString(); return toQString(xContext->getAccessibleId());
} #endif case QAccessible::Value: case QAccessible::Help: case QAccessible::Accelerator: case QAccessible::UserText: default: return QString();
}
}
QAccessible::Role QtAccessibleWidget::role() const
{
Reference<XAccessibleContext> xAc = getAccessibleContextImpl(); if (!xAc.is()) return QAccessible::NoRole;
switch (xAc->getAccessibleRole())
{ case AccessibleRole::UNKNOWN: return QAccessible::NoRole; case AccessibleRole::ALERT: return QAccessible::AlertMessage; case AccessibleRole::BLOCK_QUOTE: #if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) return QAccessible::BlockQuote; #else return QAccessible::Paragraph; #endif case AccessibleRole::COLUMN_HEADER: return QAccessible::ColumnHeader; case AccessibleRole::CANVAS: return QAccessible::Canvas; case AccessibleRole::CHECK_BOX: return QAccessible::CheckBox; case AccessibleRole::CHECK_MENU_ITEM: return QAccessible::MenuItem; case AccessibleRole::COLOR_CHOOSER: return QAccessible::ColorChooser; case AccessibleRole::COMBO_BOX: return QAccessible::ComboBox; case AccessibleRole::DATE_EDITOR: return QAccessible::EditableText; case AccessibleRole::DESKTOP_ICON: return QAccessible::Graphic; case AccessibleRole::DESKTOP_PANE: case AccessibleRole::DIRECTORY_PANE: return QAccessible::Pane; case AccessibleRole::DIALOG: return QAccessible::Dialog; case AccessibleRole::DOCUMENT: return QAccessible::Document; case AccessibleRole::EMBEDDED_OBJECT: return QAccessible::UserRole; case AccessibleRole::END_NOTE: return QAccessible::Note; case AccessibleRole::FILE_CHOOSER: return QAccessible::Dialog; case AccessibleRole::FILLER: return QAccessible::Whitespace; case AccessibleRole::FONT_CHOOSER: return QAccessible::UserRole; case AccessibleRole::FOOTER: return QAccessible::Footer; case AccessibleRole::FOOTNOTE: return QAccessible::Note; case AccessibleRole::FRAME: // report Pane instead of Window role for a frame that's not actually // an accessible frame (top-level window with title bar) itself, // but a child of the real top-level if (QAccessibleInterface* pParent = parent())
{ const QAccessible::Role eParentRole = pParent->role(); if (eParentRole == QAccessible::Window || eParentRole == QAccessible::Dialog) return QAccessible::Pane;
} return QAccessible::Window; case AccessibleRole::GLASS_PANE: return QAccessible::UserRole; case AccessibleRole::GRAPHIC: return QAccessible::Graphic; case AccessibleRole::GROUP_BOX: return QAccessible::Grouping; case AccessibleRole::HEADER: return QAccessible::UserRole; case AccessibleRole::HEADING: return QAccessible::Heading; case AccessibleRole::HYPER_LINK: return QAccessible::Link; case AccessibleRole::ICON: return QAccessible::Graphic; case AccessibleRole::INTERNAL_FRAME: return QAccessible::UserRole; case AccessibleRole::LABEL: return QAccessible::StaticText; case AccessibleRole::LAYERED_PANE: return QAccessible::Pane; case AccessibleRole::LIST: return QAccessible::List; case AccessibleRole::LIST_ITEM: return QAccessible::ListItem; case AccessibleRole::MENU: case AccessibleRole::MENU_BAR: return QAccessible::MenuBar; case AccessibleRole::MENU_ITEM: return QAccessible::MenuItem; case AccessibleRole::NOTIFICATION: return QAccessible::Notification; case AccessibleRole::OPTION_PANE: return QAccessible::Pane; case AccessibleRole::PAGE_TAB: return QAccessible::PageTab; case AccessibleRole::PAGE_TAB_LIST: return QAccessible::PageTabList; case AccessibleRole::PANEL: return QAccessible::Pane; case AccessibleRole::PARAGRAPH: return QAccessible::Paragraph; case AccessibleRole::PASSWORD_TEXT: // Qt API doesn't have a separate role to distinguish password edits, // but a 'passwordEdit' state return QAccessible::EditableText; case AccessibleRole::POPUP_MENU: return QAccessible::PopupMenu; case AccessibleRole::PUSH_BUTTON: return QAccessible::Button; case AccessibleRole::PROGRESS_BAR: return QAccessible::ProgressBar; case AccessibleRole::RADIO_BUTTON: return QAccessible::RadioButton; case AccessibleRole::RADIO_MENU_ITEM: return QAccessible::MenuItem; case AccessibleRole::ROW_HEADER: return QAccessible::RowHeader; case AccessibleRole::ROOT_PANE: return QAccessible::Pane; case AccessibleRole::SCROLL_BAR: return QAccessible::ScrollBar; case AccessibleRole::SCROLL_PANE: return QAccessible::Pane; case AccessibleRole::SHAPE: return QAccessible::Graphic; case AccessibleRole::SEPARATOR: return QAccessible::Separator; case AccessibleRole::SLIDER: return QAccessible::Slider; case AccessibleRole::SPIN_BOX: return QAccessible::SpinBox; case AccessibleRole::SPLIT_PANE: return QAccessible::Pane; case AccessibleRole::STATUS_BAR: return QAccessible::StatusBar; case AccessibleRole::TABLE: return QAccessible::Table; case AccessibleRole::TABLE_CELL: return QAccessible::Cell; case AccessibleRole::TEXT: return QAccessible::EditableText; case AccessibleRole::TEXT_FRAME: return QAccessible::Pane; case AccessibleRole::TOGGLE_BUTTON: return QAccessible::Button; case AccessibleRole::TOOL_BAR: return QAccessible::ToolBar; case AccessibleRole::TOOL_TIP: return QAccessible::ToolTip; case AccessibleRole::TREE: return QAccessible::Tree; case AccessibleRole::VIEW_PORT: return QAccessible::UserRole; case AccessibleRole::BUTTON_DROPDOWN: return QAccessible::ButtonDropDown; case AccessibleRole::BUTTON_MENU: return QAccessible::ButtonMenu; case AccessibleRole::CAPTION: return QAccessible::StaticText; case AccessibleRole::CHART: return QAccessible::Chart; case AccessibleRole::EDIT_BAR: return QAccessible::Equation; case AccessibleRole::FORM: return QAccessible::Form; case AccessibleRole::IMAGE_MAP: return QAccessible::Graphic; case AccessibleRole::NOTE: return QAccessible::Note; case AccessibleRole::RULER: return QAccessible::UserRole; case AccessibleRole::SECTION: return QAccessible::Section; case AccessibleRole::TREE_ITEM: return QAccessible::TreeItem; case AccessibleRole::TREE_TABLE: return QAccessible::Tree; case AccessibleRole::COMMENT: return QAccessible::Note; case AccessibleRole::COMMENT_END: return QAccessible::UserRole; case AccessibleRole::DOCUMENT_PRESENTATION: return QAccessible::Document; case AccessibleRole::DOCUMENT_SPREADSHEET: return QAccessible::Document; case AccessibleRole::DOCUMENT_TEXT: return QAccessible::Document; case AccessibleRole::STATIC: return QAccessible::StaticText; case AccessibleRole::WINDOW: // top-level window without title bar return QAccessible::Window;
}
namespace
{ void lcl_addState(QAccessible::State* state, sal_Int64 nState)
{ switch (nState)
{ case AccessibleStateType::INVALID:
state->invalid = true; break; case AccessibleStateType::ACTIVE:
state->active = true; break; case AccessibleStateType::ARMED: // No match break; case AccessibleStateType::BUSY:
state->busy = true; break; case AccessibleStateType::CHECKABLE:
state->checkable = true; break; case AccessibleStateType::CHECKED:
state->checked = true; break; case AccessibleStateType::EDITABLE:
state->editable = true; break; case AccessibleStateType::ENABLED:
state->disabled = false; break; case AccessibleStateType::EXPANDABLE:
state->expandable = true; break; case AccessibleStateType::EXPANDED:
state->expanded = true; break; case AccessibleStateType::FOCUSABLE:
state->focusable = true; break; case AccessibleStateType::FOCUSED:
state->focused = true; break; case AccessibleStateType::HORIZONTAL: // No match break; case AccessibleStateType::ICONIFIED: // No match break; case AccessibleStateType::INDETERMINATE:
state->checkStateMixed = true; break; case AccessibleStateType::MANAGES_DESCENDANTS: // No match break; case AccessibleStateType::MODAL:
state->modal = true; break; case AccessibleStateType::MOVEABLE:
state->movable = true; break; case AccessibleStateType::MULTI_LINE:
state->multiLine = true; break; case AccessibleStateType::OPAQUE: // No match break; case AccessibleStateType::PRESSED:
state->pressed = true; break; case AccessibleStateType::RESIZABLE:
state->sizeable = true; break; case AccessibleStateType::SELECTABLE:
state->selectable = true; break; case AccessibleStateType::SELECTED:
state->selected = true; break; case AccessibleStateType::SENSITIVE: // No match break; case AccessibleStateType::SHOWING: // No match break; case AccessibleStateType::SINGLE_LINE: // No match break; case AccessibleStateType::STALE: // No match break; case AccessibleStateType::TRANSIENT: // No match break; case AccessibleStateType::VERTICAL: // No match break; case AccessibleStateType::VISIBLE:
state->invisible = false; break; case AccessibleStateType::DEFAULT: // No match break; case AccessibleStateType::DEFUNC:
state->invalid = true; break; case AccessibleStateType::MULTI_SELECTABLE:
state->multiSelectable = true; break; default:
SAL_WARN("vcl.qt", "Unmapped state: " << nState); break;
}
}
}
if (pWindow)
{
css::uno::Reference<XAccessible> xAcc = pWindow->GetAccessible(); // insert into registry so the association between the XAccessible and the QtWidget // is remembered rather than creating a different QtXAccessible when a QObject is needed later
QtAccessibleRegistry::insert(xAcc, pObject); returnnew QtAccessibleWidget(xAcc, *pObject);
}
} if (classname == QLatin1String("QtXAccessible"))
{
QtXAccessible* pXAccessible = static_cast<QtXAccessible*>(pObject); if (pXAccessible->m_xAccessible.is())
{
QtAccessibleWidget* pRet
= new QtAccessibleWidget(pXAccessible->m_xAccessible, *pObject); // clear the reference in the QtXAccessible, no longer needed now that the QtAccessibleWidget holds one
pXAccessible->m_xAccessible.clear(); return pRet;
}
}
// unset/delete the current/default accessible of the object
QAccessibleInterface* pOldAccessible = QAccessible::queryAccessibleInterface(&rObject); const QAccessible::Id nId = QAccessible::uniqueId(pOldAccessible);
QAccessible::deleteAccessibleInterface(nId);
// let QtAccessibleWidget::customFactory set the custom accessible
rObject.setProperty(PROPERTY_ACCESSIBLE,
QVariant::fromValue(new QtAccessibleWidget(rxAccessible, rObject)));
QAccessible::queryAccessibleInterface(&rObject);
}
int count = xKeyBinding->getAccessibleKeyBindingCount(); for (int i = 0; i < count; i++)
{
Sequence<awt::KeyStroke> keyStroke = xKeyBinding->getAccessibleKeyBinding(i);
keyBindings.append(toQString(comphelper::GetkeyBindingStrByXkeyBinding(keyStroke)));
} return keyBindings;
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
// QAccessibleAttributesInterface helpers namespace
{ void lcl_insertAttribute(QHash<QAccessible::Attribute, QVariant>& rQtAttrs, const OUString& rName, const OUString& rValue)
{ if (rName == u"level"_ustr)
{
rQtAttrs.insert(QAccessible::Attribute::Level,
QVariant::fromValue(static_cast<int>(rValue.toInt32())));
} else
{ // for now, leave not explicitly handled attributes as they are and report // via QAccessible::Attribute::Custom, but should consider suggesting to // add more specific attributes on Qt side and use those instead const QVariant aVariant = rQtAttrs.value(QAccessible::Attribute::Custom,
QVariant::fromValue(QHash<QString, QString>()));
assert((aVariant.canConvert<QHash<QString, QString>>()));
QHash<QString, QString> aAttrs = aVariant.value<QHash<QString, QString>>();
aAttrs.insert(toQString(rName), toQString(rValue));
rQtAttrs.insert(QAccessible::Attribute::Custom, QVariant::fromValue(aAttrs));
}
}
}
// Text attributes are returned in format specified in IAccessible2 spec, since that // is what Qt handles: // https://wiki.linuxfoundation.org/accessibility/iaccessible2/textattributes
QString QtAccessibleWidget::attributes(int offset, int* startOffset, int* endOffset) const
{ if (startOffset == nullptr || endOffset == nullptr) return QString();
*startOffset = -1;
*endOffset = -1;
Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY); if (!xText.is()) return QString();
// handle special values for offset the same way base class's QAccessibleTextWidget::attributes does // (as defined in IAccessible 2: -1 -> length, -2 -> cursor position) if (offset == -2)
offset = cursorPosition();
if (offset < 0 || offset > nTextLength)
{
SAL_WARN("vcl.qt", "QtAccessibleWidget::attributes called with invalid offset: " << offset); return QString();
}
// Qt doesn't have the strict separation into text and object attributes, but also // supports text-specific attributes that are object attributes according to the // IAccessible2 spec.
sal_Int32 nStart = 0;
sal_Int32 nEnd = 0; const OUString aRet = AccessibleTextAttributeHelper::GetIAccessible2TextAttributes(
xText, IA2AttributeType::TextAttributes | IA2AttributeType::ObjectAttributes, static_cast<sal_Int32>(offset), nStart, nEnd);
*startOffset = static_cast<int>(nStart);
*endOffset = static_cast<int>(nEnd); return toQString(aRet);
}
int QtAccessibleWidget::characterCount() const
{
Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY); if (xText.is()) return xText->getCharacterCount(); return 0;
}
Reference<XAccessibleText> xText; if (selectionIndex == 0)
xText = Reference<XAccessibleText>(getAccessibleContextImpl(), UNO_QUERY);
if (startOffset)
*startOffset = xText.is() ? xText->getSelectionStart() : 0; if (endOffset)
*endOffset = xText.is() ? xText->getSelectionEnd() : 0;
}
int QtAccessibleWidget::selectionCount() const
{
Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY); if (xText.is() && !xText->getSelectedText().isEmpty()) return 1; // Only 1 selection supported atm return 0;
}
void QtAccessibleWidget::setCursorPosition(int position)
{
Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY); if (!xText.is()) return;
if (position < 0 || position > xText->getCharacterCount())
{
SAL_WARN("vcl.qt", "QtAccessibleWidget::setCursorPosition called with invalid offset: " << position); return;
}
xText->setCaretPosition(position);
}
void QtAccessibleWidget::setSelection(int/* selectionIndex */, int startOffset, int endOffset)
{
Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY); if (!xText.is()) return;
sal_Int32 nTextLength = xText->getCharacterCount(); if (startOffset < 0 || startOffset > nTextLength || endOffset < 0 || endOffset > nTextLength)
{
SAL_WARN("vcl.qt", "QtAccessibleWidget::setSelection called with invalid offset."); return;
}
xText->setSelection(startOffset, endOffset);
}
QString QtAccessibleWidget::text(int startOffset, int endOffset) const
{
Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY); if (!xText.is()) return QString();
sal_Int32 nTextLength = xText->getCharacterCount(); if (startOffset < 0 || startOffset > nTextLength || endOffset < 0 || endOffset > nTextLength)
{
SAL_WARN("vcl.qt", "QtAccessibleWidget::text called with invalid offset."); return QString();
}
Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY); if (!xText.is()) return QString();
constint nCharCount = characterCount(); // -1 is special value for text length if (nOffset == -1)
nOffset = nCharCount; elseif (nOffset < -1 || nOffset > nCharCount)
{
SAL_WARN("vcl.qt", "QtAccessibleWidget::textAfterOffset called with invalid offset: " << nOffset); return QString();
}
Reference<XAccessibleText> xText(getAccessibleContextImpl(), UNO_QUERY); if (!xText.is()) return QString();
constint nCharCount = characterCount(); // -1 is special value for text length if (nOffset == -1)
nOffset = nCharCount; elseif (nOffset < -1 || nOffset > nCharCount)
{
SAL_WARN("vcl.qt", "QtAccessibleWidget::textBeforeOffset called with invalid offset: " << nOffset); return QString();
}
Reference<XAccessibleValue> xValue(xAc, UNO_QUERY); if (!xValue.is()) return;
// Different types of numerical values for XAccessibleValue are possible. // If current value has an integer type, also use that for the new value, to make // sure underlying implementations expecting that can handle the value properly. const Any aCurrentValue = xValue->getCurrentValue(); if (aCurrentValue.getValueTypeClass() == css::uno::TypeClass::TypeClass_LONG)
xValue->setCurrentValue(Any(static_cast<sal_Int32>(value.toInt()))); elseif (aCurrentValue.getValueTypeClass() == css::uno::TypeClass::TypeClass_HYPER)
xValue->setCurrentValue(Any(static_cast<sal_Int64>(value.toLongLong()))); else
xValue->setCurrentValue(Any(value.toDouble()));
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.