diff options
48 files changed, 2564 insertions, 286 deletions
diff --git a/XBMC-ATV2.xcodeproj/project.pbxproj b/XBMC-ATV2.xcodeproj/project.pbxproj index e02d1ce8bc..9abd902f5f 100644 --- a/XBMC-ATV2.xcodeproj/project.pbxproj +++ b/XBMC-ATV2.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 36A9468815CF214300727135 /* VideoDbUrl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9468615CF214300727135 /* VideoDbUrl.cpp */; }; 36A9468B15CF215300727135 /* UrlOptions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9468915CF215300727135 /* UrlOptions.cpp */; }; 36A9468E15CF217400727135 /* MusicDbUrl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9468C15CF217400727135 /* MusicDbUrl.cpp */; }; + 36A95DB41624898700727135 /* GUIDialogMediaFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A95DB21624898700727135 /* GUIDialogMediaFilter.cpp */; }; 4D5D2E131301753F006ABC13 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D5D2E121301753F006ABC13 /* CFNetwork.framework */; }; 7C0A7ECD13A5DBF900AFC2BD /* AppParamParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7C0A7ECB13A5DBF900AFC2BD /* AppParamParser.cpp */; }; 7C0A7FC813A9E75400AFC2BD /* DirtyRegionSolvers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7C0A7FC413A9E75400AFC2BD /* DirtyRegionSolvers.cpp */; }; @@ -1031,6 +1032,8 @@ 36A9468A15CF215300727135 /* UrlOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UrlOptions.h; sourceTree = "<group>"; }; 36A9468C15CF217400727135 /* MusicDbUrl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MusicDbUrl.cpp; sourceTree = "<group>"; }; 36A9468D15CF217400727135 /* MusicDbUrl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MusicDbUrl.h; sourceTree = "<group>"; }; + 36A95DB21624898700727135 /* GUIDialogMediaFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GUIDialogMediaFilter.cpp; sourceTree = "<group>"; }; + 36A95DB31624898700727135 /* GUIDialogMediaFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUIDialogMediaFilter.h; sourceTree = "<group>"; }; 4D5D2E121301753F006ABC13 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; 7C0A7ECB13A5DBF900AFC2BD /* AppParamParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AppParamParser.cpp; sourceTree = "<group>"; }; 7C0A7ECC13A5DBF900AFC2BD /* AppParamParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppParamParser.h; sourceTree = "<group>"; }; @@ -4601,6 +4604,8 @@ F56C7374131EC151000AD0F6 /* GUIDialogKaiToast.h */, DF830D4915BB2D2300602BE6 /* GUIDialogKeyboardGeneric.cpp */, DF830D4A15BB2D2300602BE6 /* GUIDialogKeyboardGeneric.h */, + 36A95DB21624898700727135 /* GUIDialogMediaFilter.cpp */, + 36A95DB31624898700727135 /* GUIDialogMediaFilter.h */, F56C7377131EC151000AD0F6 /* GUIDialogMediaSource.cpp */, F56C7378131EC151000AD0F6 /* GUIDialogMediaSource.h */, F56C7379131EC151000AD0F6 /* GUIDialogMuteBug.cpp */, @@ -7568,6 +7573,7 @@ 7C4458DB161E209100A905F6 /* Screenshot.cpp in Sources */, 1D638118161E20AC003603ED /* PeripheralImon.cpp in Sources */, DF24EAC71621E58D00034265 /* DVDDemuxBXA.cpp in Sources */, + 36A95DB41624898700727135 /* GUIDialogMediaFilter.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/XBMC-IOS.xcodeproj/project.pbxproj b/XBMC-IOS.xcodeproj/project.pbxproj index a12037c377..87e991f258 100644 --- a/XBMC-IOS.xcodeproj/project.pbxproj +++ b/XBMC-IOS.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 36A9467815CF20A500727135 /* UrlOptions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9467615CF20A500727135 /* UrlOptions.cpp */; }; 36A9467B15CF20BD00727135 /* VideoDbUrl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9467915CF20BD00727135 /* VideoDbUrl.cpp */; }; 36A9467E15CF20E100727135 /* DbUrl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9467C15CF20E100727135 /* DbUrl.cpp */; }; + 36A95DAD1624896C00727135 /* GUIDialogMediaFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A95DAB1624896C00727135 /* GUIDialogMediaFilter.cpp */; }; 4D5D2E1E1301758F006ABC13 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D5D2E1D1301758F006ABC13 /* CFNetwork.framework */; }; 7C0A7EDE13A5DC2800AFC2BD /* AppParamParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7C0A7EDC13A5DC2800AFC2BD /* AppParamParser.cpp */; }; 7C0A7F9D13A9E70800AFC2BD /* GUIWindowDebugInfo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7C0A7F9B13A9E70800AFC2BD /* GUIWindowDebugInfo.cpp */; }; @@ -1035,6 +1036,8 @@ 36A9467A15CF20BD00727135 /* VideoDbUrl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VideoDbUrl.h; sourceTree = "<group>"; }; 36A9467C15CF20E100727135 /* DbUrl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DbUrl.cpp; sourceTree = "<group>"; }; 36A9467D15CF20E100727135 /* DbUrl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DbUrl.h; sourceTree = "<group>"; }; + 36A95DAB1624896C00727135 /* GUIDialogMediaFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GUIDialogMediaFilter.cpp; sourceTree = "<group>"; }; + 36A95DAC1624896C00727135 /* GUIDialogMediaFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUIDialogMediaFilter.h; sourceTree = "<group>"; }; 4D5D2E1D1301758F006ABC13 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; 7C0A7EDC13A5DC2800AFC2BD /* AppParamParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AppParamParser.cpp; sourceTree = "<group>"; }; 7C0A7EDD13A5DC2800AFC2BD /* AppParamParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppParamParser.h; sourceTree = "<group>"; }; @@ -4962,6 +4965,8 @@ F56C8357131F42E8000AD0F6 /* GUIDialogKaiToast.h */, DF830C9315BB20FC00602BE6 /* GUIDialogKeyboardGeneric.cpp */, DF830C9415BB20FC00602BE6 /* GUIDialogKeyboardGeneric.h */, + 36A95DAB1624896C00727135 /* GUIDialogMediaFilter.cpp */, + 36A95DAC1624896C00727135 /* GUIDialogMediaFilter.h */, F56C835A131F42E8000AD0F6 /* GUIDialogMediaSource.cpp */, F56C835B131F42E8000AD0F6 /* GUIDialogMediaSource.h */, F56C835C131F42E8000AD0F6 /* GUIDialogMuteBug.cpp */, @@ -7603,6 +7608,7 @@ 7C4458C8161E206100A905F6 /* Screenshot.cpp in Sources */, 1D638120161E20F2003603ED /* PeripheralImon.cpp in Sources */, DF24EADE1621E67200034265 /* DVDDemuxBXA.cpp in Sources */, + 36A95DAD1624896C00727135 /* GUIDialogMediaFilter.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/XBMC.xcodeproj/project.pbxproj b/XBMC.xcodeproj/project.pbxproj index f20a32326a..c7d4ab4a7b 100644 --- a/XBMC.xcodeproj/project.pbxproj +++ b/XBMC.xcodeproj/project.pbxproj @@ -183,6 +183,7 @@ 36A9466715CF1FD200727135 /* MusicDbUrl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9466515CF1FD200727135 /* MusicDbUrl.cpp */; }; 36A9466A15CF1FED00727135 /* UrlOptions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9466815CF1FED00727135 /* UrlOptions.cpp */; }; 36A9466D15CF201F00727135 /* VideoDbUrl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9466B15CF201F00727135 /* VideoDbUrl.cpp */; }; + 36A95DA51624894400727135 /* GUIDialogMediaFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A95DA31624894400727135 /* GUIDialogMediaFilter.cpp */; }; 3802709A13D5A653009493DD /* SystemClock.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3802709813D5A653009493DD /* SystemClock.cpp */; }; 384718D81325BA04000486D6 /* XBDateTime.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 384718D61325BA04000486D6 /* XBDateTime.cpp */; }; 38F4E57013CCCB3B00664821 /* Implementation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 38F4E56C13CCCB3B00664821 /* Implementation.cpp */; }; @@ -1361,6 +1362,8 @@ 36A9466915CF1FED00727135 /* UrlOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UrlOptions.h; sourceTree = "<group>"; }; 36A9466B15CF201F00727135 /* VideoDbUrl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = VideoDbUrl.cpp; sourceTree = "<group>"; }; 36A9466C15CF201F00727135 /* VideoDbUrl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VideoDbUrl.h; sourceTree = "<group>"; }; + 36A95DA31624894400727135 /* GUIDialogMediaFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GUIDialogMediaFilter.cpp; sourceTree = "<group>"; }; + 36A95DA41624894400727135 /* GUIDialogMediaFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GUIDialogMediaFilter.h; sourceTree = "<group>"; }; 3802709713D5A62D009493DD /* ThreadLocal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThreadLocal.h; sourceTree = "<group>"; }; 3802709813D5A653009493DD /* SystemClock.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SystemClock.cpp; sourceTree = "<group>"; }; 3802709913D5A653009493DD /* SystemClock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SystemClock.h; sourceTree = "<group>"; }; @@ -3865,6 +3868,8 @@ E38A06CD0D95AA5500FF8227 /* GUIDialogKaiToast.h */, DF830D0A15BB260C00602BE6 /* GUIDialogKeyboardGeneric.cpp */, DF830D0B15BB260C00602BE6 /* GUIDialogKeyboardGeneric.h */, + 36A95DA31624894400727135 /* GUIDialogMediaFilter.cpp */, + 36A95DA41624894400727135 /* GUIDialogMediaFilter.h */, E38E17B80D25F9FA00618676 /* GUIDialogMediaSource.cpp */, E38E17B90D25F9FA00618676 /* GUIDialogMediaSource.h */, E38E17BE0D25F9FA00618676 /* GUIDialogMuteBug.cpp */, @@ -7636,6 +7641,7 @@ 7C4458BD161E203800A905F6 /* Screenshot.cpp in Sources */, 1D638128161E211E003603ED /* PeripheralImon.cpp in Sources */, AE89ACA61621DAB800E17DBC /* DVDDemuxBXA.cpp in Sources */, + 36A95DA51624894400727135 /* GUIDialogMediaFilter.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/addons/skin.confluence/720p/DialogMediaFilter.xml b/addons/skin.confluence/720p/DialogMediaFilter.xml new file mode 100644 index 0000000000..accb0c888c --- /dev/null +++ b/addons/skin.confluence/720p/DialogMediaFilter.xml @@ -0,0 +1,184 @@ +<window id="151"> + <defaultcontrol always="true">5</defaultcontrol> + <coordinates> + <system>1</system> + <posx>240</posx> + <posy>100</posy> + </coordinates> + <include>dialogeffect</include> + <controls> + <control type="image"> + <description>background image</description> + <posx>0</posx> + <posy>0</posy> + <width>800</width> + <height>500</height> + <texture border="40">DialogBack.png</texture> + </control> + <control type="image"> + <description>Dialog Header image</description> + <posx>40</posx> + <posy>16</posy> + <width>720</width> + <height>40</height> + <texture>dialogheader.png</texture> + </control> + <control type="label" id="2"> + <description>header label</description> + <posx>40</posx> + <posy>20</posy> + <width>720</width> + <height>30</height> + <font>font13_title</font> + <label>587</label> + <align>center</align> + <aligny>center</aligny> + <textcolor>selected</textcolor> + <shadowcolor>black</shadowcolor> + </control> + <control type="button"> + <description>Close Window button</description> + <posx>710</posx> + <posy>15</posy> + <width>64</width> + <height>32</height> + <label>-</label> + <font>-</font> + <onclick>PreviousMenu</onclick> + <texturefocus>DialogCloseButton-focus.png</texturefocus> + <texturenofocus>DialogCloseButton.png</texturenofocus> + <onleft>10</onleft> + <onright>10</onright> + <onup>10</onup> + <ondown>10</ondown> + <visible>system.getbool(input.enablemouse)</visible> + </control> + + <control type="grouplist" id="5"> + <description>control area</description> + <posx>30</posx> + <posy>70</posy> + <width>720</width> + <height>350</height> + <itemgap>4</itemgap> + <pagecontrol>6</pagecontrol> + <onup>9001</onup> + <ondown>9001</ondown> + <onleft>9001</onleft> + <onright>6</onright> + </control> + <control type="scrollbar" id="6"> + <posx>755</posx> + <posy>70</posy> + <width>25</width> + <height>350</height> + <texturesliderbackground border="0,14,0,14">ScrollBarV.png</texturesliderbackground> + <texturesliderbar border="2,16,2,16">ScrollBarV_bar.png</texturesliderbar> + <texturesliderbarfocus border="2,16,2,16">ScrollBarV_bar_focus.png</texturesliderbarfocus> + <textureslidernib>ScrollBarNib.png</textureslidernib> + <textureslidernibfocus>ScrollBarNib.png</textureslidernibfocus> + <onleft>5</onleft> + <onright>9001</onright> + <showonepage>false</showonepage> + <orientation>vertical</orientation> + </control> + + <control type="button" id="7"> + <description>Default Button</description> + <posx>0</posx> + <posy>0</posy> + <height>40</height> + <font>font13</font> + <textcolor>grey2</textcolor> + <focusedcolor>white</focusedcolor> + <texturenofocus border="5">button-nofocus.png</texturenofocus> + <texturefocus border="5">button-focus2.png</texturefocus> + </control> + <control type="radiobutton" id="8"> + <description>Default RadioButton</description> + <posx>0</posx> + <posy>0</posy> + <height>40</height> + <font>font13</font> + <textcolor>grey2</textcolor> + <focusedcolor>white</focusedcolor> + <texturenofocus border="5">button-nofocus.png</texturenofocus> + <texturefocus border="5">button-focus2.png</texturefocus> + </control> + <control type="spincontrolex" id="9"> + <description>Default SpinControlex</description> + <posx>0</posx> + <posy>0</posy> + <height>40</height> + <font>font13</font> + <textcolor>grey2</textcolor> + <focusedcolor>white</focusedcolor> + <texturenofocus border="5">button-nofocus.png</texturenofocus> + <texturefocus border="5">button-focus2.png</texturefocus> + <aligny>center</aligny> + <reverse>yes</reverse> + </control> + <control type="sliderex" id="10"> + <description>Default Slider</description> + <posx>0</posx> + <posy>0</posy> + <height>40</height> + <font>font13</font> + <textcolor>grey2</textcolor> + <focusedcolor>white</focusedcolor> + <texturenofocus border="5">button-nofocus.png</texturenofocus> + <texturefocus border="5">button-focus2.png</texturefocus> + <aligny>center</aligny> + </control> + <control type="edit" id="12"> + <description>Default Edit</description> + <posx>0</posx> + <posy>0</posy> + <height>40</height> + <font>font13</font> + <textcolor>grey2</textcolor> + <focusedcolor>white</focusedcolor> + <texturenofocus border="5">button-nofocus.png</texturenofocus> + <texturefocus border="5">button-focus2.png</texturefocus> + </control> + + <control type="group" id="9001"> + <posx>190</posx> + <posy>435</posy> + <control type="button" id="28"> + <description>Ok Button</description> + <posx>0</posx> + <posy>0</posy> + <width>200</width> + <height>40</height> + <align>center</align> + <aligny>center</aligny> + <texturenofocus border="5">button-nofocus.png</texturenofocus> + <texturefocus border="5">button-focus.png</texturefocus> + <label>186</label> + <font>font12_title</font> + <onup>5</onup> + <onleft>27</onleft> + <onright>27</onright> + <ondown>5</ondown> + </control> + <control type="button" id="27"> + <description>Clear Button</description> + <posx>210</posx> + <posy>0</posy> + <width>200</width> + <height>40</height> + <align>center</align> + <aligny>center</aligny> + <texturenofocus border="5">button-nofocus.png</texturenofocus> + <texturefocus border="5">button-focus.png</texturefocus> + <label>192</label> + <font>font12_title</font> + <onup>5</onup> + <onleft>28</onleft> + <onright>28</onright> + <ondown>5</ondown> + </control> + </control> + </controls> +</window> diff --git a/addons/skin.confluence/720p/MyMusicNav.xml b/addons/skin.confluence/720p/MyMusicNav.xml index b906bbe899..723c693982 100644 --- a/addons/skin.confluence/720p/MyMusicNav.xml +++ b/addons/skin.confluence/720p/MyMusicNav.xml @@ -101,11 +101,20 @@ <include>ButtonCommonValues</include> </control> <control type="edit" id="19"> + <visible>Container.CanFilter + !Container.CanFilterAdvanced</visible> <description>Filter</description> <textwidth>230</textwidth> <include>ButtonCommonValues</include> <label>587</label> </control> + <control type="radiobutton" id="20"> + <visible>Container.CanFilterAdvanced</visible> + <description>Filter</description> + <include>ButtonCommonValues</include> + <label>587</label> + <selected>Container.Filtered</selected> + <onclick>Filter</onclick> + </control> <control type="radiobutton" id="100"> <description>Show Info Toggle</description> <textwidth>170</textwidth> diff --git a/addons/skin.confluence/720p/MyMusicSongs.xml b/addons/skin.confluence/720p/MyMusicSongs.xml index b5c449516b..193c0ae22d 100644 --- a/addons/skin.confluence/720p/MyMusicSongs.xml +++ b/addons/skin.confluence/720p/MyMusicSongs.xml @@ -98,11 +98,20 @@ <enable>Library.HasContent(Music)</enable> </control> <control type="edit" id="19"> + <visible>Container.CanFilter + !Container.CanFilterAdvanced</visible> <description>Filter</description> <textwidth>230</textwidth> <include>ButtonCommonValues</include> <label>587</label> </control> + <control type="radiobutton" id="20"> + <visible>Container.CanFilterAdvanced</visible> + <description>Filter</description> + <include>ButtonCommonValues</include> + <label>587</label> + <selected>Container.Filtered</selected> + <onclick>Filter</onclick> + </control> <include>CommonNowPlaying_Controls</include> </control> </control> diff --git a/addons/skin.confluence/720p/MyPics.xml b/addons/skin.confluence/720p/MyPics.xml index 7301d5e8c4..bf4ada48a6 100644 --- a/addons/skin.confluence/720p/MyPics.xml +++ b/addons/skin.confluence/720p/MyPics.xml @@ -103,11 +103,19 @@ <usealttexture>Container.SortDirection(Ascending)</usealttexture> </control> <control type="edit" id="19"> + <visible>Container.CanFilter + !Container.CanFilterAdvanced</visible> <description>Filter</description> <textwidth>230</textwidth> <include>ButtonCommonValues</include> <label>587</label> </control> + <control type="radiobutton" id="20"> + <visible>Container.CanFilterAdvanced</visible> + <description>Filter</description> + <include>ButtonCommonValues</include> + <label>587</label> + <onclick>Filter</onclick> + </control> <control type="label" id="201"> <width>250</width> <height>35</height> diff --git a/addons/skin.confluence/720p/MyPrograms.xml b/addons/skin.confluence/720p/MyPrograms.xml index e1b8f699ea..44f907df39 100644 --- a/addons/skin.confluence/720p/MyPrograms.xml +++ b/addons/skin.confluence/720p/MyPrograms.xml @@ -89,11 +89,19 @@ <usealttexture>Container.SortDirection(Ascending)</usealttexture> </control> <control type="edit" id="19"> + <visible>Container.CanFilter + !Container.CanFilterAdvanced</visible> <description>Filter</description> <textwidth>230</textwidth> <include>ButtonCommonValues</include> <label>587</label> </control> + <control type="radiobutton" id="20"> + <visible>Container.CanFilterAdvanced</visible> + <description>Filter</description> + <include>ButtonCommonValues</include> + <label>587</label> + <onclick>Filter</onclick> + </control> <include>CommonNowPlaying_Controls</include> </control> </control> diff --git a/addons/skin.confluence/720p/MyVideoNav.xml b/addons/skin.confluence/720p/MyVideoNav.xml index 9c76c66e8b..fb4a645970 100644 --- a/addons/skin.confluence/720p/MyVideoNav.xml +++ b/addons/skin.confluence/720p/MyVideoNav.xml @@ -98,11 +98,20 @@ <usealttexture>Container.SortDirection(Ascending)</usealttexture> </control> <control type="edit" id="19"> + <visible>Container.CanFilter + !Container.CanFilterAdvanced</visible> <description>Filter</description> <textwidth>230</textwidth> <include>ButtonCommonValues</include> <label>587</label> </control> + <control type="radiobutton" id="20"> + <visible>Container.CanFilterAdvanced</visible> + <description>Filter</description> + <include>ButtonCommonValues</include> + <label>587</label> + <selected>Container.Filtered</selected> + <onclick>Filter</onclick> + </control> <control type="radiobutton" id="99"> <description>Show Info Toggle</description> <textwidth>170</textwidth> diff --git a/addons/skin.confluence/720p/includes.xml b/addons/skin.confluence/720p/includes.xml index 6fa3b0cedd..1bf39a1df3 100644 --- a/addons/skin.confluence/720p/includes.xml +++ b/addons/skin.confluence/720p/includes.xml @@ -332,7 +332,7 @@ <scroll>false</scroll> <align>right</align> <aligny>center</aligny> - <label>$INFO[Window.Property(filter),$LOCALIZE[587] ([COLOR=blue],[/COLOR]) - ]$INFO[Container.NumItems,([COLOR=blue],[/COLOR]) $LOCALIZE[31025]]$INFO[Container.CurrentPage, - $LOCALIZE[31024] ([COLOR=blue]]$INFO[Container.NumPages,/,[/COLOR])]</label> + <label>$INFO[Container.NumItems,([COLOR=blue],[/COLOR]) $LOCALIZE[31025]]$INFO[Container.CurrentPage, - $LOCALIZE[31024] ([COLOR=blue]]$INFO[Container.NumPages,/,[/COLOR])]</label> <include>Window_OpenClose_Animation</include> </control> <control type="label"> diff --git a/language/English/strings.po b/language/English/strings.po index fb5abf8327..e33ba304ed 100644 --- a/language/English/strings.po +++ b/language/English/strings.po @@ -3201,6 +3201,12 @@ msgctxt "#1273" msgid "AirPlay" msgstr "" +#empty string id 1274 + +msgctxt "#1275" +msgid "Filter %s" +msgstr "" + #empty strings from id 1274 to 1299 msgctxt "#1300" @@ -9400,7 +9406,23 @@ msgctxt "#21465" msgid "Above video" msgstr "" -#empty strings from id 21466 to 21799 +msgctxt "#21466" +msgid "between" +msgstr "" + +msgctxt "#21467" +msgid "%.1f to %.1f" +msgstr "" + +msgctxt "#21468" +msgid "%d to %d" +msgstr "" + +msgctxt "#21469" +msgid "%s to %s" +msgstr "" + +#empty strings from id 21470 to 21799 msgctxt "#21800" msgid "File name" diff --git a/project/VS2010Express/XBMC.vcxproj b/project/VS2010Express/XBMC.vcxproj index b3b359ef66..9f32e555be 100644 --- a/project/VS2010Express/XBMC.vcxproj +++ b/project/VS2010Express/XBMC.vcxproj @@ -419,6 +419,7 @@ <ClCompile Include="..\..\xbmc\dialogs\GUIDialogGamepad.cpp" /> <ClCompile Include="..\..\xbmc\dialogs\GUIDialogKaiToast.cpp" /> <ClCompile Include="..\..\xbmc\dialogs\GUIDialogKeyboardGeneric.cpp" /> + <ClCompile Include="..\..\xbmc\dialogs\GUIDialogMediaFilter.cpp" /> <ClCompile Include="..\..\xbmc\dialogs\GUIDialogMediaSource.cpp" /> <ClCompile Include="..\..\xbmc\dialogs\GUIDialogMuteBug.cpp" /> <ClCompile Include="..\..\xbmc\dialogs\GUIDialogNumeric.cpp" /> @@ -1015,6 +1016,7 @@ <ClInclude Include="..\..\xbmc\cores\paplayer\PCMCodec.h" /> <ClInclude Include="..\..\xbmc\dialogs\GUIDialogKeyboardGeneric.h" /> <ClInclude Include="..\..\xbmc\DbUrl.h" /> + <ClInclude Include="..\..\xbmc\dialogs\GUIDialogMediaFilter.h" /> <ClInclude Include="..\..\xbmc\filesystem\ImageFile.h" /> <ClInclude Include="..\..\xbmc\filesystem\VideoDatabaseDirectory\DirectoryNodeTags.h" /> <ClInclude Include="..\..\xbmc\filesystem\windows\WINFileSMB.h" /> diff --git a/project/VS2010Express/XBMC.vcxproj.filters b/project/VS2010Express/XBMC.vcxproj.filters index 749352eb36..b341508bce 100644 --- a/project/VS2010Express/XBMC.vcxproj.filters +++ b/project/VS2010Express/XBMC.vcxproj.filters @@ -2933,6 +2933,9 @@ <ClCompile Include="..\..\xbmc\cores\dvdplayer\DVDDemuxers\DVDDemuxBXA.cpp"> <Filter>cores\dvdplayer\DVDDemuxers</Filter> </ClCompile> + <ClCompile Include="..\..\xbmc\dialogs\GUIDialogMediaFilter.cpp"> + <Filter>dialogs</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="..\..\xbmc\win32\pch.h"> @@ -5734,6 +5737,9 @@ <ClInclude Include="..\..\xbmc\utils\Screenshot.h"> <Filter>utils</Filter> </ClInclude> + <ClInclude Include="..\..\xbmc\dialogs\GUIDialogMediaFilter.h"> + <Filter>dialogs</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ResourceCompile Include="..\..\xbmc\win32\XBMC_PC.rc"> diff --git a/xbmc/Application.cpp b/xbmc/Application.cpp index d29adc9feb..6dcceb3d52 100644 --- a/xbmc/Application.cpp +++ b/xbmc/Application.cpp @@ -286,6 +286,7 @@ #include "guilib/GUIControlFactory.h" #include "dialogs/GUIDialogCache.h" #include "dialogs/GUIDialogPlayEject.h" +#include "dialogs/GUIDialogMediaFilter.h" #include "utils/XMLUtils.h" #include "addons/AddonInstaller.h" @@ -1298,6 +1299,8 @@ bool CApplication::Initialize() g_windowManager.Add(new CGUIDialogPeripheralManager); g_windowManager.Add(new CGUIDialogPeripheralSettings); + + g_windowManager.Add(new CGUIDialogMediaFilter); // window id = 151 g_windowManager.Add(new CGUIWindowMusicPlayList); // window id = 500 g_windowManager.Add(new CGUIWindowMusicSongs); // window id = 501 @@ -3475,6 +3478,7 @@ bool CApplication::Cleanup() g_windowManager.Delete(WINDOW_DIALOG_ADDON_SETTINGS); g_windowManager.Delete(WINDOW_DIALOG_ACCESS_POINTS); g_windowManager.Delete(WINDOW_DIALOG_SLIDER); + g_windowManager.Delete(WINDOW_DIALOG_MEDIA_FILTER); /* Delete PVR related windows and dialogs */ g_windowManager.Delete(WINDOW_PVR); diff --git a/xbmc/DbUrl.h b/xbmc/DbUrl.h index 4a70e59e21..c7d7b3289d 100644 --- a/xbmc/DbUrl.h +++ b/xbmc/DbUrl.h @@ -39,6 +39,7 @@ public: const std::string& GetType() const { return m_type; } void AppendPath(const std::string &subPath); + virtual void AddOption(const std::string &key, const char *value) { CUrlOptions::AddOption(key, value); updateOptions(); } virtual void AddOption(const std::string &key, const std::string &value) { CUrlOptions::AddOption(key, value); updateOptions(); } virtual void AddOption(const std::string &key, int value) { CUrlOptions::AddOption(key, value); updateOptions(); } virtual void AddOption(const std::string &key, float value) { CUrlOptions::AddOption(key, value); updateOptions(); } diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index f137f32c03..bb604b504d 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -431,7 +431,10 @@ const infomap container_bools[] ={{ "onnext", CONTAINER_MOVE_NEXT }, { "currentpage", CONTAINER_CURRENT_PAGE }, { "scrolling", CONTAINER_SCROLLING }, { "hasnext", CONTAINER_HAS_NEXT }, - { "hasprevious", CONTAINER_HAS_PREVIOUS }}; + { "hasprevious", CONTAINER_HAS_PREVIOUS }, + { "canfilter", CONTAINER_CAN_FILTER }, + { "canfilteradvanced",CONTAINER_CAN_FILTERADVANCED }, + { "filtered", CONTAINER_FILTERED }}; const infomap container_ints[] = {{ "row", CONTAINER_ROW }, { "column", CONTAINER_COLUMN }, @@ -2204,6 +2207,24 @@ bool CGUIInfoManager::GetBool(int condition1, int contextWindow, const CGUIListI bReturn = control->GetCondition(condition, 0); } } + else if (condition == CONTAINER_CAN_FILTER) + { + CGUIWindow *window = GetWindowWithCondition(contextWindow, WINDOW_CONDITION_IS_MEDIA_WINDOW); + if (window) + bReturn = !((CGUIMediaWindow*)window)->CanFilterAdvanced(); + } + else if (condition == CONTAINER_CAN_FILTERADVANCED) + { + CGUIWindow *window = GetWindowWithCondition(contextWindow, WINDOW_CONDITION_IS_MEDIA_WINDOW); + if (window) + bReturn = ((CGUIMediaWindow*)window)->CanFilterAdvanced(); + } + else if (condition == CONTAINER_FILTERED) + { + CGUIWindow *window = GetWindowWithCondition(contextWindow, WINDOW_CONDITION_IS_MEDIA_WINDOW); + if (window) + bReturn = ((CGUIMediaWindow*)window)->IsFiltered(); + } else if (condition == VIDEOPLAYER_HAS_INFO) bReturn = ((m_currentFile->HasVideoInfoTag() && !m_currentFile->GetVideoInfoTag()->IsEmpty()) || (m_currentFile->HasPVRChannelInfoTag() && !m_currentFile->GetPVRChannelInfoTag()->IsEmpty())); diff --git a/xbmc/GUIInfoManager.h b/xbmc/GUIInfoManager.h index 576a68b393..b6809380e3 100644 --- a/xbmc/GUIInfoManager.h +++ b/xbmc/GUIInfoManager.h @@ -289,6 +289,10 @@ namespace INFO #define LASTFM_CANLOVE 331 #define LASTFM_CANBAN 332 +#define CONTAINER_CAN_FILTER 342 +#define CONTAINER_CAN_FILTERADVANCED 343 +#define CONTAINER_FILTERED 344 + #define CONTAINER_SCROLL_PREVIOUS 345 // NOTE: These 5 must be kept in this consecutive order #define CONTAINER_MOVE_PREVIOUS 346 #define CONTAINER_STATIC 347 diff --git a/xbmc/dbwrappers/Database.cpp b/xbmc/dbwrappers/Database.cpp index c566e909b2..9773b35acd 100644 --- a/xbmc/dbwrappers/Database.cpp +++ b/xbmc/dbwrappers/Database.cpp @@ -203,6 +203,11 @@ CStdString CDatabase::GetSingleValue(const CStdString &strTable, const CStdStrin return GetSingleValue(query, m_pDS); } +CStdString CDatabase::GetSingleValue(const CStdString &query) +{ + return GetSingleValue(query, m_pDS); +} + bool CDatabase::DeleteValues(const CStdString &strTable, const CStdString &strWhereClause /* = CStdString() */) { bool bReturn = true; diff --git a/xbmc/dbwrappers/Database.h b/xbmc/dbwrappers/Database.h index ac9018273e..c04b13975e 100644 --- a/xbmc/dbwrappers/Database.h +++ b/xbmc/dbwrappers/Database.h @@ -83,6 +83,7 @@ public: * @return The requested value or an empty string if it wasn't found. */ CStdString GetSingleValue(const CStdString &strTable, const CStdString &strColumn, const CStdString &strWhereClause = CStdString(), const CStdString &strOrderBy = CStdString()); + CStdString GetSingleValue(const CStdString &query); /*! \brief Get a single value from a query on a dataset. \param query the query in question. @@ -134,7 +135,7 @@ public: */ bool CommitInsertQueries(); - virtual bool GetFilter(const CDbUrl &dbUrl, Filter &filter) { return true; } + virtual bool GetFilter(CDbUrl &dbUrl, Filter &filter) { return true; } virtual bool BuildSQL(const CStdString &strBaseDir, const CStdString &strQuery, Filter &filter, CStdString &strSQL, CDbUrl &dbUrl); protected: diff --git a/xbmc/dialogs/GUIDialogMediaFilter.cpp b/xbmc/dialogs/GUIDialogMediaFilter.cpp new file mode 100644 index 0000000000..964fd7f138 --- /dev/null +++ b/xbmc/dialogs/GUIDialogMediaFilter.cpp @@ -0,0 +1,913 @@ +/* + * Copyright (C) 2012 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include "GUIDialogMediaFilter.h" +#include "FileItem.h" +#include "GUIUserMessages.h" +#include "XBDateTime.h" +#include "dialogs/GUIDialogSelect.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "music/MusicDatabase.h" +#include "playlists/SmartPlayList.h" +#include "utils/MathUtils.h" +#include "video/VideoDatabase.h" + +// list of controls +#define CONTROL_HEADING 2 +// list of controls from CGUIDialogSettings +#define CONTROL_GROUP_LIST 5 +#define CONTROL_DEFAULT_BUTTON 7 +#define CONTROL_DEFAULT_RADIOBUTTON 8 +#define CONTROL_DEFAULT_SPIN 9 +#define CONTROL_DEFAULT_SLIDER 10 + +#define CONTROL_CLEAR_BUTTON 27 +#define CONTROL_OKAY_BUTTON 28 +#define CONTROL_CANCEL_BUTTON 29 +#define CONTROL_START 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 + +using namespace std; + +static const CGUIDialogMediaFilter::Filter filterList[] = { + { "movies", FieldTitle, 556, SettingInfo::EDIT, CSmartPlaylistRule::OPERATOR_CONTAINS }, + { "movies", FieldRating, 563, SettingInfo::RANGE, CSmartPlaylistRule::OPERATOR_BETWEEN }, + //{ "movies", FieldTime, 180, SettingInfo::TODO, CSmartPlaylistRule::TODO }, + { "movies", FieldInProgress, 575, SettingInfo::CHECK, CSmartPlaylistRule::OPERATOR_FALSE }, + { "movies", FieldYear, 562, SettingInfo::RANGE, CSmartPlaylistRule::OPERATOR_BETWEEN }, + { "movies", FieldTag, 20459, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + { "movies", FieldGenre, 515, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + { "movies", FieldActor, 20337, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + { "movies", FieldDirector, 20339, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + { "movies", FieldStudio, 572, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + //{ "movies", FieldLastPlayed, 568, SettingInfo::TODO, CSmartPlaylistRule::TODO }, + //{ "movies", FieldDateAdded, 570, SettingInfo::TODO, CSmartPlaylistRule::TODO }, + + { "tvshows", FieldTitle, 556, SettingInfo::EDIT, CSmartPlaylistRule::OPERATOR_CONTAINS }, + //{ "tvshows", FieldTvShowStatus, 126, SettingInfo::TODO, CSmartPlaylistRule::TODO }, + { "tvshows", FieldRating, 563, SettingInfo::RANGE, CSmartPlaylistRule::OPERATOR_BETWEEN }, + { "tvshows", FieldInProgress, 575, SettingInfo::CHECK, CSmartPlaylistRule::OPERATOR_FALSE }, + { "tvshows", FieldYear, 562, SettingInfo::RANGE, CSmartPlaylistRule::OPERATOR_BETWEEN }, + { "tvshows", FieldGenre, 515, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + { "tvshows", FieldActor, 20337, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + { "tvshows", FieldDirector, 20339, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + { "tvshows", FieldStudio, 572, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + //{ "tvshows", FieldDateAdded, 570, SettingInfo::TODO, CSmartPlaylistRule::TODO }, + + { "episodes", FieldTitle, 556, SettingInfo::EDIT, CSmartPlaylistRule::OPERATOR_CONTAINS }, + { "episodes", FieldRating, 563, SettingInfo::RANGE, CSmartPlaylistRule::OPERATOR_BETWEEN }, + { "episodes", FieldAirDate, 20416, SettingInfo::RANGE, CSmartPlaylistRule::OPERATOR_BETWEEN }, + { "episodes", FieldInProgress, 575, SettingInfo::CHECK, CSmartPlaylistRule::OPERATOR_FALSE }, + { "episodes", FieldActor, 20337, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + { "episodes", FieldDirector, 20339, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + //{ "episodes", FieldLastPlayed, 568, SettingInfo::TODO, CSmartPlaylistRule::TODO }, + //{ "episodes", FieldDateAdded, 570, SettingInfo::TODO, CSmartPlaylistRule::TODO }, + + { "musicvideos", FieldTitle, 556, SettingInfo::EDIT, CSmartPlaylistRule::OPERATOR_CONTAINS }, + { "musicvideos", FieldArtist, 557, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + { "musicvideos", FieldAlbum, 558, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + //{ "musicvideos", FieldTime, 180, SettingInfo::TODO, CSmartPlaylistRule::TODO }, + { "musicvideos", FieldYear, 562, SettingInfo::RANGE, CSmartPlaylistRule::OPERATOR_BETWEEN }, + { "musicvideos", FieldGenre, 515, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + { "musicvideos", FieldDirector, 20339, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + { "musicvideos", FieldStudio, 572, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + //{ "musicvideos", FieldLastPlayed, 568, SettingInfo::TODO, CSmartPlaylistRule::TODO }, + //{ "musicvideos", FieldDateAdded, 570, SettingInfo::TODO, CSmartPlaylistRule::TODO }, + + { "artists", FieldArtist, 557, SettingInfo::EDIT, CSmartPlaylistRule::OPERATOR_CONTAINS }, + { "artists", FieldGenre, 515, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + + { "albums", FieldAlbum, 556, SettingInfo::EDIT, CSmartPlaylistRule::OPERATOR_CONTAINS }, + { "albums", FieldArtist, 557, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + { "albums", FieldRating, 563, SettingInfo::RANGE, CSmartPlaylistRule::OPERATOR_BETWEEN }, + { "albums", FieldAlbumType, 564, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + { "albums", FieldYear, 562, SettingInfo::RANGE, CSmartPlaylistRule::OPERATOR_BETWEEN }, + { "albums", FieldGenre, 515, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + { "albums", FieldMusicLabel, 21899, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + + { "songs", FieldTitle, 556, SettingInfo::EDIT, CSmartPlaylistRule::OPERATOR_CONTAINS }, + { "songs", FieldAlbum, 558, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + { "songs", FieldArtist, 557, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + { "songs", FieldTime, 180, SettingInfo::RANGE, CSmartPlaylistRule::OPERATOR_BETWEEN }, + { "songs", FieldRating, 563, SettingInfo::RANGE, CSmartPlaylistRule::OPERATOR_BETWEEN }, + { "songs", FieldYear, 562, SettingInfo::RANGE, CSmartPlaylistRule::OPERATOR_BETWEEN }, + { "songs", FieldGenre, 515, SettingInfo::BUTTON, CSmartPlaylistRule::OPERATOR_EQUALS }, + { "songs", FieldPlaycount, 567, SettingInfo::RANGE, CSmartPlaylistRule::OPERATOR_BETWEEN }, + //{ "songs", FieldLastPlayed, 568, SettingInfo::TODO, CSmartPlaylistRule::TODO }, + //{ "songs", FieldDateAdded, 570, SettingInfo::TODO, CSmartPlaylistRule::TODO }, +}; + +#define NUM_FILTERS sizeof(filterList) / sizeof(CGUIDialogMediaFilter::Filter) + +CGUIDialogMediaFilter::CGUIDialogMediaFilter() + : CGUIDialogSettings(WINDOW_DIALOG_MEDIA_FILTER, "DialogMediaFilter.xml"), + m_dbUrl(NULL), + m_filter(NULL) +{ } + +CGUIDialogMediaFilter::~CGUIDialogMediaFilter() +{ + Reset(); +} + +bool CGUIDialogMediaFilter::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_CLICKED: + { + int control = message.GetSenderId(); + + if (control == CONTROL_CLEAR_BUTTON) + { + m_filter->Reset(); + m_filter->SetType(m_mediaType); + + for (map<uint32_t, Filter>::iterator filter = m_filters.begin(); filter != m_filters.end(); filter++) + { + filter->second.rule = NULL; + + switch (filter->second.type) + { + case SettingInfo::STRING: + case SettingInfo::EDIT: + ((CStdString *)filter->second.data)->clear(); + break; + + case SettingInfo::CHECK: + *(int *)filter->second.data = CHECK_ALL; + break; + + case SettingInfo::BUTTON: + ((CStdString *)filter->second.data)->clear(); + SET_CONTROL_LABEL2(filter->second.controlIndex, *(CStdString *)filter->second.data); + break; + + case SettingInfo::RANGE: + *(((float **)filter->second.data)[0]) = m_settings[filter->second.controlIndex - CONTROL_START].min; + *(((float **)filter->second.data)[1]) = m_settings[filter->second.controlIndex - CONTROL_START].max; + break; + + default: + continue; + } + + UpdateSetting(filter->first); + } + + CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS, 10); // 10 for advanced + g_windowManager.SendMessage(message); + return true; + } + break; + } + + case GUI_MSG_WINDOW_DEINIT: + { + Reset(); + break; + } + + default: + break; + } + + return CGUIDialogSettings::OnMessage(message); +} + +void CGUIDialogMediaFilter::ShowAndEditMediaFilter(const std::string &path, CSmartPlaylist &filter) +{ + CGUIDialogMediaFilter *dialog = (CGUIDialogMediaFilter *)g_windowManager.GetWindow(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->DoModal(); +} + +void CGUIDialogMediaFilter::OnWindowLoaded() +{ + CGUIDialogSettings::OnWindowLoaded(); + // we don't need the cancel button so let's hide it + SET_CONTROL_HIDDEN(CONTROL_CANCEL_BUTTON); +} + +void CGUIDialogMediaFilter::CreateSettings() +{ + if (m_filter == NULL) + return; + + m_settings.clear(); + int handledRules = 0; + for (unsigned int index = 0; index < NUM_FILTERS; index++) + { + if (filterList[index].mediaType != m_mediaType) + continue; + + Filter filter = filterList[index]; + filter.controlIndex = CONTROL_START + m_settings.size(); + + // check the smartplaylist if it contains a matching rule + for (vector<CSmartPlaylistRule>::iterator rule = m_filter->m_ruleCombination.m_rules.begin(); rule != m_filter->m_ruleCombination.m_rules.end(); rule++) + { + if (rule->m_field == filter.field) + { + filter.rule = &(*rule); + handledRules++; + break; + } + } + + switch (filter.type) + { + case SettingInfo::STRING: + case SettingInfo::EDIT: + { + if (filter.rule != NULL && filter.rule->m_parameter.size() == 1) + filter.data = new CStdString(filter.rule->m_parameter.at(0)); + else + filter.data = new CStdString(); + + if (filter.type == SettingInfo::STRING) + AddString(filter.field, filter.label, (CStdString *)filter.data); + else + AddEdit(filter.field, filter.label, (CStdString *)filter.data); + break; + } + + case SettingInfo::CHECK: + { + if (filter.rule == NULL) + filter.data = new int(CHECK_ALL); + else + filter.data = new int(filter.rule->m_operator == CSmartPlaylistRule::OPERATOR_TRUE ? CHECK_YES : CHECK_NO); + + vector<pair<int, int> > entries; + entries.push_back(pair<int, int>(CHECK_ALL, CHECK_LABEL_ALL)); + entries.push_back(pair<int, int>(CHECK_NO, CHECK_LABEL_NO)); + entries.push_back(pair<int, int>(CHECK_YES, CHECK_LABEL_YES)); + AddSpin(filter.field, filter.label, (int *)filter.data, entries); + break; + } + + case SettingInfo::BUTTON: + { + CStdString *values = new CStdString(); + if (filter.rule != NULL && filter.rule->m_parameter.size() > 0) + *values = filter.rule->GetLocalizedParameter(m_mediaType); + filter.data = values; + + AddButton(filter.field, filter.label); + break; + } + + case SettingInfo::RANGE: + { + float min, interval, max; + RANGEFORMATFUNCTION format; + GetRange(filter, min, interval, max, format); + + // don't create the filter if there's no real range + if (min == max) + break; + + float *valueLower = new float(); + float *valueUpper = new float(); + if (filter.rule != NULL && filter.rule->m_parameter.size() == 2) + { + *valueLower = (float)strtod(filter.rule->m_parameter.at(0), NULL); + *valueUpper = (float)strtod(filter.rule->m_parameter.at(1), NULL); + } + else + { + *valueLower = min; + *valueUpper = max; + + if (filter.rule != NULL) + { + DeleteRule(filter.field); + filter.rule = NULL; + } + } + + AddRangeSlider(filter.field, filter.label, valueLower, valueUpper, min, interval, max, format); + filter.data = m_settings[filter.controlIndex - CONTROL_START].data; + break; + } + + default: + filter.controlIndex = -1; + if (filter.rule != NULL) + handledRules--; + continue; + } + + m_filters[filter.field] = 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)); +} + +void CGUIDialogMediaFilter::SetupPage() +{ + CGUIDialogSettings::SetupPage(); + + // 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; + + CStdString format; + format.Format(g_localizeStrings.Get(1275).c_str(), g_localizeStrings.Get(localizedMediaId).c_str()); + SET_CONTROL_LABEL(CONTROL_HEADING, format); + + // now we can finally set the label/values of the button settings (genre, actors etc) + for (map<uint32_t, Filter>::const_iterator filter = m_filters.begin(); filter != m_filters.end(); filter++) + { + if (filter->second.type == SettingInfo::BUTTON && + filter->second.controlIndex >= 0 && filter->second.data != NULL) + SET_CONTROL_LABEL2(filter->second.controlIndex, *(CStdString *)filter->second.data); + } + + UpdateControls(); +} + +void CGUIDialogMediaFilter::OnSettingChanged(SettingInfo &setting) +{ + map<uint32_t, Filter>::iterator it = m_filters.find(setting.id); + if (it == m_filters.end()) + return; + + bool changed = true; + bool remove = false; + Filter& filter = it->second; + + switch (filter.type) + { + case SettingInfo::STRING: + case SettingInfo::EDIT: + { + CStdString *str = static_cast<CStdString*>(filter.data); + if (!str->empty()) + { + if (filter.rule == NULL) + filter.rule = AddRule(filter.field, filter.ruleOperator); + filter.rule->m_parameter.clear(); + filter.rule->m_parameter.push_back(*str); + } + else + remove = true; + + break; + } + + case SettingInfo::CHECK: + { + int choice = *(int *)setting.data; + if (choice > CHECK_ALL) + { + CSmartPlaylistRule::SEARCH_OPERATOR ruleOperator = choice == CHECK_YES ? CSmartPlaylistRule::OPERATOR_TRUE : CSmartPlaylistRule::OPERATOR_FALSE; + if (filter.rule == NULL) + filter.rule = AddRule(filter.field, ruleOperator); + else + filter.rule->m_operator = ruleOperator; + } + else + remove = true; + + break; + } + + case SettingInfo::BUTTON: + { + CFileItemList items; + OnBrowse(filter, items); + + if (items.Size() > 0) + { + if (filter.rule == NULL) + filter.rule = AddRule(filter.field, filter.ruleOperator); + + filter.rule->m_parameter.clear(); + for (int index = 0; index < items.Size(); index++) + filter.rule->m_parameter.push_back(items[index]->GetLabel()); + + *(CStdString *)filter.data = filter.rule->GetLocalizedParameter(m_mediaType); + } + else + { + remove = true; + *(CStdString *)filter.data = ""; + } + + SET_CONTROL_LABEL2(filter.controlIndex, *(CStdString *)filter.data); + break; + } + + case SettingInfo::RANGE: + { + SettingInfo &setting = m_settings[filter.controlIndex - CONTROL_START]; + float *valueLower = ((float **)filter.data)[0]; + float *valueUpper = ((float **)filter.data)[1]; + + if (*valueLower > setting.min || *valueUpper < setting.max) + { + if (filter.rule == NULL) + filter.rule = AddRule(filter.field, filter.ruleOperator); + + filter.rule->m_parameter.clear(); + if (filter.field == FieldAirDate) + { + CDateTime lower = (time_t)*valueLower; + CDateTime upper = (time_t)*valueUpper; + filter.rule->m_parameter.push_back(lower.GetAsDBDate()); + filter.rule->m_parameter.push_back(upper.GetAsDBDate()); + } + else + { + CStdString tmp; + tmp.Format("%.1f", *valueLower); + filter.rule->m_parameter.push_back(tmp); + tmp.clear(); + tmp.Format("%.1f", *valueUpper); + filter.rule->m_parameter.push_back(tmp); + } + } + else + { + remove = true; + *((float **)filter.data)[0] = setting.min; + *((float **)filter.data)[1] = setting.max; + } + break; + } + + default: + changed = false; + break; + } + + // we need to remove the existing rule for the title + if (remove && filter.rule != NULL) + { + DeleteRule(filter.field); + filter.rule = NULL; + } + + if (changed) + { + CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS, 10); // 10 for advanced + g_windowManager.SendMessage(message); + + UpdateControls(); + } +} + +void CGUIDialogMediaFilter::Reset() +{ + delete m_dbUrl; + m_dbUrl = NULL; + + // delete all the setting's data + for (map<uint32_t, Filter>::iterator filter = m_filters.begin(); filter != m_filters.end(); filter++) + delete filter->second.data; + + m_filters.clear(); +} + +bool CGUIDialogMediaFilter::SetPath(const std::string &path) +{ + if (path.empty() || m_filter == NULL) + 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 + 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")) + return false; + + // remove "filter" option + if (m_dbUrl->HasOption("filter")) + m_dbUrl->AddOption("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 (map<uint32_t, Filter>::iterator itFilter = m_filters.begin(); itFilter != m_filters.end(); itFilter++) + { + if (itFilter->second.type == SettingInfo::BUTTON) + { + CFileItemList items; + OnBrowse(itFilter->second, items, true); + + int size = items.Size(); + if (items.Size() == 1 && items[0]->HasProperty("total")) + size = (int)items[0]->GetProperty("total").asInteger(); + + CStdString label = g_localizeStrings.Get(itFilter->second.label); + if (size <= 1) + CONTROL_DISABLE(itFilter->second.controlIndex); + else + { + CONTROL_ENABLE(itFilter->second.controlIndex); + label.Format("%s [%d]", label, size); + } + SET_CONTROL_LABEL(itFilter->second.controlIndex, label); + } + } +} + +void CGUIDialogMediaFilter::OnBrowse(const Filter &filter, CFileItemList &items, bool countOnly /* = false */) +{ + CFileItemList selectItems; + if (m_mediaType == "movies" || m_mediaType == "tvshows" || m_mediaType == "episodes" || m_mediaType == "musicvideos") + { + CVideoDatabase videodb; + if (!videodb.Open()) + return; + + CSmartPlaylist tmpFilter = *m_filter; + for (vector<CSmartPlaylistRule>::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; + } + } + + std::set<CStdString> playlists; + CDatabase::Filter dbfilter; + dbfilter.where = tmpFilter.GetWhereClause(videodb, playlists); + + VIDEODB_CONTENT_TYPE type = VIDEODB_CONTENT_MOVIES; + if (m_mediaType == "tvshows") + type = VIDEODB_CONTENT_TVSHOWS; + else if (m_mediaType == "episodes") + type = VIDEODB_CONTENT_EPISODES; + else if (m_mediaType == "musicvideos") + type = VIDEODB_CONTENT_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; + + CSmartPlaylist tmpFilter = *m_filter; + for (vector<CSmartPlaylistRule>::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; + } + } + + std::set<CStdString> 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) + 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 (selectItems.Size() <= 0) + return; + + if (countOnly) + { + items.Copy(selectItems); + return; + } + + // sort the items + selectItems.Sort(SORT_METHOD_LABEL, SortOrderAscending); + + CGUIDialogSelect* pDialog = (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT); + pDialog->Reset(); + pDialog->SetItems(&selectItems); + CStdString strHeading; + strHeading.Format(g_localizeStrings.Get(13401), g_localizeStrings.Get(filter.label)); + pDialog->SetHeading(strHeading); + pDialog->SetMultiSelection(true); + + if (filter.rule != NULL && !filter.rule->m_parameter.empty()) + pDialog->SetSelected(filter.rule->m_parameter); + + pDialog->DoModal(); + if (pDialog->IsConfirmed()) + items.Copy(pDialog->GetSelectedItems()); + else + items.Clear(); + pDialog->Reset(); +} + +CSmartPlaylistRule* CGUIDialogMediaFilter::AddRule(Field field, CSmartPlaylistRule::SEARCH_OPERATOR ruleOperator /* = CSmartPlaylistRule::OPERATOR_CONTAINS */) +{ + CSmartPlaylistRule rule; + rule.m_field = field; + rule.m_operator = ruleOperator; + + m_filter->m_ruleCombination.m_rules.push_back(rule); + return &m_filter->m_ruleCombination.m_rules.at(m_filter->m_ruleCombination.m_rules.size() - 1); +} + +void CGUIDialogMediaFilter::DeleteRule(Field field) +{ + for (vector<CSmartPlaylistRule>::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::GetRange(const Filter &filter, float &min, float &interval, float &max, RANGEFORMATFUNCTION &formatFunction) +{ + if (filter.field == FieldRating) + { + if (m_mediaType == "movies" || m_mediaType == "tvshows" || m_mediaType == "episodes") + { + min = 0.0f; + interval = 0.1f; + max = 10.0f; + formatFunction = RangeAsFloat; + } + else if (m_mediaType == "albums" || m_mediaType == "songs") + { + min = 0.0f; + interval = 1.0f; + max = 5.0f; + formatFunction = RangeAsInt; + } + } + else if (filter.field == FieldYear) + { + formatFunction = RangeAsInt; + min = 0.0f; + interval = 1.0f; + max = 0.0f; + + if (m_mediaType == "movies" || m_mediaType == "tvshows" || m_mediaType == "musicvideos") + { + CStdString table; + CStdString year; + if (m_mediaType == "movies") + { + table = "movieview"; + year.Format("c%02d", VIDEODB_ID_YEAR); + } + else if (m_mediaType == "tvshows") + { + table = "tvshowview"; + year.Format("strftime(\"%%Y\", c%02d)", VIDEODB_ID_TV_PREMIERED); + } + else if (m_mediaType == "musicvideos") + { + table = "musicvideoview"; + year.Format("c%02d", VIDEODB_ID_MUSICVIDEO_YEAR); + } + + GetMinMax(table, year, min, max); + } + else if (m_mediaType == "albums" || m_mediaType == "songs") + { + CStdString table; + if (m_mediaType == "albums") + table = "albumview"; + else if (m_mediaType == "songs") + table = "songview"; + + CDatabase::Filter filter; + filter.where = "iYear > 0"; + GetMinMax(table, "iYear", min, max, filter); + } + } + else if (filter.field == FieldAirDate) + { + formatFunction = RangeAsDate; + min = 0.0f; + interval = 1.0f; + max = 0.0f; + + if (m_mediaType == "episodes") + { + CStdString field; field.Format("CAST(strftime(\"%%s\", c%02d) AS INTEGER)", VIDEODB_ID_EPISODE_AIRED); + + GetMinMax("episodeview", field, min, max); + interval = 60 * 60 * 24 * 7; // 1 week + } + } + else if (filter.field == FieldTime) + { + formatFunction = RangeAsTime; + min = 0.0f; + interval = 10.0f; + max = 0.0f; + + if (m_mediaType == "songs") + GetMinMax("songview", "iDuration", min, max); + } + else if (filter.field == FieldPlaycount) + { + formatFunction = RangeAsInt; + min = 0.0f; + interval = 1.0f; + max = 0.0f; + + if (m_mediaType == "songs") + GetMinMax("songview", "iTimesPlayed", min, max); + } +} + +bool CGUIDialogMediaFilter::GetMinMax(const CStdString &table, const CStdString &field, float &min, float &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; + CStdString strSQLExtra; + if (!db->BuildSQL(m_dbUrl->ToString(), strSQLExtra, extFilter, strSQLExtra, *dbUrl)) + { + delete db; + delete dbUrl; + return false; + } + + CStdString strSQL = "SELECT %s FROM %s "; + + min = (float)strtod(db->GetSingleValue(db->PrepareSQL(strSQL, CStdString("MIN(" + field + ")").c_str(), table.c_str()) + strSQLExtra).c_str(), NULL); + max = (float)strtod(db->GetSingleValue(db->PrepareSQL(strSQL, CStdString("MAX(" + field + ")").c_str(), table.c_str()) + strSQLExtra).c_str(), NULL); + + db->Close(); + delete db; + delete dbUrl; + + return true; +} + +CStdString CGUIDialogMediaFilter::RangeAsFloat(float valueLower, float valueUpper, float minimum) +{ + CStdString text; + if (valueLower != valueUpper) + text.Format(g_localizeStrings.Get(21467).c_str(), valueLower, valueUpper); + else + text.Format("%.1f", valueLower); + return text; +} + +CStdString CGUIDialogMediaFilter::RangeAsInt(float valueLower, float valueUpper, float minimum) +{ + CStdString text; + if (valueLower != valueUpper) + text.Format(g_localizeStrings.Get(21468).c_str(), MathUtils::round_int((double)valueLower), MathUtils::round_int((double)valueUpper)); + else + text.Format("%d", MathUtils::round_int((double)valueLower)); + return text; +} + +CStdString CGUIDialogMediaFilter::RangeAsDate(float valueLower, float valueUpper, float minimum) +{ + CDateTime from = (time_t)valueLower; + CDateTime to = (time_t)valueUpper; + CStdString text; + if (valueLower != valueUpper) + text.Format(g_localizeStrings.Get(21469).c_str(), from.GetAsLocalizedDate(), to.GetAsLocalizedDate()); + else + text.Format("%s", from.GetAsLocalizedDate()); + return text; +} + +CStdString CGUIDialogMediaFilter::RangeAsTime(float valueLower, float valueUpper, float minimum) +{ + CDateTime from = (time_t)valueLower; + CDateTime to = (time_t)valueUpper; + CStdString text; + if (valueLower != valueUpper) + text.Format(g_localizeStrings.Get(21469).c_str(), from.GetAsLocalizedTime("mm:ss"), to.GetAsLocalizedTime("mm:ss")); + else + text.Format("%s", from.GetAsLocalizedTime("mm:ss")); + return text; +} diff --git a/xbmc/dialogs/GUIDialogMediaFilter.h b/xbmc/dialogs/GUIDialogMediaFilter.h new file mode 100644 index 0000000000..681f068488 --- /dev/null +++ b/xbmc/dialogs/GUIDialogMediaFilter.h @@ -0,0 +1,82 @@ +#pragma once +/* + * Copyright (C) 2012 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include <map> +#include <string> + +#include "DbUrl.h" +#include "dbwrappers/Database.h" +#include "playlists/SmartPlayList.h" +#include "settings/GUIDialogSettings.h" +#include "utils/DatabaseUtils.h" +#include "utils/StdString.h" + +class CFileItemList; + +class CGUIDialogMediaFilter : public CGUIDialogSettings +{ +public: + CGUIDialogMediaFilter(); + virtual ~CGUIDialogMediaFilter(); + + virtual bool OnMessage(CGUIMessage& message); + + static void ShowAndEditMediaFilter(const std::string &path, CSmartPlaylist &filter); + + typedef struct { + std::string mediaType; + Field field; + uint32_t label; + SettingInfo::SETTING_TYPE type; + CSmartPlaylistRule::SEARCH_OPERATOR ruleOperator; + void *data; + CSmartPlaylistRule *rule; + int controlIndex; + } Filter; + +protected: + virtual void OnWindowLoaded(); + + virtual void CreateSettings(); + virtual void SetupPage(); + virtual void OnSettingChanged(SettingInfo &setting); + + void Reset(); + bool SetPath(const std::string &path); + void UpdateControls(); + + void OnBrowse(const Filter &filter, CFileItemList &items, bool countOnly = false); + CSmartPlaylistRule* AddRule(Field field, CSmartPlaylistRule::SEARCH_OPERATOR ruleOperator = CSmartPlaylistRule::OPERATOR_CONTAINS); + void DeleteRule(Field field); + void GetRange(const Filter &filter, float &min, float &interval, float &max, RANGEFORMATFUNCTION &formatFunction); + + bool GetMinMax(const CStdString &table, const CStdString &field, float &min, float &max, const CDatabase::Filter &filter = CDatabase::Filter()); + + static CStdString RangeAsFloat(float valueLower, float valueUpper, float minimum); + static CStdString RangeAsInt(float valueLower, float valueUpper, float minimum); + static CStdString RangeAsDate(float valueLower, float valueUpper, float minimum); + static CStdString RangeAsTime(float valueLower, float valueUpper, float minimum); + + CDbUrl* m_dbUrl; + std::string m_mediaType; + CSmartPlaylist *m_filter; + std::map<uint32_t, Filter> m_filters; +}; diff --git a/xbmc/dialogs/Makefile b/xbmc/dialogs/Makefile index 50a2974f36..f286370a88 100644 --- a/xbmc/dialogs/Makefile +++ b/xbmc/dialogs/Makefile @@ -9,6 +9,7 @@ SRCS=GUIDialogBoxBase.cpp \ GUIDialogGamepad.cpp \ GUIDialogKaiToast.cpp \ GUIDialogKeyboardGeneric.cpp \ + GUIDialogMediaFilter.cpp \ GUIDialogMediaSource.cpp \ GUIDialogMuteBug.cpp \ GUIDialogNumeric.cpp \ diff --git a/xbmc/filesystem/SmartPlaylistDirectory.cpp b/xbmc/filesystem/SmartPlaylistDirectory.cpp index 5e7a7df959..ab2a3877b3 100644 --- a/xbmc/filesystem/SmartPlaylistDirectory.cpp +++ b/xbmc/filesystem/SmartPlaylistDirectory.cpp @@ -50,7 +50,7 @@ namespace XFILE return GetDirectory(playlist, items); } - bool CSmartPlaylistDirectory::GetDirectory(const CSmartPlaylist &playlist, CFileItemList& items) + bool CSmartPlaylistDirectory::GetDirectory(const CSmartPlaylist &playlist, CFileItemList& items, const CStdString &strBaseDir /* = "" */, bool filter /* = false */) { bool success = false, success2 = false; std::set<CStdString> playlists; @@ -62,6 +62,8 @@ namespace XFILE if (g_guiSettings.GetBool("filelists.ignorethewhensorting")) sorting.sortAttributes = SortAttributeIgnoreArticle; + std::string option = !filter ? "xsp" : "filter"; + if (playlist.GetType().Equals("movies") || playlist.GetType().Equals("tvshows") || playlist.GetType().Equals("episodes")) @@ -71,32 +73,40 @@ namespace XFILE { MediaType mediaType = DatabaseUtils::MediaTypeFromString(playlist.GetType()); - CStdString strBaseDir; - switch (mediaType) + CStdString baseDir = strBaseDir; + if (strBaseDir.empty()) { - case MediaTypeTvShow: - case MediaTypeEpisode: - strBaseDir = "videodb://2/2/"; - break; + switch (mediaType) + { + case MediaTypeTvShow: + case MediaTypeEpisode: + baseDir = "videodb://2/2/"; + break; - case MediaTypeMovie: - strBaseDir = "videodb://1/2/"; - break; + case MediaTypeMovie: + baseDir = "videodb://1/2/"; + break; - default: - return false; + default: + return false; + } } CVideoDbUrl videoUrl; - CStdString xsp; - if (!videoUrl.FromString(strBaseDir) || !playlist.SaveAsJson(xsp, false)) + if (!videoUrl.FromString(baseDir)) return false; // store the smartplaylist as JSON in the URL as well - videoUrl.AddOption("xsp", xsp); + CStdString xsp; + if (!playlist.IsEmpty()) + { + if (!playlist.SaveAsJson(xsp, false)) + return false; + } + videoUrl.AddOption(option, xsp); - CDatabase::Filter filter; - success = db.GetSortedVideos(mediaType, videoUrl.ToString(), sorting, items, filter, true); + CDatabase::Filter dbfilter; + success = db.GetSortedVideos(mediaType, videoUrl.ToString(), sorting, items, dbfilter, true); db.Close(); } } @@ -106,15 +116,20 @@ namespace XFILE if (db.Open()) { CMusicDbUrl musicUrl; - CStdString xsp; - if (!musicUrl.FromString("musicdb://3/") || !playlist.SaveAsJson(xsp, false)) + if (!musicUrl.FromString(!strBaseDir.empty() ? strBaseDir : "musicdb://3/")) return false; // store the smartplaylist as JSON in the URL as well - musicUrl.AddOption("xsp", xsp); + CStdString xsp; + if (!playlist.IsEmpty()) + { + if (!playlist.SaveAsJson(xsp, false)) + return false; + } + musicUrl.AddOption(option, xsp); - CDatabase::Filter filter; - success = db.GetAlbumsByWhere(musicUrl.ToString(), filter, items, sorting); + CDatabase::Filter dbfilter; + success = db.GetAlbumsByWhere(musicUrl.ToString(), dbfilter, items, sorting); items.SetContent("albums"); db.Close(); } @@ -125,13 +140,17 @@ namespace XFILE if (db.Open()) { CMusicDbUrl musicUrl; - CStdString xsp; - - if (!musicUrl.FromString("musicdb://2/") || !playlist.SaveAsJson(xsp, false)) + if (!musicUrl.FromString("musicdb://2/")) return false; // store the smartplaylist as JSON in the URL as well - musicUrl.AddOption("xsp", xsp); + CStdString xsp; + if (!playlist.IsEmpty()) + { + if (!playlist.SaveAsJson(xsp, false)) + return false; + } + musicUrl.AddOption(option, xsp); CDatabase::Filter filter; success = db.GetArtistsByWhere(musicUrl.ToString(), filter, items, sorting); @@ -150,15 +169,20 @@ namespace XFILE songPlaylist.SetType("songs"); CMusicDbUrl musicUrl; - CStdString xsp; - if (!musicUrl.FromString("musicdb://4/") || !songPlaylist.SaveAsJson(xsp, false)) + if (!musicUrl.FromString(!strBaseDir.empty() ? strBaseDir : "musicdb://4/")) return false; // store the smartplaylist as JSON in the URL as well - musicUrl.AddOption("xsp", xsp); + CStdString xsp; + if (!songPlaylist.IsEmpty()) + { + if (!songPlaylist.SaveAsJson(xsp, false)) + return false; + } + musicUrl.AddOption(option, xsp); - CDatabase::Filter filter; - success = db.GetSongsByWhere(musicUrl.ToString(), filter, items, sorting); + CDatabase::Filter dbfilter; + success = db.GetSongsByWhere(musicUrl.ToString(), dbfilter, items, sorting); items.SetContent("songs"); db.Close(); } @@ -173,12 +197,17 @@ namespace XFILE mvidPlaylist.SetType("musicvideos"); CVideoDbUrl videoUrl; - CStdString xsp; - if (!videoUrl.FromString("videodb://3/2/") || !mvidPlaylist.SaveAsJson(xsp, false)) + if (!videoUrl.FromString(!strBaseDir.empty() ? strBaseDir : "videodb://3/2/")) return false; // store the smartplaylist as JSON in the URL as well - videoUrl.AddOption("xsp", xsp); + CStdString xsp; + if (!mvidPlaylist.IsEmpty()) + { + if (!mvidPlaylist.SaveAsJson(xsp, false)) + return false; + } + videoUrl.AddOption(option, xsp); CFileItemList items2; success2 = db.GetSortedVideos(MediaTypeMusicVideo, videoUrl.ToString(), sorting, items2); diff --git a/xbmc/filesystem/SmartPlaylistDirectory.h b/xbmc/filesystem/SmartPlaylistDirectory.h index 10a9a4235a..526d3a7aaf 100644 --- a/xbmc/filesystem/SmartPlaylistDirectory.h +++ b/xbmc/filesystem/SmartPlaylistDirectory.h @@ -36,7 +36,7 @@ namespace XFILE virtual bool ContainsFiles(const CStdString& strPath); virtual bool Remove(const char *strPath); - static bool GetDirectory(const CSmartPlaylist &playlist, CFileItemList& items); + static bool GetDirectory(const CSmartPlaylist &playlist, CFileItemList& items, const CStdString &strBaseDir = "", bool filter = false); static CStdString GetPlaylistByName(const CStdString& name, const CStdString& playlistType); }; diff --git a/xbmc/guilib/GUISliderControl.cpp b/xbmc/guilib/GUISliderControl.cpp index c2c3536d8f..be49d274b2 100644 --- a/xbmc/guilib/GUISliderControl.cpp +++ b/xbmc/guilib/GUISliderControl.cpp @@ -32,19 +32,26 @@ static const SliderAction actions[] = { CGUISliderControl::CGUISliderControl(int parentID, int controlID, float posX, float posY, float width, float height, const CTextureInfo& backGroundTexture, const CTextureInfo& nibTexture, const CTextureInfo& nibTextureFocus, int iType) : CGUIControl(parentID, controlID, posX, posY, width, height) , m_guiBackground(posX, posY, width, height, backGroundTexture) - , m_guiMid(posX, posY, width, height, nibTexture) - , m_guiMidFocus(posX, posY, width, height, nibTextureFocus) + , m_guiSelectorLower(posX, posY, width, height, nibTexture) + , m_guiSelectorUpper(posX, posY, width, height, nibTexture) + , m_guiSelectorLowerFocus(posX, posY, width, height, nibTextureFocus) + , m_guiSelectorUpperFocus(posX, posY, width, height, nibTextureFocus) { m_iType = iType; - m_iPercent = 0; + m_rangeSelection = false; + m_currentSelector = RangeSelectorLower; // use lower selector by default + m_iPercent[0] = 0; + m_iPercent[1] = 100; m_iStart = 0; m_iEnd = 100; m_iInterval = 1; m_fStart = 0.0f; m_fEnd = 1.0f; m_fInterval = 0.1f; - m_iValue = 0; - m_fValue = 0.0; + m_iValue[0] = m_iStart; + m_iValue[1] = m_iEnd; + m_fValue[0] = m_fStart; + m_fValue[1] = m_fEnd; ControlType = GUICONTROL_SLIDER; m_iInfoCode = 0; m_dragging = false; @@ -76,35 +83,53 @@ void CGUISliderControl::Process(unsigned int currentTime, CDirtyRegionList &dirt dirty |= m_guiBackground.SetWidth(m_width); dirty |= m_guiBackground.Process(currentTime); + CGUITexture &nibLower = (m_bHasFocus && !IsDisabled() && m_currentSelector == RangeSelectorLower) ? m_guiSelectorLowerFocus : m_guiSelectorLower; + dirty |= ProcessSelector(nibLower, currentTime, fScaleY, RangeSelectorLower); + if (m_rangeSelection) + { + CGUITexture &nibUpper = (m_bHasFocus && !IsDisabled() && m_currentSelector == RangeSelectorUpper) ? m_guiSelectorUpperFocus : m_guiSelectorUpper; + dirty |= ProcessSelector(nibUpper, currentTime, fScaleY, RangeSelectorUpper); + } + + if (dirty) + MarkDirtyRegion(); + + CGUIControl::Process(currentTime, dirtyregions); +} + +bool CGUISliderControl::ProcessSelector(CGUITexture &nib, unsigned int currentTime, float fScaleY, RangeSelector selector) +{ + bool dirty = false; // we render the nib centered at the appropriate percentage, except where the nib // would overflow the background image - CGUITexture &nib = (m_bHasFocus && !IsDisabled()) ? m_guiMidFocus : m_guiMid; - dirty |= nib.SetHeight(nib.GetTextureHeight() * fScaleY); dirty |= nib.SetWidth(nib.GetHeight() * 2); - CAspectRatio ratio(CAspectRatio::AR_KEEP); ratio.align = ASPECT_ALIGN_LEFT | ASPECT_ALIGNY_CENTER; + CAspectRatio ratio(CAspectRatio::AR_KEEP); + ratio.align = ASPECT_ALIGN_LEFT | ASPECT_ALIGNY_CENTER; dirty |= nib.SetAspectRatio(ratio); CRect rect = nib.GetRenderRect(); - float offset = GetProportion() * m_width - rect.Width() / 2; + float offset = GetProportion(selector) * m_width - rect.Width() / 2; if (offset > m_width - rect.Width()) offset = m_width - rect.Width(); if (offset < 0) offset = 0; - dirty |= nib.SetPosition(m_guiBackground.GetXPosition() + offset, m_guiBackground.GetYPosition() ); + dirty |= nib.SetPosition(m_guiBackground.GetXPosition() + offset, m_guiBackground.GetYPosition()); dirty |= nib.Process(currentTime); - if (dirty) - MarkDirtyRegion(); - - CGUIControl::Process(currentTime, dirtyregions); + return dirty; } void CGUISliderControl::Render() { m_guiBackground.Render(); - CGUITexture &nib = (m_bHasFocus && !IsDisabled()) ? m_guiMidFocus : m_guiMid; - nib.Render(); + CGUITexture &nibLower = (m_bHasFocus && !IsDisabled() && m_currentSelector == RangeSelectorLower) ? m_guiSelectorLowerFocus : m_guiSelectorLower; + nibLower.Render(); + if (m_rangeSelection) + { + CGUITexture &nibUpper = (m_bHasFocus && !IsDisabled() && m_currentSelector == RangeSelectorUpper) ? m_guiSelectorUpperFocus : m_guiSelectorUpper; + nibUpper.Render(); + } CGUIControl::Render(); } @@ -121,7 +146,8 @@ bool CGUISliderControl::OnMessage(CGUIMessage& message) case GUI_MSG_LABEL_RESET: { - SetPercentage(0); + SetPercentage(0, RangeSelectorLower); + SetPercentage(100, RangeSelectorUpper); return true; } break; @@ -137,42 +163,83 @@ bool CGUISliderControl::OnAction(const CAction &action) { case ACTION_MOVE_LEFT: //case ACTION_OSD_SHOW_VALUE_MIN: - Move( -1); + Move(-1); return true; - break; case ACTION_MOVE_RIGHT: //case ACTION_OSD_SHOW_VALUE_PLUS: Move(1); return true; - break; + + case ACTION_SELECT_ITEM: + // switch between the two sliders + SwitchRangeSelector(); + return true; + default: - return CGUIControl::OnAction(action); + break; } + + return CGUIControl::OnAction(action); } void CGUISliderControl::Move(int iNumSteps) { + bool rangeSwap = false; switch (m_iType) { case SPIN_CONTROL_TYPE_FLOAT: - m_fValue += m_fInterval * iNumSteps; - if (m_fValue < m_fStart) m_fValue = m_fStart; - if (m_fValue > m_fEnd) m_fValue = m_fEnd; - break; + { + float &value = m_fValue[m_currentSelector]; + value += m_fInterval * iNumSteps; + if (value < m_fStart) value = m_fStart; + if (value > m_fEnd) value = m_fEnd; + if (m_fValue[0] > m_fValue[1]) + { + float valueLower = m_fValue[0]; + m_fValue[0] = m_fValue[1]; + m_fValue[1] = valueLower; + rangeSwap = true; + } + break; + } case SPIN_CONTROL_TYPE_INT: - m_iValue += m_iInterval * iNumSteps; - if (m_iValue < m_iStart) m_iValue = m_iStart; - if (m_iValue > m_iEnd) m_iValue = m_iEnd; - break; + { + int &value = m_iValue[m_currentSelector]; + value += m_iInterval * iNumSteps; + if (value < m_iStart) value = m_iStart; + if (value > m_iEnd) value = m_iEnd; + if (m_iValue[0] > m_iValue[1]) + { + int valueLower = m_iValue[0]; + m_iValue[0] = m_iValue[1]; + m_iValue[1] = valueLower; + rangeSwap = true; + } + break; + } default: - m_iPercent += m_iInterval * iNumSteps; - if (m_iPercent < 0) m_iPercent = 0; - if (m_iPercent > 100) m_iPercent = 100; - break; + { + int &value = m_iPercent[m_currentSelector]; + value += m_iInterval * iNumSteps; + if (value < 0) value = 0; + if (value > 100) value = 100; + if (m_iPercent[0] > m_iPercent[1]) + { + int valueLower = m_iPercent[0]; + m_iPercent[0] = m_iPercent[1]; + m_iPercent[1] = valueLower; + rangeSwap = true; + } + break; + } } + + if (rangeSwap) + SwitchRangeSelector(); + SendClick(); } @@ -190,56 +257,130 @@ void CGUISliderControl::SendClick() } } -void CGUISliderControl::SetPercentage(int iPercent) +void CGUISliderControl::SetRangeSelection(bool rangeSelection) +{ + if (m_rangeSelection == rangeSelection) + return; + + m_rangeSelection = rangeSelection; + SetRangeSelector(RangeSelectorLower); + SetInvalid(); +} + +void CGUISliderControl::SetRangeSelector(RangeSelector selector) +{ + if (m_currentSelector == selector) + return; + + m_currentSelector = selector; + SetInvalid(); +} + +void CGUISliderControl::SwitchRangeSelector() +{ + if (m_currentSelector == RangeSelectorLower) + SetRangeSelector(RangeSelectorUpper); + else + SetRangeSelector(RangeSelectorLower); +} + +void CGUISliderControl::SetPercentage(int iPercent, RangeSelector selector /* = RangeSelectorLower */) { if (iPercent > 100) iPercent = 100; - if (iPercent < 0) iPercent = 0; - m_iPercent = iPercent; + else if (iPercent < 0) iPercent = 0; + + int iPercentLower = selector == RangeSelectorLower ? iPercent : m_iPercent[0]; + int iPercentUpper = selector == RangeSelectorUpper ? iPercent : m_iPercent[1]; + + if (!m_rangeSelection || iPercentLower <= iPercentUpper) + { + m_iPercent[0] = iPercentLower; + m_iPercent[1] = iPercentUpper; + } + else + { + m_iPercent[0] = iPercentUpper; + m_iPercent[1] = iPercentLower; + } } -int CGUISliderControl::GetPercentage() const +int CGUISliderControl::GetPercentage(RangeSelector selector /* = RangeSelectorLower */) const { - return m_iPercent; + return m_iPercent[selector]; } -void CGUISliderControl::SetIntValue(int iValue) +void CGUISliderControl::SetIntValue(int iValue, RangeSelector selector /* = RangeSelectorLower */) { if (m_iType == SPIN_CONTROL_TYPE_FLOAT) - m_fValue = (float)iValue; + SetFloatValue((float)iValue); else if (m_iType == SPIN_CONTROL_TYPE_INT) - m_iValue = iValue; + { + if (iValue > m_iEnd) iValue = m_iEnd; + else if (iValue < m_iStart) iValue = m_iStart; + + int iValueLower = selector == RangeSelectorLower ? iValue : m_iValue[0]; + int iValueUpper = selector == RangeSelectorUpper ? iValue : m_iValue[1]; + + if (!m_rangeSelection || iValueLower <= iValueUpper) + { + m_iValue[0] = iValueLower; + m_iValue[1] = iValueUpper; + } + else + { + m_iValue[0] = iValueUpper; + m_iValue[1] = iValueLower; + } + } else SetPercentage(iValue); } -int CGUISliderControl::GetIntValue() const +int CGUISliderControl::GetIntValue(RangeSelector selector /* = RangeSelectorLower */) const { if (m_iType == SPIN_CONTROL_TYPE_FLOAT) - return (int)m_fValue; + return (int)m_fValue[selector]; else if (m_iType == SPIN_CONTROL_TYPE_INT) - return m_iValue; + return m_iValue[selector]; else - return m_iPercent; + return m_iPercent[selector]; } -void CGUISliderControl::SetFloatValue(float fValue) +void CGUISliderControl::SetFloatValue(float fValue, RangeSelector selector /* = RangeSelectorLower */) { if (m_iType == SPIN_CONTROL_TYPE_FLOAT) - m_fValue = fValue; + { + if (fValue > m_fEnd) fValue = m_fEnd; + else if (fValue < m_fStart) fValue = m_fStart; + + float fValueLower = selector == RangeSelectorLower ? fValue : m_fValue[0]; + float fValueUpper = selector == RangeSelectorUpper ? fValue : m_fValue[1]; + + if (!m_rangeSelection || fValueLower <= fValueUpper) + { + m_fValue[0] = fValueLower; + m_fValue[1] = fValueUpper; + } + else + { + m_fValue[0] = fValueUpper; + m_fValue[1] = fValueLower; + } + } else if (m_iType == SPIN_CONTROL_TYPE_INT) - m_iValue = (int)fValue; + SetIntValue((int)fValue); else SetPercentage((int)fValue); } -float CGUISliderControl::GetFloatValue() const +float CGUISliderControl::GetFloatValue(RangeSelector selector /* = RangeSelectorLower */) const { if (m_iType == SPIN_CONTROL_TYPE_FLOAT) - return m_fValue; + return m_fValue[selector]; else if (m_iType == SPIN_CONTROL_TYPE_INT) - return (float)m_iValue; + return (float)m_iValue[selector]; else - return (float)m_iPercent; + return (float)m_iPercent[selector]; } void CGUISliderControl::SetIntInterval(int iInterval) @@ -264,8 +405,8 @@ void CGUISliderControl::SetRange(int iStart, int iEnd) SetFloatRange((float)iStart,(float)iEnd); else { - m_iStart = iStart; - m_iEnd = iEnd; + m_iValue[0] = m_iStart = iStart; + m_iValue[1] = m_iEnd = iEnd; } } @@ -275,8 +416,8 @@ void CGUISliderControl::SetFloatRange(float fStart, float fEnd) SetRange((int)fStart, (int)fEnd); else { - m_fStart = fStart; - m_fEnd = fEnd; + m_fValue[0] = m_fStart = fStart; + m_fValue[1] = m_fEnd = fEnd; } } @@ -284,38 +425,47 @@ void CGUISliderControl::FreeResources(bool immediately) { CGUIControl::FreeResources(immediately); m_guiBackground.FreeResources(immediately); - m_guiMid.FreeResources(immediately); - m_guiMidFocus.FreeResources(immediately); + m_guiSelectorLower.FreeResources(immediately); + m_guiSelectorUpper.FreeResources(immediately); + m_guiSelectorLowerFocus.FreeResources(immediately); + m_guiSelectorUpperFocus.FreeResources(immediately); } void CGUISliderControl::DynamicResourceAlloc(bool bOnOff) { CGUIControl::DynamicResourceAlloc(bOnOff); m_guiBackground.DynamicResourceAlloc(bOnOff); - m_guiMid.DynamicResourceAlloc(bOnOff); - m_guiMidFocus.DynamicResourceAlloc(bOnOff); + m_guiSelectorLower.DynamicResourceAlloc(bOnOff); + m_guiSelectorUpper.DynamicResourceAlloc(bOnOff); + m_guiSelectorLowerFocus.DynamicResourceAlloc(bOnOff); + m_guiSelectorUpperFocus.DynamicResourceAlloc(bOnOff); } void CGUISliderControl::AllocResources() { CGUIControl::AllocResources(); m_guiBackground.AllocResources(); - m_guiMid.AllocResources(); - m_guiMidFocus.AllocResources(); + m_guiSelectorLower.AllocResources(); + m_guiSelectorUpper.AllocResources(); + m_guiSelectorLowerFocus.AllocResources(); + m_guiSelectorUpperFocus.AllocResources(); } void CGUISliderControl::SetInvalid() { CGUIControl::SetInvalid(); m_guiBackground.SetInvalid(); - m_guiMid.SetInvalid(); - m_guiMidFocus.SetInvalid(); + m_guiSelectorLower.SetInvalid(); + m_guiSelectorUpper.SetInvalid(); + m_guiSelectorLowerFocus.SetInvalid(); + m_guiSelectorUpperFocus.SetInvalid(); } bool CGUISliderControl::HitTest(const CPoint &point) const { if (m_guiBackground.HitTest(point)) return true; - if (m_guiMid.HitTest(point)) return true; + if (m_guiSelectorLower.HitTest(point)) return true; + if (m_rangeSelection && m_guiSelectorUpper.HitTest(point)) return true; return false; } @@ -324,19 +474,39 @@ void CGUISliderControl::SetFromPosition(const CPoint &point) float fPercent = (point.x - m_guiBackground.GetXPosition()) / m_guiBackground.GetWidth(); if (fPercent < 0) fPercent = 0; if (fPercent > 1) fPercent = 1; + + RangeSelector selector = RangeSelectorLower; switch (m_iType) { case SPIN_CONTROL_TYPE_FLOAT: - m_fValue = m_fStart + (m_fEnd - m_fStart) * fPercent; - break; + { + float fValue = m_fStart + (m_fEnd - m_fStart) * fPercent; + if (m_rangeSelection && fValue >= m_fValue[1]) + selector = RangeSelectorUpper; + + SetFloatValue(fValue, selector); + break; + } case SPIN_CONTROL_TYPE_INT: - m_iValue = (int)(m_iStart + (float)(m_iEnd - m_iStart) * fPercent + 0.49f); - break; + { + int iValue = (int)(m_iStart + (float)(m_iEnd - m_iStart) * fPercent + 0.49f); + if (m_rangeSelection && iValue >= m_iValue[1]) + selector = RangeSelectorUpper; + + SetIntValue(iValue, selector); + break; + } default: - m_iPercent = (int)(fPercent * 100 + 0.49f); - break; + { + int iValue = (int)(fPercent * 100 + 0.49f); + if (m_rangeSelection && iValue >= m_iPercent[1]) + selector = RangeSelectorUpper; + + SetPercentage(iValue, selector); + break; + } } SendClick(); } @@ -411,11 +581,26 @@ CStdString CGUISliderControl::GetDescription() const return m_textValue; CStdString description; if (m_iType == SPIN_CONTROL_TYPE_FLOAT) - description.Format("%2.2f", m_fValue); + { + if (m_rangeSelection) + description.Format("[%2.2f, %2.2f]", m_fValue[0], m_fValue[1]); + else + description.Format("%2.2f", m_fValue[0]); + } else if (m_iType == SPIN_CONTROL_TYPE_INT) - description.Format("%i", m_iValue); + { + if (m_rangeSelection) + description.Format("[%i, %i]", m_iValue[0], m_iValue[1]); + else + description.Format("%i", m_iValue); + } else - description.Format("%i%%", m_iPercent); + { + if (m_rangeSelection) + description.Format("[%i%%, %i%%]", m_iPercent[0], m_iPercent[1]); + else + description.Format("%i%%", m_iPercent[0]); + } return description; } @@ -423,19 +608,21 @@ bool CGUISliderControl::UpdateColors() { bool changed = CGUIControl::UpdateColors(); changed |= m_guiBackground.SetDiffuseColor(m_diffuseColor); - changed |= m_guiMid.SetDiffuseColor(m_diffuseColor); - changed |= m_guiMidFocus.SetDiffuseColor(m_diffuseColor); + changed |= m_guiSelectorLower.SetDiffuseColor(m_diffuseColor); + changed |= m_guiSelectorUpper.SetDiffuseColor(m_diffuseColor); + changed |= m_guiSelectorLowerFocus.SetDiffuseColor(m_diffuseColor); + changed |= m_guiSelectorUpperFocus.SetDiffuseColor(m_diffuseColor); return changed; } -float CGUISliderControl::GetProportion() const +float CGUISliderControl::GetProportion(RangeSelector selector /* = RangeSelectorLower */) const { if (m_iType == SPIN_CONTROL_TYPE_FLOAT) - return (m_fValue - m_fStart) / (m_fEnd - m_fStart); + return (GetFloatValue(selector) - m_fStart) / (m_fEnd - m_fStart); else if (m_iType == SPIN_CONTROL_TYPE_INT) - return (float)(m_iValue - m_iStart) / (float)(m_iEnd - m_iStart); - return 0.01f * m_iPercent; + return (float)(GetIntValue(selector) - m_iStart) / (float)(m_iEnd - m_iStart); + return 0.01f * GetPercentage(selector); } void CGUISliderControl::SetAction(const CStdString &action) diff --git a/xbmc/guilib/GUISliderControl.h b/xbmc/guilib/GUISliderControl.h index 6b8fbfa828..45809111ab 100644 --- a/xbmc/guilib/GUISliderControl.h +++ b/xbmc/guilib/GUISliderControl.h @@ -51,6 +51,11 @@ class CGUISliderControl : public CGUIControl { public: + typedef enum { + RangeSelectorLower = 0, + RangeSelectorUpper = 1 + } RangeSelector; + CGUISliderControl(int parentID, int controlID, float posX, float posY, float width, float height, const CTextureInfo& backGroundTexture, const CTextureInfo& mibTexture, const CTextureInfo& nibTextureFocus, int iType); virtual ~CGUISliderControl(void); virtual CGUISliderControl *Clone() const { return new CGUISliderControl(*this); }; @@ -65,13 +70,17 @@ public: virtual void SetRange(int iStart, int iEnd); virtual void SetFloatRange(float fStart, float fEnd); virtual bool OnMessage(CGUIMessage& message); + bool ProcessSelector(CGUITexture &nib, unsigned int currentTime, float fScaleY, RangeSelector selector); + void SetRangeSelection(bool rangeSelection); + void SetRangeSelector(RangeSelector selector); + void SwitchRangeSelector(); void SetInfo(int iInfo); - void SetPercentage(int iPercent); - int GetPercentage() const; - void SetIntValue(int iValue); - int GetIntValue() const; - void SetFloatValue(float fValue); - float GetFloatValue() const; + void SetPercentage(int iPercent, RangeSelector selector = RangeSelectorLower); + int GetPercentage(RangeSelector selector = RangeSelectorLower) const; + void SetIntValue(int iValue, RangeSelector selector = RangeSelectorLower); + int GetIntValue(RangeSelector selector = RangeSelectorLower) const; + void SetFloatValue(float fValue, RangeSelector selector = RangeSelectorLower); + float GetFloatValue(RangeSelector selector = RangeSelectorLower) const; void SetIntInterval(int iInterval); void SetFloatInterval(float fInterval); void SetType(int iType) { m_iType = iType; }; @@ -87,25 +96,30 @@ protected: /*! \brief Get the current position of the slider as a proportion \return slider position in the range [0,1] */ - float GetProportion() const; + float GetProportion(RangeSelector selector = RangeSelectorLower) const; /*! \brief Send a click message (and/or action) to the app in response to a slider move */ void SendClick(); CGUITexture m_guiBackground; - CGUITexture m_guiMid; - CGUITexture m_guiMidFocus; + CGUITexture m_guiSelectorLower; + CGUITexture m_guiSelectorUpper; + CGUITexture m_guiSelectorLowerFocus; + CGUITexture m_guiSelectorUpperFocus; int m_iType; - int m_iPercent; + bool m_rangeSelection; + RangeSelector m_currentSelector; + + int m_iPercent[2]; - int m_iValue; + int m_iValue[2]; int m_iStart; int m_iInterval; int m_iEnd; - float m_fValue; + float m_fValue[2]; float m_fStart; float m_fInterval; float m_fEnd; diff --git a/xbmc/guilib/Key.h b/xbmc/guilib/Key.h index 31c76a9280..32c3dfd668 100644 --- a/xbmc/guilib/Key.h +++ b/xbmc/guilib/Key.h @@ -309,6 +309,8 @@ #define ACTION_SUBTITLE_VSHIFT_DOWN 231 // shift down subtitles in DVDPlayer #define ACTION_SUBTITLE_ALIGN 232 // toggle vertical alignment of subtitles +#define ACTION_FILTER 233 + // Window ID defines to make the code a bit more readable #define WINDOW_INVALID 9999 #define WINDOW_HOME 10000 @@ -388,6 +390,7 @@ #define WINDOW_DIALOG_PERIPHERAL_MANAGER 10149 #define WINDOW_DIALOG_PERIPHERAL_SETTINGS 10150 #define WINDOW_DIALOG_EXT_PROGRESS 10151 +#define WINDOW_DIALOG_MEDIA_FILTER 10152 #define WINDOW_MUSIC_PLAYLIST 10500 #define WINDOW_MUSIC_FILES 10501 diff --git a/xbmc/input/ButtonTranslator.cpp b/xbmc/input/ButtonTranslator.cpp index b49b94a457..2b29a1a945 100644 --- a/xbmc/input/ButtonTranslator.cpp +++ b/xbmc/input/ButtonTranslator.cpp @@ -189,6 +189,7 @@ static const ActionMapping actions[] = {"jumpsms7" , ACTION_JUMP_SMS7}, {"jumpsms8" , ACTION_JUMP_SMS8}, {"jumpsms9" , ACTION_JUMP_SMS9}, + {"filter" , ACTION_FILTER}, {"filterclear" , ACTION_FILTER_CLEAR}, {"filtersms2" , ACTION_FILTER_SMS2}, {"filtersms3" , ACTION_FILTER_SMS3}, @@ -335,7 +336,8 @@ static const ActionMapping windows[] = {"startup" , WINDOW_STARTUP_ANIM}, {"peripherals" , WINDOW_DIALOG_PERIPHERAL_MANAGER}, {"peripheralsettings" , WINDOW_DIALOG_PERIPHERAL_SETTINGS}, - {"extendedprogressdialog" , WINDOW_DIALOG_EXT_PROGRESS}}; + {"extendedprogressdialog" , WINDOW_DIALOG_EXT_PROGRESS}, + {"mediafilter" , WINDOW_DIALOG_MEDIA_FILTER}}; static const ActionMapping mousecommands[] = { diff --git a/xbmc/interfaces/json-rpc/AudioLibrary.cpp b/xbmc/interfaces/json-rpc/AudioLibrary.cpp index 13e5608a9d..0b52ee8b98 100644 --- a/xbmc/interfaces/json-rpc/AudioLibrary.cpp +++ b/xbmc/interfaces/json-rpc/AudioLibrary.cpp @@ -76,7 +76,7 @@ JSONRPC_STATUS CAudioLibrary::GetArtists(const CStdString &method, ITransportLay return InvalidParams; CFileItemList items; - if (!musicdatabase.GetArtistsNav(musicUrl.ToString(), items, albumArtistsOnly, genreID, albumID, songID, sorting)) + if (!musicdatabase.GetArtistsNav(musicUrl.ToString(), items, albumArtistsOnly, genreID, albumID, songID, CDatabase::Filter(), sorting)) return InternalError; // Add "artist" to "properties" array by default @@ -154,7 +154,7 @@ JSONRPC_STATUS CAudioLibrary::GetAlbums(const CStdString &method, ITransportLaye return InvalidParams; CFileItemList items; - if (!musicdatabase.GetAlbumsNav(musicUrl.ToString(), items, genreID, artistID, sorting)) + if (!musicdatabase.GetAlbumsNav(musicUrl.ToString(), items, genreID, artistID, CDatabase::Filter(), sorting)) return InternalError; int size = items.Size(); diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp index eddf3ea788..d0f80affe7 100644 --- a/xbmc/music/MusicDatabase.cpp +++ b/xbmc/music/MusicDatabase.cpp @@ -2621,29 +2621,53 @@ void CMusicDatabase::Clean() } } -bool CMusicDatabase::GetGenresNav(const CStdString& strBaseDir, CFileItemList& items) +bool CMusicDatabase::GetGenresNav(const CStdString& strBaseDir, CFileItemList& items, const Filter &filter /* = Filter() */, bool countOnly /* = false */) { try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS.get()) return false; + // get primary genres for songs - could be simplified to just SELECT * FROM genre? + CStdString strSQL = "SELECT %s FROM genre "; + + Filter extFilter = filter; CMusicDbUrl musicUrl; - if (!musicUrl.FromString(strBaseDir)) + if (!musicUrl.FromString(strBaseDir) || !GetFilter(musicUrl, extFilter)) return false; - // get primary genres for songs - could be simplified to just SELECT * FROM genre? - CStdString strSQL="SELECT * " - " FROM genre " - " WHERE idGenre IN" - "(SELECT song_genre.idGenre " - " FROM song_genre) "; + // if there are extra WHERE conditions we might need access + // to songview or albumview for these conditions + if (extFilter.where.size() > 0) + { + if (extFilter.where.find("artistview") != string::npos) + extFilter.AppendJoin("JOIN song_genre ON song_genre.idGenre = genre.idGenre JOIN songview ON songview.idSong = song_genre.idSong " + "JOIN song_artist ON song_artist.idSong = songview.idSong JOIN artistview ON artistview.idArtist = song_artist.idArtist"); + else if (extFilter.where.find("songview") != string::npos) + extFilter.AppendJoin("JOIN song_genre ON song_genre.idGenre = genre.idGenre JOIN songview ON songview.idSong = song_genre.idSong"); + else if (extFilter.where.find("albumview") != string::npos) + extFilter.AppendJoin("JOIN album_genre ON album_genre.idGenre = genre.idGenre JOIN albumview ON albumview.idAlbum = album_genre.idAlbum"); + + extFilter.AppendGroup("genre.idGenre"); + } + extFilter.AppendWhere("genre.strGenre != ''"); - // block null strings - strSQL += " AND genre.strGenre != \"\""; + if (countOnly) + { + extFilter.fields = "COUNT(DISTINCT genre.idGenre)"; + extFilter.group.clear(); + extFilter.order.clear(); + } + + CStdString strSQLExtra; + if (!BuildSQL(strSQLExtra, extFilter, strSQLExtra)) + return false; + + strSQL = PrepareSQL(strSQL.c_str(), !extFilter.fields.empty() && extFilter.fields.compare("*") != 0 ? extFilter.fields.c_str() : "genre.*") + strSQLExtra; // run query CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str()); + if (!m_pDS->query(strSQL.c_str())) return false; int iRowsFound = m_pDS->num_rows(); @@ -2653,15 +2677,25 @@ bool CMusicDatabase::GetGenresNav(const CStdString& strBaseDir, CFileItemList& i return true; } + if (countOnly) + { + CFileItemPtr pItem(new CFileItem()); + pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound); + items.Add(pItem); + + m_pDS->close(); + return true; + } + // get data from returned rows while (!m_pDS->eof()) { - CFileItemPtr pItem(new CFileItem(m_pDS->fv("strGenre").get_asString())); - pItem->GetMusicInfoTag()->SetGenre(m_pDS->fv("strGenre").get_asString()); - pItem->GetMusicInfoTag()->SetDatabaseId(m_pDS->fv("idGenre").get_asInt(), "genre"); + CFileItemPtr pItem(new CFileItem(m_pDS->fv("genre.strGenre").get_asString())); + pItem->GetMusicInfoTag()->SetGenre(m_pDS->fv("genre.strGenre").get_asString()); + pItem->GetMusicInfoTag()->SetDatabaseId(m_pDS->fv("genre.idGenre").get_asInt(), "genre"); CMusicDbUrl itemUrl = musicUrl; - CStdString strDir; strDir.Format("%ld/", m_pDS->fv("idGenre").get_asInt()); + CStdString strDir; strDir.Format("%ld/", m_pDS->fv("genre.idGenre").get_asInt()); itemUrl.AppendPath(strDir); pItem->SetPath(itemUrl.ToString()); @@ -2751,7 +2785,99 @@ bool CMusicDatabase::GetAlbumsByYear(const CStdString& strBaseDir, CFileItemList return GetAlbumsByWhere(musicUrl.ToString(), filter, items); } -bool CMusicDatabase::GetArtistsNav(const CStdString& strBaseDir, CFileItemList& items, bool albumArtistsOnly /* = false */, int idGenre /* = -1 */, int idAlbum /* = -1 */, int idSong /* = -1 */, const SortDescription &sortDescription /* = SortDescription() */) +bool CMusicDatabase::GetCommonNav(const CStdString &strBaseDir, const CStdString &table, const CStdString &labelField, CFileItemList &items, const Filter &filter /* = Filter() */, bool countOnly /* = false */) +{ + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + if (table.empty() || labelField.empty()) + return false; + + try + { + Filter extFilter = filter; + CStdString strSQL = "SELECT %s FROM " + table + " "; + extFilter.AppendGroup(labelField); + extFilter.AppendWhere(labelField + " != ''"); + + if (countOnly) + { + extFilter.fields = "COUNT(DISTINCT " + labelField + ")"; + extFilter.group.clear(); + extFilter.order.clear(); + } + + CMusicDbUrl musicUrl; + if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, musicUrl)) + return false; + + strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : labelField.c_str()); + + // run query + CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str()); + if (!m_pDS->query(strSQL.c_str())) + return false; + + int iRowsFound = m_pDS->num_rows(); + if (iRowsFound <= 0) + { + m_pDS->close(); + return false; + } + + if (countOnly) + { + CFileItemPtr pItem(new CFileItem()); + pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound); + items.Add(pItem); + + m_pDS->close(); + return true; + } + + // get data from returned rows + while (!m_pDS->eof()) + { + string labelValue = m_pDS->fv(labelField).get_asString(); + CFileItemPtr pItem(new CFileItem(labelValue)); + + CMusicDbUrl itemUrl = musicUrl; + CStdString strDir; strDir.Format("%s/", labelValue.c_str()); + itemUrl.AppendPath(strDir); + pItem->SetPath(itemUrl.ToString()); + + pItem->m_bIsFolder = true; + items.Add(pItem); + + m_pDS->next(); + } + items.SetPath(musicUrl.ToString()); + + // cleanup + m_pDS->close(); + + return true; + } + catch (...) + { + m_pDS->close(); + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + } + + return false; +} + +bool CMusicDatabase::GetAlbumTypesNav(const CStdString &strBaseDir, CFileItemList &items, const Filter &filter /* = Filter() */, bool countOnly /* = false */) +{ + return GetCommonNav(strBaseDir, "albumview", "albumview.strType", items, filter, countOnly); +} + +bool CMusicDatabase::GetMusicLabelsNav(const CStdString &strBaseDir, CFileItemList &items, const Filter &filter /* = Filter() */, bool countOnly /* = false */) +{ + return GetCommonNav(strBaseDir, "albumview", "albumview.strLabel", items, filter, countOnly); +} + +bool CMusicDatabase::GetArtistsNav(const CStdString& strBaseDir, CFileItemList& items, bool albumArtistsOnly /* = false */, int idGenre /* = -1 */, int idAlbum /* = -1 */, int idSong /* = -1 */, const Filter &filter /* = Filter() */, const SortDescription &sortDescription /* = SortDescription() */, bool countOnly /* = false */) { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS.get()) return false; @@ -2772,8 +2898,7 @@ bool CMusicDatabase::GetArtistsNav(const CStdString& strBaseDir, CFileItemList& musicUrl.AddOption("albumartistsonly", albumArtistsOnly); - Filter filter; - bool result = GetArtistsByWhere(musicUrl.ToString(), filter, items, sortDescription); + bool result = GetArtistsByWhere(musicUrl.ToString(), filter, items, sortDescription, countOnly); CLog::Log(LOGDEBUG,"Time to retrieve artists from dataset = %i", XbmcThreads::SystemClockMillis() - time); return result; @@ -2786,7 +2911,7 @@ bool CMusicDatabase::GetArtistsNav(const CStdString& strBaseDir, CFileItemList& return false; } -bool CMusicDatabase::GetArtistsByWhere(const CStdString& strBaseDir, const Filter &filter, CFileItemList& items, const SortDescription &sortDescription /* = SortDescription() */) +bool CMusicDatabase::GetArtistsByWhere(const CStdString& strBaseDir, const Filter &filter, CFileItemList& items, const SortDescription &sortDescription /* = SortDescription() */, bool countOnly /* = false */) { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS.get()) return false; @@ -2796,7 +2921,7 @@ bool CMusicDatabase::GetArtistsByWhere(const CStdString& strBaseDir, const Filte int total = -1; CStdString strSQL = "SELECT %s FROM artistview "; - + Filter extFilter = filter; CMusicDbUrl musicUrl; if (!musicUrl.FromString(strBaseDir) || !GetFilter(musicUrl, extFilter)) @@ -2822,6 +2947,13 @@ bool CMusicDatabase::GetArtistsByWhere(const CStdString& strBaseDir, const Filte extFilter.AppendGroup("artistview.idArtist"); } + if (countOnly) + { + extFilter.fields = "COUNT(DISTINCT artistview.idArtist)"; + extFilter.group.clear(); + extFilter.order.clear(); + } + CStdString strSQLExtra; if (!BuildSQL(strSQLExtra, extFilter, strSQLExtra)) return false; @@ -2847,6 +2979,16 @@ bool CMusicDatabase::GetArtistsByWhere(const CStdString& strBaseDir, const Filte return true; } + if (countOnly) + { + CFileItemPtr pItem(new CFileItem()); + pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound); + items.Add(pItem); + + m_pDS->close(); + return true; + } + // store the total value of items as a property if (total < iRowsFound) total = iRowsFound; @@ -2887,6 +3029,7 @@ bool CMusicDatabase::GetArtistsByWhere(const CStdString& strBaseDir, const Filte CLog::Log(LOGERROR, "%s - out of memory getting listing (got %i)", __FUNCTION__, items.Size()); } } + items.SetPath(musicUrl.ToString()); // cleanup m_pDS->close(); @@ -2964,7 +3107,7 @@ bool CMusicDatabase::GetAlbumFromSong(const CSong &song, CAlbum &album) return false; } -bool CMusicDatabase::GetAlbumsNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre /* = -1 */, int idArtist /* = -1 */, const SortDescription &sortDescription /* = SortDescription() */) +bool CMusicDatabase::GetAlbumsNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre /* = -1 */, int idArtist /* = -1 */, const Filter &filter /* = Filter() */, const SortDescription &sortDescription /* = SortDescription() */, bool countOnly /* = false */) { CMusicDbUrl musicUrl; if (!musicUrl.FromString(strBaseDir)) @@ -2977,11 +3120,10 @@ bool CMusicDatabase::GetAlbumsNav(const CStdString& strBaseDir, CFileItemList& i if (idArtist > 0) musicUrl.AddOption("artistid", idArtist); - Filter filter; - return GetAlbumsByWhere(musicUrl.ToString(), filter, items, sortDescription); + return GetAlbumsByWhere(musicUrl.ToString(), filter, items, sortDescription, countOnly); } -bool CMusicDatabase::GetAlbumsByWhere(const CStdString &baseDir, const Filter &filter, CFileItemList &items, const SortDescription &sortDescription /* = SortDescription() */) +bool CMusicDatabase::GetAlbumsByWhere(const CStdString &baseDir, const Filter &filter, CFileItemList &items, const SortDescription &sortDescription /* = SortDescription() */, bool countOnly /* = false */) { if (m_pDB.get() == NULL || m_pDS.get() == NULL) return false; @@ -3039,6 +3181,16 @@ bool CMusicDatabase::GetAlbumsByWhere(const CStdString &baseDir, const Filter &f if (total < iRowsFound) total = iRowsFound; items.SetProperty("total", total); + + if (countOnly) + { + CFileItemPtr pItem(new CFileItem()); + pItem->SetProperty("total", total); + items.Add(pItem); + + m_pDS->close(); + return true; + } DatabaseResults results; results.reserve(iRowsFound); @@ -3069,6 +3221,7 @@ bool CMusicDatabase::GetAlbumsByWhere(const CStdString &baseDir, const Filter &f CLog::Log(LOGERROR, "%s - out of memory getting listing (got %i)", __FUNCTION__, items.Size()); } } + items.SetPath(musicUrl.ToString()); // cleanup m_pDS->close(); @@ -3168,6 +3321,8 @@ bool CMusicDatabase::GetSongsByWhere(const CStdString &baseDir, const Filter &fi return (items.Size() > 0); } } + items.SetPath(musicUrl.ToString()); + // cleanup m_pDS->close(); CLog::Log(LOGDEBUG, "%s(%s) - took %d ms", __FUNCTION__, filter.where.c_str(), XbmcThreads::SystemClockMillis() - time); @@ -5107,7 +5262,7 @@ string CMusicDatabase::GetArtistArtForItem(int mediaId, const string &mediaType, return GetSingleValue(query, m_pDS2); } -bool CMusicDatabase::GetFilter(const CDbUrl &musicUrl, Filter &filter) +bool CMusicDatabase::GetFilter(CDbUrl &musicUrl, Filter &filter) { if (!musicUrl.IsValid()) return false; @@ -5272,8 +5427,6 @@ bool CMusicDatabase::GetFilter(const CDbUrl &musicUrl, Filter &filter) " OR songview.idSong IN (SELECT song.idSong FROM song JOIN album_artist ON song.idAlbum=album_artist.idAlbum JOIN artist ON artist.idArtist = album_artist.idArtist WHERE artist.strArtist like '%s')", // album artists option->second.asString().c_str(), option->second.asString().c_str())); } - else - return false; option = options.find("xsp"); if (option != options.end()) @@ -5290,5 +5443,23 @@ bool CMusicDatabase::GetFilter(const CDbUrl &musicUrl, Filter &filter) } } + option = options.find("filter"); + if (option != options.end()) + { + CSmartPlaylist xspFilter; + if (!xspFilter.LoadFromJson(option->second.asString())) + return false; + + // check if the filter playlist matches the item type + if (xspFilter.GetType() == type) + { + std::set<CStdString> playlists; + filter.AppendWhere(xspFilter.GetWhereClause(*this, playlists)); + } + // remove the filter if it doesn't match the item type + else + musicUrl.AddOption("filter", ""); + } + return true; } diff --git a/xbmc/music/MusicDatabase.h b/xbmc/music/MusicDatabase.h index d030c21a8c..d11c265af0 100644 --- a/xbmc/music/MusicDatabase.h +++ b/xbmc/music/MusicDatabase.h @@ -154,16 +154,19 @@ public: bool GetPaths(std::set<CStdString> &paths); bool SetPathHash(const CStdString &path, const CStdString &hash); bool GetPathHash(const CStdString &path, CStdString &hash); - bool GetGenresNav(const CStdString& strBaseDir, CFileItemList& items); + bool GetGenresNav(const CStdString& strBaseDir, CFileItemList& items, const Filter &filter = Filter(), bool countOnly = false); bool GetYearsNav(const CStdString& strBaseDir, CFileItemList& items); - bool GetArtistsNav(const CStdString& strBaseDir, CFileItemList& items, bool albumArtistsOnly = false, int idGenre = -1, int idAlbum = -1, int idSong = -1, const SortDescription &sortDescription = SortDescription()); - bool GetAlbumsNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre = -1, int idArtist = -1, const SortDescription &sortDescription = SortDescription()); + bool GetArtistsNav(const CStdString& strBaseDir, CFileItemList& items, bool albumArtistsOnly = false, int idGenre = -1, int idAlbum = -1, int idSong = -1, const Filter &filter = Filter(), const SortDescription &sortDescription = SortDescription(), bool countOnly = false); + bool GetCommonNav(const CStdString &strBaseDir, const CStdString &table, const CStdString &labelField, CFileItemList &items, const Filter &filter /* = Filter() */, bool countOnly /* = false */); + bool GetAlbumTypesNav(const CStdString &strBaseDir, CFileItemList &items, const Filter &filter = Filter(), bool countOnly = false); + bool GetMusicLabelsNav(const CStdString &strBaseDir, CFileItemList &items, const Filter &filter = Filter(), bool countOnly = false); + bool GetAlbumsNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre = -1, int idArtist = -1, const Filter &filter = Filter(), const SortDescription &sortDescription = SortDescription(), bool countOnly = false); bool GetAlbumsByYear(const CStdString &strBaseDir, CFileItemList& items, int year); bool GetSongsNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre, int idArtist,int idAlbum, const SortDescription &sortDescription = SortDescription()); bool GetSongsByYear(const CStdString& baseDir, CFileItemList& items, int year); bool GetSongsByWhere(const CStdString &baseDir, const Filter &filter, CFileItemList& items, const SortDescription &sortDescription = SortDescription()); - bool GetAlbumsByWhere(const CStdString &baseDir, const Filter &filter, CFileItemList &items, const SortDescription &sortDescription = SortDescription()); - bool GetArtistsByWhere(const CStdString& strBaseDir, const Filter &filter, CFileItemList& items, const SortDescription &sortDescription = SortDescription()); + bool GetAlbumsByWhere(const CStdString &baseDir, const Filter &filter, CFileItemList &items, const SortDescription &sortDescription = SortDescription(), bool countOnly = false); + bool GetArtistsByWhere(const CStdString& strBaseDir, const Filter &filter, CFileItemList& items, const SortDescription &sortDescription = SortDescription(), bool countOnly = false); bool GetRandomSong(CFileItem* item, int& idSong, const Filter &filter); int GetKaraokeSongsCount(); int GetSongsCount(const Filter &filter = Filter()); @@ -269,7 +272,7 @@ public: */ std::string GetArtistArtForItem(int mediaId, const std::string &mediaType, const std::string &artType); - virtual bool GetFilter(const CDbUrl &musicUrl, Filter &filter); + virtual bool GetFilter(CDbUrl &musicUrl, Filter &filter); protected: std::map<CStdString, int> m_artistCache; diff --git a/xbmc/music/windows/GUIWindowMusicBase.cpp b/xbmc/music/windows/GUIWindowMusicBase.cpp index 2e7a2e9af1..9e6fcf8054 100644 --- a/xbmc/music/windows/GUIWindowMusicBase.cpp +++ b/xbmc/music/windows/GUIWindowMusicBase.cpp @@ -1314,11 +1314,38 @@ void CGUIWindowMusicBase::OnRetrieveMusicInfo(CFileItemList& items) bool CGUIWindowMusicBase::GetDirectory(const CStdString &strDirectory, CFileItemList &items) { + CStdString directory = strDirectory; + + // check if the path contains a filter and if so load it and + // remove it from the path to get proper GUI view states etc + CSmartPlaylist filterXsp; + CMusicDbUrl musicUrl; + if (musicUrl.FromString(strDirectory)) + { + CVariant filter; + if (musicUrl.GetOption("filter", filter)) + { + // load the filter and if it's type does not match the + // path's item type reset it + if (filterXsp.LoadFromJson(filter.asString()) && !filterXsp.GetType().Equals(musicUrl.GetType().c_str())) + filterXsp.Reset(); + + // remove the "filter" option from the path + musicUrl.AddOption("filter", ""); + } + directory = musicUrl.ToString(); + } + items.SetThumbnailImage(""); - bool bResult = CGUIMediaWindow::GetDirectory(strDirectory,items); + bool bResult = CGUIMediaWindow::GetDirectory(directory, items); if (bResult) CMusicThumbLoader::FillThumb(items); + // (re-)apply the previously retrieved filter + // because it was reset in CGUIMediaWindow::GetDirectory() + if (!filterXsp.IsEmpty()) + m_filter = filterXsp; + // add in the "New Playlist" item if we're in the playlists folder if ((items.GetPath() == "special://musicplaylists/") && !items.Contains("newplaylist://")) { @@ -1350,6 +1377,15 @@ void CGUIWindowMusicBase::OnPrepareFileItems(CFileItemList &items) { } +bool CGUIWindowMusicBase::CheckFilterAdvanced(CFileItemList &items) +{ + CStdString content = items.GetContent(); + if (items.IsMusicDb() && (content.Equals("artists") || content.Equals("albums") || content.Equals("songs"))) + return true; + + return false; +} + void CGUIWindowMusicBase::OnInitWindow() { CGUIMediaWindow::OnInitWindow(); diff --git a/xbmc/music/windows/GUIWindowMusicBase.h b/xbmc/music/windows/GUIWindowMusicBase.h index 1de6b29003..2b8fd67955 100644 --- a/xbmc/music/windows/GUIWindowMusicBase.h +++ b/xbmc/music/windows/GUIWindowMusicBase.h @@ -71,6 +71,8 @@ protected: virtual void OnPrepareFileItems(CFileItemList &items); virtual CStdString GetStartFolder(const CStdString &dir); + virtual bool CheckFilterAdvanced(CFileItemList &items); + // new methods virtual void PlayItem(int iItem); virtual bool OnPlayMedia(int iItem); diff --git a/xbmc/music/windows/GUIWindowMusicNav.cpp b/xbmc/music/windows/GUIWindowMusicNav.cpp index 88c55146d3..fb0948a7e8 100644 --- a/xbmc/music/windows/GUIWindowMusicNav.cpp +++ b/xbmc/music/windows/GUIWindowMusicNav.cpp @@ -272,7 +272,7 @@ bool CGUIWindowMusicNav::Update(const CStdString &strDirectory) if (CGUIWindowMusicBase::Update(strDirectory)) { - m_thumbLoader.Load(*m_vecItems); + m_thumbLoader.Load(*m_unfilteredItems); return true; } diff --git a/xbmc/playlists/SmartPlayList.cpp b/xbmc/playlists/SmartPlayList.cpp index ee78668059..739c0d4bfd 100644 --- a/xbmc/playlists/SmartPlayList.cpp +++ b/xbmc/playlists/SmartPlayList.cpp @@ -132,7 +132,8 @@ static const operatorField operators[] = { { "contains", CSmartPlaylistRule::OPE { "inthelast", CSmartPlaylistRule::OPERATOR_IN_THE_LAST, 21410 }, { "notinthelast", CSmartPlaylistRule::OPERATOR_NOT_IN_THE_LAST, 21411 }, { "true", CSmartPlaylistRule::OPERATOR_TRUE, 20122 }, - { "false", CSmartPlaylistRule::OPERATOR_FALSE, 20424 } + { "false", CSmartPlaylistRule::OPERATOR_FALSE, 20424 }, + { "between", CSmartPlaylistRule::OPERATOR_BETWEEN, 21456 } }; #define NUM_OPERATORS sizeof(operators) / sizeof(operatorField) @@ -747,6 +748,21 @@ CStdString CSmartPlaylistRule::GetWhereClause(const CDatabase &db, const CStdStr } } + // The BETWEEN operator is handled special + if (op == OPERATOR_BETWEEN) + { + if (m_parameter.size() != 2) + return ""; + + FIELD_TYPE fieldType = GetFieldType(m_field); + if (fieldType == NUMERIC_FIELD || m_field == FieldYear) + return db.PrepareSQL("CAST(%s as DECIMAL(5,1)) BETWEEN %s AND %s", GetField(m_field, strType).c_str(), m_parameter[0].c_str(), m_parameter[1].c_str()); + else if (fieldType == SECONDS_FIELD) + return db.PrepareSQL("CAST(%s as INTEGER) BETWEEN %s AND %s", GetField(m_field, strType).c_str(), m_parameter[0].c_str(), m_parameter[1].c_str()); + else + return db.PrepareSQL("%s BETWEEN '%s' AND '%s'", GetField(m_field, strType).c_str(), m_parameter[0].c_str(), m_parameter[1].c_str()); + } + // now the query parameter CStdString wholeQuery; for (vector<CStdString>::const_iterator it = m_parameter.begin(); it != m_parameter.end(); /* it++ is done further down */) @@ -810,6 +826,13 @@ CStdString CSmartPlaylistRule::GetWhereClause(const CDatabase &db, const CStdStr else if (m_field == FieldAlbumArtist) query = GetField(FieldId, strType) + negate + " IN (SELECT album_artist.idAlbum FROM album_artist, artist WHERE album_artist.idArtist = artist.idArtist AND artist.strArtist" + parameter + ")"; } + else if (strType == "artists") + { + table = "artistview"; + + if (m_field == FieldGenre) + query = GetField(FieldId, strType) + negate + " IN (SELECT song_artist.idArtist FROM song_artist, song_genre, genre WHERE song_artist.idSong = song_genre.idSong AND song_genre.idGenre = genre.idGenre AND genre.strGenre" + parameter + ")"; + } else if (strType == "movies") { table = "movieview"; @@ -1093,11 +1116,7 @@ void CSmartPlaylistRuleCombination::AddCombination(const CSmartPlaylistRuleCombi CSmartPlaylist::CSmartPlaylist() { - m_ruleCombination.SetType(CSmartPlaylistRuleCombination::CombinationAnd); - m_limit = 0; - m_orderField = SortByNone; - m_orderAscending = true; - m_playlistType = "songs"; // sane default + Reset(); } TiXmlElement *CSmartPlaylist::OpenAndReadName(const CStdString &path) @@ -1374,6 +1393,17 @@ bool CSmartPlaylist::SaveAsJson(CStdString &json, bool full /* = true */) const return json.size() > 0; } +void CSmartPlaylist::Reset() +{ + m_ruleCombination.m_combinations.clear(); + m_ruleCombination.m_rules.clear(); + m_ruleCombination.SetType(CSmartPlaylistRuleCombination::CombinationAnd); + m_limit = 0; + m_orderField = SortByNone; + m_orderAscending = true; + m_playlistType = "songs"; // sane default +} + void CSmartPlaylist::SetName(const CStdString &name) { m_playlistName = name; diff --git a/xbmc/playlists/SmartPlayList.h b/xbmc/playlists/SmartPlayList.h index b8a2374e08..190abd04b8 100644 --- a/xbmc/playlists/SmartPlayList.h +++ b/xbmc/playlists/SmartPlayList.h @@ -60,6 +60,7 @@ public: OPERATOR_NOT_IN_THE_LAST, OPERATOR_TRUE, OPERATOR_FALSE, + OPERATOR_BETWEEN, OPERATOR_END }; @@ -137,6 +138,7 @@ public: private: friend class CSmartPlaylist; friend class CGUIDialogSmartPlaylistEditor; + friend class CGUIDialogMediaFilter; Combination m_type; CSmartPlaylistRuleCombinations m_combinations; @@ -159,6 +161,8 @@ public: TiXmlElement *OpenAndReadName(const CStdString &path); bool LoadFromXML(TiXmlElement *root, const CStdString &encoding = "UTF-8"); + void Reset(); + void SetName(const CStdString &name); void SetType(const CStdString &type); // music, video, mixed const CStdString& GetName() const { return m_playlistName; }; @@ -191,8 +195,10 @@ public: static void GetAvailableFields(const std::string &type, std::vector<std::string> &fieldList); static void GetAvailableOperators(std::vector<std::string> &operatorList); + bool IsEmpty() const { return m_ruleCombination.m_rules.empty() && m_ruleCombination.m_combinations.empty(); } private: friend class CGUIDialogSmartPlaylistEditor; + friend class CGUIDialogMediaFilter; TiXmlElement* readName(); TiXmlElement *readNameFromXml(const CStdString &xml); diff --git a/xbmc/settings/GUIDialogSettings.cpp b/xbmc/settings/GUIDialogSettings.cpp index 91b1e3aa61..5aa16cffd4 100644 --- a/xbmc/settings/GUIDialogSettings.cpp +++ b/xbmc/settings/GUIDialogSettings.cpp @@ -198,7 +198,7 @@ void CGUIDialogSettings::UpdateSetting(unsigned int id) { float value = *(float *)setting.data; pControl->SetFloatValue(value); - if (setting.formatFunction) pControl->SetTextValue(setting.formatFunction(value, setting.interval)); + if (setting.formatFunction.standard) pControl->SetTextValue(setting.formatFunction.standard(value, setting.interval)); } } else if (setting.type == SettingInfo::BUTTON_DIALOG) @@ -209,8 +209,8 @@ void CGUIDialogSettings::UpdateSetting(unsigned int id) } else if (setting.type == SettingInfo::EDIT) { - CGUIEditControl *pControl = (CGUIEditControl *)GetControl(controlID); - if (pControl && setting.data) pControl->SetLabel2(*(CStdString *)setting.data); + SET_CONTROL_LABEL(controlID, setting.name); + if (setting.data) SET_CONTROL_LABEL2(controlID, string(*(CStdString *)setting.data)); } else if (setting.type == SettingInfo::EDIT_NUM) { @@ -229,6 +229,17 @@ void CGUIDialogSettings::UpdateSetting(unsigned int id) strNewValue = "-"; SET_CONTROL_LABEL2(controlID, strNewValue); } + else if (setting.type == SettingInfo::RANGE) + { + CGUISettingsSliderControl *pControl = (CGUISettingsSliderControl *)GetControl(controlID); + float** value = (float **)setting.data; + if (pControl && setting.data) + { + pControl->SetFloatValue(*(value[0]), CGUISliderControl::RangeSelectorLower); + pControl->SetFloatValue(*(value[1]), CGUISliderControl::RangeSelectorUpper); + if (setting.formatFunction.range) pControl->SetTextValue(setting.formatFunction.range(*(value[0]), *(value[1]), setting.interval)); + } + } if (setting.enabled) { @@ -300,13 +311,13 @@ void CGUIDialogSettings::OnClick(int iID) { CGUISettingsSliderControl *pControl = (CGUISettingsSliderControl *)GetControl(iID); if (setting.data) *(float *)setting.data = pControl->GetFloatValue(); - if (setting.formatFunction) pControl->SetTextValue(setting.formatFunction(pControl->GetFloatValue(), setting.interval)); + if (setting.formatFunction.standard) pControl->SetTextValue(setting.formatFunction.standard(pControl->GetFloatValue(), setting.interval)); } else if (setting.type == SettingInfo::BUTTON && m_usePopupSliders && setting.data) { // we're using popup sliders CGUIDialogSlider::ShowAndGetInput(setting.name, *(float *)setting.data, setting.min, setting.interval, setting.max, this, &setting); - if (setting.formatFunction) - SET_CONTROL_LABEL2(iID, setting.formatFunction(*(float *)setting.data, setting.interval)); + if (setting.formatFunction.standard) + SET_CONTROL_LABEL2(iID, setting.formatFunction.standard(*(float *)setting.data, setting.interval)); } else if (setting.type == SettingInfo::STRING) { @@ -316,6 +327,19 @@ void CGUIDialogSettings::OnClick(int iID) strNewValue = "-"; SET_CONTROL_LABEL2(iID, strNewValue); } + else if (setting.type == SettingInfo::RANGE) + { + CGUISettingsSliderControl *pControl = (CGUISettingsSliderControl *)GetControl(iID); + if (setting.data) + { + *((float **)setting.data)[0] = pControl->GetFloatValue(CGUISliderControl::RangeSelectorLower); + *((float **)setting.data)[1] = pControl->GetFloatValue(CGUISliderControl::RangeSelectorUpper); + } + if (setting.formatFunction.range) + pControl->SetTextValue(setting.formatFunction.range(pControl->GetFloatValue(CGUISliderControl::RangeSelectorLower), + pControl->GetFloatValue(CGUISliderControl::RangeSelectorUpper), + setting.interval)); + } OnSettingChanged(setting); } @@ -346,8 +370,8 @@ void CGUIDialogSettings::AddSetting(SettingInfo &setting, float width, int iCont pControl = new CGUIButtonControl(*m_pOriginalSettingsButton); if (!pControl) return ; ((CGUIButtonControl *)pControl)->SetLabel(setting.name); - if (setting.formatFunction) - ((CGUIButtonControl *)pControl)->SetLabel2(setting.formatFunction(*(float *)setting.data, setting.interval)); + if (setting.formatFunction.standard) + ((CGUIButtonControl *)pControl)->SetLabel2(setting.formatFunction.standard(*(float *)setting.data, setting.interval)); pControl->SetWidth(width); } else if (setting.type == SettingInfo::EDIT && m_pOriginalEdit) @@ -404,14 +428,14 @@ void CGUIDialogSettings::AddSetting(SettingInfo &setting, float width, int iCont if (!pControl) return ; pControl->SetWidth(width); ((CGUISettingsSliderControl *)pControl)->SetText(setting.name); - if (setting.formatFunction) - ((CGUISettingsSliderControl *)pControl)->SetTextValue(setting.formatFunction(*(float *)setting.data, setting.interval)); + if (setting.formatFunction.standard) + ((CGUISettingsSliderControl *)pControl)->SetTextValue(setting.formatFunction.standard(*(float *)setting.data, setting.interval)); ((CGUISettingsSliderControl *)pControl)->SetType(SPIN_CONTROL_TYPE_FLOAT); ((CGUISettingsSliderControl *)pControl)->SetFloatRange(setting.min, setting.max); ((CGUISettingsSliderControl *)pControl)->SetFloatInterval(setting.interval); if (setting.data) ((CGUISettingsSliderControl *)pControl)->SetFloatValue(*(float *)setting.data); } - if (setting.type == SettingInfo::STRING && m_pOriginalSettingsButton) + else if (setting.type == SettingInfo::STRING && m_pOriginalSettingsButton) { pControl = new CGUIButtonControl(*m_pOriginalSettingsButton); if (!pControl) return ; @@ -422,6 +446,25 @@ void CGUIDialogSettings::AddSetting(SettingInfo &setting, float width, int iCont ((CGUIButtonControl *)pControl)->SetLabel2(strValue); pControl->SetWidth(width); } + else if (setting.type == SettingInfo::RANGE) + { + if (!m_pOriginalSlider) return; + pControl = new CGUISettingsSliderControl(*m_pOriginalSlider); + if (!pControl) return ; + pControl->SetWidth(width); + ((CGUISettingsSliderControl *)pControl)->SetText(setting.name); + if (setting.formatFunction.range) + ((CGUISettingsSliderControl *)pControl)->SetTextValue(setting.formatFunction.range(*((float **)setting.data)[0], *((float **)setting.data)[1], setting.interval)); + ((CGUISettingsSliderControl *)pControl)->SetType(SPIN_CONTROL_TYPE_FLOAT); + ((CGUISettingsSliderControl *)pControl)->SetRangeSelection(true); + ((CGUISettingsSliderControl *)pControl)->SetFloatRange(setting.min, setting.max); + ((CGUISettingsSliderControl *)pControl)->SetFloatInterval(setting.interval); + if (setting.data) + { + ((CGUISettingsSliderControl *)pControl)->SetFloatValue(*((float **)setting.data)[0], CGUISliderControl::RangeSelectorLower); + ((CGUISettingsSliderControl *)pControl)->SetFloatValue(*((float **)setting.data)[1], CGUISliderControl::RangeSelectorUpper); + } + } if (!pControl) return; pControl->SetID(iControlID); @@ -469,7 +512,7 @@ void CGUIDialogSettings::AddButton(unsigned int id, int label, float *current, f setting.min = min; setting.max = max; setting.interval = interval; - setting.formatFunction = function; + setting.formatFunction.standard = function; m_settings.push_back(setting); } @@ -583,7 +626,26 @@ void CGUIDialogSettings::AddSlider(unsigned int id, int label, float *current, f setting.interval = interval; setting.max = max; setting.data = current; - setting.formatFunction = function; + setting.formatFunction.standard = function; + m_settings.push_back(setting); +} + +void CGUIDialogSettings::AddRangeSlider(unsigned int id, int label, float *currentLower, float* currentUpper, float min, float interval, float max, RANGEFORMATFUNCTION function) +{ + SettingInfo setting; + setting.id = id; + setting.name = g_localizeStrings.Get(label); + setting.type = SettingInfo::RANGE; + setting.min = min; + setting.interval = interval; + setting.max = max; + + float** data = new float*[2]; + data[0] = currentLower; + data[1] = currentUpper; + setting.data = data; + + setting.formatFunction.range = function; m_settings.push_back(setting); } @@ -612,8 +674,19 @@ void CGUIDialogSettings::OnSliderChange(void *data, CGUISliderControl *slider) return; SettingInfo *setting = (SettingInfo *)data; - *(float *)setting->data = slider->GetFloatValue(); - OnSettingChanged(*setting); - if (setting->formatFunction) - slider->SetTextValue(setting->formatFunction(slider->GetFloatValue(), setting->interval)); + if (setting->type == SettingInfo::SLIDER) + { + *(float *)setting->data = slider->GetFloatValue(); + OnSettingChanged(*setting); + if (setting->formatFunction.standard) + slider->SetTextValue(setting->formatFunction.standard(slider->GetFloatValue(), setting->interval)); + } + else if (setting->type == SettingInfo::RANGE) + { + *((float **)setting->data)[0] = slider->GetFloatValue(CGUISliderControl::RangeSelectorLower); + *((float **)setting->data)[1] = slider->GetFloatValue(CGUISliderControl::RangeSelectorUpper); + OnSettingChanged(*setting); + if (setting->formatFunction.range) + slider->SetTextValue(setting->formatFunction.range(slider->GetFloatValue(CGUISliderControl::RangeSelectorLower), slider->GetFloatValue(CGUISliderControl::RangeSelectorUpper), setting->interval)); + } } diff --git a/xbmc/settings/GUIDialogSettings.h b/xbmc/settings/GUIDialogSettings.h index dc6400f19f..4d89f5128f 100644 --- a/xbmc/settings/GUIDialogSettings.h +++ b/xbmc/settings/GUIDialogSettings.h @@ -29,14 +29,16 @@ class CGUIRadioButtonControl; class CGUISettingsSliderControl; class CGUIEditControl; class CGUIImage; +class CGUIEditControl; typedef std::vector<CStdString> SETTINGSTRINGS; typedef CStdString (*FORMATFUNCTION) (float value, float min); +typedef CStdString (*RANGEFORMATFUNCTION) (float valueLower, float valueUpper, float min); class SettingInfo { public: - enum SETTING_TYPE { NONE=0, EDIT, EDIT_NUM, BUTTON, BUTTON_DIALOG, CHECK, CHECK_UCHAR, SPIN, SLIDER, SEPARATOR, STRING }; + enum SETTING_TYPE { NONE=0, EDIT, EDIT_NUM, BUTTON, BUTTON_DIALOG, CHECK, CHECK_UCHAR, SPIN, SLIDER, SEPARATOR, STRING, RANGE }; SettingInfo() { id = 0; @@ -46,7 +48,7 @@ public: min = 0; max = 0; interval = 0; - formatFunction = NULL; + formatFunction.standard = NULL; }; SETTING_TYPE type; CStdString name; @@ -55,7 +57,11 @@ public: float min; float max; float interval; - FORMATFUNCTION formatFunction; + union + { + FORMATFUNCTION standard; + RANGEFORMATFUNCTION range; + } formatFunction; std::vector<std::pair<int, CStdString> > entry; bool enabled; }; @@ -96,6 +102,7 @@ protected: void AddSpin(unsigned int id, int label, int *current, std::vector<std::pair<int, CStdString> > &values); void AddSpin(unsigned int id, int label, int *current, std::vector<std::pair<int, int> > &values); void AddSlider(unsigned int id, int label, float *current, float min, float interval, float max, FORMATFUNCTION formatFunction, bool allowPopup = true); + void AddRangeSlider(unsigned int id, int label, float *currentLower, float* currentUpper, float min, float interval, float max, RANGEFORMATFUNCTION formatFunction); void AddSeparator(unsigned int id); CGUIEditControl *m_pOriginalEdit; diff --git a/xbmc/utils/URIUtils.cpp b/xbmc/utils/URIUtils.cpp index 8bcd48d595..5419f0da29 100644 --- a/xbmc/utils/URIUtils.cpp +++ b/xbmc/utils/URIUtils.cpp @@ -267,6 +267,22 @@ bool URIUtils::GetParentPath(const CStdString& strPath, CStdString& strParent) strFile = url.GetHostName(); return GetParentPath(strFile, strParent); } + else if ((url.GetProtocol() == "videodb" || url.GetProtocol() == "musicdb") && !url.GetOptions().empty()) + { + CStdString options = url.GetOptions(); + size_t filterStart = options.find("filter="); + if (filterStart != string::npos) + { + size_t filterEnd = options.find("&", filterStart); + options.erase(filterStart, filterEnd - filterStart); + if (options.Equals("?")) + options.clear(); + + url.SetOptions(options); + strParent = url.Get(); + return true; + } + } else if (url.GetProtocol() == "stack") { CStackDirectory dir; diff --git a/xbmc/utils/UrlOptions.cpp b/xbmc/utils/UrlOptions.cpp index c189a089a8..f383fa332d 100644 --- a/xbmc/utils/UrlOptions.cpp +++ b/xbmc/utils/UrlOptions.cpp @@ -28,6 +28,11 @@ using namespace std; CUrlOptions::CUrlOptions() { } +CUrlOptions::CUrlOptions(const std::string &options) +{ + AddOptions(options); +} + CUrlOptions::~CUrlOptions() { } @@ -45,6 +50,14 @@ std::string CUrlOptions::GetOptionsString() const return options; } +void CUrlOptions::AddOption(const std::string &key, const char *value) +{ + if (key.empty() || value == NULL) + return; + + return AddOption(key, string(value)); +} + void CUrlOptions::AddOption(const std::string &key, const std::string &value) { if (key.empty()) @@ -120,3 +133,29 @@ void CUrlOptions::AddOptions(const std::string &options) AddOption(key, value); } } + +void CUrlOptions::AddOptions(const CUrlOptions &options) +{ + m_options.insert(options.m_options.begin(), options.m_options.end()); +} + +bool CUrlOptions::HasOption(const std::string &key) +{ + if (key.empty()) + return false; + + return m_options.find(key) != m_options.end(); +} + +bool CUrlOptions::GetOption(const std::string &key, CVariant &value) +{ + if (key.empty()) + return false; + + UrlOptions::const_iterator option = m_options.find(key); + if (option == m_options.end()) + return false; + + value = option->second; + return true; +} diff --git a/xbmc/utils/UrlOptions.h b/xbmc/utils/UrlOptions.h index 47c6e511ef..b7c0fcbf29 100644 --- a/xbmc/utils/UrlOptions.h +++ b/xbmc/utils/UrlOptions.h @@ -29,17 +29,23 @@ public: typedef std::map<std::string, CVariant> UrlOptions; CUrlOptions(); + CUrlOptions(const std::string &options); virtual ~CUrlOptions(); virtual const UrlOptions& GetOptions() const { return m_options; } virtual std::string GetOptionsString() const; + virtual void AddOption(const std::string &key, const char *value); virtual void AddOption(const std::string &key, const std::string &value); virtual void AddOption(const std::string &key, int value); virtual void AddOption(const std::string &key, float value); virtual void AddOption(const std::string &key, double value); virtual void AddOption(const std::string &key, bool value); virtual void AddOptions(const std::string &options); + virtual void AddOptions(const CUrlOptions &options); + + virtual bool HasOption(const std::string &key); + virtual bool GetOption(const std::string &key, CVariant &value); protected: UrlOptions m_options; diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index fa6c4b7363..227f3f5194 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -4429,22 +4429,22 @@ void CVideoDatabase::EraseVideoSettings() } } -bool CVideoDatabase::GetGenresNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */) +bool CVideoDatabase::GetGenresNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */) { - return GetNavCommon(strBaseDir, items, "genre", idContent, filter); + return GetNavCommon(strBaseDir, items, "genre", idContent, filter, countOnly); } -bool CVideoDatabase::GetCountriesNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */) +bool CVideoDatabase::GetCountriesNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */) { - return GetNavCommon(strBaseDir, items, "country", idContent, filter); + return GetNavCommon(strBaseDir, items, "country", idContent, filter, countOnly); } -bool CVideoDatabase::GetStudiosNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */) +bool CVideoDatabase::GetStudiosNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */) { - return GetNavCommon(strBaseDir, items, "studio", idContent, filter); + return GetNavCommon(strBaseDir, items, "studio", idContent, filter, countOnly); } -bool CVideoDatabase::GetNavCommon(const CStdString& strBaseDir, CFileItemList& items, const CStdString &type, int idContent /* = -1 */, const Filter &filter /* = Filter() */) +bool CVideoDatabase::GetNavCommon(const CStdString& strBaseDir, CFileItemList& items, const CStdString &type, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */) { try { @@ -4457,19 +4457,22 @@ bool CVideoDatabase::GetNavCommon(const CStdString& strBaseDir, CFileItemList& i { if (idContent == VIDEODB_CONTENT_MOVIES) { - strSQL = PrepareSQL("select %s.id%s, %s.str%s, path.strPath, files.playCount from %s ", type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str()); + strSQL = "select %s " + PrepareSQL("from %s ", type.c_str()); + extFilter.fields = PrepareSQL("%s.id%s, %s.str%s, path.strPath, files.playCount", type.c_str(), type.c_str(), type.c_str(), type.c_str()); extFilter.AppendJoin(PrepareSQL("join %slinkmovie on %s.id%s = %slinkmovie.id%s join movieview on %slinkmovie.idMovie = movieview.idMovie join files on files.idFile = movieview.idFile join path on path.idPath = files.idPath", type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str())); } else if (idContent == VIDEODB_CONTENT_TVSHOWS) //this will not get tvshows with 0 episodes { - strSQL = PrepareSQL("select %s.id%s, %s.str%s, path.strPath from %s ", type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str()); + strSQL = "select %s " + PrepareSQL("from %s ", type.c_str()); + extFilter.fields = PrepareSQL("%s.id%s, %s.str%s, path.strPath", type.c_str(), type.c_str(), type.c_str(), type.c_str()); extFilter.AppendJoin(PrepareSQL("join %slinktvshow on %s.id%s = %slinktvshow.id%s join episodeview on %slinktvshow.idShow = episodeview.idShow join files on files.idFile = episodeview.idFile join path on path.idPath = files.idPath", type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str())); } else if (idContent == VIDEODB_CONTENT_MUSICVIDEOS) { - strSQL = PrepareSQL("select %s.id%s, %s.str%s, path.strPath, files.playCount from %s ", type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str()); + strSQL = "select %s " + PrepareSQL("from %s ", type.c_str()); + extFilter.fields = PrepareSQL("%s.id%s, %s.str%s, path.strPath, files.playCount", type.c_str(), type.c_str(), type.c_str(), type.c_str()); extFilter.AppendJoin(PrepareSQL("join %slinkmusicvideo on %s.id%s = %slinkmusicvideo.id%s join musicvideoview on %slinkmusicvideo.idMVideo = musicvideoview.idMVideo join files on files.idFile = musicvideoview.idFile join path on path.idPath = files.idPath", type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str())); } @@ -4480,20 +4483,23 @@ bool CVideoDatabase::GetNavCommon(const CStdString& strBaseDir, CFileItemList& i { if (idContent == VIDEODB_CONTENT_MOVIES) { - strSQL = PrepareSQL("select %s.id%s, %s.str%s, count(1), count(files.playCount) from %s ", type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str()); + strSQL = "select %s " + PrepareSQL("from %s ", type.c_str()); + extFilter.fields = PrepareSQL("%s.id%s, %s.str%s, count(1), count(files.playCount)", type.c_str(), type.c_str(), type.c_str(), type.c_str()); extFilter.AppendJoin(PrepareSQL("join %slinkmovie on %s.id%s = %slinkmovie.id%s join movieview on %slinkmovie.idMovie = movieview.idMovie join files on files.idFile = movieview.idFile", type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str())); extFilter.AppendGroup(PrepareSQL("%s.id%s", type.c_str(), type.c_str())); } else if (idContent == VIDEODB_CONTENT_TVSHOWS) { - strSQL = PrepareSQL("select distinct %s.id%s, %s.str%s from %s ", type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str()); + strSQL = "select %s " + PrepareSQL("from %s ", type.c_str()); + extFilter.fields = PrepareSQL("distinct %s.id%s, %s.str%s", type.c_str(), type.c_str(), type.c_str(), type.c_str()); extFilter.AppendJoin(PrepareSQL("join %slinktvshow on %s.id%s = %slinktvshow.id%s join tvshowview on %slinktvshow.idShow = tvshowview.idShow", type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str())); } else if (idContent == VIDEODB_CONTENT_MUSICVIDEOS) { - strSQL = PrepareSQL("select %s.id%s, %s.str%s, count(1), count(files.playCount) from %s ", type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str()); + strSQL = "select %s " + PrepareSQL("from %s ", type.c_str()); + extFilter.fields = PrepareSQL("%s.id%s, %s.str%s, count(1), count(files.playCount)", type.c_str(), type.c_str(), type.c_str(), type.c_str()); extFilter.AppendJoin(PrepareSQL("join %slinkmusicvideo on %s.id%s = %slinkmusicvideo.id%s join musicvideoview on %slinkmusicvideo.idMVideo = musicvideoview.idMVideo join files on files.idFile = musicvideoview.idFile", type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str())); extFilter.AppendGroup(PrepareSQL("%s.id%s", type.c_str(), type.c_str())); @@ -4502,6 +4508,14 @@ bool CVideoDatabase::GetNavCommon(const CStdString& strBaseDir, CFileItemList& i return false; } + if (countOnly) + { + extFilter.fields = PrepareSQL("COUNT(DISTINCT %s.id%s)", type.c_str(), type.c_str()); + extFilter.group.clear(); + extFilter.order.clear(); + } + strSQL.Format(strSQL.c_str(), !extFilter.fields.empty() ? extFilter.fields.c_str() : "*"); + CVideoDbUrl videoUrl; if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl)) return false; @@ -4510,6 +4524,16 @@ bool CVideoDatabase::GetNavCommon(const CStdString& strBaseDir, CFileItemList& i if (iRowsFound <= 0) return iRowsFound == 0; + if (countOnly) + { + CFileItemPtr pItem(new CFileItem()); + pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound); + items.Add(pItem); + + m_pDS->close(); + return true; + } + if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser) { map<int, pair<CStdString,int> > mapItems; @@ -4591,7 +4615,7 @@ bool CVideoDatabase::GetNavCommon(const CStdString& strBaseDir, CFileItemList& i return false; } -bool CVideoDatabase::GetTagsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */) +bool CVideoDatabase::GetTagsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */) { CStdString mediaType; if (idContent == VIDEODB_CONTENT_MOVIES) @@ -4608,13 +4632,26 @@ bool CVideoDatabase::GetTagsNav(const CStdString& strBaseDir, CFileItemList& ite if (NULL == m_pDB.get()) return false; if (NULL == m_pDS.get()) return false; - CStdString strSQL = "SELECT tag.idTag, tag.strTag FROM taglinks "; + CStdString strSQL = "SELECT %s FROM taglinks "; Filter extFilter = filter; + extFilter.fields = "tag.idTag, tag.strTag"; extFilter.AppendJoin("JOIN tag ON tag.idTag = taglinks.idTag"); + + if (idContent == (int)VIDEODB_CONTENT_MOVIES) + extFilter.AppendJoin("JOIN movieview ON movieview.idMovie = taglinks.idMedia"); + extFilter.AppendWhere(PrepareSQL("taglinks.media_type = '%s'", mediaType.c_str())); extFilter.AppendGroup("taglinks.idTag"); + if (countOnly) + { + extFilter.fields = "COUNT(DISTINCT taglinks.idTag)"; + extFilter.group.clear(); + extFilter.order.clear(); + } + strSQL.Format(strSQL.c_str(), !extFilter.fields.empty() ? extFilter.fields.c_str() : "*"); + // parse the base path to get additional filters CVideoDbUrl videoUrl; if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl)) @@ -4624,6 +4661,16 @@ bool CVideoDatabase::GetTagsNav(const CStdString& strBaseDir, CFileItemList& ite if (iRowsFound <= 0) return iRowsFound == 0; + if (countOnly) + { + CFileItemPtr pItem(new CFileItem()); + pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound); + items.Add(pItem); + + m_pDS->close(); + return true; + } + while (!m_pDS->eof()) { int idTag = m_pDS->fv(0).get_asInt(); @@ -4791,23 +4838,23 @@ bool CVideoDatabase::GetSetsByWhere(const CStdString& strBaseDir, const Filter & return false; } -bool CVideoDatabase::GetMusicVideoAlbumsNav(const CStdString& strBaseDir, CFileItemList& items, int idArtist /* = -1 */, const Filter &filter /* = Filter() */) +bool CVideoDatabase::GetMusicVideoAlbumsNav(const CStdString& strBaseDir, CFileItemList& items, int idArtist /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */) { try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS.get()) return false; - CStdString strSQL; + CStdString strSQL = "select %s from musicvideoview "; Filter extFilter = filter; if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser) { - strSQL = PrepareSQL("select musicvideoview.c%02d, musicvideoview.idMVideo, actors.strActor, path.strPath from musicvideoview ", VIDEODB_ID_MUSICVIDEO_ALBUM); + extFilter.fields = PrepareSQL("musicvideoview.c%02d, musicvideoview.idMVideo, actors.strActor, path.strPath", VIDEODB_ID_MUSICVIDEO_ALBUM); extFilter.AppendJoin("join artistlinkmusicvideo on artistlinkmusicvideo.idMVideo = musicvideoview.idMVideo join actors on actors.idActor = artistlinkmusicvideo.idArtist join files on files.idFile = musicvideoview.idFile join path on path.idPath = files.idPath"); } else { - strSQL = PrepareSQL("select musicvideoview.c%02d, musicvideoview.idMVideo, actors.strActor from musicvideoview ", VIDEODB_ID_MUSICVIDEO_ALBUM); + extFilter.fields = PrepareSQL("musicvideoview.c%02d, musicvideoview.idMVideo, actors.strActor", VIDEODB_ID_MUSICVIDEO_ALBUM); extFilter.AppendJoin("join artistlinkmusicvideo on artistlinkmusicvideo.idMVideo = musicvideoview.idMVideo join actors on actors.idActor = artistlinkmusicvideo.idArtist"); } if (idArtist > -1) @@ -4815,6 +4862,14 @@ bool CVideoDatabase::GetMusicVideoAlbumsNav(const CStdString& strBaseDir, CFileI extFilter.AppendGroup(PrepareSQL("musicvideoview.c%02d", VIDEODB_ID_MUSICVIDEO_ALBUM)); + if (countOnly) + { + extFilter.fields = "COUNT(1)"; + extFilter.group.clear(); + extFilter.order.clear(); + } + strSQL.Format(strSQL.c_str(), !extFilter.fields.empty() ? extFilter.fields.c_str() : "*"); + CVideoDbUrl videoUrl; if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl)) return false; @@ -4823,6 +4878,16 @@ bool CVideoDatabase::GetMusicVideoAlbumsNav(const CStdString& strBaseDir, CFileI if (iRowsFound <= 0) return iRowsFound == 0; + if (countOnly) + { + CFileItemPtr pItem(new CFileItem()); + pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound); + items.Add(pItem); + + m_pDS->close(); + return true; + } + if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser) { map<int, pair<CStdString,CStdString> > mapAlbums; @@ -4900,21 +4965,21 @@ bool CVideoDatabase::GetMusicVideoAlbumsNav(const CStdString& strBaseDir, CFileI return false; } -bool CVideoDatabase::GetWritersNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */) +bool CVideoDatabase::GetWritersNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */) { - return GetPeopleNav(strBaseDir, items, "writer", idContent, filter); + return GetPeopleNav(strBaseDir, items, "writer", idContent, filter, countOnly); } -bool CVideoDatabase::GetDirectorsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */) +bool CVideoDatabase::GetDirectorsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */) { - return GetPeopleNav(strBaseDir, items, "director", idContent, filter); + return GetPeopleNav(strBaseDir, items, "director", idContent, filter, countOnly); } -bool CVideoDatabase::GetActorsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */) +bool CVideoDatabase::GetActorsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */) { - if (GetPeopleNav(strBaseDir, items, (idContent == VIDEODB_CONTENT_MUSICVIDEOS) ? "artist" : "actor", idContent, filter)) + if (GetPeopleNav(strBaseDir, items, (idContent == VIDEODB_CONTENT_MUSICVIDEOS) ? "artist" : "actor", idContent, filter, countOnly)) { // set thumbs - ideally this should be in the normal thumb setting routines - for (int i = 0; i < items.Size(); i++) + for (int i = 0; i < items.Size() && !countOnly; i++) { CFileItemPtr pItem = items[i]; if (idContent == VIDEODB_CONTENT_MUSICVIDEOS) @@ -4927,7 +4992,7 @@ bool CVideoDatabase::GetActorsNav(const CStdString& strBaseDir, CFileItemList& i return false; } -bool CVideoDatabase::GetPeopleNav(const CStdString& strBaseDir, CFileItemList& items, const CStdString &type, int idContent /* = -1 */, const Filter &filter /* = Filter() */) +bool CVideoDatabase::GetPeopleNav(const CStdString& strBaseDir, CFileItemList& items, const CStdString &type, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */) { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS.get()) return false; @@ -4950,25 +5015,29 @@ bool CVideoDatabase::GetPeopleNav(const CStdString& strBaseDir, CFileItemList& i { if (idContent == VIDEODB_CONTENT_MOVIES) { - strSQL = PrepareSQL("select actors.idActor, actors.strActor, actors.strThumb, path.strPath, files.playCount from actors "); + strSQL = "select %s from actors "; + extFilter.fields = "actors.idActor, actors.strActor, actors.strThumb, path.strPath, files.playCount"; extFilter.AppendJoin(PrepareSQL("join %slinkmovie on actors.idActor = %slinkmovie.id%s join movieview on %slinkmovie.idMovie = movieview.idMovie join files on files.idFile = movieview.idFile join path on path.idPath = files.idPath", type.c_str(), type.c_str(), type.c_str(), type.c_str())); } else if (idContent == VIDEODB_CONTENT_TVSHOWS) { - strSQL = PrepareSQL("select actors.idActor, actors.strActor, actors.strThumb, path.strPath from actors "); + strSQL = "select %s from actors "; + extFilter.fields = "actors.idActor, actors.strActor, actors.strThumb, path.strPath"; extFilter.AppendJoin(PrepareSQL("join %slinktvshow on actors.idActor = %slinktvshow.id%s join episodeview on %slinktvshow.idShow = episodeview.idShow join files on files.idFile = episodeview.idFile join path on path.idPath = files.idPath", type.c_str(), type.c_str(), type.c_str(), type.c_str())); } else if (idContent == VIDEODB_CONTENT_EPISODES) { - strSQL = PrepareSQL("select actors.idActor, actors.strActor, actors.strThumb, path.strPath, files.playCount from actors "); + strSQL = "select %s from actors "; + extFilter.fields = "actors.idActor, actors.strActor, actors.strThumb, path.strPath, files.playCount"; extFilter.AppendJoin(PrepareSQL("join %slinkepisode on actors.idActor = %slinkepisode.id%s join episodeview on %slinkepisode.idEpisode = episodeview.idEpisode join files on files.idFile = episodeview.idFile join path on path.idPath = files.idPath", type.c_str(), type.c_str(), type.c_str(), type.c_str())); } else if (idContent == VIDEODB_CONTENT_MUSICVIDEOS) { - strSQL = PrepareSQL("select actors.idActor, actors.strActor, actors.strThumb, path.strPath, files.playCount from actors "); + strSQL = "select %s from actors "; + extFilter.fields = "actors.idActor, actors.strActor, actors.strThumb, path.strPath, files.playCount"; extFilter.AppendJoin(PrepareSQL("join %slinkmusicvideo on actors.idActor = %slinkmusicvideo.id%s join musicvideoview on %slinkmusicvideo.idMVideo = musicvideoview.idMVideo join files on files.idFile = musicvideoview.idFile join path on path.idPath = files.idPath", type.c_str(), type.c_str(), type.c_str(), type.c_str())); } @@ -4979,27 +5048,31 @@ bool CVideoDatabase::GetPeopleNav(const CStdString& strBaseDir, CFileItemList& i { if (idContent == VIDEODB_CONTENT_MOVIES) { - strSQL = PrepareSQL("select actors.idActor, actors.strActor, actors.strThumb, count(1), count(files.playCount) from actors "); + strSQL ="select %s from actors "; + extFilter.fields = "actors.idActor, actors.strActor, actors.strThumb, count(1), count(files.playCount)"; extFilter.AppendJoin(PrepareSQL("join %slinkmovie on actors.idActor = %slinkmovie.id%s join movieview on %slinkmovie.idMovie = movieview.idMovie join files on files.idFile = movieview.idFile", type.c_str(), type.c_str(), type.c_str(), type.c_str())); extFilter.AppendGroup("actors.idActor"); } else if (idContent == VIDEODB_CONTENT_TVSHOWS) { - strSQL = PrepareSQL("select distinct actors.idActor, actors.strActor, actors.strThumb from actors, %slinktvshow, tvshowview ", type.c_str()); + strSQL = "select %s " + PrepareSQL("from actors, %slinktvshow, tvshowview ", type.c_str()); + extFilter.fields = "distinct actors.idActor, actors.strActor, actors.strThumb"; extFilter.AppendWhere(PrepareSQL("actors.idActor = %slinktvshow.id%s and %slinktvshow.idShow = tvshowview.idShow", type.c_str(), type.c_str(), type.c_str())); } else if (idContent == VIDEODB_CONTENT_EPISODES) { - strSQL = PrepareSQL("select actors.idActor, actors.strActor, actors.strThumb, count(1), count(files.playCount) from %slinkepisode, actors, episodeview, files ", type.c_str()); + strSQL = "select %s " + PrepareSQL("%slinkepisode, actors, episodeview, files ", type.c_str()); + extFilter.fields = "actors.idActor, actors.strActor, actors.strThumb, count(1), count(files.playCount)"; extFilter.AppendWhere(PrepareSQL("actors.idActor = %slinkepisode.id%s and %slinkepisode.idEpisode = episodeview.idEpisode and files.idFile = episodeview.idFile", type.c_str(), type.c_str(), type.c_str())); extFilter.AppendGroup("actors.idActor"); } else if (idContent == VIDEODB_CONTENT_MUSICVIDEOS) { - strSQL = PrepareSQL("select actors.idActor, actors.strActor, actors.strThumb, count(1), count(files.playCount) from actors "); + strSQL = "select %s from actors "; + extFilter.fields = "actors.idActor, actors.strActor, actors.strThumb, count(1), count(files.playCount)"; extFilter.AppendJoin(PrepareSQL("join %slinkmusicvideo on actors.idActor = %slinkmusicvideo.id%s join musicvideoview on %slinkmusicvideo.idMVideo = musicvideoview.idMVideo join files on files.idFile = musicvideoview.idFile", type.c_str(), type.c_str(), type.c_str(), type.c_str())); extFilter.AppendGroup("actors.idActor"); @@ -5008,6 +5081,14 @@ bool CVideoDatabase::GetPeopleNav(const CStdString& strBaseDir, CFileItemList& i return false; } + if (countOnly) + { + extFilter.fields = "COUNT(1)"; + extFilter.group.clear(); + extFilter.order.clear(); + } + strSQL.Format(strSQL.c_str(), !extFilter.fields.empty() ? extFilter.fields.c_str() : "*"); + CVideoDbUrl videoUrl; if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl)) return false; @@ -5024,6 +5105,16 @@ bool CVideoDatabase::GetPeopleNav(const CStdString& strBaseDir, CFileItemList& i return true; } + if (countOnly) + { + CFileItemPtr pItem(new CFileItem()); + pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound); + items.Add(pItem); + + m_pDS->close(); + return true; + } + if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser) { map<int, CActor> mapActors; @@ -5477,6 +5568,7 @@ bool CVideoDatabase::GetSeasonsNav(const CStdString& strBaseDir, CFileItemList& } m_pDS->close(); } + items.SetPath(videoUrl.ToString()); // now add any linked movies Filter movieFilter; @@ -5586,6 +5678,12 @@ bool CVideoDatabase::GetMoviesByWhere(const CStdString& strBaseDir, const Filter if (!videoUrl.FromString(strBaseDir) || !GetFilter(videoUrl, extFilter)) return false; + // if we have a "setid" option we don't want to retrieve sets + CVariant setId; + if (fetchSets && videoUrl.GetOption("setid", setId) && + setId.isInteger() && setId.asInteger() > 0) + fetchSets = false; + int total = -1; CStdString strSQL = "select %s from movieview "; @@ -5694,6 +5792,7 @@ bool CVideoDatabase::GetMoviesByWhere(const CStdString& strBaseDir, const Filter items.Add(pItem); } } + items.SetPath(videoUrl.ToString()); // cleanup m_pDS->close(); @@ -5809,6 +5908,7 @@ bool CVideoDatabase::GetTvShowsByWhere(const CStdString& strBaseDir, const Filte } Stack(items, VIDEODB_CONTENT_TVSHOWS, !filter.order.empty() || sortDescription.sortBy != SortByNone); + items.SetPath(videoUrl.ToString()); // cleanup m_pDS->close(); @@ -6106,7 +6206,7 @@ bool CVideoDatabase::GetEpisodesByWhere(const CStdString& strBaseDir, const Filt CVideoDbUrl itemUrl = videoUrl; CStdString path; - if (appendFullShowPath) + if (appendFullShowPath && videoUrl.GetItemType() != "episodes") path.Format("%ld/%ld/%ld", record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_ID).get_asInt(), movie.m_iSeason, idEpisode); else path.Format("%ld", idEpisode); @@ -6119,6 +6219,7 @@ bool CVideoDatabase::GetEpisodesByWhere(const CStdString& strBaseDir, const Filt items.Add(pItem); } } + items.SetPath(videoUrl.ToString()); // cleanup m_pDS->close(); @@ -6959,6 +7060,7 @@ bool CVideoDatabase::GetMusicVideosByWhere(const CStdString &baseDir, const Filt items.Add(item); } } + items.SetPath(videoUrl.ToString()); // cleanup m_pDS->close(); @@ -8879,7 +8981,7 @@ bool CVideoDatabase::GetItemsForPath(const CStdString &content, const CStdString return items.Size() > 0; } -bool CVideoDatabase::GetFilter(const CDbUrl &videoUrl, Filter &filter) +bool CVideoDatabase::GetFilter(CDbUrl &videoUrl, Filter &filter) { if (!videoUrl.IsValid()) return false; @@ -9297,5 +9399,23 @@ bool CVideoDatabase::GetFilter(const CDbUrl &videoUrl, Filter &filter) } } + option = options.find("filter"); + if (option != options.end()) + { + CSmartPlaylist xspFilter; + if (!xspFilter.LoadFromJson(option->second.asString())) + return false; + + // check if the filter playlist matches the item type + if (xspFilter.GetType() == itemType) + { + std::set<CStdString> playlists; + filter.AppendWhere(xspFilter.GetWhereClause(*this, playlists)); + } + // remove the filter if it doesn't match the item type + else + videoUrl.AddOption("filter", ""); + } + return true; } diff --git a/xbmc/video/VideoDatabase.h b/xbmc/video/VideoDatabase.h index 616403a4b1..3dc45c1f6d 100644 --- a/xbmc/video/VideoDatabase.h +++ b/xbmc/video/VideoDatabase.h @@ -578,16 +578,16 @@ public: bool ArbitraryExec(const CStdString& strExec); // general browsing - bool GetGenresNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1, const Filter &filter = Filter()); - bool GetCountriesNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1, const Filter &filter = Filter()); - bool GetStudiosNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1, const Filter &filter = Filter()); + bool GetGenresNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1, const Filter &filter = Filter(), bool countOnly = false); + bool GetCountriesNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1, const Filter &filter = Filter(), bool countOnly = false); + bool GetStudiosNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1, const Filter &filter = Filter(), bool countOnly = false); bool GetYearsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1, const Filter &filter = Filter()); - bool GetActorsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1, const Filter &filter = Filter()); - bool GetDirectorsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1, const Filter &filter = Filter()); - bool GetWritersNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1, const Filter &filter = Filter()); + bool GetActorsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1, const Filter &filter = Filter(), bool countOnly = false); + bool GetDirectorsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1, const Filter &filter = Filter(), bool countOnly = false); + bool GetWritersNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1, const Filter &filter = Filter(), bool countOnly = false); bool GetSetsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1); - bool GetTagsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1, const Filter &filter = Filter()); - bool GetMusicVideoAlbumsNav(const CStdString& strBaseDir, CFileItemList& items, int idArtist, const Filter &filter = Filter()); + bool GetTagsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1, const Filter &filter = Filter(), bool countOnly = false); + bool GetMusicVideoAlbumsNav(const CStdString& strBaseDir, CFileItemList& items, int idArtist, const Filter &filter = Filter(), bool countOnly = false); bool GetMoviesNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre=-1, int idYear=-1, int idActor=-1, int idDirector=-1, int idStudio=-1, int idCountry=-1, int idSet=-1, int idTag=-1, const SortDescription &sortDescription = SortDescription()); bool GetTvShowsNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre=-1, int idYear=-1, int idActor=-1, int idDirector=-1, int idStudio=-1, int idTag=-1, const SortDescription &sortDescription = SortDescription()); @@ -687,7 +687,7 @@ public: void AddTagToItem(int idItem, int idTag, const std::string &type); void RemoveTagFromItem(int idItem, int idTag, const std::string &type); - virtual bool GetFilter(const CDbUrl &videoUrl, Filter &filter); + virtual bool GetFilter(CDbUrl &videoUrl, Filter &filter); protected: int GetMovieId(const CStdString& strFilenameAndPath); @@ -756,8 +756,8 @@ protected: CVideoInfoTag GetDetailsForEpisode(const dbiplus::sql_record* const record, bool needsCast = false); CVideoInfoTag GetDetailsForMusicVideo(std::auto_ptr<dbiplus::Dataset> &pDS); CVideoInfoTag GetDetailsForMusicVideo(const dbiplus::sql_record* const record); - bool GetPeopleNav(const CStdString& strBaseDir, CFileItemList& items, const CStdString& type, int idContent=-1, const Filter &filter = Filter()); - bool GetNavCommon(const CStdString& strBaseDir, CFileItemList& items, const CStdString& type, int idContent=-1, const Filter &filter = Filter()); + bool GetPeopleNav(const CStdString& strBaseDir, CFileItemList& items, const CStdString& type, int idContent=-1, const Filter &filter = Filter(), bool countOnly = false); + bool GetNavCommon(const CStdString& strBaseDir, CFileItemList& items, const CStdString& type, int idContent=-1, const Filter &filter = Filter(), bool countOnly = false); void GetCast(const CStdString &table, const CStdString &table_id, int type_id, std::vector<SActorInfo> &cast); void GetDetailsFromDB(std::auto_ptr<dbiplus::Dataset> &pDS, int min, int max, const SDbTableOffsets *offsets, CVideoInfoTag &details, int idxOffset = 2); diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index e0a8350f76..92b77618ad 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -1783,7 +1783,34 @@ bool CGUIWindowVideoBase::Update(const CStdString &strDirectory) bool CGUIWindowVideoBase::GetDirectory(const CStdString &strDirectory, CFileItemList &items) { - bool bResult = CGUIMediaWindow::GetDirectory(strDirectory,items); + CStdString directory = strDirectory; + + // check if the path contains a filter and if so load it and + // remove it from the path to get proper GUI view states etc + CSmartPlaylist filterXsp; + CVideoDbUrl videoUrl; + if (videoUrl.FromString(strDirectory)) + { + CVariant filter; + if (videoUrl.GetOption("filter", filter)) + { + // load the filter and if it's type does not match the + // path's item type reset it + if (filterXsp.LoadFromJson(filter.asString()) && !filterXsp.GetType().Equals(videoUrl.GetItemType().c_str())) + filterXsp.Reset(); + + // remove the "filter" option from the path + videoUrl.AddOption("filter", ""); + } + directory = videoUrl.ToString(); + } + + bool bResult = CGUIMediaWindow::GetDirectory(directory, items); + + // (re-)apply the previously retrieved filter + // because it was reset in CGUIMediaWindow::GetDirectory() + if (!filterXsp.IsEmpty()) + m_filter = filterXsp; // add in the "New Playlist" item if we're in the playlists folder if ((items.GetPath() == "special://videoplaylists/") && !items.Contains("newplaylist://")) @@ -1809,7 +1836,7 @@ bool CGUIWindowVideoBase::GetDirectory(const CStdString &strDirectory, CFileItem // we may also be in a tvshow files listing // (ideally this should be removed, and our stack regexps tidied up if necessary // No "normal" episodes should stack, and multi-parts should be supported) - ADDON::ScraperPtr info = m_database.GetScraperForPath(strDirectory); + ADDON::ScraperPtr info = m_database.GetScraperForPath(directory); if (info && info->Content() == CONTENT_TVSHOWS) m_stackingAvailable = false; @@ -1830,6 +1857,15 @@ void CGUIWindowVideoBase::OnPrepareFileItems(CFileItemList &items) { } +bool CGUIWindowVideoBase::CheckFilterAdvanced(CFileItemList &items) +{ + CStdString content = items.GetContent(); + if (items.IsVideoDb() && (content.Equals("movies") || content.Equals("tvshows") || content.Equals("episodes") || content.Equals("musicvideos"))) + return true; + + return false; +} + void CGUIWindowVideoBase::AddToDatabase(int iItem) { if (iItem < 0 || iItem >= m_vecItems->Size()) diff --git a/xbmc/video/windows/GUIWindowVideoBase.h b/xbmc/video/windows/GUIWindowVideoBase.h index 9deadafe12..78d94b5822 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.h +++ b/xbmc/video/windows/GUIWindowVideoBase.h @@ -89,6 +89,8 @@ protected: virtual void OnItemLoaded(CFileItem* pItem) {}; virtual void OnPrepareFileItems(CFileItemList &items); + virtual bool CheckFilterAdvanced(CFileItemList &items); + virtual void GetContextButtons(int itemNumber, CContextButtons &buttons); void GetNonContextButtons(int itemNumber, CContextButtons &buttons); virtual bool OnContextButton(int itemNumber, CONTEXT_BUTTON button); diff --git a/xbmc/video/windows/GUIWindowVideoNav.cpp b/xbmc/video/windows/GUIWindowVideoNav.cpp index c28348d064..eac61e05a3 100644 --- a/xbmc/video/windows/GUIWindowVideoNav.cpp +++ b/xbmc/video/windows/GUIWindowVideoNav.cpp @@ -527,8 +527,24 @@ void CGUIWindowVideoNav::UpdateButtons() bool CGUIWindowVideoNav::GetFilteredItems(const CStdString &filter, CFileItemList &items) { - bool listchanged = CGUIMediaWindow::GetFilteredItems(filter, items); + bool listchanged = false; + bool updateItems = false; + if (!m_canFilterAdvanced) + listchanged = CGUIMediaWindow::GetFilteredItems(filter, items); + else + listchanged = CGUIMediaWindow::GetAdvanceFilteredItems(items, updateItems); + listchanged |= ApplyWatchedFilter(items); + + // there are new items so we need to run the thumbloader + if (updateItems) + { + if (m_thumbLoader.IsLoading()) + m_thumbLoader.StopThread(); + + m_thumbLoader.Load(items); + } + return listchanged; } diff --git a/xbmc/windows/GUIMediaWindow.cpp b/xbmc/windows/GUIMediaWindow.cpp index 6cbe81c22b..25f791a8c3 100644 --- a/xbmc/windows/GUIMediaWindow.cpp +++ b/xbmc/windows/GUIMediaWindow.cpp @@ -64,16 +64,18 @@ #include "interfaces/python/XBPython.h" #endif #include "interfaces/Builtins.h" +#include "dialogs/GUIDialogMediaFilter.h" +#include "filesystem/SmartPlaylistDirectory.h" #if defined(TARGET_ANDROID) #include "xbmc/android/activity/XBMCApp.h" #endif -#define CONTROL_BTNVIEWASICONS 2 -#define CONTROL_BTNSORTBY 3 -#define CONTROL_BTNSORTASC 4 -#define CONTROL_BTN_FILTER 19 +#define CONTROL_BTNVIEWASICONS 2 +#define CONTROL_BTNSORTBY 3 +#define CONTROL_BTNSORTASC 4 +#define CONTROL_BTN_FILTER 19 -#define CONTROL_LABELFILES 12 +#define CONTROL_LABELFILES 12 using namespace std; using namespace ADDON; @@ -87,6 +89,7 @@ CGUIMediaWindow::CGUIMediaWindow(int id, const char *xmlFile) m_vecItems->SetPath("?"); m_iLastControl = -1; m_iSelectedItem = -1; + m_canFilterAdvanced = false; m_guiState.reset(CGUIViewState::GetViewState(GetID(), *m_vecItems)); } @@ -175,6 +178,9 @@ bool CGUIMediaWindow::OnAction(const CAction &action) if (CGUIWindow::OnAction(action)) return true; + if (action.GetID() == ACTION_FILTER) + return Filter(); + // live filtering if (action.GetID() == ACTION_FILTER_CLEAR) { @@ -226,6 +232,13 @@ bool CGUIMediaWindow::OnMessage(CGUIMessage& message) CGUIDialogContextMenu* pDlg = (CGUIDialogContextMenu*)g_windowManager.GetWindow(WINDOW_DIALOG_CONTEXT_MENU); if (pDlg && pDlg->IsActive()) pDlg->Close(); + + // get rid of any active filtering + if (m_canFilterAdvanced) + { + m_canFilterAdvanced = false; + m_filter.Reset(); + } // Call ClearFileItems() after our window has finished doing any WindowClose // animations @@ -273,22 +286,10 @@ bool CGUIMediaWindow::OnMessage(CGUIMessage& message) } else if (iControl == CONTROL_BTN_FILTER) { - if (GetControl(iControl)->GetControlType() == CGUIControl::GUICONTROL_EDIT) - { // filter updated - CGUIMessage selected(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_BTN_FILTER); - OnMessage(selected); - OnFilterItems(selected.GetLabel()); + if (m_canFilterAdvanced) return true; - } - if (GetProperty("filter").empty()) - { - CStdString filter = GetProperty("filter").asString(); - CGUIKeyboardFactory::ShowAndGetFilter(filter, false); - SetProperty("filter", filter); - } - else - OnFilterItems(""); - return true; + + return Filter(); } else if (m_viewControl.HasControl(iControl)) // list/thumb control { @@ -415,16 +416,21 @@ bool CGUIMediaWindow::OnMessage(CGUIMessage& message) } else if (message.GetParam1() == GUI_MSG_FILTER_ITEMS && IsActive()) { - CStdString filter(GetProperty("filter").asString()); - if (message.GetParam2() == 1) // append - filter += message.GetStringParam(); - else if (message.GetParam2() == 2) - { // delete - if (filter.size()) - filter = filter.Left(filter.size() - 1); + CStdString filter; + // check if this is meant for advanced filtering + if (message.GetParam2() != 10) + { + filter = GetProperty("filter").asString(); + if (message.GetParam2() == 1) // append + filter += message.GetStringParam(); + else if (message.GetParam2() == 2) + { // delete + if (filter.size()) + filter = filter.Left(filter.size() - 1); + } + else + filter = message.GetStringParam(); } - else - filter = message.GetStringParam(); OnFilterItems(filter); return true; } @@ -561,10 +567,8 @@ void CGUIMediaWindow::UpdateButtons() items.Format("%i %s", m_vecItems->GetObjectCount(), g_localizeStrings.Get(127).c_str()); SET_CONTROL_LABEL(CONTROL_LABELFILES, items); - //#ifdef PRE_SKIN_VERSION_3 - SET_CONTROL_SELECTED(GetID(),CONTROL_BTN_FILTER, !GetProperty("filter").empty()); - SET_CONTROL_LABEL2(CONTROL_BTN_FILTER, GetProperty("filter").asString()); - //#endif + if (!m_canFilterAdvanced) + SET_CONTROL_LABEL2(CONTROL_BTN_FILTER, GetProperty("filter").asString()); } void CGUIMediaWindow::ClearFileItems() @@ -637,7 +641,7 @@ bool CGUIMediaWindow::GetDirectory(const CStdString &strDirectory, CFileItemList if (items.Size()) items.Clear(); - CStdString strParentPath=m_history.GetParentPath(); + CStdString strParentPath = m_history.GetParentPath(); CLog::Log(LOGDEBUG,"CGUIMediaWindow::GetDirectory (%s)", strDirectory.c_str()); CLog::Log(LOGDEBUG," ParentPath = [%s]", strParentPath.c_str()); @@ -700,6 +704,8 @@ bool CGUIMediaWindow::GetDirectory(const CStdString &strDirectory, CFileItemList // clear the filter SetProperty("filter", ""); + m_canFilterAdvanced = false; + m_filter.Reset(); return true; } @@ -723,10 +729,10 @@ bool CGUIMediaWindow::Update(const CStdString &strDirectory) GetDirectoryHistoryString(pItem.get(), strSelectedItem); } } + + CStdString strCurrentDirectory = m_vecItems->GetPath(); - CStdString strOldDirectory = m_vecItems->GetPath(); - - m_history.SetSelectedItem(strSelectedItem, strOldDirectory); + m_history.SetSelectedItem(strSelectedItem, strCurrentDirectory); CFileItemList items; if (!GetDirectory(strDirectory, items)) @@ -734,7 +740,7 @@ bool CGUIMediaWindow::Update(const CStdString &strDirectory) CLog::Log(LOGERROR,"CGUIMediaWindow::GetDirectory(%s) failed", strDirectory.c_str()); // if the directory is the same as the old directory, then we'll return // false. Else, we assume we can get the previous directory - if (strDirectory.Equals(strOldDirectory)) + if (strDirectory.Equals(strCurrentDirectory)) return false; // We assume, we can get the parent @@ -781,6 +787,11 @@ bool CGUIMediaWindow::Update(const CStdString &strDirectory) } m_iLastControl = GetFocusedControlID(); + // Check whether to enabled advanced filtering based on the content type + m_canFilterAdvanced = CheckFilterAdvanced(*m_vecItems); + if (m_canFilterAdvanced) + m_filter.SetType(m_vecItems->GetContent()); + // Ask the derived class if it wants to load additional info // for the fileitems like media info or additional // filtering on the items, setting thumbs. @@ -943,6 +954,13 @@ bool CGUIMediaWindow::OnClick(int iItem) if (!items.AlwaysCache()) items.RemoveDiscCache(GetID()); + // if we have a filtered list, we need to add the filtered + // path to be able to come back to the filtered view + CStdString strCurrentDirectory = m_vecItems->GetPath(); + if (m_canFilterAdvanced && !m_filter.IsEmpty() && + !m_unfilteredItems->GetPath().Equals(strCurrentDirectory)) + m_history.AddPath(strCurrentDirectory); + CFileItem directory(*pItem); if (!Update(directory.GetPath())) ShowShareErrorMessage(&directory); @@ -1095,7 +1113,6 @@ void CGUIMediaWindow::GoParentFolder() // if vector is not empty, pop parent // if vector is empty, parent is root source listing - CStdString strOldPath(m_vecItems->GetPath()); strParent = m_history.RemoveParentPath(); Update(strParent); } @@ -1543,7 +1560,34 @@ void CGUIMediaWindow::OnFilterItems(const CStdString &filter) { m_vecItems->ClearItems(); m_vecItems->Append(items); - SetProperty("filter", filter); + + if (!m_canFilterAdvanced) + SetProperty("filter", filter); + else + { + m_vecItems->SetPath(items.GetPath()); + + // to be able to select the same item as before we need to adjust + // the path of the item i.e. add or remove the "filter=" URL option + CURL curUrl(currentItem), newUrl(items.GetPath()); + CUrlOptions curOptions(curUrl.GetOptions()), newOptions(newUrl.GetOptions()); + + if (newOptions.HasOption("filter")) + { + CVariant filter; + if (newOptions.GetOption("filter", filter) && filter.isString()) + curOptions.AddOption("filter", filter.asString()); + } + else if (curOptions.HasOption("filter")) + curOptions.AddOption("filter", ""); + + string options = curOptions.GetOptionsString(); + if (!options.empty()) + curUrl.SetOptions("?" + options); + else + curUrl.SetOptions(""); + currentItem = curUrl.Get(); + } } // and update our view control + buttons @@ -1554,6 +1598,12 @@ void CGUIMediaWindow::OnFilterItems(const CStdString &filter) bool CGUIMediaWindow::GetFilteredItems(const CStdString &filter, CFileItemList &items) { + if (m_canFilterAdvanced) + { + bool hasNewItems; + return GetAdvanceFilteredItems(items, hasNewItems); + } + CStdString trimmedFilter(filter); trimmedFilter.TrimLeft().ToLower(); @@ -1593,7 +1643,112 @@ bool CGUIMediaWindow::GetFilteredItems(const CStdString &filter, CFileItemList & items.ClearItems(); items.Append(filteredItems); - return (items.GetObjectCount() > 0); + + return items.GetObjectCount() > 0; +} + +bool CGUIMediaWindow::GetAdvanceFilteredItems(CFileItemList &items, bool &hasNewItems) +{ + hasNewItems = false; + + CFileItemList resultItems; + XFILE::CSmartPlaylistDirectory::GetDirectory(m_filter, resultItems, m_vecItems->GetPath(), true); + + // put together a lookup map for faster path comparison + map<CStdString, CFileItemPtr> lookup; + for (int j = 0; j < resultItems.Size(); j++) + { + CStdString itemPath = resultItems[j]->GetPath(); + size_t pos = itemPath.find('?'); + if (pos != string::npos) + itemPath.erase(pos); + itemPath.ToLower(); + + lookup[itemPath] = resultItems[j]; + } + + // loop through all the original items and find + // those which are still part of the filter + CFileItemList filteredItems; + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items.Get(i); + if (item->IsParentFolder()) + { + filteredItems.Add(item); + continue; + } + + // check if the item is part of the resultItems list + // by comparing their paths (but ignoring any special + // options because they differ from filter to filter) + CStdString path = item->GetPath(); + size_t pos = path.find('?'); + if (pos != string::npos) + path.erase(pos); + path.ToLower(); + + map<CStdString, CFileItemPtr>::iterator itItem = lookup.find(path); + if (itItem != lookup.end()) + { + // we need to copy the path of the filtered + // item to be able to keep the applied filters + item->SetPath(itItem->second->GetPath()); + + // add the item to the list of filtered items + filteredItems.Add(item); + + // remove the item from the lists + resultItems.Remove(itItem->second.get()); + lookup.erase(itItem); + } + } + + if (resultItems.Size() > 0) + { + filteredItems.Append(resultItems); + hasNewItems = true; + } + + items.ClearItems(); + items.Append(filteredItems); + items.SetPath(resultItems.GetPath()); + return true; +} + +bool CGUIMediaWindow::IsFiltered() +{ + return (!m_canFilterAdvanced && !GetProperty("filter").empty()) || + (m_canFilterAdvanced && !m_filter.IsEmpty()); +} + +bool CGUIMediaWindow::Filter() +{ + // basic filtering + if (!m_canFilterAdvanced) + { + const CGUIControl *btnFilter = GetControl(CONTROL_BTN_FILTER); + if (btnFilter != NULL && btnFilter->GetControlType() == CGUIControl::GUICONTROL_EDIT) + { // filter updated + CGUIMessage selected(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_BTN_FILTER); + OnMessage(selected); + OnFilterItems(selected.GetLabel()); + return true; + } + if (GetProperty("filter").empty()) + { + CStdString filter = GetProperty("filter").asString(); + CGUIKeyboardFactory::ShowAndGetFilter(filter, false); + SetProperty("filter", filter); + } + else + OnFilterItems(""); + } + // advanced filtering + else + CGUIDialogMediaFilter::ShowAndEditMediaFilter(m_vecItems->GetPath(), m_filter); + + return true; } CStdString CGUIMediaWindow::GetStartFolder(const CStdString &dir) diff --git a/xbmc/windows/GUIMediaWindow.h b/xbmc/windows/GUIMediaWindow.h index 4421aff442..8dc7521e0a 100644 --- a/xbmc/windows/GUIMediaWindow.h +++ b/xbmc/windows/GUIMediaWindow.h @@ -25,6 +25,7 @@ #include "filesystem/DirectoryHistory.h" #include "GUIViewControl.h" #include "dialogs/GUIDialogContextMenu.h" +#include "playlists/SmartPlayList.h" class CFileItemList; @@ -47,6 +48,9 @@ public: virtual CFileItemPtr GetCurrentListItem(int offset = 0); const CGUIViewState *GetViewState() const; + virtual bool CanFilterAdvanced() { return m_canFilterAdvanced; } + virtual bool IsFiltered(); + protected: virtual void LoadAdditionalTags(TiXmlElement *root); CGUIControl *GetFirstFocusableControl(int id); @@ -73,6 +77,9 @@ protected: void ClearFileItems(); virtual void SortItems(CFileItemList &items); + virtual bool CheckFilterAdvanced(CFileItemList &items) { return false; } + virtual bool Filter(); + /* \brief Called on response to a GUI_MSG_FILTER_ITEMS message Filters the current list with the given filter using FilterItems() \param filter the filter to use. @@ -87,6 +94,14 @@ protected: */ virtual bool GetFilteredItems(const CStdString &filter, CFileItemList &items); + /* \brief Retrieve the advance filtered item list + \param items CFileItemList to filter + \param hasNewItems Whether the filtered item list contains new items + which were not present in the original list + \sa GetFilteredItems + */ + virtual bool GetAdvanceFilteredItems(CFileItemList &items, bool &hasNewItems); + // check for a disc or connection virtual bool HaveDiscOrConnection(const CStdString& strPath, int iDriveType); void ShowShareErrorMessage(CFileItem* pItem); @@ -121,4 +136,7 @@ protected: int m_iLastControl; int m_iSelectedItem; CStdString m_startDirectory; + + CSmartPlaylist m_filter; + bool m_canFilterAdvanced; }; |