summaryrefslogtreecommitdiffstats
path: root/xbmc/dialogs
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/dialogs')
-rw-r--r--xbmc/dialogs/CMakeLists.txt69
-rw-r--r--xbmc/dialogs/GUIDialogBoxBase.cpp187
-rw-r--r--xbmc/dialogs/GUIDialogBoxBase.h61
-rw-r--r--xbmc/dialogs/GUIDialogBusy.cpp147
-rw-r--r--xbmc/dialogs/GUIDialogBusy.h51
-rw-r--r--xbmc/dialogs/GUIDialogBusyNoCancel.cpp47
-rw-r--r--xbmc/dialogs/GUIDialogBusyNoCancel.h24
-rw-r--r--xbmc/dialogs/GUIDialogButtonMenu.cpp46
-rw-r--r--xbmc/dialogs/GUIDialogButtonMenu.h21
-rw-r--r--xbmc/dialogs/GUIDialogCache.cpp178
-rw-r--r--xbmc/dialogs/GUIDialogCache.h48
-rw-r--r--xbmc/dialogs/GUIDialogColorPicker.cpp260
-rw-r--r--xbmc/dialogs/GUIDialogColorPicker.h64
-rw-r--r--xbmc/dialogs/GUIDialogContextMenu.cpp705
-rw-r--r--xbmc/dialogs/GUIDialogContextMenu.h152
-rw-r--r--xbmc/dialogs/GUIDialogExtendedProgressBar.cpp155
-rw-r--r--xbmc/dialogs/GUIDialogExtendedProgressBar.h63
-rw-r--r--xbmc/dialogs/GUIDialogFileBrowser.cpp1020
-rw-r--r--xbmc/dialogs/GUIDialogFileBrowser.h88
-rw-r--r--xbmc/dialogs/GUIDialogGamepad.cpp326
-rw-r--r--xbmc/dialogs/GUIDialogGamepad.h35
-rw-r--r--xbmc/dialogs/GUIDialogKaiToast.cpp186
-rw-r--r--xbmc/dialogs/GUIDialogKaiToast.h58
-rw-r--r--xbmc/dialogs/GUIDialogKeyboardGeneric.cpp843
-rw-r--r--xbmc/dialogs/GUIDialogKeyboardGeneric.h95
-rw-r--r--xbmc/dialogs/GUIDialogKeyboardTouch.cpp86
-rw-r--r--xbmc/dialogs/GUIDialogKeyboardTouch.h40
-rw-r--r--xbmc/dialogs/GUIDialogMediaFilter.cpp937
-rw-r--r--xbmc/dialogs/GUIDialogMediaFilter.h92
-rw-r--r--xbmc/dialogs/GUIDialogMediaSource.cpp620
-rw-r--r--xbmc/dialogs/GUIDialogMediaSource.h56
-rw-r--r--xbmc/dialogs/GUIDialogNumeric.cpp916
-rw-r--r--xbmc/dialogs/GUIDialogNumeric.h82
-rw-r--r--xbmc/dialogs/GUIDialogOK.cpp99
-rw-r--r--xbmc/dialogs/GUIDialogOK.h43
-rw-r--r--xbmc/dialogs/GUIDialogPlayEject.cpp101
-rw-r--r--xbmc/dialogs/GUIDialogPlayEject.h27
-rw-r--r--xbmc/dialogs/GUIDialogPlayerControls.cpp19
-rw-r--r--xbmc/dialogs/GUIDialogPlayerControls.h19
-rw-r--r--xbmc/dialogs/GUIDialogPlayerProcessInfo.cpp29
-rw-r--r--xbmc/dialogs/GUIDialogPlayerProcessInfo.h20
-rw-r--r--xbmc/dialogs/GUIDialogProgress.cpp292
-rw-r--r--xbmc/dialogs/GUIDialogProgress.h80
-rw-r--r--xbmc/dialogs/GUIDialogSeekBar.cpp130
-rw-r--r--xbmc/dialogs/GUIDialogSeekBar.h28
-rw-r--r--xbmc/dialogs/GUIDialogSelect.cpp405
-rw-r--r--xbmc/dialogs/GUIDialogSelect.h76
-rw-r--r--xbmc/dialogs/GUIDialogSimpleMenu.cpp154
-rw-r--r--xbmc/dialogs/GUIDialogSimpleMenu.h28
-rw-r--r--xbmc/dialogs/GUIDialogSlider.cpp125
-rw-r--r--xbmc/dialogs/GUIDialogSlider.h57
-rw-r--r--xbmc/dialogs/GUIDialogSmartPlaylistEditor.cpp648
-rw-r--r--xbmc/dialogs/GUIDialogSmartPlaylistEditor.h64
-rw-r--r--xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp575
-rw-r--r--xbmc/dialogs/GUIDialogSmartPlaylistRule.h38
-rw-r--r--xbmc/dialogs/GUIDialogSubMenu.cpp30
-rw-r--r--xbmc/dialogs/GUIDialogSubMenu.h20
-rw-r--r--xbmc/dialogs/GUIDialogTextViewer.cpp132
-rw-r--r--xbmc/dialogs/GUIDialogTextViewer.h39
-rw-r--r--xbmc/dialogs/GUIDialogVolumeBar.cpp88
-rw-r--r--xbmc/dialogs/GUIDialogVolumeBar.h34
-rw-r--r--xbmc/dialogs/GUIDialogYesNo.cpp243
-rw-r--r--xbmc/dialogs/GUIDialogYesNo.h153
-rw-r--r--xbmc/dialogs/IGUIVolumeBarCallback.h28
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 &param /* = "" */)
+{
+ 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 &param = "");
+ 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;
+};