summaryrefslogtreecommitdiffstats
path: root/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp')
-rw-r--r--xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp575
1 files changed, 575 insertions, 0 deletions
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;
+}
+