1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-08-04 06:45:45 +02:00

calloverlay: implement new action button design

- minor cosmetic changes to the call view overlay
- add the ability to change audio/video input during a call
  from the call screen overlay

Gitlab: #411
Change-Id: Id6bbb2278d807f3bd7ad1478db405669088584ce
This commit is contained in:
Andreas Traczyk 2021-05-20 13:33:17 -04:00
parent 3cca2e7078
commit abde3cbac0
32 changed files with 1252 additions and 486 deletions

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
<path d="M16.1,11.3c1.1-0.7,1.8-2,1.8-3.3c0-2.2-1.8-4-4-4c-2.2,0-4,1.8-4,4c0,1.3,0.7,2.6,1.8,3.3c-0.6,0.3-1.2,0.6-1.8,1.1
c-0.3-0.3-0.6-0.5-1-0.7c0.6-0.6,1-1.4,1-2.3c0-1.8-1.4-3.2-3.2-3.2c-1.8,0-3.2,1.4-3.2,3.2c0,0.9,0.4,1.7,1,2.3
c-1.5,0.8-2.5,2.4-2.5,4.2c0,0.6,0.5,1.1,1.1,1.1h4.7c0,0.6,0.5,1.1,1.1,1.1h6.5l-0.3-0.2c-0.3-0.2-0.5-0.6-0.7-1l0-0.1H9.1
c0-0.3,0.1-0.7,0.1-1c0.1-0.6,0.4-1.1,0.7-1.6c0.2-0.3,0.4-0.6,0.7-0.8c0.9-0.8,2.1-1.3,3.3-1.3c1.1,0,2.1,0.4,3,1l0.1,0.1l0.1-0.1
c0.1-0.2,0.2-0.4,0.4-0.6c0.1-0.1,0.1-0.1,0.2-0.2l0.1-0.1l-0.1-0.1C17.2,11.8,16.6,11.5,16.1,11.3z M16.6,7.9
c0,1.5-1.2,2.7-2.7,2.7c-1.5,0-2.7-1.2-2.7-2.7c0-1.5,1.2-2.7,2.7-2.7C15.4,5.2,16.6,6.4,16.6,7.9z M6.6,11.3c-1.1,0-2-0.9-2-2
c0-1.1,0.9-2,2-2c1.1,0,2,0.9,2,2C8.6,10.4,7.7,11.3,6.6,11.3z M9,13.3c-0.5,0.7-0.9,1.5-1.1,2.4H3.2c0.1-1.8,1.6-3.3,3.4-3.3
C7.5,12.4,8.4,12.7,9,13.3z"/>
<path d="M21.2,15.6l-1.7,0l0-1.7c0-0.4-0.3-0.7-0.7-0.7c-0.2,0-0.4,0.1-0.5,0.2c-0.1,0.1-0.2,0.3-0.2,0.5l0,1.7l-1.7,0
c-0.2,0-0.4,0.1-0.5,0.2c-0.1,0.1-0.2,0.3-0.2,0.5c0,0.4,0.3,0.7,0.7,0.7l1.7,0l0,1.7c0,0.4,0.3,0.7,0.7,0.7h0
c0.2,0,0.4-0.1,0.5-0.2c0.1-0.1,0.2-0.3,0.2-0.5l0-1.7l1.7,0c0.2,0,0.4-0.1,0.5-0.2c0.1-0.1,0.2-0.3,0.2-0.5
C21.9,16,21.6,15.7,21.2,15.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
<g id="Icones_Outline">
<g id="Chat_Black_24dp">
<g id="Shape" transform="translate(3.000000, 4.000000)">
<g>
<path d="M4.6,17c-0.2,0-0.4,0-0.6-0.1c-0.6-0.3-0.9-0.8-0.9-1.5V13H2.3C0.5,13-1,11.5-1,9.6V2.3C-1,0.5,0.5-1,2.3-1h13.3
C17.5-1,19,0.5,19,2.3v7.3c0,1.8-1.5,3.3-3.3,3.3H9.5l-3.8,3.6C5.4,16.8,5,17,4.6,17z M2.3,0.4c-1.1,0-1.9,0.9-1.9,2v7.3
c0,1,0.8,1.9,1.9,1.9h2.2v3.9c0,0,0,0.1,0.1,0.1s0.1,0,0.2,0l4.2-4h6.7c1,0,1.9-0.8,1.9-1.9V2.3c0-1-0.8-1.9-1.9-1.9H2.3
L2.3,0.4z"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 878 B

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
<path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z M12,20.3c-4.6,0-8.3-3.7-8.3-8.3S7.4,3.7,12,3.7
s8.3,3.7,8.3,8.3S16.6,20.3,12,20.3z"/>
</svg>

After

Width:  |  Height:  |  Size: 510 B

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
<path id="Path" d="M12.6,8.9V7.8l2.6,2.2l-2.6,2.2V11c-3.4,0.1-4.2,2.6-4.2,2.6C8.5,9.6,11.7,9,12.6,8.9z"/>
<g id="Icones_Outline">
<g id="Laptop_Black_24dp">
<g transform="translate(2.000000, 5.000000)">
<g id="Shape">
<path d="M17,12H2.8c-0.5,0-0.9-0.2-1.2-0.5C1.2,11.1,1,10.7,1,10.2V1.3c0-0.5,0.2-0.9,0.5-1.2s0.8-0.5,1.2-0.5H17
c0.5,0,0.9,0.2,1.2,0.5c0.3,0.3,0.5,0.8,0.5,1.2v8.9c0,0.5-0.2,0.9-0.5,1.2S17.5,12,17,12z M2.8,0.9C2.7,0.9,2.6,1,2.5,1
c0,0.1-0.1,0.2-0.1,0.3v8.9c0,0.1,0,0.2,0.1,0.3c0,0,0.1,0.1,0.3,0.1H17c0.1,0,0.2-0.1,0.3-0.1c0.1-0.1,0.1-0.2,0.1-0.3V1.3
c0-0.1,0-0.2-0.1-0.3c0,0-0.1-0.1-0.3-0.1C17,0.9,2.8,0.9,2.8,0.9z"/>
</g>
<g id="Line-2">
<path d="M19.5,14.4h-19c-0.4,0-0.7-0.3-0.7-0.7S0.1,13,0.5,13h19c0.4,0,0.7,0.3,0.7,0.7S19.9,14.4,19.5,14.4z"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
<g>
<path d="M14.6,4c-0.4-0.7-1.3-0.8-2-0.4L6.8,7.7H4.1C3,7.7,2,8.7,2,9.9v4.3c0,1.2,1,2.1,2.1,2.1h2.6l5.8,4.1
c0.2,0.2,0.5,0.2,0.8,0.2c0.8,0,1.4-0.6,1.5-1.4V4.8C14.9,4.5,14.8,4.2,14.6,4z M13.4,19.2L13.4,19.2l-5.7-4l-0.4-0.3h-1H6.1h-2
c-0.4,0-0.7-0.3-0.7-0.7V9.9c0-0.4,0.3-0.7,0.7-0.7H6h0.2h1l0.4-0.3l5.7-4L13.4,19.2L13.4,19.2z"/>
<path d="M19.1,12c0,1.5-0.6,2.9-1.6,4c-0.3,0.3-0.7,0.3-1,0c-0.3-0.3-0.3-0.7,0-1c1.6-1.7,1.6-4.3,0-6c-0.3-0.3-0.3-0.7,0-1
c0.3-0.3,0.7-0.3,1,0C18.6,9.1,19.1,10.5,19.1,12z"/>
<path d="M18.9,5.1c-0.3-0.3-0.7-0.2-1,0c-0.3,0.3-0.2,0.7,0,1c3.3,2.9,3.5,8,0.6,11.2c-0.2,0.2-0.4,0.4-0.6,0.6
c-0.3,0.3-0.3,0.7,0,1c0.3,0.3,0.7,0.3,1,0c3.8-3.5,4.1-9.4,0.7-13.2C19.4,5.5,19.1,5.3,18.9,5.1L18.9,5.1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
<g>
<path d="M14.6,4c-0.4-0.7-1.3-0.8-2-0.4L6.8,7.7H4.1C3,7.7,2,8.7,2,9.9v4.3c0,1.2,1,2.1,2.1,2.1h2.6l5.8,4.1
c0.2,0.2,0.5,0.2,0.8,0.2c0.8,0,1.4-0.6,1.5-1.4V4.8C14.9,4.5,14.8,4.2,14.6,4z M13.4,19.2L13.4,19.2l-5.7-4l-0.4-0.3h-1H6.1h-2
c-0.4,0-0.7-0.3-0.7-0.7V9.9c0-0.4,0.3-0.7,0.7-0.7H6h0.2h1l0.4-0.3l5.7-4L13.4,19.2L13.4,19.2z"/>
<path d="M19.1,12c0,1.5-0.6,2.9-1.6,4c-0.3,0.3-0.7,0.3-1,0c-0.3-0.3-0.3-0.7,0-1c1.6-1.7,1.6-4.3,0-6c-0.3-0.3-0.3-0.7,0-1
c0.3-0.3,0.7-0.3,1,0C18.6,9.1,19.1,10.5,19.1,12z"/>
<path d="M18.9,5.1c-0.3-0.3-0.7-0.2-1,0c-0.3,0.3-0.2,0.7,0,1c3.3,2.9,3.5,8,0.6,11.2c-0.2,0.2-0.4,0.4-0.6,0.6
c-0.3,0.3-0.3,0.7,0,1c0.3,0.3,0.7,0.3,1,0c3.8-3.5,4.1-9.4,0.7-13.2C19.4,5.5,19.1,5.3,18.9,5.1L18.9,5.1z"/>
</g>
<g>
<path d="M3.3,20.7c-0.2,0-0.3-0.1-0.5-0.2c-0.3-0.3-0.3-0.7-0.1-1L16.3,3.7c0.3-0.3,0.7-0.3,1-0.1c0.3,0.3,0.3,0.7,0.1,1L3.8,20.5
C3.7,20.6,3.5,20.7,3.3,20.7z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -106,7 +106,6 @@
<file>src/mainview/components/CallStackView.qml</file> <file>src/mainview/components/CallStackView.qml</file>
<file>src/mainview/components/InitialCallPage.qml</file> <file>src/mainview/components/InitialCallPage.qml</file>
<file>src/mainview/components/CallOverlay.qml</file> <file>src/mainview/components/CallOverlay.qml</file>
<file>src/mainview/components/CallOverlayButtonGroup.qml</file>
<file>src/mainview/components/ContactSearchBar.qml</file> <file>src/mainview/components/ContactSearchBar.qml</file>
<file>src/mainview/components/OngoingCallPage.qml</file> <file>src/mainview/components/OngoingCallPage.qml</file>
<file>src/mainview/components/ParticipantOverlay.qml</file> <file>src/mainview/components/ParticipantOverlay.qml</file>
@ -137,5 +136,10 @@
<file>src/mainview/components/SmartListItemDelegate.qml</file> <file>src/mainview/components/SmartListItemDelegate.qml</file>
<file>src/mainview/components/BadgeNotifier.qml</file> <file>src/mainview/components/BadgeNotifier.qml</file>
<file>src/mainview/components/ParticipantsLayer.qml</file> <file>src/mainview/components/ParticipantsLayer.qml</file>
<file>src/mainview/components/MainOverlay.qml</file>
<file>src/mainview/components/CallButtonDelegate.qml</file>
<file>src/mainview/components/CallActionBar.qml</file>
<file>src/commoncomponents/HalfPill.qml</file>
<file>src/commoncomponents/MaterialToolTip.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View file

