diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /xbmc/dialogs | |
parent | Initial commit. (diff) | |
download | kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip |
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/dialogs')
64 files changed, 11582 insertions, 0 deletions
diff --git a/xbmc/dialogs/CMakeLists.txt b/xbmc/dialogs/CMakeLists.txt new file mode 100644 index 0000000..bd14222 --- /dev/null +++ b/xbmc/dialogs/CMakeLists.txt @@ -0,0 +1,69 @@ +set(SOURCES GUIDialogBoxBase.cpp + GUIDialogBusy.cpp + GUIDialogBusyNoCancel.cpp + GUIDialogButtonMenu.cpp + GUIDialogCache.cpp + GUIDialogColorPicker.cpp + GUIDialogContextMenu.cpp + GUIDialogExtendedProgressBar.cpp + GUIDialogFileBrowser.cpp + GUIDialogGamepad.cpp + GUIDialogKaiToast.cpp + GUIDialogKeyboardGeneric.cpp + GUIDialogKeyboardTouch.cpp + GUIDialogMediaFilter.cpp + GUIDialogMediaSource.cpp + GUIDialogNumeric.cpp + GUIDialogOK.cpp + GUIDialogPlayerControls.cpp + GUIDialogPlayerProcessInfo.cpp + GUIDialogProgress.cpp + GUIDialogSeekBar.cpp + GUIDialogSelect.cpp + GUIDialogSimpleMenu.cpp + GUIDialogSlider.cpp + GUIDialogSmartPlaylistEditor.cpp + GUIDialogSmartPlaylistRule.cpp + GUIDialogSubMenu.cpp + GUIDialogTextViewer.cpp + GUIDialogVolumeBar.cpp + GUIDialogYesNo.cpp) + +set(HEADERS GUIDialogBoxBase.h + GUIDialogBusy.h + GUIDialogBusyNoCancel.h + GUIDialogButtonMenu.h + GUIDialogCache.h + GUIDialogColorPicker.h + GUIDialogContextMenu.h + GUIDialogExtendedProgressBar.h + GUIDialogFileBrowser.h + GUIDialogGamepad.h + GUIDialogKaiToast.h + GUIDialogKeyboardGeneric.h + GUIDialogKeyboardTouch.h + GUIDialogMediaFilter.h + GUIDialogMediaSource.h + GUIDialogNumeric.h + GUIDialogOK.h + GUIDialogPlayerControls.h + GUIDialogPlayerProcessInfo.h + GUIDialogProgress.h + GUIDialogSeekBar.h + GUIDialogSelect.h + GUIDialogSimpleMenu.h + GUIDialogSlider.h + GUIDialogSmartPlaylistEditor.h + GUIDialogSmartPlaylistRule.h + GUIDialogSubMenu.h + GUIDialogTextViewer.h + GUIDialogVolumeBar.h + GUIDialogYesNo.h + IGUIVolumeBarCallback.h) + +if(ENABLE_OPTICAL) + list(APPEND SOURCES GUIDialogPlayEject.cpp) + list(APPEND HEADERS GUIDialogPlayEject.h) +endif() + +core_add_library(dialogs) diff --git a/xbmc/dialogs/GUIDialogBoxBase.cpp b/xbmc/dialogs/GUIDialogBoxBase.cpp new file mode 100644 index 0000000..1d87949 --- /dev/null +++ b/xbmc/dialogs/GUIDialogBoxBase.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogBoxBase.h" + +#include "guilib/GUIMessage.h" +#include "guilib/LocalizeStrings.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +#include <mutex> + +#define CONTROL_HEADING 1 +#define CONTROL_LINES_START 2 +#define CONTROL_TEXTBOX 9 + +CGUIDialogBoxBase::CGUIDialogBoxBase(int id, const std::string &xmlFile) + : CGUIDialog(id, xmlFile) +{ + m_bConfirmed = false; + m_loadType = KEEP_IN_MEMORY; + m_hasTextbox = false; +} + +CGUIDialogBoxBase::~CGUIDialogBoxBase(void) = default; + +bool CGUIDialogBoxBase::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_WINDOW_INIT: + { + CGUIDialog::OnMessage(message); + m_bConfirmed = false; + return true; + } + break; + } + return CGUIDialog::OnMessage(message); +} + +bool CGUIDialogBoxBase::IsConfirmed() const +{ + return m_bConfirmed; +} + +void CGUIDialogBoxBase::SetHeading(const CVariant& heading) +{ + std::string label = GetLocalized(heading); + std::unique_lock<CCriticalSection> lock(m_section); + if (label != m_strHeading) + { + m_strHeading = label; + SetInvalid(); + } +} + +void CGUIDialogBoxBase::SetLine(unsigned int iLine, const CVariant& line) +{ + std::string label = GetLocalized(line); + std::unique_lock<CCriticalSection> lock(m_section); + std::vector<std::string> lines = StringUtils::Split(m_text, '\n'); + if (iLine >= lines.size()) + lines.resize(iLine+1); + lines[iLine] = label; + std::string text = StringUtils::Join(lines, "\n"); + SetText(text); +} + +void CGUIDialogBoxBase::SetText(const CVariant& text) +{ + std::string label = GetLocalized(text); + std::unique_lock<CCriticalSection> lock(m_section); + StringUtils::Trim(label, "\n"); + if (label != m_text) + { + m_text = label; + SetInvalid(); + } +} + +void CGUIDialogBoxBase::SetChoice(int iButton, const CVariant &choice) // iButton == 0 for no, 1 for yes +{ + if (iButton < 0 || iButton >= DIALOG_MAX_CHOICES) + return; + + std::string label = GetLocalized(choice); + std::unique_lock<CCriticalSection> lock(m_section); + if (label != m_strChoices[iButton]) + { + m_strChoices[iButton] = label; + SetInvalid(); + } +} + +void CGUIDialogBoxBase::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) +{ + if (m_bInvalidated) + { // take a copy of our labels to save holding the lock for too long + std::string heading, text; + std::vector<std::string> choices; + choices.reserve(DIALOG_MAX_CHOICES); + { + std::unique_lock<CCriticalSection> lock(m_section); + heading = m_strHeading; + text = m_text; + for (const std::string& choice : m_strChoices) + choices.push_back(choice); + } + SET_CONTROL_LABEL(CONTROL_HEADING, heading); + if (m_hasTextbox) + { + SET_CONTROL_LABEL(CONTROL_TEXTBOX, text); + } + else + { + std::vector<std::string> lines = StringUtils::Split(text, "\n", DIALOG_MAX_LINES); + lines.resize(DIALOG_MAX_LINES); + for (size_t i = 0 ; i < lines.size(); ++i) + SET_CONTROL_LABEL(CONTROL_LINES_START + i, lines[i]); + } + for (size_t i = 0 ; i < choices.size() ; ++i) + SET_CONTROL_LABEL(CONTROL_CHOICES_START + i, choices[i]); + } + CGUIDialog::Process(currentTime, dirtyregions); +} + +void CGUIDialogBoxBase::OnInitWindow() +{ + // set focus to default + m_lastControlID = m_defaultControl; + + m_hasTextbox = false; + const CGUIControl *control = GetControl(CONTROL_TEXTBOX); + if (control && control->GetControlType() == CGUIControl::GUICONTROL_TEXTBOX) + m_hasTextbox = true; + + // set initial labels + { + std::unique_lock<CCriticalSection> lock(m_section); + for (int i = 0 ; i < DIALOG_MAX_CHOICES ; ++i) + { + if (m_strChoices[i].empty()) + m_strChoices[i] = GetDefaultLabel(CONTROL_CHOICES_START + i); + } + } + CGUIDialog::OnInitWindow(); +} + +void CGUIDialogBoxBase::OnDeinitWindow(int nextWindowID) +{ + // make sure we set default labels for heading, lines and choices + { + std::unique_lock<CCriticalSection> lock(m_section); + m_strHeading.clear(); + m_text.clear(); + for (std::string& choice : m_strChoices) + choice.clear(); + } + + CGUIDialog::OnDeinitWindow(nextWindowID); +} + +std::string CGUIDialogBoxBase::GetLocalized(const CVariant &var) const +{ + if (var.isString()) + return var.asString(); + else if (var.isInteger() && var.asInteger()) + return g_localizeStrings.Get((uint32_t)var.asInteger()); + return ""; +} + +std::string CGUIDialogBoxBase::GetDefaultLabel(int controlId) const +{ + int labelId = GetDefaultLabelID(controlId); + return labelId != -1 ? g_localizeStrings.Get(labelId) : ""; +} + +int CGUIDialogBoxBase::GetDefaultLabelID(int controlId) const +{ + return -1; +} diff --git a/xbmc/dialogs/GUIDialogBoxBase.h b/xbmc/dialogs/GUIDialogBoxBase.h new file mode 100644 index 0000000..41ab31b --- /dev/null +++ b/xbmc/dialogs/GUIDialogBoxBase.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" +#include "threads/CriticalSection.h" + +#include <vector> + +#define CONTROL_CHOICES_START 10 +#define CONTROL_NO_BUTTON CONTROL_CHOICES_START +#define CONTROL_YES_BUTTON CONTROL_CHOICES_START + 1 +#define CONTROL_CUSTOM_BUTTON CONTROL_CHOICES_START + 2 +#define CONTROL_PROGRESS_BAR 20 + +#define DIALOG_MAX_LINES 3 +#define DIALOG_MAX_CHOICES 3 + +class CVariant; + +class CGUIDialogBoxBase : + public CGUIDialog +{ +public: + CGUIDialogBoxBase(int id, const std::string &xmlFile); + ~CGUIDialogBoxBase(void) override; + bool OnMessage(CGUIMessage& message) override; + bool IsConfirmed() const; + void SetLine(unsigned int iLine, const CVariant& line); + void SetText(const CVariant& text); + void SetHeading(const CVariant& heading); + void SetChoice(int iButton, const CVariant &choice); +protected: + std::string GetDefaultLabel(int controlId) const; + virtual int GetDefaultLabelID(int controlId) const; + /*! \brief Get a localized string from a variant + If the variant is already a string we return directly, else if it's an integer we return the corresponding + localized string. + \param var the variant to localize. + */ + std::string GetLocalized(const CVariant &var) const; + + void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override; + void OnInitWindow() override; + void OnDeinitWindow(int nextWindowID) override; + + bool m_bConfirmed; + bool m_hasTextbox; + + // actual strings + CCriticalSection m_section; + std::string m_strHeading; + std::string m_text; + std::string m_strChoices[DIALOG_MAX_CHOICES]; +}; diff --git a/xbmc/dialogs/GUIDialogBusy.cpp b/xbmc/dialogs/GUIDialogBusy.cpp new file mode 100644 index 0000000..e53f1fc --- /dev/null +++ b/xbmc/dialogs/GUIDialogBusy.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogBusy.h" + +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "threads/IRunnable.h" +#include "threads/Thread.h" +#include "utils/log.h" + +using namespace std::chrono_literals; + +class CBusyWaiter : public CThread +{ + std::shared_ptr<CEvent> m_done; + IRunnable *m_runnable; +public: + explicit CBusyWaiter(IRunnable *runnable) : + CThread(runnable, "waiting"), m_done(new CEvent()), m_runnable(runnable) { } + + ~CBusyWaiter() override { StopThread(); } + + bool Wait(unsigned int displaytime, bool allowCancel) + { + std::shared_ptr<CEvent> e_done(m_done); + + Create(); + auto start = std::chrono::steady_clock::now(); + if (!CGUIDialogBusy::WaitOnEvent(*e_done, displaytime, allowCancel)) + { + m_runnable->Cancel(); + + auto end = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); + + unsigned int remaining = + (duration.count() >= displaytime) ? 0 : displaytime - duration.count(); + CGUIDialogBusy::WaitOnEvent(*e_done, remaining, false); + return false; + } + return true; + } + + // 'this' is actually deleted from the thread where it's on the stack + void Process() override + { + std::shared_ptr<CEvent> e_done(m_done); + + CThread::Process(); + (*e_done).Set(); + } + +}; + +bool CGUIDialogBusy::Wait(IRunnable *runnable, unsigned int displaytime, bool allowCancel) +{ + if (!runnable) + return false; + CBusyWaiter waiter(runnable); + if (!waiter.Wait(displaytime, allowCancel)) + { + return false; + } + return true; +} + +bool CGUIDialogBusy::WaitOnEvent(CEvent &event, unsigned int displaytime /* = 100 */, bool allowCancel /* = true */) +{ + bool cancelled = false; + if (!event.Wait(std::chrono::milliseconds(displaytime))) + { + // throw up the progress + CGUIDialogBusy* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogBusy>(WINDOW_DIALOG_BUSY); + if (dialog) + { + if (dialog->IsDialogRunning()) + { + CLog::Log(LOGFATAL, "Logic error due to two concurrent busydialogs, this is a known issue. " + "The application will exit."); + throw std::logic_error("busy dialog already running"); + } + + dialog->Open(); + + while (!event.Wait(1ms)) + { + dialog->ProcessRenderLoop(false); + if (allowCancel && dialog->IsCanceled()) + { + cancelled = true; + break; + } + } + + dialog->Close(true); + } + } + return !cancelled; +} + +CGUIDialogBusy::CGUIDialogBusy(void) + : CGUIDialog(WINDOW_DIALOG_BUSY, "DialogBusy.xml", DialogModalityType::MODAL) +{ + m_loadType = LOAD_ON_GUI_INIT; + m_bCanceled = false; +} + +CGUIDialogBusy::~CGUIDialogBusy(void) = default; + +void CGUIDialogBusy::Open_Internal(bool bProcessRenderLoop, const std::string& param /* = "" */) +{ + m_bCanceled = false; + m_bLastVisible = true; + + CGUIDialog::Open_Internal(false, param); +} + + +void CGUIDialogBusy::DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions) +{ + bool visible = CServiceBroker::GetGUI()->GetWindowManager().IsModalDialogTopmost(WINDOW_DIALOG_BUSY); + if(!visible && m_bLastVisible) + dirtyregions.push_back(CDirtyRegion(m_renderRegion)); + m_bLastVisible = visible; + + CGUIDialog::DoProcess(currentTime, dirtyregions); +} + +void CGUIDialogBusy::Render() +{ + if(!m_bLastVisible) + return; + CGUIDialog::Render(); +} + +bool CGUIDialogBusy::OnBack(int actionID) +{ + m_bCanceled = true; + return true; +} diff --git a/xbmc/dialogs/GUIDialogBusy.h b/xbmc/dialogs/GUIDialogBusy.h new file mode 100644 index 0000000..586d6a3 --- /dev/null +++ b/xbmc/dialogs/GUIDialogBusy.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +class IRunnable; +class CEvent; + +class CGUIDialogBusy: public CGUIDialog +{ +public: + CGUIDialogBusy(void); + ~CGUIDialogBusy(void) override; + bool OnBack(int actionID) override; + void DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions) override; + void Render() override; + /*! \brief set the current progress of the busy operation + \param progress a percentage of progress + */ + bool IsCanceled() { return m_bCanceled; } + + /*! \brief Wait for a runnable to execute off-thread. + Creates a thread to run the given runnable, and while waiting + it displays the busy dialog. + \param runnable the IRunnable to run. + \param displaytime the time in ms to wait prior to showing the busy dialog (defaults to 100ms) + \param allowCancel whether the user can cancel the wait, defaults to true. + \return true if the runnable completes, false if the user cancels early. + */ + static bool Wait(IRunnable *runnable, unsigned int displaytime, bool allowCancel); + + /*! \brief Wait on an event while displaying the busy dialog. + Throws up the busy dialog after the given time. + \param event the CEvent to wait on. + \param displaytime the time in ms to wait prior to showing the busy dialog (defaults to 100ms) + \param allowCancel whether the user can cancel the wait, defaults to true. + \return true if the event completed, false if cancelled. + */ + static bool WaitOnEvent(CEvent &event, unsigned int displaytime = 100, bool allowCancel = true); +protected: + void Open_Internal(bool bProcessRenderLoop, const std::string& param = "") override; + bool m_bCanceled; + bool m_bLastVisible = false; +}; diff --git a/xbmc/dialogs/GUIDialogBusyNoCancel.cpp b/xbmc/dialogs/GUIDialogBusyNoCancel.cpp new file mode 100644 index 0000000..2bddd01 --- /dev/null +++ b/xbmc/dialogs/GUIDialogBusyNoCancel.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogBusyNoCancel.h" + +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "threads/Thread.h" + +CGUIDialogBusyNoCancel::CGUIDialogBusyNoCancel(void) + : CGUIDialog(WINDOW_DIALOG_BUSY_NOCANCEL, "DialogBusy.xml", DialogModalityType::MODAL) +{ + m_loadType = LOAD_ON_GUI_INIT; +} + +CGUIDialogBusyNoCancel::~CGUIDialogBusyNoCancel(void) = default; + +void CGUIDialogBusyNoCancel::Open_Internal(bool bProcessRenderLoop, + const std::string& param /* = "" */) +{ + m_bLastVisible = true; + + CGUIDialog::Open_Internal(false, param); +} + +void CGUIDialogBusyNoCancel::DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions) +{ + bool visible = CServiceBroker::GetGUI()->GetWindowManager().IsModalDialogTopmost(WINDOW_DIALOG_BUSY_NOCANCEL); + if (!visible && m_bLastVisible) + dirtyregions.push_back(CDirtyRegion(m_renderRegion)); + m_bLastVisible = visible; + + CGUIDialog::DoProcess(currentTime, dirtyregions); +} + +void CGUIDialogBusyNoCancel::Render() +{ + if(!m_bLastVisible) + return; + CGUIDialog::Render(); +} diff --git a/xbmc/dialogs/GUIDialogBusyNoCancel.h b/xbmc/dialogs/GUIDialogBusyNoCancel.h new file mode 100644 index 0000000..b50a080 --- /dev/null +++ b/xbmc/dialogs/GUIDialogBusyNoCancel.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +class CGUIDialogBusyNoCancel: public CGUIDialog +{ +public: + CGUIDialogBusyNoCancel(void); + ~CGUIDialogBusyNoCancel(void) override; + void DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions) override; + void Render() override; + +protected: + void Open_Internal(bool bProcessRenderLoop, const std::string& param = "") override; + bool m_bLastVisible = false; +}; diff --git a/xbmc/dialogs/GUIDialogButtonMenu.cpp b/xbmc/dialogs/GUIDialogButtonMenu.cpp new file mode 100644 index 0000000..04ed916 --- /dev/null +++ b/xbmc/dialogs/GUIDialogButtonMenu.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogButtonMenu.h" + +#include "guilib/GUIMessage.h" + +#define CONTROL_BUTTON_LABEL 3100 + +CGUIDialogButtonMenu::CGUIDialogButtonMenu(int id, const std::string &xmlFile) +: CGUIDialog(id, xmlFile.c_str()) +{ + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogButtonMenu::~CGUIDialogButtonMenu(void) = default; + +bool CGUIDialogButtonMenu::OnMessage(CGUIMessage &message) +{ + bool bRet = CGUIDialog::OnMessage(message); + if (message.GetMessage() == GUI_MSG_CLICKED) + { + // someone has been clicked - deinit... + Close(); + return true; + } + return bRet; +} + +void CGUIDialogButtonMenu::FrameMove() +{ + // get the active control, and put it's label into the label control + const CGUIControl *pControl = GetFocusedControl(); + if (pControl && (pControl->GetControlType() == CGUIControl::GUICONTROL_BUTTON || + pControl->GetControlType() == CGUIControl::GUICONTROL_TOGGLEBUTTON || + pControl->GetControlType() == CGUIControl::GUICONTROL_COLORBUTTON)) + { + SET_CONTROL_LABEL(CONTROL_BUTTON_LABEL, pControl->GetDescription()); + } + CGUIDialog::FrameMove(); +} diff --git a/xbmc/dialogs/GUIDialogButtonMenu.h b/xbmc/dialogs/GUIDialogButtonMenu.h new file mode 100644 index 0000000..60cb10c --- /dev/null +++ b/xbmc/dialogs/GUIDialogButtonMenu.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +class CGUIDialogButtonMenu : + public CGUIDialog +{ +public: + CGUIDialogButtonMenu(int id = WINDOW_DIALOG_BUTTON_MENU, const std::string &xmlFile = "DialogButtonMenu.xml"); + ~CGUIDialogButtonMenu(void) override; + bool OnMessage(CGUIMessage &message) override; + void FrameMove() override; +}; diff --git a/xbmc/dialogs/GUIDialogCache.cpp b/xbmc/dialogs/GUIDialogCache.cpp new file mode 100644 index 0000000..6ec8b20 --- /dev/null +++ b/xbmc/dialogs/GUIDialogCache.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogCache.h" + +#include "ServiceBroker.h" +#include "dialogs/GUIDialogProgress.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "messaging/ApplicationMessenger.h" +#include "threads/SystemClock.h" +#include "utils/Variant.h" +#include "utils/log.h" + +#include <mutex> + +using namespace std::chrono_literals; + +CGUIDialogCache::CGUIDialogCache(std::chrono::milliseconds delay, + const std::string& strHeader, + const std::string& strMsg) + : CThread("GUIDialogCache"), m_strHeader(strHeader), m_strLinePrev(strMsg) +{ + bSentCancel = false; + + m_pDlg = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS); + + if (!m_pDlg) + return; + + /* if progress dialog is already running, take it over */ + if( m_pDlg->IsDialogRunning() ) + delay = 0ms; + + if (delay == 0ms) + OpenDialog(); + else + m_endtime.Set(delay); + + Create(true); +} + +void CGUIDialogCache::Close(bool bForceClose) +{ + bSentCancel = true; + + // we cannot wait for the app thread to process the close message + // as this might happen during player startup which leads to a deadlock + if (m_pDlg && m_pDlg->IsDialogRunning()) + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_WINDOW_CLOSE, -1, bForceClose ? 1 : 0, + static_cast<void*>(m_pDlg)); + + //Set stop, this will kill this object, when thread stops + CThread::m_bStop = true; +} + +CGUIDialogCache::~CGUIDialogCache() +{ + if(m_pDlg && m_pDlg->IsDialogRunning()) + m_pDlg->Close(); +} + +void CGUIDialogCache::OpenDialog() +{ + if (m_pDlg) + { + if (m_strHeader.empty()) + m_pDlg->SetHeading(CVariant{438}); + else + m_pDlg->SetHeading(CVariant{m_strHeader}); + + m_pDlg->SetLine(2, CVariant{m_strLinePrev}); + m_pDlg->Open(); + } + bSentCancel = false; +} + +void CGUIDialogCache::SetHeader(const std::string& strHeader) +{ + m_strHeader = strHeader; +} + +void CGUIDialogCache::SetHeader(int nHeader) +{ + SetHeader(g_localizeStrings.Get(nHeader)); +} + +void CGUIDialogCache::SetMessage(const std::string& strMessage) +{ + if (m_pDlg) + { + m_pDlg->SetLine(0, CVariant{m_strLinePrev2}); + m_pDlg->SetLine(1, CVariant{m_strLinePrev}); + m_pDlg->SetLine(2, CVariant{strMessage}); + } + m_strLinePrev2 = m_strLinePrev; + m_strLinePrev = strMessage; +} + +bool CGUIDialogCache::OnFileCallback(void* pContext, int ipercent, float avgSpeed) +{ + if (m_pDlg) + { + m_pDlg->ShowProgressBar(true); + m_pDlg->SetPercentage(ipercent); + } + + if( IsCanceled() ) + return false; + else + return true; +} + +void CGUIDialogCache::Process() +{ + if (!m_pDlg) + return; + + while( true ) + { + + { //Section to make the lock go out of scope before sleep + + if( CThread::m_bStop ) break; + + try + { + std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext()); + m_pDlg->Progress(); + if( bSentCancel ) + { + CThread::Sleep(10ms); + continue; + } + + if(m_pDlg->IsCanceled()) + { + bSentCancel = true; + } + else if( !m_pDlg->IsDialogRunning() && m_endtime.IsTimePast() + && !CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_DIALOG_YES_NO) ) + OpenDialog(); + } + catch(...) + { + CLog::Log(LOGERROR, "Exception in CGUIDialogCache::Process()"); + } + } + + CThread::Sleep(10ms); + } +} + +void CGUIDialogCache::ShowProgressBar(bool bOnOff) +{ + if (m_pDlg) + m_pDlg->ShowProgressBar(bOnOff); +} + +void CGUIDialogCache::SetPercentage(int iPercentage) +{ + if (m_pDlg) + m_pDlg->SetPercentage(iPercentage); +} + +bool CGUIDialogCache::IsCanceled() const +{ + if (m_pDlg && m_pDlg->IsDialogRunning()) + return m_pDlg->IsCanceled(); + else + return false; +} diff --git a/xbmc/dialogs/GUIDialogCache.h b/xbmc/dialogs/GUIDialogCache.h new file mode 100644 index 0000000..3166cbe --- /dev/null +++ b/xbmc/dialogs/GUIDialogCache.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "filesystem/IFileTypes.h" +#include "threads/SystemClock.h" +#include "threads/Thread.h" + +#include <string> + +class CGUIDialogProgress; + +class CGUIDialogCache : public CThread, public XFILE::IFileCallback +{ +public: + CGUIDialogCache(std::chrono::milliseconds delay = std::chrono::milliseconds(100), + const std::string& strHeader = "", + const std::string& strMsg = ""); + ~CGUIDialogCache() override; + void SetHeader(const std::string& strHeader); + void SetHeader(int nHeader); + void SetMessage(const std::string& strMessage); + bool IsCanceled() const; + void ShowProgressBar(bool bOnOff); + void SetPercentage(int iPercentage); + + void Close(bool bForceClose = false); + + void Process() override; + bool OnFileCallback(void* pContext, int ipercent, float avgSpeed) override; + +protected: + + void OpenDialog(); + + XbmcThreads::EndTime<> m_endtime; + CGUIDialogProgress* m_pDlg; + std::string m_strHeader; + std::string m_strLinePrev; + std::string m_strLinePrev2; + bool bSentCancel; +}; diff --git a/xbmc/dialogs/GUIDialogColorPicker.cpp b/xbmc/dialogs/GUIDialogColorPicker.cpp new file mode 100644 index 0000000..77cee11 --- /dev/null +++ b/xbmc/dialogs/GUIDialogColorPicker.cpp @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogColorPicker.h" + +#include "FileItem.h" +#include "filesystem/SpecialProtocol.h" +#include "guilib/GUIColorManager.h" +#include "guilib/GUIMessage.h" +#include "guilib/LocalizeStrings.h" +#include "input/Key.h" +#include "utils/ColorUtils.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <algorithm> +#include <utility> +#include <vector> + +#define CONTROL_HEADING 1 +#define CONTROL_NUMBER_OF_ITEMS 2 +#define CONTROL_ICON_LIST 6 +#define CONTROL_CANCEL_BUTTON 7 + +CGUIDialogColorPicker::CGUIDialogColorPicker() + : CGUIDialogBoxBase(WINDOW_DIALOG_COLOR_PICKER, "DialogColorPicker.xml"), + m_vecList(new CFileItemList()), + m_focusToButton(false) +{ + m_bConfirmed = false; + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogColorPicker::~CGUIDialogColorPicker() +{ + delete m_vecList; +} + +bool CGUIDialogColorPicker::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_DEINIT: + { + CGUIDialogBoxBase::OnMessage(message); + m_selectedColor.clear(); + for (int i = 0; i < m_vecList->Size(); i++) + { + CFileItemPtr item = m_vecList->Get(i); + if (item->IsSelected()) + { + m_selectedColor = item->GetLabel2(); + break; + } + } + m_vecList->Clear(); + return true; + } + break; + + case GUI_MSG_WINDOW_INIT: + { + m_bConfirmed = false; + CGUIDialogBoxBase::OnMessage(message); + return true; + } + break; + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (m_viewControl.HasControl(CONTROL_ICON_LIST)) + { + int iAction = message.GetParam1(); + if (ACTION_SELECT_ITEM == iAction || ACTION_MOUSE_LEFT_CLICK == iAction) + { + int iSelected = m_viewControl.GetSelectedItem(); + if (iSelected >= 0 && iSelected < m_vecList->Size()) + { + CFileItemPtr item(m_vecList->Get(iSelected)); + for (int i = 0; i < m_vecList->Size(); i++) + m_vecList->Get(i)->Select(false); + item->Select(true); + OnSelect(iSelected); + } + } + } + if (iControl == CONTROL_CANCEL_BUTTON) + { + m_selectedColor.clear(); + m_vecList->Clear(); + m_bConfirmed = false; + Close(); + } + } + break; + + case GUI_MSG_SETFOCUS: + { + if (m_viewControl.HasControl(message.GetControlId())) + { + if (m_vecList->IsEmpty()) + { + SET_CONTROL_FOCUS(CONTROL_CANCEL_BUTTON, 0); + return true; + } + if (m_viewControl.GetCurrentControl() != message.GetControlId()) + { + m_viewControl.SetFocused(); + return true; + } + } + } + break; + } + + return CGUIDialogBoxBase::OnMessage(message); +} + +void CGUIDialogColorPicker::OnSelect(int idx) +{ + m_bConfirmed = true; + Close(); +} + +bool CGUIDialogColorPicker::OnBack(int actionID) +{ + m_vecList->Clear(); + m_bConfirmed = false; + return CGUIDialogBoxBase::OnBack(actionID); +} + +void CGUIDialogColorPicker::Reset() +{ + m_focusToButton = false; + m_selectedColor.clear(); + m_vecList->Clear(); +} + +void CGUIDialogColorPicker::AddItem(const CFileItem& item) +{ + m_vecList->Add(CFileItemPtr(new CFileItem(item))); +} + +void CGUIDialogColorPicker::SetItems(const CFileItemList& pList) +{ + // need to make internal copy of list to be sure dialog is owner of it + m_vecList->Clear(); + m_vecList->Copy(pList); +} + +void CGUIDialogColorPicker::LoadColors() +{ + LoadColors(CSpecialProtocol::TranslatePathConvertCase("special://xbmc/system/dialogcolors.xml")); +} + +void CGUIDialogColorPicker::LoadColors(const std::string& filePath) +{ + CGUIColorManager colorManager; + std::vector<std::pair<std::string, UTILS::COLOR::ColorInfo>> colors; + if (colorManager.LoadColorsListFromXML(filePath, colors, true)) + { + for (auto& color : colors) + { + CFileItem* item = new CFileItem(color.first); + item->SetLabel2(StringUtils::Format("{:08X}", color.second.colorARGB)); + m_vecList->Add(CFileItemPtr(item)); + } + } + else + CLog::Log(LOGERROR, "{} - An error occurred when loading colours from the xml file.", + __FUNCTION__); +} + +std::string CGUIDialogColorPicker::GetSelectedColor() const +{ + return m_selectedColor; +} + +int CGUIDialogColorPicker::GetSelectedItem() const +{ + if (m_selectedColor.empty()) + return -1; + for (int index = 0; index < m_vecList->Size(); index++) + { + if (StringUtils::CompareNoCase(m_selectedColor, m_vecList->Get(index)->GetLabel2()) == 0) + { + return index; + } + } + return -1; +} + +void CGUIDialogColorPicker::SetSelectedColor(const std::string& colorHex) +{ + m_selectedColor = colorHex; +} + +void CGUIDialogColorPicker::SetButtonFocus(bool buttonFocus) +{ + m_focusToButton = buttonFocus; +} + +CGUIControl* CGUIDialogColorPicker::GetFirstFocusableControl(int id) +{ + if (m_viewControl.HasControl(id)) + id = m_viewControl.GetCurrentControl(); + return CGUIDialogBoxBase::GetFirstFocusableControl(id); +} + +void CGUIDialogColorPicker::OnWindowLoaded() +{ + CGUIDialogBoxBase::OnWindowLoaded(); + m_viewControl.Reset(); + m_viewControl.SetParentWindow(GetID()); + m_viewControl.AddView(GetControl(CONTROL_ICON_LIST)); +} + +void CGUIDialogColorPicker::OnInitWindow() +{ + if (!m_vecList || m_vecList->IsEmpty()) + CLog::Log(LOGERROR, "{} - No colours have been added in the list.", __FUNCTION__); + + m_viewControl.SetItems(*m_vecList); + m_viewControl.SetCurrentView(CONTROL_ICON_LIST); + + SET_CONTROL_LABEL(CONTROL_NUMBER_OF_ITEMS, + StringUtils::Format("{} {}", m_vecList->Size(), g_localizeStrings.Get(127))); + + SET_CONTROL_LABEL(CONTROL_CANCEL_BUTTON, g_localizeStrings.Get(222)); + + CGUIDialogBoxBase::OnInitWindow(); + + // focus one of the buttons if explicitly requested + // ATTENTION: this must be done after calling CGUIDialogBoxBase::OnInitWindow() + if (m_focusToButton) + { + SET_CONTROL_FOCUS(CONTROL_CANCEL_BUTTON, 0); + } + + // if nothing is selected, select first item + m_viewControl.SetSelectedItem(std::max(GetSelectedItem(), 0)); +} + +void CGUIDialogColorPicker::OnDeinitWindow(int nextWindowID) +{ + m_viewControl.Clear(); + CGUIDialogBoxBase::OnDeinitWindow(nextWindowID); +} + +void CGUIDialogColorPicker::OnWindowUnload() +{ + CGUIDialogBoxBase::OnWindowUnload(); + m_viewControl.Reset(); +} diff --git a/xbmc/dialogs/GUIDialogColorPicker.h b/xbmc/dialogs/GUIDialogColorPicker.h new file mode 100644 index 0000000..1e7c65d --- /dev/null +++ b/xbmc/dialogs/GUIDialogColorPicker.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "GUIDialogBoxBase.h" +#include "view/GUIViewControl.h" + +#include <string> + +class CFileItem; +class CFileItemList; + +class CGUIDialogColorPicker : public CGUIDialogBoxBase +{ +public: + CGUIDialogColorPicker(); + ~CGUIDialogColorPicker() override; + bool OnMessage(CGUIMessage& message) override; + bool OnBack(int actionID) override; + void Reset(); + /*! \brief Add a color item (Label property must be the color name, Label2 property must be the color hex) + \param item The CFileItem + */ + void AddItem(const CFileItem& item); + /*! \brief Set a list of color items (Label property must be the color name, Label2 property must be the color hex) + \param pList The CFileItemList + */ + void SetItems(const CFileItemList& pList); + /*! \brief Load a list of colors from the default xml */ + void LoadColors(); + /*! \brief Load a list of colors from the specified xml file path + \param filePath The xml file path + */ + void LoadColors(const std::string& filePath); + /*! \brief Get the hex value of the selected color */ + std::string GetSelectedColor() const; + /*! \brief Set the selected color by hex value */ + void SetSelectedColor(const std::string& hexColor); + /*! \brief Set the focus to the control button */ + void SetButtonFocus(bool buttonFocus); + +protected: + CGUIControl* GetFirstFocusableControl(int id) override; + void OnWindowLoaded() override; + void OnInitWindow() override; + void OnDeinitWindow(int nextWindowID) override; + void OnWindowUnload() override; + + virtual void OnSelect(int idx); + +private: + int GetSelectedItem() const; + + CGUIViewControl m_viewControl; + CFileItemList* m_vecList; + bool m_focusToButton; + std::string m_selectedColor; +}; diff --git a/xbmc/dialogs/GUIDialogContextMenu.cpp b/xbmc/dialogs/GUIDialogContextMenu.cpp new file mode 100644 index 0000000..dcc9ede --- /dev/null +++ b/xbmc/dialogs/GUIDialogContextMenu.cpp @@ -0,0 +1,705 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogContextMenu.h" + +#include "FileItem.h" +#include "GUIDialogFileBrowser.h" +#include "GUIDialogMediaSource.h" +#include "GUIDialogYesNo.h" +#include "GUIPassword.h" +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "TextureDatabase.h" +#include "URL.h" +#include "Util.h" +#include "addons/Scraper.h" +#include "favourites/FavouritesService.h" +#include "guilib/GUIButtonControl.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIControlGroupList.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/Key.h" +#include "media/MediaLockState.h" +#include "profiles/ProfileManager.h" +#include "profiles/dialogs/GUIDialogLockSettings.h" +#include "settings/MediaSourceSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "storage/MediaManager.h" +#include "utils/FileUtils.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" + +#define BACKGROUND_IMAGE 999 +#define GROUP_LIST 996 +#define BUTTON_TEMPLATE 1000 +#define BUTTON_START 1001 +#define BUTTON_END (BUTTON_START + (int)m_buttons.size() - 1) + +void CContextButtons::Add(unsigned int button, const std::string &label) +{ + for (const auto& i : *this) + if (i.first == button) + return; // already have this button + push_back(std::pair<unsigned int, std::string>(button, label)); +} + +void CContextButtons::Add(unsigned int button, int label) +{ + for (const auto& i : *this) + if (i.first == button) + return; // already have added this button + push_back(std::pair<unsigned int, std::string>(button, g_localizeStrings.Get(label))); +} + +CGUIDialogContextMenu::CGUIDialogContextMenu(void) + : CGUIDialog(WINDOW_DIALOG_CONTEXT_MENU, "DialogContextMenu.xml") +{ + m_clickedButton = -1; + m_backgroundImageSize = 0; + m_loadType = KEEP_IN_MEMORY; + m_coordX = 0.0f; + m_coordY = 0.0f; +} + +CGUIDialogContextMenu::~CGUIDialogContextMenu(void) = default; + +bool CGUIDialogContextMenu::OnMessage(CGUIMessage &message) +{ + if (message.GetMessage() == GUI_MSG_CLICKED) + { // someone has been clicked - deinit... + if (message.GetSenderId() >= BUTTON_START && message.GetSenderId() <= BUTTON_END) + m_clickedButton = message.GetSenderId() - BUTTON_START; + Close(); + return true; + } + else if (message.GetMessage() == GUI_MSG_PLAYBACK_AVSTARTED) + { + // playback was just started from elsewhere - close the dialog + Close(); + return true; + } + return CGUIDialog::OnMessage(message); +} + +bool CGUIDialogContextMenu::OnAction(const CAction& action) +{ + if (action.GetID() == ACTION_CONTEXT_MENU || + action.GetID() == ACTION_SWITCH_PLAYER) + { + Close(); + return true; + } + + return CGUIDialog::OnAction(action); +} + +void CGUIDialogContextMenu::OnInitWindow() +{ + m_clickedButton = -1; + // set initial control focus + m_lastControlID = m_initiallyFocusedButtonIdx + BUTTON_START; + CGUIDialog::OnInitWindow(); +} + +void CGUIDialogContextMenu::SetupButtons() +{ + if (!m_buttons.size()) + return; + + // disable the template button control + CGUIButtonControl *pButtonTemplate = dynamic_cast<CGUIButtonControl *>(GetFirstFocusableControl(BUTTON_TEMPLATE)); + if (!pButtonTemplate) + pButtonTemplate = dynamic_cast<CGUIButtonControl *>(GetControl(BUTTON_TEMPLATE)); + if (!pButtonTemplate) + return; + pButtonTemplate->SetVisible(false); + + CGUIControlGroupList* pGroupList = dynamic_cast<CGUIControlGroupList *>(GetControl(GROUP_LIST)); + + // add our buttons + if (pGroupList) + { + for (unsigned int i = 0; i < m_buttons.size(); i++) + { + CGUIButtonControl* pButton = new CGUIButtonControl(*pButtonTemplate); + if (pButton) + { // set the button's ID and position + int id = BUTTON_START + i; + pButton->SetID(id); + pButton->SetVisible(true); + pButton->SetLabel(m_buttons[i].second); + pButton->SetPosition(pButtonTemplate->GetXPosition(), pButtonTemplate->GetYPosition()); + // try inserting context buttons at position specified by template + // button, if template button is not in grouplist fallback to adding + // new buttons at the end of grouplist + if (!pGroupList->InsertControl(pButton, pButtonTemplate)) + pGroupList->AddControl(pButton); + } + } + } + + // fix up background images placement and size + CGUIControl *pControl = GetControl(BACKGROUND_IMAGE); + if (pControl) + { + // first set size of background image + if (pGroupList) + { + if (pGroupList->GetOrientation() == VERTICAL) + { + // keep gap between bottom edges of grouplist and background image + pControl->SetHeight(m_backgroundImageSize - pGroupList->Size() + pGroupList->GetHeight()); + } + else + { + // keep gap between right edges of grouplist and background image + pControl->SetWidth(m_backgroundImageSize - pGroupList->Size() + pGroupList->GetWidth()); + } + } + } + + // update our default control + if (pGroupList) + m_defaultControl = pGroupList->GetID(); +} + +void CGUIDialogContextMenu::SetPosition(float posX, float posY) +{ + if (posY + GetHeight() > m_coordsRes.iHeight) + posY = m_coordsRes.iHeight - GetHeight(); + if (posY < 0) posY = 0; + if (posX + GetWidth() > m_coordsRes.iWidth) + posX = m_coordsRes.iWidth - GetWidth(); + if (posX < 0) posX = 0; + CGUIDialog::SetPosition(posX, posY); +} + +float CGUIDialogContextMenu::GetHeight() const +{ + if (m_backgroundImage) + return m_backgroundImage->GetHeight(); + else + return CGUIDialog::GetHeight(); +} + +float CGUIDialogContextMenu::GetWidth() const +{ + if (m_backgroundImage) + return m_backgroundImage->GetWidth(); + else + return CGUIDialog::GetWidth(); +} + +bool CGUIDialogContextMenu::SourcesMenu(const std::string &strType, const CFileItemPtr& item, float posX, float posY) +{ + //! @todo This should be callable even if we don't have any valid items + if (!item) + return false; + + // grab our context menu + CContextButtons buttons; + GetContextButtons(strType, item, buttons); + + int button = ShowAndGetChoice(buttons); + if (button >= 0) + return OnContextButton(strType, item, (CONTEXT_BUTTON)button); + return false; +} + +void CGUIDialogContextMenu::GetContextButtons(const std::string &type, const CFileItemPtr& item, CContextButtons &buttons) +{ + // Add buttons to the ContextMenu that should be visible for both sources and autosourced items + // Optical removable drives automatically have the static Eject button added (see CEjectDisk). + // Here we only add the eject button to HDD drives + if (item && item->IsRemovable() && !item->IsDVD() && !item->IsCDDA()) + { + buttons.Add(CONTEXT_BUTTON_EJECT_DRIVE, 13420); // Remove safely + } + + // Next, Add buttons to the ContextMenu that should ONLY be visible for sources and not autosourced items + CMediaSource *share = GetShare(type, item.get()); + + if (CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetCurrentProfile().canWriteSources() || g_passwordManager.bMasterUser) + { + if (share) + { + // Note. from now on, remove source & disable plugin should mean the same thing + //! @todo might be smart to also combine editing source & plugin settings into one concept/dialog + // Note. Temporarily disabled ability to remove plugin sources until installer is operational + + CURL url(share->strPath); + bool isAddon = ADDON::TranslateContent(url.GetProtocol()) != CONTENT_NONE; + if (!share->m_ignore && !isAddon) + buttons.Add(CONTEXT_BUTTON_EDIT_SOURCE, 1027); // Edit Source + if (type != "video") + buttons.Add(CONTEXT_BUTTON_SET_DEFAULT, 13335); // Set as Default + if (!share->m_ignore && !isAddon) + buttons.Add(CONTEXT_BUTTON_REMOVE_SOURCE, 522); // Remove Source + + buttons.Add(CONTEXT_BUTTON_SET_THUMB, 20019); + } + if (!GetDefaultShareNameByType(type).empty()) + buttons.Add(CONTEXT_BUTTON_CLEAR_DEFAULT, 13403); // Clear Default + } + if (share && LOCK_MODE_EVERYONE != CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetMasterProfile().getLockMode()) + { + if (share->m_iHasLock == LOCK_STATE_NO_LOCK && (CServiceBroker::GetSettingsComponent() + ->GetProfileManager() + ->GetCurrentProfile() + .canWriteSources() || + g_passwordManager.bMasterUser)) + buttons.Add(CONTEXT_BUTTON_ADD_LOCK, 12332); + else if (share->m_iHasLock == LOCK_STATE_LOCK_BUT_UNLOCKED) + buttons.Add(CONTEXT_BUTTON_REMOVE_LOCK, 12335); + else if (share->m_iHasLock == LOCK_STATE_LOCKED) + { + buttons.Add(CONTEXT_BUTTON_REMOVE_LOCK, 12335); + + bool maxRetryExceeded = false; + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MASTERLOCK_MAXRETRIES) != 0) + maxRetryExceeded = (share->m_iBadPwdCount >= CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MASTERLOCK_MAXRETRIES)); + + if (maxRetryExceeded) + buttons.Add(CONTEXT_BUTTON_RESET_LOCK, 12334); + else + buttons.Add(CONTEXT_BUTTON_CHANGE_LOCK, 12356); + } + } + if (share && !g_passwordManager.bMasterUser && item->m_iHasLock == LOCK_STATE_LOCK_BUT_UNLOCKED) + buttons.Add(CONTEXT_BUTTON_REACTIVATE_LOCK, 12353); +} + +bool CGUIDialogContextMenu::OnContextButton(const std::string &type, const CFileItemPtr& item, CONTEXT_BUTTON button) +{ + // buttons that are available on both sources and autosourced items + if (!item) + return false; + + switch (button) + { + case CONTEXT_BUTTON_EJECT_DRIVE: + return CServiceBroker::GetMediaManager().Eject(item->GetPath()); + default: + break; + } + + // the rest of the operations require a valid share + CMediaSource *share = GetShare(type, item.get()); + if (!share) + return false; + + switch (button) + { + case CONTEXT_BUTTON_EDIT_SOURCE: + if (CServiceBroker::GetSettingsComponent()->GetProfileManager()->IsMasterProfile()) + { + if (!g_passwordManager.IsMasterLockUnlocked(true)) + return false; + } + else if (!g_passwordManager.IsProfileLockUnlocked()) + return false; + + return CGUIDialogMediaSource::ShowAndEditMediaSource(type, *share); + + case CONTEXT_BUTTON_REMOVE_SOURCE: + { + if (CServiceBroker::GetSettingsComponent()->GetProfileManager()->IsMasterProfile()) + { + if (!g_passwordManager.IsMasterLockUnlocked(true)) + return false; + } + else + { + if (!CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetCurrentProfile().canWriteSources() && !g_passwordManager.IsMasterLockUnlocked(false)) + return false; + if (CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetCurrentProfile().canWriteSources() && !g_passwordManager.IsProfileLockUnlocked()) + return false; + } + // prompt user if they want to really delete the source + if (!CGUIDialogYesNo::ShowAndGetInput(CVariant{751}, CVariant{750})) + return false; + + // check default before we delete, as deletion will kill the share object + std::string defaultSource(GetDefaultShareNameByType(type)); + if (!defaultSource.empty()) + { + if (share->strName == defaultSource) + ClearDefault(type); + } + CMediaSourceSettings::GetInstance().DeleteSource(type, share->strName, share->strPath); + return true; + } + case CONTEXT_BUTTON_SET_DEFAULT: + if (CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetCurrentProfile().canWriteSources() && !g_passwordManager.IsProfileLockUnlocked()) + return false; + else if (!g_passwordManager.IsMasterLockUnlocked(true)) + return false; + + // make share default + SetDefault(type, share->strName); + return true; + + case CONTEXT_BUTTON_CLEAR_DEFAULT: + if (CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetCurrentProfile().canWriteSources() && !g_passwordManager.IsProfileLockUnlocked()) + return false; + else if (!g_passwordManager.IsMasterLockUnlocked(true)) + return false; + // remove share default + ClearDefault(type); + return true; + + case CONTEXT_BUTTON_SET_THUMB: + { + if (CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetCurrentProfile().canWriteSources() && !g_passwordManager.IsProfileLockUnlocked()) + return false; + else if (!g_passwordManager.IsMasterLockUnlocked(true)) + return false; + + // setup our thumb list + CFileItemList items; + + // add the current thumb, if available + if (!share->m_strThumbnailImage.empty()) + { + CFileItemPtr current(new CFileItem("thumb://Current", false)); + current->SetArt("thumb", share->m_strThumbnailImage); + current->SetLabel(g_localizeStrings.Get(20016)); + items.Add(current); + } + else if (item->HasArt("thumb")) + { // already have a thumb that the share doesn't know about - must be a local one, so we mayaswell reuse it. + CFileItemPtr current(new CFileItem("thumb://Current", false)); + current->SetArt("thumb", item->GetArt("thumb")); + current->SetLabel(g_localizeStrings.Get(20016)); + items.Add(current); + } + // see if there's a local thumb for this item + std::string folderThumb = item->GetFolderThumb(); + if (CFileUtils::Exists(folderThumb)) + { + CFileItemPtr local(new CFileItem("thumb://Local", false)); + local->SetArt("thumb", folderThumb); + local->SetLabel(g_localizeStrings.Get(20017)); + items.Add(local); + } + // and add a "no thumb" entry as well + CFileItemPtr nothumb(new CFileItem("thumb://None", false)); + nothumb->SetArt("icon", item->GetArt("icon")); + nothumb->SetLabel(g_localizeStrings.Get(20018)); + items.Add(nothumb); + + std::string strThumb; + VECSOURCES shares; + CServiceBroker::GetMediaManager().GetLocalDrives(shares); + if (!CGUIDialogFileBrowser::ShowAndGetImage(items, shares, g_localizeStrings.Get(1030), strThumb)) + return false; + + if (strThumb == "thumb://Current") + return true; + + if (strThumb == "thumb://Local") + strThumb = folderThumb; + + if (strThumb == "thumb://None") + strThumb = ""; + + if (!share->m_ignore) + { + CMediaSourceSettings::GetInstance().UpdateSource(type,share->strName,"thumbnail",strThumb); + CMediaSourceSettings::GetInstance().Save(); + } + else if (!strThumb.empty()) + { // this is some sort of an auto-share, so store in the texture database + CTextureDatabase db; + if (db.Open()) + db.SetTextureForPath(item->GetPath(), "thumb", strThumb); + } + + CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_UPDATE_SOURCES); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + return true; + } + + case CONTEXT_BUTTON_ADD_LOCK: + { + // prompt user for mastercode when changing lock settings) only for default user + if (!g_passwordManager.IsMasterLockUnlocked(true)) + return false; + + std::string strNewPassword = ""; + if (!CGUIDialogLockSettings::ShowAndGetLock(share->m_iLockMode,strNewPassword)) + return false; + // password entry and re-entry succeeded, write out the lock data + share->m_iHasLock = LOCK_STATE_LOCKED; + CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "lockcode", strNewPassword); + strNewPassword = std::to_string(share->m_iLockMode); + CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "lockmode", strNewPassword); + CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "badpwdcount", "0"); + CMediaSourceSettings::GetInstance().Save(); + + // lock of a mediasource has been added + // => refresh favourites due to possible visibility changes + CServiceBroker::GetFavouritesService().RefreshFavourites(); + + CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_UPDATE_SOURCES); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + return true; + } + case CONTEXT_BUTTON_RESET_LOCK: + { + // prompt user for profile lock when changing lock settings + if (!g_passwordManager.IsMasterLockUnlocked(true)) + return false; + + CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "badpwdcount", "0"); + CMediaSourceSettings::GetInstance().Save(); + CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_UPDATE_SOURCES); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + return true; + } + case CONTEXT_BUTTON_REMOVE_LOCK: + { + if (!g_passwordManager.IsMasterLockUnlocked(true)) + return false; + + // prompt user if they want to really remove the lock + if (!CGUIDialogYesNo::ShowAndGetInput(CVariant{12335}, CVariant{750})) + return false; + + share->m_iHasLock = LOCK_STATE_NO_LOCK; + CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "lockmode", "0"); + CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "lockcode", "0"); + CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "badpwdcount", "0"); + CMediaSourceSettings::GetInstance().Save(); + + // lock of a mediasource has been removed + // => refresh favourites due to possible visibility changes + CServiceBroker::GetFavouritesService().RefreshFavourites(); + + CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_UPDATE_SOURCES); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + return true; + } + case CONTEXT_BUTTON_REACTIVATE_LOCK: + { + bool maxRetryExceeded = false; + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MASTERLOCK_MAXRETRIES) != 0) + maxRetryExceeded = (share->m_iBadPwdCount >= CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MASTERLOCK_MAXRETRIES)); + if (!maxRetryExceeded) + { + // don't prompt user for mastercode when reactivating a lock + g_passwordManager.LockSource(type, share->strName, true); + + // lock of a mediasource has been reactivated + // => refresh favourites due to possible visibility changes + CServiceBroker::GetFavouritesService().RefreshFavourites(); + return true; + } + return false; + } + case CONTEXT_BUTTON_CHANGE_LOCK: + { + if (!g_passwordManager.IsMasterLockUnlocked(true)) + return false; + + std::string strNewPW; + std::string strNewLockMode; + if (CGUIDialogLockSettings::ShowAndGetLock(share->m_iLockMode,strNewPW)) + strNewLockMode = std::to_string(share->m_iLockMode); + else + return false; + // password ReSet and re-entry succeeded, write out the lock data + CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "lockcode", strNewPW); + CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "lockmode", strNewLockMode); + CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "badpwdcount", "0"); + CMediaSourceSettings::GetInstance().Save(); + + // lock of a mediasource has been changed + // => refresh favourites due to possible visibility changes + CServiceBroker::GetFavouritesService().RefreshFavourites(); + + CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_UPDATE_SOURCES); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + return true; + } + default: + break; + } + return false; +} + +CMediaSource *CGUIDialogContextMenu::GetShare(const std::string &type, const CFileItem *item) +{ + VECSOURCES *shares = CMediaSourceSettings::GetInstance().GetSources(type); + if (!shares || !item) + return nullptr; + for (unsigned int i = 0; i < shares->size(); i++) + { + CMediaSource &testShare = shares->at(i); + if (URIUtils::IsDVD(testShare.strPath)) + { + if (!item->IsDVD()) + continue; + } + else + { + if (!URIUtils::CompareWithoutSlashAtEnd(testShare.strPath, item->GetPath())) + continue; + } + // paths match, what about share name - only match the leftmost + // characters as the label may contain other info (status for instance) + if (StringUtils::StartsWithNoCase(item->GetLabel(), testShare.strName)) + { + return &testShare; + } + } + return nullptr; +} + +void CGUIDialogContextMenu::OnWindowLoaded() +{ + m_coordX = m_posX; + m_coordY = m_posY; + + const CGUIControlGroupList* pGroupList = dynamic_cast<const CGUIControlGroupList *>(GetControl(GROUP_LIST)); + m_backgroundImage = GetControl(BACKGROUND_IMAGE); + if (m_backgroundImage && pGroupList) + { + if (pGroupList->GetOrientation() == VERTICAL) + m_backgroundImageSize = m_backgroundImage->GetHeight(); + else + m_backgroundImageSize = m_backgroundImage->GetWidth(); + } + + CGUIDialog::OnWindowLoaded(); +} + +void CGUIDialogContextMenu::OnDeinitWindow(int nextWindowID) +{ + //we can't be sure that controls are removed on window unload + //we have to remove them to be sure that they won't stay for next use of context menu + for (unsigned int i = 0; i < m_buttons.size(); i++) + { + const CGUIControl *control = GetControl(BUTTON_START + i); + if (control) + { + RemoveControl(control); + delete control; + } + } + + m_buttons.clear(); + m_initiallyFocusedButtonIdx = 0; + CGUIDialog::OnDeinitWindow(nextWindowID); +} + +std::string CGUIDialogContextMenu::GetDefaultShareNameByType(const std::string &strType) +{ + VECSOURCES *pShares = CMediaSourceSettings::GetInstance().GetSources(strType); + std::string strDefault = CMediaSourceSettings::GetInstance().GetDefaultSource(strType); + + if (!pShares) return ""; + + bool bIsSourceName(false); + int iIndex = CUtil::GetMatchingSource(strDefault, *pShares, bIsSourceName); + if (iIndex < 0 || iIndex >= (int)pShares->size()) + return ""; + + return pShares->at(iIndex).strName; +} + +void CGUIDialogContextMenu::SetDefault(const std::string &strType, const std::string &strDefault) +{ + CMediaSourceSettings::GetInstance().SetDefaultSource(strType, strDefault); + CMediaSourceSettings::GetInstance().Save(); +} + +void CGUIDialogContextMenu::ClearDefault(const std::string &strType) +{ + SetDefault(strType, ""); +} + +void CGUIDialogContextMenu::SwitchMedia(const std::string& strType, const std::string& strPath) +{ + // create menu + CContextButtons choices; + if (strType != "music") + choices.Add(WINDOW_MUSIC_NAV, 2); + if (strType != "video") + choices.Add(WINDOW_VIDEO_NAV, 3); + if (strType != "pictures") + choices.Add(WINDOW_PICTURES, 1); + if (strType != "files") + choices.Add(WINDOW_FILES, 7); + + int window = ShowAndGetChoice(choices); + if (window >= 0) + { + CUtil::DeleteDirectoryCache(); + CServiceBroker::GetGUI()->GetWindowManager().ChangeActiveWindow(window, strPath); + } +} + +int CGUIDialogContextMenu::Show(const CContextButtons& choices, int focusedButtonIdx /* = 0 */) +{ + auto dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogContextMenu>(WINDOW_DIALOG_CONTEXT_MENU); + if (!dialog) + return -1; + + dialog->m_buttons = choices; + dialog->Initialize(); + dialog->SetInitialVisibility(); + dialog->SetupButtons(); + dialog->PositionAtCurrentFocus(); + dialog->m_initiallyFocusedButtonIdx = focusedButtonIdx; + dialog->Open(); + return dialog->m_clickedButton; +} + +int CGUIDialogContextMenu::ShowAndGetChoice(const CContextButtons &choices) +{ + if (choices.empty()) + return -1; + + CGUIDialogContextMenu *pMenu = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogContextMenu>(WINDOW_DIALOG_CONTEXT_MENU); + if (pMenu) + { + pMenu->m_buttons = choices; + pMenu->Initialize(); + pMenu->SetInitialVisibility(); + pMenu->SetupButtons(); + pMenu->PositionAtCurrentFocus(); + pMenu->Open(); + + int idx = pMenu->m_clickedButton; + if (idx != -1) + return choices[idx].first; + } + return -1; +} + +void CGUIDialogContextMenu::PositionAtCurrentFocus() +{ + CGUIWindow *window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog()); + if (window) + { + const CGUIControl *focusedControl = window->GetFocusedControl(); + if (focusedControl) + { + CPoint pos = focusedControl->GetRenderPosition() + CPoint(focusedControl->GetWidth() * 0.5f, focusedControl->GetHeight() * 0.5f); + SetPosition(m_coordX + pos.x - GetWidth() * 0.5f, m_coordY + pos.y - GetHeight() * 0.5f); + return; + } + } + // no control to center at, so just center the window + CenterWindow(); +} diff --git a/xbmc/dialogs/GUIDialogContextMenu.h b/xbmc/dialogs/GUIDialogContextMenu.h new file mode 100644 index 0000000..420e458 --- /dev/null +++ b/xbmc/dialogs/GUIDialogContextMenu.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +#include <string> +#include <utility> +#include <vector> + + +class CMediaSource; + +enum CONTEXT_BUTTON { CONTEXT_BUTTON_CANCELLED = 0, + CONTEXT_BUTTON_RENAME, + CONTEXT_BUTTON_DELETE, + CONTEXT_BUTTON_MOVE, + CONTEXT_BUTTON_SETTINGS, + CONTEXT_BUTTON_RIP_CD, + CONTEXT_BUTTON_CANCEL_RIP_CD, + CONTEXT_BUTTON_RIP_TRACK, + CONTEXT_BUTTON_EJECT_DRIVE, + CONTEXT_BUTTON_EDIT_SOURCE, + CONTEXT_BUTTON_REMOVE_SOURCE, + CONTEXT_BUTTON_SET_DEFAULT, + CONTEXT_BUTTON_CLEAR_DEFAULT, + CONTEXT_BUTTON_SET_THUMB, + CONTEXT_BUTTON_ADD_LOCK, + CONTEXT_BUTTON_REMOVE_LOCK, + CONTEXT_BUTTON_CHANGE_LOCK, + CONTEXT_BUTTON_RESET_LOCK, + CONTEXT_BUTTON_REACTIVATE_LOCK, + CONTEXT_BUTTON_VIEW_SLIDESHOW, + CONTEXT_BUTTON_RECURSIVE_SLIDESHOW, + CONTEXT_BUTTON_REFRESH_THUMBS, + CONTEXT_BUTTON_SWITCH_MEDIA, + CONTEXT_BUTTON_MOVE_ITEM, + CONTEXT_BUTTON_MOVE_HERE, + CONTEXT_BUTTON_CANCEL_MOVE, + CONTEXT_BUTTON_MOVE_ITEM_UP, + CONTEXT_BUTTON_MOVE_ITEM_DOWN, + CONTEXT_BUTTON_CLEAR, + CONTEXT_BUTTON_QUEUE_ITEM, + CONTEXT_BUTTON_PLAY_ITEM, + CONTEXT_BUTTON_PLAY_WITH, + CONTEXT_BUTTON_PLAY_PARTYMODE, + CONTEXT_BUTTON_PLAY_PART, + CONTEXT_BUTTON_RESUME_ITEM, + CONTEXT_BUTTON_EDIT, + CONTEXT_BUTTON_EDIT_SMART_PLAYLIST, + CONTEXT_BUTTON_INFO, + CONTEXT_BUTTON_INFO_ALL, + CONTEXT_BUTTON_CDDB, + CONTEXT_BUTTON_SCAN, + CONTEXT_BUTTON_SCAN_TO_LIBRARY, + CONTEXT_BUTTON_SET_ARTIST_THUMB, + CONTEXT_BUTTON_SET_SEASON_ART, + CONTEXT_BUTTON_CANCEL_PARTYMODE, + CONTEXT_BUTTON_MARK_WATCHED, + CONTEXT_BUTTON_MARK_UNWATCHED, + CONTEXT_BUTTON_SET_CONTENT, + CONTEXT_BUTTON_EDIT_PARTYMODE, + CONTEXT_BUTTON_LINK_MOVIE, + CONTEXT_BUTTON_UNLINK_MOVIE, + CONTEXT_BUTTON_GO_TO_ARTIST, + CONTEXT_BUTTON_GO_TO_ALBUM, + CONTEXT_BUTTON_PLAY_OTHER, + CONTEXT_BUTTON_SET_ACTOR_THUMB, + CONTEXT_BUTTON_UNLINK_BOOKMARK, + CONTEXT_BUTTON_ACTIVATE, + CONTEXT_BUTTON_GROUP_MANAGER, + CONTEXT_BUTTON_CHANNEL_MANAGER, + CONTEXT_BUTTON_SET_MOVIESET_ART, + CONTEXT_BUTTON_PLAY_AND_QUEUE, + CONTEXT_BUTTON_PLAY_ONLY_THIS, + CONTEXT_BUTTON_UPDATE_EPG, + CONTEXT_BUTTON_TAGS_ADD_ITEMS, + CONTEXT_BUTTON_TAGS_REMOVE_ITEMS, + CONTEXT_BUTTON_SET_MOVIESET, + CONTEXT_BUTTON_MOVIESET_ADD_REMOVE_ITEMS, + CONTEXT_BUTTON_BROWSE_INTO, + CONTEXT_BUTTON_EDIT_SORTTITLE, + CONTEXT_BUTTON_DELETE_ALL, + CONTEXT_BUTTON_HELP, + CONTEXT_BUTTON_PLAY_NEXT, + CONTEXT_BUTTON_NAVIGATE, + }; + +class CContextButtons : public std::vector< std::pair<size_t, std::string> > +{ +public: + void Add(unsigned int, const std::string &label); + void Add(unsigned int, int label); +}; + +class CGUIDialogContextMenu : + public CGUIDialog +{ +public: + CGUIDialogContextMenu(void); + ~CGUIDialogContextMenu(void) override; + bool OnMessage(CGUIMessage &message) override; + bool OnAction(const CAction& action) override; + void SetPosition(float posX, float posY) override; + + static bool SourcesMenu(const std::string &strType, const CFileItemPtr& item, float posX, float posY); + static void SwitchMedia(const std::string& strType, const std::string& strPath); + + static void GetContextButtons(const std::string &type, const CFileItemPtr& item, CContextButtons &buttons); + static bool OnContextButton(const std::string &type, const CFileItemPtr& item, CONTEXT_BUTTON button); + + /*! Show the context menu with the given choices and return the index of the selected item, + or -1 if cancelled. + */ + static int Show(const CContextButtons& choices, int focusedButton = 0); + + /*! Legacy method that returns the context menu id, or -1 on cancel */ + static int ShowAndGetChoice(const CContextButtons &choices); + +protected: + void SetupButtons(); + + /*! \brief Position the context menu in the middle of the focused control. + If no control is available it is positioned in the middle of the screen. + */ + void PositionAtCurrentFocus(); + + float GetWidth() const override; + float GetHeight() const override; + void OnInitWindow() override; + void OnWindowLoaded() override; + void OnDeinitWindow(int nextWindowID) override; + static std::string GetDefaultShareNameByType(const std::string &strType); + static void SetDefault(const std::string &strType, const std::string &strDefault); + static void ClearDefault(const std::string &strType); + static CMediaSource *GetShare(const std::string &type, const CFileItem *item); + +private: + float m_coordX, m_coordY; + /// \brief Stored size of background image (height or width depending on grouplist orientation) + float m_backgroundImageSize; + int m_initiallyFocusedButtonIdx = 0; + int m_clickedButton; + CContextButtons m_buttons; + const CGUIControl *m_backgroundImage = nullptr; +}; diff --git a/xbmc/dialogs/GUIDialogExtendedProgressBar.cpp b/xbmc/dialogs/GUIDialogExtendedProgressBar.cpp new file mode 100644 index 0000000..9fc1c02 --- /dev/null +++ b/xbmc/dialogs/GUIDialogExtendedProgressBar.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogExtendedProgressBar.h" + +#include "guilib/GUIMessage.h" +#include "guilib/GUIProgressControl.h" +#include "utils/TimeUtils.h" + +#include <cmath> +#include <mutex> + +#define CONTROL_LABELHEADER 30 +#define CONTROL_LABELTITLE 31 +#define CONTROL_PROGRESS 32 + +#define ITEM_SWITCH_TIME_MS 2000 + +std::string CGUIDialogProgressBarHandle::Text(void) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + std::string retVal(m_strText); + return retVal; +} + +void CGUIDialogProgressBarHandle::SetText(const std::string &strText) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strText = strText; +} + +void CGUIDialogProgressBarHandle::SetTitle(const std::string &strTitle) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strTitle = strTitle; +} + +void CGUIDialogProgressBarHandle::SetProgress(int currentItem, int itemCount) +{ + float fPercentage = (currentItem*100.0f)/itemCount; + if (!std::isnan(fPercentage)) + m_fPercentage = std::min(100.0f, fPercentage); +} + +CGUIDialogExtendedProgressBar::CGUIDialogExtendedProgressBar(void) + : CGUIDialog(WINDOW_DIALOG_EXT_PROGRESS, "DialogExtendedProgressBar.xml", DialogModalityType::MODELESS) +{ + m_loadType = LOAD_ON_GUI_INIT; + m_iLastSwitchTime = 0; + m_iCurrentItem = 0; +} + +CGUIDialogProgressBarHandle *CGUIDialogExtendedProgressBar::GetHandle(const std::string &strTitle) +{ + CGUIDialogProgressBarHandle *handle = new CGUIDialogProgressBarHandle(strTitle); + { + std::unique_lock<CCriticalSection> lock(m_critSection); + m_handles.push_back(handle); + } + + Open(); + + return handle; +} + +bool CGUIDialogExtendedProgressBar::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_INIT: + { + m_iLastSwitchTime = CTimeUtils::GetFrameTime(); + m_iCurrentItem = 0; + CGUIDialog::OnMessage(message); + + UpdateState(0); + return true; + } + break; + } + + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogExtendedProgressBar::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) +{ + if (m_active) + UpdateState(currentTime); + + CGUIDialog::Process(currentTime, dirtyregions); +} + +void CGUIDialogExtendedProgressBar::UpdateState(unsigned int currentTime) +{ + std::string strHeader; + std::string strTitle; + float fProgress(-1.0f); + + { + std::unique_lock<CCriticalSection> lock(m_critSection); + + // delete finished items + for (int iPtr = m_handles.size() - 1; iPtr >= 0; iPtr--) + { + if (m_handles.at(iPtr)->IsFinished()) + { + delete m_handles.at(iPtr); + m_handles.erase(m_handles.begin() + iPtr); + } + } + + if (!m_handles.size()) + { + Close(false, 0, true, false); + return; + } + + // ensure the current item is in our range + if (m_iCurrentItem >= m_handles.size()) + m_iCurrentItem = m_handles.size() - 1; + + // update the current item ptr + if (currentTime > m_iLastSwitchTime && + currentTime - m_iLastSwitchTime >= ITEM_SWITCH_TIME_MS) + { + m_iLastSwitchTime = currentTime; + + // select next item + if (++m_iCurrentItem > m_handles.size() - 1) + m_iCurrentItem = 0; + } + + CGUIDialogProgressBarHandle *handle = m_handles.at(m_iCurrentItem); + if (handle) + { + strTitle = handle->Text(); + strHeader = handle->Title(); + fProgress = handle->Percentage(); + } + } + + SET_CONTROL_LABEL(CONTROL_LABELHEADER, strHeader); + SET_CONTROL_LABEL(CONTROL_LABELTITLE, strTitle); + + if (fProgress > -1.0f) + { + SET_CONTROL_VISIBLE(CONTROL_PROGRESS); + CONTROL_SELECT_ITEM(CONTROL_PROGRESS, (unsigned int)fProgress); + } +} diff --git a/xbmc/dialogs/GUIDialogExtendedProgressBar.h b/xbmc/dialogs/GUIDialogExtendedProgressBar.h new file mode 100644 index 0000000..c6a0609 --- /dev/null +++ b/xbmc/dialogs/GUIDialogExtendedProgressBar.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +#include <string> +#include <vector> + +class CGUIDialogProgressBarHandle +{ +public: + explicit CGUIDialogProgressBarHandle(const std::string &strTitle) : + m_fPercentage(0), + m_strTitle(strTitle), + m_bFinished(false) {} + virtual ~CGUIDialogProgressBarHandle(void) = default; + + const std::string &Title(void) { return m_strTitle; } + void SetTitle(const std::string &strTitle); + + std::string Text(void) const; + void SetText(const std::string &strText); + + bool IsFinished(void) const { return m_bFinished; } + void MarkFinished(void) { m_bFinished = true; } + + float Percentage(void) const { return m_fPercentage;} + void SetPercentage(float fPercentage) { m_fPercentage = fPercentage; } + void SetProgress(int currentItem, int itemCount); + +private: + mutable CCriticalSection m_critSection; + float m_fPercentage; + std::string m_strTitle; + std::string m_strText; + bool m_bFinished; +}; + +class CGUIDialogExtendedProgressBar : public CGUIDialog +{ +public: + CGUIDialogExtendedProgressBar(void); + ~CGUIDialogExtendedProgressBar(void) override = default; + bool OnMessage(CGUIMessage& message) override; + void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override; + + CGUIDialogProgressBarHandle *GetHandle(const std::string &strTitle); + +protected: + void UpdateState(unsigned int currentTime); + + CCriticalSection m_critSection; + unsigned int m_iCurrentItem; + unsigned int m_iLastSwitchTime; + std::vector<CGUIDialogProgressBarHandle *> m_handles; +}; diff --git a/xbmc/dialogs/GUIDialogFileBrowser.cpp b/xbmc/dialogs/GUIDialogFileBrowser.cpp new file mode 100644 index 0000000..a9f81cb --- /dev/null +++ b/xbmc/dialogs/GUIDialogFileBrowser.cpp @@ -0,0 +1,1020 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogFileBrowser.h" + +#include "AutoSwitch.h" +#include "FileItem.h" +#include "GUIDialogContextMenu.h" +#include "GUIDialogMediaSource.h" +#include "GUIDialogYesNo.h" +#include "GUIPassword.h" +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "Util.h" +#include "filesystem/Directory.h" +#include "filesystem/File.h" +#include "filesystem/MultiPathDirectory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/Key.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "network/GUIDialogNetworkSetup.h" +#include "network/Network.h" +#include "profiles/ProfileManager.h" +#include "settings/MediaSourceSettings.h" +#include "settings/SettingsComponent.h" +#include "storage/MediaManager.h" +#include "utils/FileExtensionProvider.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" + +using namespace KODI::MESSAGING; +using namespace XFILE; + +#define CONTROL_LIST 450 +#define CONTROL_THUMBS 451 +#define CONTROL_HEADING_LABEL 411 +#define CONTROL_LABEL_PATH 412 +#define CONTROL_OK 413 +#define CONTROL_CANCEL 414 +#define CONTROL_NEWFOLDER 415 +#define CONTROL_FLIP 416 + +CGUIDialogFileBrowser::CGUIDialogFileBrowser() + : CGUIDialog(WINDOW_DIALOG_FILE_BROWSER, "FileBrowser.xml") +{ + m_Directory = new CFileItem; + m_vecItems = new CFileItemList; + m_bConfirmed = false; + m_Directory->m_bIsFolder = true; + m_browsingForFolders = 0; + m_browsingForImages = false; + m_useFileDirectories = false; + m_addNetworkShareEnabled = false; + m_singleList = false; + m_thumbLoader.SetObserver(this); + m_flipEnabled = false; + m_bFlip = false; + m_multipleSelection = false; + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogFileBrowser::~CGUIDialogFileBrowser() +{ + delete m_Directory; + delete m_vecItems; +} + +bool CGUIDialogFileBrowser::OnAction(const CAction &action) +{ + if (action.GetID() == ACTION_PARENT_DIR) + { + GoParentFolder(); + return true; + } + if ((action.GetID() == ACTION_CONTEXT_MENU || action.GetID() == ACTION_MOUSE_RIGHT_CLICK) && m_Directory->GetPath().empty()) + { + int iItem = m_viewControl.GetSelectedItem(); + if ((!m_addSourceType.empty() && iItem != m_vecItems->Size()-1)) + return OnPopupMenu(iItem); + if (m_addNetworkShareEnabled && CServiceBroker::GetMediaManager().HasLocation(m_selectedPath)) + { + // need to make sure this source is not an auto added location + // as users locations might have the same paths + CFileItemPtr pItem = (*m_vecItems)[iItem]; + for (unsigned int i=0;i<m_shares.size();++i) + { + if (StringUtils::EqualsNoCase(m_shares[i].strName, pItem->GetLabel()) && m_shares[i].m_ignore) + return false; + } + + return OnPopupMenu(iItem); + } + + return false; + } + + return CGUIDialog::OnAction(action); +} + +bool CGUIDialogFileBrowser::OnBack(int actionID) +{ + if (actionID == ACTION_NAV_BACK && !m_vecItems->IsVirtualDirectoryRoot()) + { + GoParentFolder(); + return true; + } + return CGUIDialog::OnBack(actionID); +} + +bool CGUIDialogFileBrowser::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_WINDOW_DEINIT: + { + if (m_thumbLoader.IsLoading()) + m_thumbLoader.StopThread(); + CGUIDialog::OnMessage(message); + ClearFileItems(); + m_addNetworkShareEnabled = false; + return true; + } + break; + + case GUI_MSG_WINDOW_INIT: + { + m_bConfirmed = false; + m_bFlip = false; + bool bIsDir = false; + // this code allows two different selection modes for directories + // end the path with a slash to start inside the directory + if (URIUtils::HasSlashAtEnd(m_selectedPath)) + { + bIsDir = true; + bool bFool; + int iSource = CUtil::GetMatchingSource(m_selectedPath,m_shares,bFool); + bFool = true; + if (iSource > -1 && iSource < (int)m_shares.size()) + { + if (URIUtils::PathEquals(m_shares[iSource].strPath, m_selectedPath)) + bFool = false; + } + + if (bFool && !CDirectory::Exists(m_selectedPath)) + m_selectedPath.clear(); + } + else + { + if (!CFile::Exists(m_selectedPath) && !CDirectory::Exists(m_selectedPath)) + m_selectedPath.clear(); + } + + // find the parent folder if we are a file browser (don't do this for folders) + m_Directory->SetPath(m_selectedPath); + if (!m_browsingForFolders && !bIsDir) + m_Directory->SetPath(URIUtils::GetParentPath(m_selectedPath)); + Update(m_Directory->GetPath()); + m_viewControl.SetSelectedItem(m_selectedPath); + return CGUIDialog::OnMessage(message); + } + break; + + case GUI_MSG_CLICKED: + { + if (m_viewControl.HasControl(message.GetSenderId())) // list control + { + int iItem = m_viewControl.GetSelectedItem(); + int iAction = message.GetParam1(); + if (iItem < 0) break; + CFileItemPtr pItem = (*m_vecItems)[iItem]; + if ((iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK) && + (!m_multipleSelection || pItem->m_bIsShareOrDrive || pItem->m_bIsFolder)) + { + OnClick(iItem); + return true; + } + else if ((iAction == ACTION_HIGHLIGHT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK || iAction == ACTION_SELECT_ITEM) && + (m_multipleSelection && !pItem->m_bIsShareOrDrive && !pItem->m_bIsFolder)) + { + pItem->Select(!pItem->IsSelected()); + CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), message.GetSenderId(), iItem + 1); + OnMessage(msg); + } + } + else if (message.GetSenderId() == CONTROL_OK) + { + if (m_browsingForFolders == 2) + { + int iItem = m_viewControl.GetSelectedItem(); + + std::string strPath; + if (iItem == 0) + strPath = m_selectedPath; + else + strPath = (*m_vecItems)[iItem]->GetPath(); + + std::string strTest = URIUtils::AddFileToFolder(strPath, "1"); + CFile file; + if (file.OpenForWrite(strTest,true)) + { + file.Close(); + CFile::Delete(strTest); + m_bConfirmed = true; + Close(); + } + else + HELPERS::ShowOKDialogText(CVariant{257}, CVariant{20072}); + } + else + { + if (m_multipleSelection) + { + for (int iItem = 0; iItem < m_vecItems->Size(); ++iItem) + { + CFileItemPtr pItem = (*m_vecItems)[iItem]; + if (pItem->IsSelected()) + m_markedPath.push_back(pItem->GetPath()); + } + } + m_bConfirmed = true; + Close(); + } + return true; + } + else if (message.GetSenderId() == CONTROL_CANCEL) + { + Close(); + return true; + } + else if (message.GetSenderId() == CONTROL_NEWFOLDER) + { + std::string strInput; + if (CGUIKeyboardFactory::ShowAndGetInput(strInput, CVariant{g_localizeStrings.Get(119)}, false)) + { + std::string strPath = URIUtils::AddFileToFolder(m_vecItems->GetPath(), strInput); + if (CDirectory::Create(strPath)) + Update(m_vecItems->GetPath()); + else + HELPERS::ShowOKDialogText(CVariant{20069}, CVariant{20072}); + } + } + else if (message.GetSenderId() == CONTROL_FLIP) + m_bFlip = !m_bFlip; + } + break; + case GUI_MSG_SETFOCUS: + { + if (m_viewControl.HasControl(message.GetControlId()) && m_viewControl.GetCurrentControl() != message.GetControlId()) + { + m_viewControl.SetFocused(); + return true; + } + } + break; + case GUI_MSG_NOTIFY_ALL: + { // Message is received only if this window is active + if (message.GetParam1() == GUI_MSG_REMOVED_MEDIA) + { + if (m_Directory->IsVirtualDirectoryRoot() && IsActive()) + { + int iItem = m_viewControl.GetSelectedItem(); + Update(m_Directory->GetPath()); + m_viewControl.SetSelectedItem(iItem); + } + else if (m_Directory->IsRemovable()) + { // check that we have this removable share still + if (!m_rootDir.IsInSource(m_Directory->GetPath())) + { // don't have this share any more + if (IsActive()) Update(""); + else + { + m_history.ClearPathHistory(); + m_Directory->SetPath(""); + } + } + } + return true; + } + else if (message.GetParam1()==GUI_MSG_UPDATE_SOURCES) + { // State of the sources changed, so update our view + if (m_Directory->IsVirtualDirectoryRoot() && IsActive()) + { + int iItem = m_viewControl.GetSelectedItem(); + Update(m_Directory->GetPath()); + m_viewControl.SetSelectedItem(iItem); + } + return true; + } + else if (message.GetParam1()==GUI_MSG_UPDATE_PATH) + { + if (IsActive()) + { + if((message.GetStringParam() == m_Directory->GetPath()) || + (m_Directory->IsMultiPath() && XFILE::CMultiPathDirectory::HasPath(m_Directory->GetPath(), message.GetStringParam()))) + { + int iItem = m_viewControl.GetSelectedItem(); + Update(m_Directory->GetPath()); + m_viewControl.SetSelectedItem(iItem); + } + } + } + } + break; + + } + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogFileBrowser::ClearFileItems() +{ + m_viewControl.Clear(); + m_vecItems->Clear(); // will clean up everything +} + +void CGUIDialogFileBrowser::OnSort() +{ + if (!m_singleList) + m_vecItems->Sort(SortByLabel, SortOrderAscending); +} + +void CGUIDialogFileBrowser::Update(const std::string &strDirectory) +{ + const CURL pathToUrl(strDirectory); + + if (m_browsingForImages && m_thumbLoader.IsLoading()) + m_thumbLoader.StopThread(); + // get selected item + int iItem = m_viewControl.GetSelectedItem(); + std::string strSelectedItem; + if (iItem >= 0 && iItem < m_vecItems->Size()) + { + CFileItemPtr pItem = (*m_vecItems)[iItem]; + if (!pItem->IsParentFolder()) + { + strSelectedItem = pItem->GetPath(); + URIUtils::RemoveSlashAtEnd(strSelectedItem); + m_history.SetSelectedItem(strSelectedItem, m_Directory->GetPath().empty()?"empty":m_Directory->GetPath()); + } + } + + if (!m_singleList) + { + CFileItemList items; + std::string strParentPath; + + if (!m_rootDir.GetDirectory(pathToUrl, items, m_useFileDirectories, false)) + { + CLog::Log(LOGERROR, "CGUIDialogFileBrowser::GetDirectory({}) failed", + pathToUrl.GetRedacted()); + + // We assume, we can get the parent + // directory again + std::string strParentPath = m_history.GetParentPath(); + m_history.RemoveParentPath(); + Update(strParentPath); + return; + } + + // check if current directory is a root share + if (!m_rootDir.IsSource(strDirectory)) + { + if (URIUtils::GetParentPath(strDirectory, strParentPath)) + { + CFileItemPtr pItem(new CFileItem("..")); + pItem->SetPath(strParentPath); + pItem->m_bIsFolder = true; + pItem->m_bIsShareOrDrive = false; + items.AddFront(pItem, 0); + } + } + else + { + // yes, this is the root of a share + // add parent path to the virtual directory + CFileItemPtr pItem(new CFileItem("..")); + pItem->SetPath(""); + pItem->m_bIsShareOrDrive = false; + pItem->m_bIsFolder = true; + items.AddFront(pItem, 0); + strParentPath = ""; + } + + ClearFileItems(); + m_vecItems->Copy(items); + m_Directory->SetPath(strDirectory); + m_strParentPath = strParentPath; + } + + // if we're getting the root source listing + // make sure the path history is clean + if (strDirectory.empty()) + m_history.ClearPathHistory(); + + // some evil stuff don't work with the '/' mask, e.g. shoutcast directory - make sure no files are in there + if (m_browsingForFolders) + { + for (int i=0;i<m_vecItems->Size();++i) + if (!(*m_vecItems)[i]->m_bIsFolder) + { + m_vecItems->Remove(i); + i--; + } + } + + // No need to set thumbs + + m_vecItems->FillInDefaultIcons(); + + OnSort(); + + if (m_Directory->GetPath().empty() && m_addNetworkShareEnabled && + (CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE || + CServiceBroker::GetSettingsComponent()->GetProfileManager()->IsMasterProfile() || g_passwordManager.bMasterUser)) + { // we are in the virtual directory - add the "Add Network Location" item + CFileItemPtr pItem(new CFileItem(g_localizeStrings.Get(1032))); + pItem->SetPath("net://"); + pItem->m_bIsFolder = true; + m_vecItems->Add(pItem); + } + if (m_Directory->GetPath().empty() && !m_addSourceType.empty()) + { + CFileItemPtr pItem(new CFileItem(g_localizeStrings.Get(21359))); + pItem->SetPath("source://"); + pItem->m_bIsFolder = true; + m_vecItems->Add(pItem); + } + + m_viewControl.SetItems(*m_vecItems); + m_viewControl.SetCurrentView((m_browsingForImages && CAutoSwitch::ByFileCount(*m_vecItems)) ? CONTROL_THUMBS : CONTROL_LIST); + + std::string strPath2 = m_Directory->GetPath(); + URIUtils::RemoveSlashAtEnd(strPath2); + strSelectedItem = m_history.GetSelectedItem(strPath2==""?"empty":strPath2); + + bool bSelectedFound = false; + for (int i = 0; i < m_vecItems->Size(); ++i) + { + CFileItemPtr pItem = (*m_vecItems)[i]; + strPath2 = pItem->GetPath(); + URIUtils::RemoveSlashAtEnd(strPath2); + if (strPath2 == strSelectedItem) + { + m_viewControl.SetSelectedItem(i); + bSelectedFound = true; + break; + } + } + + // if we haven't found the selected item, select the first item + if (!bSelectedFound) + m_viewControl.SetSelectedItem(0); + + m_history.AddPath(m_Directory->GetPath()); + + if (m_browsingForImages) + m_thumbLoader.Load(*m_vecItems); +} + +void CGUIDialogFileBrowser::FrameMove() +{ + int item = m_viewControl.GetSelectedItem(); + if (item >= 0) + { + // if we are browsing for folders, and not in the root directory, then we use the parent path, + // else we use the current file's path + if (m_browsingForFolders && !m_Directory->IsVirtualDirectoryRoot()) + m_selectedPath = m_Directory->GetPath(); + else + m_selectedPath = (*m_vecItems)[item]->GetPath(); + if (m_selectedPath == "net://") + { + SET_CONTROL_LABEL(CONTROL_LABEL_PATH, g_localizeStrings.Get(1032)); // "Add Network Location..." + } + else + { + // Update the current path label + CURL url(m_selectedPath); + std::string safePath = url.GetWithoutUserDetails(); + SET_CONTROL_LABEL(CONTROL_LABEL_PATH, safePath); + } + if ((!m_browsingForFolders && (*m_vecItems)[item]->m_bIsFolder) || ((*m_vecItems)[item]->GetPath() == "image://Browse")) + { + CONTROL_DISABLE(CONTROL_OK); + } + else + { + CONTROL_ENABLE(CONTROL_OK); + } + if (m_browsingForFolders == 2) + { + CONTROL_ENABLE(CONTROL_NEWFOLDER); + } + else + { + CONTROL_DISABLE(CONTROL_NEWFOLDER); + } + if (m_flipEnabled) + { + CONTROL_ENABLE(CONTROL_FLIP); + } + else + { + CONTROL_DISABLE(CONTROL_FLIP); + } + } + CGUIDialog::FrameMove(); +} + +void CGUIDialogFileBrowser::OnClick(int iItem) +{ + if ( iItem < 0 || iItem >= m_vecItems->Size() ) return ; + CFileItemPtr pItem = (*m_vecItems)[iItem]; + std::string strPath = pItem->GetPath(); + + if (pItem->m_bIsFolder) + { + if (pItem->GetPath() == "net://") + { // special "Add Network Location" item + OnAddNetworkLocation(); + return; + } + if (pItem->GetPath() == "source://") + { // special "Add Source" item + OnAddMediaSource(); + return; + } + if (!m_addSourceType.empty()) + { + OnEditMediaSource(pItem.get()); + return; + } + if ( pItem->m_bIsShareOrDrive ) + { + if ( !HaveDiscOrConnection( pItem->m_iDriveType ) ) + return ; + } + Update(strPath); + } + else if (!m_browsingForFolders) + { + m_selectedPath = pItem->GetPath(); + m_bConfirmed = true; + Close(); + } +} + +bool CGUIDialogFileBrowser::HaveDiscOrConnection( int iDriveType ) +{ + if ( iDriveType == CMediaSource::SOURCE_TYPE_DVD ) + { + if (!CServiceBroker::GetMediaManager().IsDiscInDrive()) + { + HELPERS::ShowOKDialogText(CVariant{218}, CVariant{219}); + return false; + } + } + else if ( iDriveType == CMediaSource::SOURCE_TYPE_REMOTE ) + { + //! @todo Handle not connected to a remote share + if ( !CServiceBroker::GetNetwork().IsConnected() ) + { + HELPERS::ShowOKDialogText(CVariant{220}, CVariant{221}); + return false; + } + } + + return true; +} + +void CGUIDialogFileBrowser::GoParentFolder() +{ + std::string strPath(m_strParentPath); + if (strPath.size() == 2) + if (strPath[1] == ':') + URIUtils::AddSlashAtEnd(strPath); + Update(strPath); +} + +void CGUIDialogFileBrowser::OnWindowLoaded() +{ + CGUIDialog::OnWindowLoaded(); + m_viewControl.Reset(); + m_viewControl.SetParentWindow(GetID()); + m_viewControl.AddView(GetControl(CONTROL_LIST)); + m_viewControl.AddView(GetControl(CONTROL_THUMBS)); +} + +void CGUIDialogFileBrowser::OnWindowUnload() +{ + CGUIDialog::OnWindowUnload(); + m_viewControl.Reset(); +} + +bool CGUIDialogFileBrowser::ShowAndGetImage(const CFileItemList &items, const VECSOURCES &shares, const std::string &heading, std::string &result, bool* flip, int label) +{ + CGUIDialogFileBrowser *browser = new CGUIDialogFileBrowser(); + if (!browser) + return false; + CServiceBroker::GetGUI()->GetWindowManager().AddUniqueInstance(browser); + + browser->m_browsingForImages = true; + browser->m_singleList = true; + browser->m_vecItems->Clear(); + browser->m_vecItems->Append(items); + if (true) + { + CFileItemPtr item(new CFileItem("image://Browse", false)); + item->SetLabel(g_localizeStrings.Get(20153)); + item->SetArt("icon", "DefaultFolder.png"); + browser->m_vecItems->Add(item); + } + browser->SetHeading(heading); + browser->m_flipEnabled = flip?true:false; + browser->Open(); + bool confirmed(browser->IsConfirmed()); + if (confirmed) + { + result = browser->m_selectedPath; + if (result == "image://Browse") + { // "Browse for thumb" + CServiceBroker::GetGUI()->GetWindowManager().Remove(browser->GetID()); + delete browser; + return ShowAndGetImage(shares, g_localizeStrings.Get(label), result); + } + } + + if (flip) + *flip = browser->m_bFlip != 0; + + CServiceBroker::GetGUI()->GetWindowManager().Remove(browser->GetID()); + delete browser; + + return confirmed; +} + +bool CGUIDialogFileBrowser::ShowAndGetImage(const VECSOURCES &shares, const std::string &heading, std::string &path) +{ + return ShowAndGetFile(shares, CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), heading, path, true); // true for use thumbs +} + +bool CGUIDialogFileBrowser::ShowAndGetImageList(const VECSOURCES &shares, const std::string &heading, std::vector<std::string> &path) +{ + return ShowAndGetFileList(shares, CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), heading, path, true); // true for use thumbs +} + +bool CGUIDialogFileBrowser::ShowAndGetDirectory(const VECSOURCES &shares, const std::string &heading, std::string &path, bool bWriteOnly) +{ + // an extension mask of "/" ensures that no files are shown + if (bWriteOnly) + { + VECSOURCES shareWritable; + for (unsigned int i=0;i<shares.size();++i) + { + if (shares[i].IsWritable()) + shareWritable.push_back(shares[i]); + } + + return ShowAndGetFile(shareWritable, "/w", heading, path); + } + + return ShowAndGetFile(shares, "/", heading, path); +} + +bool CGUIDialogFileBrowser::ShowAndGetFile(const VECSOURCES &shares, const std::string &mask, const std::string &heading, std::string &path, bool useThumbs /* = false */, bool useFileDirectories /* = false */) +{ + CGUIDialogFileBrowser *browser = new CGUIDialogFileBrowser(); + if (!browser) + return false; + CServiceBroker::GetGUI()->GetWindowManager().AddUniqueInstance(browser); + + browser->m_useFileDirectories = useFileDirectories; + + browser->m_browsingForImages = useThumbs; + browser->SetHeading(heading); + browser->SetSources(shares); + std::string strMask = mask; + if (mask == "/") + browser->m_browsingForFolders=1; + else + if (mask == "/w") + { + browser->m_browsingForFolders=2; + strMask = "/"; + } + else + browser->m_browsingForFolders = 0; + + browser->m_rootDir.SetMask(strMask); + browser->m_selectedPath = path; + browser->m_addNetworkShareEnabled = false; + browser->Open(); + bool confirmed(browser->IsConfirmed()); + if (confirmed) + path = browser->m_selectedPath; + CServiceBroker::GetGUI()->GetWindowManager().Remove(browser->GetID()); + delete browser; + return confirmed; +} + +// same as above, starting in a single directory +bool CGUIDialogFileBrowser::ShowAndGetFile(const std::string &directory, const std::string &mask, const std::string &heading, std::string &path, bool useThumbs /* = false */, bool useFileDirectories /* = false */, bool singleList /* = false */) +{ + CGUIDialogFileBrowser *browser = new CGUIDialogFileBrowser(); + if (!browser) + return false; + CServiceBroker::GetGUI()->GetWindowManager().AddUniqueInstance(browser); + + browser->m_useFileDirectories = useFileDirectories; + browser->m_browsingForImages = useThumbs; + browser->SetHeading(heading); + + // add a single share for this directory + if (!singleList) + { + VECSOURCES shares; + CMediaSource share; + share.strPath = directory; + URIUtils::RemoveSlashAtEnd(share.strPath); // this is needed for the dodgy code in WINDOW_INIT + shares.push_back(share); + browser->SetSources(shares); + } + else + { + browser->m_vecItems->Clear(); + CDirectory::GetDirectory(directory,*browser->m_vecItems, "", DIR_FLAG_DEFAULTS); + CFileItemPtr item(new CFileItem("file://Browse", false)); + item->SetLabel(g_localizeStrings.Get(20153)); + item->SetArt("icon", "DefaultFolder.png"); + browser->m_vecItems->Add(item); + browser->m_singleList = true; + } + std::string strMask = mask; + if (mask == "/") + browser->m_browsingForFolders=1; + else + if (mask == "/w") + { + browser->m_browsingForFolders=2; + strMask = "/"; + } + else + browser->m_browsingForFolders = 0; + + browser->m_rootDir.SetMask(strMask); + browser->m_selectedPath = directory; + browser->m_addNetworkShareEnabled = false; + browser->Open(); + bool confirmed(browser->IsConfirmed()); + if (confirmed) + path = browser->m_selectedPath; + if (path == "file://Browse") + { // "Browse for thumb" + CServiceBroker::GetGUI()->GetWindowManager().Remove(browser->GetID()); + delete browser; + VECSOURCES shares; + CServiceBroker::GetMediaManager().GetLocalDrives(shares); + + return ShowAndGetFile(shares, mask, heading, path, useThumbs,useFileDirectories); + } + CServiceBroker::GetGUI()->GetWindowManager().Remove(browser->GetID()); + delete browser; + return confirmed; +} + +bool CGUIDialogFileBrowser::ShowAndGetFileList(const VECSOURCES &shares, const std::string &mask, const std::string &heading, std::vector<std::string> &path, bool useThumbs /* = false */, bool useFileDirectories /* = false */) +{ + CGUIDialogFileBrowser *browser = new CGUIDialogFileBrowser(); + if (!browser) + return false; + CServiceBroker::GetGUI()->GetWindowManager().AddUniqueInstance(browser); + + browser->m_useFileDirectories = useFileDirectories; + browser->m_multipleSelection = true; + browser->m_browsingForImages = useThumbs; + browser->SetHeading(heading); + browser->SetSources(shares); + browser->m_browsingForFolders = 0; + browser->m_rootDir.SetMask(mask); + browser->m_addNetworkShareEnabled = false; + browser->Open(); + bool confirmed(browser->IsConfirmed()); + if (confirmed) + { + if (browser->m_markedPath.size()) + path = browser->m_markedPath; + else + path.push_back(browser->m_selectedPath); + } + CServiceBroker::GetGUI()->GetWindowManager().Remove(browser->GetID()); + delete browser; + return confirmed; +} + +void CGUIDialogFileBrowser::SetHeading(const std::string &heading) +{ + Initialize(); + SET_CONTROL_LABEL(CONTROL_HEADING_LABEL, heading); +} + +bool CGUIDialogFileBrowser::ShowAndGetSource(std::string &path, bool allowNetworkShares, VECSOURCES* additionalShare /* = NULL */, const std::string& strType /* = "" */) +{ + // Technique is + // 1. Show Filebrowser with currently defined local, and optionally the network locations. + // 2. Have the "Add Network Location" option in addition. + // 3a. If the "Add Network Location" is pressed, then: + // a) Fire up the network location dialog to grab the new location + // b) Check the location by doing a GetDirectory() - if it fails, prompt the user + // to allow them to add currently disconnected network shares. + // c) Save this location to our xml file (network.xml) + // d) Return to 1. + // 3b. If the "Add Source" is pressed, then: + // a) Fire up the media source dialog to add the new location + // 4. Optionally allow user to browse the local and network locations for their share. + // 5. On OK, return. + + // Create a new filebrowser window + CGUIDialogFileBrowser *browser = new CGUIDialogFileBrowser(); + if (!browser) return false; + + // Add it to our window manager + CServiceBroker::GetGUI()->GetWindowManager().AddUniqueInstance(browser); + + VECSOURCES shares; + if (!strType.empty()) + { + if (additionalShare) + shares = *additionalShare; + browser->m_addSourceType = strType; + } + else + { + browser->SetHeading(g_localizeStrings.Get(1023)); + + CServiceBroker::GetMediaManager().GetLocalDrives(shares); + + // Now the additional share if appropriate + if (additionalShare) + { + shares.insert(shares.end(),additionalShare->begin(),additionalShare->end()); + } + + // Now add the network shares... + if (allowNetworkShares) + { + CServiceBroker::GetMediaManager().GetNetworkLocations(shares); + } + } + + browser->SetSources(shares); + browser->m_rootDir.SetMask("/"); + browser->m_rootDir.AllowNonLocalSources(false); // don't allow plug n play shares + browser->m_browsingForFolders = 1; + browser->m_addNetworkShareEnabled = allowNetworkShares; + browser->m_selectedPath = ""; + browser->Open(); + bool confirmed = browser->IsConfirmed(); + if (confirmed) + path = browser->m_selectedPath; + + CServiceBroker::GetGUI()->GetWindowManager().Remove(browser->GetID()); + delete browser; + return confirmed; +} + +void CGUIDialogFileBrowser::SetSources(const VECSOURCES &shares) +{ + m_shares = shares; + if (!m_shares.size() && m_addSourceType.empty()) + CServiceBroker::GetMediaManager().GetLocalDrives(m_shares); + m_rootDir.SetSources(m_shares); +} + +void CGUIDialogFileBrowser::OnAddNetworkLocation() +{ + // ok, fire up the network location dialog + std::string path; + if (CGUIDialogNetworkSetup::ShowAndGetNetworkAddress(path)) + { + // verify the path by doing a GetDirectory. + CFileItemList items; + if (CDirectory::GetDirectory(path, items, "", DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_ALLOW_PROMPT) || CGUIDialogYesNo::ShowAndGetInput(CVariant{1001}, CVariant{1002})) + { // add the network location to the shares list + CMediaSource share; + share.strPath = path; //setPath(path); + CURL url(path); + share.strName = url.GetWithoutUserDetails(); + URIUtils::RemoveSlashAtEnd(share.strName); + m_shares.push_back(share); + // add to our location manager... + CServiceBroker::GetMediaManager().AddNetworkLocation(path); + } + } + m_rootDir.SetSources(m_shares); + Update(m_vecItems->GetPath()); +} + +void CGUIDialogFileBrowser::OnAddMediaSource() +{ + if (CGUIDialogMediaSource::ShowAndAddMediaSource(m_addSourceType)) + { + SetSources(*CMediaSourceSettings::GetInstance().GetSources(m_addSourceType)); + Update(""); + } +} + +void CGUIDialogFileBrowser::OnEditMediaSource(CFileItem* pItem) +{ + if (CGUIDialogMediaSource::ShowAndEditMediaSource(m_addSourceType,pItem->GetLabel())) + { + SetSources(*CMediaSourceSettings::GetInstance().GetSources(m_addSourceType)); + Update(""); + } +} + +bool CGUIDialogFileBrowser::OnPopupMenu(int iItem) +{ + CContextButtons choices; + choices.Add(1, m_addSourceType.empty() ? 20133 : 21364); + choices.Add(2, m_addSourceType.empty() ? 20134 : 21365); + + int btnid = CGUIDialogContextMenu::ShowAndGetChoice(choices); + if (btnid == 1) + { + if (m_addNetworkShareEnabled) + { + std::string strOldPath=m_selectedPath,newPath=m_selectedPath; + VECSOURCES shares=m_shares; + if (CGUIDialogNetworkSetup::ShowAndGetNetworkAddress(newPath)) + { + CServiceBroker::GetMediaManager().SetLocationPath(strOldPath, newPath); + CURL url(newPath); + for (unsigned int i=0;i<shares.size();++i) + { + if (URIUtils::CompareWithoutSlashAtEnd(shares[i].strPath, strOldPath))//getPath().Equals(strOldPath)) + { + shares[i].strName = url.GetWithoutUserDetails(); + shares[i].strPath = newPath; + URIUtils::RemoveSlashAtEnd(shares[i].strName); + break; + } + } + // refresh dialog content + SetSources(shares); + m_rootDir.SetMask("/"); + m_browsingForFolders = 1; + m_addNetworkShareEnabled = true; + m_selectedPath = url.GetWithoutUserDetails(); + Update(m_Directory->GetPath()); + m_viewControl.SetSelectedItem(iItem); + } + } + else + { + CFileItemPtr item = m_vecItems->Get(iItem); + OnEditMediaSource(item.get()); + } + } + if (btnid == 2) + { + if (m_addNetworkShareEnabled) + { + CServiceBroker::GetMediaManager().RemoveLocation(m_selectedPath); + + for (unsigned int i=0;i<m_shares.size();++i) + { + if (URIUtils::CompareWithoutSlashAtEnd(m_shares[i].strPath, m_selectedPath) && !m_shares[i].m_ignore) // getPath().Equals(m_selectedPath)) + { + m_shares.erase(m_shares.begin()+i); + break; + } + } + m_rootDir.SetSources(m_shares); + m_rootDir.SetMask("/"); + + m_browsingForFolders = 1; + m_addNetworkShareEnabled = true; + m_selectedPath = ""; + + Update(m_Directory->GetPath()); + } + else + { + CMediaSourceSettings::GetInstance().DeleteSource(m_addSourceType,(*m_vecItems)[iItem]->GetLabel(),(*m_vecItems)[iItem]->GetPath()); + SetSources(*CMediaSourceSettings::GetInstance().GetSources(m_addSourceType)); + Update(""); + } + } + + return true; +} + +CFileItemPtr CGUIDialogFileBrowser::GetCurrentListItem(int offset) +{ + int item = m_viewControl.GetSelectedItem(); + if (item < 0 || !m_vecItems->Size()) return CFileItemPtr(); + + item = (item + offset) % m_vecItems->Size(); + if (item < 0) item += m_vecItems->Size(); + return (*m_vecItems)[item]; +} + +CGUIControl *CGUIDialogFileBrowser::GetFirstFocusableControl(int id) +{ + if (m_viewControl.HasControl(id)) + id = m_viewControl.GetCurrentControl(); + return CGUIWindow::GetFirstFocusableControl(id); +} + + diff --git a/xbmc/dialogs/GUIDialogFileBrowser.h b/xbmc/dialogs/GUIDialogFileBrowser.h new file mode 100644 index 0000000..8cafaba --- /dev/null +++ b/xbmc/dialogs/GUIDialogFileBrowser.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "filesystem/DirectoryHistory.h" +#include "filesystem/VirtualDirectory.h" +#include "guilib/GUIDialog.h" +#include "pictures/PictureThumbLoader.h" +#include "view/GUIViewControl.h" + +#include <string> +#include <vector> + +class CFileItem; +class CFileItemList; + +class CGUIDialogFileBrowser : public CGUIDialog, public IBackgroundLoaderObserver +{ +public: + CGUIDialogFileBrowser(void); + ~CGUIDialogFileBrowser(void) override; + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction &action) override; + bool OnBack(int actionID) override; + void FrameMove() override; + void OnWindowLoaded() override; + void OnWindowUnload() override; + bool IsConfirmed() { return m_bConfirmed; } + void SetHeading(const std::string &heading); + + static bool ShowAndGetDirectory(const VECSOURCES &shares, const std::string &heading, std::string &path, bool bWriteOnly=false); + static bool ShowAndGetFile(const VECSOURCES &shares, const std::string &mask, const std::string &heading, std::string &path, bool useThumbs = false, bool useFileDirectories = false); + static bool ShowAndGetFile(const std::string &directory, const std::string &mask, const std::string &heading, std::string &path, bool useThumbs = false, bool useFileDirectories = false, bool singleList = false); + static bool ShowAndGetSource(std::string &path, bool allowNetworkShares, VECSOURCES* additionalShare = NULL, const std::string& strType=""); + static bool ShowAndGetFileList(const VECSOURCES &shares, const std::string &mask, const std::string &heading, std::vector<std::string> &path, bool useThumbs = false, bool useFileDirectories = false); + static bool ShowAndGetImage(const VECSOURCES &shares, const std::string &heading, std::string &path); + static bool ShowAndGetImage(const CFileItemList &items, const VECSOURCES &shares, const std::string &heading, std::string &path, bool* flip=NULL, int label=21371); + static bool ShowAndGetImageList(const VECSOURCES &shares, const std::string &heading, std::vector<std::string> &path); + + void SetSources(const VECSOURCES &shares); + + void OnItemLoaded(CFileItem *item) override {}; + + bool HasListItems() const override { return true; } + CFileItemPtr GetCurrentListItem(int offset = 0) override; + int GetViewContainerID() const override { return m_viewControl.GetCurrentControl(); } + +protected: + void GoParentFolder(); + void OnClick(int iItem); + void OnSort(); + void ClearFileItems(); + void Update(const std::string &strDirectory); + bool HaveDiscOrConnection( int iDriveType ); + bool OnPopupMenu(int iItem); + void OnAddNetworkLocation(); + void OnAddMediaSource(); + void OnEditMediaSource(CFileItem* pItem); + CGUIControl *GetFirstFocusableControl(int id) override; + + VECSOURCES m_shares; + XFILE::CVirtualDirectory m_rootDir; + CFileItemList* m_vecItems; + CFileItem* m_Directory; + std::string m_strParentPath; + std::string m_selectedPath; + CDirectoryHistory m_history; + int m_browsingForFolders; // 0 - no, 1 - yes, 2 - yes, only writable + bool m_bConfirmed; + int m_bFlip; + bool m_addNetworkShareEnabled; + bool m_flipEnabled; + std::string m_addSourceType; + bool m_browsingForImages; + bool m_useFileDirectories; + bool m_singleList; // if true, we have no shares or anything + bool m_multipleSelection; + std::vector<std::string> m_markedPath; + + CPictureThumbLoader m_thumbLoader; + CGUIViewControl m_viewControl; +}; diff --git a/xbmc/dialogs/GUIDialogGamepad.cpp b/xbmc/dialogs/GUIDialogGamepad.cpp new file mode 100644 index 0000000..07f5809 --- /dev/null +++ b/xbmc/dialogs/GUIDialogGamepad.cpp @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogGamepad.h" + +#include "ServiceBroker.h" +#include "guilib/GUIAudioManager.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/Key.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "utils/Digest.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +#include <utility> + +using namespace KODI::MESSAGING; +using KODI::UTILITY::CDigest; + +CGUIDialogGamepad::CGUIDialogGamepad(void) + : CGUIDialogBoxBase(WINDOW_DIALOG_GAMEPAD, "DialogConfirm.xml") +{ + m_bCanceled = false; + m_iRetries = 0; + m_bUserInputCleanup = true; + m_bHideInputChars = true; + m_cHideInputChar = '*'; +} + +CGUIDialogGamepad::~CGUIDialogGamepad(void) = default; + +void CGUIDialogGamepad::OnInitWindow() +{ + // hide all controls + for (int i = 0; i < DIALOG_MAX_CHOICES; ++i) + SET_CONTROL_HIDDEN(CONTROL_CHOICES_START + i); + SET_CONTROL_HIDDEN(CONTROL_PROGRESS_BAR); + + CGUIDialogBoxBase::OnInitWindow(); +} + +bool CGUIDialogGamepad::OnAction(const CAction &action) +{ + if ((action.GetButtonCode() >= KEY_BUTTON_A && + action.GetButtonCode() <= KEY_BUTTON_RIGHT_TRIGGER) || + (action.GetButtonCode() >= KEY_BUTTON_DPAD_UP && + action.GetButtonCode() <= KEY_BUTTON_DPAD_RIGHT) || + (action.GetID() >= ACTION_MOVE_LEFT && + action.GetID() <= ACTION_MOVE_DOWN) || + action.GetID() == ACTION_PLAYER_PLAY + ) + { + switch (action.GetButtonCode()) + { + case KEY_BUTTON_A : m_strUserInput += "A"; break; + case KEY_BUTTON_B : m_strUserInput += "B"; break; + case KEY_BUTTON_X : m_strUserInput += "X"; break; + case KEY_BUTTON_Y : m_strUserInput += "Y"; break; + case KEY_BUTTON_BLACK : m_strUserInput += "K"; break; + case KEY_BUTTON_WHITE : m_strUserInput += "W"; break; + case KEY_BUTTON_LEFT_TRIGGER : m_strUserInput += "("; break; + case KEY_BUTTON_RIGHT_TRIGGER : m_strUserInput += ")"; break; + case KEY_BUTTON_DPAD_UP : m_strUserInput += "U"; break; + case KEY_BUTTON_DPAD_DOWN : m_strUserInput += "D"; break; + case KEY_BUTTON_DPAD_LEFT : m_strUserInput += "L"; break; + case KEY_BUTTON_DPAD_RIGHT : m_strUserInput += "R"; break; + default: + switch (action.GetID()) + { + case ACTION_MOVE_LEFT: m_strUserInput += "L"; break; + case ACTION_MOVE_RIGHT: m_strUserInput += "R"; break; + case ACTION_MOVE_UP: m_strUserInput += "U"; break; + case ACTION_MOVE_DOWN: m_strUserInput += "D"; break; + case ACTION_PLAYER_PLAY: m_strUserInput += "P"; break; + default: + return true; + } + break; + } + + std::string strHiddenInput(m_strUserInput); + for (int i = 0; i < (int)strHiddenInput.size(); i++) + { + strHiddenInput[i] = m_cHideInputChar; + } + SetLine(2, CVariant{std::move(strHiddenInput)}); + return true; + } + else if (action.GetButtonCode() == KEY_BUTTON_BACK || action.GetID() == ACTION_PREVIOUS_MENU || action.GetID() == ACTION_NAV_BACK) + { + m_bConfirmed = false; + m_bCanceled = true; + m_strUserInput = ""; + m_bHideInputChars = true; + Close(); + return true; + } + else if (action.GetButtonCode() == KEY_BUTTON_START || action.GetID() == ACTION_SELECT_ITEM) + { + m_bConfirmed = false; + m_bCanceled = false; + + std::string md5pword2 = CDigest::Calculate(CDigest::Type::MD5, m_strUserInput); + + if (!StringUtils::EqualsNoCase(m_strPassword, md5pword2)) + { + // incorrect password entered + m_iRetries--; + + // don't clean up if the calling code wants the bad user input + if (m_bUserInputCleanup) + m_strUserInput = ""; + else + m_bUserInputCleanup = true; + + m_bHideInputChars = true; + Close(); + return true; + } + + // correct password entered + m_bConfirmed = true; + m_iRetries = 0; + m_strUserInput = ""; + m_bHideInputChars = true; + Close(); + return true; + } + else if (action.GetID() >= REMOTE_0 && action.GetID() <= REMOTE_9) + { + return true; // unhandled + } + else + { + return CGUIDialog::OnAction(action); + } +} + +bool CGUIDialogGamepad::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_WINDOW_INIT: + { + m_bConfirmed = false; + m_bCanceled = false; + m_cHideInputChar = g_localizeStrings.Get(12322).c_str()[0]; + CGUIDialog::OnMessage(message); + return true; + } + break; + + case GUI_MSG_CLICKED: + { + m_bConfirmed = false; + m_bCanceled = false; + } + break; + } + return CGUIDialogBoxBase::OnMessage(message); +} + +// \brief Show gamepad keypad and replace aTextString with user input. +// \param aTextString String to preload into the keyboard accumulator. Overwritten with user input if return=true. +// \param dlgHeading String shown on dialog title. Converts to localized string if contains a positive integer. +// \param bHideUserInput Masks user input as asterisks if set as true. Currently not yet implemented. +// \return true if successful display and user input. false if unsuccessful display, no user input, or canceled editing. +bool CGUIDialogGamepad::ShowAndGetInput(std::string& aTextString, const std::string &dlgHeading, bool bHideUserInput) +{ + // Prompt user for input + std::string strUserInput; + if (ShowAndVerifyInput(strUserInput, dlgHeading, aTextString, "", "", true, bHideUserInput)) + { + // user entry was blank + return false; + } + + if (strUserInput.empty()) + // user canceled out + return false; + + + // We should have a string to return + aTextString = strUserInput; + return true; +} + +// \brief Show gamepad keypad twice to get and confirm a user-entered password string. +// \param strNewPassword String to preload into the keyboard accumulator. Overwritten with user input if return=true. +// \return true if successful display and user input entry/re-entry. false if unsuccessful display, no user input, or canceled editing. +bool CGUIDialogGamepad::ShowAndVerifyNewPassword(std::string& strNewPassword) +{ + // Prompt user for password input + std::string strUserInput; + if (ShowAndVerifyInput(strUserInput, "12340", "12330", "12331", "", true, true)) + { + //! @todo Show error to user saying the password entry was blank + HELPERS::ShowOKDialogText(CVariant{12357}, CVariant{12358}); // Password is empty/blank + return false; + } + + if (strUserInput.empty()) + // user canceled out + return false; + + // Prompt again for password input, this time sending previous input as the password to verify + if (!ShowAndVerifyInput(strUserInput, "12341", "12330", "12331", "", false, true)) + { + //! @todo Show error to user saying the password re-entry failed + HELPERS::ShowOKDialogText(CVariant{12357}, CVariant{12344}); // Password do not match + return false; + } + + // password entry and re-entry succeeded + strNewPassword = strUserInput; + return true; +} + +// \brief Show gamepad keypad and verify user input against strPassword. +// \param strPassword Value to compare against user input. +// \param dlgHeading String shown on dialog title. Converts to localized string if contains a positive integer. +// \param iRetries If greater than 0, shows "Incorrect password, %d retries left" on dialog line 2, else line 2 is blank. +// \return 0 if successful display and user input. 1 if unsuccessful input. -1 if no user input or canceled editing. +int CGUIDialogGamepad::ShowAndVerifyPassword(std::string& strPassword, const std::string& dlgHeading, int iRetries) +{ + std::string strLine2; + if (0 < iRetries) + { + // Show a string telling user they have iRetries retries left + strLine2 = StringUtils::Format("{} {} {}", g_localizeStrings.Get(12342), iRetries, + g_localizeStrings.Get(12343)); + } + + // make a copy of strPassword to prevent from overwriting it later + std::string strPassTemp = strPassword; + if (ShowAndVerifyInput(strPassTemp, dlgHeading, g_localizeStrings.Get(12330), g_localizeStrings.Get(12331), strLine2, true, true)) + { + // user entered correct password + return 0; + } + + if (strPassTemp.empty()) + // user canceled out + return -1; + + // user must have entered an incorrect password + return 1; +} + +// \brief Show gamepad keypad and verify user input against strToVerify. +// \param strToVerify Value to compare against user input. +// \param dlgHeading String shown on dialog title. Converts to localized string if contains a positive integer. +// \param dlgLine0 String shown on dialog line 0. Converts to localized string if contains a positive integer. +// \param dlgLine1 String shown on dialog line 1. Converts to localized string if contains a positive integer. +// \param dlgLine2 String shown on dialog line 2. Converts to localized string if contains a positive integer. +// \param bGetUserInput If set as true and return=true, strToVerify is overwritten with user input string. +// \param bHideInputChars Masks user input as asterisks if set as true. Currently not yet implemented. +// \return true if successful display and user input. false if unsuccessful display, no user input, or canceled editing. +bool CGUIDialogGamepad::ShowAndVerifyInput(std::string& strToVerify, const std::string& dlgHeading, + const std::string& dlgLine0, const std::string& dlgLine1, + const std::string& dlgLine2, bool bGetUserInput, bool bHideInputChars) +{ + // Prompt user for password input + CGUIDialogGamepad *pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogGamepad>(WINDOW_DIALOG_GAMEPAD); + pDialog->m_strPassword = strToVerify; + pDialog->m_bUserInputCleanup = !bGetUserInput; + pDialog->m_bHideInputChars = bHideInputChars; + + // HACK: This won't work if the label specified is actually a positive numeric value, but that's very unlikely + if (!StringUtils::IsNaturalNumber(dlgHeading)) + pDialog->SetHeading(CVariant{dlgHeading}); + else + pDialog->SetHeading(CVariant{atoi(dlgHeading.c_str())}); + + if (!StringUtils::IsNaturalNumber(dlgLine0)) + pDialog->SetLine(0, CVariant{dlgLine0}); + else + pDialog->SetLine(0, CVariant{atoi(dlgLine0.c_str())}); + + if (!StringUtils::IsNaturalNumber(dlgLine1)) + pDialog->SetLine(1, CVariant{dlgLine1}); + else + pDialog->SetLine(1, CVariant{atoi(dlgLine1.c_str())}); + + if (!StringUtils::IsNaturalNumber(dlgLine2)) + pDialog->SetLine(2, CVariant{dlgLine2}); + else + pDialog->SetLine(2, CVariant{atoi(dlgLine2.c_str())}); + + CGUIComponent* gui = CServiceBroker::GetGUI(); + if (gui) + gui->GetAudioManager().Enable(false); // don't do sounds during pwd input + + pDialog->Open(); + + if (gui) + gui->GetAudioManager().Enable(true); + + if (bGetUserInput && !pDialog->IsCanceled()) + { + strToVerify = CDigest::Calculate(CDigest::Type::MD5, pDialog->m_strUserInput); + pDialog->m_strUserInput = ""; + } + + if (!pDialog->IsConfirmed() || pDialog->IsCanceled()) + { + // user canceled out or entered an incorrect password + return false; + } + + // user entered correct password + return true; +} + +bool CGUIDialogGamepad::IsCanceled() const +{ + return m_bCanceled; +} + diff --git a/xbmc/dialogs/GUIDialogGamepad.h b/xbmc/dialogs/GUIDialogGamepad.h new file mode 100644 index 0000000..db520ac --- /dev/null +++ b/xbmc/dialogs/GUIDialogGamepad.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "GUIDialogBoxBase.h" + +class CGUIDialogGamepad : + public CGUIDialogBoxBase +{ +public: + CGUIDialogGamepad(void); + ~CGUIDialogGamepad(void) override; + bool OnMessage(CGUIMessage& message) override; + bool IsCanceled() const; + std::string m_strUserInput; + std::string m_strPassword; + int m_iRetries; + bool m_bUserInputCleanup; + bool m_bHideInputChars; + static bool ShowAndGetInput(std::string& aTextString, const std::string& dlgHeading, bool bHideUserInput); + static bool ShowAndVerifyNewPassword(std::string& strNewPassword); + static int ShowAndVerifyPassword(std::string& strPassword, const std::string& dlgHeading, int iRetries); + static bool ShowAndVerifyInput(std::string& strPassword, const std::string& dlgHeading, const std::string& dlgLine0, const std::string& dlgLine1, const std::string& dlgLine2, bool bGetUserInput, bool bHideInputChars); +protected: + bool OnAction(const CAction &action) override; + void OnInitWindow() override; + bool m_bCanceled; + char m_cHideInputChar; +}; diff --git a/xbmc/dialogs/GUIDialogKaiToast.cpp b/xbmc/dialogs/GUIDialogKaiToast.cpp new file mode 100644 index 0000000..2f8ca00 --- /dev/null +++ b/xbmc/dialogs/GUIDialogKaiToast.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogKaiToast.h" + +#include "ServiceBroker.h" +#include "guilib/GUIFadeLabelControl.h" +#include "guilib/GUIMessage.h" +#include "peripherals/Peripherals.h" +#include "utils/TimeUtils.h" + +#include <mutex> + +#define POPUP_ICON 400 +#define POPUP_CAPTION_TEXT 401 +#define POPUP_NOTIFICATION_BUTTON 402 + +CGUIDialogKaiToast::TOASTQUEUE CGUIDialogKaiToast::m_notifications; +CCriticalSection CGUIDialogKaiToast::m_critical; + +CGUIDialogKaiToast::CGUIDialogKaiToast(void) + : CGUIDialog(WINDOW_DIALOG_KAI_TOAST, "DialogNotification.xml", DialogModalityType::MODELESS) +{ + m_loadType = LOAD_ON_GUI_INIT; + m_timer = 0; + m_toastDisplayTime = 0; + m_toastMessageTime = 0; +} + +CGUIDialogKaiToast::~CGUIDialogKaiToast(void) = default; + +bool CGUIDialogKaiToast::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_WINDOW_INIT: + { + CGUIDialog::OnMessage(message); + ResetTimer(); + return true; + } + break; + + case GUI_MSG_WINDOW_DEINIT: + { + } + break; + } + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogKaiToast::QueueNotification(eMessageType eType, const std::string& aCaption, const std::string& aDescription, unsigned int displayTime /*= TOAST_DISPLAY_TIME*/, bool withSound /*= true*/, unsigned int messageTime /*= TOAST_MESSAGE_TIME*/) +{ + AddToQueue("", eType, aCaption, aDescription, displayTime, withSound, messageTime); +} + +void CGUIDialogKaiToast::QueueNotification(const std::string& aCaption, const std::string& aDescription) +{ + QueueNotification("", aCaption, aDescription); +} + +void CGUIDialogKaiToast::QueueNotification(const std::string& aImageFile, const std::string& aCaption, const std::string& aDescription, unsigned int displayTime /*= TOAST_DISPLAY_TIME*/, bool withSound /*= true*/, unsigned int messageTime /*= TOAST_MESSAGE_TIME*/) +{ + AddToQueue(aImageFile, Default, aCaption, aDescription, displayTime, withSound, messageTime); +} + +void CGUIDialogKaiToast::AddToQueue(const std::string& aImageFile, const eMessageType eType, const std::string& aCaption, const std::string& aDescription, unsigned int displayTime /*= TOAST_DISPLAY_TIME*/, bool withSound /*= true*/, unsigned int messageTime /*= TOAST_MESSAGE_TIME*/) +{ + std::unique_lock<CCriticalSection> lock(m_critical); + + if (!m_notifications.empty()) + { + const auto& last = m_notifications.back(); + if (last.eType == eType && last.imagefile == aImageFile && last.caption == aCaption && + last.description == aDescription) + return; // avoid duplicates in a row. + } + + Notification toast; + toast.eType = eType; + toast.imagefile = aImageFile; + toast.caption = aCaption; + toast.description = aDescription; + toast.displayTime = displayTime > TOAST_MESSAGE_TIME + 500 ? displayTime : TOAST_MESSAGE_TIME + 500; + toast.messageTime = messageTime; + toast.withSound = withSound; + + m_notifications.push(toast); +} + +bool CGUIDialogKaiToast::DoWork() +{ + std::unique_lock<CCriticalSection> lock(m_critical); + + if (!m_notifications.empty() && + CTimeUtils::GetFrameTime() - m_timer > m_toastMessageTime) + { + // if we have a fade label control for the text to display, ensure the whole text was shown + // (scrolled to the end) before we move on to the next message + const CGUIFadeLabelControl* notificationText = + dynamic_cast<const CGUIFadeLabelControl*>(GetControl(POPUP_NOTIFICATION_BUTTON)); + if (notificationText && !notificationText->AllLabelsShown()) + return false; + + Notification toast = m_notifications.front(); + m_notifications.pop(); + lock.unlock(); + + m_toastDisplayTime = toast.displayTime; + m_toastMessageTime = toast.messageTime; + + std::unique_lock<CCriticalSection> lock2(CServiceBroker::GetWinSystem()->GetGfxContext()); + + if(!Initialize()) + return false; + + SET_CONTROL_LABEL(POPUP_CAPTION_TEXT, toast.caption); + + SET_CONTROL_LABEL(POPUP_NOTIFICATION_BUTTON, toast.description); + + // set the appropriate icon + { + std::string icon = toast.imagefile; + if (icon.empty()) + { + if (toast.eType == Warning) + icon = "DefaultIconWarning.png"; + else if (toast.eType == Error) + icon = "DefaultIconError.png"; + else + icon = "DefaultIconInfo.png"; + } + SET_CONTROL_FILENAME(POPUP_ICON, icon); + } + + // Play the window specific init sound for each notification queued + SetSound(toast.withSound); + + // Activate haptics for this notification + CServiceBroker::GetPeripherals().OnUserNotification(); + + ResetTimer(); + return true; + } + + return false; +} + + +void CGUIDialogKaiToast::ResetTimer() +{ + m_timer = CTimeUtils::GetFrameTime(); +} + +void CGUIDialogKaiToast::FrameMove() +{ + // Fading does not count as display time + if (IsAnimating(ANIM_TYPE_WINDOW_OPEN)) + ResetTimer(); + + // now check if we should exit + if (CTimeUtils::GetFrameTime() - m_timer > m_toastDisplayTime) + { + bool bClose = true; + + // if we have a fade label control for the text to display, ensure the whole text was shown + // (scrolled to the end) before we're closing the toast dialog + const CGUIFadeLabelControl* notificationText = + dynamic_cast<const CGUIFadeLabelControl*>(GetControl(POPUP_NOTIFICATION_BUTTON)); + if (notificationText) + { + std::unique_lock<CCriticalSection> lock(m_critical); + bClose = notificationText->AllLabelsShown() && m_notifications.empty(); + } + + if (bClose) + Close(); + } + + CGUIDialog::FrameMove(); +} diff --git a/xbmc/dialogs/GUIDialogKaiToast.h b/xbmc/dialogs/GUIDialogKaiToast.h new file mode 100644 index 0000000..78f2919 --- /dev/null +++ b/xbmc/dialogs/GUIDialogKaiToast.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +#include <queue> + +#define TOAST_DISPLAY_TIME 5000L // default 5 seconds +#define TOAST_MESSAGE_TIME 1000L // minimal message time 1 second + +class CGUIDialogKaiToast: public CGUIDialog +{ +public: + CGUIDialogKaiToast(void); + ~CGUIDialogKaiToast(void) override; + + enum eMessageType { Default = 0, Info, Warning, Error }; + + struct Notification + { + std::string caption; + std::string description; + std::string imagefile; + eMessageType eType; + unsigned int displayTime; + unsigned int messageTime; + bool withSound; + }; + + typedef std::queue<Notification> TOASTQUEUE; + + static void QueueNotification(eMessageType eType, const std::string& aCaption, const std::string& aDescription, unsigned int displayTime = TOAST_DISPLAY_TIME, bool withSound = true, unsigned int messageTime = TOAST_MESSAGE_TIME); + static void QueueNotification(const std::string& aCaption, const std::string& aDescription); + static void QueueNotification(const std::string& aImageFile, const std::string& aCaption, const std::string& aDescription, unsigned int displayTime = TOAST_DISPLAY_TIME, bool withSound = true, unsigned int messageTime = TOAST_MESSAGE_TIME); + bool DoWork(); + + bool OnMessage(CGUIMessage& message) override; + void FrameMove() override; + void ResetTimer(); + +protected: + static void AddToQueue(const std::string& aImageFile, const eMessageType eType, const std::string& aCaption, const std::string& aDescription, unsigned int displayTime, bool withSound, unsigned int messageTime); + + unsigned int m_timer; + + unsigned int m_toastDisplayTime; + unsigned int m_toastMessageTime; + + static TOASTQUEUE m_notifications; + static CCriticalSection m_critical; +}; diff --git a/xbmc/dialogs/GUIDialogKeyboardGeneric.cpp b/xbmc/dialogs/GUIDialogKeyboardGeneric.cpp new file mode 100644 index 0000000..5eeed8f --- /dev/null +++ b/xbmc/dialogs/GUIDialogKeyboardGeneric.cpp @@ -0,0 +1,843 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogKeyboardGeneric.h" + +#include "GUIDialogNumeric.h" +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIEditControl.h" +#include "guilib/GUILabelControl.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/InputCodingTable.h" +#include "input/Key.h" +#include "input/KeyboardLayoutManager.h" +#include "input/XBMC_vkeys.h" +#include "interfaces/AnnouncementManager.h" +#include "messaging/ApplicationMessenger.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "speech/ISpeechRecognition.h" +#include "speech/ISpeechRecognitionListener.h" +#include "speech/SpeechRecognitionErrors.h" +#include "utils/CharsetConverter.h" +#include "utils/RegExp.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "windowing/WinSystem.h" + +#include <mutex> + +using namespace KODI::MESSAGING; + +#define BUTTON_ID_OFFSET 100 +#define BUTTONS_PER_ROW 20 +#define BUTTONS_MAX_ROWS 4 + +#define CTL_BUTTON_DONE 300 +#define CTL_BUTTON_CANCEL 301 +#define CTL_BUTTON_SHIFT 302 +#define CTL_BUTTON_CAPS 303 +#define CTL_BUTTON_SYMBOLS 304 +#define CTL_BUTTON_LEFT 305 +#define CTL_BUTTON_RIGHT 306 +#define CTL_BUTTON_IP_ADDRESS 307 +#define CTL_BUTTON_CLEAR 308 +#define CTL_BUTTON_LAYOUT 309 +#define CTL_BUTTON_REVEAL 310 +#define CTL_LABEL_HEADING 311 +#define CTL_EDIT 312 +#define CTL_LABEL_HZCODE 313 +#define CTL_LABEL_HZLIST 314 + +#define CTL_BUTTON_BACKSPACE 8 +#define CTL_BUTTON_SPACE 32 + +#define SEARCH_DELAY 1000 + +class CSpeechRecognitionListener : public speech::ISpeechRecognitionListener +{ +public: + CSpeechRecognitionListener(int dialogId) : m_dialogId(dialogId) {} + + void OnReadyForSpeech() override + { + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, + g_localizeStrings.Get(39177), // Speech to text + g_localizeStrings.Get(39179)); // Listening... + } + + void OnError(int recognitionError) override + { + uint32_t msgId = 0; + switch (recognitionError) + { + case speech::RecognitionError::SERVICE_NOT_AVAILABLE: + msgId = 39178; // Speech recognition service not available + break; + case speech::RecognitionError::NO_MATCH: + msgId = 39180; // No recognition result matched + break; + case speech::RecognitionError::INSUFFICIENT_PERMISSIONS: + msgId = 39185; // Insufficient permissions for speech recognition + break; + default: + msgId = 39181; // Speech recognition error + break; + } + + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, + g_localizeStrings.Get(39177), // Speech to text + g_localizeStrings.Get(msgId)); + } + + void OnResults(const std::vector<std::string>& results) override + { + if (!results.empty()) + { + CGUIMessage msg(GUI_MSG_SET_TEXT, m_dialogId, CTL_EDIT); + msg.SetLabel(results.front()); + + // dispatch to GUI thread + CServiceBroker::GetAppMessenger()->SendGUIMessage(msg, m_dialogId); + } + } + +private: + const int m_dialogId{0}; +}; + +CGUIDialogKeyboardGeneric::CGUIDialogKeyboardGeneric() +: CGUIDialog(WINDOW_DIALOG_KEYBOARD, "DialogKeyboard.xml") +, CGUIKeyboard() +, m_pCharCallback(NULL) +{ + m_bIsConfirmed = false; + m_bShift = false; + m_hiddenInput = false; + m_keyType = LOWER; + m_currentLayout = 0; + m_loadType = KEEP_IN_MEMORY; + m_isKeyboardNavigationMode = false; + m_previouslyFocusedButton = 0; + m_pos = 0; + m_listwidth = 600; +} + +void CGUIDialogKeyboardGeneric::OnWindowLoaded() +{ + CGUIEditControl *edit = static_cast<CGUIEditControl*>(GetControl(CTL_EDIT)); + if (edit) + { + // add control CTL_LABEL_HZCODE and CTL_LABEL_HZLIST if not exist + CGUIControlGroup *ParentControl = static_cast<CGUIControlGroup*>(edit->GetParentControl()); + CLabelInfo labelInfo = edit->GetLabelInfo(); + float px = edit->GetXPosition(); + float py = edit->GetYPosition(); + float pw = edit->GetWidth(); + float ph = edit->GetHeight(); + + CGUILabelControl* control = static_cast<CGUILabelControl*>(GetControl(CTL_LABEL_HZCODE)); + if (!control) + { + control = new CGUILabelControl(GetID(), CTL_LABEL_HZCODE, px, py + ph, 90, 30, labelInfo, false, false); + ParentControl->AddControl(control); + } + + control = static_cast<CGUILabelControl*>(GetControl(CTL_LABEL_HZLIST)); + if (!control) + { + labelInfo.align = XBFONT_CENTER_Y; + control = new CGUILabelControl(GetID(), CTL_LABEL_HZLIST, px + 95, py + ph, pw - 95, 30, labelInfo, false, false); + ParentControl->AddControl(control); + } + } + + CGUIDialog::OnWindowLoaded(); +} + +void CGUIDialogKeyboardGeneric::OnInitWindow() +{ + CGUIDialog::OnInitWindow(); + + m_bIsConfirmed = false; + m_isKeyboardNavigationMode = false; + + // fill in the keyboard layouts + m_currentLayout = 0; + m_layouts.clear(); + const KeyboardLayouts& keyboardLayouts = CServiceBroker::GetKeyboardLayoutManager()->GetLayouts(); + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + std::vector<CVariant> layoutNames = settings->GetList(CSettings::SETTING_LOCALE_KEYBOARDLAYOUTS); + std::string activeLayout = settings->GetString(CSettings::SETTING_LOCALE_ACTIVEKEYBOARDLAYOUT); + + for (const auto& layoutName : layoutNames) + { + const auto keyboardLayout = keyboardLayouts.find(layoutName.asString()); + if (keyboardLayout != keyboardLayouts.end()) + { + m_layouts.emplace_back(keyboardLayout->second); + if (layoutName.asString() == activeLayout) + m_currentLayout = m_layouts.size() - 1; + } + } + + // set alphabetic (capitals) + UpdateButtons(); + + // set heading + if (!m_strHeading.empty()) + { + SET_CONTROL_LABEL(CTL_LABEL_HEADING, m_strHeading); + SET_CONTROL_VISIBLE(CTL_LABEL_HEADING); + } + else + { + SET_CONTROL_HIDDEN(CTL_LABEL_HEADING); + } + // set type + { + CGUIMessage msg(GUI_MSG_SET_TYPE, GetID(), CTL_EDIT, m_hiddenInput ? CGUIEditControl::INPUT_TYPE_PASSWORD : CGUIEditControl::INPUT_TYPE_TEXT); + OnMessage(msg); + } + if (m_hiddenInput) + { + SET_CONTROL_VISIBLE(CTL_BUTTON_REVEAL); + SET_CONTROL_LABEL(CTL_BUTTON_REVEAL, g_localizeStrings.Get(12308)); + } + else + SET_CONTROL_HIDDEN(CTL_BUTTON_REVEAL); + + SetEditText(m_text); + + // get HZLIST label options + CGUILabelControl* pEdit = static_cast<CGUILabelControl*>(GetControl(CTL_LABEL_HZLIST)); + CLabelInfo labelInfo = pEdit->GetLabelInfo(); + m_listfont = labelInfo.font; + m_listwidth = pEdit->GetWidth(); + m_hzcode.clear(); + m_words.clear(); + SET_CONTROL_LABEL(CTL_LABEL_HZCODE, ""); + SET_CONTROL_LABEL(CTL_LABEL_HZLIST, ""); + + CVariant data; + data["title"] = m_strHeading; + data["type"] = !m_hiddenInput ? "keyboard" : "password"; + data["value"] = GetText(); + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Input, "OnInputRequested", data); +} + +bool CGUIDialogKeyboardGeneric::OnAction(const CAction &action) +{ + int actionId = action.GetID(); + bool handled = true; + if (actionId == (KEY_VKEY | XBMCVK_BACK)) + Backspace(); + else if (actionId == ACTION_ENTER || + (actionId == ACTION_SELECT_ITEM && (m_isKeyboardNavigationMode || GetFocusedControlID() == CTL_EDIT))) + OnOK(); + else if (actionId == ACTION_SHIFT) + OnShift(); + else if (actionId == ACTION_SYMBOLS) + OnSymbols(); + // don't handle move left/right and select in the edit control + else if (!m_isKeyboardNavigationMode && + (actionId == ACTION_MOVE_LEFT || + actionId == ACTION_MOVE_RIGHT || + actionId == ACTION_SELECT_ITEM)) + handled = false; + else if (actionId == ACTION_VOICE_RECOGNIZE) + OnVoiceRecognition(); + else + { + std::wstring wch = L""; + wch.insert(wch.begin(), action.GetUnicode()); + std::string ch; + g_charsetConverter.wToUTF8(wch, ch); + handled = CodingCharacter(ch); + if (!handled) + { + // send action to edit control + CGUIControl *edit = GetControl(CTL_EDIT); + if (edit) + handled = edit->OnAction(action); + if (!handled && actionId >= KEY_VKEY && actionId < KEY_UNICODE) + { + unsigned char b = actionId & 0xFF; + if (b == XBMCVK_TAB) + { + // Toggle left/right key mode + m_isKeyboardNavigationMode = !m_isKeyboardNavigationMode; + if (m_isKeyboardNavigationMode) + { + m_previouslyFocusedButton = GetFocusedControlID(); + SET_CONTROL_FOCUS(edit->GetID(), 0); + } + else + SET_CONTROL_FOCUS(m_previouslyFocusedButton, 0); + handled = true; + } + } + } + } + + if (!handled) // unhandled by us - let's see if the baseclass wants it + handled = CGUIDialog::OnAction(action); + + return handled; +} + +bool CGUIDialogKeyboardGeneric::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + + switch (iControl) + { + case CTL_BUTTON_DONE: + OnOK(); + break; + case CTL_BUTTON_CANCEL: + Close(); + break; + case CTL_BUTTON_SHIFT: + OnShift(); + break; + case CTL_BUTTON_CAPS: + if (m_keyType == LOWER) + m_keyType = CAPS; + else if (m_keyType == CAPS) + m_keyType = LOWER; + UpdateButtons(); + break; + case CTL_BUTTON_LAYOUT: + OnLayout(); + break; + case CTL_BUTTON_REVEAL: + OnReveal(); + break; + case CTL_BUTTON_SYMBOLS: + OnSymbols(); + break; + case CTL_BUTTON_LEFT: + MoveCursor( -1); + break; + case CTL_BUTTON_RIGHT: + MoveCursor(1); + break; + case CTL_BUTTON_IP_ADDRESS: + OnIPAddress(); + break; + case CTL_BUTTON_CLEAR: + SetEditText(""); + break; + case CTL_EDIT: + { + CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), CTL_EDIT); + OnMessage(msg); + // update callback I guess? + if (m_pCharCallback) + { // we did _something_, so make sure our search message filter is reset + m_pCharCallback(this, msg.GetLabel()); + } + m_text = msg.GetLabel(); + return true; + } + default: + OnClickButton(iControl); + break; + } + } + break; + + case GUI_MSG_SET_TEXT: + { + // the edit control only handles these messages if it is either focused + // or its specific control ID is set in the message. As neither is the + // case here (focus is on one of the keyboard buttons) we have to force + // the control ID of the message to the control ID of the edit control + // (unfortunately we have to create a whole copy of the message object for that) + CGUIMessage messageCopy(message.GetMessage(), message.GetSenderId(), CTL_EDIT, message.GetParam1(), message.GetParam2(), message.GetItem()); + messageCopy.SetLabel(message.GetLabel()); + + // ensure this goes to the edit control + CGUIControl *edit = GetControl(CTL_EDIT); + if (edit) + edit->OnMessage(messageCopy); + + // close the dialog if requested + if (message.GetMessage() == GUI_MSG_SET_TEXT && message.GetParam1() > 0) + OnOK(); + return true; + } + case GUI_MSG_CODINGTABLE_LOOKUP_COMPLETED: + { + const std::string& code = message.GetStringParam(); + if (code == m_hzcode) + { + int response = message.GetParam1(); + auto words = m_codingtable->GetResponse(response); + m_words.insert(m_words.end(), words.begin(), words.end()); + ShowWordList(0); + } + } + } + + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogKeyboardGeneric::SetEditText(const std::string &text) +{ + CGUIMessage msg(GUI_MSG_SET_TEXT, GetID(), CTL_EDIT); + msg.SetLabel(text); + OnMessage(msg); +} + +void CGUIDialogKeyboardGeneric::SetText(const std::string& text) +{ + m_text = text; +} + +const std::string &CGUIDialogKeyboardGeneric::GetText() const +{ + return m_text; +} + +void CGUIDialogKeyboardGeneric::Character(const std::string &ch) +{ + if (ch.empty()) return; + if (!CodingCharacter(ch)) + NormalCharacter(ch); +} + +void CGUIDialogKeyboardGeneric::NormalCharacter(const std::string &ch) +{ + // send text to edit control + CGUIControl *edit = GetControl(CTL_EDIT); + if (edit) + { + CAction action(ACTION_INPUT_TEXT); + action.SetText(ch); + edit->OnAction(action); + } +} + +void CGUIDialogKeyboardGeneric::Backspace() +{ + if (m_codingtable && m_hzcode.length() > 0) + { + std::wstring tmp; + g_charsetConverter.utf8ToW(m_hzcode, tmp); + tmp.erase(tmp.length() - 1, 1); + g_charsetConverter.wToUTF8(tmp, m_hzcode); + + switch (m_codingtable->GetType()) + { + case IInputCodingTable::TYPE_WORD_LIST: + SetControlLabel(CTL_LABEL_HZCODE, m_hzcode); + ChangeWordList(0); + break; + + case IInputCodingTable::TYPE_CONVERT_STRING: + SetEditText(m_codingtable->ConvertString(m_hzcode)); + break; + } + } + else + { + // send action to edit control + CGUIControl *edit = GetControl(CTL_EDIT); + if (edit) + edit->OnAction(CAction(ACTION_BACKSPACE)); + + if (m_codingtable && m_codingtable->GetType() == IInputCodingTable::TYPE_CONVERT_STRING) + m_codingtable->SetTextPrev(GetText()); + } +} + +void CGUIDialogKeyboardGeneric::OnClickButton(int iButtonControl) +{ + if (iButtonControl == CTL_BUTTON_BACKSPACE) + { + Backspace(); + } + else if (iButtonControl == CTL_BUTTON_SPACE) + { + Character(" "); + } + else + { + const CGUIControl* pButton = GetControl(iButtonControl); + // Do not register input for buttons with id >= 500 + if (pButton && iButtonControl < 500) + { + Character(pButton->GetDescription()); + // reset the shift keys + if (m_bShift) OnShift(); + } + } +} + +void CGUIDialogKeyboardGeneric::UpdateButtons() +{ + SET_CONTROL_SELECTED(GetID(), CTL_BUTTON_SHIFT, m_bShift); + SET_CONTROL_SELECTED(GetID(), CTL_BUTTON_CAPS, m_keyType == CAPS); + SET_CONTROL_SELECTED(GetID(), CTL_BUTTON_SYMBOLS, m_keyType == SYMBOLS); + + if (m_currentLayout >= m_layouts.size()) + m_currentLayout = 0; + CKeyboardLayout layout = m_layouts.empty() ? CKeyboardLayout() : m_layouts[m_currentLayout]; + m_codingtable = layout.GetCodingTable(); + if (m_codingtable && !m_codingtable->IsInitialized()) + m_codingtable->Initialize(); + + bool bShowWordList = false; + if (m_codingtable) + { + switch (m_codingtable->GetType()) + { + case IInputCodingTable::TYPE_WORD_LIST: + bShowWordList = true; + break; + + case IInputCodingTable::TYPE_CONVERT_STRING: + m_codingtable->SetTextPrev(GetText()); + m_hzcode.clear(); + break; + } + } + + if (bShowWordList) + { + SET_CONTROL_VISIBLE(CTL_LABEL_HZCODE); + SET_CONTROL_VISIBLE(CTL_LABEL_HZLIST); + } + else + { + SET_CONTROL_HIDDEN(CTL_LABEL_HZCODE); + SET_CONTROL_HIDDEN(CTL_LABEL_HZLIST); + } + SET_CONTROL_LABEL(CTL_BUTTON_LAYOUT, layout.GetName()); + + unsigned int modifiers = CKeyboardLayout::ModifierKeyNone; + if ((m_keyType == CAPS && !m_bShift) || (m_keyType == LOWER && m_bShift)) + modifiers |= CKeyboardLayout::ModifierKeyShift; + if (m_keyType == SYMBOLS) + { + modifiers |= CKeyboardLayout::ModifierKeySymbol; + if (m_bShift) + modifiers |= CKeyboardLayout::ModifierKeyShift; + } + + for (unsigned int row = 0; row < BUTTONS_MAX_ROWS; row++) + { + for (unsigned int column = 0; column < BUTTONS_PER_ROW; column++) + { + int buttonID = (row * BUTTONS_PER_ROW) + column + BUTTON_ID_OFFSET; + std::string label = layout.GetCharAt(row, column, modifiers); + SetControlLabel(buttonID, label); + if (!label.empty()) + SET_CONTROL_VISIBLE(buttonID); + else + SET_CONTROL_HIDDEN(buttonID); + } + } +} + +void CGUIDialogKeyboardGeneric::OnDeinitWindow(int nextWindowID) +{ + for (auto& layout : m_layouts) + { + auto codingTable = layout.GetCodingTable(); + if (codingTable && codingTable->IsInitialized()) + codingTable->Deinitialize(); + } + // call base class + CGUIDialog::OnDeinitWindow(nextWindowID); + // reset the heading (we don't always have this) + m_strHeading = ""; + + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Input, "OnInputFinished"); +} + +void CGUIDialogKeyboardGeneric::MoveCursor(int iAmount) +{ + if (m_codingtable && m_words.size()) + ChangeWordList(iAmount); + else + { + CGUIControl *edit = GetControl(CTL_EDIT); + if (edit) + edit->OnAction(CAction(iAmount < 0 ? ACTION_CURSOR_LEFT : ACTION_CURSOR_RIGHT)); + } +} + +void CGUIDialogKeyboardGeneric::OnLayout() +{ + m_currentLayout++; + if (m_currentLayout >= m_layouts.size()) + m_currentLayout = 0; + CKeyboardLayout layout = m_layouts.empty() ? CKeyboardLayout() : m_layouts[m_currentLayout]; + CServiceBroker::GetSettingsComponent()->GetSettings()->SetString(CSettings::SETTING_LOCALE_ACTIVEKEYBOARDLAYOUT, layout.GetName()); + UpdateButtons(); +} + +void CGUIDialogKeyboardGeneric::OnSymbols() +{ + if (m_keyType == SYMBOLS) + m_keyType = LOWER; + else + m_keyType = SYMBOLS; + UpdateButtons(); +} + +void CGUIDialogKeyboardGeneric::OnReveal() +{ + m_hiddenInput = !m_hiddenInput; + SET_CONTROL_LABEL(CTL_BUTTON_REVEAL, g_localizeStrings.Get(m_hiddenInput ? 12308 : 12309)); + CGUIMessage msg(GUI_MSG_SET_TYPE, GetID(), CTL_EDIT, + m_hiddenInput ? CGUIEditControl::INPUT_TYPE_PASSWORD + : CGUIEditControl::INPUT_TYPE_TEXT); + OnMessage(msg); +} + +void CGUIDialogKeyboardGeneric::OnShift() +{ + m_bShift = !m_bShift; + UpdateButtons(); +} + +void CGUIDialogKeyboardGeneric::OnIPAddress() +{ + // find any IP address in the current string if there is any + // We match to #.#.#.# + std::string text = GetText(); + std::string ip; + CRegExp reg; + reg.RegComp("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+"); + int start = reg.RegFind(text.c_str()); + int length = 0; + if (start > -1) + { + length = reg.GetSubLength(0); + ip = text.substr(start, length); + } + else + start = text.size(); + if (CGUIDialogNumeric::ShowAndGetIPAddress(ip, g_localizeStrings.Get(14068))) + SetEditText(text.substr(0, start) + ip.c_str() + text.substr(start + length)); +} + +void CGUIDialogKeyboardGeneric::OnVoiceRecognition() +{ + const auto speechRecognition = CServiceBroker::GetSpeechRecognition(); + if (speechRecognition) + { + if (!m_speechRecognitionListener) + m_speechRecognitionListener = std::make_shared<CSpeechRecognitionListener>(GetID()); + + speechRecognition->StartSpeechRecognition(m_speechRecognitionListener); + } + else + { + CLog::LogF(LOGWARNING, "No voice recognition implementation available."); + } +} + +void CGUIDialogKeyboardGeneric::SetControlLabel(int id, const std::string &label) +{ // find all controls with this id, and set all their labels + CGUIMessage message(GUI_MSG_LABEL_SET, GetID(), id); + message.SetLabel(label); + for (unsigned int i = 0; i < m_children.size(); i++) + { + if (m_children[i]->GetID() == id || m_children[i]->IsGroup()) + m_children[i]->OnMessage(message); + } +} + +void CGUIDialogKeyboardGeneric::OnOK() +{ + m_bIsConfirmed = true; + Close(); +} + +void CGUIDialogKeyboardGeneric::SetHeading(const std::string &heading) +{ + m_strHeading = heading; +} + +int CGUIDialogKeyboardGeneric::GetWindowId() const +{ + return GetID(); +} + +void CGUIDialogKeyboardGeneric::Cancel() +{ + m_bIsConfirmed = false; + Close(); +} + +bool CGUIDialogKeyboardGeneric::ShowAndGetInput(char_callback_t pCallback, const std::string &initialString, std::string &typedString, const std::string &heading, bool bHiddenInput) +{ + CGUIDialogKeyboardGeneric *pKeyboard = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogKeyboardGeneric>(WINDOW_DIALOG_KEYBOARD); + + if (!pKeyboard) + return false; + + m_pCharCallback = pCallback; + // setup keyboard + pKeyboard->Initialize(); + pKeyboard->SetHeading(heading); + pKeyboard->SetHiddenInput(bHiddenInput); + pKeyboard->SetText(initialString); + pKeyboard->Open(); + pKeyboard->Close(); + + // If have text - update this. + if (pKeyboard->IsConfirmed()) + { + typedString = pKeyboard->GetText(); + return true; + } + else return false; +} + +float CGUIDialogKeyboardGeneric::GetStringWidth(const std::wstring & utf16) +{ + vecText utf32; + + utf32.resize(utf16.size()); + for (unsigned int i = 0; i < utf16.size(); i++) + utf32[i] = utf16[i]; + + return m_listfont->GetTextWidth(utf32); +} + +void CGUIDialogKeyboardGeneric::ChangeWordList(int direct) +{ + if (direct == 0) + { + m_pos = 0; + m_words.clear(); + m_codingtable->GetWordListPage(m_hzcode, true); + } + else + { + ShowWordList(direct); + if (direct > 0 && m_pos + m_num == static_cast<int>(m_words.size())) + m_codingtable->GetWordListPage(m_hzcode, false); + } +} + +void CGUIDialogKeyboardGeneric::ShowWordList(int direct) +{ + std::unique_lock<CCriticalSection> lock(m_CS); + std::wstring hzlist = L""; + CServiceBroker::GetWinSystem()->GetGfxContext().SetScalingResolution(m_coordsRes, true); + float width = m_listfont->GetCharWidth(L'<') + m_listfont->GetCharWidth(L'>'); + float spacewidth = m_listfont->GetCharWidth(L' '); + float numwidth = m_listfont->GetCharWidth(L'1') + m_listfont->GetCharWidth(L'.'); + int i; + + if (direct >= 0) + { + if (direct > 0) + m_pos += m_num; + if (m_pos > static_cast<int>(m_words.size()) - 1) + m_pos = 0; + for (i = 0; m_pos + i < static_cast<int>(m_words.size()); i++) + { + if ((i > 0 && width + GetStringWidth(m_words[m_pos + i]) + numwidth > m_listwidth) || i > 9) + break; + hzlist.insert(hzlist.length(), 1, (wchar_t)(i + 48)); + hzlist.insert(hzlist.length(), 1, L'.'); + hzlist.append(m_words[m_pos + i]); + hzlist.insert(hzlist.length(), 1, L' '); + width += GetStringWidth(m_words[m_pos + i]) + numwidth + spacewidth; + } + m_num = i; + } + else + { + if (m_pos == 0) + return; + for (i = 1; i <= 10; i++) + { + if (m_pos - i < 0 || (i > 1 && width + GetStringWidth(m_words[m_pos - i]) + numwidth > m_listwidth)) + break; + width += GetStringWidth(m_words[m_pos - i]) + numwidth + spacewidth; + } + m_num = --i; + m_pos -= m_num; + for (i = 0; i < m_num; i++) + { + hzlist.insert(hzlist.length(), 1, (wchar_t)(i + 48)); + hzlist.insert(hzlist.length(), 1, L'.'); + hzlist.append(m_words[m_pos + i]); + hzlist.insert(hzlist.length(), 1, L' '); + } + } + hzlist.erase(hzlist.find_last_not_of(L' ') + 1); + if (m_pos > 0) + hzlist.insert(0, 1, L'<'); + if (m_pos + m_num < static_cast<int>(m_words.size())) + hzlist.insert(hzlist.length(), 1, L'>'); + std::string utf8String; + g_charsetConverter.wToUTF8(hzlist, utf8String); + SET_CONTROL_LABEL(CTL_LABEL_HZLIST, utf8String); +} + +bool CGUIDialogKeyboardGeneric::CodingCharacter(const std::string &ch) +{ + if (!m_codingtable) + return false; + + switch (m_codingtable->GetType()) + { + case IInputCodingTable::TYPE_CONVERT_STRING: + if (!ch.empty() && ch[0] != 0) + { + m_hzcode += ch; + SetEditText(m_codingtable->ConvertString(m_hzcode)); + return true; + } + break; + + case IInputCodingTable::TYPE_WORD_LIST: + if (m_codingtable->GetCodeChars().find(ch) != std::string::npos) + { + m_hzcode += ch; + SetControlLabel(CTL_LABEL_HZCODE, m_hzcode); + ChangeWordList(0); + return true; + } + if (ch[0] >= '0' && ch[0] <= '9') + { + int i = m_pos + (int)ch[0] - 48; + if (i < (m_pos + m_num)) + { + m_hzcode = ""; + SetControlLabel(CTL_LABEL_HZCODE, m_hzcode); + std::string utf8String; + g_charsetConverter.wToUTF8(m_words[i], utf8String); + NormalCharacter(utf8String); + } + return true; + } + break; + } + + return false; +} diff --git a/xbmc/dialogs/GUIDialogKeyboardGeneric.h b/xbmc/dialogs/GUIDialogKeyboardGeneric.h new file mode 100644 index 0000000..c88d056 --- /dev/null +++ b/xbmc/dialogs/GUIDialogKeyboardGeneric.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" +#include "guilib/GUIKeyboard.h" +#include "input/KeyboardLayout.h" + +#include <memory> +#include <string> +#include <vector> + +class CGUIFont; + +class CSpeechRecognitionListener; + +enum KEYBOARD {CAPS, LOWER, SYMBOLS}; + +class CGUIDialogKeyboardGeneric : public CGUIDialog, public CGUIKeyboard +{ + public: + CGUIDialogKeyboardGeneric(); + + //CGUIKeyboard Interface + bool ShowAndGetInput(char_callback_t pCallback, const std::string &initialString, std::string &typedString, const std::string &heading, bool bHiddenInput) override; + void Cancel() override; + int GetWindowId() const override; + + void SetHeading(const std::string& heading); + void SetText(const std::string& text); + const std::string &GetText() const; + bool IsConfirmed() { return m_bIsConfirmed; } + void SetHiddenInput(bool hiddenInput) { m_hiddenInput = hiddenInput; } + bool IsInputHidden() const { return m_hiddenInput; } + + protected: + void OnWindowLoaded() override; + void OnInitWindow() override; + bool OnAction(const CAction &action) override; + bool OnMessage(CGUIMessage& message) override; + void OnDeinitWindow(int nextWindowID) override; + void SetControlLabel(int id, const std::string &label); + void OnShift(); + void MoveCursor(int iAmount); + void OnLayout(); + void OnReveal(); + void OnSymbols(); + void OnIPAddress(); + void OnVoiceRecognition(); + void OnOK(); + + private: + void OnClickButton(int iButtonControl); + void UpdateButtons(); + void Character(const std::string &ch); + void Backspace(); + void SetEditText(const std::string& text); + float GetStringWidth(const std::wstring& utf16); + void ChangeWordList(int direct); // direct: 0 - first page, 1 - next page, -1 - prev page + void ShowWordList(int which); // which: 0 - current page, 1 - next page, -1 -prev page + bool CodingCharacter(const std::string &ch); + void NormalCharacter(const std::string &ch); + + bool m_bIsConfirmed; + KEYBOARD m_keyType; + bool m_bShift; + bool m_hiddenInput; + bool m_isKeyboardNavigationMode; + int m_previouslyFocusedButton; + + std::vector<CKeyboardLayout> m_layouts; + unsigned int m_currentLayout; + + std::string m_strHeading; + std::string m_text; ///< current text + + IInputCodingTablePtr m_codingtable; + std::vector<std::wstring> m_words; + std::string m_hzcode; + int m_pos; + int m_num = 0; + float m_listwidth; + CGUIFont *m_listfont = nullptr; + CCriticalSection m_CS; + + char_callback_t m_pCharCallback; + + std::shared_ptr<CSpeechRecognitionListener> m_speechRecognitionListener; +}; diff --git a/xbmc/dialogs/GUIDialogKeyboardTouch.cpp b/xbmc/dialogs/GUIDialogKeyboardTouch.cpp new file mode 100644 index 0000000..e9511ce --- /dev/null +++ b/xbmc/dialogs/GUIDialogKeyboardTouch.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogKeyboardTouch.h" +#if defined(TARGET_DARWIN_EMBEDDED) +#include "platform/darwin/ios-common/DarwinEmbedKeyboard.h" +#endif + +CGUIDialogKeyboardTouch::CGUIDialogKeyboardTouch() +: CGUIDialog(WINDOW_DIALOG_KEYBOARD_TOUCH, "") +, CGUIKeyboard() +, CThread("keyboard") +, m_pCharCallback(NULL) +{ +} + +bool CGUIDialogKeyboardTouch::ShowAndGetInput(char_callback_t pCallback, const std::string &initialString, std::string &typedString, const std::string &heading, bool bHiddenInput) +{ +#if defined(TARGET_DARWIN_EMBEDDED) + m_keyboard.reset(new CDarwinEmbedKeyboard()); +#endif + + if (!m_keyboard) + return false; + + m_pCharCallback = pCallback; + m_initialString = initialString; + m_typedString = typedString; + m_heading = heading; + m_bHiddenInput = bHiddenInput; + + m_confirmed = false; + Initialize(); + Open(); + + m_keyboard.reset(); + + if (m_confirmed) + { + typedString = m_typedString; + return true; + } + + return false; +} + +bool CGUIDialogKeyboardTouch::SetTextToKeyboard(const std::string &text, bool closeKeyboard) +{ + if (m_keyboard) + return m_keyboard->SetTextToKeyboard(text, closeKeyboard); + + return false; +} + +void CGUIDialogKeyboardTouch::Cancel() +{ + if (m_keyboard) + m_keyboard->Cancel(); +} + +int CGUIDialogKeyboardTouch::GetWindowId() const +{ + return GetID(); +} + +void CGUIDialogKeyboardTouch::OnInitWindow() +{ + CGUIDialog::OnInitWindow(); + m_windowLoaded = true; + m_active = true; + Create(); +} + +void CGUIDialogKeyboardTouch::Process() +{ + if (m_keyboard) + { + m_confirmed = m_keyboard->ShowAndGetInput(m_pCharCallback, m_initialString, m_typedString, m_heading, m_bHiddenInput); + } + Close(); +}
\ No newline at end of file diff --git a/xbmc/dialogs/GUIDialogKeyboardTouch.h b/xbmc/dialogs/GUIDialogKeyboardTouch.h new file mode 100644 index 0000000..e7ad433 --- /dev/null +++ b/xbmc/dialogs/GUIDialogKeyboardTouch.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" +#include "guilib/GUIKeyboard.h" + +#include <atomic> +#include <memory> + +class CGUIDialogKeyboardTouch : public CGUIDialog, public CGUIKeyboard, public CThread +{ +public: + CGUIDialogKeyboardTouch(); + bool ShowAndGetInput(char_callback_t pCallback, const std::string &initialString, std::string &typedString, const std::string &heading, bool bHiddenInput) override; + bool SetTextToKeyboard(const std::string &text, bool closeKeyboard = false) override; + void Cancel() override; + int GetWindowId() const override; + +protected: + void OnInitWindow() override; + using CGUIControlGroup::Process; + void Process() override; + + char_callback_t m_pCharCallback; + std::string m_initialString; + std::string m_typedString; + std::string m_heading; + bool m_bHiddenInput; + + std::unique_ptr<CGUIKeyboard> m_keyboard; + std::atomic_bool m_active; + bool m_confirmed; +}; diff --git a/xbmc/dialogs/GUIDialogMediaFilter.cpp b/xbmc/dialogs/GUIDialogMediaFilter.cpp new file mode 100644 index 0000000..4985627 --- /dev/null +++ b/xbmc/dialogs/GUIDialogMediaFilter.cpp @@ -0,0 +1,937 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogMediaFilter.h" + +#include "DbUrl.h" +#include "FileItem.h" +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "XBDateTime.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "music/MusicDatabase.h" +#include "music/MusicDbUrl.h" +#include "playlists/SmartPlayList.h" +#include "settings/SettingUtils.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingDefinitions.h" +#include "settings/windows/GUIControlSettings.h" +#include "utils/SortUtils.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" +#include "video/VideoDbUrl.h" + +#define CONTROL_HEADING 2 + +#define CONTROL_OKAY_BUTTON 28 +#define CONTROL_CANCEL_BUTTON 29 +#define CONTROL_CLEAR_BUTTON 30 + +#define CHECK_ALL -1 +#define CHECK_NO 0 +#define CHECK_YES 1 +#define CHECK_LABEL_ALL 593 +#define CHECK_LABEL_NO 106 +#define CHECK_LABEL_YES 107 + +static const CGUIDialogMediaFilter::Filter filterList[] = { + { "movies", FieldTitle, 556, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_CONTAINS }, + { "movies", FieldRating, 563, SettingType::Number, "range", "number", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "movies", FieldUserRating, 38018, SettingType::Integer, "range", "integer", CDatabaseQueryRule::OPERATOR_BETWEEN }, + //{ "movies", FieldTime, 180, SettingType::Integer, "range", "time", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "movies", FieldInProgress, 575, SettingType::Integer, "toggle", "", CDatabaseQueryRule::OPERATOR_FALSE }, + { "movies", FieldYear, 562, SettingType::Integer, "range", "integer", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "movies", FieldTag, 20459, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "movies", FieldGenre, 515, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "movies", FieldActor, 20337, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "movies", FieldDirector, 20339, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "movies", FieldStudio, 572, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + + { "tvshows", FieldTitle, 556, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_CONTAINS }, + //{ "tvshows", FieldTvShowStatus, 126, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "tvshows", FieldRating, 563, SettingType::Number, "range", "number", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "tvshows", FieldUserRating, 38018, SettingType::Integer, "range", "integer", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "tvshows", FieldInProgress, 575, SettingType::Integer, "toggle", "", CDatabaseQueryRule::OPERATOR_FALSE }, + { "tvshows", FieldYear, 562, SettingType::Integer, "range", "integer", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "tvshows", FieldTag, 20459, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "tvshows", FieldGenre, 515, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "tvshows", FieldActor, 20337, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "tvshows", FieldDirector, 20339, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "tvshows", FieldStudio, 572, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + + { "episodes", FieldTitle, 556, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_CONTAINS }, + { "episodes", FieldRating, 563, SettingType::Number, "range", "number", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "episodes", FieldUserRating, 38018, SettingType::Integer, "range", "integer", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "episodes", FieldAirDate, 20416, SettingType::Integer, "range", "date", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "episodes", FieldInProgress, 575, SettingType::Integer, "toggle", "", CDatabaseQueryRule::OPERATOR_FALSE }, + { "episodes", FieldActor, 20337, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "episodes", FieldDirector, 20339, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + + { "musicvideos", FieldTitle, 556, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_CONTAINS }, + { "musicvideos", FieldRating, 563, SettingType::Number, "range", "number", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "musicvideos", FieldUserRating, 38018, SettingType::Integer, "range", "integer", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "musicvideos", FieldArtist, 557, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "musicvideos", FieldAlbum, 558, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + //{ "musicvideos", FieldTime, 180, SettingType::Integer, "range", "time", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "musicvideos", FieldYear, 562, SettingType::Integer, "range", "integer", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "musicvideos", FieldTag, 20459, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "musicvideos", FieldGenre, 515, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "musicvideos", FieldDirector, 20339, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "musicvideos", FieldStudio, 572, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + + { "artists", FieldArtist, 557, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_CONTAINS }, + { "artists", FieldSource, 39030, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "artists", FieldGenre, 515, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "artists", FieldMoods, 175, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_CONTAINS }, + { "artists", FieldStyles, 176, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_CONTAINS }, + { "artists", FieldInstruments, 21892, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_CONTAINS }, + { "artists", FieldArtistType, 564, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "artists", FieldGender, 39025, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "artists", FieldDisambiguation, 39026, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_CONTAINS }, + { "artists", FieldBiography, 21887, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_CONTAINS }, + { "artists", FieldBorn, 21893, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_CONTAINS }, + { "artists", FieldBandFormed, 21894, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_CONTAINS }, + { "artists", FieldDisbanded, 21896, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_CONTAINS }, + { "artists", FieldDied, 21897, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_CONTAINS }, + + { "albums", FieldAlbum, 556, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_CONTAINS }, +// { "albums", FieldArtist, 557, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "albums", FieldDiscTitle, 38076, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_CONTAINS }, + { "albums", FieldAlbumArtist, 566, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "albums", FieldSource, 39030, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "albums", FieldRating, 563, SettingType::Number, "range", "number", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "albums", FieldUserRating, 38018, SettingType::Integer, "range", "integer", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "albums", FieldAlbumType, 564, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "albums", FieldYear, 562, SettingType::Integer, "range", "integer", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "albums", FieldGenre, 515, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "albums", FieldMusicLabel, 21899, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "albums", FieldCompilation, 204, SettingType::Boolean, "toggle", "", CDatabaseQueryRule::OPERATOR_FALSE }, + { "albums", FieldIsBoxset, 38074, SettingType::Boolean, "toggle", "", CDatabaseQueryRule::OPERATOR_FALSE }, + { "albums", FieldOrigYear, 38078, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_CONTAINS }, + + { "songs", FieldTitle, 556, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_CONTAINS }, + { "songs", FieldAlbum, 558, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "songs", FieldDiscTitle, 38076, SettingType::String, "edit", "string", CDatabaseQueryRule::OPERATOR_CONTAINS }, + { "songs", FieldArtist, 557, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "songs", FieldTime, 180, SettingType::Integer, "range", "time", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "songs", FieldRating, 563, SettingType::Number, "range", "number", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "songs", FieldUserRating, 38018, SettingType::Integer, "range", "integer", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "songs", FieldYear, 562, SettingType::Integer, "range", "integer", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "songs", FieldGenre, 515, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS }, + { "songs", FieldPlaycount, 567, SettingType::Integer, "range", "integer", CDatabaseQueryRule::OPERATOR_BETWEEN }, + { "songs", FieldSource, 39030, SettingType::List, "list", "string", CDatabaseQueryRule::OPERATOR_EQUALS } +}; + +CGUIDialogMediaFilter::CGUIDialogMediaFilter() + : CGUIDialogSettingsManualBase(WINDOW_DIALOG_MEDIA_FILTER, "DialogSettings.xml"), + m_dbUrl(NULL), + m_filter(NULL) +{ } + +CGUIDialogMediaFilter::~CGUIDialogMediaFilter() +{ + Reset(); +} + +bool CGUIDialogMediaFilter::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_CLICKED: + { + if (message.GetSenderId()== CONTROL_CLEAR_BUTTON) + { + m_filter->Reset(); + m_filter->SetType(m_mediaType); + + for (auto& filter : m_filters) + { + filter.second.rule = nullptr; + filter.second.setting->Reset(); + } + + TriggerFilter(); + return true; + } + break; + } + + case GUI_MSG_REFRESH_LIST: + { + TriggerFilter(); + UpdateControls(); + break; + } + + case GUI_MSG_WINDOW_DEINIT: + { + Reset(); + break; + } + + default: + break; + } + + return CGUIDialogSettingsManualBase::OnMessage(message); +} + +void CGUIDialogMediaFilter::ShowAndEditMediaFilter(const std::string &path, CSmartPlaylist &filter) +{ + CGUIDialogMediaFilter *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogMediaFilter>(WINDOW_DIALOG_MEDIA_FILTER); + if (dialog == NULL) + return; + + // initialize and show the dialog + dialog->Initialize(); + dialog->m_filter = &filter; + + // must be called after setting the filter/smartplaylist + if (!dialog->SetPath(path)) + return; + + dialog->Open(); +} + +void CGUIDialogMediaFilter::OnWindowLoaded() +{ + CGUIDialogSettingsManualBase::OnWindowLoaded(); + + // we don't need the cancel button so let's hide it + SET_CONTROL_HIDDEN(CONTROL_CANCEL_BUTTON); +} + +void CGUIDialogMediaFilter::OnInitWindow() +{ + CGUIDialogSettingsManualBase::OnInitWindow(); + + UpdateControls(); +} + +void CGUIDialogMediaFilter::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + CGUIDialogSettingsManualBase::OnSettingChanged(setting); + + std::map<std::string, Filter>::iterator it = m_filters.find(setting->GetId()); + if (it == m_filters.end()) + return; + + bool remove = false; + Filter& filter = it->second; + + if (filter.controlType == "edit") + { + std::string value = setting->ToString(); + if (!value.empty()) + { + if (filter.rule == NULL) + filter.rule = AddRule(filter.field, filter.ruleOperator); + filter.rule->m_parameter.clear(); + filter.rule->m_parameter.push_back(value); + } + else + remove = true; + } + else if (filter.controlType == "toggle") + { + int choice = std::static_pointer_cast<const CSettingInt>(setting)->GetValue(); + if (choice > CHECK_ALL) + { + CDatabaseQueryRule::SEARCH_OPERATOR ruleOperator = choice == CHECK_YES ? CDatabaseQueryRule::OPERATOR_TRUE : CDatabaseQueryRule::OPERATOR_FALSE; + if (filter.rule == NULL) + filter.rule = AddRule(filter.field, ruleOperator); + else + filter.rule->m_operator = ruleOperator; + } + else + remove = true; + } + else if (filter.controlType == "list") + { + std::vector<CVariant> values = CSettingUtils::GetList(std::static_pointer_cast<const CSettingList>(setting)); + if (!values.empty()) + { + if (filter.rule == NULL) + filter.rule = AddRule(filter.field, filter.ruleOperator); + + filter.rule->m_parameter.clear(); + for (const auto& itValue : values) + filter.rule->m_parameter.push_back(itValue.asString()); + } + else + remove = true; + } + else if (filter.controlType == "range") + { + const std::shared_ptr<const CSettingList> settingList = std::static_pointer_cast<const CSettingList>(setting); + std::vector<CVariant> values = CSettingUtils::GetList(settingList); + if (values.size() != 2) + return; + + std::string strValueLower, strValueUpper; + + SettingConstPtr definition = settingList->GetDefinition(); + if (definition->GetType() == SettingType::Integer) + { + const std::shared_ptr<const CSettingInt> definitionInt = std::static_pointer_cast<const CSettingInt>(definition); + int valueLower = static_cast<int>(values.at(0).asInteger()); + int valueUpper = static_cast<int>(values.at(1).asInteger()); + + if (valueLower > definitionInt->GetMinimum() || + valueUpper < definitionInt->GetMaximum()) + { + if (filter.controlFormat == "date") + { + strValueLower = CDateTime(static_cast<time_t>(valueLower)).GetAsDBDate(); + strValueUpper = CDateTime(static_cast<time_t>(valueUpper)).GetAsDBDate(); + } + else + { + strValueLower = values.at(0).asString(); + strValueUpper = values.at(1).asString(); + } + } + } + else if (definition->GetType() == SettingType::Number) + { + const std::shared_ptr<const CSettingNumber> definitionNumber = std::static_pointer_cast<const CSettingNumber>(definition); + float valueLower = values.at(0).asFloat(); + float valueUpper = values.at(1).asFloat(); + + if (static_cast<double>(valueLower) > definitionNumber->GetMinimum() || + static_cast<double>(valueUpper) < definitionNumber->GetMaximum()) + { + strValueLower = values.at(0).asString(); + strValueUpper = values.at(1).asString(); + } + } + else + return; + + if (!strValueLower.empty() && !strValueUpper.empty()) + { + // prepare the filter rule + if (filter.rule == NULL) + filter.rule = AddRule(filter.field, filter.ruleOperator); + filter.rule->m_parameter.clear(); + + filter.rule->m_parameter.push_back(strValueLower); + filter.rule->m_parameter.push_back(strValueUpper); + } + else + remove = true; + } + else + return; + + // we need to remove the existing rule for the title + if (remove && filter.rule != NULL) + { + DeleteRule(filter.field); + filter.rule = NULL; + } + + CGUIMessage msg(GUI_MSG_REFRESH_LIST, GetID(), 0); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, WINDOW_DIALOG_MEDIA_FILTER); +} + +void CGUIDialogMediaFilter::SetupView() +{ + CGUIDialogSettingsManualBase::SetupView(); + + // set the heading label based on the media type + uint32_t localizedMediaId = 0; + if (m_mediaType == "movies") + localizedMediaId = 20342; + else if (m_mediaType == "tvshows") + localizedMediaId = 20343; + else if (m_mediaType == "episodes") + localizedMediaId = 20360; + else if (m_mediaType == "musicvideos") + localizedMediaId = 20389; + else if (m_mediaType == "artists") + localizedMediaId = 133; + else if (m_mediaType == "albums") + localizedMediaId = 132; + else if (m_mediaType == "songs") + localizedMediaId = 134; + + // set the heading + SET_CONTROL_LABEL(CONTROL_HEADING, StringUtils::Format(g_localizeStrings.Get(1275), + g_localizeStrings.Get(localizedMediaId))); + + SET_CONTROL_LABEL(CONTROL_OKAY_BUTTON, 186); + SET_CONTROL_LABEL(CONTROL_CLEAR_BUTTON, 192); +} + +void CGUIDialogMediaFilter::InitializeSettings() +{ + CGUIDialogSettingsManualBase::InitializeSettings(); + + if (m_filter == NULL) + return; + + Reset(true); + + int handledRules = 0; + + const std::shared_ptr<CSettingCategory> category = AddCategory("filter", -1); + if (category == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogMediaFilter: unable to setup filters"); + return; + } + + const std::shared_ptr<CSettingGroup> group = AddGroup(category); + if (group == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogMediaFilter: unable to setup filters"); + return; + } + + for (const Filter& f : filterList) + { + if (f.mediaType != m_mediaType) + continue; + + Filter filter = f; + + // check the smartplaylist if it contains a matching rule + for (auto& rule : m_filter->m_ruleCombination.m_rules) + { + if (rule->m_field == filter.field) + { + filter.rule = static_cast<CSmartPlaylistRule*>(rule.get()); + handledRules++; + break; + } + } + + std::string settingId = StringUtils::Format("filter.{}.{}", filter.mediaType, filter.field); + if (filter.controlType == "edit") + { + CVariant data; + if (filter.rule != NULL && filter.rule->m_parameter.size() == 1) + data = filter.rule->m_parameter.at(0); + + if (filter.settingType == SettingType::String) + filter.setting = AddEdit(group, settingId, filter.label, SettingLevel::Basic, data.asString(), true, false, filter.label, true); + else if (filter.settingType == SettingType::Integer) + filter.setting = AddEdit(group, settingId, filter.label, SettingLevel::Basic, static_cast<int>(data.asInteger()), 0, 1, 0, false, static_cast<int>(filter.label), true); + else if (filter.settingType == SettingType::Number) + filter.setting = AddEdit(group, settingId, filter.label, SettingLevel::Basic, data.asFloat(), 0.0f, 1.0f, 0.0f, false, filter.label, true); + } + else if (filter.controlType == "toggle") + { + int value = CHECK_ALL; + if (filter.rule != NULL) + value = filter.rule->m_operator == CDatabaseQueryRule::OPERATOR_TRUE ? CHECK_YES : CHECK_NO; + + TranslatableIntegerSettingOptions entries; + entries.push_back(TranslatableIntegerSettingOption(CHECK_LABEL_ALL, CHECK_ALL)); + entries.push_back(TranslatableIntegerSettingOption(CHECK_LABEL_NO, CHECK_NO)); + entries.push_back(TranslatableIntegerSettingOption(CHECK_LABEL_YES, CHECK_YES)); + + filter.setting = AddSpinner(group, settingId, filter.label, SettingLevel::Basic, value, entries, true); + } + else if (filter.controlType == "list") + { + std::vector<std::string> values; + if (filter.rule != NULL && !filter.rule->m_parameter.empty()) + { + values = StringUtils::Split(filter.rule->GetParameter(), DATABASEQUERY_RULE_VALUE_SEPARATOR); + if (values.size() == 1 && values.at(0).empty()) + values.erase(values.begin()); + } + + filter.setting = AddList(group, settingId, filter.label, SettingLevel::Basic, values, GetStringListOptions, filter.label); + } + else if (filter.controlType == "range") + { + CVariant valueLower, valueUpper; + if (filter.rule != NULL) + { + if (filter.rule->m_parameter.size() == 2) + { + valueLower = filter.rule->m_parameter.at(0); + valueUpper = filter.rule->m_parameter.at(1); + } + else + { + DeleteRule(filter.field); + filter.rule = NULL; + } + } + + if (filter.settingType == SettingType::Integer) + { + int min = 0; + int interval = 0; + int max = 0; + GetRange(filter, min, interval, max); + + // don't create the filter if there's no real range + if (min == max) + continue; + + int iValueLower = valueLower.isNull() ? min : static_cast<int>(valueLower.asInteger()); + int iValueUpper = valueUpper.isNull() ? max : static_cast<int>(valueUpper.asInteger()); + + if (filter.controlFormat == "integer") + filter.setting = AddRange(group, settingId, filter.label, SettingLevel::Basic, iValueLower, iValueUpper, min, interval, max, -1, 21469, true); + else if (filter.controlFormat == "percentage") + filter.setting = AddPercentageRange(group, settingId, filter.label, SettingLevel::Basic, iValueLower, iValueUpper, -1, 1, 21469, true); + else if (filter.controlFormat == "date") + filter.setting = AddDateRange(group, settingId, filter.label, SettingLevel::Basic, iValueLower, iValueUpper, min, interval, max, -1, 21469, true); + else if (filter.controlFormat == "time") + filter.setting = AddTimeRange(group, settingId, filter.label, SettingLevel::Basic, iValueLower, iValueUpper, min, interval, max, -1, 21469, true); + } + else if (filter.settingType == SettingType::Number) + { + float min = 0; + float interval = 0; + float max = 0; + GetRange(filter, min, interval, max); + + // don't create the filter if there's no real range + if (min == max) + continue; + + float fValueLower = valueLower.isNull() ? min : valueLower.asFloat(); + float fValueUpper = valueUpper.isNull() ? max : valueUpper.asFloat(); + + filter.setting = AddRange(group, settingId, filter.label, SettingLevel::Basic, fValueLower, fValueUpper, min, interval, max, -1, 21469, true); + } + } + else + { + if (filter.rule != NULL) + handledRules--; + + CLog::Log(LOGWARNING, + "CGUIDialogMediaFilter: filter {} of media type {} with unknown control type '{}'", + filter.field, filter.mediaType, filter.controlType); + continue; + } + + if (filter.setting == NULL) + { + if (filter.rule != NULL) + handledRules--; + + CLog::Log(LOGWARNING, + "CGUIDialogMediaFilter: failed to create filter {} of media type {} with control " + "type '{}'", + filter.field, filter.mediaType, filter.controlType); + continue; + } + + m_filters.insert(make_pair(settingId, filter)); + } + + // make sure that no change in capacity size is needed when adding new rules + // which would copy around the rules and our pointers in the Filter struct + // wouldn't work anymore + m_filter->m_ruleCombination.m_rules.reserve(m_filters.size() + (m_filter->m_ruleCombination.m_rules.size() - handledRules)); +} + +bool CGUIDialogMediaFilter::SetPath(const std::string &path) +{ + if (path.empty() || m_filter == NULL) + { + CLog::Log(LOGWARNING, "CGUIDialogMediaFilter::SetPath({}): invalid path or filter", path); + return false; + } + + delete m_dbUrl; + bool video = false; + if (path.find("videodb://") == 0) + { + m_dbUrl = new CVideoDbUrl(); + video = true; + } + else if (path.find("musicdb://") == 0) + m_dbUrl = new CMusicDbUrl(); + else + { + CLog::Log( + LOGWARNING, + "CGUIDialogMediaFilter::SetPath({}): invalid path (neither videodb:// nor musicdb://)", + path); + return false; + } + + if (!m_dbUrl->FromString(path) || + (video && m_dbUrl->GetType() != "movies" && m_dbUrl->GetType() != "tvshows" && m_dbUrl->GetType() != "episodes" && m_dbUrl->GetType() != "musicvideos") || + (!video && m_dbUrl->GetType() != "artists" && m_dbUrl->GetType() != "albums" && m_dbUrl->GetType() != "songs")) + { + CLog::Log(LOGWARNING, "CGUIDialogMediaFilter::SetPath({}): invalid media type", path); + return false; + } + + // remove "filter" option + if (m_dbUrl->HasOption("filter")) + m_dbUrl->RemoveOption("filter"); + + if (video) + m_mediaType = ((CVideoDbUrl*)m_dbUrl)->GetItemType(); + else + m_mediaType = m_dbUrl->GetType(); + + m_filter->SetType(m_mediaType); + return true; +} + +void CGUIDialogMediaFilter::UpdateControls() +{ + for (const auto& itFilter : m_filters) + { + if (itFilter.second.controlType != "list") + continue; + + std::vector<std::string> items; + int size = GetItems(itFilter.second, items, true); + + std::string label = g_localizeStrings.Get(itFilter.second.label); + BaseSettingControlPtr control = GetSettingControl(itFilter.second.setting->GetId()); + if (control == NULL) + continue; + + if (size <= 0 || + (size == 1 && itFilter.second.field != FieldSet && itFilter.second.field != FieldTag)) + CONTROL_DISABLE(control->GetID()); + else + { + CONTROL_ENABLE(control->GetID()); + label = StringUtils::Format(g_localizeStrings.Get(21470), label, size); + } + SET_CONTROL_LABEL(control->GetID(), label); + } +} + +void CGUIDialogMediaFilter::TriggerFilter() const +{ + if (m_filter == NULL) + return; + + CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS, 10); // 10 for advanced + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message); +} + +void CGUIDialogMediaFilter::Reset(bool filtersOnly /* = false */) +{ + if (!filtersOnly) + { + delete m_dbUrl; + m_dbUrl = NULL; + } + + m_filters.clear(); +} + +int CGUIDialogMediaFilter::GetItems(const Filter &filter, std::vector<std::string> &items, bool countOnly /* = false */) +{ + CFileItemList selectItems; + + // remove the rule for the field of the filter we want to retrieve items for + CSmartPlaylist tmpFilter = *m_filter; + for (CDatabaseQueryRules::iterator rule = tmpFilter.m_ruleCombination.m_rules.begin(); + rule != tmpFilter.m_ruleCombination.m_rules.end(); ++rule) + { + if ((*rule)->m_field == filter.field) + { + tmpFilter.m_ruleCombination.m_rules.erase(rule); + break; + } + } + + if (m_mediaType == "movies" || m_mediaType == "tvshows" || m_mediaType == "episodes" || m_mediaType == "musicvideos") + { + CVideoDatabase videodb; + if (!videodb.Open()) + return -1; + + std::set<std::string> playlists; + CDatabase::Filter dbfilter; + dbfilter.where = tmpFilter.GetWhereClause(videodb, playlists); + + VideoDbContentType type = VideoDbContentType::MOVIES; + if (m_mediaType == "tvshows") + type = VideoDbContentType::TVSHOWS; + else if (m_mediaType == "episodes") + type = VideoDbContentType::EPISODES; + else if (m_mediaType == "musicvideos") + type = VideoDbContentType::MUSICVIDEOS; + + if (filter.field == FieldGenre) + videodb.GetGenresNav(m_dbUrl->ToString(), selectItems, type, dbfilter, countOnly); + else if (filter.field == FieldActor || filter.field == FieldArtist) + videodb.GetActorsNav(m_dbUrl->ToString(), selectItems, type, dbfilter, countOnly); + else if (filter.field == FieldDirector) + videodb.GetDirectorsNav(m_dbUrl->ToString(), selectItems, type, dbfilter, countOnly); + else if (filter.field == FieldStudio) + videodb.GetStudiosNav(m_dbUrl->ToString(), selectItems, type, dbfilter, countOnly); + else if (filter.field == FieldAlbum) + videodb.GetMusicVideoAlbumsNav(m_dbUrl->ToString(), selectItems, -1, dbfilter, countOnly); + else if (filter.field == FieldTag) + videodb.GetTagsNav(m_dbUrl->ToString(), selectItems, type, dbfilter, countOnly); + } + else if (m_mediaType == "artists" || m_mediaType == "albums" || m_mediaType == "songs") + { + CMusicDatabase musicdb; + if (!musicdb.Open()) + return -1; + + std::set<std::string> playlists; + CDatabase::Filter dbfilter; + dbfilter.where = tmpFilter.GetWhereClause(musicdb, playlists); + + if (filter.field == FieldGenre) + musicdb.GetGenresNav(m_dbUrl->ToString(), selectItems, dbfilter, countOnly); + else if (filter.field == FieldArtist || filter.field == FieldAlbumArtist) + musicdb.GetArtistsNav(m_dbUrl->ToString(), selectItems, m_mediaType == "albums", -1, -1, -1, dbfilter, SortDescription(), countOnly); + else if (filter.field == FieldAlbum) + musicdb.GetAlbumsNav(m_dbUrl->ToString(), selectItems, -1, -1, dbfilter, SortDescription(), countOnly); + else if (filter.field == FieldAlbumType) + musicdb.GetAlbumTypesNav(m_dbUrl->ToString(), selectItems, dbfilter, countOnly); + else if (filter.field == FieldMusicLabel) + musicdb.GetMusicLabelsNav(m_dbUrl->ToString(), selectItems, dbfilter, countOnly); + if (filter.field == FieldSource) + musicdb.GetSourcesNav(m_dbUrl->ToString(), selectItems, dbfilter, countOnly); + } + + int size = selectItems.Size(); + if (size <= 0) + return 0; + + if (countOnly) + { + if (size == 1 && selectItems.Get(0)->HasProperty("total")) + return (int)selectItems.Get(0)->GetProperty("total").asInteger(); + return 0; + } + + // sort the items + selectItems.Sort(SortByLabel, SortOrderAscending); + + for (int index = 0; index < size; ++index) + items.push_back(selectItems.Get(index)->GetLabel()); + + return items.size(); +} + +CSmartPlaylistRule* CGUIDialogMediaFilter::AddRule(Field field, CDatabaseQueryRule::SEARCH_OPERATOR ruleOperator /* = CDatabaseQueryRule::OPERATOR_CONTAINS */) +{ + CSmartPlaylistRule rule; + rule.m_field = field; + rule.m_operator = ruleOperator; + + m_filter->m_ruleCombination.AddRule(rule); + return (CSmartPlaylistRule *)m_filter->m_ruleCombination.m_rules.back().get(); +} + +void CGUIDialogMediaFilter::DeleteRule(Field field) +{ + for (CDatabaseQueryRules::iterator rule = m_filter->m_ruleCombination.m_rules.begin(); + rule != m_filter->m_ruleCombination.m_rules.end(); ++rule) + { + if ((*rule)->m_field == field) + { + m_filter->m_ruleCombination.m_rules.erase(rule); + break; + } + } +} + +void CGUIDialogMediaFilter::GetStringListOptions(const SettingConstPtr& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data) +{ + if (setting == NULL || data == NULL) + return; + + CGUIDialogMediaFilter *mediaFilter = static_cast<CGUIDialogMediaFilter*>(data); + + std::map<std::string, Filter>::const_iterator itFilter = mediaFilter->m_filters.find(setting->GetId()); + if (itFilter == mediaFilter->m_filters.end()) + return; + + std::vector<std::string> items; + if (mediaFilter->GetItems(itFilter->second, items, false) <= 0) + return; + + for (const auto& item : items) + list.emplace_back(item, item); +} + +void CGUIDialogMediaFilter::GetRange(const Filter &filter, int &min, int &interval, int &max) +{ + if (filter.field == FieldUserRating && + (m_mediaType == "movies" || m_mediaType == "tvshows" || m_mediaType == "episodes"|| m_mediaType == "musicvideos" || m_mediaType == "albums" || m_mediaType == "songs")) + { + min = 0; + interval = 1; + max = 10; + } + else if (filter.field == FieldYear) + { + min = 0; + interval = 1; + max = 0; + + if (m_mediaType == "movies" || m_mediaType == "tvshows" || m_mediaType == "musicvideos") + { + std::string table; + std::string year; + if (m_mediaType == "movies") + { + table = "movie_view"; + year = DatabaseUtils::GetField(FieldYear, MediaTypeMovie, DatabaseQueryPartWhere); + } + else if (m_mediaType == "tvshows") + { + table = "tvshow_view"; + year = StringUtils::Format( + "strftime(\"%%Y\", {})", + DatabaseUtils::GetField(FieldYear, MediaTypeTvShow, DatabaseQueryPartWhere)); + } + else if (m_mediaType == "musicvideos") + { + table = "musicvideo_view"; + year = DatabaseUtils::GetField(FieldYear, MediaTypeMusicVideo, DatabaseQueryPartWhere); + } + + CDatabase::Filter filter; + filter.where = year + " > 0"; + GetMinMax(table, year, min, max, filter); + } + else if (m_mediaType == "albums" || m_mediaType == "songs") + { + std::string table; + if (m_mediaType == "albums") + table = "albumview"; + else if (m_mediaType == "songs") + table = "songview"; + else + return; + + CDatabase::Filter filter; + filter.where = DatabaseUtils::GetField(FieldYear, CMediaTypes::FromString(m_mediaType), DatabaseQueryPartWhere) + " > 0"; + GetMinMax(table, DatabaseUtils::GetField(FieldYear, CMediaTypes::FromString(m_mediaType), DatabaseQueryPartSelect), min, max, filter); + } + } + else if (filter.field == FieldAirDate) + { + min = 0; + interval = 1; + max = 0; + + if (m_mediaType == "episodes") + { + std::string field = StringUtils::Format("CAST(strftime(\"%%s\", c{:02}) AS INTEGER)", + VIDEODB_ID_EPISODE_AIRED); + + GetMinMax("episode_view", field, min, max); + interval = 60 * 60 * 24 * 7; // 1 week + } + } + else if (filter.field == FieldTime) + { + min = 0; + interval = 10; + max = 0; + + if (m_mediaType == "songs") + GetMinMax("songview", "iDuration", min, max); + } + else if (filter.field == FieldPlaycount) + { + min = 0; + interval = 1; + max = 0; + + if (m_mediaType == "songs") + GetMinMax("songview", "iTimesPlayed", min, max); + } +} + +void CGUIDialogMediaFilter::GetRange(const Filter &filter, float &min, float &interval, float &max) +{ + if (filter.field == FieldRating && + (m_mediaType == "movies" || m_mediaType == "tvshows" || m_mediaType == "episodes" || m_mediaType == "musicvideos" || m_mediaType == "albums" || m_mediaType == "songs")) + { + min = 0.0f; + interval = 0.1f; + max = 10.0f; + } +} + +bool CGUIDialogMediaFilter::GetMinMax(const std::string &table, const std::string &field, int &min, int &max, const CDatabase::Filter &filter /* = CDatabase::Filter() */) +{ + if (table.empty() || field.empty()) + return false; + + CDatabase *db = NULL; + CDbUrl *dbUrl = NULL; + if (m_mediaType == "movies" || m_mediaType == "tvshows" || m_mediaType == "episodes" || m_mediaType == "musicvideos") + { + CVideoDatabase *videodb = new CVideoDatabase(); + if (!videodb->Open()) + { + delete videodb; + return false; + } + + db = videodb; + dbUrl = new CVideoDbUrl(); + } + else if (m_mediaType == "artists" || m_mediaType == "albums" || m_mediaType == "songs") + { + CMusicDatabase *musicdb = new CMusicDatabase(); + if (!musicdb->Open()) + { + delete musicdb; + return false; + } + + db = musicdb; + dbUrl = new CMusicDbUrl(); + } + + if (db == NULL || !db->IsOpen() || dbUrl == NULL) + { + delete db; + delete dbUrl; + return false; + } + + CDatabase::Filter extFilter = filter; + std::string strSQLExtra; + if (!db->BuildSQL(m_dbUrl->ToString(), strSQLExtra, extFilter, strSQLExtra, *dbUrl)) + { + delete db; + delete dbUrl; + return false; + } + + std::string strSQL = "SELECT %s FROM %s "; + + min = static_cast<int>(strtol(db->GetSingleValue(db->PrepareSQL(strSQL, ("MIN(" + field + ")").c_str(), table.c_str()) + strSQLExtra).c_str(), NULL, 0)); + max = static_cast<int>(strtol(db->GetSingleValue(db->PrepareSQL(strSQL, ("MAX(" + field + ")").c_str(), table.c_str()) + strSQLExtra).c_str(), NULL, 0)); + + db->Close(); + delete db; + delete dbUrl; + + return true; +} diff --git a/xbmc/dialogs/GUIDialogMediaFilter.h b/xbmc/dialogs/GUIDialogMediaFilter.h new file mode 100644 index 0000000..893f1c2 --- /dev/null +++ b/xbmc/dialogs/GUIDialogMediaFilter.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "dbwrappers/Database.h" +#include "dbwrappers/DatabaseQuery.h" +#include "settings/dialogs/GUIDialogSettingsManualBase.h" +#include "settings/lib/SettingType.h" +#include "utils/DatabaseUtils.h" + +#include <map> +#include <string> +#include <utility> +#include <vector> + +class CDbUrl; +class CSetting; +class CSmartPlaylist; +class CSmartPlaylistRule; +struct StringSettingOption; + +class CGUIDialogMediaFilter : public CGUIDialogSettingsManualBase +{ +public: + CGUIDialogMediaFilter(); + ~CGUIDialogMediaFilter() override; + + // specializations of CGUIControl + bool OnMessage(CGUIMessage &message) override; + + static void ShowAndEditMediaFilter(const std::string &path, CSmartPlaylist &filter); + + struct Filter + { + std::string mediaType; + Field field; + uint32_t label; + SettingType settingType; + std::string controlType; + std::string controlFormat; + CDatabaseQueryRule::SEARCH_OPERATOR ruleOperator; + std::shared_ptr<CSetting> setting = nullptr; + CSmartPlaylistRule* rule = nullptr; + void* data = nullptr; + }; + +protected: + // specializations of CGUIWindow + void OnWindowLoaded() override; + void OnInitWindow() override; + + // implementations of ISettingCallback + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + + // specialization of CGUIDialogSettingsBase + bool AllowResettingSettings() const override { return false; } + bool Save() override { return true; } + std::chrono::milliseconds GetDelayMs() const override { return std::chrono::milliseconds(500); } + + // specialization of CGUIDialogSettingsManualBase + void SetupView() override; + void InitializeSettings() override; + + bool SetPath(const std::string &path); + void UpdateControls(); + void TriggerFilter() const; + void Reset(bool filtersOnly = false); + + int GetItems(const Filter &filter, std::vector<std::string> &items, bool countOnly = false); + void GetRange(const Filter &filter, int &min, int &interval, int &max); + void GetRange(const Filter &filter, float &min, float &interval, float &max); + bool GetMinMax(const std::string &table, const std::string &field, int &min, int &max, const CDatabase::Filter &filter = CDatabase::Filter()); + + CSmartPlaylistRule* AddRule(Field field, CDatabaseQueryRule::SEARCH_OPERATOR ruleOperator = CDatabaseQueryRule::OPERATOR_CONTAINS); + void DeleteRule(Field field); + + static void GetStringListOptions(const std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data); + + CDbUrl* m_dbUrl; + std::string m_mediaType; + CSmartPlaylist *m_filter; + std::map<std::string, Filter> m_filters; +}; diff --git a/xbmc/dialogs/GUIDialogMediaSource.cpp b/xbmc/dialogs/GUIDialogMediaSource.cpp new file mode 100644 index 0000000..13b13e5 --- /dev/null +++ b/xbmc/dialogs/GUIDialogMediaSource.cpp @@ -0,0 +1,620 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogMediaSource.h" +#include "ServiceBroker.h" +#include "guilib/GUIKeyboardFactory.h" +#include "GUIDialogFileBrowser.h" +#include "video/windows/GUIWindowVideoBase.h" +#include "music/windows/GUIWindowMusicBase.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "input/Key.h" +#include "Util.h" +#include "utils/URIUtils.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "filesystem/Directory.h" +#include "filesystem/PVRDirectory.h" +#include "GUIDialogYesNo.h" +#include "FileItem.h" +#include "settings/MediaSourceSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "guilib/LocalizeStrings.h" +#include "PasswordManager.h" +#include "URL.h" +#include "pvr/recordings/PVRRecordingsPath.h" + +#if defined(TARGET_ANDROID) +#include "utils/FileUtils.h" + +#include "platform/android/activity/XBMCApp.h" +#endif + +#ifdef TARGET_WINDOWS_STORE +#include "platform/win10/filesystem/WinLibraryDirectory.h" +#endif + +using namespace XFILE; + +#define CONTROL_HEADING 2 +#define CONTROL_PATH 10 +#define CONTROL_PATH_BROWSE 11 +#define CONTROL_NAME 12 +#define CONTROL_PATH_ADD 13 +#define CONTROL_PATH_REMOVE 14 +#define CONTROL_OK 18 +#define CONTROL_CANCEL 19 +#define CONTROL_CONTENT 20 + +CGUIDialogMediaSource::CGUIDialogMediaSource(void) + : CGUIDialog(WINDOW_DIALOG_MEDIA_SOURCE, "DialogMediaSource.xml") +{ + m_paths = new CFileItemList; + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogMediaSource::~CGUIDialogMediaSource() +{ + delete m_paths; +} + +bool CGUIDialogMediaSource::OnBack(int actionID) +{ + m_confirmed = false; + return CGUIDialog::OnBack(actionID); +} + +bool CGUIDialogMediaSource::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + int iAction = message.GetParam1(); + if (iControl == CONTROL_PATH && (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK)) + OnPath(GetSelectedItem()); + else if (iControl == CONTROL_PATH_BROWSE) + OnPathBrowse(GetSelectedItem()); + else if (iControl == CONTROL_PATH_ADD) + OnPathAdd(); + else if (iControl == CONTROL_PATH_REMOVE) + OnPathRemove(GetSelectedItem()); + else if (iControl == CONTROL_NAME) + { + OnEditChanged(iControl, m_name); + UpdateButtons(); + } + else if (iControl == CONTROL_OK) + OnOK(); + else if (iControl == CONTROL_CANCEL) + OnCancel(); + else + break; + return true; + } + break; + case GUI_MSG_WINDOW_INIT: + { + UpdateButtons(); + } + break; + case GUI_MSG_SETFOCUS: + if (message.GetControlId() == CONTROL_PATH_BROWSE || + message.GetControlId() == CONTROL_PATH_ADD || + message.GetControlId() == CONTROL_PATH_REMOVE) + { + HighlightItem(GetSelectedItem()); + } + else + HighlightItem(-1); + break; + } + return CGUIDialog::OnMessage(message); +} + +// \brief Show CGUIDialogMediaSource dialog and prompt for a new media source. +// \return True if the media source is added, false otherwise. +bool CGUIDialogMediaSource::ShowAndAddMediaSource(const std::string &type) +{ + CGUIDialogMediaSource *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogMediaSource>(WINDOW_DIALOG_MEDIA_SOURCE); + if (!dialog) return false; + dialog->Initialize(); + dialog->SetShare(CMediaSource()); + dialog->SetTypeOfMedia(type); + dialog->Open(); + bool confirmed(dialog->IsConfirmed()); + if (confirmed) + { + // Add this media source + // Get unique source name + std::string strName = dialog->GetUniqueMediaSourceName(); + + CMediaSource share; + share.FromNameAndPaths(type, strName, dialog->GetPaths()); + if (dialog->m_paths->Size() > 0) + share.m_strThumbnailImage = dialog->m_paths->Get(0)->GetArt("thumb"); + CMediaSourceSettings::GetInstance().AddShare(type, share); + OnMediaSourceChanged(type, "", share); + } + dialog->m_paths->Clear(); + return confirmed; +} + +bool CGUIDialogMediaSource::ShowAndEditMediaSource(const std::string &type, const std::string&share) +{ + VECSOURCES* pShares = CMediaSourceSettings::GetInstance().GetSources(type); + if (pShares) + { + for (unsigned int i = 0;i<pShares->size();++i) + { + if (StringUtils::EqualsNoCase((*pShares)[i].strName, share)) + return ShowAndEditMediaSource(type, (*pShares)[i]); + } + } + return false; +} + +bool CGUIDialogMediaSource::ShowAndEditMediaSource(const std::string &type, const CMediaSource &share) +{ + std::string strOldName = share.strName; + CGUIDialogMediaSource *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogMediaSource>(WINDOW_DIALOG_MEDIA_SOURCE); + if (!dialog) return false; + dialog->Initialize(); + dialog->SetShare(share); + dialog->SetTypeOfMedia(type, true); + dialog->Open(); + bool confirmed(dialog->IsConfirmed()); + if (confirmed) + { + // Update media source + // Get unique new source name when changed + std::string strName(dialog->m_name); + if (!StringUtils::EqualsNoCase(dialog->m_name, strOldName)) + strName = dialog->GetUniqueMediaSourceName(); + + CMediaSource newShare; + newShare.FromNameAndPaths(type, strName, dialog->GetPaths()); + CMediaSourceSettings::GetInstance().UpdateShare(type, strOldName, newShare); + + OnMediaSourceChanged(type, strOldName, newShare); + } + dialog->m_paths->Clear(); + return confirmed; +} + +std::string CGUIDialogMediaSource::GetUniqueMediaSourceName() +{ + // Get unique source name for this media type + unsigned int i, j = 2; + bool bConfirmed = false; + VECSOURCES* pShares = CMediaSourceSettings::GetInstance().GetSources(m_type); + std::string strName = m_name; + while (!bConfirmed) + { + for (i = 0; i<pShares->size(); ++i) + { + if (StringUtils::EqualsNoCase((*pShares)[i].strName, strName)) + break; + } + if (i < pShares->size()) + // found a match - try next + strName = StringUtils::Format("{} ({})", m_name, j++); + else + bConfirmed = true; + } + return strName; +} + +void CGUIDialogMediaSource::OnMediaSourceChanged(const std::string& type, const std::string& oldName, const CMediaSource& share) +{ + // Processing once media source added/edited - library scraping and scanning + if (!StringUtils::StartsWithNoCase(share.strPath, "rss://") && + !StringUtils::StartsWithNoCase(share.strPath, "rsss://") && + !StringUtils::StartsWithNoCase(share.strPath, "upnp://")) + { + if (type == "video" && !URIUtils::IsLiveTV(share.strPath)) + // Assign content to a path, refresh scraper information optionally start a scan + CGUIWindowVideoBase::OnAssignContent(share.strPath); + else if (type == "music") + CGUIWindowMusicBase::OnAssignContent(oldName, share); + } +} + +void CGUIDialogMediaSource::OnPathBrowse(int item) +{ + if (item < 0 || item >= m_paths->Size()) return; + // Browse is called. Open the filebrowser dialog. + // Ignore current path is best at this stage?? + std::string path = m_paths->Get(item)->GetPath(); + bool allowNetworkShares(m_type != "programs"); + VECSOURCES extraShares; + + if (m_name != CUtil::GetTitleFromPath(path)) + m_bNameChanged = true; + path.clear(); + + if (m_type == "music") + { + CMediaSource share1; +#if defined(TARGET_ANDROID) + // add the default android music directory + std::string path; + if (CXBMCApp::GetExternalStorage(path, "music") && !path.empty() && CDirectory::Exists(path)) + { + share1.strPath = path; + share1.strName = g_localizeStrings.Get(20240); + share1.m_ignore = true; + extraShares.push_back(share1); + } +#endif + +#if defined(TARGET_WINDOWS_STORE) + // add the default UWP music directory + std::string path; + if (XFILE::CWinLibraryDirectory::GetStoragePath(m_type, path) && !path.empty() && CDirectory::Exists(path)) + { + share1.strPath = path; + share1.strName = g_localizeStrings.Get(20245); + share1.m_ignore = true; + extraShares.push_back(share1); + } +#endif + + // add the music playlist location + share1.strPath = "special://musicplaylists/"; + share1.strName = g_localizeStrings.Get(20011); + share1.m_ignore = true; + extraShares.push_back(share1); + + // add the recordings dir as needed + if (CPVRDirectory::HasRadioRecordings()) + { + share1.strPath = PVR::CPVRRecordingsPath::PATH_ACTIVE_RADIO_RECORDINGS; + share1.strName = g_localizeStrings.Get(19017); // Recordings + extraShares.push_back(share1); + } + if (CPVRDirectory::HasDeletedRadioRecordings()) + { + share1.strPath = PVR::CPVRRecordingsPath::PATH_DELETED_RADIO_RECORDINGS; + share1.strName = g_localizeStrings.Get(19184); // Deleted recordings + extraShares.push_back(share1); + } + + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_AUDIOCDS_RECORDINGPATH) != "") + { + share1.strPath = "special://recordings/"; + share1.strName = g_localizeStrings.Get(21883); + extraShares.push_back(share1); + } + } + else if (m_type == "video") + { + CMediaSource share1; +#if defined(TARGET_ANDROID) + // add the default android video directory + std::string path; + if (CXBMCApp::GetExternalStorage(path, "videos") && !path.empty() && CFileUtils::Exists(path)) + { + share1.strPath = path; + share1.strName = g_localizeStrings.Get(20241); + share1.m_ignore = true; + extraShares.push_back(share1); + } +#endif +#if defined(TARGET_WINDOWS_STORE) + // add the default UWP music directory + std::string path; + if (XFILE::CWinLibraryDirectory::GetStoragePath(m_type, path) && !path.empty() && CDirectory::Exists(path)) + { + share1.strPath = path; + share1.strName = g_localizeStrings.Get(20246); + share1.m_ignore = true; + extraShares.push_back(share1); + } +#endif + + // add the video playlist location + share1.m_ignore = true; + share1.strPath = "special://videoplaylists/"; + share1.strName = g_localizeStrings.Get(20012); + extraShares.push_back(share1); + + // add the recordings dir as needed + if (CPVRDirectory::HasTVRecordings()) + { + share1.strPath = PVR::CPVRRecordingsPath::PATH_ACTIVE_TV_RECORDINGS; + share1.strName = g_localizeStrings.Get(19017); // Recordings + extraShares.push_back(share1); + } + if (CPVRDirectory::HasDeletedTVRecordings()) + { + share1.strPath = PVR::CPVRRecordingsPath::PATH_DELETED_TV_RECORDINGS; + share1.strName = g_localizeStrings.Get(19184); // Deleted recordings + extraShares.push_back(share1); + } + } + else if (m_type == "pictures") + { + CMediaSource share1; +#if defined(TARGET_ANDROID) + // add the default android music directory + std::string path; + if (CXBMCApp::GetExternalStorage(path, "pictures") && !path.empty() && CFileUtils::Exists(path)) + { + share1.strPath = path; + share1.strName = g_localizeStrings.Get(20242); + share1.m_ignore = true; + extraShares.push_back(share1); + } + + path.clear(); + if (CXBMCApp::GetExternalStorage(path, "photos") && !path.empty() && CFileUtils::Exists(path)) + { + share1.strPath = path; + share1.strName = g_localizeStrings.Get(20243); + share1.m_ignore = true; + extraShares.push_back(share1); + } +#endif +#if defined(TARGET_WINDOWS_STORE) + // add the default UWP music directory + std::string path; + if (XFILE::CWinLibraryDirectory::GetStoragePath(m_type, path) && !path.empty() && CDirectory::Exists(path)) + { + share1.strPath = path; + share1.strName = g_localizeStrings.Get(20247); + share1.m_ignore = true; + extraShares.push_back(share1); + } + path.clear(); + if (XFILE::CWinLibraryDirectory::GetStoragePath("photos", path) && !path.empty() && CDirectory::Exists(path)) + { + share1.strPath = path; + share1.strName = g_localizeStrings.Get(20248); + share1.m_ignore = true; + extraShares.push_back(share1); + } +#endif + + share1.m_ignore = true; + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_DEBUG_SCREENSHOTPATH) != "") + { + share1.strPath = "special://screenshots/"; + share1.strName = g_localizeStrings.Get(20008); + extraShares.push_back(share1); + } + } + else if (m_type == "games") + { + // nothing to add + } + else if (m_type == "programs") + { + // nothing to add + } + if (CGUIDialogFileBrowser::ShowAndGetSource(path, allowNetworkShares, extraShares.size() == 0 ? NULL : &extraShares)) + { + if (item < m_paths->Size()) // if the skin does funky things, m_paths may have been cleared + m_paths->Get(item)->SetPath(path); + if (!m_bNameChanged || m_name.empty()) + { + CURL url(path); + m_name = url.GetWithoutUserDetails(); + URIUtils::RemoveSlashAtEnd(m_name); + m_name = CUtil::GetTitleFromPath(m_name); + } + UpdateButtons(); + } +} + +void CGUIDialogMediaSource::OnPath(int item) +{ + if (item < 0 || item >= m_paths->Size()) return; + + std::string path(m_paths->Get(item)->GetPath()); + if (m_name != CUtil::GetTitleFromPath(path)) + m_bNameChanged = true; + + CGUIKeyboardFactory::ShowAndGetInput(path, CVariant{ g_localizeStrings.Get(1021) }, false); + m_paths->Get(item)->SetPath(path); + + if (!m_bNameChanged || m_name.empty()) + { + CURL url(m_paths->Get(item)->GetPath()); + m_name = url.GetWithoutUserDetails(); + URIUtils::RemoveSlashAtEnd(m_name); + m_name = CUtil::GetTitleFromPath(m_name); + } + UpdateButtons(); +} + +void CGUIDialogMediaSource::OnOK() +{ + // Verify the paths by doing a GetDirectory. + CFileItemList items; + + // Create temp media source to encode path urls as multipath + // Name of actual source may need to be made unique when saved in sources + CMediaSource share; + share.FromNameAndPaths(m_type, m_name, GetPaths()); + + if (StringUtils::StartsWithNoCase(share.strPath, "plugin://") || + CDirectory::GetDirectory(share.strPath, items, "", DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_ALLOW_PROMPT) || + CGUIDialogYesNo::ShowAndGetInput(CVariant{ 1001 }, CVariant{ 1025 })) + { + m_confirmed = true; + Close(); + } +} + +void CGUIDialogMediaSource::OnCancel() +{ + m_confirmed = false; + Close(); +} + +void CGUIDialogMediaSource::UpdateButtons() +{ + if (!m_paths->Size()) // sanity + return; + + CONTROL_ENABLE_ON_CONDITION(CONTROL_OK, !m_paths->Get(0)->GetPath().empty() && !m_name.empty()); + CONTROL_ENABLE_ON_CONDITION(CONTROL_PATH_ADD, !m_paths->Get(0)->GetPath().empty()); + CONTROL_ENABLE_ON_CONDITION(CONTROL_PATH_REMOVE, m_paths->Size() > 1); + // name + SET_CONTROL_LABEL2(CONTROL_NAME, m_name); + SendMessage(GUI_MSG_SET_TYPE, CONTROL_NAME, 0, 1022); + + int currentItem = GetSelectedItem(); + SendMessage(GUI_MSG_LABEL_RESET, CONTROL_PATH); + for (int i = 0; i < m_paths->Size(); i++) + { + CFileItemPtr item = m_paths->Get(i); + std::string path; + CURL url(item->GetPath()); + path = url.GetWithoutUserDetails(); + if (path.empty()) path = "<" + g_localizeStrings.Get(231) + ">"; // <None> + item->SetLabel(path); + } + CGUIMessage msg(GUI_MSG_LABEL_BIND, GetID(), CONTROL_PATH, 0, 0, m_paths); + OnMessage(msg); + SendMessage(GUI_MSG_ITEM_SELECT, CONTROL_PATH, currentItem); + + SET_CONTROL_HIDDEN(CONTROL_CONTENT); +} + +void CGUIDialogMediaSource::SetShare(const CMediaSource &share) +{ + m_paths->Clear(); + for (unsigned int i = 0; i < share.vecPaths.size(); i++) + { + CFileItemPtr item(new CFileItem(share.vecPaths[i], true)); + m_paths->Add(item); + } + if (0 == share.vecPaths.size()) + { + CFileItemPtr item(new CFileItem("", true)); + m_paths->Add(item); + } + m_name = share.strName; + UpdateButtons(); +} + +void CGUIDialogMediaSource::SetTypeOfMedia(const std::string &type, bool editNotAdd) +{ + m_type = type; + std::string heading; + if (editNotAdd) + { + if (type == "video") + heading = g_localizeStrings.Get(10053); + else if (type == "music") + heading = g_localizeStrings.Get(10054); + else if (type == "pictures") + heading = g_localizeStrings.Get(10055); + else if (type == "games") + heading = g_localizeStrings.Get(35252); // "Edit game source" + else if (type == "programs") + heading = g_localizeStrings.Get(10056); + else + heading = g_localizeStrings.Get(10057); + } + else + { + if (type == "video") + heading = g_localizeStrings.Get(10048); + else if (type == "music") + heading = g_localizeStrings.Get(10049); + else if (type == "pictures") + heading = g_localizeStrings.Get(13006); + else if (type == "games") + heading = g_localizeStrings.Get(35251); // "Add game source" + else if (type == "programs") + heading = g_localizeStrings.Get(10051); + else + heading = g_localizeStrings.Get(10052); + } + SET_CONTROL_LABEL(CONTROL_HEADING, heading); +} + +int CGUIDialogMediaSource::GetSelectedItem() +{ + CGUIMessage message(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_PATH); + OnMessage(message); + int value = message.GetParam1(); + if (value < 0 || value >= m_paths->Size()) return 0; + return value; +} + +void CGUIDialogMediaSource::HighlightItem(int item) +{ + for (int i = 0; i < m_paths->Size(); i++) + m_paths->Get(i)->Select(false); + if (item >= 0 && item < m_paths->Size()) + m_paths->Get(item)->Select(true); + CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), CONTROL_PATH, item); + OnMessage(msg); +} + +void CGUIDialogMediaSource::OnPathRemove(int item) +{ + m_paths->Remove(item); + UpdateButtons(); + if (item >= m_paths->Size()) + HighlightItem(m_paths->Size() - 1); + else + HighlightItem(item); + if (m_paths->Size() <= 1) + { + SET_CONTROL_FOCUS(CONTROL_PATH_ADD, 0); + } +} + +void CGUIDialogMediaSource::OnPathAdd() +{ + // add a new item and select it as well + CFileItemPtr item(new CFileItem("", true)); + m_paths->Add(item); + UpdateButtons(); + HighlightItem(m_paths->Size() - 1); +} + +std::vector<std::string> CGUIDialogMediaSource::GetPaths() const +{ + std::vector<std::string> paths; + for (int i = 0; i < m_paths->Size(); i++) + { + if (!m_paths->Get(i)->GetPath().empty()) + { // strip off the user and password for supported paths (anything that the password manager can auth) + // and add the user/pass to the password manager - note, we haven't confirmed that it works + // at this point, but if it doesn't, the user will get prompted anyway in implementation. + CURL url(m_paths->Get(i)->GetPath()); + if (CPasswordManager::GetInstance().IsURLSupported(url) && !url.GetUserName().empty()) + { + CPasswordManager::GetInstance().SaveAuthenticatedURL(url); + url.SetPassword(""); + url.SetUserName(""); + url.SetDomain(""); + } + paths.push_back(url.Get()); + } + } + return paths; +} + +void CGUIDialogMediaSource::OnDeinitWindow(int nextWindowID) +{ + CGUIDialog::OnDeinitWindow(nextWindowID); + + // clear paths container + CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), CONTROL_PATH, 0); + OnMessage(msg); +} diff --git a/xbmc/dialogs/GUIDialogMediaSource.h b/xbmc/dialogs/GUIDialogMediaSource.h new file mode 100644 index 0000000..e858e13 --- /dev/null +++ b/xbmc/dialogs/GUIDialogMediaSource.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +#include <string> +#include <vector> + +class CFileItemList; +class CMediaSource; + +class CGUIDialogMediaSource : + public CGUIDialog +{ +public: + CGUIDialogMediaSource(void); + ~CGUIDialogMediaSource(void) override; + bool OnMessage(CGUIMessage& message) override; + void OnDeinitWindow(int nextWindowID) override; + bool OnBack(int actionID) override; + static bool ShowAndAddMediaSource(const std::string &type); + static bool ShowAndEditMediaSource(const std::string &type, const CMediaSource &share); + static bool ShowAndEditMediaSource(const std::string &type, const std::string &share); + + bool IsConfirmed() const { return m_confirmed; } + + void SetShare(const CMediaSource &share); + void SetTypeOfMedia(const std::string &type, bool editNotAdd = false); +protected: + void OnPathBrowse(int item); + void OnPath(int item); + void OnPathAdd(); + void OnPathRemove(int item); + void OnOK(); + void OnCancel(); + void UpdateButtons(); + int GetSelectedItem(); + void HighlightItem(int item); + std::string GetUniqueMediaSourceName(); + static void OnMediaSourceChanged(const std::string& type, const std::string& oldName, const CMediaSource& share); + + std::vector<std::string> GetPaths() const; + + std::string m_type; + std::string m_name; + CFileItemList* m_paths; + bool m_confirmed = false; + bool m_bNameChanged = false; +}; diff --git a/xbmc/dialogs/GUIDialogNumeric.cpp b/xbmc/dialogs/GUIDialogNumeric.cpp new file mode 100644 index 0000000..182cf6a --- /dev/null +++ b/xbmc/dialogs/GUIDialogNumeric.cpp @@ -0,0 +1,916 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogNumeric.h" + +#include "ServiceBroker.h" +#include "XBDateTime.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUILabelControl.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/Key.h" +#include "input/XBMC_vkeys.h" +#include "interfaces/AnnouncementManager.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "utils/Digest.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +#include <cassert> + +#define CONTROL_HEADING_LABEL 1 +#define CONTROL_INPUT_LABEL 4 +#define CONTROL_NUM0 10 +#define CONTROL_NUM9 19 +#define CONTROL_PREVIOUS 20 +#define CONTROL_ENTER 21 +#define CONTROL_NEXT 22 +#define CONTROL_BACKSPACE 23 + +using namespace KODI::MESSAGING; +using KODI::UTILITY::CDigest; + +CGUIDialogNumeric::CGUIDialogNumeric(void) + : CGUIDialog(WINDOW_DIALOG_NUMERIC, "DialogNumeric.xml"), + m_bConfirmed{false}, + m_bCanceled{false}, + m_mode{INPUT_PASSWORD}, + m_block{}, + m_lastblock{}, + m_dirty{false} +{ + memset(&m_datetime, 0, sizeof(KODI::TIME::SystemTime)); + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogNumeric::~CGUIDialogNumeric(void) = default; + +void CGUIDialogNumeric::OnInitWindow() +{ + CGUIDialog::OnInitWindow(); + + CVariant data; + switch (m_mode) + { + case INPUT_TIME: + data["type"] = "time"; + break; + case INPUT_DATE: + data["type"] = "date"; + break; + case INPUT_IP_ADDRESS: + data["type"] = "ip"; + break; + case INPUT_PASSWORD: + data["type"] = "numericpassword"; + break; + case INPUT_NUMBER: + data["type"] = "number"; + break; + case INPUT_TIME_SECONDS: + data["type"] = "seconds"; + break; + default: + data["type"] = "keyboard"; + break; + } + + const CGUIControl *control = GetControl(CONTROL_HEADING_LABEL); + if (control != nullptr) + data["title"] = control->GetDescription(); + + data["value"] = GetOutputString(); + + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Input, "OnInputRequested", data); +} + +void CGUIDialogNumeric::OnDeinitWindow(int nextWindowID) +{ + // call base class + CGUIDialog::OnDeinitWindow(nextWindowID); + + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Input, "OnInputFinished"); +} + +bool CGUIDialogNumeric::OnAction(const CAction &action) +{ + if (action.GetID() == ACTION_NEXT_ITEM) + OnNext(); + else if (action.GetID() == ACTION_PREV_ITEM) + OnPrevious(); + else if (action.GetID() == ACTION_BACKSPACE) + OnBackSpace(); + else if (action.GetID() == ACTION_ENTER) + OnOK(); + else if (action.GetID() >= REMOTE_0 && action.GetID() <= REMOTE_9) + OnNumber(action.GetID() - REMOTE_0); + else if (action.GetID() >= KEY_VKEY && action.GetID() < KEY_UNICODE) + { + // input from the keyboard (vkey, not ascii) + uint8_t b = action.GetID() & 0xFF; + if (b == XBMCVK_LEFT) + OnPrevious(); + else if (b == XBMCVK_RIGHT) + OnNext(); + else if (b == XBMCVK_RETURN || b == XBMCVK_NUMPADENTER) + OnOK(); + else if (b == XBMCVK_BACK) + OnBackSpace(); + else if (b == XBMCVK_ESCAPE) + OnCancel(); + } + else if (action.GetID() == KEY_UNICODE) + { // input from the keyboard + if (action.GetUnicode() == 10 || action.GetUnicode() == 13) + OnOK(); // enter + else if (action.GetUnicode() == 8) + OnBackSpace(); // backspace + else if (action.GetUnicode() == 27) + OnCancel(); // escape + else if (action.GetUnicode() == 46) + OnNext(); // '.' + else if (action.GetUnicode() >= 48 && action.GetUnicode() < 58) // number + OnNumber(action.GetUnicode() - 48); + } + else + return CGUIDialog::OnAction(action); + + return true; +} + +bool CGUIDialogNumeric::OnBack(int actionID) +{ + OnCancel(); + return true; +} + +bool CGUIDialogNumeric::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_WINDOW_INIT: + { + m_bConfirmed = false; + m_bCanceled = false; + m_dirty = false; + return CGUIDialog::OnMessage(message); + } + break; + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + m_bConfirmed = false; + m_bCanceled = false; + if (CONTROL_NUM0 <= iControl && iControl <= CONTROL_NUM9) // User numeric entry via dialog button UI + { + OnNumber(iControl - 10); + return true; + } + else if (iControl == CONTROL_PREVIOUS) + { + OnPrevious(); + return true; + } + else if (iControl == CONTROL_NEXT) + { + OnNext(); + return true; + } + else if (iControl == CONTROL_BACKSPACE) + { + OnBackSpace(); + return true; + } + else if (iControl == CONTROL_ENTER) + { + OnOK(); + return true; + } + } + break; + + case GUI_MSG_SET_TEXT: + SetMode(m_mode, message.GetLabel()); + + // close the dialog if requested + if (message.GetParam1() > 0) + OnOK(); + break; + } + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogNumeric::OnBackSpace() +{ + if (!m_dirty && m_block) + { + --m_block; + return; + } + if (m_mode == INPUT_NUMBER || m_mode == INPUT_PASSWORD) + { // just go back one character + if (!m_number.empty()) + m_number.erase(m_number.length() - 1); + } + else if (m_mode == INPUT_IP_ADDRESS) + { + if (m_ip[m_block]) + m_ip[m_block] /= 10; + else if (m_block) + { + --m_block; + m_dirty = false; + } + } + else if (m_mode == INPUT_TIME) + { + if (m_block == 0) + m_datetime.hour /= 10; + else if (m_datetime.minute) + m_datetime.minute /= 10; + else + { + m_block = 0; + m_dirty = false; + } + } + else if (m_mode == INPUT_TIME_SECONDS) + { + if (m_block == 0) + m_datetime.hour /= 10; + else if (m_block == 1) + { + if (m_datetime.minute) + m_datetime.minute /= 10; + else + { + m_block = 0; + m_dirty = false; + } + } + else if (m_datetime.second) + m_datetime.minute /= 10; + else + { + m_block = 0; + m_dirty = false; + } + } + else if (m_mode == INPUT_DATE) + { + if (m_block == 0) + m_datetime.day /= 10; + else if (m_block == 1) + { + if (m_datetime.month) + m_datetime.month /= 10; + else + { + m_block = 0; + m_dirty = false; + } + } + else if (m_datetime.year) // m_block == 2 + m_datetime.year /= 10; + else + { + m_block = 1; + m_dirty = false; + } + } +} + +void CGUIDialogNumeric::OnPrevious() +{ + if (m_block) + m_block--; + m_dirty = false; +} + +void CGUIDialogNumeric::OnNext() +{ + if (m_mode == INPUT_IP_ADDRESS && m_block==0 && m_ip[0]==0) + return; + + if (m_block < m_lastblock) + m_block++; + m_dirty = false; + if (m_mode == INPUT_DATE) + VerifyDate(m_block == 2); +} + +void CGUIDialogNumeric::FrameMove() +{ + std::string strLabel; + unsigned int start = 0; + unsigned int end = 0; + if (m_mode == INPUT_PASSWORD) + strLabel.assign(m_number.length(), '*'); + else if (m_mode == INPUT_NUMBER) + strLabel = m_number; + else if (m_mode == INPUT_TIME) + { // format up the time + strLabel = StringUtils::Format("{:2}:{:02}", m_datetime.hour, m_datetime.minute); + start = m_block * 3; + end = m_block * 3 + 2; + } + else if (m_mode == INPUT_TIME_SECONDS) + { // format up the time + strLabel = StringUtils::Format("{:2}:{:02}:{:02}", m_datetime.hour, m_datetime.minute, + m_datetime.second); + start = m_block * 3; + end = m_block * 3 + 2; + } + else if (m_mode == INPUT_DATE) + { // format up the date + strLabel = + StringUtils::Format("{:2}/{:2}/{:4}", m_datetime.day, m_datetime.month, m_datetime.year); + start = m_block * 3; + end = m_block * 3 + 2; + if (m_block == 2) + end = m_block * 3 + 4; + } + else if (m_mode == INPUT_IP_ADDRESS) + { // format up the date + strLabel = StringUtils::Format("{:3}.{:3}.{:3}.{:3}", m_ip[0], m_ip[1], m_ip[2], m_ip[3]); + start = m_block * 4; + end = m_block * 4 + 3; + } + CGUILabelControl *pLabel = dynamic_cast<CGUILabelControl *>(GetControl(CONTROL_INPUT_LABEL)); + if (pLabel) + { + pLabel->SetLabel(strLabel); + pLabel->SetHighlight(start, end); + } + CGUIDialog::FrameMove(); +} + +void CGUIDialogNumeric::OnNumber(uint32_t num) +{ + ResetAutoClose(); + + switch (m_mode) + { + case INPUT_NUMBER: + case INPUT_PASSWORD: + m_number += num + '0'; + break; + case INPUT_TIME: + HandleInputTime(num); + break; + case INPUT_TIME_SECONDS: + HandleInputSeconds(num); + break; + case INPUT_DATE: + HandleInputDate(num); + break; + case INPUT_IP_ADDRESS: + HandleInputIP(num); + break; + } +} + +void CGUIDialogNumeric::SetMode(INPUT_MODE mode, const KODI::TIME::SystemTime& initial) +{ + m_mode = mode; + m_block = 0; + m_lastblock = 0; + if (m_mode == INPUT_TIME || m_mode == INPUT_TIME_SECONDS || m_mode == INPUT_DATE) + { + m_datetime = initial; + m_lastblock = (m_mode != INPUT_TIME) ? 2 : 1; + } +} + +void CGUIDialogNumeric::SetMode(INPUT_MODE mode, const std::string &initial) +{ + m_mode = mode; + m_block = 0; + m_lastblock = 0; + if (m_mode == INPUT_TIME || m_mode == INPUT_TIME_SECONDS || m_mode == INPUT_DATE) + { + CDateTime dateTime; + if (m_mode == INPUT_TIME || m_mode == INPUT_TIME_SECONDS) + { + // check if we have a pure number + if (initial.find_first_not_of("0123456789") == std::string::npos) + { + long seconds = strtol(initial.c_str(), nullptr, 10); + dateTime = seconds; + } + else + { + std::string tmp = initial; + // if we are handling seconds and if the string only contains + // "mm:ss" we need to add dummy "hh:" to get "hh:mm:ss" + if (m_mode == INPUT_TIME_SECONDS && tmp.length() <= 5) + tmp = "00:" + tmp; + dateTime.SetFromDBTime(tmp); + } + } + else if (m_mode == INPUT_DATE) + { + std::string tmp = initial; + StringUtils::Replace(tmp, '/', '.'); + dateTime.SetFromDBDate(tmp); + } + + if (!dateTime.IsValid()) + return; + + dateTime.GetAsSystemTime(m_datetime); + m_lastblock = (m_mode == INPUT_DATE) ? 2 : 1; + } + else if (m_mode == INPUT_IP_ADDRESS) + { + m_lastblock = 3; + auto blocks = StringUtils::Split(initial, '.'); + if (blocks.size() != 4) + return; + + for (size_t i = 0; i < blocks.size(); ++i) + { + if (blocks[i].length() > 3) + return; + + m_ip[i] = static_cast<uint8_t>(atoi(blocks[i].c_str())); + } + } + else if (m_mode == INPUT_NUMBER || m_mode == INPUT_PASSWORD) + m_number = initial; +} + +KODI::TIME::SystemTime CGUIDialogNumeric::GetOutput() const +{ + assert(m_mode == INPUT_TIME || m_mode == INPUT_TIME_SECONDS || m_mode == INPUT_DATE); + return m_datetime; +} + +std::string CGUIDialogNumeric::GetOutputString() const +{ + switch (m_mode) + { + case INPUT_DATE: + return StringUtils::Format("{:02}/{:02}/{:04}", m_datetime.day, m_datetime.month, + m_datetime.year); + case INPUT_TIME: + return StringUtils::Format("{}:{:02}", m_datetime.hour, m_datetime.minute); + case INPUT_TIME_SECONDS: + return StringUtils::Format("{}:{:02}:{:02}", m_datetime.hour, m_datetime.minute, + m_datetime.second); + case INPUT_IP_ADDRESS: + return StringUtils::Format("{}.{}.{}.{}", m_ip[0], m_ip[1], m_ip[2], m_ip[3]); + case INPUT_NUMBER: + case INPUT_PASSWORD: + return m_number; + } + + //should never get here + return std::string(); +} + +bool CGUIDialogNumeric::ShowAndGetSeconds(std::string &timeString, const std::string &heading) +{ + CGUIDialogNumeric *pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogNumeric>(WINDOW_DIALOG_NUMERIC); + if (!pDialog) return false; + int seconds = StringUtils::TimeStringToSeconds(timeString); + KODI::TIME::SystemTime time = {}; + time.hour = seconds / 3600; + time.minute = (seconds - time.hour * 3600) / 60; + time.second = seconds - time.hour * 3600 - time.minute * 60; + pDialog->SetMode(INPUT_TIME_SECONDS, time); + pDialog->SetHeading(heading); + pDialog->Open(); + if (!pDialog->IsConfirmed() || pDialog->IsCanceled()) + return false; + time = pDialog->GetOutput(); + seconds = time.hour * 3600 + time.minute * 60 + time.second; + timeString = StringUtils::SecondsToTimeString(seconds); + return true; +} + +bool CGUIDialogNumeric::ShowAndGetTime(KODI::TIME::SystemTime& time, const std::string& heading) +{ + CGUIDialogNumeric *pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogNumeric>(WINDOW_DIALOG_NUMERIC); + if (!pDialog) return false; + pDialog->SetMode(INPUT_TIME, time); + pDialog->SetHeading(heading); + pDialog->Open(); + if (!pDialog->IsConfirmed() || pDialog->IsCanceled()) + return false; + time = pDialog->GetOutput(); + return true; +} + +bool CGUIDialogNumeric::ShowAndGetDate(KODI::TIME::SystemTime& date, const std::string& heading) +{ + CGUIDialogNumeric *pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogNumeric>(WINDOW_DIALOG_NUMERIC); + if (!pDialog) return false; + pDialog->SetMode(INPUT_DATE, date); + pDialog->SetHeading(heading); + pDialog->Open(); + if (!pDialog->IsConfirmed() || pDialog->IsCanceled()) + return false; + date = pDialog->GetOutput(); + return true; +} + +bool CGUIDialogNumeric::ShowAndGetIPAddress(std::string &IPAddress, const std::string &heading) +{ + CGUIDialogNumeric *pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogNumeric>(WINDOW_DIALOG_NUMERIC); + if (!pDialog) return false; + pDialog->SetMode(INPUT_IP_ADDRESS, IPAddress); + pDialog->SetHeading(heading); + pDialog->Open(); + if (!pDialog->IsConfirmed() || pDialog->IsCanceled()) + return false; + IPAddress = pDialog->GetOutputString(); + return true; +} + +bool CGUIDialogNumeric::ShowAndGetNumber(std::string& strInput, const std::string &strHeading, unsigned int iAutoCloseTimeoutMs /* = 0 */, bool bSetHidden /* = false */) +{ + // Prompt user for password input + CGUIDialogNumeric *pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogNumeric>(WINDOW_DIALOG_NUMERIC); + pDialog->SetHeading( strHeading ); + + if (bSetHidden) + pDialog->SetMode(INPUT_PASSWORD, strInput); + else + pDialog->SetMode(INPUT_NUMBER, strInput); + if (iAutoCloseTimeoutMs) + pDialog->SetAutoClose(iAutoCloseTimeoutMs); + + pDialog->Open(); + + if (!pDialog->IsAutoClosed() && (!pDialog->IsConfirmed() || pDialog->IsCanceled())) + return false; + strInput = pDialog->GetOutputString(); + return true; +} + +// \brief Show numeric keypad twice to get and confirm a user-entered password string. +// \param strNewPassword String to preload into the keyboard accumulator. Overwritten with user input if return=true. +// \return true if successful display and user input entry/re-entry. false if unsuccessful display, no user input, or canceled editing. +bool CGUIDialogNumeric::ShowAndVerifyNewPassword(std::string& strNewPassword) +{ + // Prompt user for password input + std::string strUserInput; + InputVerificationResult ret = ShowAndVerifyInput(strUserInput, g_localizeStrings.Get(12340), false); + if (ret != InputVerificationResult::SUCCESS) + { + if (ret == InputVerificationResult::FAILED) + { + // Show error to user saying the password entry was blank + HELPERS::ShowOKDialogText(CVariant{12357}, CVariant{12358}); // Password is empty/blank + } + return false; + } + + if (strUserInput.empty()) + // user canceled out + return false; + + // Prompt again for password input, this time sending previous input as the password to verify + ret = ShowAndVerifyInput(strUserInput, g_localizeStrings.Get(12341), true); + if (ret != InputVerificationResult::SUCCESS) + { + if (ret == InputVerificationResult::FAILED) + { + // Show error to user saying the password re-entry failed + HELPERS::ShowOKDialogText(CVariant{12357}, CVariant{12344}); // Password do not match + } + return false; + } + + // password entry and re-entry succeeded + strNewPassword = strUserInput; + return true; +} + +// \brief Show numeric keypad and verify user input against strPassword. +// \param strPassword Value to compare against user input. +// \param strHeading String shown on dialog title. Converts to localized string if contains a positive integer. +// \param iRetries If greater than 0, shows "Incorrect password, %d retries left" on dialog line 2, else line 2 is blank. +// \return 0 if successful display and user input. 1 if unsuccessful input. -1 if no user input or canceled editing. +int CGUIDialogNumeric::ShowAndVerifyPassword(std::string& strPassword, const std::string& strHeading, int iRetries) +{ + std::string strTempHeading = strHeading; + if (iRetries > 0) + { + // Show a string telling user they have iRetries retries left + strTempHeading = StringUtils::Format("{}. {} {} {}", strHeading, g_localizeStrings.Get(12342), + iRetries, g_localizeStrings.Get(12343)); + } + + // make a copy of strPassword to prevent from overwriting it later + std::string strPassTemp = strPassword; + InputVerificationResult ret = ShowAndVerifyInput(strPassTemp, strTempHeading, true); + if (ret == InputVerificationResult::SUCCESS) + return 0; // user entered correct password + + if (ret == InputVerificationResult::CANCELED) + return -1; // user canceled out + + return 1; // user must have entered an incorrect password +} + +// \brief Show numeric keypad and verify user input against strToVerify. +// \param strToVerify Value to compare against user input. +// \param dlgHeading String shown on dialog title. +// \param bVerifyInput If set as true we verify the users input versus strToVerify. +// \return the result of the check (success, failed, or canceled by user). +InputVerificationResult CGUIDialogNumeric::ShowAndVerifyInput(std::string& strToVerify, const std::string& dlgHeading, bool bVerifyInput) +{ + // Prompt user for password input + CGUIDialogNumeric *pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogNumeric>(WINDOW_DIALOG_NUMERIC); + pDialog->SetHeading(dlgHeading); + + std::string strInput; + if (!bVerifyInput) + strInput = strToVerify; + + pDialog->SetMode(INPUT_PASSWORD, strInput); + pDialog->Open(); + + strInput = pDialog->GetOutputString(); + + if (!pDialog->IsConfirmed() || pDialog->IsCanceled()) + { + // user canceled out + strToVerify = ""; + return InputVerificationResult::CANCELED; + } + + const std::string md5pword2 = CDigest::Calculate(CDigest::Type::MD5, strInput); + + if (!bVerifyInput) + { + strToVerify = md5pword2; + return InputVerificationResult::SUCCESS; + } + + return StringUtils::EqualsNoCase(strToVerify, md5pword2) ? InputVerificationResult::SUCCESS : InputVerificationResult::FAILED; +} + +bool CGUIDialogNumeric::IsConfirmed() const +{ + return m_bConfirmed; +} + +bool CGUIDialogNumeric::IsCanceled() const +{ + return m_bCanceled; +} + +void CGUIDialogNumeric::SetHeading(const std::string& strHeading) +{ + Initialize(); + CGUIMessage msg(GUI_MSG_LABEL_SET, GetID(), CONTROL_HEADING_LABEL); + msg.SetLabel(strHeading); + OnMessage(msg); +} + +void CGUIDialogNumeric::VerifyDate(bool checkYear) +{ + if (m_datetime.day == 0) + m_datetime.day = 1; + if (m_datetime.month == 0) + m_datetime.month = 1; + // check for number of days in the month + if (m_datetime.day == 31) + { + if (m_datetime.month == 4 || m_datetime.month == 6 || m_datetime.month == 9 || + m_datetime.month == 11) + m_datetime.day = 30; + } + if (m_datetime.month == 2 && m_datetime.day > 28) + { + m_datetime.day = 29; // max in february. + if (checkYear) + { + // leap years occur when the year is divisible by 4 but not by 100, or the year is divisible by 400 + // thus they don't occur, if the year has a remainder when divided by 4, or when the year is divisible by 100 but not by 400 + if ((m_datetime.year % 4) || (!(m_datetime.year % 100) && (m_datetime.year % 400))) + m_datetime.day = 28; + } + } +} + +void CGUIDialogNumeric::OnOK() +{ + m_bConfirmed = true; + m_bCanceled = false; + Close(); +} + +void CGUIDialogNumeric::OnCancel() +{ + m_bConfirmed = false; + m_bCanceled = true; + Close(); +} + +void CGUIDialogNumeric::HandleInputIP(uint32_t num) +{ + if (m_dirty && ((m_ip[m_block] < 25) || (m_ip[m_block] == 25 && num < 6) || !(m_block == 0 && num == 0))) + { + m_ip[m_block] *= 10; + m_ip[m_block] += num; + } + else + m_ip[m_block] = num; + + if (m_ip[m_block] > 25 || (m_ip[m_block] == 0 && num == 0)) + { + ++m_block; + if (m_block > 3) + m_block = 0; + m_dirty = false; + } + else + m_dirty = true; +} + +void CGUIDialogNumeric::HandleInputDate(uint32_t num) +{ + if (m_block == 0) // day of month + { + if (m_dirty && (m_datetime.day < 3 || num < 2)) + { + m_datetime.day *= 10; + m_datetime.day += num; + } + else + m_datetime.day = num; + + if (m_datetime.day > 3) + { + m_block = 1; // move to months + m_dirty = false; + } + else + m_dirty = true; + } + else if (m_block == 1) // months + { + if (m_dirty && num < 3) + { + m_datetime.month *= 10; + m_datetime.month += num; + } + else + m_datetime.month = num; + + if (m_datetime.month > 1) + { + VerifyDate(false); + m_block = 2; // move to year + m_dirty = false; + } + else + m_dirty = true; + } + else // year + { + if (m_dirty && m_datetime.year < 1000) // have taken input + { + m_datetime.year *= 10; + m_datetime.year += num; + } + else + m_datetime.year = num; + + if (m_datetime.year > 1000) + { + VerifyDate(true); + m_block = 0; // move to day of month + m_dirty = false; + } + else + m_dirty = true; + } +} + +void CGUIDialogNumeric::HandleInputSeconds(uint32_t num) +{ + if (m_block == 0) // hour + { + if (m_dirty) // have input the first digit + { + m_datetime.hour *= 10; + m_datetime.hour += num; + m_block = 1; // move to minutes - allows up to 99 hours + m_dirty = false; + } + else // this is the first digit + { + m_datetime.hour = num; + m_dirty = true; + } + } + else if (m_block == 1) // minute + { + if (m_dirty) // have input the first digit + { + m_datetime.minute *= 10; + m_datetime.minute += num; + m_block = 2; // move to seconds - allows up to 99 minutes + m_dirty = false; + } + else // this is the first digit + { + m_datetime.minute = num; + if (num > 5) + { + m_block = 2; // move to seconds + m_dirty = false; + } + else + m_dirty = true; + } + } + else // seconds + { + if (m_dirty) // have input the first digit + { + m_datetime.second *= 10; + m_datetime.second += num; + m_block = 0; // move to hours + m_dirty = false; + } + else // this is the first digit + { + m_datetime.second = num; + if (num > 5) + { + m_block = 0; // move to hours + m_dirty = false; + } + else + m_dirty = true; + } + } +} + +void CGUIDialogNumeric::HandleInputTime(uint32_t num) +{ + if (m_block == 0) // hour + { + if (m_dirty) // have input the first digit + { + if (m_datetime.hour < 2 || num < 4) + { + m_datetime.hour *= 10; + m_datetime.hour += num; + } + else + m_datetime.hour = num; + + m_block = 1; // move to minutes + m_dirty = false; + } + else // this is the first digit + { + m_datetime.hour = num; + + if (num > 2) + { + m_block = 1; // move to minutes + m_dirty = false; + } + else + m_dirty = true; + } + } + else // minute + { + if (m_dirty) // have input the first digit + { + m_datetime.minute *= 10; + m_datetime.minute += num; + m_block = 0; // move to hours + m_dirty = false; + } + else // this is the first digit + { + m_datetime.minute = num; + + if (num > 5) + { + m_block = 0; // move to hours + m_dirty = false; + } + else + m_dirty = true; + } + } +} + diff --git a/xbmc/dialogs/GUIDialogNumeric.h b/xbmc/dialogs/GUIDialogNumeric.h new file mode 100644 index 0000000..2932053 --- /dev/null +++ b/xbmc/dialogs/GUIDialogNumeric.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" +#include "utils/XTimeUtils.h" + +#include <cstdint> + +enum class InputVerificationResult +{ + CANCELED, + FAILED, + SUCCESS +}; + +class CGUIDialogNumeric : + public CGUIDialog +{ +public: + enum INPUT_MODE { INPUT_TIME = 1, INPUT_DATE, INPUT_IP_ADDRESS, INPUT_PASSWORD, INPUT_NUMBER, INPUT_TIME_SECONDS }; + CGUIDialogNumeric(void); + ~CGUIDialogNumeric(void) override; + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction &action) override; + bool OnBack(int actionID) override; + void FrameMove() override; + + bool IsConfirmed() const; + bool IsCanceled() const; + bool IsInputHidden() const { return m_mode == INPUT_PASSWORD; } + + static bool ShowAndVerifyNewPassword(std::string& strNewPassword); + static int ShowAndVerifyPassword(std::string& strPassword, const std::string& strHeading, int iRetries); + static InputVerificationResult ShowAndVerifyInput(std::string& strPassword, const std::string& strHeading, bool bGetUserInput); + + void SetHeading(const std::string &strHeading); + void SetMode(INPUT_MODE mode, const KODI::TIME::SystemTime& initial); + void SetMode(INPUT_MODE mode, const std::string &initial); + KODI::TIME::SystemTime GetOutput() const; + std::string GetOutputString() const; + + static bool ShowAndGetTime(KODI::TIME::SystemTime& time, const std::string& heading); + static bool ShowAndGetDate(KODI::TIME::SystemTime& date, const std::string& heading); + static bool ShowAndGetIPAddress(std::string &IPAddress, const std::string &heading); + static bool ShowAndGetNumber(std::string& strInput, const std::string &strHeading, unsigned int iAutoCloseTimeoutMs = 0, bool bSetHidden = false); + static bool ShowAndGetSeconds(std::string& timeString, const std::string &heading); + +protected: + void OnInitWindow() override; + void OnDeinitWindow(int nextWindowID) override; + + void OnNumber(uint32_t num); + void VerifyDate(bool checkYear); + void OnNext(); + void OnPrevious(); + void OnBackSpace(); + void OnOK(); + void OnCancel(); + + void HandleInputIP(uint32_t num); + void HandleInputDate(uint32_t num); + void HandleInputSeconds(uint32_t num); + void HandleInputTime(uint32_t num); + + bool m_bConfirmed; + bool m_bCanceled; + + INPUT_MODE m_mode; // the current input mode + KODI::TIME::SystemTime m_datetime; // for time and date modes + uint8_t m_ip[4]; // for ip address mode + uint32_t m_block; // for time, date, and IP methods. + uint32_t m_lastblock; + bool m_dirty; // true if the current block has been changed. + std::string m_number; ///< for number or password input +}; diff --git a/xbmc/dialogs/GUIDialogOK.cpp b/xbmc/dialogs/GUIDialogOK.cpp new file mode 100644 index 0000000..af626dc --- /dev/null +++ b/xbmc/dialogs/GUIDialogOK.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogOK.h" + +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIMessage.h" +#include "guilib/GUIWindowManager.h" +#include "utils/Variant.h" + +CGUIDialogOK::CGUIDialogOK(void) + : CGUIDialogBoxBase(WINDOW_DIALOG_OK, "DialogConfirm.xml") +{ +} + +CGUIDialogOK::~CGUIDialogOK(void) = default; + +bool CGUIDialogOK::OnMessage(CGUIMessage& message) +{ + if (message.GetMessage() == GUI_MSG_CLICKED) + { + int iControl = message.GetSenderId(); + if (iControl == CONTROL_YES_BUTTON) + { + m_bConfirmed = true; + Close(); + return true; + } + } + return CGUIDialogBoxBase::OnMessage(message); +} + +// \brief Show CGUIDialogOK dialog, then wait for user to dismiss it. +bool CGUIDialogOK::ShowAndGetInput(const CVariant& heading, const CVariant& text) +{ + CGUIDialogOK *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogOK>(WINDOW_DIALOG_OK); + if (!dialog) + return false; + dialog->SetHeading(heading); + dialog->SetText(text); + dialog->Open(); + return dialog->IsConfirmed(); +} + +// \brief Show CGUIDialogOK dialog, then wait for user to dismiss it. +bool CGUIDialogOK::ShowAndGetInput(const CVariant& heading, + const CVariant& line0, + const CVariant& line1, + const CVariant& line2) +{ + CGUIDialogOK *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogOK>(WINDOW_DIALOG_OK); + if (!dialog) + return false; + dialog->SetHeading(heading); + dialog->SetLine(0, line0); + dialog->SetLine(1, line1); + dialog->SetLine(2, line2); + dialog->Open(); + return dialog->IsConfirmed(); +} + +bool CGUIDialogOK::ShowAndGetInput(const HELPERS::DialogOKMessage & options) +{ + if (!options.heading.isNull()) + SetHeading(options.heading); + if (!options.text.isNull()) + SetText(options.text); + + for (size_t i = 0; i < 3; ++i) + { + if (!options.lines[i].isNull()) + SetLine(i, options.lines[i]); + } + Open(); + return IsConfirmed(); +} + +void CGUIDialogOK::OnInitWindow() +{ + SET_CONTROL_HIDDEN(CONTROL_NO_BUTTON); + SET_CONTROL_HIDDEN(CONTROL_CUSTOM_BUTTON); + SET_CONTROL_HIDDEN(CONTROL_PROGRESS_BAR); + SET_CONTROL_FOCUS(CONTROL_YES_BUTTON, 0); + + CGUIDialogBoxBase::OnInitWindow(); +} + +int CGUIDialogOK::GetDefaultLabelID(int controlId) const +{ + if (controlId == CONTROL_YES_BUTTON) + return 186; + return CGUIDialogBoxBase::GetDefaultLabelID(controlId); +} diff --git a/xbmc/dialogs/GUIDialogOK.h b/xbmc/dialogs/GUIDialogOK.h new file mode 100644 index 0000000..94678e3 --- /dev/null +++ b/xbmc/dialogs/GUIDialogOK.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "GUIDialogBoxBase.h" +#include "messaging/helpers/DialogOKHelper.h" + +class CGUIMessage; +class CVariant; + +using namespace KODI::MESSAGING; + +class CGUIDialogOK : + public CGUIDialogBoxBase +{ +public: + CGUIDialogOK(void); + ~CGUIDialogOK(void) override; + bool OnMessage(CGUIMessage& message) override; + static bool ShowAndGetInput(const CVariant& heading, const CVariant& text); + static bool ShowAndGetInput(const CVariant& heading, + const CVariant& line0, + const CVariant& line1, + const CVariant& line2); + /*! + \brief Open a OK dialog and wait for input + + \param[in] options a struct of type DialogOKMessage containing + the options to set for this dialog. + + \sa KODI::MESSAGING::HELPERS::DialogOKMessage + */ + bool ShowAndGetInput(const HELPERS::DialogOKMessage& options); +protected: + void OnInitWindow() override; + int GetDefaultLabelID(int controlId) const override; +}; diff --git a/xbmc/dialogs/GUIDialogPlayEject.cpp b/xbmc/dialogs/GUIDialogPlayEject.cpp new file mode 100644 index 0000000..14ee2bc --- /dev/null +++ b/xbmc/dialogs/GUIDialogPlayEject.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogPlayEject.h" + +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "storage/MediaManager.h" +#include "utils/Variant.h" + +#include <utility> + +#define ID_BUTTON_PLAY 11 +#define ID_BUTTON_EJECT 10 + +CGUIDialogPlayEject::CGUIDialogPlayEject() + : CGUIDialogYesNo(WINDOW_DIALOG_PLAY_EJECT) +{ +} + +CGUIDialogPlayEject::~CGUIDialogPlayEject() = default; + +bool CGUIDialogPlayEject::OnMessage(CGUIMessage& message) +{ + if (message.GetMessage() == GUI_MSG_CLICKED) + { + int iControl = message.GetSenderId(); + if (iControl == ID_BUTTON_PLAY) + { + if (CServiceBroker::GetMediaManager().IsDiscInDrive()) + { + m_bConfirmed = true; + Close(); + } + + return true; + } + if (iControl == ID_BUTTON_EJECT) + { + CServiceBroker::GetMediaManager().ToggleTray(); + return true; + } + } + + return CGUIDialogYesNo::OnMessage(message); +} + +void CGUIDialogPlayEject::FrameMove() +{ + CONTROL_ENABLE_ON_CONDITION(ID_BUTTON_PLAY, CServiceBroker::GetMediaManager().IsDiscInDrive()); + + CGUIDialogYesNo::FrameMove(); +} + +void CGUIDialogPlayEject::OnInitWindow() +{ + if (CServiceBroker::GetMediaManager().IsDiscInDrive()) + { + m_defaultControl = ID_BUTTON_PLAY; + } + else + { + CONTROL_DISABLE(ID_BUTTON_PLAY); + m_defaultControl = ID_BUTTON_EJECT; + } + + CGUIDialogYesNo::OnInitWindow(); +} + +bool CGUIDialogPlayEject::ShowAndGetInput(const std::string& strLine1, + const std::string& strLine2, + unsigned int uiAutoCloseTime /* = 0 */) +{ + + // Create the dialog + CGUIDialogPlayEject * pDialog = (CGUIDialogPlayEject *)CServiceBroker::GetGUI()->GetWindowManager(). + GetWindow(WINDOW_DIALOG_PLAY_EJECT); + if (!pDialog) + return false; + + // Setup dialog parameters + pDialog->SetHeading(CVariant{219}); + pDialog->SetLine(0, CVariant{429}); + pDialog->SetLine(1, CVariant{strLine1}); + pDialog->SetLine(2, CVariant{strLine2}); + pDialog->SetChoice(ID_BUTTON_PLAY - 10, 208); + pDialog->SetChoice(ID_BUTTON_EJECT - 10, 13391); + if (uiAutoCloseTime) + pDialog->SetAutoClose(uiAutoCloseTime); + + // Display the dialog + pDialog->Open(); + + return pDialog->IsConfirmed(); +} diff --git a/xbmc/dialogs/GUIDialogPlayEject.h b/xbmc/dialogs/GUIDialogPlayEject.h new file mode 100644 index 0000000..8354b53 --- /dev/null +++ b/xbmc/dialogs/GUIDialogPlayEject.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "GUIDialogYesNo.h" + +class CGUIDialogPlayEject : public CGUIDialogYesNo +{ +public: + CGUIDialogPlayEject(); + ~CGUIDialogPlayEject() override; + bool OnMessage(CGUIMessage& message) override; + void FrameMove() override; + + static bool ShowAndGetInput(const std::string& strLine1, + const std::string& strLine2, + unsigned int uiAutoCloseTime = 0); + +protected: + void OnInitWindow() override; +}; diff --git a/xbmc/dialogs/GUIDialogPlayerControls.cpp b/xbmc/dialogs/GUIDialogPlayerControls.cpp new file mode 100644 index 0000000..e137eb1 --- /dev/null +++ b/xbmc/dialogs/GUIDialogPlayerControls.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogPlayerControls.h" + + +CGUIDialogPlayerControls::CGUIDialogPlayerControls(void) + : CGUIDialog(WINDOW_DIALOG_PLAYER_CONTROLS, "PlayerControls.xml") +{ + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogPlayerControls::~CGUIDialogPlayerControls(void) = default; + diff --git a/xbmc/dialogs/GUIDialogPlayerControls.h b/xbmc/dialogs/GUIDialogPlayerControls.h new file mode 100644 index 0000000..e2bb1a4 --- /dev/null +++ b/xbmc/dialogs/GUIDialogPlayerControls.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +class CGUIDialogPlayerControls : + public CGUIDialog +{ +public: + CGUIDialogPlayerControls(void); + ~CGUIDialogPlayerControls(void) override; +}; diff --git a/xbmc/dialogs/GUIDialogPlayerProcessInfo.cpp b/xbmc/dialogs/GUIDialogPlayerProcessInfo.cpp new file mode 100644 index 0000000..8276311 --- /dev/null +++ b/xbmc/dialogs/GUIDialogPlayerProcessInfo.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogPlayerProcessInfo.h" + +#include "input/Key.h" + +CGUIDialogPlayerProcessInfo::CGUIDialogPlayerProcessInfo(void) + : CGUIDialog(WINDOW_DIALOG_PLAYER_PROCESS_INFO, "DialogPlayerProcessInfo.xml") +{ + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogPlayerProcessInfo::~CGUIDialogPlayerProcessInfo(void) = default; + +bool CGUIDialogPlayerProcessInfo::OnAction(const CAction &action) +{ + if (action.GetID() == ACTION_PLAYER_PROCESS_INFO) + { + Close(); + return true; + } + return CGUIDialog::OnAction(action); +} diff --git a/xbmc/dialogs/GUIDialogPlayerProcessInfo.h b/xbmc/dialogs/GUIDialogPlayerProcessInfo.h new file mode 100644 index 0000000..7413770 --- /dev/null +++ b/xbmc/dialogs/GUIDialogPlayerProcessInfo.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +class CGUIDialogPlayerProcessInfo : public CGUIDialog +{ +public: + CGUIDialogPlayerProcessInfo(void); + ~CGUIDialogPlayerProcessInfo(void) override; + + bool OnAction(const CAction &action) override; +}; diff --git a/xbmc/dialogs/GUIDialogProgress.cpp b/xbmc/dialogs/GUIDialogProgress.cpp new file mode 100644 index 0000000..21acb0a --- /dev/null +++ b/xbmc/dialogs/GUIDialogProgress.cpp @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogProgress.h" + +#include "guilib/GUIProgressControl.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/guiinfo/GUIInfoLabels.h" +#include "utils/Variant.h" +#include "utils/log.h" + +#include <mutex> + +using namespace std::chrono_literals; + +CGUIDialogProgress::CGUIDialogProgress(void) + : CGUIDialogBoxBase(WINDOW_DIALOG_PROGRESS, "DialogConfirm.xml") +{ + Reset(); +} + +CGUIDialogProgress::~CGUIDialogProgress(void) = default; + +void CGUIDialogProgress::Reset() +{ + std::unique_lock<CCriticalSection> lock(m_section); + m_iCurrent = 0; + m_iMax = 0; + m_percentage = 0; + m_showProgress = true; + m_bCanCancel = true; + m_iChoice = CHOICE_NONE; + m_supportedChoices = {}; + + SetInvalid(); +} + +void CGUIDialogProgress::SetCanCancel(bool bCanCancel) +{ + std::unique_lock<CCriticalSection> lock(m_section); + m_bCanCancel = bCanCancel; + SetInvalid(); +} + +void CGUIDialogProgress::ShowChoice(int iChoice, const CVariant& label) +{ + if (iChoice >= 0 && iChoice < DIALOG_MAX_CHOICES) + { + m_supportedChoices[iChoice] = true; + SetChoice(iChoice, label); + SetInvalid(); + } +} + +int CGUIDialogProgress::GetChoice() const +{ + return m_iChoice; +} + +void CGUIDialogProgress::Open(const std::string ¶m /* = "" */) +{ + CLog::Log(LOGDEBUG, "DialogProgress::Open called {}", m_active ? "(already running)!" : ""); + + { + std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext()); + ShowProgressBar(true); + } + + CGUIDialog::Open(false, param); + + while (m_active && IsAnimating(ANIM_TYPE_WINDOW_OPEN)) + { + Progress(); + // we should have rendered at least once by now - if we haven't, then + // we must be running from fullscreen video or similar where the + // calling thread handles rendering (ie not main app thread) but + // is waiting on this routine before rendering begins + if (!HasProcessed()) + break; + } +} + +void CGUIDialogProgress::Progress() +{ + if (m_active) + { + ProcessRenderLoop(); + } +} + +bool CGUIDialogProgress::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + + case GUI_MSG_WINDOW_DEINIT: + Reset(); + break; + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (iControl >= CONTROL_CHOICES_START && iControl < (CONTROL_CHOICES_START + DIALOG_MAX_CHOICES)) + { + // special handling for choice 0 mapped to cancel button + if (m_bCanCancel && !m_supportedChoices[0] && (iControl == CONTROL_CHOICES_START)) + { + if (m_iChoice != CHOICE_CANCELED) + { + std::string strHeading = m_strHeading; + strHeading.append(" : "); + strHeading.append(g_localizeStrings.Get(16024)); + CGUIDialogBoxBase::SetHeading(CVariant{strHeading}); + m_iChoice = CHOICE_CANCELED; + } + } + else + { + m_iChoice = iControl - CONTROL_CHOICES_START; + } + return true; + } + } + break; + } + return CGUIDialog::OnMessage(message); +} + +bool CGUIDialogProgress::OnBack(int actionID) +{ + if (m_bCanCancel) + m_iChoice = CHOICE_CANCELED; + else + m_iChoice = CHOICE_NONE; + + return true; +} + +void CGUIDialogProgress::OnWindowLoaded() +{ + CGUIDialog::OnWindowLoaded(); + CGUIControl *control = GetControl(CONTROL_PROGRESS_BAR); + if (control && control->GetControlType() == CGUIControl::GUICONTROL_PROGRESS) + { + // make sure we have the appropriate info set + CGUIProgressControl *progress = static_cast<CGUIProgressControl*>(control); + if (!progress->GetInfo()) + progress->SetInfo(SYSTEM_PROGRESS_BAR); + } +} + +void CGUIDialogProgress::SetPercentage(int iPercentage) +{ + if (iPercentage < 0) iPercentage = 0; + if (iPercentage > 100) iPercentage = 100; + + if (iPercentage != m_percentage) + MarkDirtyRegion(); + + m_percentage = iPercentage; +} + +void CGUIDialogProgress::SetProgressMax(int iMax) +{ + m_iMax=iMax; + m_iCurrent=0; +} + +void CGUIDialogProgress::SetProgressAdvance(int nSteps/*=1*/) +{ + m_iCurrent+=nSteps; + + if (m_iCurrent>m_iMax) + m_iCurrent=0; + + if (m_iMax > 0) + SetPercentage((m_iCurrent*100)/m_iMax); +} + +bool CGUIDialogProgress::Abort() +{ + return m_active ? IsCanceled() : false; +} + +void CGUIDialogProgress::ShowProgressBar(bool bOnOff) +{ + std::unique_lock<CCriticalSection> lock(m_section); + m_showProgress = bOnOff; + SetInvalid(); +} + +bool CGUIDialogProgress::Wait(int progresstime /*= 10*/) +{ + CEvent m_done; + while (!m_done.Wait(std::chrono::milliseconds(progresstime)) && m_active && !IsCanceled()) + Progress(); + + return !IsCanceled(); +} + +bool CGUIDialogProgress::WaitOnEvent(CEvent& event) +{ + while (!event.Wait(1ms)) + { + if (IsCanceled()) + return false; + + Progress(); + } + + return !IsCanceled(); +} + +void CGUIDialogProgress::UpdateControls() +{ + // take a copy to save holding the lock for too long + bool bShowProgress; + bool bShowCancel; + std::array<bool, DIALOG_MAX_CHOICES> choices; + { + std::unique_lock<CCriticalSection> lock(m_section); + bShowProgress = m_showProgress; + bShowCancel = m_bCanCancel; + choices = m_supportedChoices; + } + + if (bShowProgress) + SET_CONTROL_VISIBLE(CONTROL_PROGRESS_BAR); + else + SET_CONTROL_HIDDEN(CONTROL_PROGRESS_BAR); + + bool bAllHidden = true; + for (int i = 0; i < DIALOG_MAX_CHOICES; ++i) + { + if (choices[i]) + { + bAllHidden = false; + SET_CONTROL_VISIBLE(CONTROL_CHOICES_START + i); + } + else + SET_CONTROL_HIDDEN(CONTROL_CHOICES_START + i); + } + + // special handling for choice 0 mapped to cancel button + if (bShowCancel && bAllHidden) + SET_CONTROL_VISIBLE(CONTROL_CHOICES_START); +} + +void CGUIDialogProgress::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) +{ + if (m_bInvalidated) + UpdateControls(); + + CGUIDialogBoxBase::Process(currentTime, dirtyregions); +} + +void CGUIDialogProgress::OnInitWindow() +{ + UpdateControls(); + + bool bNoFocus = true; + for (int i = 0; i < DIALOG_MAX_CHOICES; ++i) + { + if (m_supportedChoices[i]) + { + bNoFocus = false; + SET_CONTROL_FOCUS(CONTROL_CHOICES_START + i, 0); + break; + } + } + + // special handling for choice 0 mapped to cancel button + if (m_bCanCancel && bNoFocus) + SET_CONTROL_FOCUS(CONTROL_CHOICES_START,0 ); + + CGUIDialogBoxBase::OnInitWindow(); +} + +int CGUIDialogProgress::GetDefaultLabelID(int controlId) const +{ + // special handling for choice 0 mapped to cancel button + if (m_bCanCancel && !m_supportedChoices[0] && (controlId == CONTROL_CHOICES_START)) + return 222; // Cancel + + return CGUIDialogBoxBase::GetDefaultLabelID(controlId); +} diff --git a/xbmc/dialogs/GUIDialogProgress.h b/xbmc/dialogs/GUIDialogProgress.h new file mode 100644 index 0000000..ba84269 --- /dev/null +++ b/xbmc/dialogs/GUIDialogProgress.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "GUIDialogBoxBase.h" +#include "IProgressCallback.h" + +#include <array> + +class CGUIDialogProgress : + public CGUIDialogBoxBase, public IProgressCallback +{ +public: + CGUIDialogProgress(void); + ~CGUIDialogProgress(void) override; + + void Reset(); + void Open(const std::string ¶m = ""); + bool OnMessage(CGUIMessage& message) override; + bool OnBack(int actionID) override; + void OnWindowLoaded() override; + void Progress(); + bool IsCanceled() const { return m_iChoice == CHOICE_CANCELED; } + void SetPercentage(int iPercentage); + int GetPercentage() const { return m_percentage; } + void ShowProgressBar(bool bOnOff); + + void ShowChoice(int iChoice, const CVariant& label); + + static constexpr int CHOICE_NONE = -2; + static constexpr int CHOICE_CANCELED = -1; + int GetChoice() const; + + /*! \brief Wait for the progress dialog to be closed or canceled, while regularly + rendering to allow for pointer movement or progress to be shown. Used when showing + the progress of a process that is taking place on a separate thread and may be + reporting progress infrequently. + \param progresstime the time in ms to wait between rendering the dialog (defaults to 10ms) + \return true if the dialog is closed, false if the user cancels early. + */ + bool Wait(int progresstime = 10); + + /*! \brief Wait on an event or for the progress dialog to be canceled, while + regularly rendering to allow for pointer movement or progress to be shown. + \param event the CEvent to wait on. + \return true if the event completed, false if cancelled. + */ + bool WaitOnEvent(CEvent& event); + + // Implements IProgressCallback + void SetProgressMax(int iMax) override; + void SetProgressAdvance(int nSteps=1) override; + bool Abort() override; + + void SetCanCancel(bool bCanCancel); + +protected: + void OnInitWindow() override; + int GetDefaultLabelID(int controlId) const override; + void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override; + + bool m_bCanCancel; + + int m_iCurrent; + int m_iMax; + int m_percentage; + bool m_showProgress; + + std::array<bool, DIALOG_MAX_CHOICES> m_supportedChoices = {}; + int m_iChoice = CHOICE_NONE; + +private: + void UpdateControls(); +}; diff --git a/xbmc/dialogs/GUIDialogSeekBar.cpp b/xbmc/dialogs/GUIDialogSeekBar.cpp new file mode 100644 index 0000000..d2f462d --- /dev/null +++ b/xbmc/dialogs/GUIDialogSeekBar.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogSeekBar.h" + +#include "GUIInfoManager.h" +#include "SeekHandler.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIMessage.h" +#include "guilib/guiinfo/GUIInfoLabels.h" + +#include <cmath> + +#define POPUP_SEEK_PROGRESS 401 +#define POPUP_SEEK_EPG_EVENT_PROGRESS 402 +#define POPUP_SEEK_TIMESHIFT_PROGRESS 403 + +CGUIDialogSeekBar::CGUIDialogSeekBar(void) + : CGUIDialog(WINDOW_DIALOG_SEEK_BAR, "DialogSeekBar.xml", DialogModalityType::MODELESS) +{ + m_loadType = LOAD_ON_GUI_INIT; // the application class handles our resources +} + +CGUIDialogSeekBar::~CGUIDialogSeekBar(void) = default; + +bool CGUIDialogSeekBar::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_INIT: + case GUI_MSG_WINDOW_DEINIT: + return CGUIDialog::OnMessage(message); + case GUI_MSG_ITEM_SELECT: + if (message.GetSenderId() == GetID() && + (message.GetControlId() == POPUP_SEEK_PROGRESS || + message.GetControlId() == POPUP_SEEK_EPG_EVENT_PROGRESS || + message.GetControlId() == POPUP_SEEK_TIMESHIFT_PROGRESS)) + return CGUIDialog::OnMessage(message); + break; + case GUI_MSG_REFRESH_TIMER: + return CGUIDialog::OnMessage(message); + } + return false; // don't process anything other than what we need! +} + +void CGUIDialogSeekBar::FrameMove() +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (!appPlayer->HasPlayer()) + { + Close(true); + return; + } + + int progress = GetProgress(); + if (progress != m_lastProgress) + CONTROL_SELECT_ITEM(POPUP_SEEK_PROGRESS, m_lastProgress = progress); + + int epgEventProgress = GetEpgEventProgress(); + if (epgEventProgress != m_lastEpgEventProgress) + CONTROL_SELECT_ITEM(POPUP_SEEK_EPG_EVENT_PROGRESS, m_lastEpgEventProgress = epgEventProgress); + + int timeshiftProgress = GetTimeshiftProgress(); + if (timeshiftProgress != m_lastTimeshiftProgress) + CONTROL_SELECT_ITEM(POPUP_SEEK_TIMESHIFT_PROGRESS, m_lastTimeshiftProgress = timeshiftProgress); + + CGUIDialog::FrameMove(); +} + +int CGUIDialogSeekBar::GetProgress() const +{ + const CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager(); + + int progress = 0; + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->GetSeekHandler().GetSeekSize() != 0) + infoMgr.GetInt(progress, PLAYER_SEEKBAR, INFO::DEFAULT_CONTEXT); + else + infoMgr.GetInt(progress, PLAYER_PROGRESS, INFO::DEFAULT_CONTEXT); + + return progress; +} + +int CGUIDialogSeekBar::GetEpgEventProgress() const +{ + CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager(); + + int progress = 0; + infoMgr.GetInt(progress, PVR_EPG_EVENT_PROGRESS, INFO::DEFAULT_CONTEXT); + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + int seekSize = appPlayer->GetSeekHandler().GetSeekSize(); + if (seekSize != 0) + { + int total = 0; + infoMgr.GetInt(total, PVR_EPG_EVENT_DURATION, INFO::DEFAULT_CONTEXT); + + float totalTime = static_cast<float>(total); + if (totalTime == 0.0f) + return 0; + + float percentPerSecond = 100.0f / totalTime; + float percent = progress + percentPerSecond * seekSize; + percent = std::max(0.0f, std::min(percent, 100.0f)); + return std::lrintf(percent); + } + + return progress; +} + +int CGUIDialogSeekBar::GetTimeshiftProgress() const +{ + const CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager(); + + int progress = 0; + infoMgr.GetInt(progress, PVR_TIMESHIFT_SEEKBAR, INFO::DEFAULT_CONTEXT); + + return progress; +} diff --git a/xbmc/dialogs/GUIDialogSeekBar.h b/xbmc/dialogs/GUIDialogSeekBar.h new file mode 100644 index 0000000..314bf27 --- /dev/null +++ b/xbmc/dialogs/GUIDialogSeekBar.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +class CGUIDialogSeekBar : public CGUIDialog +{ +public: + CGUIDialogSeekBar(void); + ~CGUIDialogSeekBar(void) override; + bool OnMessage(CGUIMessage& message) override; + void FrameMove() override; +private: + int GetProgress() const; + int GetEpgEventProgress() const; + int GetTimeshiftProgress() const; + + int m_lastProgress = 0; + int m_lastEpgEventProgress = 0; + int m_lastTimeshiftProgress = 0; +}; diff --git a/xbmc/dialogs/GUIDialogSelect.cpp b/xbmc/dialogs/GUIDialogSelect.cpp new file mode 100644 index 0000000..0f125b2 --- /dev/null +++ b/xbmc/dialogs/GUIDialogSelect.cpp @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogSelect.h" + +#include "FileItem.h" +#include "guilib/GUIMessage.h" +#include "guilib/LocalizeStrings.h" +#include "input/Key.h" +#include "utils/StringUtils.h" + +#define CONTROL_HEADING 1 +#define CONTROL_NUMBER_OF_ITEMS 2 +#define CONTROL_SIMPLE_LIST 3 +#define CONTROL_DETAILED_LIST 6 +#define CONTROL_EXTRA_BUTTON 5 +#define CONTROL_EXTRA_BUTTON2 8 +#define CONTROL_CANCEL_BUTTON 7 + +CGUIDialogSelect::CGUIDialogSelect() : CGUIDialogSelect(WINDOW_DIALOG_SELECT) {} + +CGUIDialogSelect::CGUIDialogSelect(int windowId) + : CGUIDialogBoxBase(windowId, "DialogSelect.xml"), + m_vecList(std::make_unique<CFileItemList>()), + m_bButtonEnabled(false), + m_bButton2Enabled(false), + m_bButtonPressed(false), + m_bButton2Pressed(false), + m_useDetails(false), + m_multiSelection(false) +{ + m_bConfirmed = false; + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogSelect::~CGUIDialogSelect(void) = default; + +bool CGUIDialogSelect::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_DEINIT: + { + CGUIDialogBoxBase::OnMessage(message); + + m_bButtonEnabled = false; + m_bButton2Enabled = false; + m_useDetails = false; + m_multiSelection = false; + + // construct selected items list + m_selectedItems.clear(); + m_selectedItem = nullptr; + for (int i = 0 ; i < m_vecList->Size() ; i++) + { + CFileItemPtr item = m_vecList->Get(i); + if (item->IsSelected()) + { + m_selectedItems.push_back(i); + if (!m_selectedItem) + m_selectedItem = item; + } + } + m_vecList->Clear(); + return true; + } + break; + + case GUI_MSG_WINDOW_INIT: + { + m_bButtonPressed = false; + m_bButton2Pressed = false; + m_bConfirmed = false; + CGUIDialogBoxBase::OnMessage(message); + return true; + } + break; + + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (m_viewControl.HasControl(CONTROL_SIMPLE_LIST)) + { + int iAction = message.GetParam1(); + if (ACTION_SELECT_ITEM == iAction || ACTION_MOUSE_LEFT_CLICK == iAction) + { + int iSelected = m_viewControl.GetSelectedItem(); + if (iSelected >= 0 && iSelected < m_vecList->Size()) + { + CFileItemPtr item(m_vecList->Get(iSelected)); + if (m_multiSelection) + item->Select(!item->IsSelected()); + else + { + for (int i = 0 ; i < m_vecList->Size() ; i++) + m_vecList->Get(i)->Select(false); + item->Select(true); + OnSelect(iSelected); + } + } + } + } + if (iControl == CONTROL_EXTRA_BUTTON2) + { + m_bButton2Pressed = true; + if (m_multiSelection) + m_bConfirmed = true; + Close(); + } + if (iControl == CONTROL_EXTRA_BUTTON) + { + m_selectedItem = nullptr; + m_bButtonPressed = true; + if (m_multiSelection) + m_bConfirmed = true; + Close(); + } + else if (iControl == CONTROL_CANCEL_BUTTON) + { + m_selectedItem = nullptr; + m_vecList->Clear(); + m_selectedItems.clear(); + m_bConfirmed = false; + Close(); + } + } + break; + case GUI_MSG_SETFOCUS: + { + if (m_viewControl.HasControl(message.GetControlId())) + { + if (m_vecList->IsEmpty()) + { + if (m_bButtonEnabled) + SET_CONTROL_FOCUS(CONTROL_EXTRA_BUTTON, 0); + else + SET_CONTROL_FOCUS(CONTROL_CANCEL_BUTTON, 0); + return true; + } + if (m_viewControl.GetCurrentControl() != message.GetControlId()) + { + m_viewControl.SetFocused(); + return true; + } + } + } + break; + } + + return CGUIDialogBoxBase::OnMessage(message); +} + +void CGUIDialogSelect::OnSelect(int idx) +{ + m_bConfirmed = true; + Close(); +} + +bool CGUIDialogSelect::OnBack(int actionID) +{ + m_selectedItem = nullptr; + m_vecList->Clear(); + m_selectedItems.clear(); + m_bConfirmed = false; + return CGUIDialogBoxBase::OnBack(actionID); +} + +void CGUIDialogSelect::Reset() +{ + m_bButtonEnabled = false; + m_bButtonPressed = false; + m_bButton2Enabled = false; + m_bButton2Pressed = false; + + m_useDetails = false; + m_multiSelection = false; + m_focusToButton = false; + m_selectedItem = nullptr; + m_vecList->Clear(); + m_selectedItems.clear(); +} + +int CGUIDialogSelect::Add(const std::string& strLabel) +{ + CFileItemPtr pItem(new CFileItem(strLabel)); + m_vecList->Add(pItem); + return m_vecList->Size() - 1; +} + +int CGUIDialogSelect::Add(const CFileItem& item) +{ + m_vecList->Add(CFileItemPtr(new CFileItem(item))); + return m_vecList->Size() - 1; +} + +void CGUIDialogSelect::SetItems(const CFileItemList& pList) +{ + // need to make internal copy of list to be sure dialog is owner of it + m_vecList->Clear(); + m_vecList->Copy(pList); + + m_viewControl.SetItems(*m_vecList); +} + +int CGUIDialogSelect::GetSelectedItem() const +{ + return m_selectedItems.size() > 0 ? m_selectedItems[0] : -1; +} + +const CFileItemPtr CGUIDialogSelect::GetSelectedFileItem() const +{ + if (m_selectedItem) + return m_selectedItem; + return CFileItemPtr(new CFileItem); +} + +const std::vector<int>& CGUIDialogSelect::GetSelectedItems() const +{ + return m_selectedItems; +} + +void CGUIDialogSelect::EnableButton(bool enable, int label) +{ + m_bButtonEnabled = enable; + m_buttonLabel = g_localizeStrings.Get(label); +} + +void CGUIDialogSelect::EnableButton(bool enable, const std::string& label) +{ + m_bButtonEnabled = enable; + m_buttonLabel = label; +} + +void CGUIDialogSelect::EnableButton2(bool enable, int label) +{ + m_bButton2Enabled = enable; + m_button2Label = g_localizeStrings.Get(label); +} + +void CGUIDialogSelect::EnableButton2(bool enable, const std::string& label) +{ + m_bButton2Enabled = enable; + m_button2Label = label; +} + +bool CGUIDialogSelect::IsButtonPressed() +{ + return m_bButtonPressed; +} + +bool CGUIDialogSelect::IsButton2Pressed() +{ + return m_bButton2Pressed; +} + +void CGUIDialogSelect::Sort(bool bSortOrder /*=true*/) +{ + m_vecList->Sort(SortByLabel, bSortOrder ? SortOrderAscending : SortOrderDescending); +} + +void CGUIDialogSelect::SetSelected(int iSelected) +{ + if (iSelected < 0 || iSelected >= m_vecList->Size() || + m_vecList->Get(iSelected).get() == NULL) + return; + + // only set m_iSelected if there is no multi-select + // or if it doesn't have a valid value yet + // or if the current value is bigger than the new one + // so that we always focus the item nearest to the beginning of the list + if (!m_multiSelection || !m_selectedItem || + (!m_selectedItems.empty() && m_selectedItems.back() > iSelected)) + m_selectedItem = m_vecList->Get(iSelected); + m_vecList->Get(iSelected)->Select(true); + m_selectedItems.push_back(iSelected); +} + +void CGUIDialogSelect::SetSelected(const std::string &strSelectedLabel) +{ + for (int index = 0; index < m_vecList->Size(); index++) + { + if (strSelectedLabel == m_vecList->Get(index)->GetLabel()) + { + SetSelected(index); + return; + } + } +} + +void CGUIDialogSelect::SetSelected(const std::vector<int>& selectedIndexes) +{ + for (auto i : selectedIndexes) + SetSelected(i); +} + +void CGUIDialogSelect::SetSelected(const std::vector<std::string> &selectedLabels) +{ + for (const auto& label : selectedLabels) + SetSelected(label); +} + +void CGUIDialogSelect::SetUseDetails(bool useDetails) +{ + m_useDetails = useDetails; +} + +void CGUIDialogSelect::SetMultiSelection(bool multiSelection) +{ + m_multiSelection = multiSelection; +} + +void CGUIDialogSelect::SetButtonFocus(bool buttonFocus) +{ + m_focusToButton = buttonFocus; +} + +CGUIControl *CGUIDialogSelect::GetFirstFocusableControl(int id) +{ + if (m_viewControl.HasControl(id)) + id = m_viewControl.GetCurrentControl(); + return CGUIDialogBoxBase::GetFirstFocusableControl(id); +} + +void CGUIDialogSelect::OnWindowLoaded() +{ + CGUIDialogBoxBase::OnWindowLoaded(); + m_viewControl.Reset(); + m_viewControl.SetParentWindow(GetID()); + m_viewControl.AddView(GetControl(CONTROL_SIMPLE_LIST)); + m_viewControl.AddView(GetControl(CONTROL_DETAILED_LIST)); +} + +void CGUIDialogSelect::OnInitWindow() +{ + m_viewControl.SetItems(*m_vecList); + m_selectedItems.clear(); + for(int i = 0 ; i < m_vecList->Size(); i++) + { + auto item = m_vecList->Get(i); + if (item->IsSelected()) + { + m_selectedItems.push_back(i); + if (m_selectedItem == nullptr) + m_selectedItem = item; + } + } + m_viewControl.SetCurrentView(m_useDetails ? CONTROL_DETAILED_LIST : CONTROL_SIMPLE_LIST); + + SET_CONTROL_LABEL(CONTROL_NUMBER_OF_ITEMS, + StringUtils::Format("{} {}", m_vecList->Size(), g_localizeStrings.Get(127))); + + if (m_multiSelection) + EnableButton(true, 186); + + if (m_bButtonEnabled) + { + SET_CONTROL_LABEL(CONTROL_EXTRA_BUTTON, m_buttonLabel); + SET_CONTROL_VISIBLE(CONTROL_EXTRA_BUTTON); + } + else + SET_CONTROL_HIDDEN(CONTROL_EXTRA_BUTTON); + + if (m_bButton2Enabled) + { + SET_CONTROL_LABEL(CONTROL_EXTRA_BUTTON2, m_button2Label); + SET_CONTROL_VISIBLE(CONTROL_EXTRA_BUTTON2); + } + else + SET_CONTROL_HIDDEN(CONTROL_EXTRA_BUTTON2); + + SET_CONTROL_LABEL(CONTROL_CANCEL_BUTTON, g_localizeStrings.Get(222)); + + CGUIDialogBoxBase::OnInitWindow(); + + // focus one of the buttons if explicitly requested + // ATTENTION: this must be done after calling CGUIDialogBoxBase::OnInitWindow() + if (m_focusToButton) + { + if (m_bButtonEnabled) + SET_CONTROL_FOCUS(CONTROL_EXTRA_BUTTON, 0); + else + SET_CONTROL_FOCUS(CONTROL_CANCEL_BUTTON, 0); + } + + // if nothing is selected, select first item + m_viewControl.SetSelectedItem(std::max(GetSelectedItem(), 0)); +} + +void CGUIDialogSelect::OnDeinitWindow(int nextWindowID) +{ + m_viewControl.Clear(); + CGUIDialogBoxBase::OnDeinitWindow(nextWindowID); +} + +void CGUIDialogSelect::OnWindowUnload() +{ + CGUIDialogBoxBase::OnWindowUnload(); + m_viewControl.Reset(); +} diff --git a/xbmc/dialogs/GUIDialogSelect.h b/xbmc/dialogs/GUIDialogSelect.h new file mode 100644 index 0000000..7edb493 --- /dev/null +++ b/xbmc/dialogs/GUIDialogSelect.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "GUIDialogBoxBase.h" +#include "view/GUIViewControl.h" + +#include <string> +#include <vector> + +class CFileItem; +class CFileItemList; + +class CGUIDialogSelect : public CGUIDialogBoxBase +{ +public: + CGUIDialogSelect(); + ~CGUIDialogSelect(void) override; + bool OnMessage(CGUIMessage& message) override; + bool OnBack(int actionID) override; + + void Reset(); + int Add(const std::string& strLabel); + int Add(const CFileItem& item); + void SetItems(const CFileItemList& items); + const CFileItemPtr GetSelectedFileItem() const; + int GetSelectedItem() const; + const std::vector<int>& GetSelectedItems() const; + void EnableButton(bool enable, int label); + void EnableButton(bool enable, const std::string& label); + void EnableButton2(bool enable, int label); + void EnableButton2(bool enable, const std::string& label); + bool IsButtonPressed(); + bool IsButton2Pressed(); + void Sort(bool bSortOrder = true); + void SetSelected(int iSelected); + void SetSelected(const std::string &strSelectedLabel); + void SetSelected(const std::vector<int>& selectedIndexes); + void SetSelected(const std::vector<std::string> &selectedLabels); + void SetUseDetails(bool useDetails); + void SetMultiSelection(bool multiSelection); + void SetButtonFocus(bool buttonFocus); + +protected: + explicit CGUIDialogSelect(int windowid); + CGUIControl *GetFirstFocusableControl(int id) override; + void OnWindowLoaded() override; + void OnInitWindow() override; + void OnDeinitWindow(int nextWindowID) override; + void OnWindowUnload() override; + + virtual void OnSelect(int idx); + + CFileItemPtr m_selectedItem; + std::unique_ptr<CFileItemList> m_vecList; + CGUIViewControl m_viewControl; + +private: + bool m_bButtonEnabled; + bool m_bButton2Enabled; + bool m_bButtonPressed; + bool m_bButton2Pressed; + std::string m_buttonLabel; + std::string m_button2Label; + bool m_useDetails; + bool m_multiSelection; + bool m_focusToButton{}; + + std::vector<int> m_selectedItems; +}; diff --git a/xbmc/dialogs/GUIDialogSimpleMenu.cpp b/xbmc/dialogs/GUIDialogSimpleMenu.cpp new file mode 100644 index 0000000..0d4d11a --- /dev/null +++ b/xbmc/dialogs/GUIDialogSimpleMenu.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + + +#include "GUIDialogSimpleMenu.h" + +#include "FileItem.h" +#include "GUIDialogSelect.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "dialogs/GUIDialogBusy.h" +#include "filesystem/Directory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "settings/DiscSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "threads/IRunnable.h" +#include "utils/FileUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "video/VideoInfoTag.h" + +namespace +{ +class CGetDirectoryItems : public IRunnable +{ +public: + CGetDirectoryItems(const std::string &path, CFileItemList &items, const XFILE::CDirectory::CHints &hints) + : m_path(path), m_items(items), m_hints(hints) + { + } + void Run() override + { + m_result = XFILE::CDirectory::GetDirectory(m_path, m_items, m_hints); + } + bool m_result; +protected: + std::string m_path; + CFileItemList &m_items; + XFILE::CDirectory::CHints m_hints; +}; +} + + +bool CGUIDialogSimpleMenu::ShowPlaySelection(CFileItem& item) +{ + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_DISC_PLAYBACK) != BD_PLAYBACK_SIMPLE_MENU) + return true; + + if (item.IsBDFile()) + { + std::string root = URIUtils::GetParentPath(item.GetDynPath()); + URIUtils::RemoveSlashAtEnd(root); + if (URIUtils::GetFileName(root) == "BDMV") + { + CURL url("bluray://"); + url.SetHostName(URIUtils::GetParentPath(root)); + url.SetFileName("root"); + return ShowPlaySelection(item, url.Get()); + } + } + + if (item.IsDiscImage()) + { + CURL url2("udf://"); + url2.SetHostName(item.GetDynPath()); + url2.SetFileName("BDMV/index.bdmv"); + if (CFileUtils::Exists(url2.Get())) + { + url2.SetFileName(""); + + CURL url("bluray://"); + url.SetHostName(url2.Get()); + url.SetFileName("root"); + return ShowPlaySelection(item, url.Get()); + } + } + return true; +} + +bool CGUIDialogSimpleMenu::ShowPlaySelection(CFileItem& item, const std::string& directory) +{ + + CFileItemList items; + + if (!GetDirectoryItems(directory, items, XFILE::CDirectory::CHints())) + { + CLog::Log(LOGERROR, + "CGUIWindowVideoBase::ShowPlaySelection - Failed to get play directory for {}", + directory); + return true; + } + + if (items.IsEmpty()) + { + CLog::Log(LOGERROR, "CGUIWindowVideoBase::ShowPlaySelection - Failed to get any items {}", + directory); + return true; + } + + CGUIDialogSelect* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); + while (true) + { + dialog->Reset(); + dialog->SetHeading(CVariant{25006}); // Select playback item + dialog->SetItems(items); + dialog->SetUseDetails(true); + dialog->Open(); + + CFileItemPtr item_new = dialog->GetSelectedFileItem(); + if (!item_new || dialog->GetSelectedItem() < 0) + { + CLog::Log(LOGDEBUG, "CGUIWindowVideoBase::ShowPlaySelection - User aborted {}", directory); + break; + } + + if (item_new->m_bIsFolder == false) + { + std::string original_path = item.GetDynPath(); + item.SetDynPath(item_new->GetDynPath()); + item.SetProperty("get_stream_details_from_player", true); + item.SetProperty("original_listitem_url", original_path); + return true; + } + + items.Clear(); + if (!GetDirectoryItems(item_new->GetDynPath(), items, XFILE::CDirectory::CHints()) || items.IsEmpty()) + { + CLog::Log(LOGERROR, "CGUIWindowVideoBase::ShowPlaySelection - Failed to get any items {}", + item_new->GetPath()); + break; + } + } + + return false; +} + +bool CGUIDialogSimpleMenu::GetDirectoryItems(const std::string &path, CFileItemList &items, + const XFILE::CDirectory::CHints &hints) +{ + CGetDirectoryItems getItems(path, items, hints); + if (!CGUIDialogBusy::Wait(&getItems, 100, true)) + { + return false; + } + return getItems.m_result; +} diff --git a/xbmc/dialogs/GUIDialogSimpleMenu.h b/xbmc/dialogs/GUIDialogSimpleMenu.h new file mode 100644 index 0000000..ed476a4 --- /dev/null +++ b/xbmc/dialogs/GUIDialogSimpleMenu.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "filesystem/Directory.h" + +#include <string> + +class CFileItem; +class CFileItemList; + +class CGUIDialogSimpleMenu +{ +public: + + /*! \brief Show dialog allowing selection of wanted playback item */ + static bool ShowPlaySelection(CFileItem& item); + static bool ShowPlaySelection(CFileItem& item, const std::string& directory); + +protected: + static bool GetDirectoryItems(const std::string &path, CFileItemList &items, const XFILE::CDirectory::CHints &hints); +}; diff --git a/xbmc/dialogs/GUIDialogSlider.cpp b/xbmc/dialogs/GUIDialogSlider.cpp new file mode 100644 index 0000000..5d9fb1d --- /dev/null +++ b/xbmc/dialogs/GUIDialogSlider.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogSlider.h" + +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUISliderControl.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/Key.h" + +#define CONTROL_HEADING 10 +#define CONTROL_SLIDER 11 +#define CONTROL_LABEL 12 + +CGUIDialogSlider::CGUIDialogSlider(void) + : CGUIDialog(WINDOW_DIALOG_SLIDER, "DialogSlider.xml") +{ + m_callback = NULL; + m_callbackData = NULL; + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogSlider::~CGUIDialogSlider(void) = default; + +bool CGUIDialogSlider::OnAction(const CAction &action) +{ + if (action.GetID() == ACTION_SELECT_ITEM) + { + Close(); + return true; + } + return CGUIDialog::OnAction(action); +} + +bool CGUIDialogSlider::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_CLICKED: + if (message.GetSenderId() == CONTROL_SLIDER) + { + CGUISliderControl *slider = dynamic_cast<CGUISliderControl *>(GetControl(CONTROL_SLIDER)); + if (slider && m_callback) + { + m_callback->OnSliderChange(m_callbackData, slider); + SET_CONTROL_LABEL(CONTROL_LABEL, slider->GetDescription()); + } + } + break; + case GUI_MSG_WINDOW_DEINIT: + m_callback = NULL; + m_callbackData = NULL; + break; + } + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogSlider::SetSlider(const std::string &label, float value, float min, float delta, float max, ISliderCallback *callback, void *callbackData) +{ + SET_CONTROL_LABEL(CONTROL_HEADING, label); + CGUISliderControl *slider = dynamic_cast<CGUISliderControl *>(GetControl(CONTROL_SLIDER)); + m_callback = callback; + m_callbackData = callbackData; + if (slider) + { + slider->SetType(SLIDER_CONTROL_TYPE_FLOAT); + slider->SetFloatRange(min, max); + slider->SetFloatInterval(delta); + slider->SetFloatValue(value); + if (m_callback) + { + m_callback->OnSliderChange(m_callbackData, slider); + SET_CONTROL_LABEL(CONTROL_LABEL, slider->GetDescription()); + } + } +} + +void CGUIDialogSlider::OnWindowLoaded() +{ + // ensure our callbacks are NULL, incase we were loaded via some non-standard means + m_callback = NULL; + m_callbackData = NULL; + CGUIDialog::OnWindowLoaded(); +} + +void CGUIDialogSlider::SetModalityType(DialogModalityType type) +{ + m_modalityType = type; +} + +void CGUIDialogSlider::ShowAndGetInput(const std::string &label, float value, float min, float delta, float max, ISliderCallback *callback, void *callbackData) +{ + // grab the slider dialog + CGUIDialogSlider *slider = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSlider>(WINDOW_DIALOG_SLIDER); + if (!slider) + return; + + // set the label and value + slider->Initialize(); + slider->SetSlider(label, value, min, delta, max, callback, callbackData); + slider->SetModalityType(DialogModalityType::MODAL); + slider->Open(); +} + +void CGUIDialogSlider::Display(int label, float value, float min, float delta, float max, ISliderCallback *callback) +{ + // grab the slider dialog + CGUIDialogSlider *slider = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSlider>(WINDOW_DIALOG_SLIDER); + if (!slider) + return; + + // set the label and value + slider->Initialize(); + slider->SetAutoClose(1000); + slider->SetSlider(g_localizeStrings.Get(label), value, min, delta, max, callback, NULL); + slider->SetModalityType(DialogModalityType::MODELESS); + slider->Open(); +} diff --git a/xbmc/dialogs/GUIDialogSlider.h b/xbmc/dialogs/GUIDialogSlider.h new file mode 100644 index 0000000..5d09a90 --- /dev/null +++ b/xbmc/dialogs/GUIDialogSlider.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" +#include "guilib/ISliderCallback.h" + +class CGUIDialogSlider : public CGUIDialog +{ +public: + CGUIDialogSlider(); + ~CGUIDialogSlider(void) override; + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction &action) override; + + void SetModalityType(DialogModalityType type); + + /*! \brief Show the slider dialog and wait for the user to change the value + Shows the slider until the user is happy with the adjusted value. Calls back with each change to the callback function + allowing changes to take place immediately. + \param label description of what is being changed by the slider + \param value start value of the slider + \param min minimal value the slider may take + \param delta amount the slider advances for a single click + \param max maximal value the slider may take + \param callback callback class that implements ISliderCallback::OnSliderChange + \param callbackData pointer to callback-specific data (defaults to NULL) + \sa ISliderCallback, Display + */ + static void ShowAndGetInput(const std::string &label, float value, float min, float delta, float max, ISliderCallback *callback, void *callbackData = NULL); + + /*! \brief Show the slider dialog as a response to user input + Shows the slider with the given values for a short period of time, used for UI feedback of a set user action. + This function is asynchronous. + \param label id of the description label for the slider + \param value start value of the slider + \param min minimal value the slider may take + \param delta amount the slider advances for a single click + \param max maximal value the slider may take + \param callback callback class that implements ISliderCallback::OnSliderChange + \sa ISliderCallback, ShowAndGetInput + */ + static void Display(int label, float value, float min, float delta, float max, ISliderCallback *callback); +protected: + void SetSlider(const std::string &label, float value, float min, float delta, float max, ISliderCallback *callback, void *callbackData); + void OnWindowLoaded() override; + + ISliderCallback *m_callback; + void *m_callbackData; +}; + diff --git a/xbmc/dialogs/GUIDialogSmartPlaylistEditor.cpp b/xbmc/dialogs/GUIDialogSmartPlaylistEditor.cpp new file mode 100644 index 0000000..49d6398 --- /dev/null +++ b/xbmc/dialogs/GUIDialogSmartPlaylistEditor.cpp @@ -0,0 +1,648 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogSmartPlaylistEditor.h" + +#include "FileItem.h" +#include "GUIDialogContextMenu.h" +#include "GUIDialogSelect.h" +#include "GUIDialogSmartPlaylistRule.h" +#include "ServiceBroker.h" +#include "Util.h" +#include "filesystem/File.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/Key.h" +#include "profiles/ProfileManager.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/SortUtils.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" + +#include <utility> + +#define CONTROL_HEADING 2 +#define CONTROL_RULE_LIST 10 +#define CONTROL_NAME 12 +#define CONTROL_RULE_ADD 13 +#define CONTROL_RULE_REMOVE 14 +#define CONTROL_RULE_EDIT 15 +#define CONTROL_MATCH 16 +#define CONTROL_LIMIT 17 +#define CONTROL_ORDER_FIELD 18 +#define CONTROL_ORDER_DIRECTION 19 +#define CONTROL_GROUP_BY 23 +#define CONTROL_GROUP_MIXED 24 + +#define CONTROL_OK 20 +#define CONTROL_CANCEL 21 +#define CONTROL_TYPE 22 + +typedef struct +{ + CGUIDialogSmartPlaylistEditor::PLAYLIST_TYPE type; + char string[13]; + int localizedString; +} translateType; + +static const translateType types[] = { { CGUIDialogSmartPlaylistEditor::TYPE_SONGS, "songs", 134 }, + { CGUIDialogSmartPlaylistEditor::TYPE_ALBUMS, "albums", 132 }, + { CGUIDialogSmartPlaylistEditor::TYPE_ARTISTS, "artists", 133 }, + { CGUIDialogSmartPlaylistEditor::TYPE_MIXED, "mixed", 20395 }, + { CGUIDialogSmartPlaylistEditor::TYPE_MUSICVIDEOS, "musicvideos", 20389 }, + { CGUIDialogSmartPlaylistEditor::TYPE_MOVIES, "movies", 20342 }, + { CGUIDialogSmartPlaylistEditor::TYPE_TVSHOWS, "tvshows", 20343 }, + { CGUIDialogSmartPlaylistEditor::TYPE_EPISODES, "episodes", 20360 } + }; + +CGUIDialogSmartPlaylistEditor::CGUIDialogSmartPlaylistEditor(void) + : CGUIDialog(WINDOW_DIALOG_SMART_PLAYLIST_EDITOR, "SmartPlaylistEditor.xml") +{ + m_cancelled = false; + m_ruleLabels = new CFileItemList; + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogSmartPlaylistEditor::~CGUIDialogSmartPlaylistEditor() +{ + delete m_ruleLabels; +} + +bool CGUIDialogSmartPlaylistEditor::OnBack(int actionID) +{ + m_cancelled = true; + return CGUIDialog::OnBack(actionID); +} + +bool CGUIDialogSmartPlaylistEditor::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + int iAction = message.GetParam1(); + if (iControl == CONTROL_RULE_LIST && (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK)) + OnRuleList(GetSelectedItem()); + else if (iControl == CONTROL_RULE_ADD) + OnRuleAdd(); + else if (iControl == CONTROL_RULE_EDIT) + OnRuleList(GetSelectedItem()); + else if (iControl == CONTROL_RULE_REMOVE) + OnRuleRemove(GetSelectedItem()); + else if (iControl == CONTROL_NAME) + OnName(); + else if (iControl == CONTROL_OK) + OnOK(); + else if (iControl == CONTROL_CANCEL) + OnCancel(); + else if (iControl == CONTROL_MATCH) + OnMatch(); + else if (iControl == CONTROL_LIMIT) + OnLimit(); + else if (iControl == CONTROL_ORDER_FIELD) + OnOrder(); + else if (iControl == CONTROL_ORDER_DIRECTION) + OnOrderDirection(); + else if (iControl == CONTROL_TYPE) + OnType(); + else if (iControl == CONTROL_GROUP_BY) + OnGroupBy(); + else if (iControl == CONTROL_GROUP_MIXED) + OnGroupMixed(); + else if (iControl == CONTROL_RULE_LIST && (iAction == ACTION_CONTEXT_MENU || iAction == ACTION_MOUSE_RIGHT_CLICK)) + OnPopupMenu(GetSelectedItem()); + else + return CGUIDialog::OnMessage(message); + return true; + } + break; + case GUI_MSG_FOCUSED: + if (message.GetControlId() == CONTROL_RULE_REMOVE || + message.GetControlId() == CONTROL_RULE_EDIT) + HighlightItem(GetSelectedItem()); + else + { + if (message.GetControlId() == CONTROL_RULE_LIST) + UpdateRuleControlButtons(); + + HighlightItem(-1); + } + break; + case GUI_MSG_WINDOW_INIT: + { + const std::string& startupList = message.GetStringParam(0); + if (!startupList.empty()) + { + int party = 0; + if (URIUtils::PathEquals(startupList, CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetUserDataItem("PartyMode.xsp"))) + party = 1; + else if (URIUtils::PathEquals(startupList, CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetUserDataItem("PartyMode-Video.xsp"))) + party = 2; + + if ((party && !XFILE::CFile::Exists(startupList)) || + m_playlist.Load(startupList)) + { + m_path = startupList; + + if (party == 1) + m_mode = "partymusic"; + else if (party == 2) + m_mode = "partyvideo"; + else + { + PLAYLIST_TYPE type = ConvertType(m_playlist.GetType()); + if (type == TYPE_SONGS || type == TYPE_ALBUMS || type == TYPE_ARTISTS) + m_mode = "music"; + else + m_mode = "video"; + } + } + else + return false; + } + } + break; + case GUI_MSG_WINDOW_DEINIT: + { + m_playlist.Reset(); + } + break; + } + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogSmartPlaylistEditor::OnPopupMenu(int item) +{ + if (item < 0 || static_cast<size_t>(item) >= m_playlist.m_ruleCombination.m_rules.size()) + return; + // highlight the item + m_ruleLabels->Get(item)->Select(true); + + CContextButtons choices; + choices.Add(1, 15015); + + int button = CGUIDialogContextMenu::ShowAndGetChoice(choices); + + // unhighlight the item + m_ruleLabels->Get(item)->Select(false); + + if (button == 1) + OnRuleRemove(item); +} + +void CGUIDialogSmartPlaylistEditor::OnRuleList(int item) +{ + if (item < 0 || item > static_cast<int>(m_playlist.m_ruleCombination.m_rules.size())) + return; + if (item == static_cast<int>(m_playlist.m_ruleCombination.m_rules.size())) + OnRuleAdd(); + else + { + CSmartPlaylistRule rule = *std::static_pointer_cast<CSmartPlaylistRule>(m_playlist.m_ruleCombination.m_rules[item]); + if (CGUIDialogSmartPlaylistRule::EditRule(rule, m_playlist.GetType())) + *m_playlist.m_ruleCombination.m_rules[item] = rule; + } + UpdateButtons(); +} + +void CGUIDialogSmartPlaylistEditor::OnOK() +{ + std::string systemPlaylistsPath = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH); + // save our playlist + if (m_path.empty()) + { + std::string filename(CUtil::MakeLegalFileName(m_playlist.m_playlistName)); + std::string path; + if (CGUIKeyboardFactory::ShowAndGetInput(filename, CVariant{g_localizeStrings.Get(16013)}, false)) + { + path = URIUtils::AddFileToFolder(systemPlaylistsPath, m_playlist.GetSaveLocation(), + CUtil::MakeLegalFileName(filename)); + } + else + return; + if (!URIUtils::HasExtension(path, ".xsp")) + path += ".xsp"; + + // should we check whether we should overwrite? + m_path = path; + } + else + { + // check if we need to actually change the save location for this playlist + // this occurs if the user switches from music video <> songs <> mixed + if (StringUtils::StartsWith(m_path, systemPlaylistsPath)) + { + std::string filename = URIUtils::GetFileName(m_path); + std::string strFolder = m_path.substr(systemPlaylistsPath.size(), m_path.size() - filename.size() - systemPlaylistsPath.size() - 1); + if (strFolder != m_playlist.GetSaveLocation()) + { // move to the correct folder + XFILE::CFile::Delete(m_path); + m_path = URIUtils::AddFileToFolder(systemPlaylistsPath, m_playlist.GetSaveLocation(), filename); + } + } + } + + m_playlist.Save(m_path); + + m_cancelled = false; + Close(); +} + +void CGUIDialogSmartPlaylistEditor::OnCancel() +{ + m_cancelled = true; + Close(); +} + +void CGUIDialogSmartPlaylistEditor::OnMatch() +{ + // toggle between AND and OR setting + if (m_playlist.m_ruleCombination.GetType() == CSmartPlaylistRuleCombination::CombinationOr) + m_playlist.m_ruleCombination.SetType(CSmartPlaylistRuleCombination::CombinationAnd); + else + m_playlist.m_ruleCombination.SetType(CSmartPlaylistRuleCombination::CombinationOr); + UpdateButtons(); +} + +void CGUIDialogSmartPlaylistEditor::OnName() +{ + std::string name = m_playlist.m_playlistName; + if (CGUIKeyboardFactory::ShowAndGetInput(name, CVariant{16012}, false)) + { + m_playlist.m_playlistName = name; + UpdateButtons(); + } +} + +void CGUIDialogSmartPlaylistEditor::OnLimit() +{ + std::vector<int> limits = {0, 10, 25, 50, 100, 250, 500, 1000}; + CGUIDialogSelect* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); + dialog->Reset(); + int selected = -1; + for (auto limit = limits.begin(); limit != limits.end(); limit++) + { + if (*limit == static_cast<int>(m_playlist.m_limit)) + selected = std::distance(limits.begin(), limit); + if (*limit == 0) + dialog->Add(g_localizeStrings.Get(21428)); + else + dialog->Add(StringUtils::Format(g_localizeStrings.Get(21436), *limit)); + } + dialog->SetHeading(CVariant{ 21427 }); + dialog->SetSelected(selected); + dialog->Open(); + int newSelected = dialog->GetSelectedItem(); + if (!dialog->IsConfirmed() || newSelected < 0 || limits[newSelected] == static_cast<int>(m_playlist.m_limit)) + return; + m_playlist.m_limit = limits[newSelected]; + UpdateButtons(); +} + +void CGUIDialogSmartPlaylistEditor::OnType() +{ + std::vector<PLAYLIST_TYPE> allowedTypes = GetAllowedTypes(m_mode); + CGUIDialogSelect* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); + dialog->Reset(); + for (auto allowedType: allowedTypes) + dialog->Add(GetLocalizedType(allowedType)); + dialog->SetHeading(CVariant{ 564 }); + dialog->SetSelected(GetLocalizedType(ConvertType(m_playlist.GetType()))); + dialog->Open(); + int newSelected = dialog->GetSelectedItem(); + if (!dialog->IsConfirmed() || newSelected < 0 || allowedTypes[newSelected] == ConvertType(m_playlist.GetType())) + return; + + m_playlist.SetType(ConvertType(allowedTypes[newSelected])); + + // Remove any invalid grouping left over when changing the type + Field currentGroup = CSmartPlaylistRule::TranslateGroup(m_playlist.GetGroup().c_str()); + if (currentGroup != FieldNone && currentGroup != FieldUnknown) + { + std::vector<Field> groups = CSmartPlaylistRule::GetGroups(m_playlist.GetType()); + if (std::find(groups.begin(), groups.end(), currentGroup) == groups.end()) + m_playlist.SetGroup(CSmartPlaylistRule::TranslateGroup(FieldUnknown)); + } + + UpdateButtons(); +} + +void CGUIDialogSmartPlaylistEditor::OnOrder() +{ + std::vector<SortBy> orders = CSmartPlaylistRule::GetOrders(m_playlist.GetType()); + CGUIDialogSelect* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); + dialog->Reset(); + for (auto order: orders) + dialog->Add(g_localizeStrings.Get(SortUtils::GetSortLabel(order))); + dialog->SetHeading(CVariant{ 21429 }); + dialog->SetSelected(g_localizeStrings.Get(SortUtils::GetSortLabel(m_playlist.m_orderField))); + dialog->Open(); + int newSelected = dialog->GetSelectedItem(); + if (!dialog->IsConfirmed() || newSelected < 0 || orders[newSelected] == m_playlist.m_orderField) + return; + m_playlist.m_orderField = orders[newSelected]; + UpdateButtons(); +} + +void CGUIDialogSmartPlaylistEditor::OnOrderDirection() +{ + if (m_playlist.m_orderDirection == SortOrderDescending) + m_playlist.m_orderDirection = SortOrderAscending; + else + m_playlist.m_orderDirection = SortOrderDescending; + UpdateButtons(); +} + +void CGUIDialogSmartPlaylistEditor::OnGroupBy() +{ + std::vector<Field> groups = CSmartPlaylistRule::GetGroups(m_playlist.GetType()); + Field currentGroup = CSmartPlaylistRule::TranslateGroup(m_playlist.GetGroup().c_str()); + CGUIDialogSelect* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); + dialog->Reset(); + for (auto group : groups) + dialog->Add(CSmartPlaylistRule::GetLocalizedGroup(group)); + dialog->SetHeading(CVariant{ 21458 }); + dialog->SetSelected(CSmartPlaylistRule::GetLocalizedGroup(currentGroup)); + dialog->Open(); + int newSelected = dialog->GetSelectedItem(); + // check if selection has changed + if (!dialog->IsConfirmed() || newSelected < 0 || groups[newSelected] == currentGroup) + return; + m_playlist.SetGroup(CSmartPlaylistRule::TranslateGroup(groups[newSelected])); + + if (m_playlist.IsGroupMixed() && !CSmartPlaylistRule::CanGroupMix(currentGroup)) + m_playlist.SetGroupMixed(false); + + UpdateButtons(); +} + +void CGUIDialogSmartPlaylistEditor::OnGroupMixed() +{ + m_playlist.SetGroupMixed(!m_playlist.IsGroupMixed()); + UpdateButtons(); +} + +void CGUIDialogSmartPlaylistEditor::UpdateButtons() +{ + CONTROL_ENABLE(CONTROL_OK); // always enabled since we can have no rules -> match everything (as we do with default partymode playlists) + + if (m_mode == "partyvideo" || m_mode == "partymusic") + { + SET_CONTROL_LABEL2(CONTROL_NAME, g_localizeStrings.Get(16035)); + CONTROL_DISABLE(CONTROL_NAME); + } + else + SET_CONTROL_LABEL2(CONTROL_NAME, m_playlist.m_playlistName); + + UpdateRuleControlButtons(); + + if (m_playlist.m_ruleCombination.GetType() == CSmartPlaylistRuleCombination::CombinationOr) + SET_CONTROL_LABEL2(CONTROL_MATCH, g_localizeStrings.Get(21426)); // one or more of the rules + else + SET_CONTROL_LABEL2(CONTROL_MATCH, g_localizeStrings.Get(21425)); // all of the rules + CONTROL_ENABLE_ON_CONDITION(CONTROL_MATCH, m_playlist.m_ruleCombination.m_rules.size() > 1); + if (m_playlist.m_limit == 0) + SET_CONTROL_LABEL2(CONTROL_LIMIT, g_localizeStrings.Get(21428)); // no limit + else + SET_CONTROL_LABEL2(CONTROL_LIMIT, + StringUtils::Format(g_localizeStrings.Get(21436), m_playlist.m_limit)); + int currentItem = GetSelectedItem(); + CGUIMessage msgReset(GUI_MSG_LABEL_RESET, GetID(), CONTROL_RULE_LIST); + OnMessage(msgReset); + m_ruleLabels->Clear(); + for (const auto& rule: m_playlist.m_ruleCombination.m_rules) + { + CFileItemPtr item(new CFileItem("", false)); + item->SetLabel(std::static_pointer_cast<CSmartPlaylistRule>(rule)->GetLocalizedRule()); + m_ruleLabels->Add(item); + } + CFileItemPtr item(new CFileItem("", false)); + item->SetLabel(g_localizeStrings.Get(21423)); + m_ruleLabels->Add(item); + CGUIMessage msg(GUI_MSG_LABEL_BIND, GetID(), CONTROL_RULE_LIST, 0, 0, m_ruleLabels); + OnMessage(msg); + SendMessage(GUI_MSG_ITEM_SELECT, GetID(), CONTROL_RULE_LIST, currentItem); + + if (m_playlist.m_orderDirection != SortOrderDescending) + { + SET_CONTROL_LABEL2(CONTROL_ORDER_DIRECTION, g_localizeStrings.Get(21430)); + } + else + { + SET_CONTROL_LABEL2(CONTROL_ORDER_DIRECTION, g_localizeStrings.Get(21431)); + } + + SET_CONTROL_LABEL2(CONTROL_ORDER_FIELD, g_localizeStrings.Get(SortUtils::GetSortLabel(m_playlist.m_orderField))); + SET_CONTROL_LABEL2(CONTROL_TYPE, GetLocalizedType(ConvertType(m_playlist.GetType()))); + + // setup groups + std::vector<Field> groups = CSmartPlaylistRule::GetGroups(m_playlist.GetType()); + Field currentGroup = CSmartPlaylistRule::TranslateGroup(m_playlist.GetGroup().c_str()); + SET_CONTROL_LABEL2(CONTROL_GROUP_BY, CSmartPlaylistRule::GetLocalizedGroup(currentGroup)); + if (m_playlist.IsGroupMixed()) + CONTROL_SELECT(CONTROL_GROUP_MIXED); + else + CONTROL_DESELECT(CONTROL_GROUP_MIXED); + + // disable the group controls if there's no group + // or only one group which can't be mixed + if (groups.empty() || + (groups.size() == 1 && !CSmartPlaylistRule::CanGroupMix(groups[0]))) + { + CONTROL_DISABLE(CONTROL_GROUP_BY); + CONTROL_DISABLE(CONTROL_GROUP_MIXED); + } + else + { + CONTROL_ENABLE(CONTROL_GROUP_BY); + CONTROL_ENABLE_ON_CONDITION(CONTROL_GROUP_MIXED, CSmartPlaylistRule::CanGroupMix(currentGroup)); + } +} + +void CGUIDialogSmartPlaylistEditor::UpdateRuleControlButtons() +{ + int iSize = m_playlist.m_ruleCombination.m_rules.size(); + int iItem = GetSelectedItem(); + // only enable the remove control if ... + CONTROL_ENABLE_ON_CONDITION(CONTROL_RULE_REMOVE, + iSize > 0 && // there is at least one item + iItem >= 0 && iItem < iSize && // and a valid item is selected + m_playlist.m_ruleCombination.m_rules[iItem]->m_field != FieldNone); // and it is not be empty +} + +void CGUIDialogSmartPlaylistEditor::OnInitWindow() +{ + m_cancelled = false; + + std::vector<PLAYLIST_TYPE> allowedTypes = GetAllowedTypes(m_mode); + // check if our playlist type is allowed + PLAYLIST_TYPE type = ConvertType(m_playlist.GetType()); + bool allowed = false; + for (auto allowedType: allowedTypes) + { + if (type == allowedType) + allowed = true; + } + if (!allowed && allowedTypes.size()) + m_playlist.SetType(ConvertType(allowedTypes[0])); + + UpdateButtons(); + + SET_CONTROL_LABEL(CONTROL_HEADING, 21432); + + CGUIDialog::OnInitWindow(); +} + +void CGUIDialogSmartPlaylistEditor::OnDeinitWindow(int nextWindowID) +{ + CGUIDialog::OnDeinitWindow(nextWindowID); + SendMessage(GUI_MSG_LABEL_RESET, CONTROL_RULE_LIST); + SendMessage(GUI_MSG_LABEL_RESET, CONTROL_TYPE); + m_ruleLabels->Clear(); +} + +CGUIDialogSmartPlaylistEditor::PLAYLIST_TYPE CGUIDialogSmartPlaylistEditor::ConvertType(const std::string &type) +{ + for (const translateType& t : types) + if (type == t.string) + return t.type; + assert(false); + return TYPE_SONGS; +} + +std::string CGUIDialogSmartPlaylistEditor::GetLocalizedType(PLAYLIST_TYPE type) +{ + for (const translateType& t : types) + if (t.type == type) + return g_localizeStrings.Get(t.localizedString); + assert(false); + return ""; +} + +std::string CGUIDialogSmartPlaylistEditor::ConvertType(PLAYLIST_TYPE type) +{ + for (const translateType& t : types) + if (t.type == type) + return t.string; + assert(false); + return "songs"; +} + +int CGUIDialogSmartPlaylistEditor::GetSelectedItem() +{ + CGUIMessage message(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_RULE_LIST); + OnMessage(message); + return message.GetParam1(); +} + +void CGUIDialogSmartPlaylistEditor::HighlightItem(int item) +{ + for (int i = 0; i < m_ruleLabels->Size(); i++) + (*m_ruleLabels)[i]->Select(false); + if (item >= 0 && item < m_ruleLabels->Size()) + (*m_ruleLabels)[item]->Select(true); + CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), CONTROL_RULE_LIST, item); + OnMessage(msg); +} + +std::vector<CGUIDialogSmartPlaylistEditor::PLAYLIST_TYPE> CGUIDialogSmartPlaylistEditor::GetAllowedTypes(const std::string& mode) +{ + std::vector<PLAYLIST_TYPE> allowedTypes; + if (mode == "partymusic") + { + allowedTypes.push_back(TYPE_SONGS); + allowedTypes.push_back(TYPE_MIXED); + } + else if (mode == "partyvideo") + { + allowedTypes.push_back(TYPE_MUSICVIDEOS); + allowedTypes.push_back(TYPE_MIXED); + } + else if (mode == "music") + { // music types + mixed + allowedTypes.push_back(TYPE_SONGS); + allowedTypes.push_back(TYPE_ALBUMS); + allowedTypes.push_back(TYPE_ARTISTS); + allowedTypes.push_back(TYPE_MIXED); + } + else if (mode == "video") + { // general category for videos + allowedTypes.push_back(TYPE_MOVIES); + allowedTypes.push_back(TYPE_TVSHOWS); + allowedTypes.push_back(TYPE_EPISODES); + allowedTypes.push_back(TYPE_MUSICVIDEOS); + allowedTypes.push_back(TYPE_MIXED); + } + return allowedTypes; +} + +void CGUIDialogSmartPlaylistEditor::OnRuleRemove(int item) +{ + if (item < 0 || item >= (int)m_playlist.m_ruleCombination.m_rules.size()) return; + m_playlist.m_ruleCombination.m_rules.erase(m_playlist.m_ruleCombination.m_rules.begin() + item); + + UpdateButtons(); + if (item >= m_ruleLabels->Size()) + HighlightItem(m_ruleLabels->Size() - 1); + else + HighlightItem(item); +} + +void CGUIDialogSmartPlaylistEditor::OnRuleAdd() +{ + CSmartPlaylistRule rule; + if (CGUIDialogSmartPlaylistRule::EditRule(rule,m_playlist.GetType())) + m_playlist.m_ruleCombination.AddRule(rule); + UpdateButtons(); +} + +bool CGUIDialogSmartPlaylistEditor::NewPlaylist(const std::string &type) +{ + CGUIDialogSmartPlaylistEditor *editor = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSmartPlaylistEditor>(WINDOW_DIALOG_SMART_PLAYLIST_EDITOR); + if (!editor) return false; + + editor->m_path = ""; + editor->m_playlist = CSmartPlaylist(); + editor->m_mode = type; + editor->Initialize(); + editor->Open(); + return !editor->m_cancelled; +} + +bool CGUIDialogSmartPlaylistEditor::EditPlaylist(const std::string &path, const std::string &type) +{ + CGUIDialogSmartPlaylistEditor *editor = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSmartPlaylistEditor>(WINDOW_DIALOG_SMART_PLAYLIST_EDITOR); + if (!editor) return false; + + editor->m_mode = type; + if (URIUtils::PathEquals(path, CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetUserDataItem("PartyMode.xsp"))) + editor->m_mode = "partymusic"; + if (URIUtils::PathEquals(path, CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetUserDataItem("PartyMode-Video.xsp"))) + editor->m_mode = "partyvideo"; + + CSmartPlaylist playlist; + bool loaded(playlist.Load(path)); + if (!loaded) + { // failed to load + if (!StringUtils::StartsWithNoCase(editor->m_mode, "party")) + return false; // only edit normal playlists that exist + // party mode playlists can be edited even if they don't exist + playlist.SetType(editor->m_mode == "partymusic" ? "songs" : "musicvideos"); + } + + editor->m_playlist = playlist; + editor->m_path = path; + editor->Initialize(); + editor->Open(); + return !editor->m_cancelled; +} diff --git a/xbmc/dialogs/GUIDialogSmartPlaylistEditor.h b/xbmc/dialogs/GUIDialogSmartPlaylistEditor.h new file mode 100644 index 0000000..c754f91 --- /dev/null +++ b/xbmc/dialogs/GUIDialogSmartPlaylistEditor.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" +#include "playlists/SmartPlayList.h" + +class CFileItemList; + +class CGUIDialogSmartPlaylistEditor : + public CGUIDialog +{ +public: + enum PLAYLIST_TYPE { TYPE_SONGS = 1, TYPE_ALBUMS, TYPE_ARTISTS, TYPE_MIXED, TYPE_MUSICVIDEOS, TYPE_MOVIES, TYPE_TVSHOWS, TYPE_EPISODES }; + + CGUIDialogSmartPlaylistEditor(void); + ~CGUIDialogSmartPlaylistEditor(void) override; + bool OnMessage(CGUIMessage& message) override; + bool OnBack(int actionID) override; + void OnInitWindow() override; + void OnDeinitWindow(int nextWindowID) override; + + static bool EditPlaylist(const std::string &path, const std::string &type = ""); + static bool NewPlaylist(const std::string &type); + +protected: + void OnRuleList(int item); + void OnRuleAdd(); + void OnRuleRemove(int item); + void OnMatch(); + void OnLimit(); + void OnName(); + void OnType(); + void OnOrder(); + void OnOrderDirection(); + void OnGroupBy(); + void OnGroupMixed(); + void OnOK(); + void OnCancel(); + void OnPopupMenu(int item); + void UpdateButtons(); + void UpdateRuleControlButtons(); + int GetSelectedItem(); + void HighlightItem(int item); + std::vector<PLAYLIST_TYPE> GetAllowedTypes(const std::string& mode); + PLAYLIST_TYPE ConvertType(const std::string &type); + std::string ConvertType(PLAYLIST_TYPE type); + std::string GetLocalizedType(PLAYLIST_TYPE type); + + CSmartPlaylist m_playlist; + + // our list of rules for display purposes + CFileItemList* m_ruleLabels; + + std::string m_path; + bool m_cancelled; + std::string m_mode; // mode we're in (partymode etc.) +}; diff --git a/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp b/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp new file mode 100644 index 0000000..671219b --- /dev/null +++ b/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp @@ -0,0 +1,575 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogSmartPlaylistRule.h" + +#include "FileItem.h" +#include "GUIDialogFileBrowser.h" +#include "GUIDialogSelect.h" +#include "ServiceBroker.h" +#include "filesystem/Directory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIEditControl.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "music/MusicDatabase.h" +#include "settings/MediaSourceSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "storage/MediaManager.h" +#include "utils/LabelFormatter.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "video/VideoDatabase.h" + +#include <utility> + +#define CONTROL_FIELD 15 +#define CONTROL_OPERATOR 16 +#define CONTROL_VALUE 17 +#define CONTROL_OK 18 +#define CONTROL_CANCEL 19 +#define CONTROL_BROWSE 20 + +CGUIDialogSmartPlaylistRule::CGUIDialogSmartPlaylistRule(void) + : CGUIDialog(WINDOW_DIALOG_SMART_PLAYLIST_RULE, "SmartPlaylistRule.xml") +{ + m_cancelled = false; + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogSmartPlaylistRule::~CGUIDialogSmartPlaylistRule() = default; + +bool CGUIDialogSmartPlaylistRule::OnBack(int actionID) +{ + m_cancelled = true; + return CGUIDialog::OnBack(actionID); +} + +bool CGUIDialogSmartPlaylistRule::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (iControl == CONTROL_OK) + OnOK(); + else if (iControl == CONTROL_CANCEL) + OnCancel(); + else if (iControl == CONTROL_VALUE) + { + std::string parameter; + OnEditChanged(iControl, parameter); + m_rule.SetParameter(parameter); + } + else if (iControl == CONTROL_OPERATOR) + OnOperator(); + else if (iControl == CONTROL_FIELD) + OnField(); + else if (iControl == CONTROL_BROWSE) + OnBrowse(); + return true; + } + break; + + case GUI_MSG_VALIDITY_CHANGED: + CONTROL_ENABLE_ON_CONDITION(CONTROL_OK, message.GetParam1()); + break; + } + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogSmartPlaylistRule::OnOK() +{ + m_cancelled = false; + Close(); +} + +void CGUIDialogSmartPlaylistRule::OnBrowse() +{ + CFileItemList items; + CMusicDatabase database; + database.Open(); + CVideoDatabase videodatabase; + videodatabase.Open(); + + std::string basePath; + if (CSmartPlaylist::IsMusicType(m_type)) + basePath = "musicdb://"; + else + basePath = "videodb://"; + + VideoDbContentType type = VideoDbContentType::MOVIES; + if (m_type == "movies") + basePath += "movies/"; + else if (m_type == "tvshows") + { + type = VideoDbContentType::TVSHOWS; + basePath += "tvshows/"; + } + else if (m_type == "musicvideos") + { + type = VideoDbContentType::MUSICVIDEOS; + basePath += "musicvideos/"; + } + else if (m_type == "episodes") + { + if (m_rule.m_field == FieldGenre || m_rule.m_field == FieldYear || + m_rule.m_field == FieldStudio) + type = VideoDbContentType::TVSHOWS; + else + type = VideoDbContentType::EPISODES; + basePath += "tvshows/"; + } + + int iLabel = 0; + if (m_rule.m_field == FieldGenre) + { + if (m_type == "tvshows" || + m_type == "episodes" || + m_type == "movies") + videodatabase.GetGenresNav(basePath + "genres/", items, type); + else if (m_type == "songs" || + m_type == "albums" || + m_type == "artists" || + m_type == "mixed") + database.GetGenresNav("musicdb://genres/",items); + if (m_type == "musicvideos" || + m_type == "mixed") + { + CFileItemList items2; + videodatabase.GetGenresNav("videodb://musicvideos/genres/", items2, + VideoDbContentType::MUSICVIDEOS); + items.Append(items2); + } + iLabel = 515; + } + else if (m_rule.m_field == FieldSource) + { + if (m_type == "songs" || + m_type == "albums" || + m_type == "artists" || + m_type == "mixed") + { + database.GetSourcesNav("musicdb://sources/", items); + iLabel = 39030; + } + } + else if (m_rule.m_field == FieldRole) + { + if (m_type == "artists" || m_type == "mixed") + { + database.GetRolesNav("musicdb://songs/", items); + iLabel = 38033; + } + } + else if (m_rule.m_field == FieldCountry) + { + videodatabase.GetCountriesNav(basePath, items, type); + iLabel = 574; + } + else if (m_rule.m_field == FieldArtist || m_rule.m_field == FieldAlbumArtist) + { + if (CSmartPlaylist::IsMusicType(m_type)) + database.GetArtistsNav("musicdb://artists/", items, m_rule.m_field == FieldAlbumArtist, -1); + if (m_type == "musicvideos" || + m_type == "mixed") + { + CFileItemList items2; + videodatabase.GetMusicVideoArtistsByName("", items2); + items.Append(items2); + } + iLabel = 557; + } + else if (m_rule.m_field == FieldAlbum) + { + if (CSmartPlaylist::IsMusicType(m_type)) + database.GetAlbumsNav("musicdb://albums/", items); + if (m_type == "musicvideos" || + m_type == "mixed") + { + CFileItemList items2; + videodatabase.GetMusicVideoAlbumsByName("", items2); + items.Append(items2); + } + iLabel = 558; + } + else if (m_rule.m_field == FieldActor) + { + videodatabase.GetActorsNav(basePath + "actors/",items,type); + iLabel = 20337; + } + else if (m_rule.m_field == FieldYear) + { + if (CSmartPlaylist::IsMusicType(m_type)) + database.GetYearsNav("musicdb://years/", items); + if (CSmartPlaylist::IsVideoType(m_type)) + { + CFileItemList items2; + videodatabase.GetYearsNav(basePath + "years/", items2, type); + items.Append(items2); + } + iLabel = 562; + } + else if (m_rule.m_field == FieldOrigYear) + { + database.GetYearsNav("musicdb://originalyears/", items); + iLabel = 38078; + } + else if (m_rule.m_field == FieldDirector) + { + videodatabase.GetDirectorsNav(basePath + "directors/", items, type); + iLabel = 20339; + } + else if (m_rule.m_field == FieldStudio) + { + videodatabase.GetStudiosNav(basePath + "studios/", items, type); + iLabel = 572; + } + else if (m_rule.m_field == FieldWriter) + { + videodatabase.GetWritersNav(basePath, items, type); + iLabel = 20417; + } + else if (m_rule.m_field == FieldTvShowTitle || + (m_type == "tvshows" && m_rule.m_field == FieldTitle)) + { + videodatabase.GetTvShowsNav(basePath + "titles/", items); + iLabel = 20343; + } + else if (m_rule.m_field == FieldTitle) + { + if (m_type == "songs" || m_type == "mixed") + { + database.GetSongsNav("musicdb://songs/", items, -1, -1, -1); + iLabel = 134; + } + if (m_type == "movies") + { + videodatabase.GetMoviesNav(basePath + "titles/", items); + iLabel = 20342; + } + if (m_type == "episodes") + { + videodatabase.GetEpisodesNav(basePath + "titles/-1/-1/", items); + // we need to replace the db label (<season>x<episode> <title>) with the title only + CLabelFormatter format("%T", ""); + for (int i = 0; i < items.Size(); i++) + format.FormatLabel(items[i].get()); + iLabel = 20360; + } + if (m_type == "musicvideos" || m_type == "mixed") + { + videodatabase.GetMusicVideosNav(basePath + "titles/", items); + iLabel = 20389; + } + } + else if (m_rule.m_field == FieldPlaylist || m_rule.m_field == FieldVirtualFolder) + { + // use filebrowser to grab another smart playlist + + // Note: This can cause infinite loops (playlist that refers to the same playlist) but I don't + // think there's any decent way to deal with this, as the infinite loop may be an arbitrary + // number of playlists deep, eg playlist1 -> playlist2 -> playlist3 ... -> playlistn -> playlist1 + if (CSmartPlaylist::IsVideoType(m_type)) + XFILE::CDirectory::GetDirectory("special://videoplaylists/", items, ".xsp", XFILE::DIR_FLAG_NO_FILE_DIRS); + if (CSmartPlaylist::IsMusicType(m_type)) + { + CFileItemList items2; + XFILE::CDirectory::GetDirectory("special://musicplaylists/", items2, ".xsp", XFILE::DIR_FLAG_NO_FILE_DIRS); + items.Append(items2); + } + + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + CSmartPlaylist playlist; + // don't list unloadable smartplaylists or any referenceable smartplaylists + // which do not match the type of the current smartplaylist + if (!playlist.Load(item->GetPath()) || + (m_rule.m_field == FieldPlaylist && + (!CSmartPlaylist::CheckTypeCompatibility(m_type, playlist.GetType()) || + (!playlist.GetGroup().empty() || playlist.IsGroupMixed())))) + { + items.Remove(i); + i -= 1; + continue; + } + + if (!playlist.GetName().empty()) + item->SetLabel(playlist.GetName()); + } + iLabel = 559; + } + else if (m_rule.m_field == FieldPath) + { + VECSOURCES sources; + if (m_type == "songs" || m_type == "mixed") + sources = *CMediaSourceSettings::GetInstance().GetSources("music"); + if (CSmartPlaylist::IsVideoType(m_type)) + { + VECSOURCES sources2 = *CMediaSourceSettings::GetInstance().GetSources("video"); + sources.insert(sources.end(),sources2.begin(),sources2.end()); + } + CServiceBroker::GetMediaManager().GetLocalDrives(sources); + + std::string path = m_rule.GetParameter(); + CGUIDialogFileBrowser::ShowAndGetDirectory(sources, g_localizeStrings.Get(657), path, false); + if (!m_rule.m_parameter.empty()) + m_rule.m_parameter.clear(); + if (!path.empty()) + m_rule.m_parameter.emplace_back(std::move(path)); + + UpdateButtons(); + return; + } + else if (m_rule.m_field == FieldSet) + { + videodatabase.GetSetsNav("videodb://movies/sets/", items, VideoDbContentType::MOVIES); + iLabel = 20434; + } + else if (m_rule.m_field == FieldTag) + { + VideoDbContentType type = VideoDbContentType::MOVIES; + if (m_type == "tvshows" || + m_type == "episodes") + type = VideoDbContentType::TVSHOWS; + else if (m_type == "musicvideos") + type = VideoDbContentType::MUSICVIDEOS; + else if (m_type != "movies") + return; + + videodatabase.GetTagsNav(basePath + "tags/", items, type); + iLabel = 20459; + } + else + { //! @todo Add browseability in here. + assert(false); + } + + // sort the items + items.Sort(SortByLabel, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone); + + CGUIDialogSelect* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); + pDialog->Reset(); + pDialog->SetItems(items); + std::string strHeading = + StringUtils::Format(g_localizeStrings.Get(13401), g_localizeStrings.Get(iLabel)); + pDialog->SetHeading(CVariant{std::move(strHeading)}); + pDialog->SetMultiSelection(m_rule.m_field != FieldPlaylist && m_rule.m_field != FieldVirtualFolder); + + if (!m_rule.m_parameter.empty()) + pDialog->SetSelected(m_rule.m_parameter); + + pDialog->Open(); + if (pDialog->IsConfirmed()) + { + m_rule.m_parameter.clear(); + for (int i : pDialog->GetSelectedItems()) + m_rule.m_parameter.push_back(items.Get(i)->GetLabel()); + + UpdateButtons(); + } + pDialog->Reset(); +} + +std::pair<std::string, int> OperatorLabel(CDatabaseQueryRule::SEARCH_OPERATOR op) +{ + return std::make_pair(CSmartPlaylistRule::GetLocalizedOperator(op), op); +} + +std::vector<std::pair<std::string, int>> CGUIDialogSmartPlaylistRule::GetValidOperators(const CSmartPlaylistRule& rule) +{ + std::vector< std::pair<std::string, int> > labels; + switch (rule.GetFieldType(rule.m_field)) + { + case CDatabaseQueryRule::TEXT_FIELD: + // text fields - add the usual comparisons + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_EQUALS)); + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_DOES_NOT_EQUAL)); + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_CONTAINS)); + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_DOES_NOT_CONTAIN)); + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_STARTS_WITH)); + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_ENDS_WITH)); + break; + + case CDatabaseQueryRule::REAL_FIELD: + case CDatabaseQueryRule::NUMERIC_FIELD: + case CDatabaseQueryRule::SECONDS_FIELD: + // numerical fields - less than greater than + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_EQUALS)); + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_DOES_NOT_EQUAL)); + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_GREATER_THAN)); + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_LESS_THAN)); + break; + + case CDatabaseQueryRule::DATE_FIELD: + // date field + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_AFTER)); + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_BEFORE)); + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_IN_THE_LAST)); + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_NOT_IN_THE_LAST)); + break; + + case CDatabaseQueryRule::PLAYLIST_FIELD: + CONTROL_ENABLE(CONTROL_BROWSE); + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_EQUALS)); + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_DOES_NOT_EQUAL)); + break; + + case CDatabaseQueryRule::BOOLEAN_FIELD: + CONTROL_DISABLE(CONTROL_VALUE); + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_TRUE)); + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_FALSE)); + break; + + case CDatabaseQueryRule::TEXTIN_FIELD: + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_EQUALS)); + labels.push_back(OperatorLabel(CDatabaseQueryRule::OPERATOR_DOES_NOT_EQUAL)); + break; + } + return labels; +} + +void CGUIDialogSmartPlaylistRule::OnCancel() +{ + m_cancelled = true; + Close(); +} + +void CGUIDialogSmartPlaylistRule::OnField() +{ + const auto fields = CSmartPlaylistRule::GetFields(m_type); + CGUIDialogSelect* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); + dialog->Reset(); + dialog->SetHeading(CVariant{20427}); + int selected = -1; + for (auto field = fields.begin(); field != fields.end(); field++) + { + dialog->Add(CSmartPlaylistRule::GetLocalizedField(*field)); + if (*field == m_rule.m_field) + selected = std::distance(fields.begin(), field); + } + if (selected > -1) + dialog->SetSelected(selected); + dialog->Open(); + int newSelected = dialog->GetSelectedItem(); + // check if selection has changed + if (!dialog->IsConfirmed() || newSelected < 0 || newSelected == selected) + return; + + m_rule.m_field = fields[newSelected]; + // check if operator is still valid. if not, reset to first valid one + std::vector< std::pair<std::string, int> > validOperators = GetValidOperators(m_rule); + bool isValid = false; + for (auto op : validOperators) + if (std::get<0>(op) == std::get<0>(OperatorLabel(m_rule.m_operator))) + isValid = true; + if (!isValid) + m_rule.m_operator = (CDatabaseQueryRule::SEARCH_OPERATOR)std::get<1>(validOperators[0]); + + m_rule.SetParameter(""); + UpdateButtons(); +} + +void CGUIDialogSmartPlaylistRule::OnOperator() +{ + const auto labels = GetValidOperators(m_rule); + CGUIDialogSelect* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); + dialog->Reset(); + dialog->SetHeading(CVariant{ 16023 }); + for (auto label : labels) + dialog->Add(std::get<0>(label)); + dialog->SetSelected(CSmartPlaylistRule::GetLocalizedOperator(m_rule.m_operator)); + dialog->Open(); + int newSelected = dialog->GetSelectedItem(); + // check if selection has changed + if (!dialog->IsConfirmed() || newSelected < 0) + return; + + m_rule.m_operator = (CDatabaseQueryRule::SEARCH_OPERATOR)std::get<1>(labels[newSelected]); + UpdateButtons(); +} + +void CGUIDialogSmartPlaylistRule::UpdateButtons() +{ + if (m_rule.m_field == 0) + m_rule.m_field = CSmartPlaylistRule::GetFields(m_type)[0]; + SET_CONTROL_LABEL(CONTROL_FIELD, CSmartPlaylistRule::GetLocalizedField(m_rule.m_field)); + + CONTROL_ENABLE(CONTROL_VALUE); + if (CSmartPlaylistRule::IsFieldBrowseable(m_rule.m_field)) + CONTROL_ENABLE(CONTROL_BROWSE); + else + CONTROL_DISABLE(CONTROL_BROWSE); + SET_CONTROL_LABEL(CONTROL_OPERATOR, std::get<0>(OperatorLabel(m_rule.m_operator))); + + // update label2 appropriately + SET_CONTROL_LABEL2(CONTROL_VALUE, m_rule.GetParameter()); + CGUIEditControl::INPUT_TYPE type = CGUIEditControl::INPUT_TYPE_TEXT; + CDatabaseQueryRule::FIELD_TYPE fieldType = m_rule.GetFieldType(m_rule.m_field); + switch (fieldType) + { + case CDatabaseQueryRule::TEXT_FIELD: + case CDatabaseQueryRule::PLAYLIST_FIELD: + case CDatabaseQueryRule::TEXTIN_FIELD: + case CDatabaseQueryRule::REAL_FIELD: + case CDatabaseQueryRule::NUMERIC_FIELD: + type = CGUIEditControl::INPUT_TYPE_TEXT; + break; + case CDatabaseQueryRule::DATE_FIELD: + if (m_rule.m_operator == CDatabaseQueryRule::OPERATOR_IN_THE_LAST || + m_rule.m_operator == CDatabaseQueryRule::OPERATOR_NOT_IN_THE_LAST) + type = CGUIEditControl::INPUT_TYPE_TEXT; + else + type = CGUIEditControl::INPUT_TYPE_DATE; + break; + case CDatabaseQueryRule::SECONDS_FIELD: + type = CGUIEditControl::INPUT_TYPE_SECONDS; + break; + case CDatabaseQueryRule::BOOLEAN_FIELD: + type = CGUIEditControl::INPUT_TYPE_NUMBER; + break; + } + SendMessage(GUI_MSG_SET_TYPE, CONTROL_VALUE, type, 21420); +} + +void CGUIDialogSmartPlaylistRule::OnInitWindow() +{ + CGUIDialog::OnInitWindow(); + + UpdateButtons(); + + CGUIEditControl *editControl = dynamic_cast<CGUIEditControl*>(GetControl(CONTROL_VALUE)); + if (editControl != NULL) + editControl->SetInputValidation(CSmartPlaylistRule::Validate, &m_rule); +} + +void CGUIDialogSmartPlaylistRule::OnDeinitWindow(int nextWindowID) +{ + CGUIDialog::OnDeinitWindow(nextWindowID); + + // reset field spincontrolex + SendMessage(GUI_MSG_LABEL_RESET, CONTROL_FIELD); + // reset operator spincontrolex + SendMessage(GUI_MSG_LABEL_RESET, CONTROL_OPERATOR); +} + +bool CGUIDialogSmartPlaylistRule::EditRule(CSmartPlaylistRule &rule, const std::string& type) +{ + CGUIDialogSmartPlaylistRule *editor = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSmartPlaylistRule>(WINDOW_DIALOG_SMART_PLAYLIST_RULE); + if (!editor) return false; + + editor->m_rule = rule; + editor->m_type = type; + editor->Open(); + rule = editor->m_rule; + return !editor->m_cancelled; +} + diff --git a/xbmc/dialogs/GUIDialogSmartPlaylistRule.h b/xbmc/dialogs/GUIDialogSmartPlaylistRule.h new file mode 100644 index 0000000..23ca11d --- /dev/null +++ b/xbmc/dialogs/GUIDialogSmartPlaylistRule.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" +#include "playlists/SmartPlayList.h" + +class CGUIDialogSmartPlaylistRule : + public CGUIDialog +{ +public: + CGUIDialogSmartPlaylistRule(void); + ~CGUIDialogSmartPlaylistRule(void) override; + bool OnMessage(CGUIMessage& message) override; + bool OnBack(int actionID) override; + void OnInitWindow() override; + void OnDeinitWindow(int nextWindowID) override; + + static bool EditRule(CSmartPlaylistRule &rule, const std::string& type="songs"); + +protected: + void OnField(); + void OnOperator(); + void OnOK(); + void OnCancel(); + void UpdateButtons(); + void OnBrowse(); + std::vector< std::pair<std::string, int> > GetValidOperators(const CSmartPlaylistRule& rule); + CSmartPlaylistRule m_rule; + bool m_cancelled; + std::string m_type; +}; diff --git a/xbmc/dialogs/GUIDialogSubMenu.cpp b/xbmc/dialogs/GUIDialogSubMenu.cpp new file mode 100644 index 0000000..ab1cdb2 --- /dev/null +++ b/xbmc/dialogs/GUIDialogSubMenu.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogSubMenu.h" + +#include "guilib/GUIMessage.h" + +CGUIDialogSubMenu::CGUIDialogSubMenu(int id, const std::string &xmlFile) + : CGUIDialog(id, xmlFile.c_str()) +{ +} + +CGUIDialogSubMenu::~CGUIDialogSubMenu(void) = default; + +bool CGUIDialogSubMenu::OnMessage(CGUIMessage &message) +{ + if (message.GetMessage() == GUI_MSG_CLICKED) + { + // someone has been clicked - deinit... + CGUIDialog::OnMessage(message); + Close(); + return true; + } + return CGUIDialog::OnMessage(message); +} diff --git a/xbmc/dialogs/GUIDialogSubMenu.h b/xbmc/dialogs/GUIDialogSubMenu.h new file mode 100644 index 0000000..eaf77d2 --- /dev/null +++ b/xbmc/dialogs/GUIDialogSubMenu.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +class CGUIDialogSubMenu : + public CGUIDialog +{ +public: + CGUIDialogSubMenu(int id = WINDOW_DIALOG_SUB_MENU, const std::string &xmlFile = "DialogSubMenu.xml"); + ~CGUIDialogSubMenu(void) override; + bool OnMessage(CGUIMessage &message) override; +}; diff --git a/xbmc/dialogs/GUIDialogTextViewer.cpp b/xbmc/dialogs/GUIDialogTextViewer.cpp new file mode 100644 index 0000000..363f102 --- /dev/null +++ b/xbmc/dialogs/GUIDialogTextViewer.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogTextViewer.h" + +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "filesystem/File.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "utils/URIUtils.h" +#include "utils/log.h" + +using namespace XFILE; + +#define CONTROL_HEADING 1 +#define CONTROL_TEXTAREA 5 + +CGUIDialogTextViewer::CGUIDialogTextViewer(void) + : CGUIDialog(WINDOW_DIALOG_TEXT_VIEWER, "DialogTextViewer.xml") +{ + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogTextViewer::~CGUIDialogTextViewer(void) = default; + +bool CGUIDialogTextViewer::OnAction(const CAction &action) +{ + if (action.GetID() == ACTION_TOGGLE_FONT) + { + UseMonoFont(!m_mono); + return true; + } + + return CGUIDialog::OnAction(action); +} + +bool CGUIDialogTextViewer::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_WINDOW_INIT: + { + CGUIDialog::OnMessage(message); + SetHeading(); + SetText(); + UseMonoFont(m_mono); + return true; + } + break; + case GUI_MSG_NOTIFY_ALL: + { + if (message.GetParam1() == GUI_MSG_UPDATE) + { + SetText(); + SetHeading(); + return true; + } + } + break; + default: + break; + } + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogTextViewer::SetText() +{ + CGUIMessage msg(GUI_MSG_LABEL_SET, GetID(), CONTROL_TEXTAREA); + msg.SetLabel(m_strText); + OnMessage(msg); +} + +void CGUIDialogTextViewer::SetHeading() +{ + CGUIMessage msg(GUI_MSG_LABEL_SET, GetID(), CONTROL_HEADING); + msg.SetLabel(m_strHeading); + OnMessage(msg); +} + +void CGUIDialogTextViewer::UseMonoFont(bool use) +{ + m_mono = use; + CGUIMessage msg(GUI_MSG_SET_TYPE, GetID(), CONTROL_TEXTAREA, use ? 1 : 0); + OnMessage(msg); +} + +void CGUIDialogTextViewer::OnDeinitWindow(int nextWindowID) +{ + CGUIDialog::OnDeinitWindow(nextWindowID); + + // reset text area + CGUIMessage msgReset(GUI_MSG_LABEL_RESET, GetID(), CONTROL_TEXTAREA); + OnMessage(msgReset); + + // reset heading + SET_CONTROL_LABEL(CONTROL_HEADING, ""); +} + +void CGUIDialogTextViewer::ShowForFile(const std::string& path, bool useMonoFont) +{ + CFile file; + if (file.Open(path)) + { + std::string data; + try + { + data.resize(file.GetLength()+1); + file.Read(&data[0], file.GetLength()); + CGUIDialogTextViewer* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogTextViewer>(WINDOW_DIALOG_TEXT_VIEWER); + pDialog->SetHeading(URIUtils::GetFileName(path)); + pDialog->SetText(data); + pDialog->UseMonoFont(useMonoFont); + pDialog->Open(); + } + catch(const std::bad_alloc&) + { + CLog::Log(LOGERROR, "Not enough memory to load text file {}", path); + } + catch(...) + { + CLog::Log(LOGERROR, "Exception while trying to view text file {}", path); + } + } +} diff --git a/xbmc/dialogs/GUIDialogTextViewer.h b/xbmc/dialogs/GUIDialogTextViewer.h new file mode 100644 index 0000000..1e7bacd --- /dev/null +++ b/xbmc/dialogs/GUIDialogTextViewer.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +class CGUIDialogTextViewer : + public CGUIDialog +{ +public: + CGUIDialogTextViewer(void); + ~CGUIDialogTextViewer(void) override; + bool OnMessage(CGUIMessage& message) override; + void SetText(const std::string& strText) { m_strText = strText; } + void SetHeading(const std::string& strHeading) { m_strHeading = strHeading; } + void UseMonoFont(bool use); + + //! \brief Load a file into memory and show in dialog. + //! \param path Path to file + //! \param useMonoFont True to use monospace font + static void ShowForFile(const std::string& path, bool useMonoFont); +protected: + void OnDeinitWindow(int nextWindowID) override; + bool OnAction(const CAction &action) override; + + std::string m_strText; + std::string m_strHeading; + bool m_mono = false; + + void SetText(); + void SetHeading(); +}; + diff --git a/xbmc/dialogs/GUIDialogVolumeBar.cpp b/xbmc/dialogs/GUIDialogVolumeBar.cpp new file mode 100644 index 0000000..a4d903f --- /dev/null +++ b/xbmc/dialogs/GUIDialogVolumeBar.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogVolumeBar.h" + +#include "IGUIVolumeBarCallback.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationVolumeHandling.h" +#include "guilib/GUIMessage.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" + +#include <mutex> + +#define VOLUME_BAR_DISPLAY_TIME 1000L + +CGUIDialogVolumeBar::CGUIDialogVolumeBar(void) + : CGUIDialog(WINDOW_DIALOG_VOLUME_BAR, "DialogVolumeBar.xml", DialogModalityType::MODELESS) +{ + m_loadType = LOAD_ON_GUI_INIT; +} + +CGUIDialogVolumeBar::~CGUIDialogVolumeBar(void) = default; + +bool CGUIDialogVolumeBar::OnAction(const CAction &action) +{ + if (action.GetID() == ACTION_VOLUME_UP || action.GetID() == ACTION_VOLUME_DOWN || action.GetID() == ACTION_VOLUME_SET || action.GetID() == ACTION_MUTE) + { + const auto& components = CServiceBroker::GetAppComponents(); + const auto appVolume = components.GetComponent<CApplicationVolumeHandling>(); + if (appVolume->IsMuted() || + appVolume->GetVolumeRatio() <= CApplicationVolumeHandling::VOLUME_MINIMUM) + { // cancel the timer, dialog needs to stay visible + CancelAutoClose(); + } + else + { // reset the timer, as we've changed the volume level + SetAutoClose(VOLUME_BAR_DISPLAY_TIME); + } + MarkDirtyRegion(); + return true; + } + return CGUIDialog::OnAction(action); +} + +bool CGUIDialogVolumeBar::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_INIT: + case GUI_MSG_WINDOW_DEINIT: + return CGUIDialog::OnMessage(message); + } + return false; // don't process anything other than what we need! +} + +void CGUIDialogVolumeBar::RegisterCallback(IGUIVolumeBarCallback *callback) +{ + std::unique_lock<CCriticalSection> lock(m_callbackMutex); + + m_callbacks.insert(callback); +} + +void CGUIDialogVolumeBar::UnregisterCallback(IGUIVolumeBarCallback *callback) +{ + std::unique_lock<CCriticalSection> lock(m_callbackMutex); + + m_callbacks.erase(callback); +} + +bool CGUIDialogVolumeBar::IsVolumeBarEnabled() const +{ + std::unique_lock<CCriticalSection> lock(m_callbackMutex); + + // Hide volume bar if any callbacks are shown + for (const auto &callback : m_callbacks) + { + if (callback->IsShown()) + return false; + } + + return true; +} diff --git a/xbmc/dialogs/GUIDialogVolumeBar.h b/xbmc/dialogs/GUIDialogVolumeBar.h new file mode 100644 index 0000000..e8413d0 --- /dev/null +++ b/xbmc/dialogs/GUIDialogVolumeBar.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" +#include "threads/CriticalSection.h" + +#include <set> + +class IGUIVolumeBarCallback; + +class CGUIDialogVolumeBar : public CGUIDialog +{ +public: + CGUIDialogVolumeBar(void); + ~CGUIDialogVolumeBar(void) override; + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction &action) override; + + // Volume bar interface + void RegisterCallback(IGUIVolumeBarCallback *callback); + void UnregisterCallback(IGUIVolumeBarCallback *callback); + bool IsVolumeBarEnabled() const; + +private: + std::set<IGUIVolumeBarCallback*> m_callbacks; + mutable CCriticalSection m_callbackMutex; +}; diff --git a/xbmc/dialogs/GUIDialogYesNo.cpp b/xbmc/dialogs/GUIDialogYesNo.cpp new file mode 100644 index 0000000..56709c3 --- /dev/null +++ b/xbmc/dialogs/GUIDialogYesNo.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogYesNo.h" + +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "input/Key.h" +#include "messaging/helpers/DialogHelper.h" + +CGUIDialogYesNo::CGUIDialogYesNo(int overrideId /* = -1 */) + : CGUIDialogBoxBase(overrideId == -1 ? WINDOW_DIALOG_YES_NO : overrideId, "DialogConfirm.xml") +{ + Reset(); +} + +CGUIDialogYesNo::~CGUIDialogYesNo() = default; + +bool CGUIDialogYesNo::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + int iAction = message.GetParam1(); + if (true || ACTION_SELECT_ITEM == iAction) + { + if (iControl == CONTROL_NO_BUTTON) + { + m_bConfirmed = false; + Close(); + return true; + } + if (iControl == CONTROL_YES_BUTTON) + { + m_bConfirmed = true; + Close(); + return true; + } + if (iControl == CONTROL_CUSTOM_BUTTON) + { + m_bConfirmed = false; + m_bCustom = true; + Close(); + return true; + } + } + } + break; + } + return CGUIDialogBoxBase::OnMessage(message); +} + +bool CGUIDialogYesNo::OnBack(int actionID) +{ + m_bCanceled = true; + m_bConfirmed = false; + m_bCustom = false; + return CGUIDialogBoxBase::OnBack(actionID); +} + +void CGUIDialogYesNo::OnInitWindow() +{ + if (!m_strChoices[2].empty()) + SET_CONTROL_VISIBLE(CONTROL_CUSTOM_BUTTON); + else + SET_CONTROL_HIDDEN(CONTROL_CUSTOM_BUTTON); + SET_CONTROL_HIDDEN(CONTROL_PROGRESS_BAR); + SET_CONTROL_FOCUS(m_defaultButtonId, 0); + + CGUIDialogBoxBase::OnInitWindow(); +} + +bool CGUIDialogYesNo::ShowAndGetInput(const CVariant& heading, + const CVariant& line0, + const CVariant& line1, + const CVariant& line2, + bool& bCanceled) +{ + return ShowAndGetInput(heading, line0, line1, line2, bCanceled, "", "", NO_TIMEOUT); +} + +bool CGUIDialogYesNo::ShowAndGetInput(const CVariant& heading, + const CVariant& line0, + const CVariant& line1, + const CVariant& line2, + const CVariant& noLabel /* = "" */, + const CVariant& yesLabel /* = "" */) +{ + bool bDummy(false); + return ShowAndGetInput(heading, line0, line1, line2, bDummy, noLabel, yesLabel, NO_TIMEOUT); +} + +bool CGUIDialogYesNo::ShowAndGetInput(const CVariant& heading, + const CVariant& line0, + const CVariant& line1, + const CVariant& line2, + bool& bCanceled, + const CVariant& noLabel, + const CVariant& yesLabel, + unsigned int autoCloseTime) +{ + CGUIDialogYesNo *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogYesNo>(WINDOW_DIALOG_YES_NO); + if (!dialog) + return false; + + dialog->SetHeading(heading); + dialog->SetLine(0, line0); + dialog->SetLine(1, line1); + dialog->SetLine(2, line2); + if (autoCloseTime) + dialog->SetAutoClose(autoCloseTime); + dialog->SetChoice(0, !noLabel.empty() ? noLabel : 106); + dialog->SetChoice(1, !yesLabel.empty() ? yesLabel : 107); + dialog->SetChoice(2, ""); + dialog->m_bCanceled = false; + dialog->m_defaultButtonId = CONTROL_NO_BUTTON; + dialog->Open(); + + bCanceled = dialog->m_bCanceled; + return (dialog->IsConfirmed()) ? true : false; +} + +bool CGUIDialogYesNo::ShowAndGetInput(const CVariant& heading, const CVariant& text) +{ + bool bDummy(false); + return ShowAndGetInput(heading, text, "", "", bDummy); +} + +bool CGUIDialogYesNo::ShowAndGetInput(const CVariant& heading, + const CVariant& text, + bool& bCanceled, + const CVariant& noLabel /* = "" */, + const CVariant& yesLabel /* = "" */, + unsigned int autoCloseTime, + int defaultButtonId /* = CONTROL_NO_BUTTON */) +{ + int result = + ShowAndGetInput(heading, text, noLabel, yesLabel, "", autoCloseTime, defaultButtonId); + + bCanceled = result == -1; + return result == 1; +} + +void CGUIDialogYesNo::Reset() +{ + m_bConfirmed = false; + m_bCanceled = false; + m_bCustom = false; + m_bAutoClosed = false; + m_defaultButtonId = CONTROL_NO_BUTTON; +} + +int CGUIDialogYesNo::GetResult() const +{ + if (m_bCanceled) + return -1; + else if (m_bCustom) + return 2; + else if (IsConfirmed()) + return 1; + else + return 0; +} + +int CGUIDialogYesNo::ShowAndGetInput(const CVariant& heading, + const CVariant& text, + const CVariant& noLabel, + const CVariant& yesLabel, + const CVariant& customLabel, + unsigned int autoCloseTime, + int defaultButtonId /* = CONTROL_NO_BUTTON */) +{ + CGUIDialogYesNo *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogYesNo>(WINDOW_DIALOG_YES_NO); + if (!dialog) + return false; + + dialog->SetHeading(heading); + dialog->SetText(text); + if (autoCloseTime > 0) + dialog->SetAutoClose(autoCloseTime); + dialog->m_bCanceled = false; + dialog->m_bCustom = false; + dialog->m_defaultButtonId = defaultButtonId; + dialog->SetChoice(0, !noLabel.empty() ? noLabel : 106); + dialog->SetChoice(1, !yesLabel.empty() ? yesLabel : 107); + dialog->SetChoice(2, customLabel); // Button only visible when label is not empty + dialog->Open(); + + return dialog->GetResult(); +} + +int CGUIDialogYesNo::ShowAndGetInput(const KODI::MESSAGING::HELPERS::DialogYesNoMessage& options) +{ + //Set default yes/no labels, these might be overwritten further down if specified + //by the caller + SetChoice(0, 106); + SetChoice(1, 107); + SetChoice(2, ""); + if (!options.heading.isNull()) + SetHeading(options.heading); + if (!options.text.isNull()) + SetText(options.text); + if (!options.noLabel.isNull()) + SetChoice(0, options.noLabel); + if (!options.yesLabel.isNull()) + SetChoice(1, options.yesLabel); + if (!options.customLabel.isNull()) + SetChoice(2, options.customLabel); + if (options.autoclose > 0) + SetAutoClose(options.autoclose); + m_bCanceled = false; + m_bCustom = false; + m_defaultButtonId = CONTROL_NO_BUTTON; + + for (size_t i = 0; i < 3; ++i) + { + if (!options.lines[i].isNull()) + SetLine(i, options.lines[i]); + } + + Open(); + + return GetResult(); +} + +int CGUIDialogYesNo::GetDefaultLabelID(int controlId) const +{ + if (controlId == CONTROL_NO_BUTTON) + return 106; + else if (controlId == CONTROL_YES_BUTTON) + return 107; + else if (controlId == CONTROL_CUSTOM_BUTTON) + return -1; + return CGUIDialogBoxBase::GetDefaultLabelID(controlId); +} diff --git a/xbmc/dialogs/GUIDialogYesNo.h b/xbmc/dialogs/GUIDialogYesNo.h new file mode 100644 index 0000000..e8baac2 --- /dev/null +++ b/xbmc/dialogs/GUIDialogYesNo.h @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "GUIDialogBoxBase.h" +#include "utils/Variant.h" + +namespace KODI +{ + namespace MESSAGING + { + namespace HELPERS + { + struct DialogYesNoMessage; + } + } +} + +class CGUIDialogYesNo : + public CGUIDialogBoxBase +{ +public: + explicit CGUIDialogYesNo(int overrideId = -1); + ~CGUIDialogYesNo(void) override; + bool OnMessage(CGUIMessage& message) override; + bool OnBack(int actionID) override; + + void Reset(); + int GetResult() const; + + enum TimeOut + { + NO_TIMEOUT = 0 + }; + + /*! \brief Show a yes-no dialog, then wait for user to dismiss it. + \param heading Localized label id or string for the heading of the dialog + \param line0 Localized label id or string for line 1 of the dialog message + \param line1 Localized label id or string for line 2 of the dialog message + \param line2 Localized label id or string for line 3 of the dialog message + \param bCanceled Holds true if the dialog was canceled otherwise false + \return true if user selects Yes, otherwise false if user selects No. + */ + static bool ShowAndGetInput(const CVariant& heading, + const CVariant& line0, + const CVariant& line1, + const CVariant& line2, + bool& bCanceled); + + /*! \brief Show a yes-no dialog, then wait for user to dismiss it. + \param heading Localized label id or string for the heading of the dialog + \param line0 Localized label id or string for line 1 of the dialog message + \param line1 Localized label id or string for line 2 of the dialog message + \param line2 Localized label id or string for line 3 of the dialog message + \param iNoLabel Localized label id or string for the no button + \param iYesLabel Localized label id or string for the yes button + \return true if user selects Yes, otherwise false if user selects No. + */ + static bool ShowAndGetInput(const CVariant& heading, + const CVariant& line0, + const CVariant& line1, + const CVariant& line2, + const CVariant& noLabel = "", + const CVariant& yesLabel = ""); + + /*! \brief Show a yes-no dialog, then wait for user to dismiss it. + \param heading Localized label id or string for the heading of the dialog + \param line0 Localized label id or string for line 1 of the dialog message + \param line1 Localized label id or string for line 2 of the dialog message + \param line2 Localized label id or string for line 3 of the dialog message + \param bCanceled Holds true if the dialog was canceled otherwise false + \param iNoLabel Localized label id or string for the no button + \param iYesLabel Localized label id or string for the yes button + \param autoCloseTime Time in ms before the dialog becomes automatically closed + \return true if user selects Yes, otherwise false if user selects No. + */ + static bool ShowAndGetInput(const CVariant& heading, + const CVariant& line0, + const CVariant& line1, + const CVariant& line2, + bool& bCanceled, + const CVariant& noLabel, + const CVariant& yesLabel, + unsigned int autoCloseTime); + + /*! \brief Show a yes-no dialog, then wait for user to dismiss it. + \param heading Localized label id or string for the heading of the dialog + \param text Localized label id or string for the dialog message + \return true if user selects Yes, otherwise false if user selects No. + */ + static bool ShowAndGetInput(const CVariant& heading, const CVariant& text); + + /*! \brief Show a yes-no dialog, then wait for user to dismiss it. + \param heading Localized label id or string for the heading of the dialog + \param text Localized label id or string for the dialog message + \param bCanceled Holds true if the dialog was canceled otherwise false + \param iNoLabel Localized label id or string for the no button + \param iYesLabel Localized label id or string for the yes button + \param autoCloseTime Time in ms before the dialog becomes automatically closed + \param defaultButtonId Specifies the default focused button + \return true if user selects Yes, otherwise false if user selects No. + */ + static bool ShowAndGetInput(const CVariant& heading, + const CVariant& text, + bool& bCanceled, + const CVariant& noLabel, + const CVariant& yesLabel, + unsigned int autoCloseTime, + int defaultButtonId = CONTROL_NO_BUTTON); + + /*! \brief Show a yes-no dialog with 3rd custom button, then wait for user to dismiss it. + \param heading Localized label id or string for the heading of the dialog + \param text Localized label id or string for the dialog message + \param noLabel Localized label id or string for the no button + \param yesLabel Localized label id or string for the yes button + \param customLabel Localized label id or string for the custom button + \param autoCloseTime Time in ms before the dialog becomes automatically closed + \param defaultButtonId Specifies the default focused button + \return -1 for cancelled, 0 for No, 1 for Yes and 2 for custom button + */ + static int ShowAndGetInput(const CVariant& heading, + const CVariant& text, + const CVariant& noLabel, + const CVariant& yesLabel, + const CVariant& customLabel, + unsigned int autoCloseTime, + int defaultButtonId = CONTROL_NO_BUTTON); + + /*! + \brief Open a Yes/No dialog and wait for input + + \param[in] options a struct of type DialogYesNoMessage containing + the options to set for this dialog. + + \returns -1 for cancelled, 0 for No and 1 for Yes + \sa KODI::MESSAGING::HELPERS::DialogYesNoMessage + */ + int ShowAndGetInput(const KODI::MESSAGING::HELPERS::DialogYesNoMessage& options); + +protected: + void OnInitWindow() override; + int GetDefaultLabelID(int controlId) const override; + + bool m_bCanceled; + bool m_bCustom; + int m_defaultButtonId; +}; diff --git a/xbmc/dialogs/IGUIVolumeBarCallback.h b/xbmc/dialogs/IGUIVolumeBarCallback.h new file mode 100644 index 0000000..5e4012a --- /dev/null +++ b/xbmc/dialogs/IGUIVolumeBarCallback.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +/*! + * \brief Interface to expose properties to the volume bar dialog + */ +class IGUIVolumeBarCallback +{ +public: + virtual ~IGUIVolumeBarCallback() = default; + + /*! + * \brief Return true if the callback is active in the GUI + * + * If a registered callback is shown in the GUI, the volume bar is disabled + * until no more callbacks are shown. + * + * \return True if the callback is active in the GUI, false otherwise + */ + virtual bool IsShown() const = 0; +}; |