1 /*
2  *  Copyright (C) 2005-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "GUIDialogSelect.h"
10 
11 #include "FileItem.h"
12 #include "guilib/GUIMessage.h"
13 #include "guilib/LocalizeStrings.h"
14 #include "input/Key.h"
15 #include "utils/StringUtils.h"
16 
17 #define CONTROL_HEADING         1
18 #define CONTROL_NUMBER_OF_ITEMS 2
19 #define CONTROL_SIMPLE_LIST     3
20 #define CONTROL_DETAILED_LIST   6
21 #define CONTROL_EXTRA_BUTTON    5
22 #define CONTROL_EXTRA_BUTTON2 8
23 #define CONTROL_CANCEL_BUTTON   7
24 
CGUIDialogSelect()25 CGUIDialogSelect::CGUIDialogSelect() : CGUIDialogSelect(WINDOW_DIALOG_SELECT) {}
26 
CGUIDialogSelect(int windowId)27 CGUIDialogSelect::CGUIDialogSelect(int windowId)
28     : CGUIDialogBoxBase(windowId, "DialogSelect.xml"),
29     m_bButtonEnabled(false),
30     m_bButton2Enabled(false),
31     m_bButtonPressed(false),
32     m_bButton2Pressed(false),
33     m_selectedItem(nullptr),
34     m_useDetails(false),
35     m_multiSelection(false),
36     m_selectedItems(),
37     m_vecList(new CFileItemList())
38 {
39   m_bConfirmed = false;
40   m_loadType = KEEP_IN_MEMORY;
41 }
42 
43 CGUIDialogSelect::~CGUIDialogSelect(void) = default;
44 
OnMessage(CGUIMessage & message)45 bool CGUIDialogSelect::OnMessage(CGUIMessage& message)
46 {
47   switch (message.GetMessage())
48   {
49   case GUI_MSG_WINDOW_DEINIT:
50     {
51       CGUIDialogBoxBase::OnMessage(message);
52 
53       m_bButtonEnabled = false;
54       m_bButton2Enabled = false;
55       m_useDetails = false;
56       m_multiSelection = false;
57 
58       // construct selected items list
59       m_selectedItems.clear();
60       m_selectedItem = nullptr;
61       for (int i = 0 ; i < m_vecList->Size() ; i++)
62       {
63         CFileItemPtr item = m_vecList->Get(i);
64         if (item->IsSelected())
65         {
66           m_selectedItems.push_back(i);
67           if (!m_selectedItem)
68             m_selectedItem = item;
69         }
70       }
71       m_vecList->Clear();
72       return true;
73     }
74     break;
75 
76   case GUI_MSG_WINDOW_INIT:
77     {
78       m_bButtonPressed = false;
79       m_bButton2Pressed = false;
80       m_bConfirmed = false;
81       CGUIDialogBoxBase::OnMessage(message);
82       return true;
83     }
84     break;
85 
86 
87   case GUI_MSG_CLICKED:
88     {
89       int iControl = message.GetSenderId();
90       if (m_viewControl.HasControl(CONTROL_SIMPLE_LIST))
91       {
92         int iAction = message.GetParam1();
93         if (ACTION_SELECT_ITEM == iAction || ACTION_MOUSE_LEFT_CLICK == iAction)
94         {
95           int iSelected = m_viewControl.GetSelectedItem();
96           if (iSelected >= 0 && iSelected < m_vecList->Size())
97           {
98             CFileItemPtr item(m_vecList->Get(iSelected));
99             if (m_multiSelection)
100               item->Select(!item->IsSelected());
101             else
102             {
103               for (int i = 0 ; i < m_vecList->Size() ; i++)
104                 m_vecList->Get(i)->Select(false);
105               item->Select(true);
106               OnSelect(iSelected);
107             }
108           }
109         }
110       }
111       if (iControl == CONTROL_EXTRA_BUTTON2)
112       {
113         m_bButton2Pressed = true;
114         if (m_multiSelection)
115           m_bConfirmed = true;
116         Close();
117       }
118       if (iControl == CONTROL_EXTRA_BUTTON)
119       {
120         m_selectedItem = nullptr;
121         m_bButtonPressed = true;
122         if (m_multiSelection)
123           m_bConfirmed = true;
124         Close();
125       }
126       else if (iControl == CONTROL_CANCEL_BUTTON)
127       {
128         m_selectedItem = nullptr;
129         m_vecList->Clear();
130         m_selectedItems.clear();
131         m_bConfirmed = false;
132         Close();
133       }
134     }
135     break;
136   case GUI_MSG_SETFOCUS:
137     {
138       if (m_viewControl.HasControl(message.GetControlId()))
139       {
140         if (m_vecList->IsEmpty())
141         {
142           if (m_bButtonEnabled)
143             SET_CONTROL_FOCUS(CONTROL_EXTRA_BUTTON, 0);
144           else
145             SET_CONTROL_FOCUS(CONTROL_CANCEL_BUTTON, 0);
146           return true;
147         }
148         if (m_viewControl.GetCurrentControl() != message.GetControlId())
149         {
150           m_viewControl.SetFocused();
151           return true;
152         }
153       }
154     }
155     break;
156   }
157 
158   return CGUIDialogBoxBase::OnMessage(message);
159 }
160 
OnSelect(int idx)161 void CGUIDialogSelect::OnSelect(int idx)
162 {
163   m_bConfirmed = true;
164   Close();
165 }
166 
OnBack(int actionID)167 bool CGUIDialogSelect::OnBack(int actionID)
168 {
169   m_selectedItem = nullptr;
170   m_vecList->Clear();
171   m_selectedItems.clear();
172   m_bConfirmed = false;
173   return CGUIDialogBoxBase::OnBack(actionID);
174 }
175 
Reset()176 void CGUIDialogSelect::Reset()
177 {
178   m_bButtonEnabled = false;
179   m_bButtonPressed = false;
180   m_bButton2Enabled = false;
181   m_bButton2Pressed = false;
182 
183   m_useDetails = false;
184   m_multiSelection = false;
185   m_focusToButton = false;
186   m_selectedItem = nullptr;
187   m_vecList->Clear();
188   m_selectedItems.clear();
189 }
190 
Add(const std::string & strLabel)191 int CGUIDialogSelect::Add(const std::string& strLabel)
192 {
193   CFileItemPtr pItem(new CFileItem(strLabel));
194   m_vecList->Add(pItem);
195   return m_vecList->Size() - 1;
196 }
197 
Add(const CFileItem & item)198 int CGUIDialogSelect::Add(const CFileItem& item)
199 {
200   m_vecList->Add(CFileItemPtr(new CFileItem(item)));
201   return m_vecList->Size() - 1;
202 }
203 
SetItems(const CFileItemList & pList)204 void CGUIDialogSelect::SetItems(const CFileItemList& pList)
205 {
206   // need to make internal copy of list to be sure dialog is owner of it
207   m_vecList->Clear();
208   m_vecList->Copy(pList);
209 
210   m_viewControl.SetItems(*m_vecList);
211 }
212 
GetSelectedItem() const213 int CGUIDialogSelect::GetSelectedItem() const
214 {
215   return m_selectedItems.size() > 0 ? m_selectedItems[0] : -1;
216 }
217 
GetSelectedFileItem() const218 const CFileItemPtr CGUIDialogSelect::GetSelectedFileItem() const
219 {
220   if (m_selectedItem)
221     return m_selectedItem;
222   return CFileItemPtr(new CFileItem);
223 }
224 
GetSelectedItems() const225 const std::vector<int>& CGUIDialogSelect::GetSelectedItems() const
226 {
227   return m_selectedItems;
228 }
229 
EnableButton(bool enable,int label)230 void CGUIDialogSelect::EnableButton(bool enable, int label)
231 {
232   m_bButtonEnabled = enable;
233   m_buttonLabel = g_localizeStrings.Get(label);
234 }
235 
EnableButton(bool enable,const std::string & label)236 void CGUIDialogSelect::EnableButton(bool enable, const std::string& label)
237 {
238   m_bButtonEnabled = enable;
239   m_buttonLabel = label;
240 }
241 
EnableButton2(bool enable,int label)242 void CGUIDialogSelect::EnableButton2(bool enable, int label)
243 {
244   m_bButton2Enabled = enable;
245   m_button2Label = g_localizeStrings.Get(label);
246 }
247 
EnableButton2(bool enable,const std::string & label)248 void CGUIDialogSelect::EnableButton2(bool enable, const std::string& label)
249 {
250   m_bButton2Enabled = enable;
251   m_button2Label = label;
252 }
253 
IsButtonPressed()254 bool CGUIDialogSelect::IsButtonPressed()
255 {
256   return m_bButtonPressed;
257 }
258 
IsButton2Pressed()259 bool CGUIDialogSelect::IsButton2Pressed()
260 {
261   return m_bButton2Pressed;
262 }
263 
Sort(bool bSortOrder)264 void CGUIDialogSelect::Sort(bool bSortOrder /*=true*/)
265 {
266   m_vecList->Sort(SortByLabel, bSortOrder ? SortOrderAscending : SortOrderDescending);
267 }
268 
SetSelected(int iSelected)269 void CGUIDialogSelect::SetSelected(int iSelected)
270 {
271   if (iSelected < 0 || iSelected >= m_vecList->Size() ||
272       m_vecList->Get(iSelected).get() == NULL)
273     return;
274 
275   // only set m_iSelected if there is no multi-select
276   // or if it doesn't have a valid value yet
277   // or if the current value is bigger than the new one
278   // so that we always focus the item nearest to the beginning of the list
279   if (!m_multiSelection || !m_selectedItem ||
280       (!m_selectedItems.empty() && m_selectedItems.back() > iSelected))
281     m_selectedItem = m_vecList->Get(iSelected);
282   m_vecList->Get(iSelected)->Select(true);
283   m_selectedItems.push_back(iSelected);
284 }
285 
SetSelected(const std::string & strSelectedLabel)286 void CGUIDialogSelect::SetSelected(const std::string &strSelectedLabel)
287 {
288   for (int index = 0; index < m_vecList->Size(); index++)
289   {
290     if (strSelectedLabel == m_vecList->Get(index)->GetLabel())
291     {
292       SetSelected(index);
293       return;
294     }
295   }
296 }
297 
SetSelected(const std::vector<int> & selectedIndexes)298 void CGUIDialogSelect::SetSelected(const std::vector<int>& selectedIndexes)
299 {
300   for (auto i : selectedIndexes)
301     SetSelected(i);
302 }
303 
SetSelected(const std::vector<std::string> & selectedLabels)304 void CGUIDialogSelect::SetSelected(const std::vector<std::string> &selectedLabels)
305 {
306   for (const auto& label : selectedLabels)
307     SetSelected(label);
308 }
309 
SetUseDetails(bool useDetails)310 void CGUIDialogSelect::SetUseDetails(bool useDetails)
311 {
312   m_useDetails = useDetails;
313 }
314 
SetMultiSelection(bool multiSelection)315 void CGUIDialogSelect::SetMultiSelection(bool multiSelection)
316 {
317   m_multiSelection = multiSelection;
318 }
319 
SetButtonFocus(bool buttonFocus)320 void CGUIDialogSelect::SetButtonFocus(bool buttonFocus)
321 {
322   m_focusToButton = buttonFocus;
323 }
324 
GetFirstFocusableControl(int id)325 CGUIControl *CGUIDialogSelect::GetFirstFocusableControl(int id)
326 {
327   if (m_viewControl.HasControl(id))
328     id = m_viewControl.GetCurrentControl();
329   return CGUIDialogBoxBase::GetFirstFocusableControl(id);
330 }
331 
OnWindowLoaded()332 void CGUIDialogSelect::OnWindowLoaded()
333 {
334   CGUIDialogBoxBase::OnWindowLoaded();
335   m_viewControl.Reset();
336   m_viewControl.SetParentWindow(GetID());
337   m_viewControl.AddView(GetControl(CONTROL_SIMPLE_LIST));
338   m_viewControl.AddView(GetControl(CONTROL_DETAILED_LIST));
339 }
340 
OnInitWindow()341 void CGUIDialogSelect::OnInitWindow()
342 {
343   m_viewControl.SetItems(*m_vecList);
344   m_selectedItems.clear();
345   for(int i = 0 ; i < m_vecList->Size(); i++)
346   {
347     auto item = m_vecList->Get(i);
348     if (item->IsSelected())
349     {
350       m_selectedItems.push_back(i);
351       if (m_selectedItem == nullptr)
352         m_selectedItem = item;
353     }
354   }
355   m_viewControl.SetCurrentView(m_useDetails ? CONTROL_DETAILED_LIST : CONTROL_SIMPLE_LIST);
356 
357   SET_CONTROL_LABEL(CONTROL_NUMBER_OF_ITEMS, StringUtils::Format("%i %s",
358       m_vecList->Size(), g_localizeStrings.Get(127).c_str()));
359 
360   if (m_multiSelection)
361     EnableButton(true, 186);
362 
363   if (m_bButtonEnabled)
364   {
365     SET_CONTROL_LABEL(CONTROL_EXTRA_BUTTON, m_buttonLabel);
366     SET_CONTROL_VISIBLE(CONTROL_EXTRA_BUTTON);
367   }
368   else
369     SET_CONTROL_HIDDEN(CONTROL_EXTRA_BUTTON);
370 
371   if (m_bButton2Enabled)
372   {
373     SET_CONTROL_LABEL(CONTROL_EXTRA_BUTTON2, m_button2Label);
374     SET_CONTROL_VISIBLE(CONTROL_EXTRA_BUTTON2);
375   }
376   else
377     SET_CONTROL_HIDDEN(CONTROL_EXTRA_BUTTON2);
378 
379   SET_CONTROL_LABEL(CONTROL_CANCEL_BUTTON, g_localizeStrings.Get(222));
380 
381   CGUIDialogBoxBase::OnInitWindow();
382 
383   // focus one of the buttons if explicitly requested
384   // ATTENTION: this must be done after calling CGUIDialogBoxBase::OnInitWindow()
385   if (m_focusToButton)
386   {
387     if (m_bButtonEnabled)
388       SET_CONTROL_FOCUS(CONTROL_EXTRA_BUTTON, 0);
389     else
390       SET_CONTROL_FOCUS(CONTROL_CANCEL_BUTTON, 0);
391   }
392 
393   // if nothing is selected, select first item
394   m_viewControl.SetSelectedItem(std::max(GetSelectedItem(), 0));
395 }
396 
OnDeinitWindow(int nextWindowID)397 void CGUIDialogSelect::OnDeinitWindow(int nextWindowID)
398 {
399   m_viewControl.Clear();
400   CGUIDialogBoxBase::OnDeinitWindow(nextWindowID);
401 }
402 
OnWindowUnload()403 void CGUIDialogSelect::OnWindowUnload()
404 {
405   CGUIDialogBoxBase::OnWindowUnload();
406   m_viewControl.Reset();
407 }
408