@ -134,5 +134,11 @@
<file>images/icons/settings-24px.svg</file> <file>images/icons/settings-24px.svg</file>
<file>images/icons/quote.svg</file> <file>images/icons/quote.svg</file>
<file>images/icons/plugins-24px.svg</file> <file>images/icons/plugins-24px.svg</file>
<file>images/icons/record_black_24dp.svg</file>
<file>images/icons/share_screen_black_24dp.svg</file>
<file>images/icons/chat_black_24dp.svg</file>
<file>images/icons/add_people_black_24dp.svg</file>
<file>images/icons/spk_black_24dp.svg</file>
<file>images/icons/spk_none_black_24dp.svg</file>
</qresource> </qresource>
</RCC> </RCC>

View file

@ -62,6 +62,8 @@ AudioDeviceModel::data(const QModelIndex& index, int role) const
} }
case Role::RawDeviceName: case Role::RawDeviceName:
return QVariant(devices_.at(index.row())); return QVariant(devices_.at(index.row()));
case Role::isCurrent:
return QVariant(index.row() == getCurrentIndex());
default: default:
break; break;
} }
@ -115,7 +117,7 @@ AudioDeviceModel::reset()
} }
int int
AudioDeviceModel::getCurrentIndex() AudioDeviceModel::getCurrentIndex() const
{ {
QString currentId = lrcInstance_->avModel().getInputDevice(); QString currentId = lrcInstance_->avModel().getInputDevice();
auto resultList = match(index(0, 0), Qt::DisplayRole, QVariant(currentId)); auto resultList = match(index(0, 0), Qt::DisplayRole, QVariant(currentId));

View file

@ -28,7 +28,7 @@ public:
Q_ENUM(Type) Q_ENUM(Type)
Q_PROPERTY(Type type MEMBER type_ NOTIFY typeChanged) Q_PROPERTY(Type type MEMBER type_ NOTIFY typeChanged)
enum Role { DeviceName = Qt::UserRole + 1, RawDeviceName }; enum Role { DeviceName = Qt::UserRole + 1, RawDeviceName, isCurrent };
Q_ENUM(Role) Q_ENUM(Role)
Q_SIGNALS: Q_SIGNALS:
@ -56,10 +56,10 @@ public:
Qt::ItemFlags flags(const QModelIndex& index) const override; Qt::ItemFlags flags(const QModelIndex& index) const override;
Q_INVOKABLE void reset(); Q_INVOKABLE void reset();
Q_INVOKABLE int getCurrentIndex(); Q_INVOKABLE int getCurrentIndex() const;
private: private:
QVector<QString> devices_; QVector<QString> devices_;
Type type_ {Type::Invalid}; Type type_ {Type::Invalid};
}; };

View file

@ -69,13 +69,19 @@ AvAdapter::populateVideoDeviceContextMenuItem()
} }
void void
AvAdapter::onVideoContextMenuDeviceItemClicked(const QString& deviceName) AvAdapter::selectVideoInputDeviceByName(const QString& deviceName)
{ {
auto deviceId = lrcInstance_->avModel().getDeviceIdFromName(deviceName); auto deviceId = lrcInstance_->avModel().getDeviceIdFromName(deviceName);
if (deviceId.isEmpty()) { if (deviceId.isEmpty()) {
qWarning() << "Couldn't find device: " << deviceName; qWarning() << "Couldn't find device: " << deviceName;
return; return;
} }
selectVideoInputDeviceById(deviceId);
}
void
AvAdapter::selectVideoInputDeviceById(const QString& deviceId)
{
lrcInstance_->avModel().setCurrentVideoCaptureDevice(deviceId); lrcInstance_->avModel().setCurrentVideoCaptureDevice(deviceId);
lrcInstance_->avModel().switchInputTo(deviceId, getCurrentCallId()); lrcInstance_->avModel().switchInputTo(deviceId, getCurrentCallId());
} }

View file

@ -45,8 +45,11 @@ protected:
// Return needed info for populating video device context menu item. // Return needed info for populating video device context menu item.
Q_INVOKABLE QVariantMap populateVideoDeviceContextMenuItem(); Q_INVOKABLE QVariantMap populateVideoDeviceContextMenuItem();
// Preview video input switching. // switch preview video input by device name
Q_INVOKABLE void onVideoContextMenuDeviceItemClicked(const QString& deviceName); Q_INVOKABLE void selectVideoInputDeviceByName(const QString& deviceName);
// switch preview video input by device id
Q_INVOKABLE void selectVideoInputDeviceById(const QString& deviceId);
// Share the screen specificed by screen number. // Share the screen specificed by screen number.
Q_INVOKABLE void shareEntireScreen(int screenNumber); Q_INVOKABLE void shareEntireScreen(int screenNumber);

View file

@ -639,7 +639,6 @@ CallAdapter::updateCallOverlay(const lrc::api::conversation::Info& convInfo)
isVideoMuted, isVideoMuted,
isRecording, isRecording,
accInfo.profileInfo.type == lrc::api::profile::Type::SIP, accInfo.profileInfo.type == lrc::api::profile::Type::SIP,
!convInfo.confId.isEmpty(),
bestName); bestName);
} }

View file

@ -95,7 +95,6 @@ Q_SIGNALS:
bool isVideoMuted, bool isVideoMuted,
bool isRecording, bool isRecording,
bool isSIP, bool isSIP,
bool isConferenceCall,
const QString& bestName); const QString& bestName);
void remoteRecordingChanged(const QStringList& peers, bool state); void remoteRecordingChanged(const QStringList& peers, bool state);
void eraseRemoteRecording(); void eraseRemoteRecording();

View file

@ -44,15 +44,10 @@ CallControlListModel::data(const QModelIndex& index, int role) const
auto item = data_.at(index.row()); auto item = data_.at(index.row());
switch (role) { switch (role) {
case Role::DummyRole: case Role::ItemAction:
break; return QVariant::fromValue(item.itemAction);
#define X(t, role) \ case Role::BadgeCount:
case Role::role: \ return QVariant::fromValue(item.badgeCount);
return QVariant::fromValue(item.role);
CC_ROLES
#undef X
default:
break;
} }
return QVariant(); return QVariant();
} }
@ -62,9 +57,8 @@ CallControlListModel::roleNames() const
{ {
using namespace CallControl; using namespace CallControl;
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
#define X(t, role) roles[role] = #role; roles[ItemAction] = "ItemAction";
CC_ROLES roles[BadgeCount] = "BadgeCount";
#undef X
return roles; return roles;
} }
@ -73,7 +67,7 @@ CallControlListModel::setBadgeCount(int row, int count)
{ {
if (row >= rowCount()) if (row >= rowCount())
return; return;
data_[row].BadgeCount = count; data_[row].badgeCount = count;
auto idx = index(row, 0); auto idx = index(row, 0);
Q_EMIT dataChanged(idx, idx); Q_EMIT dataChanged(idx, idx);
} }
@ -86,6 +80,12 @@ CallControlListModel::addItem(const CallControl::Item& item)
endResetModel(); endResetModel();
} }
void
CallControlListModel::clearData()
{
data_.clear();
}
IndexRangeFilterProxyModel::IndexRangeFilterProxyModel(QAbstractListModel* parent) IndexRangeFilterProxyModel::IndexRangeFilterProxyModel(QAbstractListModel* parent)
: QSortFilterProxyModel(parent) : QSortFilterProxyModel(parent)
{ {
@ -129,25 +129,15 @@ CallOverlayModel::CallOverlayModel(LRCInstance* instance, QObject* parent)
} }
void void
CallOverlayModel::addPrimaryControl(const QVariantMap& props) CallOverlayModel::addPrimaryControl(const QVariant& action)
{ {
CallControl::Item item { primaryModel_->addItem(CallControl::Item {action.value<QObject*>()});
#define X(t, role) props[#role].value<t>(),
CC_ROLES
#undef X
};
primaryModel_->addItem(item);
} }
void void
CallOverlayModel::addSecondaryControl(const QVariantMap& props) CallOverlayModel::addSecondaryControl(const QVariant& action)
{ {
CallControl::Item item { secondaryModel_->addItem(CallControl::Item {action.value<QObject*>()});
#define X(t, role) props[#role].value<t>(),
CC_ROLES
#undef X
};
secondaryModel_->addItem(item);
setControlRanges(); setControlRanges();
} }
@ -187,6 +177,13 @@ CallOverlayModel::overflowHiddenModel()
return QVariant::fromValue(overflowHiddenModel_); return QVariant::fromValue(overflowHiddenModel_);
} }
void
CallOverlayModel::clearControls()
{
primaryModel_->clearData();
secondaryModel_->clearData();
}
void void
CallOverlayModel::registerFilter(QQuickWindow* object, QQuickItem* item) CallOverlayModel::registerFilter(QQuickWindow* object, QQuickItem* item)
{ {

View file

@ -27,28 +27,15 @@
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <QQuickItem> #include <QQuickItem>
#define CC_ROLES \
X(QObject*, ItemAction) \
X(int, BadgeCount) \
X(bool, HasBackground) \
X(QObject*, MenuAction) \
X(QString, Name)
namespace CallControl { namespace CallControl {
Q_NAMESPACE Q_NAMESPACE
enum Role { enum Role { ItemAction = Qt::UserRole + 1, BadgeCount };
DummyRole = Qt::UserRole + 1,
#define X(t, role) role,
CC_ROLES
#undef X
};
Q_ENUM_NS(Role) Q_ENUM_NS(Role)
struct Item struct Item
{ {
#define X(t, role) t role; QObject* itemAction;
CC_ROLES int badgeCount {0};
#undef X
}; };
} // namespace CallControl } // namespace CallControl
@ -64,6 +51,7 @@ public:
void setBadgeCount(int row, int count); void setBadgeCount(int row, int count);
void addItem(const CallControl::Item& item); void addItem(const CallControl::Item& item);
void clearData();
private: private:
QList<CallControl::Item> data_; QList<CallControl::Item> data_;
@ -93,9 +81,10 @@ class CallOverlayModel : public QObject
public: public:
CallOverlayModel(LRCInstance* instance, QObject* parent = nullptr); CallOverlayModel(LRCInstance* instance, QObject* parent = nullptr);
Q_INVOKABLE void addPrimaryControl(const QVariantMap& props); Q_INVOKABLE void addPrimaryControl(const QVariant& action);
Q_INVOKABLE void addSecondaryControl(const QVariantMap& props); Q_INVOKABLE void addSecondaryControl(const QVariant& action);
Q_INVOKABLE void setBadgeCount(int row, int count); Q_INVOKABLE void setBadgeCount(int row, int count);
Q_INVOKABLE void clearControls();
Q_INVOKABLE QVariant primaryModel(); Q_INVOKABLE QVariant primaryModel();
Q_INVOKABLE QVariant secondaryModel(); Q_INVOKABLE QVariant secondaryModel();

View file

@ -0,0 +1,55 @@
/*
* Copyright (C) 2021 by Savoir-faire Linux
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.14
Item {
id: root
enum Type {
None,
Top,
Left,
Bottom,
Right
}
property int type: HalfPill.None
property int radius: 0
property alias color: rect.color
clip: true
Rectangle {
id: rect
property bool horizontal: type === HalfPill.Left ||
type == HalfPill.Right
property bool direction: type === HalfPill.Right ||
type == HalfPill.Bottom
radius: root.radius * (type !== HalfPill.None)
width: root.size + radius
height: root.size + radius
anchors.fill: root
anchors.leftMargin: horizontal * direction * -radius
anchors.rightMargin: horizontal * !direction * -radius
anchors.topMargin: !horizontal * direction * -radius
anchors.bottomMargin: !horizontal * !direction * -radius
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (C) 2021 by Savoir-faire Linux
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.0
import QtQuick.Controls 2.14
import net.jami.Constants 1.0
ToolTip {
id: root
onVisibleChanged: {
if (visible)
animation.start()
}
contentItem: Text {
id: label
text: root.text
font: root.font
color: "white"
}
background: Rectangle {
color: "#c4272727"
radius: 5
}
ParallelAnimation {
id: animation
NumberAnimation {
target: root; properties: "opacity"
from: 0; to: 1.0
duration: JamiTheme.shortFadeDuration
}
NumberAnimation {
target: root; properties: "scale"
from: 0.5; to: 1.0
duration: JamiTheme.shortFadeDuration * 0.5
}
}
}

View file

@ -25,8 +25,8 @@ import net.jami.Models 1.0
Image { Image {
id: root id: root
property real containerWidth property real containerWidth: 30
property real containerHeight property real containerHeight: 30
property int padding: 0 property int padding: 0
property point offset: Qt.point(0, 0) property point offset: Qt.point(0, 0)

View file

@ -186,7 +186,7 @@ Item {
property string peerStoppedRecording: qsTr("Peer stopped recording") property string peerStoppedRecording: qsTr("Peer stopped recording")
property string isCallingYou: qsTr("is calling you") property string isCallingYou: qsTr("is calling you")
// CallOverlayButtonGroup // CallOverlay
property string mute: qsTr("Mute") property string mute: qsTr("Mute")
property string unmute: qsTr("Unmute") property string unmute: qsTr("Unmute")
property string hangup: qsTr("End call") property string hangup: qsTr("End call")

View file

@ -164,8 +164,9 @@ Item {
property color bgDarkMode_: rgba256(32, 32, 32, 100) property color bgDarkMode_: rgba256(32, 32, 32, 100)
property int shortFadeDuration: 150 property int shortFadeDuration: 150
property int overlayFadeDelay: 2000 property int recordBlinkDuration: 500
property int overlayFadeDuration: 500 property int overlayFadeDelay: 4000
property int overlayFadeDuration: 250
property int smartListTransitionDuration: 120 property int smartListTransitionDuration: 120
// Sizes // Sizes

View file

@ -0,0 +1,382 @@
/*
* Copyright (C) 2021 by Savoir-faire Linux
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import net.jami.Models 1.0
import net.jami.Adapters 1.0
import net.jami.Constants 1.0
import "../../commoncomponents"
Control {
id: root
property alias overflowOpen: overflowButton.popup.visible
property bool subMenuOpen: false
property real itemSpacing: 2
property bool localIsRecording: false
signal chatClicked
signal addToConferenceClicked
signal transferClicked // TODO: bind this
signal shareScreenClicked
signal shareScreenAreaClicked // TODO: bind this
signal pluginsClicked
Component {
id: buttonDelegate
CallButtonDelegate {
width: root.height
height: width
onSubMenuVisibleChanged: subMenuOpen = subMenuVisible
}
}
Connections {
target: AvAdapter
// TODO: audio device list updates
function onVideoDeviceListChanged(listIsEmpty) {
videoInputDeviceListModel.reset();
}
}
property list<Action> menuActions: [
Action {
id: audioInputMenuAction
text: JamiStrings.selectAudioInputDevice
property var listModel: AudioDeviceModel {
id: audioInputDeviceListModel
lrcInstance: LRCInstance
type: AudioDeviceModel.Type.Record
}
function accept(index) {
AvAdapter.stopAudioMeter(false)
AVModel.setInputDevice(listModel.data(
listModel.index(index, 0),
AudioDeviceModel.RawDeviceName))
AvAdapter.startAudioMeter(false)
}
},
Action {
id: audioOutputMenuAction
text: JamiStrings.selectAudioOutputDevice
property var listModel: AudioDeviceModel {
id: audioOutputDeviceListModel
lrcInstance: LRCInstance
type: AudioDeviceModel.Type.Playback
}
function accept(index) {
AvAdapter.stopAudioMeter(false)
AVModel.setOutputDevice(listModel.data(
listModel.index(index, 0),
AudioDeviceModel.RawDeviceName))
AvAdapter.startAudioMeter(false)
}
},
Action {
id: videoInputMenuAction
text: JamiStrings.selectVideoDevice
property var listModel: VideoInputDeviceModel {
id: videoInputDeviceListModel
lrcInstance: LRCInstance
}
function accept(index) {
if(listModel.deviceCount() < 1)
return
try {
var deviceId = listModel.data(
listModel.index(index, 0),
VideoInputDeviceModel.DeviceId)
var deviceName = listModel.data(
listModel.index(index, 0),
VideoInputDeviceModel.DeviceName)
if(deviceId.length === 0) {
console.warn("Couldn't find device: " + deviceName)
return
}
if (AVModel.getCurrentVideoCaptureDevice() !== deviceId) {
AVModel.setCurrentVideoCaptureDevice(deviceId)
AVModel.setDefaultDevice(deviceId)
}
AvAdapter.selectVideoInputDeviceById(deviceId)
} catch(err){ console.warn(err.message) }
}
}
]
property list<Action> primaryActions: [
Action {
id: muteAudioAction
onTriggered: CallAdapter.muteThisCallToggle()
checkable: true
icon.source: checked ?
"qrc:/images/icons/mic_off-24px.svg" :
"qrc:/images/icons/mic-24px.svg"
icon.color: checked ? "red" : "white"
text: !checked ? JamiStrings.mute : JamiStrings.unmute
property var menuAction: audioInputMenuAction
},
Action {
id: hangupAction
onTriggered: CallAdapter.hangUpThisCall()
icon.source: "qrc:/images/icons/ic_call_end_white_24px.svg"
icon.color: "white"
text: JamiStrings.hangup
property bool hasBg: true
},
Action {
id: muteVideoAction
onTriggered: CallAdapter.videoPauseThisCallToggle()
checkable: true
icon.source: checked ?
"qrc:/images/icons/videocam_off-24px.svg" :
"qrc:/images/icons/videocam-24px.svg"
icon.color: checked ? "red" : "white"
text: !checked ? JamiStrings.pauseVideo : JamiStrings.resumeVideo
property var menuAction: videoInputMenuAction
}
]
property list<Action> secondaryActions: [
Action {
id: audioOutputAction
// temp hack for missing back-end, just open device selection
property bool bypassMuteAction: true
checkable: !bypassMuteAction
icon.source: "qrc:/images/icons/spk_black_24dp.svg"
icon.color: "white"
text: JamiStrings.selectAudioOutputDevice
property var menuAction: audioOutputMenuAction
},
Action {
id: addPersonAction
onTriggered: root.addToConferenceClicked()
icon.source: "qrc:/images/icons/add_people_black_24dp.svg"
icon.color: "white"
text: JamiStrings.addParticipants
},
Action {
id: chatAction
onTriggered: root.chatClicked()
icon.source: "qrc:/images/icons/chat_black_24dp.svg"
icon.color: "white"
text: JamiStrings.chat
},
Action {
id: shareAction
onTriggered: root.shareScreenClicked()
icon.source: "qrc:/images/icons/share_screen_black_24dp.svg"
icon.color: "white"
text: JamiStrings.shareScreen
property real size: 34
},
Action {
id: recordAction
onTriggered: CallAdapter.recordThisCallToggle()
checkable: true
icon.source: "qrc:/images/icons/record_black_24dp.svg"
icon.color: checked ? "red" : "white"
text: !checked ? JamiStrings.startRec : JamiStrings.stopRec
property bool blinksWhenChecked: true
property real size: 28
},
Action {
id: pluginsAction
onTriggered: root.pluginsClicked()
icon.source: "qrc:/images/icons/plugins-24px.svg"
icon.color: "white"
text: JamiStrings.viewPlugin
enabled: UtilsAdapter.checkShowPluginsButton(true)
}
]
property var overflowItemCount
Connections {
target: callOverlay
function onIsAudioOnlyChanged() { reset() }
function onIsSIPChanged() { reset() }
function onIsModeratorChanged() { reset() }
}
function reset() {
CallOverlayModel.clearControls();
// centered controls
CallOverlayModel.addPrimaryControl(muteAudioAction)
CallOverlayModel.addPrimaryControl(hangupAction)
if (!isAudioOnly)
CallOverlayModel.addPrimaryControl(muteVideoAction)
// overflow controls
CallOverlayModel.addSecondaryControl(audioOutputAction)
if (isModerator && !isSIP)
CallOverlayModel.addSecondaryControl(addPersonAction)
CallOverlayModel.addSecondaryControl(chatAction)
if (!isAudioOnly)
CallOverlayModel.addSecondaryControl(shareAction)
CallOverlayModel.addSecondaryControl(recordAction)
if (UtilsAdapter.checkShowPluginsButton(true))
CallOverlayModel.addSecondaryControl(pluginsAction)
overflowItemCount = CallOverlayModel.secondaryModel().rowCount()
muteAudioAction.checked = isAudioMuted
muteVideoAction.checked = isVideoMuted
}
Item {
id: centralControls
anchors.centerIn: parent
width: childrenRect.width
height: root.height
RowLayout {
spacing: 0
ListView {
property bool centeredGroup: true
orientation: ListView.Horizontal
implicitWidth: contentWidth
implicitHeight: contentHeight
model: CallOverlayModel.primaryModel()
delegate: buttonDelegate
}
}
}
Item {
id: overflowRect
property real remainingSpace: (root.width - centralControls.width) / 2
anchors.right: parent.right
width: childrenRect.width
height: root.height
RowLayout {
spacing: itemSpacing
ListView {
id: overflowItemListView
orientation: ListView.Horizontal
implicitWidth: contentWidth
implicitHeight: overflowRect.height
spacing: itemSpacing
property int overflowIndex: {
var maxItems = Math.floor((overflowRect.remainingSpace - 24) / root.height) - 1
return Math.min(overflowItemCount, maxItems)
}
property int nOverflowItems: overflowItemCount - overflowIndex
onNOverflowItemsChanged: {
var diff = overflowItemListView.count - nOverflowItems
var effectiveOverflowIndex = overflowIndex
if (effectiveOverflowIndex === overflowItemCount - 1)
effectiveOverflowIndex += diff
CallOverlayModel.overflowIndex = effectiveOverflowIndex
}
model: CallOverlayModel.overflowModel()
delegate: buttonDelegate
}
ComboBox {
id: overflowButton
visible: CallOverlayModel.overflowIndex < overflowItemCount
width: root.height
height: width
model: CallOverlayModel.overflowHiddenModel()
delegate: buttonDelegate
indicator: null
contentItem: Text {
text: "⋮"
color: "white"
font.pointSize: 24
font.weight: Font.Bold
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
background: Rectangle {
implicitWidth: root.height
implicitHeight: implicitWidth
color: overflowButton.down ?
"#80aaaaaa" :
overflowButton.hovered ?
"#80777777" :
"#80444444"
}
Item {
implicitHeight: children[0].contentHeight
width: overflowButton.width
anchors.bottom: parent.top
anchors.bottomMargin: itemSpacing
visible: !overflowButton.popup.visible
ListView {
spacing: itemSpacing
anchors.fill: parent
model: CallOverlayModel.overflowVisibleModel()
delegate: buttonDelegate
ScrollIndicator.vertical: ScrollIndicator {}
add: Transition {
NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: 80 }
NumberAnimation { property: "scale"; from: 0; to: 1.0; duration: 80 }
}
}
}
popup: Popup {
y: overflowButton.height + itemSpacing
width: overflowButton.width
implicitHeight: contentItem.implicitHeight
padding: 0
contentItem: ListView {
id: overflowListView
spacing: itemSpacing
implicitHeight: contentHeight
model: overflowButton.popup.visible ?
overflowButton.delegateModel :
null
ScrollIndicator.vertical: ScrollIndicator {}
}
background: Rectangle {
color: "transparent"
}
}
}
}
}
}

View file

@ -0,0 +1,304 @@
/*
* Copyright (C) 2021 by Savoir-faire Linux
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.14
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtGraphicalEffects 1.14
import net.jami.Models 1.0
import net.jami.Constants 1.0
import "../../commoncomponents"
ItemDelegate {
id: wrapper
property bool isFirst: index < 1
property bool isLast: index + 1 < ListView.view.count ? false : true
property bool hasLast: ListView.view.centeredGroup !== undefined
property bool isVertical: wrapper.ListView.view.orientation === ListView.Vertical
property alias subMenuVisible: menu.popup.visible
action: ItemAction
checkable: ItemAction.checkable
// hide the action's visual elements like the blurry looking icon
icon.source: ""
text: ""
z: index
// TODO: remove this when output volume control is implemented
MouseArea {
visible: ItemAction.bypassMuteAction !== undefined &&
ItemAction.bypassMuteAction && !menu.popup.visible
anchors.fill: wrapper
onClicked: menu.popup.open()
}
background: HalfPill {
anchors.fill: parent
radius: 5
color: {
if (supplimentaryBackground.visible)
return "#c4272727"
return wrapper.down ?
"#c4777777" :
wrapper.hovered && !menu.hovered ?
"#c4444444" :
"#c4272727"
}
type: {
if (isVertical) {
if (isFirst)
return HalfPill.Top
else if (isLast && hasLast)
return HalfPill.Bottom
} else {
if (isFirst)
return HalfPill.Left
else if (isLast && hasLast)
return HalfPill.Right
}
return HalfPill.None
}
Behavior on color {
ColorAnimation { duration: JamiTheme.shortFadeDuration }
}
}
Rectangle {
id: supplimentaryBackground
visible: ItemAction.hasBg !== undefined
color: wrapper.down ?
Qt.lighter(JamiTheme.refuseRed, 1.5) :
wrapper.hovered && !menu.hovered ?
JamiTheme.refuseRed :
JamiTheme.refuseRedTransparent
anchors.fill: parent
radius: width / 2
Behavior on color {
ColorAnimation { duration: JamiTheme.shortFadeDuration }
}
}
ResponsiveImage {
id: icon
// TODO: remove this when the icons are size corrected
property real size: ItemAction.size !== undefined ?
ItemAction.size : 30
containerWidth: size
containerHeight: size
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
source: ItemAction.icon.source
color: ItemAction.icon.color
SequentialAnimation on opacity {
loops: Animation.Infinite
running: ItemAction.blinksWhenChecked !== undefined &&
ItemAction.blinksWhenChecked && checked
NumberAnimation { from: 1; to: 0; duration: JamiTheme.recordBlinkDuration }
NumberAnimation { from: 0; to: 1; duration: JamiTheme.recordBlinkDuration }
}
}
// custom anchor for the tooltips
Item {
anchors.top: !isVertical ? parent.bottom : undefined
anchors.topMargin: 25
anchors.horizontalCenter: !isVertical ? parent.horizontalCenter : undefined
anchors.right: isVertical ? parent.left : undefined
anchors.rightMargin: isVertical ? toolTip.contentWidth / 2 + 12 : 0
anchors.verticalCenter: isVertical ? parent.verticalCenter : undefined
anchors.verticalCenterOffset: isVertical ? toolTip.contentHeight / 2 + 4 : 0
MaterialToolTip {
id: toolTip
parent: parent
visible: text.length > 0 && (wrapper.hovered || menu.hovered)
text: menu.hovered ? menuAction.text : ItemAction.text
verticalPadding: 1
font.pointSize: 9
}
}
property var menuAction: ItemAction.menuAction
ComboBox {
id: menu
indicator: null
visible: menuAction !== undefined && !BadgeCount
anchors.horizontalCenter: parent.horizontalCenter
width: 18
height: width
y: -4
Connections {
target: menuAction !== undefined ?
menuAction :
null
function onTriggered() {
itemListView.currentIndex =
menuAction.listModel.getCurrentIndex()
}
}
contentItem: Text {
text: "^"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: "white"
}
background: Rectangle {
color: menu.down ?
"#aaaaaa" :
menu.hovered ?
"#777777" :
"#444444"
radius: 4
}
onActivated: menuAction.accept(index)
model: visible ? menuAction.listModel : null
delegate: ItemDelegate {
id: menuItem
width: itemListView.menuItemWidth
height: itemListView.menuItemHeight
background: Rectangle {
anchors.fill: parent
color: menuItem.down ?
"#c4aaaaaa" :
menuItem.hovered ?
"#c4777777" :
"transparent"
}
contentItem: RowLayout {
anchors.fill: parent
anchors.margins: 6
ResponsiveImage {
source: menuItem.ListView.isCurrentItem ?
"qrc:/images/icons/check_box-24px.svg" :
"qrc:/images/icons/check_box_outline_blank-24px.svg"
layer.enabled: true
layer.effect: ColorOverlay { color: "white" }
}
Text {
Layout.fillWidth: true
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
text: DeviceName
elide: Text.ElideRight
color: "white"
}
}
}
popup: Popup {
id: itemPopup
y: isVertical ?
-(implicitHeight - wrapper.height) / 2 :
-implicitHeight - 12
x: isVertical ?
-implicitWidth - 24 :
-(implicitWidth - wrapper.width) / 2 - 18
implicitWidth: contentItem.implicitWidth
implicitHeight: contentItem.implicitHeight
leftPadding: 0
rightPadding: 0
onOpened: menuAction.triggered()
contentItem: ListView {
id: itemListView
property real menuItemWidth: 0
property real menuItemHeight: 39
orientation: ListView.Vertical
implicitWidth: menuItemWidth
implicitHeight: Math.min(contentHeight,
menuItemHeight * 6) + 24
ScrollIndicator.vertical: ScrollIndicator {}
clip: true
model: menu.delegateModel
TextMetrics { id: itemTextMetrics }
// recalc list width based on max item width
onCountChanged: {
// Hack: use AudioDeviceModel.DeviceName role for video as well
var maxWidth = 0
for (var i = 0; i < count; ++i) {
var idx = menuAction.listModel.index(i, 0)
itemTextMetrics.text = menuAction.listModel.data(
idx, AudioDeviceModel.DeviceName)
if (itemTextMetrics.boundingRect.width > maxWidth)
maxWidth = itemTextMetrics.boundingRect.width
}
// 30(icon) + 5(layout spacing) + 12(margins)
menuItemWidth = Math.min(256, maxWidth + 30 + 5 + 12)
}
}
background: Rectangle {
anchors.fill: parent
radius: 5
color: "#c4272727"
}
}
layer.enabled: true
layer.effect: DropShadow {
z: -1
horizontalOffset: 0
verticalOffset: 0
radius: 8.0
samples: 16
color: "#80000000"
}
}
BadgeNotifier {
id: badge
count: BadgeCount
anchors.horizontalCenter: parent.horizontalCenter
width: 18
height: width
radius: 4
y: -4
}
}

View file

@ -2,7 +2,7 @@
* Copyright (C) 2020 by Savoir-faire Linux * Copyright (C) 2020 by Savoir-faire Linux
* Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com> * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
* Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -29,6 +29,8 @@ import net.jami.Adapters 1.0
import net.jami.Constants 1.0 import net.jami.Constants 1.0
import "../js/contactpickercreation.js" as ContactPickerCreation import "../js/contactpickercreation.js" as ContactPickerCreation
import "../js/selectscreenwindowcreation.js" as SelectScreenWindowCreation
import "../js/screenrubberbandcreation.js" as ScreenRubberBandCreation
import "../js/pluginhandlerpickercreation.js" as PluginHandlerPickerCreation import "../js/pluginhandlerpickercreation.js" as PluginHandlerPickerCreation
import "../../commoncomponents" import "../../commoncomponents"
@ -37,13 +39,18 @@ Item {
id: root id: root
property alias participantsLayer: __participantsLayer property alias participantsLayer: __participantsLayer
property string timeText: "00:00"
property string remoteRecordingLabel: "" property bool isPaused
property bool isVideoMuted: true property bool isAudioOnly
property bool isAudioOnly: false property bool isAudioMuted
property bool isVideoMuted
property bool isRecording
property bool isSIP
property bool isModerator
property string bestName: "" property string bestName: ""
signal overlayChatButtonClicked signal chatButtonClicked
onVisibleChanged: if (!visible) callViewContextMenu.close() onVisibleChanged: if (!visible) callViewContextMenu.close()
@ -54,25 +61,23 @@ Item {
function setRecording(localIsRecording) { function setRecording(localIsRecording) {
callViewContextMenu.localIsRecording = localIsRecording callViewContextMenu.localIsRecording = localIsRecording
recordingRect.visible = localIsRecording mainOverlay.recordingVisible = localIsRecording
|| callViewContextMenu.peerIsRecording || callViewContextMenu.peerIsRecording
} }
function updateButtonStatus(isPaused, isAudioOnly, isAudioMuted, isVideoMuted, function updateUI(isPaused, isAudioOnly, isAudioMuted,
isRecording, isSIP, isConferenceCall) { isVideoMuted, isRecording, isSIP) {
root.isVideoMuted = isVideoMuted if (isPaused !== undefined) {
callViewContextMenu.isSIP = isSIP root.isPaused = isPaused
callViewContextMenu.isPaused = isPaused root.isAudioOnly = isAudioOnly
callViewContextMenu.isAudioOnly = isAudioOnly root.isAudioMuted = isAudioMuted
callViewContextMenu.localIsRecording = isRecording root.isVideoMuted = isVideoMuted
recordingRect.visible = isRecording root.isRecording = isRecording
callOverlayButtonGroup.setButtonStatus(isPaused, isAudioOnly, root.isSIP = isSIP
isAudioMuted, isVideoMuted, mainOverlay.recordingVisible = isRecording
isSIP, isConferenceCall) }
}
function updateMenu() { root.isModerator = CallAdapter.isCurrentModerator()
callOverlayButtonGroup.updateMenu()
} }
function showOnHoldImage(visible) { function showOnHoldImage(visible) {
@ -109,17 +114,19 @@ Item {
: JamiStrings.isRecording) : JamiStrings.isRecording)
} }
remoteRecordingLabel = state? label : JamiStrings.peerStoppedRecording mainOverlay.remoteRecordingLabel = state ?
label :
JamiStrings.peerStoppedRecording
callViewContextMenu.peerIsRecording = state callViewContextMenu.peerIsRecording = state
recordingRect.visible = callViewContextMenu.localIsRecording mainOverlay.recordingVisible = callViewContextMenu.localIsRecording
|| callViewContextMenu.peerIsRecording || callViewContextMenu.peerIsRecording
callOverlayRectMouseArea.entered() callOverlayRectMouseArea.entered()
} }
function resetRemoteRecording() { function resetRemoteRecording() {
remoteRecordingLabel = "" mainOverlay.remoteRecordingLabel = ""
callViewContextMenu.peerIsRecording = false callViewContextMenu.peerIsRecording = false
recordingRect.visible = callViewContextMenu.localIsRecording mainOverlay.recordingVisible = callViewContextMenu.localIsRecording
} }
SipInputPanel { SipInputPanel {
@ -143,182 +150,61 @@ Item {
source: "qrc:/images/icons/ic_pause_white_100px.svg" source: "qrc:/images/icons/ic_pause_white_100px.svg"
} }
Item { function openContactPicker(type) {
ContactPickerCreation.createContactPickerObjects(type, root)
ContactPickerCreation.openContactPicker()
}
function openShareScreen() {
if (Qt.application.screens.length === 1) {
AvAdapter.shareEntireScreen(0)
} else {
SelectScreenWindowCreation.createSelectScreenWindowObject()
SelectScreenWindowCreation.showSelectScreenWindow()
}
}
function openShareScreenArea() {
if (Qt.platform.os !== "windows") {
AvAdapter.shareScreenArea(0, 0, 0, 0)
} else {
ScreenRubberBandCreation.createScreenRubberBandWindowObject()
ScreenRubberBandCreation.showScreenRubberBandWindow()
}
}
function openPluginsMenu() {
PluginHandlerPickerCreation.createPluginHandlerPickerObjects(root, true)
PluginHandlerPickerCreation.openPluginHandlerPicker()
}
MainOverlay {
id: mainOverlay id: mainOverlay
anchors.fill: parent anchors.fill: parent
opacity: 0
// (un)subscribe to an app-wide mouse move event trap filtered bestName: root.bestName
// for the overlay's geometry
onVisibleChanged: visible ?
CallOverlayModel.registerFilter(appWindow, this) :
CallOverlayModel.unregisterFilter(appWindow, this)
Connections { Connections {
target: CallOverlayModel target: mainOverlay.callActionBar
function onChatClicked() { root.chatButtonClicked() }
function onMouseMoved(item) { function onAddToConferenceClicked() { openContactPicker(ContactList.CONFERENCE) }
if (item === mainOverlay) { function onTransferClicked() { openContactPicker(ContactList.TRANSFER) }
mainOverlay.opacity = 1 function onShareScreenClicked() { openShareScreen() }
fadeOutTimer.restart() function onShareScreenAreaClicked() { openShareScreenArea() }
} function onPluginsClicked() { openPluginsMenu() }
}
} }
// control overlay fade out.
Timer {
id: fadeOutTimer
interval: JamiTheme.overlayFadeDelay
onTriggered: {
if (callOverlayButtonGroup.hovered)
return
mainOverlay.opacity = 0
resetLabelsTimer.restart()
}
}
// Timer to reset recording label and call duration time
Timer {
id: resetLabelsTimer
interval: 1000
running: root.visible
repeat: true
onTriggered: {
timeText = CallAdapter.getCallDurationTime(LRCInstance.currentAccountId,
LRCInstance.selectedConvUid)
if (mainOverlay.opacity === 0 && !callViewContextMenu.peerIsRecording)
remoteRecordingLabel = ""
}
}
Item {
id: overlayUpperPartRect
anchors.top: parent.top
width: parent.width
height: 50
RowLayout {
anchors.fill: parent
Text {
id: jamiBestNameText
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.preferredWidth: overlayUpperPartRect.width / 3
Layout.preferredHeight: 50
leftPadding: 16
font.pointSize: JamiTheme.textFontSize
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
text: textMetricsjamiBestNameText.elidedText
color: "white"
TextMetrics {
id: textMetricsjamiBestNameText
font: jamiBestNameText.font
text: {
if (!root.isAudioOnly) {
if (remoteRecordingLabel === "") {
return root.bestName
} else {
return remoteRecordingLabel
}
}
return ""
}
elideWidth: overlayUpperPartRect.width / 3
elide: Qt.ElideRight
}
}
Text {
id: callTimerText
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.preferredWidth: 64
Layout.minimumWidth: 64
Layout.preferredHeight: 48
Layout.rightMargin: recordingRect.visible?
0 : JamiTheme.preferredMarginSize
font.pointSize: JamiTheme.textFontSize
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
text: textMetricscallTimerText.elidedText
color: "white"
TextMetrics {
id: textMetricscallTimerText
font: callTimerText.font
text: timeText
elideWidth: overlayUpperPartRect.width / 4
elide: Qt.ElideRight
}
}
Rectangle {
id: recordingRect
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.rightMargin: JamiTheme.preferredMarginSize
height: 16
width: 16
radius: height / 2
color: "red"
SequentialAnimation on color {
loops: Animation.Infinite
running: true
ColorAnimation { from: "red"; to: "transparent"; duration: 500 }
ColorAnimation { from: "transparent"; to: "red"; duration: 500 }
}
}
}
}
CallOverlayButtonGroup {
id: callOverlayButtonGroup
anchors.bottom: parent.bottom
anchors.bottomMargin: 10
anchors.horizontalCenter: parent.horizontalCenter
height: 56
width: root.width
onChatButtonClicked: {
root.overlayChatButtonClicked()
}
onAddToConferenceButtonClicked: {
// Create contact picker - conference.
ContactPickerCreation.createContactPickerObjects(
ContactList.CONFERENCE,
root)
ContactPickerCreation.openContactPicker()
}
}
Behavior on opacity { NumberAnimation { duration: JamiTheme.overlayFadeDuration }}
} }
CallViewContextMenu { CallViewContextMenu {
id: callViewContextMenu id: callViewContextMenu
onTransferCallButtonClicked: { isSIP: root.isSIP
// Create contact picker - sip transfer. isPaused: root.isPaused
ContactPickerCreation.createContactPickerObjects( isAudioOnly: root.isAudioOnly
ContactList.TRANSFER, localIsRecording: root.isRecording
root)
ContactPickerCreation.openContactPicker()
}
onPluginItemClicked: { onTransferCallButtonClicked: openContactPicker(ContactList.TRANSFER)
// Create plugin handler picker - PLUGINS onPluginItemClicked: openPluginsMenu()
PluginHandlerPickerCreation.createPluginHandlerPickerObjects(root, true)
PluginHandlerPickerCreation.openPluginHandlerPicker()
}
} }
} }

View file

@ -1,211 +0,0 @@
/*
* Copyright (C) 2020 by Savoir-faire Linux
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
* Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
*
* This program 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.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls.Universal 2.14
import QtQml 2.14
import net.jami.Adapters 1.0
import net.jami.Models 1.0
import net.jami.Constants 1.0
import "../../commoncomponents"
Control {
id: root
// ButtonCounts here is to make sure that flow layout margin is calculated correctly,
// since no other methods can make buttons at the layout center.
property var isModerator: true
property var isSip: false
signal chatButtonClicked
signal addToConferenceButtonClicked
function updateMenu() {
root.isModerator = CallAdapter.isCurrentModerator()
addToConferenceButton.visible = !root.isSip && root.isModerator
}
function setButtonStatus(isPaused, isAudioOnly, isAudioMuted, isVideoMuted,
isSIP, isConferenceCall) {
root.isModerator = CallAdapter.isCurrentModerator()
root.isSip = isSIP
noVideoButton.visible = !isAudioOnly
addToConferenceButton.visible = !root.isSIP && root.isModerator
noMicButton.checked = isAudioMuted
noVideoButton.checked = isVideoMuted
}
z: 2
RowLayout {
id: callOverlayButtonGroup
spacing: 8
height: 56
anchors.fill: parent
Item {
Layout.preferredWidth: {
// TODO: refactor with Flow if possible
// 6 is the number of button
// If ~ 500px, go into wide mode
if (callOverlayButtonGroup.width < (JamiTheme.callButtonPreferredSize * 6 -
callOverlayButtonGroup.spacing * 6 + 300)) {
return 0
} else {
return callOverlayButtonGroup.width / 2 - JamiTheme.callButtonPreferredSize * 1.5 -
callOverlayButtonGroup.spacing
}
}
}
PushButton {
id: noMicButton
Layout.leftMargin: 8
Layout.preferredWidth: JamiTheme.callButtonPreferredSize
Layout.preferredHeight: JamiTheme.callButtonPreferredSize
pressedColor: JamiTheme.invertedPressedButtonColor
hoveredColor: JamiTheme.invertedHoveredButtonColor
normalColor: JamiTheme.invertedNormalButtonColor
normalImageSource: "qrc:/images/icons/mic-24px.svg"
imageColor: JamiTheme.whiteColor
checkable: true
checkedImageSource: "qrc:/images/icons/mic_off-24px.svg"
checkedImageColor: JamiTheme.declineButtonPressedRed
toolTipText: !checked ? JamiStrings.mute : JamiStrings.unmute
onClicked: CallAdapter.muteThisCallToggle()
}
PushButton {
id: hangUpButton
Layout.preferredWidth: JamiTheme.callButtonPreferredSize
Layout.preferredHeight: JamiTheme.callButtonPreferredSize
pressedColor: JamiTheme.declineButtonPressedRed
hoveredColor: JamiTheme.declineButtonHoverRed
normalColor: JamiTheme.declineButtonRed
source: "qrc:/images/icons/ic_call_end_white_24px.svg"
imageColor: JamiTheme.whiteColor
toolTipText: JamiStrings.hangup
onClicked: CallAdapter.hangUpThisCall()
}
PushButton {
id: noVideoButton
Layout.preferredWidth: JamiTheme.callButtonPreferredSize
Layout.preferredHeight: JamiTheme.callButtonPreferredSize
pressedColor: JamiTheme.invertedPressedButtonColor
hoveredColor: JamiTheme.invertedHoveredButtonColor
normalColor: JamiTheme.invertedNormalButtonColor
normalImageSource: "qrc:/images/icons/videocam-24px.svg"
imageColor: JamiTheme.whiteColor
checkable: true
checkedImageSource: "qrc:/images/icons/videocam_off-24px.svg"
checkedImageColor: JamiTheme.declineButtonPressedRed
toolTipText: !checked ? JamiStrings.pauseVideo : JamiStrings.resumeVideo
onClicked: CallAdapter.videoPauseThisCallToggle()
}
Item {
Layout.fillWidth: true
}
PushButton {
id: addToConferenceButton
Layout.preferredWidth: JamiTheme.callButtonPreferredSize
Layout.preferredHeight: JamiTheme.callButtonPreferredSize
visible: !isModerator && !isSip
pressedColor: JamiTheme.invertedPressedButtonColor
hoveredColor: JamiTheme.invertedHoveredButtonColor
normalColor: JamiTheme.invertedNormalButtonColor
source: "qrc:/images/icons/group_add-24px.svg"
imageColor: JamiTheme.whiteColor
toolTipText: JamiStrings.addParticipants
onClicked: root.addToConferenceButtonClicked()
}
PushButton {
id: chatButton
Layout.preferredWidth: JamiTheme.callButtonPreferredSize
Layout.preferredHeight: JamiTheme.callButtonPreferredSize
pressedColor: JamiTheme.invertedPressedButtonColor
hoveredColor: JamiTheme.invertedHoveredButtonColor
normalColor: JamiTheme.invertedNormalButtonColor
source: "qrc:/images/icons/chat-24px.svg"
imageColor: JamiTheme.whiteColor
toolTipText: JamiStrings.chat
onClicked: root.chatButtonClicked()
}
PushButton {
id: optionsButton
Layout.preferredWidth: JamiTheme.callButtonPreferredSize
Layout.preferredHeight: JamiTheme.callButtonPreferredSize
Layout.rightMargin: 8
pressedColor: JamiTheme.invertedPressedButtonColor
hoveredColor: JamiTheme.invertedHoveredButtonColor
normalColor: JamiTheme.invertedNormalButtonColor
source: "qrc:/images/icons/more_vert-24px.svg"
imageColor: JamiTheme.whiteColor
toolTipText: JamiStrings.moreOptions
onClicked: {
var rectPos = mapToItem(callStackViewWindow, optionsButton.x, optionsButton.y)
callViewContextMenu.x = rectPos.x + optionsButton.width / 2
- callViewContextMenu.width / 2
callViewContextMenu.y = rectPos.y - 12 - callViewContextMenu.height
callViewContextMenu.openMenu()
}
}
}
}

View file

@ -0,0 +1,200 @@
/*
* Copyright (C) 2020-2021 by Savoir-faire Linux
* Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
* Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program 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.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls.Universal 2.14
import QtQml 2.14
import net.jami.Models 1.0
import net.jami.Adapters 1.0
import net.jami.Constants 1.0
import "../../commoncomponents"
Item {
id: root
property string timeText: "00:00"
property string remoteRecordingLabel: ""
property string bestName: ""
property alias recordingVisible: recordingRect.visible
property alias callActionBar: __callActionBar
property bool frozen: callActionBar.overflowOpen ||
callActionBar.hovered ||
callActionBar.subMenuOpen
opacity: 0
// (un)subscribe to an app-wide mouse move event trap filtered
// for the overlay's geometry
onVisibleChanged: visible ?
CallOverlayModel.registerFilter(appWindow, this) :
CallOverlayModel.unregisterFilter(appWindow, this)
Connections {
target: CallOverlayModel
function onMouseMoved(item) {
if (item === root) {
root.opacity = 1
fadeOutTimer.restart()
}
}
}
// control overlay fade out.
Timer {
id: fadeOutTimer
interval: JamiTheme.overlayFadeDelay
onTriggered: {
if (frozen)
return
root.opacity = 0
resetLabelsTimer.restart()
}
}
// Timer to reset recording label and call duration time
Timer {
id: resetLabelsTimer
interval: 1000
running: root.visible
repeat: true
onTriggered: {
root.timeText = CallAdapter.getCallDurationTime(
LRCInstance.currentAccountId,
LRCInstance.selectedConvUid)
if (root.opacity === 0 && !callViewContextMenu.peerIsRecording)
root.remoteRecordingLabel = ""
}
}
Item {
id: overlayUpperPartRect
anchors.top: parent.top
width: parent.width
height: 50
RowLayout {
anchors.fill: parent
Text {
id: jamiBestNameText
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.preferredWidth: overlayUpperPartRect.width / 3
Layout.preferredHeight: 50
leftPadding: 16
font.pointSize: JamiTheme.textFontSize
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
text: textMetricsjamiBestNameText.elidedText
color: "white"
TextMetrics {
id: textMetricsjamiBestNameText
font: jamiBestNameText.font
text: {
if (!root.isAudioOnly) {
if (remoteRecordingLabel === "") {
return root.bestName
} else {
return remoteRecordingLabel
}
}
return ""
}
elideWidth: overlayUpperPartRect.width / 3
elide: Qt.ElideRight
}
}
Text {
id: callTimerText
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.preferredWidth: 64
Layout.minimumWidth: 64
Layout.preferredHeight: 48
Layout.rightMargin: recordingRect.visible?
0 : JamiTheme.preferredMarginSize
font.pointSize: JamiTheme.textFontSize
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
text: textMetricscallTimerText.elidedText
color: "white"
TextMetrics {
id: textMetricscallTimerText
font: callTimerText.font
text: timeText
elideWidth: overlayUpperPartRect.width / 4
elide: Qt.ElideRight
}
}
Rectangle {
id: recordingRect
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.rightMargin: JamiTheme.preferredMarginSize
height: 16
width: 16
radius: height / 2
color: "red"
SequentialAnimation on color {
loops: Animation.Infinite
running: true
ColorAnimation {
from: "red"; to: "transparent";
duration: JamiTheme.recordBlinkDuration
}
ColorAnimation {
from: "transparent"; to: "red";
duration: JamiTheme.recordBlinkDuration
}
}
}
}
}
CallActionBar {
id: __callActionBar
anchors {
bottom: parent.bottom
bottomMargin: 26
}
width: parent.width
height: 55
}
Behavior on opacity { NumberAnimation { duration: JamiTheme.overlayFadeDuration }}
}

View file

@ -302,15 +302,11 @@ Rectangle {
target: CallAdapter target: CallAdapter
function onUpdateOverlay(isPaused, isAudioOnly, isAudioMuted, isVideoMuted, function onUpdateOverlay(isPaused, isAudioOnly, isAudioMuted, isVideoMuted,
isRecording, isSIP, isConferenceCall, bestName) { isRecording, isSIP, bestName) {
callOverlay.showOnHoldImage(isPaused) callOverlay.showOnHoldImage(isPaused)
audioCallPageRectCentralRect.visible = !isPaused && root.isAudioOnly audioCallPageRectCentralRect.visible = !isPaused && root.isAudioOnly
callOverlay.updateButtonStatus(isPaused, callOverlay.updateUI(isPaused, isAudioOnly, isAudioMuted, isVideoMuted,
isAudioOnly, isRecording, isSIP)
isAudioMuted,
isVideoMuted,
isRecording, isSIP,
isConferenceCall)
root.bestName = bestName root.bestName = bestName
callOverlay.participantsLayer.update(CallAdapter.getConferencesInfos()) callOverlay.participantsLayer.update(CallAdapter.getConferencesInfos())
} }
@ -329,7 +325,7 @@ Rectangle {
} }
} }
onOverlayChatButtonClicked: { onChatButtonClicked: {
inCallMessageWebViewStack.visible ? inCallMessageWebViewStack.visible ?
closeInCallConversation() : closeInCallConversation() :
openInCallConversation() openInCallConversation()

View file

@ -40,7 +40,7 @@ Item {
// TODO: in the future the conference layout should be entirely managed by the client // TODO: in the future the conference layout should be entirely managed by the client
// Hack: truncate and ceil participant's overlay position and size to correct // Hack: truncate and ceil participant's overlay position and size to correct
// when they are not exacts // when they are not exacts
callOverlay.updateMenu() callOverlay.updateUI()
var showMax = false var showMax = false
var showMin = false var showMin = false

View file

@ -50,6 +50,6 @@ GeneralMenuItem {
onClicked: { onClicked: {
var deviceName = videoCallPageContextMenuDeviceItem.itemName var deviceName = videoCallPageContextMenuDeviceItem.itemName
AvAdapter.onVideoContextMenuDeviceItemClicked(deviceName) AvAdapter.selectVideoInputDeviceByName(deviceName)
} }
} }

View file

@ -63,7 +63,7 @@ ColumnLayout {
} }
deviceComboBoxSetting.setCurrentIndex( deviceComboBoxSetting.setCurrentIndex(
deviceComboBoxSetting.comboModel.getCurrentSettingIndex(), true) deviceComboBoxSetting.comboModel.getCurrentIndex(), true)
hardwareAccelControl.checked = AVModel.getHardwareAcceleration() hardwareAccelControl.checked = AVModel.getHardwareAcceleration()
} }

View file

@ -97,6 +97,8 @@ VideoInputDeviceModel::data(const QModelIndex& index, int role) const
return QVariant((QString) currentDeviceSetting.size); return QVariant((QString) currentDeviceSetting.size);
case Role::DeviceName_UTF8: case Role::DeviceName_UTF8:
return QVariant(currentDeviceSetting.name.toUtf8()); return QVariant(currentDeviceSetting.name.toUtf8());
case Role::isCurrent:
return QVariant(index.row() == getCurrentIndex());
} }
return QVariant(); return QVariant();
} }
@ -111,6 +113,7 @@ VideoInputDeviceModel::roleNames() const
roles[CurrentFrameRate] = "CurrentFrameRate"; roles[CurrentFrameRate] = "CurrentFrameRate";
roles[CurrentResolution] = "CurrentResolution"; roles[CurrentResolution] = "CurrentResolution";
roles[DeviceName_UTF8] = "DeviceName_UTF8"; roles[DeviceName_UTF8] = "DeviceName_UTF8";
roles[isCurrent] = "isCurrent";
return roles; return roles;
} }
@ -159,15 +162,9 @@ VideoInputDeviceModel::deviceCount()
} }
int int
VideoInputDeviceModel::getCurrentSettingIndex() VideoInputDeviceModel::getCurrentIndex() const
{ {
QString currentId = lrcInstance_->avModel().getCurrentVideoCaptureDevice(); QString currentId = lrcInstance_->avModel().getCurrentVideoCaptureDevice();
auto resultList = match(index(0, 0), DeviceId, QVariant(currentId)); auto resultList = match(index(0, 0), DeviceId, QVariant(currentId));
return resultList.size() > 0 ? resultList[0].row() : 0;
int resultRowIndex = 0;
if (resultList.size() > 0) {
resultRowIndex = resultList[0].row();
}
return resultRowIndex;
} }

View file

@ -25,12 +25,13 @@ class VideoInputDeviceModel : public AbstractListModelBase
Q_OBJECT Q_OBJECT
public: public:
enum Role { enum Role {
DeviceChannel = Qt::UserRole + 1, DeviceName = Qt::UserRole + 1,
DeviceName, DeviceChannel,
DeviceId, DeviceId,
CurrentFrameRate, CurrentFrameRate,
CurrentResolution, CurrentResolution,
DeviceName_UTF8 DeviceName_UTF8,
isCurrent
}; };
Q_ENUM(Role) Q_ENUM(Role)
@ -59,8 +60,7 @@ public:
* This function is to reset the model when there's new account added. * This function is to reset the model when there's new account added.
*/ */
Q_INVOKABLE int deviceCount(); Q_INVOKABLE int deviceCount();
/*
* This function is to get the current device id in the demon. // get model index of the current device
*/ Q_INVOKABLE int getCurrentIndex() const;
Q_INVOKABLE int getCurrentSettingIndex();
}; };