├── .gitattributes ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── assets ├── html │ ├── config.html │ ├── confirm.html │ ├── input.html │ ├── input.notUsed │ ├── inputApply.notUsed │ ├── messageBox.html │ ├── styles10.css │ └── styles7.css ├── images │ ├── noArtist │ │ ├── Blue.png │ │ ├── Dark 1.png │ │ ├── Grey 1.png │ │ ├── Grey 2.png │ │ ├── Grey 3.png │ │ ├── Grey 4.png │ │ ├── Grey 5.png │ │ ├── Grey 6.png │ │ └── small │ │ │ ├── Blue.png │ │ │ ├── Dark 1.png │ │ │ ├── Grey 1.png │ │ │ ├── Grey 2.png │ │ │ ├── Grey 3.png │ │ │ ├── Grey 4.png │ │ │ ├── Grey 5.png │ │ │ └── Grey 6.png │ ├── noCover │ │ ├── CD 1.png │ │ ├── CD 2.png │ │ ├── CD 3.png │ │ ├── Dark 1.png │ │ ├── Dark 2.png │ │ ├── Dark 3.png │ │ ├── Grey 1.jpg │ │ ├── Grey 2.png │ │ ├── Grey 3.png │ │ ├── Light.png │ │ ├── Multi-coloured 1.png │ │ ├── Multi-coloured 2.png │ │ └── small │ │ │ ├── CD 1.png │ │ │ ├── CD 2.png │ │ │ ├── CD 3.png │ │ │ ├── Dark 1.png │ │ │ ├── Dark 2.png │ │ │ ├── Dark 3.png │ │ │ ├── Grey 1.jpg │ │ │ ├── Grey 2.png │ │ │ ├── Grey 3.png │ │ │ ├── Light.png │ │ │ ├── Multi-coloured 1.png │ │ │ └── Multi-coloured 2.png │ └── root │ │ ├── Black 1.png │ │ ├── Blue 1.png │ │ ├── Blue 2.png │ │ ├── Collage.png │ │ ├── Crystal.png │ │ ├── Glow.png │ │ ├── Green 1.png │ │ ├── Green 2.png │ │ ├── Grey 1.png │ │ ├── Grey 2.png │ │ ├── Grey 3.png │ │ ├── Grey 4.png │ │ ├── Multi-coloured 1.png │ │ ├── Multi-coloured 2.png │ │ ├── Multi-coloured 3.png │ │ ├── Purple 1.png │ │ ├── Purple 2.png │ │ ├── Red 1.png │ │ ├── Red 2.png │ │ ├── Red 3.png │ │ └── small │ │ ├── Black 1.png │ │ ├── Blue 1.png │ │ ├── Blue 2.png │ │ ├── Collage.png │ │ ├── Crystal.png │ │ ├── Glow.png │ │ ├── Green 1.png │ │ ├── Green 2.png │ │ ├── Grey 1.png │ │ ├── Grey 2.png │ │ ├── Grey 3.png │ │ ├── Grey 4.png │ │ ├── Multi-coloured 1.png │ │ ├── Multi-coloured 2.png │ │ ├── Multi-coloured 3.png │ │ ├── Purple 1.png │ │ ├── Purple 2.png │ │ ├── Red 1.png │ │ ├── Red 2.png │ │ └── Red 3.png └── licences │ └── licences.txt ├── main.js ├── package.json └── scripts ├── buttons.js ├── callbacks.js ├── helpers.js ├── images.js ├── initialise.js ├── interface.js ├── library.js ├── menu.js ├── panel.js ├── populate.js ├── popupbox.js ├── properties.js ├── scrollbar.js ├── search.js ├── timers.js └── utils.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v2.4.0 2 | 3 | ### Added 4 | - **New facets / multi-panel mode** (for guidance on set-up, see help in options > views tab) 5 | - each panel can contain a facet, tree view or album art 6 | - panels needn't be contiguous 7 | - no longer restricted to two panels 8 | - **BREAKING change**: if two panel mode previously used, source panel name now has to be entered (menu > source > select source panel) 9 | 10 | - **Mode presets** (options > behaviour tab) 11 | - Browser 12 | - keeps playing playlist 13 | - easy to browse track lists without interfering with playing playlist, e.g. in album art or facet modes 14 | - works best with playback follows cursor OFF & playlist visible 15 | - double-click status bar to view playing playlist (custom themes may have a different method) 16 | - Player 17 | - play and manage without a playlist 18 | - easy management options 19 | - Default 20 | - free choice as before 21 | 22 | - **Statistics** (menu) 23 | - bitrate 24 | - duration 25 | - total size 26 | - rating 27 | - popularity 28 | - date 29 | - playback queue 30 | - playcount 31 | - first played 32 | - last played 33 | - date added 34 | 35 | - **Album art drop shadow** (options > album art > thumbnail drop shadow) 36 | 37 | - **$selected** 38 | - for use in filters 39 | - works like $nowplaying except applies to selected (focused) track 40 | - example, artist IS $selected{$meta(artist,0)} displays tracks by selected artist. 41 | 42 | - **Before search memory option**: original state shows on clearing search (options > behaviour tab) 43 | 44 | - **Shortcut keys**: more added to search box: ctrl+backspace etc 45 | 46 | ### Changed 47 | - Library initialisation: 48 | - optimisations for foobar2000 v2 49 | - no longer initialises for invisible panels, waits until panel shown 50 | - Jump search improved: repeat presses of same letter advance position 51 | - Albumart: reinstated ability to set number of index characters that show on scrollbar drag (album art tab) 52 | - Follow selection (playlist) option now applies to tree and facet views as well as album art (setting now in behaviour tab). Uses foobar2000 preferences > display > selection viewers setting 53 | - Made calculating durations more efficient 54 | 55 | ### Fixed 56 | - various minor draw and alignment issues 57 | 58 |
59 | 60 | # v2.3.4 61 | 62 | ### Added 63 | - Some compatibility fixes for foobar2000 v2.0 64 | 65 | ### Changed 66 | - Arrow & triangle node styles reworked for improved look on foobar2000 v2.0. The previous styles, now called arrows light and triangles light, can be set on the display tab\node style 67 | 68 |
69 | 70 | # v2.3.3 71 | 72 | ### Added 73 | - Display of item durations for tree and album art views (enable on display tab) 74 | - Intelligent group level sort by year 75 | - this automatically adjusts the view pattern to enable sorting in different ways, e.g. to enable reverse sorting $nodisplay snipetts are inserted into the view pattern. This is reversible by selecting the default sort provided the view isn't manually edited to alter the snippetts 76 | - Wine: font check 77 | 78 | ### Fixed 79 | - Search text was difficult to read with certain theme and colour combinations 80 | 81 |
82 | 83 | # v2.3.2 84 | 85 | ### Added 86 | - Album art view: hover frame can now be displayed as an image border (display tab) 87 | - Menu configure that opens Spider Monkey Configuration (right click + shift) 88 | 89 | ### Changed 90 | - Settings are now always added to the context menu if shift is pressed 91 | 92 | ### Fixed 93 | - Occasional issues with the options dialog not opening due to the feature checker wrongly reporting Spider Monkey Panel Show HTML Dialog as unsupported. In such cases there is now a confirm to guard against false negatives. Additionally, there is a manual setting in the first panel property 94 | - Minor colour selection and draw issues 95 | 96 |
97 | 98 | # v2.3.1 99 | 100 | ### Fixed 101 | Issue with getting album art index character 102 | 103 | ### Added 104 | Checks to test if ShowHtmlDialog is supported, with fallback to an alternative where possible (thx to regor) 105 | 106 |
107 | 108 | # v2.3.0 109 | 110 | ### Changed 111 | - Album art index character(s) that show on scrollbar drag are now configurable: 112 | - No limit to number, e.g. can be single letter up to full name (album art tab) 113 | - Year can be autodetected and 4 digits shown, if number above is less than 4 (album art tab) 114 | - Improved horizontal flow mode metrics 115 | - Removed the ability to auto-update from old versions (v2.1.3 or earlier) 116 | 117 | ### Added 118 | - New clean preset with alternative selection model and new node style (menu\quicksetup) 119 | - info: alternative selection model is automatically used if selection\full line is enabled (behaviour tab) & hover effect (background) is set to none (display tab). It works best with highlight text on hover enabled (display tab) 120 | - New random colour dark theme (display tab) 121 | - Ability to show source at root, e.g. library, playlist name, panel etc (display tab). Default off. Useful if you don't use a constant source 122 | - Option to customise the name of 'Library Tree Panel Selection' playlist (panel properties: 'Playlist: Panel Selection'): it's used to save the contents of the 2nd panel between foobar2000 restarts. 123 | - Hover effect: tree / album art: option to always use mouse pointer (no hand) (display tab) 124 | 125 | ### Fixed 126 | - Album art: draw issue with dark overlay labels (compact/grid mode) 127 | - Keystroke navigation irregularities 128 | - No display sorting of albums starting with numbers 129 | - plman.GetPlaylistLockedActions(playlistIndex) error when playlistIndex is invalid 130 | - Lines sometimes overdrawing expanded [-] square icon 131 | - Rare library update crash 132 | - Wine stabilisation: library tree should no longer give errors in Wine, but some limitations remain: 133 | - copy & paste into search box may not work. It can be fixed by installing [this version of Spider Monkey Panel (v1.6.2-dev+7c0928bf)](https://github.com/Wil-B/Find-and-Play/files/8575143/foo_spider_monkey_panel.zip) which includes marc2k3's utils.GetClipboardText/utils.SetClipboardText (thx to marc2k3) 134 | - recycler is unlikely to work: it's used when refreshing images: windows explorer can be used to remove images from the cache instead 135 | - options dialog may not load: menu now indicates there was a problem & console explains what can be done instead 136 | - Miscellaneous fixes 137 | 138 |
139 | 140 | # v2.2.0 141 | ### Added 142 | - Album art 143 | - Playlist source 144 | - Two panel mode (optional) 145 | - Album art flow mode 146 | - '¦' soft splitter that combines different fields into same branch level, e.g. 147 | `%album artist%¦%artist% or %%¦%%` 148 | - New options dialog 149 | 150 | ### Changed 151 | - Send to new playlist now checks playlist lock status 152 | - Smooth scroll: enhanced smoothness when using scrollbar 153 | - Refactored code 154 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | License 2 | 3 | Copyright (c) 2021-2023 WilB 4 | 5 | The above copyright notice shall be included in all 6 | copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 9 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 10 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 11 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 12 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 13 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 14 | SOFTWARE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Library Tree 2 | 3 | Feature rich library viewer and browser for [foobar2000](https://www.foobar2000.org). 4 | 5 | ### FEATURES 6 | - Tree viewer 7 | - Album art browser 8 | - New facets 9 | - Statistics 10 | - Album art flow mode 11 | - Library and playlist sources 12 | - Single panel and multiple panel modes 13 | - Mode presets 14 | - browser: keep playing playlist 15 | - player: play without a playlist 16 | - default: choice of all actions 17 | 18 | ### REQUIREMENTS: 19 | - [foobar2000](https://www.foobar2000.org) 20 | - [Spider Monkey Panel 1.5.2+](https://www.foobar2000.org/components) 21 | - IE8 or later 22 | - [FontAwesome](https://github.com/FortAwesome/Font-Awesome/blob/fa-4/fonts/fontawesome-webfont.ttf?raw=true) 23 | - Segoe UI Symbol 24 | - should be present on most windows systems 25 | - if you're on win7 and symbols don't render properly you can download an updated version that includes newer symbols [here](https://www.stephanpringle.com/corrupted-segoe-ui-symbol-font/) 26 | 27 | ### INSTALLATION 28 | Install as a package as follows. 29 | 30 | New install or update: 31 | 1) Add a spider monkey panel to foobar2000 if required 32 | 2) Close any instances of windows explorer using foobar2000 folders or subfolders 33 | 3) Right click the spider monkey panel while pressing the windows key + shift 34 | 4) Choose configure panel 35 | 5) On the script tab ensure package is selected 36 | 6) Open package manager if it doesn't open automatically 37 | 7) Import the package 38 | 39 | Tip: check out Quick setup for a flavour of capabilities 40 | 41 | ### SUPPORT 42 | The official discussion thread for Library Tree is located at [HydrogenAudio](https://hydrogenaud.io/index.php?topic=111060.0) and that's a great place to go for questions and other support issues. 43 | 44 | ## Screenshots 45 | 46 | #### Two panel mode with artist images and covers 47 | 48 | The screenshot is using the dark theme and columns UI with dividing splitter hidden. Left pane: quick setup: artist photos (labels right). Right pane: quick setup: album covers (labels bottom) 49 | 50 | #### Flow mode (upper) and tree modes (lower) 51 | 52 | Tree modes shows various node styles with, left to right: user interface theme; dark theme; blend theme; album art background 53 | 54 | #### Two panel mode with alphabet index and covers 55 | 56 | To set up the above, position two Spider Monkey Panels side by side. Add library tree to each. The screenshot is using the dark theme (display tab) and columns UI with the dividing splitter hidden. 57 | - Right panel: set source to panel & follow instructions on pop-up 58 | - Left panel: on display tab, tick 'List view (tree)'. Use a view pattern something like: 59 | ``` 60 | $cut(%artist%,1)|%artist%|$if2(%album%,εXtra)|[[%discnumber%.]%tracknumber%. ][%track artist% - ]%title% 61 | ``` 62 | 63 | #### Dark mode colours (left and right) + album art background (middle) 64 | 65 | - LEFT: Quick setup: covers (labels right) 66 | - MIDDLE: Tree with jump search and cover as background. Setup: display tab > theme > cover and adjust cover opacity according to taste 67 | - RIGHT: Tree with item durations, item counts and sort menu. Quick setup: ultra modern 68 | - Display of durations can be enabled for any tree or album art view on the display tab 69 | 70 | ### Credits 71 | - Original Jscript library search (2013): thanhdat1710 72 | - Original JS smooth browser design (2015): Br3tt (aka falstaff) 73 | - [TT-ReBORN](https://github.com/TT-ReBORN) for clean preset inspiration and collaborative effort with new sort code 74 | -------------------------------------------------------------------------------- /assets/html/confirm.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 |
27 | 76 | 77 | -------------------------------------------------------------------------------- /assets/html/input.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 21 | 22 | 23 |
24 |
25 |
26 | 27 | 28 |
29 | 87 | 88 | -------------------------------------------------------------------------------- /assets/html/input.notUsed: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 21 | 22 | 23 |
24 |
25 |
26 | 27 | 28 |
29 | 82 | 83 | -------------------------------------------------------------------------------- /assets/html/inputApply.notUsed: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 21 | 22 | 23 |
24 |
25 |
26 | 27 | 28 | 29 |
30 | 86 | 87 | -------------------------------------------------------------------------------- /assets/html/messageBox.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 18 | Panel and Playlist Sources 19 | 20 | 21 |
Panel source
22 |
This enables a multiple panel mode that supports a New Facets style. Panels can also contain a tree view or album art. 23 | 24 | See help on the "Views" tab for setup guidance.
25 |
Playlist source
26 |
Active playlist 27 | The sort order of the playlist is used, except for multi-value tag or soft splitter views where sorting is necessary. Items are highlighted in the playlist, not sent. 28 | 29 | Other playlists 30 | These behave the same as the library source.
31 |
32 |
33 |
34 | 94 | 95 | -------------------------------------------------------------------------------- /assets/html/styles10.css: -------------------------------------------------------------------------------- 1 | body {color:WindowText; background-color:Menu; font:caption;} 2 | input {font:caption; border:1px solid #7A7A7A; width:100%;} 3 | input:focus {outline:none !important; border:1px solid #0078D7;} 4 | input:hover:focus {outline:none !important; border:1px solid #0078D7;} 5 | input:hover {outline:none !important; border:1px solid #000000;} 6 | label {font:caption;} 7 | button {font:caption; background:#E1E1E1; color:ButtonText; border:1px solid #ADADAD; margin:5px; padding:3px; width:auto; min-width:70px; display:inline-block;} 8 | button:focus {outline:none !important; border:2px solid #0078D7; padding:2px;} 9 | button:hover {background:#e5f1fb; outline:none !important; border:1px solid #0078D7; padding:3px;}div {overflow:hidden;} 10 | button:focus:hover {background:#e5f1fb; outline:none !important; border:2px solid #0078D7; padding:2px;} span {display:block; overflow:hidden; padding-right:10px;} 11 | button[disabled] {background:#CCCCCC; color:#EBEBE4;} 12 | /* --Suppress button:hover manually, since not() is not working =( */ 13 | button[disabled]:hover {border:1px solid #ADADAD; padding:3px;} -------------------------------------------------------------------------------- /assets/html/styles7.css: -------------------------------------------------------------------------------- 1 | body {color:WindowText; background-color:Menu; font:caption;} 2 | input {font:caption; border:1px solid #7A7A7A; width:100%;} 3 | input:focus {outline:none !important; border:1px solid #0078D7;} 4 | input:hover:focus {outline:none !important; border:1px solid #0078D7;} 5 | input:hover {outline:none !important; border:1px solid #000000;} 6 | label {font:caption;} 7 | button {font:caption; background:#E1E1E1; color:ButtonText; border:1px solid #ADADAD; margin:5px; padding:3px; width:auto; min-width:70px; display:inline-block;} 8 | button:focus {border:1px solid #0078D7; padding:3px;} 9 | button:hover {background:#e5f1fb; border:1px solid #0078D7; padding:3px;} 10 | button:focus:hover {background:#e5f1fb; border:1px solid #0078D7; padding:3px;} 11 | button[disabled] {background:#CCCCCC; color:#EBEBE4;} 12 | /* --Suppress button:hover manually, since not() is not working =( */ 13 | button[disabled]:hover {border:1px solid #ADADAD; padding:3px;} -------------------------------------------------------------------------------- /assets/images/noArtist/Blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noArtist/Blue.png -------------------------------------------------------------------------------- /assets/images/noArtist/Dark 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noArtist/Dark 1.png -------------------------------------------------------------------------------- /assets/images/noArtist/Grey 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noArtist/Grey 1.png -------------------------------------------------------------------------------- /assets/images/noArtist/Grey 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noArtist/Grey 2.png -------------------------------------------------------------------------------- /assets/images/noArtist/Grey 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noArtist/Grey 3.png -------------------------------------------------------------------------------- /assets/images/noArtist/Grey 4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noArtist/Grey 4.png -------------------------------------------------------------------------------- /assets/images/noArtist/Grey 5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noArtist/Grey 5.png -------------------------------------------------------------------------------- /assets/images/noArtist/Grey 6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noArtist/Grey 6.png -------------------------------------------------------------------------------- /assets/images/noArtist/small/Blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noArtist/small/Blue.png -------------------------------------------------------------------------------- /assets/images/noArtist/small/Dark 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noArtist/small/Dark 1.png -------------------------------------------------------------------------------- /assets/images/noArtist/small/Grey 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noArtist/small/Grey 1.png -------------------------------------------------------------------------------- /assets/images/noArtist/small/Grey 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noArtist/small/Grey 2.png -------------------------------------------------------------------------------- /assets/images/noArtist/small/Grey 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noArtist/small/Grey 3.png -------------------------------------------------------------------------------- /assets/images/noArtist/small/Grey 4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noArtist/small/Grey 4.png -------------------------------------------------------------------------------- /assets/images/noArtist/small/Grey 5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noArtist/small/Grey 5.png -------------------------------------------------------------------------------- /assets/images/noArtist/small/Grey 6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noArtist/small/Grey 6.png -------------------------------------------------------------------------------- /assets/images/noCover/CD 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/CD 1.png -------------------------------------------------------------------------------- /assets/images/noCover/CD 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/CD 2.png -------------------------------------------------------------------------------- /assets/images/noCover/CD 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/CD 3.png -------------------------------------------------------------------------------- /assets/images/noCover/Dark 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/Dark 1.png -------------------------------------------------------------------------------- /assets/images/noCover/Dark 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/Dark 2.png -------------------------------------------------------------------------------- /assets/images/noCover/Dark 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/Dark 3.png -------------------------------------------------------------------------------- /assets/images/noCover/Grey 1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/Grey 1.jpg -------------------------------------------------------------------------------- /assets/images/noCover/Grey 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/Grey 2.png -------------------------------------------------------------------------------- /assets/images/noCover/Grey 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/Grey 3.png -------------------------------------------------------------------------------- /assets/images/noCover/Light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/Light.png -------------------------------------------------------------------------------- /assets/images/noCover/Multi-coloured 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/Multi-coloured 1.png -------------------------------------------------------------------------------- /assets/images/noCover/Multi-coloured 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/Multi-coloured 2.png -------------------------------------------------------------------------------- /assets/images/noCover/small/CD 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/small/CD 1.png -------------------------------------------------------------------------------- /assets/images/noCover/small/CD 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/small/CD 2.png -------------------------------------------------------------------------------- /assets/images/noCover/small/CD 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/small/CD 3.png -------------------------------------------------------------------------------- /assets/images/noCover/small/Dark 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/small/Dark 1.png -------------------------------------------------------------------------------- /assets/images/noCover/small/Dark 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/small/Dark 2.png -------------------------------------------------------------------------------- /assets/images/noCover/small/Dark 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/small/Dark 3.png -------------------------------------------------------------------------------- /assets/images/noCover/small/Grey 1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/small/Grey 1.jpg -------------------------------------------------------------------------------- /assets/images/noCover/small/Grey 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/small/Grey 2.png -------------------------------------------------------------------------------- /assets/images/noCover/small/Grey 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/small/Grey 3.png -------------------------------------------------------------------------------- /assets/images/noCover/small/Light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/small/Light.png -------------------------------------------------------------------------------- /assets/images/noCover/small/Multi-coloured 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/small/Multi-coloured 1.png -------------------------------------------------------------------------------- /assets/images/noCover/small/Multi-coloured 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/noCover/small/Multi-coloured 2.png -------------------------------------------------------------------------------- /assets/images/root/Black 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Black 1.png -------------------------------------------------------------------------------- /assets/images/root/Blue 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Blue 1.png -------------------------------------------------------------------------------- /assets/images/root/Blue 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Blue 2.png -------------------------------------------------------------------------------- /assets/images/root/Collage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Collage.png -------------------------------------------------------------------------------- /assets/images/root/Crystal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Crystal.png -------------------------------------------------------------------------------- /assets/images/root/Glow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Glow.png -------------------------------------------------------------------------------- /assets/images/root/Green 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Green 1.png -------------------------------------------------------------------------------- /assets/images/root/Green 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Green 2.png -------------------------------------------------------------------------------- /assets/images/root/Grey 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Grey 1.png -------------------------------------------------------------------------------- /assets/images/root/Grey 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Grey 2.png -------------------------------------------------------------------------------- /assets/images/root/Grey 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Grey 3.png -------------------------------------------------------------------------------- /assets/images/root/Grey 4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Grey 4.png -------------------------------------------------------------------------------- /assets/images/root/Multi-coloured 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Multi-coloured 1.png -------------------------------------------------------------------------------- /assets/images/root/Multi-coloured 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Multi-coloured 2.png -------------------------------------------------------------------------------- /assets/images/root/Multi-coloured 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Multi-coloured 3.png -------------------------------------------------------------------------------- /assets/images/root/Purple 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Purple 1.png -------------------------------------------------------------------------------- /assets/images/root/Purple 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Purple 2.png -------------------------------------------------------------------------------- /assets/images/root/Red 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Red 1.png -------------------------------------------------------------------------------- /assets/images/root/Red 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Red 2.png -------------------------------------------------------------------------------- /assets/images/root/Red 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/Red 3.png -------------------------------------------------------------------------------- /assets/images/root/small/Black 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Black 1.png -------------------------------------------------------------------------------- /assets/images/root/small/Blue 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Blue 1.png -------------------------------------------------------------------------------- /assets/images/root/small/Blue 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Blue 2.png -------------------------------------------------------------------------------- /assets/images/root/small/Collage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Collage.png -------------------------------------------------------------------------------- /assets/images/root/small/Crystal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Crystal.png -------------------------------------------------------------------------------- /assets/images/root/small/Glow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Glow.png -------------------------------------------------------------------------------- /assets/images/root/small/Green 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Green 1.png -------------------------------------------------------------------------------- /assets/images/root/small/Green 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Green 2.png -------------------------------------------------------------------------------- /assets/images/root/small/Grey 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Grey 1.png -------------------------------------------------------------------------------- /assets/images/root/small/Grey 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Grey 2.png -------------------------------------------------------------------------------- /assets/images/root/small/Grey 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Grey 3.png -------------------------------------------------------------------------------- /assets/images/root/small/Grey 4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Grey 4.png -------------------------------------------------------------------------------- /assets/images/root/small/Multi-coloured 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Multi-coloured 1.png -------------------------------------------------------------------------------- /assets/images/root/small/Multi-coloured 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Multi-coloured 2.png -------------------------------------------------------------------------------- /assets/images/root/small/Multi-coloured 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Multi-coloured 3.png -------------------------------------------------------------------------------- /assets/images/root/small/Purple 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Purple 1.png -------------------------------------------------------------------------------- /assets/images/root/small/Purple 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Purple 2.png -------------------------------------------------------------------------------- /assets/images/root/small/Red 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Red 1.png -------------------------------------------------------------------------------- /assets/images/root/small/Red 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Red 2.png -------------------------------------------------------------------------------- /assets/images/root/small/Red 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wil-B/Library-Tree/e78b7e1d003c3507ff8ef97872cc6edc4d874ec8/assets/images/root/small/Red 3.png -------------------------------------------------------------------------------- /assets/licences/licences.txt: -------------------------------------------------------------------------------- 1 | ========== YaMD5 ========== 2 | 3 | The MIT License (MIT) 4 | 5 | Copyright (C) 2014 Raymond Hill 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS INTHE SOFTWARE. -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof my_utils === 'undefined') include('utils.js'); 4 | 5 | const loadAsync = window.GetProperty('Load Library Tree Asynchronously', true); 6 | 7 | async function readFiles(files) { 8 | for (const file of files) { 9 | if (window.ID) { // fix pss issue 10 | await include(my_utils.getScriptPath + file); 11 | } 12 | } 13 | } 14 | 15 | const files = [ 16 | 'helpers.js', 17 | 'properties.js', 18 | 'interface.js', 19 | 'panel.js', 20 | 'scrollbar.js', 21 | 'library.js', 22 | 'populate.js', 23 | 'search.js', 24 | 'buttons.js', 25 | 'popupbox.js', 26 | 'timers.js', 27 | 'menu.js', 28 | 'initialise.js', 29 | 'images.js', 30 | 'callbacks.js' 31 | ]; 32 | 33 | if (loadAsync) { 34 | readFiles(files).then(() => { 35 | if (!window.ID) return; // fix pss issue 36 | on_size(); 37 | window.Repaint(); 38 | }); 39 | } else { 40 | files.forEach(v => include(my_utils.getScriptPath + v)); 41 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "WilB", 3 | "description": "Feature rich media library viewer for foobar2000.\r\n\r\n• Tree viewer\r\n• Album art browser\r\n• New facets\r\n• Statistics\r\n• Album art flow mode\r\n\r\nFor guidance on setting up new facets / multiple panel mode, see help on views tab\r\n\r\nCredits\r\n\r\n• Original Jscript library search (2013): thanhdat1710\r\n• Original JS smooth browser design (2015): Br3tt (aka falstaff)", 4 | "enableDragDrop": true, 5 | "id": "{E85C9EF0-778B-46DD-AF20-F4BE831360DD}", 6 | "name": "Library Tree", 7 | "shouldGrabFocus": true, 8 | "version": "2.4.0" 9 | } -------------------------------------------------------------------------------- /scripts/buttons.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Buttons { 4 | constructor() { 5 | this.alpha = 255; 6 | this.arc = 1; 7 | this.cur = null; 8 | this.Dn = false; 9 | this.hoverArea = 4; 10 | this.hot_h = 4; 11 | this.margin = Math.max(ppt.margin * 2 + 2, 12) / 4; 12 | this.trace = false; 13 | this.transition; 14 | this.vertical = true; 15 | 16 | this.b = {} 17 | this.btns = {} 18 | this.s = {} 19 | 20 | this.cross = { 21 | hover: null, 22 | normal: null 23 | } 24 | 25 | this.q = { 26 | s_img: null 27 | } 28 | 29 | this.scr = { 30 | bg: null, 31 | btns: ['scrollUp', 'scrollDn'], 32 | iconFontName: 'Segoe UI Symbol', 33 | iconFontStyle: 0, 34 | img: null, 35 | opaque: ui.getOpaque(), 36 | pad: $.clamp(ppt.sbarButPad / 100, -0.5, 0.3) 37 | } 38 | 39 | this.tooltip = { 40 | delay: true, 41 | show: true, 42 | start: Date.now() - 2000 43 | } 44 | 45 | this.setSbarIcon(); 46 | this.createImages(); 47 | } 48 | 49 | // Methods 50 | 51 | checkScrollBtns(x, y, hover_btn) { 52 | if (sbar.timer_but) { 53 | if ((this.btns['scrollUp'].down || this.btns['scrollDn'].down) && !this.btns['scrollUp'].trace(x, y) && !this.btns['scrollDn'].trace(x, y)) { 54 | this.btns['scrollUp'].cs('normal'); 55 | this.btns['scrollDn'].cs('normal'); 56 | clearTimeout(sbar.timer_but); 57 | sbar.timer_but = false; 58 | sbar.count = -1; 59 | } 60 | } else if (hover_btn) this.scr.btns.forEach(v => { 61 | if (hover_btn.name == v && hover_btn.down) { 62 | this.btns[v].cs('down'); 63 | hover_btn.l_dn(); 64 | } 65 | }); 66 | } 67 | 68 | clear() { 69 | this.Dn = false; 70 | Object.values(this.btns).forEach(v => v.down = false); 71 | } 72 | 73 | createImages() { 74 | let sz = this.scr.arrow == 0 ? Math.max(Math.round(ui.sbar.but_h * 1.666667), 1) : 100; 75 | const sc = sz / 100; 76 | const iconFont = gdi.Font(this.scr.iconFontName, sz, this.scr.iconFontStyle); 77 | this.alpha = !ui.sbar.col ? [75, 192, 228] : [68, 153, 255]; 78 | const hovAlpha = (!ui.sbar.col ? 75 : (!ui.sbar.type ? 68 : 51)) * 0.4; 79 | this.scr.hover = !ui.sbar.col ? RGBA(ui.col.t, ui.col.t, ui.col.t, hovAlpha) : ui.col.text & RGBA(255, 255, 255, hovAlpha); 80 | this.q.s_img = $.gr(100, 100, true, g => { 81 | g.SetSmoothingMode(2); 82 | g.DrawLine(59, 59, 90, 90, 10, !ui.id.local ? ui.col.txt_box_h : ui.col.txt_box); 83 | g.DrawEllipse(10, 10, 54, 54, 10, !ui.id.local ? ui.col.txt_box_h : ui.col.txt_box); 84 | g.FillEllipse(16, 16, 42, 42, 0x0AFAFAFA); 85 | g.SetSmoothingMode(0); 86 | }); 87 | this.q.s_img.RotateFlip(4); 88 | this.scr.img = $.gr(sz, sz, true, g => { 89 | g.SetTextRenderingHint(3); 90 | g.SetSmoothingMode(2); 91 | if (ui.sbar.col) { 92 | this.scr.arrow == 0 ? g.FillPolygon(ui.col.text, 1, [50 * sc, 0, 100 * sc, 76 * sc, 0, 76 * sc]) : g.DrawString(this.scr.arrow, iconFont, ui.col.text, 0, sz * this.scr.pad, sz, sz, StringFormat(1, 1)); 93 | } else { 94 | this.scr.arrow == 0 ? g.FillPolygon(RGBA(ui.col.t, ui.col.t, ui.col.t, 255), 1, [50 * sc, 0, 100 * sc, 76 * sc, 0, 76 * sc]) : 95 | g.DrawString(this.scr.arrow, iconFont, RGBA(ui.col.t, ui.col.t, ui.col.t, 255), 0, sz * this.scr.pad, sz, sz, StringFormat(1, 1)); 96 | } 97 | g.SetSmoothingMode(0); 98 | }); 99 | this.scr.bg = $.gr(sz, sz, true, g => { 100 | g.SetTextRenderingHint(3); 101 | g.SetSmoothingMode(2); 102 | if (ui.sbar.col) { 103 | this.scr.arrow == 0 ? g.FillPolygon(ui.col.bg, 1, [50 * sc, 0, 100 * sc, 76 * sc, 0, 76 * sc]) : g.DrawString(this.scr.arrow, iconFont, ui.col.bg, 0, sz * this.scr.pad, sz, sz, StringFormat(1, 1)); 104 | } else { 105 | this.scr.arrow == 0 ? g.FillPolygon(ui.col.bg, 1, [50 * sc, 0, 100 * sc, 76 * sc, 0, 76 * sc]) : 106 | g.DrawString(this.scr.arrow, iconFont, ui.col.bg, 0, sz * this.scr.pad, sz, sz, StringFormat(1, 1)); 107 | } 108 | g.SetSmoothingMode(0); 109 | }); 110 | sz = 100; 111 | this.cross.normal = $.gr(sz, sz, true, g => { 112 | g.SetTextRenderingHint(3); 113 | g.SetSmoothingMode(2); 114 | let nn = 31; 115 | let offset1 = 12; 116 | let offset2 = 2; 117 | g.DrawLine(offset1, nn - offset2, 100 - nn * 2 + offset1, 100 - nn - offset2, 5, !ui.id.local ? ui.col.txt_box_h : ui.col.txt_box); 118 | g.DrawLine(offset1, 100 - nn - offset2, 100 - nn * 2 + offset1, nn - offset2, 5, !ui.id.local ? ui.col.txt_box_h : ui.col.txt_box); 119 | g.SetSmoothingMode(0); 120 | }); 121 | this.cross.hover = $.gr(sz, sz, true, g => { 122 | g.SetTextRenderingHint(3); 123 | g.SetSmoothingMode(2); 124 | let nn = 28; 125 | let offset1 = 9; 126 | let offset2 = 2; 127 | g.DrawLine(offset1, nn - offset2, 100 - nn * 2 + offset1, 100 - nn - offset2, 5, !ui.id.local ? ui.col.txt_box_h : ui.col.txt_box); 128 | g.DrawLine(offset1, 100 - nn - offset2, 100 - nn * 2 + offset1, nn - offset2, 5, !ui.id.local ? ui.col.txt_box_h : ui.col.txt_box); 129 | g.SetSmoothingMode(0); 130 | }); 131 | } 132 | 133 | draw(gr) { 134 | Object.values(this.btns).forEach(v => { 135 | if (!v.hide) v.draw(gr); 136 | }); 137 | } 138 | 139 | lbtn_dn(x, y) { 140 | this.move(x, y); 141 | if (!this.cur || this.cur.hide) { 142 | this.Dn = false; 143 | return false 144 | } else this.Dn = this.cur.name; 145 | this.cur.down = true; 146 | this.cur.cs('down'); 147 | this.cur.lbtn_dn(x, y); 148 | return true; 149 | } 150 | 151 | lbtn_up(x, y) { 152 | if (!this.cur || this.cur.hide || this.Dn != this.cur.name) { 153 | this.clear(); 154 | return false; 155 | } 156 | this.clear(); 157 | if (this.cur.trace(x, y)) this.cur.cs('hover'); 158 | this.cur.lbtn_up(x, y); 159 | return true; 160 | } 161 | 162 | leave() { 163 | if (this.cur) { 164 | this.cur.cs('normal'); 165 | if (!this.cur.hide) this.transition.start(); 166 | } 167 | this.cur = null; 168 | } 169 | 170 | move(x, y) { 171 | const hover_btn = Object.values(this.btns).find(v => { 172 | if (!this.Dn || this.Dn == v.name) return v.trace(x, y); 173 | }); 174 | let hand = false; 175 | this.checkScrollBtns(x, y, hover_btn); 176 | if (hover_btn) { 177 | hand = hover_btn.hand; 178 | } 179 | pop.hand = hand; 180 | if (hover_btn && hover_btn.hide) { 181 | if (this.cur) { 182 | this.cur.cs('normal'); 183 | this.transition.start(); 184 | } 185 | this.cur = null; 186 | return null; 187 | } // btn hidden, ignore 188 | if (this.cur === hover_btn) return this.cur; 189 | if (this.cur) { 190 | this.cur.cs('normal'); 191 | this.transition.start(); 192 | } // return prev btn to normal state 193 | if (hover_btn && !(hover_btn.down && hover_btn.type < 4)) { 194 | hover_btn.cs('hover'); 195 | if (this.tooltip.show && hover_btn.tiptext) hover_btn.tt.show(hover_btn.tiptext()); 196 | this.transition.start(); 197 | } 198 | this.cur = hover_btn; 199 | return this.cur; 200 | } 201 | 202 | on_script_unload() { 203 | this.tt(''); 204 | } 205 | 206 | reset() { 207 | this.transition.stop(); 208 | } 209 | 210 | setSbarIcon() { 211 | switch (ppt.sbarButType) { 212 | case 0: 213 | this.scr.iconFontName = 'Segoe UI Symbol'; 214 | this.scr.iconFontStyle = 0; 215 | if (!ui.sbar.type) { 216 | this.scr.arrow = ui.sbar.but_w < Math.round(14 * $.scale) ? '\uE018' : '\uE0A0'; 217 | this.scr.pad = ui.sbar.but_w < Math.round(15 * $.scale) ? -0.3 : -0.22; 218 | } else { 219 | this.scr.arrow = ui.sbar.but_w < Math.round(14 * $.scale) ? '\uE018' : '\uE0A0'; 220 | this.scr.pad = ui.sbar.but_w < Math.round(14 * $.scale) ? -0.26 : -0.22; 221 | } 222 | break; 223 | case 1: 224 | this.scr.arrow = 0; 225 | break; 226 | case 2: 227 | this.scr.iconFontName = ppt.butCustIconFont; 228 | this.scr.iconFontStyle = 0; 229 | this.scr.arrow = ppt.arrowSymbol.charAt().trim(); 230 | if (!this.scr.arrow.length) this.scr.arrow = 0; 231 | this.scr.pad = $.clamp(ppt.sbarButPad / 100, -0.5, 0.3); 232 | break; 233 | } 234 | } 235 | 236 | setScrollBtnsHide(set, autoHide) { 237 | if (autoHide) { 238 | this.scr.btns.forEach(v => { 239 | if (this.btns[v]) this.btns[v].hide = set; 240 | }); 241 | panel.treePaint(); 242 | } else { 243 | if (!this.btns || (!ppt.sbarShow && !set)) return; 244 | this.scr.btns.forEach(v => { 245 | if (this.btns[v]) this.btns[v].hide = sbar.scrollable_lines < 1 || !ppt.sbarShow; 246 | }); 247 | } 248 | } 249 | 250 | setSearchBtnsHide() { 251 | const noShow = !ppt.searchShow; 252 | const searching = (ppt.filterShow || ppt.settingsShow) && panel.search.txt; 253 | const o1 = this.btns.s_img; 254 | if (o1) o1.hide = noShow || searching; 255 | const o2 = this.btns.cross2; 256 | if (o2) o2.hide = noShow || !searching; 257 | } 258 | 259 | refresh(upd) { 260 | if (upd) { 261 | this.b.x = ui.w - ui.sz.marginSearch - Math.round(ui.row.h * 0.59); 262 | this.b.h = ui.row.h; 263 | this.b.y = Math.round((panel.search.sp - this.b.h * 0.4) / 2 - this.b.h * 0.27); 264 | this.scr.opaque = ui.getOpaque(); 265 | this.vertical = !panel.imgView || img.style.vertical; 266 | switch (true) { 267 | case this.vertical: 268 | this.scr.x1 = panel.sbar_x; 269 | this.scr.yUp1 = sbar.y; 270 | this.scr.yDn1 = sbar.y + sbar.h - ui.sbar.but_h; 271 | if (ui.sbar.type != 2) { 272 | this.scr.x1 -= 1; 273 | this.scr.hotOffset = this.scr.yUp1 - panel.search.h; 274 | this.scr.x2 = (ui.sbar.but_h - ui.sbar.but_w) / 2; 275 | this.scr.yUp2 = -ui.sbar.arrowPad + this.scr.yUp1 + (ui.sbar.but_h - 1 - ui.sbar.but_w) / 2; 276 | this.scr.yDn2 = ui.sbar.arrowPad + this.scr.yDn1 + (ui.sbar.but_h - 1 - ui.sbar.but_w) / 2; 277 | } 278 | break; 279 | case !this.vertical: 280 | this.scr.x1 = sbar.x; 281 | this.scr.x3 = sbar.x + sbar.w - ui.sbar.but_h; 282 | this.scr.xLeft1 = sbar.x; 283 | this.scr.xRight1 = sbar.x + sbar.w - ui.sbar.but_h; 284 | this.scr.y1 = panel.sbar_y; 285 | if (ui.sbar.type != 2) { 286 | this.scr.y1 -= 1; 287 | this.scr.hotOffset = this.scr.xLeft1 - 0; 288 | this.scr.y2 = (ui.sbar.but_h - ui.sbar.but_w) / 2; 289 | this.scr.xLeft2 = -ui.sbar.arrowPad + this.scr.xLeft1 + (ui.sbar.but_h - 1 - ui.sbar.but_w) / 2; 290 | this.scr.xRight2 = ui.sbar.arrowPad + this.scr.xRight1 + (ui.sbar.but_h - 1 - ui.sbar.but_w) / 2; 291 | } 292 | break; 293 | } 294 | 295 | 296 | this.q.x = ui.sz.marginSearch; 297 | this.q.y = (panel.search.sp - ui.row.h * 0.6) / 2 + (ui.row.h - ui.font.main.Size) % 2; 298 | this.q.h = ui.row.h * 0.6; 299 | this.hoverArea = Math.round(panel.search.sp / 8); 300 | this.hot_h = Math.max(panel.search.sp - this.hoverArea * 2, 4); 301 | this.margin = Math.max(ppt.margin * 2 + 2, 12) / 4; 302 | this.arc = Math.max(Math.round(Math.min(panel.search.sp - this.hoverArea * 2, panel.settings.w + this.margin / 2) / 4), 1); 303 | this.s.w1 = panel.settings.w + but.margin; 304 | this.s.w2 = ui.w - ui.sz.marginSearch - 1 + panel.settings.offset; 305 | this.s.x = this.s.w2 - panel.settings.w - but.margin / 2; 306 | } 307 | if (ppt.sbarShow) { 308 | switch (ui.sbar.type) { 309 | case 2: 310 | switch (true) { 311 | case this.vertical: 312 | this.btns.scrollUp = new Btn(this.scr.x1, this.scr.yUp1, ui.sbar.but_h, ui.sbar.but_h, 3, '', '', '', { 313 | normal: 1, 314 | hover: 2, 315 | down: 3 316 | }, ppt.sbarShow == 1 && sbar.narrow.show || sbar.scrollable_lines < 1, () => sbar.but(1), '', '', false, 'scrollUp'); 317 | this.btns.scrollDn = new Btn(this.scr.x1, this.scr.yDn1, ui.sbar.but_h, ui.sbar.but_h, 3, '', '', '', { 318 | normal: 5, 319 | hover: 6, 320 | down: 7 321 | }, ppt.sbarShow == 1 && sbar.narrow.show || sbar.scrollable_lines < 1, () => sbar.but(-1), '', '', false, 'scrollDn'); 322 | break; 323 | case !this.vertical: 324 | this.btns.scrollUp = new Btn(this.scr.xLeft1, this.scr.y1, ui.sbar.but_h, ui.sbar.but_h, 3, '', '', '', { 325 | normal: 9, 326 | hover: 10, 327 | down: 11 328 | }, ppt.sbarShow == 1 && sbar.narrow.show || sbar.scrollable_lines < 1, () => sbar.but(1), '', '', false, 'scrollUp'); 329 | this.btns.scrollDn = new Btn(this.scr.xRight1, this.scr.y1, ui.sbar.but_h, ui.sbar.but_h, 3, '', '', '', { 330 | normal: 13, 331 | hover: 14, 332 | down: 15 333 | }, ppt.sbarShow == 1 && sbar.narrow.show || sbar.scrollable_lines < 1, () => sbar.but(-1), '', '', false, 'scrollDn'); 334 | break; 335 | } 336 | break; 337 | default: 338 | switch (true) { 339 | case this.vertical: 340 | this.btns.scrollUp = new Btn(this.scr.x1, this.scr.yUp1 - this.scr.hotOffset, ui.sbar.but_h, ui.sbar.but_h + this.scr.hotOffset, 1, this.scr.x2, this.scr.yUp2, ui.sbar.but_w, '', ppt.sbarShow == 1 && sbar.narrow.show || sbar.scrollable_lines < 1, () => sbar.but(1), '', '', false, 'scrollUp'); 341 | this.btns.scrollDn = new Btn(this.scr.x1, this.scr.yDn1, ui.sbar.but_h, ui.sbar.but_h + this.scr.hotOffset, 2, this.scr.x2, this.scr.yDn2, ui.sbar.but_w, '', ppt.sbarShow == 1 && sbar.narrow.show || sbar.scrollable_lines < 1, () => sbar.but(-1), '', '', false, 'scrollDn'); 342 | break; 343 | case !this.vertical: 344 | this.btns.scrollUp = new Btn(this.scr.xLeft1 - this.scr.hotOffset, this.scr.y1, ui.sbar.but_h, ui.sbar.but_h + this.scr.hotOffset, 1, this.scr.y2, this.scr.xLeft2, ui.sbar.but_w, '', ppt.sbarShow == 1 && sbar.narrow.show || sbar.scrollable_lines < 1, () => sbar.but(1), '', '', false, 'scrollUp'); 345 | this.btns.scrollDn = new Btn(this.scr.xRight1, this.scr.y1, ui.sbar.but_h, ui.sbar.but_h + this.scr.hotOffset, 2, this.scr.y2, this.scr.xRight2, ui.sbar.but_w, '', ppt.sbarShow == 1 && sbar.narrow.show || sbar.scrollable_lines < 1, () => sbar.but(-1), '', '', false, 'scrollDn'); 346 | break; 347 | } 348 | break; 349 | } 350 | } 351 | this.transition = new Transition(this.btns, v => v.state !== 'normal'); 352 | this.btns.s_img = new Btn(this.q.x - this.margin / 2, this.hoverArea, this.q.h + this.margin, this.hot_h, 4, this.q.x, this.q.y, this.q.h, { 353 | normal: this.q.s_img 354 | }, false, '', () => sMenu.load(this.q.x - this.margin / 2, panel.search.h), () => 'History and query syntax help. Ctrl+E focuses search', true, 's_img'); 355 | 356 | this.btns.cross2 = new Btn(this.q.x - this.margin / 2, this.hoverArea, this.q.h + this.margin, this.hot_h, 5, this.q.x, this.b.y, this.b.h, { 357 | normal: this.cross.normal, 358 | hover: this.cross.hover 359 | }, true, '', () => search.clear(), () => panel.search.txt ? 'Clear search text (escape). Double click to show history' : 'No search text to clear', true, 'cross2'); 360 | this.btns.filter = new Btn(ppt.searchShow ? panel.filter.x + this.margin / 2 : panel.filter.x - this.margin / 2, 0, ppt.searchShow ? panel.filter.w - this.margin : panel.filter.w + this.margin, panel.search.sp, 6, panel.filter.x, ppt.searchShow ? panel.cc : panel.lc, panel.filter.w, { 361 | normal: ui.col.txt_box, 362 | hover: !ui.id.local ? (!ui.img.blurDark ? ui.col.txt_box_h : ui.col.text) : ui.col.txt_box 363 | }, !ppt.filterShow, '', () => fMenu.load(panel.filter.x, panel.search.h), () => 'Filter', true, 'filter'); 364 | 365 | this.btns.settings = new Btn(this.s.x, panel.settings.offset, this.s.w1, panel.search.sp, 7, this.s.w2, panel.search.sp, panel.settings.y, { 366 | normal: ui.col.txt_box, 367 | hover: !ui.id.local ? (!ui.img.blurDark ? ui.col.txt_box_h : ui.col.text) : ui.col.txt_box 368 | }, !ppt.settingsShow, '', () => men.rbtn_up(this.s.x, panel.search.h, true), () => 'Settings', true, 'settings'); 369 | 370 | this.btns.cross1 = new Btn(this.b.x - this.margin / 2, this.hoverArea, this.q.h + this.margin, this.hot_h, 5, this.b.x, this.b.y, this.b.h, { 371 | normal: this.cross.normal, 372 | hover: this.cross.hover 373 | }, !ppt.searchShow || ppt.filterShow || ppt.settingsShow, '', () => search.clear(), () => panel.search.txt ? 'Clear search text (escape)' : 'No search text to clear', true, 'cross1'); 374 | this.setSearchBtnsHide(); 375 | } 376 | 377 | traceBtn(btn, x, y) { 378 | const o = this.btns[btn]; 379 | return o && o.trace(x, y); 380 | } 381 | 382 | tt(n, force) { 383 | if (tooltip.Text === n && !force) return; 384 | pop.checkTooltipFont('btn'); 385 | tooltip.Text = n; 386 | tooltip.Activate(); 387 | } 388 | } 389 | 390 | class Btn { 391 | constructor(x, y, w, h, type, p1, p2, p3, item, hide, l_dn, l_up, tiptext, hand, name) { 392 | this.x = x; 393 | this.y = y; 394 | this.w = w; 395 | this.h = h; 396 | this.type = type; 397 | this.p1 = p1; 398 | this.p2 = p2; 399 | this.p3 = p3; 400 | this.item = item; 401 | this.hide = hide; 402 | this.l_dn = l_dn; 403 | this.l_up = l_up; 404 | this.tt = new Tooltip; 405 | this.tiptext = tiptext; 406 | this.hand = hand; 407 | this.name = name; 408 | this.transition_factor = 0; 409 | this.state = 'normal'; 410 | } 411 | 412 | // Methods 413 | 414 | cs(state) { 415 | this.state = state; 416 | if (state === 'down' || state === 'normal') this.tt.clear(); 417 | this.repaint(); 418 | } 419 | 420 | draw(gr) { 421 | switch (this.type) { 422 | case 1: 423 | case 2: 424 | this.drawScrollBtn(gr); 425 | break; 426 | case 3: 427 | ui.theme.SetPartAndStateID(1, this.item[this.state]); 428 | ui.theme.DrawThemeBackground(gr, this.x, this.y, this.w, this.h); 429 | break; 430 | case 4: 431 | this.drawSearch(gr); 432 | break; 433 | case 5: 434 | this.drawCross(gr); 435 | break; 436 | case 6: 437 | this.drawFilter(gr); 438 | break; 439 | case 7: 440 | this.drawSettings(gr); 441 | break; 442 | } 443 | } 444 | 445 | drawCross(gr) { 446 | const a = !ui.id.local ? panel.search.txt ? (this.state !== 'down' ? Math.min(170 + (255 - 170) * this.transition_factor, 255) : 255) : 170 : 255; 447 | const crossIm = this.state === 'normal' || !panel.search.txt ? this.item.normal : this.item.hover; 448 | const colRect = this.state !== 'down' ? ui.getBlend(ui.col.bg4, ui.col.bg5, this.transition_factor, true) : ui.col.bg4; 449 | gr.SetSmoothingMode(2); 450 | gr.FillRoundRect(this.x, this.y, this.w, this.h, but.arc, but.arc, colRect); 451 | gr.SetSmoothingMode(0); 452 | gr.SetInterpolationMode(2); 453 | if (crossIm) gr.DrawImage(crossIm, this.p1, this.p2, this.p3, this.p3, 0, 0, crossIm.Width, crossIm.Height, 0, a); 454 | gr.SetInterpolationMode(0); 455 | } 456 | 457 | drawFilter(gr) { 458 | const colText = !ui.id.local ? (this.state !== 'down' ? ui.getBlend(this.item.hover, this.item.normal, this.transition_factor, true) : this.item.hover) : this.item.normal; 459 | const colRect = this.state !== 'down' ? ui.getBlend(ui.col.bg4, ui.col.bg5, this.transition_factor, true) : ui.col.bg4; 460 | gr.SetSmoothingMode(2); 461 | gr.FillRoundRect(this.x, but.hoverArea, this.w, but.hot_h, but.arc, but.arc, colRect); 462 | gr.SetSmoothingMode(0); 463 | if (!ui.img.blurDark) gr.GdiDrawText(panel.filter.mode[ppt.filterBy].name, panel.filter.font, colText, this.p1, this.y, this.p3, this.h, this.p2); 464 | else { 465 | gr.SetTextRenderingHint(5); 466 | gr.DrawString(panel.filter.mode[ppt.filterBy].name, panel.filter.font, colText, this.p1 - 1, this.y - 1, this.p3, this.h, StringFormat(1, 1)); 467 | } 468 | } 469 | 470 | drawScrollBtn(gr) { 471 | const a = this.state !== 'down' ? Math.min(but.alpha[0] + (but.alpha[1] - but.alpha[0]) * this.transition_factor, but.alpha[1]) : but.alpha[2]; 472 | switch (true) { 473 | case but.vertical: 474 | if (this.state !== 'normal' && ui.sbar.type == 1) gr.FillSolidRect(sbar.x, this.y + (this.type == 1 ? but.scr.hotOffset - panel.sbar_o : 0), sbar.w, this.h - but.scr.hotOffset + panel.sbar_o, but.scr.hover); 475 | if (but.scr.opaque && but.scr.bg) gr.DrawImage(but.scr.bg, this.x + this.p1, this.p2, this.p3, this.p3, 0, 0, but.scr.bg.Width, but.scr.bg.Height, this.type == 1 ? 0 : 180); 476 | if (but.scr.img) gr.DrawImage(but.scr.img, this.x + this.p1, this.p2, this.p3, this.p3, 0, 0, but.scr.img.Width, but.scr.img.Height, this.type == 1 ? 0 : 180, a); 477 | break; 478 | case !but.vertical: 479 | if (this.state !== 'normal' && ui.sbar.type == 1) gr.FillSolidRect(this.x + (this.type == 1 ? but.scr.hotOffset - panel.sbar_o : 0), sbar.y, this.w - but.scr.hotOffset + panel.sbar_o, sbar.h, but.scr.hover); 480 | if (but.scr.opaque && but.scr.bg) gr.DrawImage(but.scr.bg, this.p2, this.y + this.p1, this.p3, this.p3, 0, 0, but.scr.bg.Width, but.scr.bg.Height, this.type == 1 ? 270 : 90); 481 | if (but.scr.img) gr.DrawImage(but.scr.img, this.p2, this.y + this.p1, this.p3, this.p3, 0, 0, but.scr.img.Width, but.scr.img.Height, this.type == 1 ? 270 : 90, a); 482 | break; 483 | } 484 | } 485 | 486 | drawSearch(gr) { 487 | const a = !ui.id.local ? (this.state !== 'down' ? Math.min(170 + (255 - 170) * this.transition_factor, 255) : 255) : 255; 488 | const colRect = this.state !== 'down' ? ui.getBlend(ui.col.bg4, ui.col.bg5, this.transition_factor, true) : ui.col.bg4; 489 | gr.SetSmoothingMode(2); 490 | gr.FillRoundRect(this.x, this.y, this.w, this.h, but.arc, but.arc, colRect); 491 | gr.SetSmoothingMode(0); 492 | gr.SetInterpolationMode(2); 493 | if (this.item.normal) gr.DrawImage(this.item.normal, this.p1, this.p2, this.p3, this.p3, 0, 0, this.item.normal.Width, this.item.normal.Height, 0, a); 494 | gr.SetInterpolationMode(0); 495 | } 496 | 497 | drawSettings(gr) { 498 | const colText = !ui.id.local ? (this.state !== 'down' ? ui.getBlend(this.item.hover, this.item.normal, this.transition_factor, true) : this.item.hover) : this.item.normal; 499 | const colRect = this.state !== 'down' ? ui.getBlend(ui.col.bg4, ui.col.bg5, this.transition_factor, true) : ui.col.bg4; 500 | gr.SetSmoothingMode(2); 501 | gr.FillRoundRect(this.x, but.hoverArea, this.w, but.hot_h, but.arc, but.arc, colRect); 502 | gr.SetSmoothingMode(0); 503 | if (!ui.img.blurDark) gr.GdiDrawText(panel.settings.icon, panel.settings.font, colText, 0, this.y, this.p1, this.p2, panel.rc); 504 | else { 505 | gr.SetTextRenderingHint(5); 506 | gr.DrawString(panel.settings.icon, panel.settings.font, colText, 0, this.y - 1, this.p1, this.p2, StringFormat(2, 1)); 507 | } 508 | } 509 | 510 | lbtn_dn(x, y) { 511 | if (!but.Dn) return; 512 | this.l_dn && this.l_dn(x, y); 513 | } 514 | 515 | lbtn_up() { 516 | if (this.l_up) this.l_up(); 517 | } 518 | 519 | repaint() { 520 | const expXY = 2; 521 | const expWH = 4; 522 | window.RepaintRect(this.x - expXY, this.y - expXY, this.w + expWH, this.h + expWH); 523 | } 524 | 525 | trace(x, y) { 526 | but.trace = !this.hide && x > this.x && x < this.x + this.w && y > this.y && y < this.y + this.h; 527 | return but.trace; 528 | } 529 | } 530 | 531 | class Tooltip { 532 | constructor() { 533 | this.id = Math.ceil(Math.random().toFixed(8) * 1000); 534 | this.tt_timer = new TooltipTimer(); 535 | } 536 | 537 | // Methods 538 | 539 | clear() { 540 | this.tt_timer.stop(this.id); 541 | } 542 | 543 | show(text) { 544 | if (Date.now() - but.tooltip.start > 2000 && but.tooltip.delay) this.showDelayed(text); 545 | else this.showImmediate(text); 546 | but.tooltip.start = Date.now(); 547 | } 548 | 549 | showDelayed(text) { 550 | this.tt_timer.start(this.id, text); 551 | } 552 | 553 | showImmediate(text) { 554 | this.tt_timer.set_id(this.id); 555 | this.tt_timer.stop(this.id); 556 | but.tt(text); 557 | } 558 | 559 | stop() { 560 | this.tt_timer.forceStop(); 561 | } 562 | } 563 | 564 | class TooltipTimer { 565 | constructor() { 566 | this.delay_timer; 567 | this.tt_caller = undefined; 568 | } 569 | 570 | // Methods 571 | 572 | forceStop() { 573 | but.tooltip.delay = true; 574 | but.tt(''); 575 | if (this.delay_timer) { 576 | clearTimeout(this.delay_timer); 577 | this.delay_timer = null; 578 | } 579 | } 580 | 581 | set_id(id) { 582 | this.tt_caller = id; 583 | } 584 | 585 | start(id, text) { 586 | const old_caller = this.tt_caller; 587 | this.tt_caller = id; 588 | if (!this.delay_timer && tooltip.Text) but.tt(text, old_caller !== this.tt_caller); 589 | else { 590 | this.forceStop(); 591 | if (!this.delay_timer) { 592 | this.delay_timer = setTimeout(() => { 593 | but.tt(text); 594 | this.delay_timer = null; 595 | }, 500); 596 | } 597 | } 598 | } 599 | 600 | stop(id) { 601 | if (this.tt_caller === id) this.forceStop(); 602 | } 603 | } 604 | 605 | class Transition { 606 | constructor(items, hover) { 607 | this.hover = hover; 608 | this.items = items; 609 | this.transition_timer = null; 610 | } 611 | 612 | // Methods 613 | 614 | start() { 615 | const hover_in_step = 0.2; 616 | const hover_out_step = 0.06; 617 | if (!this.transition_timer) { 618 | this.transition_timer = setInterval(() => { 619 | Object.values(this.items).forEach(v => { 620 | const saved = v.transition_factor; 621 | if (this.hover(v)) v.transition_factor = Math.min(1, v.transition_factor += hover_in_step); 622 | else v.transition_factor = Math.max(0, v.transition_factor -= hover_out_step); 623 | if (saved !== v.transition_factor) { 624 | v.repaint(); 625 | } 626 | }); 627 | const running = Object.values(this.items).some(v => v.transition_factor > 0 && v.transition_factor < 1); 628 | if (!running) this.stop(); 629 | }, 25); 630 | } 631 | } 632 | 633 | stop() { 634 | if (this.transition_timer) { 635 | clearInterval(this.transition_timer); 636 | this.transition_timer = null; 637 | } 638 | } 639 | } -------------------------------------------------------------------------------- /scripts/callbacks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function on_colours_changed(keepCache) { 4 | ui.getColours(); 5 | 6 | if (panel.colMarker) { 7 | panel.getFields(ppt.viewBy, ppt.filterBy); 8 | if (lib) { 9 | lib.getLibrary(); 10 | lib.rootNodes(true, true); 11 | } 12 | } 13 | sbar.setCol(); 14 | pop.createImages(); 15 | but.createImages(); 16 | if (!keepCache) img.clearCache(); 17 | img.createImages(); 18 | but.refresh(true); 19 | sbar.resetAuto(); 20 | ui.createImages(); 21 | if (!ppt.themed) ui.blurReset(); 22 | window.Repaint(); 23 | } 24 | 25 | function on_font_changed() { 26 | sbar.logScroll(); 27 | pop.deactivateTooltip(); 28 | ui.getFont(); 29 | panel.on_size(true); 30 | if (ui.style.topBarShow || ppt.sbarShow) but.refresh(true); 31 | sbar.resetAuto(); 32 | window.Repaint(); 33 | sbar.setScroll(); 34 | } 35 | 36 | function on_char(code) { 37 | pop.on_char(code); 38 | find.on_char(code); 39 | if (!ppt.searchShow) return; 40 | search.on_char(code); 41 | } 42 | 43 | function on_focus(is_focused) { 44 | if (!is_focused) { 45 | timer.clear(timer.cursor); 46 | panel.search.cursor = false; 47 | panel.searchPaint(); 48 | } 49 | pop.on_focus(is_focused); 50 | } 51 | 52 | function on_get_album_art_done(handle, art_id, image, image_path) { 53 | ui.on_get_album_art_done(handle, image, image_path); 54 | } 55 | 56 | function on_item_focus_change(playlistIndex) { 57 | lib.checkFilter(); 58 | if (!pop.setFocus) { 59 | if (ppt.followPlaylistFocus && playlistIndex == $.pl_active && !ppt.libSource) { 60 | setSelection(fb.GetFocusItem()); 61 | } 62 | } else pop.setFocus = false; 63 | ui.focus_changed(); 64 | } 65 | 66 | function on_key_down(vkey) { 67 | pop.on_key_down(vkey); 68 | img.on_key_down(vkey); 69 | if (!ppt.searchShow) return; 70 | search.on_key_down(vkey); 71 | } 72 | 73 | function on_key_up(vkey) { 74 | img.on_key_up(vkey); 75 | if (!ppt.searchShow) return; 76 | search.on_key_up(vkey) 77 | } 78 | 79 | function on_library_items_added(handleList) { 80 | if (ppt.libSource == 2) return; 81 | if (lib.v2_init) { 82 | lib.v2_init = false; 83 | if (ui.w < 1 || !window.IsVisible) return; 84 | lib.initialise(handleList); 85 | return; 86 | } 87 | if (!ppt.libAutoSync || ppt.fixedPlaylist || !ppt.libSource) return; 88 | lib.treeState(false, 2, handleList, 0); 89 | } 90 | 91 | function on_library_items_removed(handleList) { 92 | if (!ppt.libAutoSync || ppt.fixedPlaylist || !ppt.libSource) return; 93 | if (ppt.libSource == 2) { 94 | const libList = lib.list.Clone(); 95 | libList.Sort(); 96 | handleList.Sort(); 97 | handleList.MakeIntersection(libList); 98 | } 99 | lib.treeState(false, 2, handleList, 2); 100 | } 101 | 102 | function on_library_items_changed(handleList) { 103 | if (!ppt.libAutoSync || ppt.fixedPlaylist || !ppt.libSource) return; 104 | if (ppt.libSource == 2) { 105 | const libList = lib.list.Clone(); 106 | libList.Sort(); 107 | handleList.Sort(); 108 | handleList.MakeIntersection(libList); 109 | } 110 | lib.treeState(false, 2, handleList, 1); 111 | } 112 | 113 | function on_main_menu(index) { 114 | pop.on_main_menu(index); 115 | } 116 | 117 | function on_metadb_changed(handleList, isDatabase) { 118 | if (isDatabase && !panel.statistics || lib.list.Count != lib.libNode.length) return; 119 | if (ppt.fixedPlaylist || !ppt.libSource) { 120 | handleList.Convert().some(h => { 121 | const i = lib.full_list.Find(h); 122 | if (i != -1) { 123 | const isMainChanged = lib.isMainChanged(handleList); 124 | if (isMainChanged) lib.treeState(false, 2); 125 | ui.focus_changed(); 126 | return true; 127 | } 128 | }); 129 | } 130 | } 131 | 132 | function on_mouse_lbtn_dblclk(x, y) { 133 | but.lbtn_dn(x, y); 134 | if (ppt.searchShow) search.lbtn_dblclk(x, y); 135 | pop.lbtn_dblclk(x, y); 136 | sbar.lbtn_dblclk(x, y); 137 | } 138 | 139 | function on_mouse_lbtn_down(x, y) { 140 | if (ppt.touchControl) panel.last_pressed_coord = { 141 | x: x, 142 | y: y 143 | }; 144 | if (ui.style.topBarShow || ppt.sbarShow) but.lbtn_dn(x, y); 145 | if (ppt.searchShow) search.lbtn_dn(x, y); 146 | pop.lbtn_dn(x, y); 147 | sbar.lbtn_dn(x, y); 148 | ui.sz.y_start = y; 149 | } 150 | 151 | function on_mouse_lbtn_up(x, y) { 152 | pop.lbtn_up(x, y); 153 | if (ppt.searchShow) search.lbtn_up(); 154 | but.lbtn_up(x, y); 155 | sbar.lbtn_up(); 156 | } 157 | 158 | function on_mouse_leave() { 159 | if (ui.style.topBarShow || ppt.sbarShow) but.leave(); 160 | sbar.leave(); 161 | pop.leave(); 162 | } 163 | 164 | function on_mouse_mbtn_dblclk(x, y, mask) { 165 | pop.mbtnDblClickOrAltDblClick(x, y, mask, 'mbtn'); 166 | } 167 | 168 | function on_mouse_mbtn_down(x, y) { 169 | pop.mbtn_dn(x, y); 170 | } 171 | 172 | function on_mouse_mbtn_up(x, y, mask) { 173 | // hacks at default settings blocks on_mouse_mbtn_up, at least in windows; workaround configure hacks: main window > move with > caption only & ensure pseudo-caption doesn't overlap buttons 174 | pop.mbtnUpOrAltClickUp(x, y, mask, 'mbtn'); 175 | } 176 | 177 | function on_mouse_move(x, y) { 178 | if (panel.m.x == x && panel.m.y == y) return; 179 | pop.hand = false; 180 | if (ui.style.topBarShow || ppt.sbarShow) but.move(x, y); 181 | if (ppt.searchShow) search.move(x, y); 182 | pop.move(x, y); 183 | pop.dragDrop(x, y); 184 | sbar.move(x, y); 185 | ui.zoomDrag(x, y); 186 | panel.m.x = x; 187 | panel.m.y = y; 188 | } 189 | 190 | function on_mouse_rbtn_up(x, y) { 191 | if (y < panel.search.h && x > panel.search.x && x < panel.search.x + panel.search.w) { 192 | if (ppt.searchShow) search.rbtn_up(x, y); 193 | } else men.rbtn_up(x, y); 194 | return true; 195 | } 196 | 197 | function on_mouse_wheel(step) { 198 | pop.deactivateTooltip(); 199 | if (!vk.k('zoom')) sbar.wheel(step); 200 | else ui.wheel(step); 201 | } 202 | 203 | function on_notify_data(name, info) { 204 | if (ppt.libSource == 2 && name != 'bio_imgChange') { 205 | const panelSelectionPlaylists = ppt.panelSelectionPlaylist.split(/\s*\|\s*/); 206 | panelSelectionPlaylists.some(v=> { 207 | if (name == v) { 208 | lib.list = new FbMetadbHandleList(info); 209 | if ($.equalHandles(lib.list.Convert(), lib.full_list.Convert())) return; 210 | lib.full_list = lib.list.Clone(); 211 | ppt.lastPanelSelectionPlaylist = `${v} Cache`; 212 | const pln = plman.FindOrCreatePlaylist(`${v} Cache`, false); 213 | plman.ClearPlaylist(pln); 214 | plman.InsertPlaylistItems(pln, 0, lib.list); 215 | lib.searchCache = {}; 216 | pop.clearTree(); 217 | pop.cache = { 218 | 'standard': {}, 219 | 'search': {}, 220 | 'filter': {} 221 | } 222 | lib.treeState(false, 2, null, 3); 223 | ui.expandHandle = lib.list.Count ? lib.list[0] : null; 224 | ui.on_playback_new_track(); 225 | lib.treeState(false, ppt.rememberTree); 226 | return; 227 | } 228 | }); 229 | } 230 | 231 | switch (name) { 232 | case '!!.tags update': 233 | lib.treeState(false, 2); 234 | break; 235 | case 'newThemeColours': 236 | if (!ppt.themed) break; 237 | ppt.theme = info.theme; 238 | ppt.themeBgImage = info.themeBgImage; 239 | ppt.themeColour = info.themeColour; 240 | on_colours_changed(true); 241 | break; 242 | case 'Sync col': { 243 | if (!ppt.themed) break; 244 | const themeLight = ppt.themeLight; 245 | if (themeLight != info.themeLight) { 246 | ppt.themeLight = info.themeLight; 247 | on_colours_changed(true); 248 | } 249 | break; 250 | } 251 | case 'Sync image': 252 | if (!ppt.themed) break; 253 | sync.image(new GdiBitmap(info.image), info.id); 254 | break; 255 | } 256 | if (ui.id.local && name.startsWith('opt_')) { 257 | const clone = typeof info === 'string' ? String(info) : info; 258 | on_notify(name, clone); 259 | } 260 | } 261 | 262 | function on_paint(gr) { 263 | if (!lib.initialised) { 264 | lib.initialise(); 265 | } 266 | ui.draw(gr); 267 | lib.checkTree(); 268 | img.draw(gr); 269 | ui.drawLine(gr); 270 | search.draw(gr); 271 | pop.draw(gr); 272 | sbar.draw(gr); 273 | but.draw(gr); 274 | find.draw(gr); 275 | } 276 | 277 | function on_playback_new_track(handle) { 278 | lib.checkFilter(); 279 | pop.getNowplaying(handle); 280 | if (!ppt.recItemImage || ppt.libSource != 2) ui.on_playback_new_track(handle); 281 | } 282 | 283 | function on_playback_stop(reason) { 284 | if (reason == 2) return; 285 | pop.getNowplaying('', true); 286 | on_item_focus_change(); 287 | } 288 | 289 | function on_playback_queue_changed() { 290 | on_queue_changed(); 291 | } 292 | 293 | function on_playlists_changed() { 294 | men.playlists_changed(); 295 | if ($.pl_active != plman.ActivePlaylist) $.pl_active = plman.ActivePlaylist; 296 | let fixedPlaylistIndex = -1; 297 | if (ppt.fixedPlaylist) { 298 | fixedPlaylistIndex = plman.FindPlaylist(ppt.fixedPlaylistName); 299 | if (fixedPlaylistIndex == -1) { 300 | ppt.fixedPlaylist = false; 301 | ppt.libSource = 0; 302 | if (panel.imgView) img.clearCache(); 303 | lib.playlist_update(); 304 | } 305 | } 306 | } 307 | 308 | function on_playlist_items_added(playlistIndex) { 309 | if (ppt.fixedPlaylist) { 310 | const fixedPlaylistIndex = plman.FindPlaylist(ppt.fixedPlaylistName); 311 | if (playlistIndex == fixedPlaylistIndex) { 312 | lib.playlist_update(playlistIndex); 313 | return 314 | } 315 | } 316 | if (!ppt.libSource && playlistIndex == $.pl_active) { 317 | lib.playlist_update(playlistIndex); 318 | 319 | } 320 | } 321 | 322 | function on_playlist_items_removed(playlistIndex) { 323 | if (ppt.fixedPlaylist) { 324 | const fixedPlaylistIndex = plman.FindPlaylist(ppt.fixedPlaylistName); 325 | if (playlistIndex == fixedPlaylistIndex) { 326 | lib.playlist_update(playlistIndex); 327 | return 328 | } 329 | } 330 | 331 | if (!ppt.libSource && playlistIndex == $.pl_active) { 332 | lib.playlist_update(playlistIndex); 333 | } 334 | } 335 | 336 | function on_playlist_items_reordered(playlistIndex) { 337 | if (!ppt.libSource && playlistIndex == $.pl_active) { 338 | lib.playlist_update(playlistIndex); 339 | } 340 | } 341 | 342 | function on_playlist_switch() { 343 | $.pl_active = plman.ActivePlaylist; 344 | if (!ppt.libSource) { 345 | lib.playlist_update(); 346 | } 347 | ui.focus_changed(); 348 | } 349 | 350 | const on_queue_changed = $.debounce(() => { 351 | if (ppt.itemShowStatistics != 7) return; 352 | pop.tree.forEach(v => { 353 | v.id = ''; 354 | v.count = ''; 355 | delete v.statistics; 356 | delete v._statistics; 357 | }); 358 | pop.cache = { 359 | 'standard': {}, 360 | 'search': {}, 361 | 'filter': {} 362 | } 363 | panel.treePaint(); 364 | }, 250, { 365 | leading: true, 366 | trailing: true 367 | }); 368 | 369 | function on_script_unload() { 370 | but.on_script_unload(); 371 | pop.deactivateTooltip(); 372 | } 373 | 374 | function on_selection_changed() { 375 | if (!panel.setSelection()) return; 376 | setSelection(fb.GetSelection()); 377 | } 378 | 379 | const windowMetricsPath = `${fb.ProfilePath}settings\\themed\\windowMetrics.json`; 380 | function on_size() { 381 | ui.w = window.Width; 382 | ui.h = window.Height; 383 | if (!ui.w || !ui.h) return; 384 | 385 | pop.deactivateTooltip(); 386 | tooltip.SetMaxWidth(Math.max(ui.w, 800)); 387 | ui.blurReset(); 388 | ui.calcText(true) 389 | 390 | if (ppt.themed && ppt.theme) { 391 | const themed_image = `${fb.ProfilePath}settings\\themed\\themed_image.bmp`; 392 | if ($.file(themed_image)) sync.image(gdi.Image(themed_image)); 393 | } 394 | 395 | panel.on_size(); 396 | if (ui.style.topBarShow || ppt.sbarShow) but.refresh(true); 397 | sbar.resetAuto(); 398 | find.on_size(); 399 | 400 | if (!ppt.themed) return; 401 | const windowMetrics = $.jsonParse(windowMetricsPath, {}, 'file'); 402 | windowMetrics[window.Name] = { 403 | w: ui.w, 404 | h: ui.h 405 | } 406 | $.save(windowMetricsPath, JSON.stringify(windowMetrics, null, 3), true); 407 | } 408 | 409 | function setSelection(handle) { 410 | if (!handle || !panel.list.Count) return; 411 | const item = panel.list.Find(handle); 412 | let idx = -1; 413 | pop.tree.forEach((v, i) => { 414 | if (!v.root && pop.inRange(item, v.item)) idx = i; 415 | }); 416 | if (idx != -1) { 417 | if (!panel.imgView) pop.focusShow(idx); 418 | else pop.showItem(idx, 'focus'); 419 | } 420 | } -------------------------------------------------------------------------------- /scripts/helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const requiredVersionStr = '1.5.2'; 4 | 5 | function is_compatible(requiredVersionStr) { 6 | const requiredVersion = requiredVersionStr.split('.'); 7 | const currentVersion = utils.Version.split('.'); 8 | if (currentVersion.length > 3) currentVersion.length = 3; 9 | for (let i = 0; i < currentVersion.length; ++i) 10 | if (currentVersion[i] != requiredVersion[i]) return currentVersion[i] > requiredVersion[i]; 11 | return true; 12 | } 13 | if (!is_compatible(requiredVersionStr)) fb.ShowPopupMessage(`Library Tree requires v${requiredVersionStr}. Current component version is v${utils.Version}.`); 14 | 15 | const doc = new ActiveXObject('htmlfile'); 16 | const fso = new ActiveXObject('Scripting.FileSystemObject'); 17 | const tooltip = window.Tooltip; 18 | const WshShell = new ActiveXObject('WScript.Shell'); 19 | 20 | class Helpers { 21 | constructor() { 22 | this.pl_active = plman.ActivePlaylist; 23 | this.scale = this.getDpi(); 24 | } 25 | 26 | // Methods 27 | 28 | average(arr) { 29 | return arr.reduce((a, b) => a + b, 0) / arr.length; 30 | } 31 | 32 | browser(c) { 33 | if (!this.run(c)) fb.ShowPopupMessage('Unable to launch your default browser.', 'Library Tree'); 34 | } 35 | 36 | buildPth(pth) { 37 | let result, tmpFileLoc = ''; 38 | let UNC = pth.startsWith('\\\\'); 39 | if (UNC) pth = pth.replace('\\\\', ''); 40 | const pattern = /(.*?)\\/gm; 41 | while ((result = pattern.exec(pth))) { 42 | tmpFileLoc = tmpFileLoc.concat(result[0]); 43 | if (UNC) { 44 | tmpFileLoc = `\\\\${tmpFileLoc}`; 45 | UNC = false; 46 | } 47 | this.create(tmpFileLoc); 48 | } 49 | } 50 | 51 | clamp(num, min, max) { 52 | num = num <= max ? num : max; 53 | num = num >= min ? num : min; 54 | return num; 55 | } 56 | 57 | create(fo) { 58 | try { 59 | if (!this.folder(fo)) fso.CreateFolder(fo); 60 | } catch (e) {} 61 | } 62 | 63 | debounce(e,r,i) { 64 | var o,u,a,c,v,f,d=0,m=!1,j=!1,n=!0;if('function'!=typeof e)throw new TypeError('debounce: invalid function');function T(i){var n=o,t=u;return o=u=void 0,d=i,c=e.apply(t,n)}function b(i){var n=i-f;return void 0===f||r<=n||n<0||j&&a<=i-d}function l(){var i,n,t=Date.now();if(b(t))return w(t);v=setTimeout(l,(n=r-((i=t)-f),j?Math.min(n,a-(i-d)):n))}function w(i){return v=void 0,n&&o?T(i):(o=u=void 0,c)}function t(){var i,n=Date.now(),t=b(n);if(o=arguments,u=this,f=n,t){if(void 0===v)return d=i=f,v=setTimeout(l,r),m?T(i):c;if(j)return v=setTimeout(l,r),T(f)}return void 0===v&&(v=setTimeout(l,r)),c}return r=parseFloat(r)||0,this.isObject(i)&&(m=!!i.leading,a=((j='maxWait'in i))?Math.max(parseFloat(i.maxWait)||0,r):a,n='trailing'in i?!!i.trailing:n),t.cancel=function(){void 0!==v&&clearTimeout(v),o=f=u=v=void(d=0)},t.flush=function(){return void 0===v?c:w(Date.now())},t; 65 | } 66 | 67 | equal(arr1, arr2) { 68 | if (!this.isArray(arr1) || !this.isArray(arr2)) return false; 69 | let i = arr1.length; 70 | if (i != arr2.length) return false; 71 | while (i--) 72 | if (arr1[i] !== arr2[i]) return false; 73 | return true; 74 | } 75 | 76 | equalHandles(arr1, arr2) { 77 | if (!this.isArray(arr1) || !this.isArray(arr2)) return false; 78 | let i = arr1.length; 79 | if (i != arr2.length) return false; 80 | while (i--) 81 | if (!arr1[i].Compare(arr2[i])) return false; 82 | return true; 83 | } 84 | 85 | file(f) { 86 | return typeof f === 'string' && fso.FileExists(f); 87 | } 88 | 89 | folder(fo) { 90 | return typeof fo === 'string' && fso.FolderExists(fo); 91 | } 92 | 93 | getClipboardData() { 94 | try { 95 | return utils.GetClipboardText(); 96 | } catch(e) { 97 | try { 98 | return doc.parentWindow.clipboardData.getData('Text'); 99 | } catch (e) { 100 | return null; 101 | } 102 | } 103 | } 104 | 105 | getDpi() { 106 | let dpi = 120; 107 | try { 108 | dpi = WshShell.RegRead('HKCU\\Control Panel\\Desktop\\WindowMetrics\\AppliedDPI'); 109 | } catch (e) {} 110 | return Math.max(dpi / 120, 1); 111 | } 112 | 113 | gr(w, h, im, func) { 114 | if (isNaN(w) || isNaN(h)) return; 115 | let i = gdi.CreateImage(Math.max(w, 2), Math.max(h, 2)); 116 | let g = i.GetGraphics(); 117 | func(g, i); 118 | i.ReleaseGraphics(g); 119 | g = null; 120 | if (im) return i; 121 | else i = null; 122 | } 123 | 124 | isArray(arr) { 125 | return Array.isArray(arr); 126 | } 127 | 128 | isObject(t) { 129 | const e = typeof t; 130 | return null != t && ('object' == e || 'function' == e); 131 | } 132 | 133 | jsonParse(n, defaultVal, type) { 134 | switch (type) { 135 | case 'file': 136 | try { 137 | return JSON.parse(this.open(n)); 138 | } catch (e) { 139 | return defaultVal; 140 | } 141 | default: 142 | try { 143 | return JSON.parse(n); 144 | } catch (e) { 145 | return defaultVal; 146 | } 147 | } 148 | } 149 | 150 | objHasOwnProperty(obj, key) { 151 | return Object.prototype.hasOwnProperty.call(obj, key); 152 | } 153 | 154 | open(f) { 155 | try { // handle locked files 156 | return this.file(f) ? utils.ReadTextFile(f) : ''; 157 | } catch (e) { 158 | return ''; 159 | } 160 | } 161 | 162 | padNumber(num, len, base) { 163 | if (!base) base = 10; 164 | return ('000000' + num.toString(base)).substr(-len); 165 | } 166 | 167 | query(h, q) { 168 | let l = new FbMetadbHandleList(); 169 | try { 170 | l = fb.GetQueryItems(h, q); 171 | } catch (e) {} 172 | return l; 173 | } 174 | 175 | range(start, stop, step) { 176 | step = step || 1; 177 | return Array.from({length: (stop - start) / step + 1}, (_, i) => start + i * step); 178 | } 179 | 180 | regexEscape(n) { 181 | return n.replace(/[*+\-?^!:&"~${}()|[\]/\\]/g, '\\$&'); 182 | } 183 | 184 | replaceAt(str, pos, chr) { 185 | return str.substring(0, pos) + chr + str.substring(pos + 1); 186 | } 187 | 188 | RGBAtoRGB(c, bg) { 189 | c = this.toRGBA(c); 190 | bg = this.toRGB(bg); 191 | const r = c[0] / 255; 192 | const g = c[1] / 255; 193 | const b = c[2] / 255; 194 | const a = c[3] / 255; 195 | const bgr = bg[0] / 255; 196 | const bgg = bg[1] / 255; 197 | const bgb = bg[2] / 255; 198 | let nR = ((1 - a) * bgr) + (a * r); 199 | let nG = ((1 - a) * bgg) + (a * g); 200 | let nB = ((1 - a) * bgb) + (a * b); 201 | nR = this.clamp(Math.round(nR * 255), 0, 255); 202 | nG = this.clamp(Math.round(nG * 255), 0, 255); 203 | nB = this.clamp(Math.round(nB * 255), 0, 255); 204 | return RGB(nR, nG, nB); 205 | } 206 | 207 | RGBtoRGBA(rgb, a) { 208 | return a << 24 | rgb & 0x00FFFFFF; 209 | } 210 | 211 | run(c) { 212 | try { 213 | WshShell.Run(c); 214 | return true; 215 | } catch (e) { 216 | return false; 217 | } 218 | } 219 | 220 | save(fn, text, bom) { 221 | try { 222 | utils.WriteTextFile(fn, text, bom) 223 | } catch (e) { 224 | this.trace('error saving: ' + fn); 225 | } 226 | } 227 | 228 | setClipboardData(n) { 229 | try { 230 | utils.SetClipboardText(n); 231 | } catch(e) { 232 | try { 233 | doc.parentWindow.clipboardData.setData('Text', n); 234 | } catch(e) { 235 | this.trace('unable to set clipboard text'); 236 | } 237 | } 238 | } 239 | 240 | split(n, type) { 241 | switch (type) { 242 | case 0: 243 | return n.replace(/\s+|^,+|,+$/g, '').split(','); 244 | case 1: 245 | return n.replace(/^[,\s]+|[,\s]+$/g, '').split(/[,-]/); 246 | } 247 | } 248 | 249 | throttle(e,i,t) { 250 | var n=!0,r=!0;if('function'!=typeof e)throw new TypeError('throttle: invalid function');return this.isObject(t)&&(n='leading'in t?!!txt.leading:n,r='trailing'in t?!!txt.trailing:r),this.debounce(e,i,{leading:n,maxWait:i,trailing:r}); 251 | } 252 | 253 | titlecase(n) { 254 | return n.replace(/[A-Za-z0-9\u00C0-\u00FF]+[^\s-/]*/g, match => { 255 | if (match.substr(1).search(/[A-Z]|\../) > -1) return match; 256 | return match.charAt(0).toUpperCase() + match.substr(1); 257 | }); 258 | } 259 | 260 | toRGB(c) { 261 | return [c >> 16 & 0xff, c >> 8 & 0xff, c & 0xff]; 262 | } 263 | 264 | toRGBA(c) { 265 | return [c >> 16 & 0xff, c >> 8 & 0xff, c & 0xff, c >> 24 & 0xff]; 266 | } 267 | 268 | trace(message) { 269 | console.log('Library Tree' + ': ' + message); 270 | } 271 | 272 | value(num, def, type) { 273 | num = parseFloat(num); 274 | if (isNaN(num)) return def; 275 | switch (type) { 276 | case 0: 277 | return num; 278 | case 1: 279 | if (num !== 1 && num !== 0) return def; 280 | break; 281 | case 2: 282 | if (num > 2 || num < 0) return def; 283 | break; 284 | } 285 | return num; 286 | } 287 | 288 | wshPopup(prompt, caption) { 289 | try { 290 | const ns = WshShell.Popup(prompt, 0, caption, 1); 291 | if (ns == 1) return true; 292 | return false; 293 | } catch (e) { 294 | return true; 295 | } 296 | } 297 | } 298 | 299 | const $ = new Helpers; 300 | 301 | function RGB(r, g, b) { 302 | return 0xff000000 | r << 16 | g << 8 | b; 303 | } 304 | 305 | function RGBA(r, g, b, a) { 306 | return a << 24 | r << 16 | g << 8 | b; 307 | } 308 | 309 | function StringFormat() { 310 | const a = arguments; 311 | const flags = 0; 312 | let h_align = 0; 313 | let v_align = 0; 314 | let trimming = 0; 315 | switch (a.length) { 316 | case 3: 317 | trimming = a[2]; /*fall through*/ 318 | case 2: 319 | v_align = a[1]; /*fall through*/ 320 | case 1: 321 | h_align = a[0]; 322 | break; 323 | default: 324 | return 0; 325 | } 326 | return (h_align << 28 | v_align << 24 | trimming << 20 | flags); 327 | } 328 | 329 | function Bezier(){const i=4,c=.001,o=1e-7,v=10,l=11,s=1/(l-1),n=typeof Float32Array==='function';function e(r,n){return 1-3*n+3*r}function u(r,n){return 3*n-6*r}function a(r){return 3*r}function w(r,n,t){return((e(n,t)*r+u(n,t))*r+a(n))*r}function y(r,n,t){return 3*e(n,t)*r*r+2*u(n,t)*r+a(n)}function h(r,n,t,e,u){let a,f,i=0;do{f=n+(t-n)/2;a=w(f,e,u)-r;if(a>0){t=f}else{n=f}}while(Math.abs(a)>o&&++i=c){return A(r,a,i,o)}else if(f===0){return a}else{return h(r,n,n+s,i,o)}}return function r(n){if(n===0){return 0}if(n===1){return 1}return w(u(n),t,e)}} this.scroll = bezier(0.25, 0.1, 0.25, 1); this.full = this.scroll; this.step = this.scroll; this.bar = bezier(0.165,0.84,0.44,1); this.barFast = bezier(0.19, 1, 0.22, 1); this.inertia = bezier(0.23, 1, 0.32, 1);} 330 | const ease = new Bezier; 331 | 332 | function MD5(){const b=function(l,n){let m=l[0],j=l[1],p=l[2],o=l[3];m+=(j&p|~j&o)+n[0]-680876936|0;m=(m<<7|m>>>25)+j|0;o+=(m&j|~m&p)+n[1]-389564586|0;o=(o<<12|o>>>20)+m|0;p+=(o&m|~o&j)+n[2]+606105819|0;p=(p<<17|p>>>15)+o|0;j+=(p&o|~p&m)+n[3]-1044525330|0;j=(j<<22|j>>>10)+p|0;m+=(j&p|~j&o)+n[4]-176418897|0;m=(m<<7|m>>>25)+j|0;o+=(m&j|~m&p)+n[5]+1200080426|0;o=(o<<12|o>>>20)+m|0;p+=(o&m|~o&j)+n[6]-1473231341|0;p=(p<<17|p>>>15)+o|0;j+=(p&o|~p&m)+n[7]-45705983|0;j=(j<<22|j>>>10)+p|0;m+=(j&p|~j&o)+n[8]+1770035416|0;m=(m<<7|m>>>25)+j|0;o+=(m&j|~m&p)+n[9]-1958414417|0;o=(o<<12|o>>>20)+m|0;p+=(o&m|~o&j)+n[10]-42063|0;p=(p<<17|p>>>15)+o|0;j+=(p&o|~p&m)+n[11]-1990404162|0;j=(j<<22|j>>>10)+p|0;m+=(j&p|~j&o)+n[12]+1804603682|0;m=(m<<7|m>>>25)+j|0;o+=(m&j|~m&p)+n[13]-40341101|0;o=(o<<12|o>>>20)+m|0;p+=(o&m|~o&j)+n[14]-1502002290|0;p=(p<<17|p>>>15)+o|0;j+=(p&o|~p&m)+n[15]+1236535329|0;j=(j<<22|j>>>10)+p|0;m+=(j&o|p&~o)+n[1]-165796510|0;m=(m<<5|m>>>27)+j|0;o+=(m&p|j&~p)+n[6]-1069501632|0;o=(o<<9|o>>>23)+m|0;p+=(o&j|m&~j)+n[11]+643717713|0;p=(p<<14|p>>>18)+o|0;j+=(p&m|o&~m)+n[0]-373897302|0;j=(j<<20|j>>>12)+p|0;m+=(j&o|p&~o)+n[5]-701558691|0;m=(m<<5|m>>>27)+j|0;o+=(m&p|j&~p)+n[10]+38016083|0;o=(o<<9|o>>>23)+m|0;p+=(o&j|m&~j)+n[15]-660478335|0;p=(p<<14|p>>>18)+o|0;j+=(p&m|o&~m)+n[4]-405537848|0;j=(j<<20|j>>>12)+p|0;m+=(j&o|p&~o)+n[9]+568446438|0;m=(m<<5|m>>>27)+j|0;o+=(m&p|j&~p)+n[14]-1019803690|0;o=(o<<9|o>>>23)+m|0;p+=(o&j|m&~j)+n[3]-187363961|0;p=(p<<14|p>>>18)+o|0;j+=(p&m|o&~m)+n[8]+1163531501|0;j=(j<<20|j>>>12)+p|0;m+=(j&o|p&~o)+n[13]-1444681467|0;m=(m<<5|m>>>27)+j|0;o+=(m&p|j&~p)+n[2]-51403784|0;o=(o<<9|o>>>23)+m|0;p+=(o&j|m&~j)+n[7]+1735328473|0;p=(p<<14|p>>>18)+o|0;j+=(p&m|o&~m)+n[12]-1926607734|0;j=(j<<20|j>>>12)+p|0;m+=(j^p^o)+n[5]-378558|0;m=(m<<4|m>>>28)+j|0;o+=(m^j^p)+n[8]-2022574463|0;o=(o<<11|o>>>21)+m|0;p+=(o^m^j)+n[11]+1839030562|0;p=(p<<16|p>>>16)+o|0;j+=(p^o^m)+n[14]-35309556|0;j=(j<<23|j>>>9)+p|0;m+=(j^p^o)+n[1]-1530992060|0;m=(m<<4|m>>>28)+j|0;o+=(m^j^p)+n[4]+1272893353|0;o=(o<<11|o>>>21)+m|0;p+=(o^m^j)+n[7]-155497632|0;p=(p<<16|p>>>16)+o|0;j+=(p^o^m)+n[10]-1094730640|0;j=(j<<23|j>>>9)+p|0;m+=(j^p^o)+n[13]+681279174|0;m=(m<<4|m>>>28)+j|0;o+=(m^j^p)+n[0]-358537222|0;o=(o<<11|o>>>21)+m|0;p+=(o^m^j)+n[3]-722521979|0;p=(p<<16|p>>>16)+o|0;j+=(p^o^m)+n[6]+76029189|0;j=(j<<23|j>>>9)+p|0;m+=(j^p^o)+n[9]-640364487|0;m=(m<<4|m>>>28)+j|0;o+=(m^j^p)+n[12]-421815835|0;o=(o<<11|o>>>21)+m|0;p+=(o^m^j)+n[15]+530742520|0;p=(p<<16|p>>>16)+o|0;j+=(p^o^m)+n[2]-995338651|0;j=(j<<23|j>>>9)+p|0;m+=(p^(j|~o))+n[0]-198630844|0;m=(m<<6|m>>>26)+j|0;o+=(j^(m|~p))+n[7]+1126891415|0;o=(o<<10|o>>>22)+m|0;p+=(m^(o|~j))+n[14]-1416354905|0;p=(p<<15|p>>>17)+o|0;j+=(o^(p|~m))+n[5]-57434055|0;j=(j<<21|j>>>11)+p|0;m+=(p^(j|~o))+n[12]+1700485571|0;m=(m<<6|m>>>26)+j|0;o+=(j^(m|~p))+n[3]-1894986606|0;o=(o<<10|o>>>22)+m|0;p+=(m^(o|~j))+n[10]-1051523|0;p=(p<<15|p>>>17)+o|0;j+=(o^(p|~m))+n[1]-2054922799|0;j=(j<<21|j>>>11)+p|0;m+=(p^(j|~o))+n[8]+1873313359|0;m=(m<<6|m>>>26)+j|0;o+=(j^(m|~p))+n[15]-30611744|0;o=(o<<10|o>>>22)+m|0;p+=(m^(o|~j))+n[6]-1560198380|0;p=(p<<15|p>>>17)+o|0;j+=(o^(p|~m))+n[13]+1309151649|0;j=(j<<21|j>>>11)+p|0;m+=(p^(j|~o))+n[4]-145523070|0;m=(m<<6|m>>>26)+j|0;o+=(j^(m|~p))+n[11]-1120210379|0;o=(o<<10|o>>>22)+m|0;p+=(m^(o|~j))+n[2]+718787259|0;p=(p<<15|p>>>17)+o|0;j+=(o^(p|~m))+n[9]-343485551|0;j=(j<<21|j>>>11)+p|0;l[0]=m+l[0]|0;l[1]=j+l[1]|0;l[2]=p+l[2]|0;l[3]=o+l[3]|0};const e='0123456789abcdef';const d=[];const c=function(k){const q=e;const o=d;let r,p,l;for(let m=0;m<4;m++){p=m*8;r=k[m];for(l=0;l<8;l+=2){o[p+1+l]=q.charAt(r&15);r>>>=4;o[p+0+l]=q.charAt(r&15);r>>>=4}}return o.join('')};const i=function(){this._dataLength=0;this._state=new Int32Array(4);this._buffer=new ArrayBuffer(68);this._bufferLength=0;this._buffer8=new Uint8Array(this._buffer,0,68);this._buffer32=new Uint32Array(this._buffer,0,17);this.start()};const a=new Int32Array([1732584193,-271733879,-1732584194,271733878]);const h=new Int32Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);i.prototype.appendStr=function(n){const k=this._buffer8;const j=this._buffer32;let o=this._bufferLength;for(let l=0;l>>6)+192;k[o++]=m&63|128}else{if(m<55296||m>56319){k[o++]=(m>>>12)+224;k[o++]=(m>>>6&63)|128;k[o++]=(m&63)|128}else{m=((m-55296)*1024)+(n.charCodeAt(++l)-56320)+65536;if(m>1114111){throw'Unicode standard supports code points up to U+10FFFF'}k[o++]=(m>>>18)+240;k[o++]=(m>>>12&63)|128;k[o++]=(m>>>6&63)|128;k[o++]=(m&63)|128}}}if(o>=64){this._dataLength+=64;b(this._state,j);o-=64;j[0]=j[16]}}this._bufferLength=o;return this};i.prototype.appendAsciiStr=function(o){const l=this._buffer8;const k=this._buffer32;let p=this._bufferLength;let n=0,m=0;for(;;){n=Math.min(o.length-m,64-p);while(n--){l[p++]=o.charCodeAt(m++)}if(p<64){break}this._dataLength+=64;b(this._state,k);p=0}this._bufferLength=p;return this};i.prototype.start=function(){this._dataLength=0;this._bufferLength=0;this._state.set(a);return this};i.prototype.end=function(){const q=this._bufferLength;this._dataLength+=q;const r=this._buffer8;r[q]=128;r[q+1]=r[q+2]=r[q+3]=0;const k=this._buffer32;const m=(q>>2)+1;k.set(h.subarray(m),m);if(q>55){b(this._state,k);k.set(h)}const j=this._dataLength*8;if(j<=4294967295){k[14]=j}else{const n=j.toString(16).match(/(.*?)(.{0,8})$/);const o=parseInt(n[2],16);const l=parseInt(n[1],16)||0;k[14]=o;k[15]=l}b(this._state,k);return c(this._state)};const f=new i();i.hashStr=function(k){return f.start().appendStr(k).end()};return i} // https://github.com/gorhill/yamd5.js 333 | const md5 = new MD5; -------------------------------------------------------------------------------- /scripts/initialise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let pop; 4 | const ui = new UserInterface; 5 | const panel = new Panel; 6 | const sbar = new Scrollbar; 7 | const vk = new Vkeys; 8 | const lib = new Library; 9 | pop = new Populate; 10 | const search = new Search; 11 | const find = new Find; 12 | const but = new Buttons; 13 | const popUpBox = new PopUpBox; 14 | const men = new MenuItems; 15 | const timer = new Timers; 16 | 17 | if (!ppt.get('Software Notice Checked', false)) fb.ShowPopupMessage('License\r\n\r\nCopyright (c) 2021-2022 WilB\r\n\r\nThe above copyright notice shall be included in all copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.', 'Library Tree'); 18 | ppt.set('Software Notice Checked', true); -------------------------------------------------------------------------------- /scripts/menu.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const MF_GRAYED = 0x00000001; 4 | const MF_STRING = 0x00000000; 5 | 6 | class MenuManager { 7 | constructor(name, clearArr, baseMenu) { 8 | this.baseMenu = baseMenu || 'baseMenu'; 9 | this.clearArr = clearArr; 10 | this.func = {}; 11 | this.idx = 0; 12 | this.menu = {}; 13 | this.menuItems = []; 14 | this.menuNames = []; 15 | this.name = name; 16 | } 17 | 18 | // Methods 19 | 20 | addItem(v) { 21 | if (v.separator && !v.str) { 22 | const separator = this.get(v.separator); 23 | if (separator) this.menu[v.menuName].AppendMenuSeparator(); 24 | } else { 25 | const hide = this.get(v.hide); 26 | if (hide || !v.str) return; 27 | this.idx++; 28 | if (!this.clearArr) this.executeFunctions(v, ['checkItem', 'checkRadio', 'flags', 'menuName', 'separator', 'str']); // if clearArr, functions redundant & not supported 29 | const a = this.clearArr ? v : this; 30 | const menu = this.menu[a.menuName]; 31 | menu.AppendMenuItem(a.flags, this.idx, a.str); 32 | if (a.checkItem) menu.CheckMenuItem(this.idx, a.checkItem); 33 | if (a.checkRadio) menu.CheckMenuRadioItem(this.idx, this.idx, this.idx); 34 | if (a.separator) menu.AppendMenuSeparator(); 35 | this.func[this.idx] = v.func; 36 | } 37 | } 38 | 39 | addSeparator({menuName = this.baseMenu, separator = true}) {this.menuItems.push({ menuName: menuName || this.baseMenu, separator: separator});} 40 | 41 | appendMenu(v) { 42 | const a = this.clearArr ? v : this; 43 | if (!this.clearArr) this.executeFunctions(v, ['hide', 'menuName']); 44 | if (a.menuName == this.baseMenu || a.hide) return; 45 | if (!this.clearArr) this.executeFunctions(v, ['appendTo', 'flags', 'separator', 'str']); 46 | const menu = this.menu[a.appendTo || this.baseMenu]; 47 | this.menu[a.menuName].AppendTo(menu, a.flags, a.str || a.menuName) 48 | if (a.separator) menu.AppendMenuSeparator(); 49 | } 50 | 51 | clear() { 52 | this.menu = {} 53 | this.func = {} 54 | this.idx = 0; 55 | if (this.clearArr) { 56 | this.menuItems = []; 57 | this.menuNames = []; 58 | } 59 | } 60 | 61 | createMenu(menuName = this.baseMenu) { 62 | menuName = this.get(menuName); 63 | this.menu[menuName] = window.CreatePopupMenu(); 64 | } 65 | 66 | executeFunctions(v, items) { 67 | let i = 0; 68 | let ln = items.length; 69 | while (i < ln) { 70 | const w = items[i]; 71 | this[w] = this.get(v[w]) 72 | i++; 73 | } 74 | } 75 | 76 | get(v) { 77 | if (v instanceof Function) return v(); 78 | return v; 79 | } 80 | 81 | load(x, y) { 82 | if (!this.menuItems.length) men[this.name](); 83 | let i = 0; 84 | let ln = this.menuNames.length; 85 | while (i < ln) { 86 | this.createMenu(this.menuNames[i]) 87 | i++; 88 | } 89 | 90 | i = 0; 91 | ln = this.menuItems.length; 92 | while (i < ln) { 93 | const v = this.menuItems[i]; 94 | !v.appendMenu ? this.addItem(v) : this.appendMenu(v) 95 | i++; 96 | } 97 | 98 | let Context; 99 | if (men.show_context) { 100 | Context = fb.CreateContextMenuManager(); 101 | Context.InitContext(men.items); 102 | this.menu[this.baseMenu].AppendMenuSeparator(); 103 | Context.BuildMenu(this.menu[this.baseMenu], 5000); 104 | } 105 | 106 | const idx = this.menu[this.baseMenu].TrackPopupMenu(x, y); 107 | this.run(idx); 108 | 109 | if (men.show_context) { 110 | if (idx >= 5000 && idx <= 5800) Context.ExecuteByID(idx - 5000); 111 | men.show_context = false; 112 | } 113 | 114 | this.clear(); 115 | } 116 | 117 | newItem({str = null, func = null, menuName = this.baseMenu, flags = MF_STRING, checkItem = false, checkRadio = false, separator = false, hide = false}) {this.menuItems.push({str: str, func: func, menuName: menuName, flags: flags, checkItem: checkItem, checkRadio: checkRadio, separator: separator, hide: hide});} 118 | 119 | newMenu({menuName = this.baseMenu, str = '', appendTo = this.baseMenu, flags = MF_STRING, separator = false, hide = false}) { 120 | this.menuNames.push(menuName); 121 | if (menuName != this.baseMenu) this.menuItems.push({menuName: menuName, appendMenu: true, str: str, appendTo: appendTo, flags: flags, separator: separator, hide: hide}); 122 | } 123 | 124 | run(idx) { 125 | const v = this.func[idx]; 126 | if (v instanceof Function) v(); 127 | } 128 | } 129 | 130 | const clearArr = true; 131 | const menu = new MenuManager('mainMenu', clearArr); 132 | const fMenu = new MenuManager('filterMenu', clearArr); 133 | const sMenu = new MenuManager('searchHistoryMenu', clearArr); 134 | const searchMenu = new MenuManager('searchMenu'); 135 | 136 | class MenuItems { 137 | constructor() { 138 | this.expandable = false; 139 | this.ix = -1; 140 | this.items = new FbMetadbHandleList(); 141 | this.nm = ''; 142 | this.pl = []; 143 | this.r_up = false; 144 | this.show_context = false; 145 | this.treeExpandLimit = $.file('C:\\check_local\\1450343922.txt') ? 6000 : $.clamp(ppt.treeExpandLimit, 10, 6000); 146 | this.playlists_changed(true); 147 | this.settingsBtnDn = false; 148 | this.validItem = false; 149 | } 150 | 151 | // Methods 152 | 153 | mainMenu() { 154 | menu.newMenu({hide: !this.settingsBtnDn && ppt.settingsShow && this.validItem}); 155 | 156 | if (this.validItem) { 157 | ['Send to current playlist' + '\tEnter', 'Add to current playlist' + '\tShift+enter', 'Send to new playlist' + '\tCtrl+enter', 'Show nowplaying'].forEach((v, i) => menu.newItem({ 158 | str: v, 159 | func: () => this.setPlaylist(i), 160 | flags: this.getPaylistFlag(i), 161 | separator: i == 2 162 | })); 163 | } 164 | 165 | if (this.validItem && ppt.albumArtOptionsShow) { 166 | menu.newItem({ 167 | str: !panel.imgView ? 'Show album art' : (!ppt.facetView ? 'Show tree' : 'Show text'), 168 | func: () => this.setPlaylist(4), 169 | flags: !panel.pn_h_auto || ppt.pn_h != ppt.pn_h_min ? MF_STRING : MF_GRAYED, 170 | separator: !panel.imgView //|| this.show_context && !ui.style.topBarShow 171 | }); 172 | } 173 | 174 | if (this.validItem && panel.imgView) { 175 | menu.newItem({ 176 | str: ppt.artId != 4 ? 'Show artists' : 'Show albums', 177 | func: () => {ppt.artId = ppt.artId != 4 ? 4 : 0; this.setPlaylist(5);}, 178 | separator: this.show_context && !ui.style.topBarShow 179 | }); 180 | } 181 | 182 | if (this.validItem && !panel.imgView) { 183 | ['Collapse all\tNum -', 'Expand\tNum *'].forEach((v, i) => menu.newItem({ 184 | str: v, 185 | func: () => this.setTreeState(i), 186 | flags: !i || i == 1 && this.expandable ? MF_STRING : MF_GRAYED, 187 | separator: i == 1 && this.show_context && (!ppt.settingsShow && !ppt.searchShow && !ppt.filterShow || this.shift) 188 | })); 189 | } 190 | 191 | menu.newMenu({menuName: 'Settings', hide: !this.show_context || ui.style.topBarShow && !this.shift}); 192 | 193 | const mainMenu = () => this.show_context ? 'Settings' : 'baseMenu'; 194 | 195 | menu.newMenu({menuName: 'Views', appendTo: mainMenu(), separator: true}); 196 | panel.menu.forEach((v, i) => menu.newItem({ 197 | menuName: 'Views', 198 | str: v, 199 | func: () => this.setView(i), 200 | checkRadio: i == ppt.viewBy, 201 | separator: i > panel.menu.length - 3 202 | })); 203 | 204 | const d = {} 205 | this.getSortData(d); 206 | menu.newMenu({menuName: d.menuName, appendTo: 'Views', flags: d.sortType ? MF_STRING : MF_GRAYED, separator: true}); 207 | if (d.sortType) { 208 | menu.newItem({ 209 | menuName: d.menuName, 210 | str: ['', 'By year', 'Albums by year'][d.sortType], 211 | flags: MF_GRAYED, 212 | separator: true 213 | }); 214 | const menuSort = [[], ['Default', 'Ascending', 'Descending'], ['Default', 'Ascending (hide year)', 'Ascending (show year)', 'Descending (hide year)', 'Descending (show year)', 'Action: year after album', 'Action: year before album']][d.sortType]; 215 | menuSort.forEach((v, i) => menu.newItem({ 216 | menuName: d.menuName, 217 | str: v, 218 | func: () => this.sortByDate(i, d), 219 | flags: i > 4 && (d.sortIX == 1 || d.sortIX == 3) ? MF_GRAYED : MF_STRING, 220 | checkRadio: d.sortIX == -1 && !i || i == d.sortIX || d.sortType == 2 && i == 5 && !ppt.yearBeforeAlbum || i == 6 && ppt.yearBeforeAlbum, 221 | separator: i == 0 || d.sortType == 2 && (i == 2 || i == 4) 222 | })); 223 | } 224 | 225 | menu.newItem({ 226 | menuName: 'Views', 227 | str: 'Configure views...', 228 | func: () => panel.open('views') 229 | }); 230 | 231 | menu.newMenu({menuName: 'Statistics', appendTo: mainMenu(), separator: true}); 232 | [pop.countsRight && !panel.imgView ? ['None', '# Tracks', '# Items'][pop.nodeCounts] : 'None', 'Bitrate', 'Duration', 'Total size', 'Rating', 'Popularity', 'Date', 'Playback queue', 'Playcount', 'First played', 'Last played', 'Added', 'Configure statistics...'].forEach((v, i) => menu.newItem({ 233 | menuName: 'Statistics', 234 | str: v, 235 | func: () => this.setStatistics(i), 236 | checkRadio: i == ppt.itemShowStatistics, 237 | separator: !i || i == 7 || i == 11 238 | })); 239 | 240 | menu.newMenu({menuName: 'Album art', appendTo: mainMenu(), hide: !panel.imgView}); 241 | ['Front', 'Back', 'Disc', 'Icon', 'Artist', 'Group: auto', 'Group: top level', 'Group: two levels', 'Change group name...', 'Configure album art...'].forEach((v, i) => menu.newItem({ 242 | menuName: 'Album art', 243 | str: v, 244 | func: () => this.setAlbumart(i), 245 | flags: i == 8 && (panel.folderView || ppt.rootNode != 3) ? MF_GRAYED : MF_STRING, 246 | checkRadio: i == ppt.artId || i - 5 == ppt.albumArtGrpLevel, 247 | separator: i == 4 || i == 7 || i == 8 248 | })); 249 | 250 | menu.newMenu({menuName: 'Quick setup', appendTo: mainMenu()}); 251 | ['Traditional', 'Modern [default]', 'Ultra-Modern', 'Clean', 'Facet'].forEach((v, i) => menu.newItem({ 252 | menuName: 'Quick setup', 253 | str: v, 254 | func: () => panel.set('quickSetup', i), 255 | separator: i == 3 || i == 4 256 | })); 257 | 258 | if (ppt.albumArtOptionsShow) { 259 | ['Covers [labels right]', 'Covers [labels bottom]', 'Covers [labels blend]', 'Artist photos [labels right]', 'Album art size +', 'Album art size -', 'Flow mode', 'Always load preset with current \'view\' pattern'].forEach((v, i) => menu.newItem({ 260 | menuName: 'Quick setup', 261 | str: v, 262 | func: () => panel.set('quickSetup', i + 5), 263 | flags: i == 4 && (ppt.thumbNailSize == 7 || !panel.imgView || ppt.albumArtFlowMode) || i == 5 && (ppt.thumbNailSize == 0 || !panel.imgView || ppt.albumArtFlowMode) ? MF_GRAYED : MF_STRING, 264 | checkItem: i == 7 && ppt.presetLoadCurView, 265 | separator: i == 2 || i == 3 || i == 5 || i == 6 266 | })); 267 | } 268 | 269 | menu.newMenu({menuName: 'Source', appendTo: mainMenu(), separator: true}); 270 | ['Library', 'Panel', 'Playlist'].forEach((v, i) => menu.newItem({ 271 | menuName: 'Source', 272 | str: v, 273 | func: () => this.setSource(i), 274 | checkRadio: i == (ppt.libSource - 1 < 0 || ppt.fixedPlaylist ? 2 : ppt.libSource - 1), 275 | separator: i == 2 276 | })); 277 | 278 | menu.newItem({ 279 | menuName: 'Source', 280 | str: 'Select source panel', 281 | func: () => this.setSourcePanel(), 282 | flags: ppt.libSource != 2 ? MF_GRAYED : MF_STRING, 283 | separator: true 284 | }); 285 | 286 | menu.newMenu({menuName: 'Select playlist', appendTo: 'Source'}); 287 | menu.newItem({ 288 | menuName: 'Select playlist', 289 | str: 'Active playlist', 290 | func: () => this.setActivePlaylist(), 291 | checkRadio: ppt.libSource == 0, 292 | separator: true 293 | }); 294 | 295 | const pl_no = Math.ceil(this.pl.length / 30); 296 | const pl_ix = ppt.fixedPlaylist ? plman.FindPlaylist(ppt.fixedPlaylistName) : -1; 297 | for (let j = 0; j < pl_no; j++) { 298 | const n = '# ' + (j * 30 + 1 + ' - ' + Math.min(this.pl.length, 30 + j * 30) + (30 + j * 30 > pl_ix && ((j * 30) - 1) < pl_ix ? ' >>>' : '')); 299 | menu.newMenu({menuName: n, appendTo: 'Select playlist'}); 300 | for (let i = j * 30; i < Math.min(this.pl.length, 30 + j * 30); i++) { 301 | menu.newItem({ 302 | menuName: n, 303 | str: this.pl[i].menuName, 304 | func: () => this.setFixedPlaylist(i), 305 | checkRadio: i == pl_ix 306 | }); 307 | } 308 | } 309 | 310 | menu.newMenu({menuName: 'Refresh', appendTo: mainMenu(), separator: true}); 311 | for (let i = 0; i < 5; i++) menu.newItem({ 312 | menuName: 'Refresh', 313 | str: ['Refresh selected images...', 'Refresh all images...', 'Reset zoom...', 'Refresh library...', 'Reload...'][i], 314 | func: () => this.setMode(i), 315 | flags: panel.imgView && !i && this.items.Count || !panel.imgView || i ? MF_STRING : MF_GRAYED, 316 | separator: i == 1 && panel.imgView, 317 | hide: i < 2 && !panel.imgView || i == 3 && ppt.libAutoSync 318 | }); 319 | 320 | for (let i = 0; i < 2; i++) menu.newItem({ 321 | menuName: mainMenu(), 322 | str: [popUpBox.ok ? 'Options...' : 'Options: see console', 'Configure...'][i], 323 | func: () => !i ? panel.open() : window.EditScript(), 324 | separator: !i && this.shift, 325 | hide: !this.settingsBtnDn && ppt.settingsShow && this.validItem && !this.shift || i && !this.shift 326 | }); 327 | } 328 | 329 | filterMenu() { 330 | fMenu.newMenu({}); 331 | for (let i = 0; i < panel.filter.menu.length + 1; i++) fMenu.newItem({ 332 | str: i != panel.filter.menu.length ? (!i ? 'No filter' : panel.filter.menu[i]) : 'Auto-manage scroll', 333 | func: () => panel.set('Filter', i), 334 | checkItem: i == panel.filter.menu.length && !ppt.reset, 335 | checkRadio: i == ppt.filterBy && i < panel.filter.menu.length, 336 | separator: !i || i == panel.filter.menu.length - 1 || i == panel.filter.menu.length 337 | }); 338 | fMenu.newItem({ 339 | str: 'Configure filters...', 340 | func: () => panel.open('filters'), 341 | }); 342 | } 343 | 344 | searchHistoryMenu() { 345 | sMenu.newMenu({}); 346 | for (let i = 0; i < search.menu.length + 2; i++) sMenu.newItem({ 347 | str: !i ? 'Query syntax help' : i < search.menu.length + 1 ? search.menu[i - 1].search : 'Clear history', 348 | func: () => this.setSearchHistory(i), 349 | flags: i != 1 || search.menu.length ? MF_STRING : MF_GRAYED, 350 | separator: !i || search.menu.length && i == search.menu.length 351 | }); 352 | } 353 | 354 | searchMenu() { 355 | searchMenu.newMenu({}); 356 | ['Copy', 'Cut', 'Paste'].forEach((v, i) => searchMenu.newItem({ 357 | str: v, 358 | func: () => this.setEdit(i), 359 | flags: () => search.start == search.end && i < 2 || i == 2 && !search.paste ? MF_GRAYED : MF_STRING, 360 | separator: i == 1 361 | })); 362 | } 363 | 364 | getPaylistFlag(i) { 365 | const pln = plman.ActivePlaylist; 366 | const plnIsValid = pln != -1 && pln < plman.PlaylistCount; 367 | const plLockAdd = plnIsValid ? plman.GetPlaylistLockedActions(pln).includes('AddItems') : false; 368 | const plLockRemoveOrAdd = plnIsValid ? plman.GetPlaylistLockedActions(pln).includes('RemoveItems') || plman.GetPlaylistLockedActions(pln).includes('ReplaceItems') || plLockAdd : false; 369 | return !i && !plLockRemoveOrAdd || i == 1 && !plLockAdd || i == 2 || i == 3 && pop.nowp != -1 ? MF_STRING : MF_GRAYED 370 | } 371 | 372 | getSortData(d) { 373 | d.name = panel.propNames[ppt.viewBy]; 374 | d.sortAlbumsByYearAfter = [``, `[$nodisplay{$sub(%date%,0#)}]%album%`, `[$nodisplay{$sub(%date%,0)}]%album%[ '['$sub(%date%,0)']']`, `[$nodisplay{$sub(4001,%date%)}]%album%`, `[$nodisplay{$sub(4002,%date%)}]%album%[ '['$sub(%date%,0)']']`]; 375 | d.sortAlbumsByYearBefore = [``, `[$nodisplay{$sub(%date%,0)}]%album%`, `['['$sub(%date%,0)']' - ]%album%`, `[$nodisplay{$sub(4003,%date%)}]%album%`, `[$nodisplay{$sub(4004,%date%)}]['['$sub(%date%,0)']' - ]%album%`]; 376 | d.sortAlbumByYear = ppt.yearBeforeAlbum ? d.sortAlbumsByYearBefore : d.sortAlbumsByYearAfter; 377 | d.sortIX = -1; 378 | d.sortType = 0; 379 | d.sortYear = [``, `$if2($nodisplay{$sub(%date%,0)},$nodisplay{-4000})`, `$nodisplay{$sub(4000,%date%)}`]; 380 | d.value = ppt.get(d.name) || ''; 381 | d.valueLength = d.value.length; 382 | let l = d.sortYear.length; 383 | while(l-- && l) { 384 | d.value = d.value.replace(RegExp($.regexEscape(d.sortYear[l]), 'g'), '') 385 | if (d.valueLength != d.value.length) { 386 | d.sortIX = l; 387 | d.valueLength = d.value.length; 388 | } 389 | } 390 | if (d.sortIX == -1) { 391 | l = d.sortAlbumsByYearAfter.length; 392 | while(l-- && l) { 393 | d.value = d.value.replace(RegExp($.regexEscape(d.sortAlbumsByYearAfter[l]), 'g'), '%album%'); 394 | if (d.valueLength != d.value.length) { 395 | d.sortIX = l; 396 | d.valueLength = d.value.length; 397 | } 398 | } 399 | } 400 | if (d.sortIX == -1) { 401 | l = d.sortAlbumsByYearBefore.length; 402 | while(l-- && l) { 403 | d.value = d.value.replace(RegExp($.regexEscape(d.sortAlbumsByYearBefore[l]), 'g'), '%album%'); 404 | if (d.valueLength != d.value.length) { 405 | d.sortIX = l; 406 | d.valueLength = d.value.length; 407 | } 408 | } 409 | } 410 | if (d.value.includes('//') && /%year%|%date%/.test(d.value)) d.sortType = 1; 411 | else if (d.value.includes('%album%')) d.sortType = 2; 412 | 413 | d.menuName = d.sortType ? 'Sort selected view' : 'Sort N/A for selected view pattern'; 414 | } 415 | 416 | loadView(clearCache, view, sel) { 417 | ui.getColours(); 418 | sbar.setCol(); 419 | but.createImages(); 420 | if (clearCache) img.clearCache(); 421 | if (sel !== undefined) { 422 | const handle = sel >= panel.list.Count ? null : panel.list[sel]; 423 | panel.set('view', view, true); 424 | if (handle) { 425 | const item = panel.list.Find(handle); 426 | let idx = -1; 427 | pop.tree.forEach((v, i) => { 428 | if (pop.inRange(item, v.item)) idx = i; 429 | }); 430 | if (idx != -1) { 431 | if (!panel.imgView) pop.focusShow(idx); 432 | else pop.showItem(idx, 'focus'); 433 | } 434 | } 435 | } else panel.set('view', view, true); 436 | but.refresh(true); 437 | } 438 | 439 | playlists_changed() { 440 | this.pl = []; 441 | for (let i = 0; i < plman.PlaylistCount; i++) this.pl.push({ 442 | menuName: plman.GetPlaylistName(i).replace(/&/g, '&&'), 443 | name: plman.GetPlaylistName(i), 444 | ix: i 445 | }); 446 | } 447 | 448 | rbtn_up(x, y, settingsBtnDn) { 449 | this.r_up = true; 450 | this.expandable = false; 451 | this.items = new FbMetadbHandleList(); 452 | this.ix = pop.get_ix(x, y, true, false); 453 | this.nm = ''; 454 | this.settingsBtnDn = settingsBtnDn; 455 | this.shift = vk.k('shift'); 456 | this.show_context = false; 457 | 458 | let item = pop.tree[this.ix]; 459 | let row = -1; 460 | const level = pop.tree.length > this.ix && this.ix != -1 ? !pop.inlineRoot ? item.level : Math.max(item.level - 1, 0) : -1; 461 | 462 | this.validItem = this.settingsBtnDn ? false : !panel.imgView ? y < panel.tree.y + pop.rows * sbar.row.h && pop.tree.length > this.ix && this.ix != -1 && (x < Math.round(ppt.treeIndent * level) + ui.icon.w + ppt.margin && (!item.track || item.root) || pop.check_ix(item, x, y, true)) : pop.tree.length > this.ix && this.ix != -1; 463 | 464 | if (!this.validItem && !this.settingsBtnDn && ppt.settingsShow && y > panel.search.sp) { 465 | this.ix = pop.row.i != -1 ? pop.row.i : !panel.imgView ? pop.tree.length - 1 : -1; 466 | if (this.ix < pop.tree.length && this.ix != -1) { 467 | item = pop.tree[this.ix]; 468 | this.validItem = true; 469 | } 470 | } 471 | 472 | if (this.validItem) { 473 | if (!item.sel) { 474 | pop.clearSelected(); 475 | item.sel = true; 476 | } 477 | pop.getTreeSel(); 478 | this.expandable = pop.trackCount(pop.tree[this.ix].item) > this.treeExpandLimit || pop.tree[this.ix].track || panel.imgView ? false : true; 479 | if (this.expandable && pop.tree.length) { 480 | let count = 0; 481 | pop.tree.forEach((v, m, arr) => { 482 | if (m == this.ix || v.sel) { 483 | if (row == -1 || m < row) { 484 | row = m; 485 | this.nm = (v.level ? arr[v.par].srt[0] : '') + v.srt[0]; 486 | this.nm = this.nm.toUpperCase(); 487 | } 488 | count += pop.trackCount(v.item); 489 | this.expandable = count <= this.treeExpandLimit; 490 | } 491 | }); 492 | } 493 | this.items = pop.getHandleList(); 494 | this.show_context = true; 495 | } else this.items = pop.getHandleList('newItems'); 496 | 497 | menu.load(x, y); 498 | this.r_up = false; 499 | } 500 | 501 | setActivePlaylist() { 502 | ppt.libSource = 0; 503 | ppt.fixedPlaylist = false; 504 | ppt.fixedPlaylistName = 'ActivePlaylist'; 505 | if (panel.imgView) img.clearCache(); 506 | lib.searchCache = {}; 507 | if (ppt.showSource) panel.setRootName(); 508 | lib.treeState(false, 2); 509 | } 510 | 511 | setAlbumart(i) { 512 | let clearCache = false; 513 | switch (i) { 514 | case 0: 515 | case 1: 516 | case 2: 517 | case 3: 518 | case 4: 519 | ppt.artId = i; 520 | break; 521 | case 5: 522 | case 6: 523 | case 7: 524 | ppt.albumArtGrpLevel = i - 5; 525 | break; 526 | case 8: { 527 | const key = `${panel.grp[ppt.viewBy].type.trim()}${panel.lines}`; 528 | const ok_callback = (status, input) => { 529 | if (status != 'cancel') { 530 | const albumArtGrpNames = $.jsonParse(ppt.albumArtGrpNames, {}); 531 | albumArtGrpNames[key] = input; 532 | ppt.albumArtGrpNames = JSON.stringify(albumArtGrpNames); 533 | } 534 | } 535 | const caption = 'Change group name'; 536 | const def = img.groupField; 537 | const prompt = 'Enter SINGULAR name, i.e. not plural\n\nName is pinned to VIEW PATTERN and GROUP LEVEL'; 538 | const fallback = popUpBox.isHtmlDialogSupported() ? popUpBox.input(caption, prompt, ok_callback, '', def) : true; 539 | if (fallback) { 540 | let ns = ''; 541 | let status = 'ok'; 542 | try { 543 | ns = utils.InputBox(0, prompt, caption, def, true); 544 | } catch(e) { 545 | status = 'cancel'; 546 | } 547 | ok_callback(status, ns); 548 | } 549 | break; 550 | } 551 | case 9: 552 | panel.open('albumArt'); 553 | break; 554 | } 555 | this.loadView(clearCache, ppt.albumArtViewBy); 556 | } 557 | 558 | setEdit(i) { 559 | switch (i) { 560 | case 0: 561 | search.on_char(vk.copy); 562 | break; 563 | case 1: 564 | search.on_char(vk.cut); 565 | break; 566 | case 2: 567 | search.on_char(vk.paste, true); 568 | break; 569 | } 570 | } 571 | 572 | setFixedPlaylist(i) { 573 | ppt.fixedPlaylistName = this.pl[i].name; 574 | ppt.fixedPlaylist = true; 575 | ppt.libSource = 1; 576 | if (panel.imgView) img.clearCache(); 577 | if (ppt.showSource) panel.setRootName(); 578 | lib.searchCache = {}; 579 | lib.treeState(false, 2); 580 | } 581 | 582 | setMode(i) { 583 | switch (i) { 584 | case 0: 585 | img.refresh(this.items); 586 | break; 587 | case 1: 588 | img.refresh('all'); 589 | break; 590 | case 2: 591 | panel.zoomReset(); 592 | break; 593 | case 3: 594 | lib.treeState(false, 2); 595 | break; 596 | case 4: 597 | window.Reload(); 598 | break; 599 | } 600 | } 601 | 602 | setPlaylist(i) { 603 | switch (i) { 604 | case 0: 605 | pop.load(pop.sel_items, true, false, pop.autoPlay.send, false, false); 606 | panel.treePaint(); 607 | lib.treeState(false, ppt.rememberTree); 608 | break; 609 | case 1: 610 | pop.load(pop.sel_items, true, true, false, false, false); 611 | lib.treeState(false, ppt.rememberTree); 612 | break; 613 | case 2: 614 | pop.sendToNewPlaylist(); 615 | panel.treePaint(); 616 | lib.treeState(false, ppt.rememberTree); 617 | break; 618 | case 3: 619 | pop.nowPlayingShow(); 620 | break; 621 | case 4: 622 | lib.logTree(); 623 | pop.clearTree(); 624 | ppt.toggle('albumArtShow'); 625 | panel.imgView = ppt.albumArtShow; 626 | this.loadView(false, !panel.imgView ? (ppt.artTreeSameView ? ppt.viewBy : ppt.treeViewBy) : (ppt.artTreeSameView ? ppt.viewBy : ppt.albumArtViewBy), pop.sel_items[0]); 627 | break; 628 | case 5: 629 | lib.logTree(); 630 | pop.clearTree(); 631 | this.loadView(false, !panel.imgView ? (ppt.artTreeSameView ? ppt.viewBy : ppt.treeViewBy) : (ppt.artTreeSameView ? ppt.viewBy : ppt.albumArtViewBy), pop.sel_items[0]); 632 | break; 633 | } 634 | } 635 | 636 | setSearchHistory(i) { 637 | switch (true) { 638 | case !i: { 639 | let fn = fb.FoobarPath + 'doc\\Query Syntax Help.html'; 640 | if (!$.file(fn)) fn = fb.FoobarPath + 'Query Syntax Help.html'; 641 | $.browser('"' + fn); 642 | break; 643 | } 644 | case i < search.menu.length + 1: 645 | panel.search.txt = search.menu[i - 1].search; 646 | search.menu[i - 1].accessed = Date.now(); 647 | search.focus(); 648 | but.setSearchBtnsHide(); 649 | lib.search(); 650 | break; 651 | case i == search.menu.length + 1: 652 | search.menu = []; 653 | ppt.searchHistory = JSON.stringify([]); 654 | break; 655 | } 656 | } 657 | 658 | setSource(i) { 659 | switch (i) { 660 | case 0: 661 | ppt.libSource = 1; 662 | ppt.fixedPlaylist = false; 663 | break; 664 | case 1: 665 | ppt.libSource = 2; 666 | ppt.fixedPlaylist = false; 667 | if (ppt.panelSourceMsg && popUpBox.isHtmlDialogSupported()) popUpBox.message(); 668 | break; 669 | case 2: { 670 | const fixedPlaylistIndex = plman.FindPlaylist(ppt.fixedPlaylistName); 671 | if (fixedPlaylistIndex != -1) ppt.fixedPlaylist = true; 672 | ppt.libSource = ppt.fixedPlaylist ? 1 : 0; 673 | if (ppt.panelSourceMsg && popUpBox.isHtmlDialogSupported()) popUpBox.message(); 674 | break; 675 | } 676 | } 677 | if (panel.imgView) img.clearCache(); 678 | lib.searchCache = {}; 679 | if (ppt.showSource) panel.setRootName(); 680 | lib.treeState(false, 2); 681 | } 682 | 683 | setSourcePanel() { 684 | const ok_callback = (status, input) => { 685 | if (status != 'cancel') { 686 | ppt.panelSelectionPlaylist = input; 687 | } 688 | } 689 | const caption = 'Panel source name'; 690 | const def = ppt.panelSelectionPlaylist; 691 | const prompt = 'Enter source panel name\n\n• To get the name, go to the library tree panel to be used as source\n• Press shift + windows key and choose configure\n• Paste the panel name or id at the top into here\n• Name is also used for a cache playlist that remembers last open state\n• Edit source panel name if required\n• For more than one source panel, use pipe separator, e.g. Genre|Artist' 692 | const fallback = popUpBox.isHtmlDialogSupported() ? popUpBox.input(caption, prompt, ok_callback, '', def) : true; 693 | if (fallback) { 694 | let ns = ''; 695 | let status = 'ok'; 696 | try { 697 | ns = utils.InputBox(0, prompt, caption, def, true); 698 | } catch(e) { 699 | status = 'cancel'; 700 | } 701 | ok_callback(status, ns); 702 | } 703 | } 704 | 705 | setStatistics(i) { 706 | if (i < 12) { 707 | const curStatisticsShown = ppt.itemShowStatistics > 0; 708 | ppt.itemShowStatistics = i; 709 | ppt.itemShowStatisticsLast = ppt.itemShowStatistics; 710 | pop.tree.forEach(v => { 711 | v.id = ''; 712 | v.count = ''; // has to reset parentheses if stats change off/on 713 | delete v.statistics; 714 | delete v._statistics; 715 | }); 716 | pop.cache = { 717 | 'standard': {}, 718 | 'search': {}, 719 | 'filter': {} 720 | } 721 | pop.statisticsShow = ppt.itemShowStatistics; 722 | pop.label = !ppt.labelStatistics || !pop.statisticsShow ? '' : pop.statistics[pop.statisticsShow]; 723 | const statisticsShown = ppt.itemShowStatistics > 0; 724 | if (panel.imgView && curStatisticsShown != statisticsShown) { 725 | img.labels = {statistics: ppt.itemShowStatistics ? 1 : 0} 726 | img.clearCache(); 727 | panel.set('view', ppt.viewBy); 728 | } 729 | panel.treePaint(); 730 | } else panel.open('display'); 731 | } 732 | 733 | setTreeState(i) { 734 | switch (i) { 735 | case 0: 736 | pop.collapseAll(); 737 | break; 738 | case 1: 739 | pop.expand(this.ix, this.nm); 740 | panel.setHeight(true); 741 | break; 742 | } 743 | pop.checkAutoHeight(); 744 | } 745 | 746 | setView(i) { 747 | if (i < panel.menu.length) { 748 | if (ppt.artTreeSameView) { 749 | ppt.treeViewBy = i; 750 | ppt.albumArtViewBy = i; 751 | } else { 752 | if (!panel.imgView) ppt.treeViewBy = i; 753 | else ppt.albumArtViewBy = i; 754 | if (ppt.treeViewBy != ppt.albumArtViewBy) { 755 | ppt.set(panel.imgView ? 'Tree' : 'Tree Image', null); 756 | ppt.set(panel.imgView ? 'Tree Search' : 'Tree Image Search', null); 757 | } 758 | } 759 | panel.set('view', i); 760 | } 761 | } 762 | 763 | sortByDate(i, d) { 764 | let sortByIX = -1; 765 | if (i > 4) { 766 | ppt.toggle('yearBeforeAlbum'); 767 | d.sortAlbumByYear = ppt.yearBeforeAlbum ? d.sortAlbumsByYearBefore : d.sortAlbumsByYearAfter; 768 | sortByIX = d.sortIX; 769 | } else sortByIX = i; 770 | if (d.sortType == 1) { 771 | if (i) { 772 | let str = d.value.split('//'); 773 | if (str[1]) { 774 | str[1] = str[1].trim().replace(/(\|\s*)(.*?(%year%|%date%))/g, '$1' + d.sortYear[i] + '$2') 775 | if (!/\|.*?(%year%|%date%)/.test(str[1])) str[1] = d.sortYear[i] + str[1]; 776 | d.value = str[0].trim() + ' // ' + str[1]; 777 | } else d.value = str[0]; 778 | } 779 | } else if (d.sortType == 2 && i && sortByIX != -1) { 780 | d.value = d.value.replace(/%album%/g, d.sortAlbumByYear[sortByIX]) 781 | } 782 | if (d.sortType == 1 || sortByIX != -1) { 783 | const expanded = []; 784 | const ix = pop.get_ix(!panel.imgView ? 0 : img.panel.x + 1, (!panel.imgView || img.style.vertical ? panel.tree.y : panel.tree.x) + sbar.row.h / 2, true, false); 785 | const curName = ix != -1 ? pop.tree[ix].name : ''; 786 | const scrollPos = sbar.scroll; 787 | const selected = []; 788 | pop.tree.forEach((v, i) => { 789 | const level = !ppt.rootNode ? v.level : v.level - 1; // 1 level memory: more is less reliable 790 | if (!level) { 791 | if (v.child.length) expanded.push(i); 792 | if (v.sel) selected.push(i); 793 | } 794 | }); 795 | ppt.set(d.name, d.value); 796 | pop.clearTree(); 797 | panel.getViews(); 798 | this.setView(ppt.viewBy); 799 | if (ix < pop.tree.length) { 800 | const name = ix != -1 ? pop.tree[ix].name : ''; 801 | if (name && name == curName) { 802 | expanded.forEach(v => { 803 | if (v < pop.tree.length) { 804 | const item = pop.tree[v]; 805 | pop.branch(item, !item.root ? false : true, true); 806 | } 807 | }); 808 | selected.forEach(v => { 809 | if (v < pop.tree.length) { 810 | pop.tree[v].sel = true; 811 | } 812 | }); 813 | } 814 | } 815 | sbar.checkScroll(scrollPos, 'full', true); 816 | } 817 | } 818 | } -------------------------------------------------------------------------------- /scripts/popupbox.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class PopUpBox { 4 | constructor() { 5 | this.getHtmlCode(); 6 | this.ok = true; 7 | this.soFeat = {clipboard: true, gecko: true} 8 | } 9 | 10 | // Methods 11 | 12 | config(cfg, ppt, cfgWindow, ok_callback) { 13 | utils.ShowHtmlDialog(0, this.configHtmlCode, { 14 | data: [cfg, ppt, cfgWindow, ok_callback], 15 | resizable: true 16 | }); 17 | } 18 | 19 | confirm(msg_title, msg_content, btn_yes_label, btn_no_label, height_adjust, h_center, confirm_callback) { 20 | utils.ShowHtmlDialog(0, this.confirmHtmlCode, { 21 | data: [msg_title, msg_content, btn_yes_label, btn_no_label, height_adjust, h_center, confirm_callback] 22 | }); 23 | } 24 | 25 | getHtmlCode() { 26 | let cssPath = `${my_utils.packagePath}/assets/html/`; 27 | if (this.getWindowsVersion() === '6.1') { 28 | cssPath += 'styles7.css'; 29 | } else { 30 | cssPath += 'styles10.css'; 31 | } 32 | this.configHtmlCode = my_utils.getAsset('\\html\\config.html').replace(/href="styles10.css"/i, `href="${cssPath}"`); 33 | this.inputHtmlCode = my_utils.getAsset('\\html\\input.html').replace(/href="styles10.css"/i, `href="${cssPath}"`); 34 | this.messageHtmlCode = my_utils.getAsset('\\html\\messageBox.html').replace(/href="styles10.css"/i, `href="${cssPath}"`); 35 | this.confirmHtmlCode = my_utils.getAsset('\\html\\confirm.html').replace(/href="styles10.css"/i, `href="${cssPath}"`); 36 | } 37 | 38 | getWindowsVersion() { 39 | let version = ''; 40 | try { 41 | version = (WshShell.RegRead('HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\CurrentMajorVersionNumber')).toString(); 42 | version += '.'; 43 | version += (WshShell.RegRead('HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\CurrentMinorVersionNumber')).toString(); 44 | return version; 45 | } catch (e) {} 46 | try { 47 | version = WshShell.RegRead('HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\CurrentVersion'); 48 | return version; 49 | } catch (e) {} 50 | return '6.1'; 51 | } 52 | 53 | input(title, msg, ok_callback, input, def) { 54 | utils.ShowHtmlDialog(0, this.inputHtmlCode, { 55 | data: [title, msg, 'Cancel', ok_callback, input, def] 56 | }); 57 | } 58 | 59 | isHtmlDialogSupported() { 60 | if (ppt.isHtmlDialogSupported != 2) return ppt.isHtmlDialogSupported; 61 | 62 | if (typeof doc === 'undefined' || !doc) { 63 | this.soFeat.gecko = false; 64 | } 65 | if (this.soFeat.gecko) { 66 | let cache = null; 67 | let clText = 'test'; 68 | try { 69 | cache = doc.parentWindow.clipboardData.getData('Text'); 70 | } catch (e) {} 71 | try { 72 | doc.parentWindow.clipboardData.setData('Text', clText); 73 | clText = doc.parentWindow.clipboardData.getData('Text'); 74 | } catch (e) { 75 | this.soFeat.clipboard = false; 76 | } 77 | if (cache) { // Just in case previous clipboard data is needed 78 | try { 79 | doc.parentWindow.clipboardData.setData('Text', cache); 80 | } catch (e) {} 81 | } 82 | if (clText !== 'test') { 83 | this.soFeat.clipboard = false; 84 | } 85 | } else { 86 | this.soFeat.clipboard = false; 87 | } 88 | 89 | ppt.isHtmlDialogSupported = this.soFeat.gecko && this.soFeat.clipboard || this.isIEInstalled() ? 1 : 0; 90 | if (!ppt.isHtmlDialogSupported) { 91 | const caption = 'Show HTML Dialog'; 92 | const prompt = 93 | `A feature check indicates that Spider Monkey Panel show html dialog isn't supported by the current operating system. 94 | 95 | This is used to display options. The console will show alternatives on closing this dialog. 96 | 97 | Occassionally, the feature check may give the wrong answer. 98 | 99 | If you're using windows and have Internet Explorer support it should work, so enter 1 and press OK. 100 | 101 | The setting is saved in panel properties as the first item and can be changed there later. 102 | 103 | Supported-1; unsupported-0`; 104 | let ns = ''; 105 | let status = 'ok' 106 | try { 107 | ns = utils.InputBox(0, prompt, caption, ppt.isHtmlDialogSupported, true); 108 | } catch(e) { 109 | status = 'cancel'; 110 | } 111 | if (status != 'cancel') { 112 | ppt.isHtmlDialogSupported = ns == 0 ? 0 : 1; 113 | } 114 | } 115 | return ppt.isHtmlDialogSupported; 116 | } 117 | 118 | isIEInstalled() { 119 | const diskLetters = Array.from(Array(26)).map((e, i) => i + 65).map((x) => `${String.fromCharCode(x)}:\\`); 120 | const paths = ['Program Files\\Internet Explorer\\ieinstal.exe', 'Program Files (x86)\\Internet Explorer\\ieinstal.exe']; 121 | return diskLetters.some(d => { 122 | try { // Needed when permission error occurs and current SMP implementation is broken for some devices.... 123 | return utils.IsDirectory(d) ? paths.some(p => utils.IsFile(d + p)) : false; 124 | } catch (e) {return false;} 125 | }); 126 | } 127 | 128 | message() { 129 | utils.ShowHtmlDialog(0, this.messageHtmlCode, { 130 | data: [this.window_ok_callback, $.scale], 131 | selection: true 132 | }); 133 | } 134 | 135 | window_ok_callback(status, clicked) { 136 | if (clicked) ppt.panelSourceMsg = false; 137 | } 138 | } -------------------------------------------------------------------------------- /scripts/properties.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class PanelProperty { 4 | constructor(name, default_value) { 5 | this.name = name; 6 | this.default_value = default_value; 7 | this.value = ppt.get(this.name, default_value); 8 | } 9 | 10 | // Methods 11 | 12 | get() { 13 | return this.value; 14 | } 15 | set(new_value) { 16 | if (this.value !== new_value) { 17 | ppt.set(this.name, new_value); 18 | this.value = new_value; 19 | } 20 | } 21 | } 22 | 23 | class PanelProperties { 24 | constructor() { 25 | // this.name_list = {}; debug 26 | } 27 | 28 | // Methods 29 | 30 | init(type, properties, thisArg) { 31 | switch (type) { 32 | case 'auto': 33 | properties.forEach(v => { 34 | // this.validate(v); debug 35 | this.add(v); 36 | }); 37 | break; 38 | case 'manual': 39 | properties.forEach(v => thisArg[v[2]] = this.get(v[0], v[1])); 40 | break; 41 | } 42 | } 43 | 44 | validate(item) { 45 | if (!$.isArray(item) || item.length !== 3 || typeof item[2] !== 'string') { 46 | throw (`invalid property: requires array: [string, any, string]`); 47 | } 48 | 49 | if (item[2] === 'add') { 50 | throw (`property_id: ${item[2]}\nThis id is reserved`); 51 | } 52 | 53 | if (this[item[2]] != null || this[item[2] + '_internal'] != null) { 54 | throw (`property_id: ${item[2]}\nThis id is already occupied`); 55 | } 56 | 57 | if (this.name_list[item[0]] != null) { 58 | throw (`property_name: ${item[0]}\nThis name is already occupied`); 59 | } 60 | } 61 | 62 | add(item) { 63 | // this.name_list[item[0]] = 1; debug 64 | this[`${item[2]}_internal`] = new PanelProperty(item[0], item[1]); 65 | 66 | Object.defineProperty(this, item[2], { 67 | get() { 68 | return this[`${item[2]}_internal`].get(); 69 | }, 70 | set(new_value) { 71 | this[`${item[2]}_internal`].set(new_value); 72 | } 73 | }); 74 | } 75 | 76 | get(name, default_value) { 77 | return window.GetProperty(name, default_value); 78 | } // initialisation 79 | 80 | set(name, new_value) { 81 | return window.SetProperty(name, new_value); 82 | } 83 | 84 | toggle(name) { 85 | this[name] = !this[name]; 86 | } 87 | } 88 | 89 | let properties = [ 90 | ['- Show Html Dialog Unsupported-0 Supported-1 Autocheck-2', 2, 'isHtmlDialogSupported'], 91 | ['Action Mode', 0, 'actionMode'], 92 | ['Alt-Click Action', 0, 'altClickAction'], 93 | 94 | ['Colour Line Dark', false, 'colLineDark'], 95 | ['Colour Swap', false, 'swapCol'], 96 | ['Cover Auto-Fill', true, 'autoFill'], 97 | ['Cover Opacity (0-100)', 10, 'covAlpha'], 98 | 99 | ['Custom Colour Text', '171,171,190', 'text'], 100 | ['Custom Colour Text Highlight', '121,194,255', 'text_h'], 101 | ['Custom Colour Text Selected', '255,255,255', 'textSel'], 102 | ['Custom Colour Text Nowplaying Highlight', 'rgb(121-194-255)', 'nowp'], 103 | ['Custom Colour Search Text', '171,171,190', 'search'], 104 | ['Custom Colour Buttons', '113,125,147', 'txt_box'], 105 | ['Custom Colour Background', '4,39,68', 'bg'], 106 | ['Custom Colour Background Accent', '18,52,85', 'bg_h'], 107 | ['Custom Colour Background Selected', '37,71,108', 'bgSel'], 108 | ['Custom Colour Frame Hover', '35,132,182', 'frame'], 109 | ['Custom Colour Frame Selected', '49,145,198', 'bgSelframe'], 110 | ['Custom Colour Item Counts', '171,171,190', 'counts'], 111 | ['Custom Colour Node Collapse', '171,171,190', 'icon_c'], 112 | ['Custom Colour Node Expand', '171,171,190', 'icon_e'], 113 | ['Custom Colour Node Hover', '121,194,255', 'icon_h'], 114 | ['Custom Colour Node Lines', '48,70,90', 'line'], 115 | ['Custom Colour Separators', '48,71,90', 's_line'], 116 | ['Custom Colour Side Marker', '121,194,255', 'sideMarker'], 117 | ['Custom Colour Transparent Fill', '0,0,0,0.06', 'bgTrans'], 118 | 119 | ['Custom Colour Text Use', false, 'textUse'], 120 | ['Custom Colour Text Highlight Use', false, 'text_hUse'], 121 | ['Custom Colour Text Selected Use', false, 'textSelUse'], 122 | ['Custom Colour Text Nowplaying Highlight Use', false, 'nowpUse'], 123 | ['Custom Colour Search Text Use', false, 'searchUse'], 124 | ['Custom Colour Buttons Use', false, 'txt_boxUse'], 125 | ['Custom Colour Background Use', false, 'bgUse'], 126 | ['Custom Colour Background Accent Use', false, 'bg_hUse'], 127 | ['Custom Colour Background Selected Use', false, 'bgSelUse'], 128 | ['Custom Colour Frame Hover Use', false, 'frameUse'], 129 | ['Custom Colour Frame Selected Use', false, 'bgSelframeUse'], 130 | ['Custom Colour Item Counts Use', false, 'countsUse'], 131 | ['Custom Colour Node Collapse Use', false, 'icon_cUse'], 132 | ['Custom Colour Node Expand Use', false, 'icon_eUse'], 133 | ['Custom Colour Node Hover Use', false, 'icon_hUse'], 134 | ['Custom Colour Node Lines Use', false, 'lineUse'], 135 | ['Custom Colour Separators Use', false, 's_lineUse'], 136 | ['Custom Colour Side Marker Use', false, 'sideMarkerUse'], 137 | ['Custom Colour Transparent Fill Use', false, 'bgTransUse'], 138 | 139 | ['Custom Font', 'Segoe UI,16,0', 'custFont'], 140 | ['Custom Font Album Art Line 1', 'Segoe UI,1', 'custAlbumArtGrpFont'], 141 | ['Custom Font Album Art Line 2', 'Segoe UI Semibold,0', 'custAlbumArtLotFont'], 142 | ['Custom Font Album Art Line 3', 'Segoe UI,0', 'custAlbumArtDurFont'], 143 | 144 | ['Custom Font Use', false, 'custFontUse'], 145 | ['Custom Font Album Art Line 1 Use', false, 'custAlbumArtGrpFontUse'], 146 | ['Custom Font Album Art Line 2 Use', false, 'custAlbumArtLotFontUse'], 147 | ['Custom Font Album Art Line 3 Use', false, 'custAlbumArtDurFontUse'], 148 | 149 | ['Custom Font Node Icon', 'Segoe UI Symbol', 'custIconFont'], 150 | ['Custom Font Scroll Icon', 'Segoe UI Symbol', 'butCustIconFont'], 151 | 152 | ['Double-Click Action', 1, 'dblClickAction'], 153 | ['Facet View', false, 'facetView'], 154 | ['Filter By', 0, 'filterBy'], 155 | ['Font Size', 16, 'baseFontSize'], 156 | ['Full Line Selection', true, 'fullLineSelection'], 157 | ['Height', 578, 'pn_h'], 158 | ['Height Auto [Expand/Collapse With Root]', false, 'pn_h_auto'], 159 | ['Height Auto-Collapse', 100, 'pn_h_min'], 160 | ['Height Auto-Expand', 578, 'pn_h_max'], 161 | ['Highlight Row', 2, 'highLightRow'], 162 | ['Highlight Frame Image', false, 'frameImage'], 163 | ['Highlight Text', false, 'highLightText'], 164 | ['Hot Key [Focus Not Needed]: 1-10 // Assign Spider Monkey Panel index in keyboard shortcuts', 'CollapseAll,0,PlaylistAdd,0,PlaylistInsert,0,PlaylistNew,0,Search,0,SearchClear,0', 'hotKeys'], 165 | 166 | ['Image Blur Background Auto-Fill', false, 'blurAutofill'], 167 | ['Image Blur Background Level (%)', 90, 'blurTemp'], 168 | ['Image Blur Background Opacity (%)', 30, 'blurAlpha'], 169 | ['Image Current Root', 17, 'curRootImg'], 170 | ['Image Current No Artist', 2, 'curNoArtistImg'], 171 | ['Image Current No Cover', 6, 'curNoCoverImg'], 172 | ['Image Disk Cache Enabled', true, 'albumArtDiskCache'], 173 | ['Image Drop Shadow', false, 'albumArtDropShadow'], 174 | ['Image Group Level', 0, 'albumArtGrpLevel'], 175 | ['Image Group Names', JSON.stringify({}), 'albumArtGrpNames'], 176 | ['Image Flip Labels', false, 'albumArtFlipLabels'], 177 | ['Image Flow Mode', false, 'albumArtFlowMode'], 178 | ['Image Follow Selection Flow Mode', true, 'flowModeFollowSelection'], 179 | ['Image Follow Selection Standard Mode', false, 'stndModeFollowSelection'], 180 | ['Image Item Overlay', 1, 'itemOverlayType'], 181 | ['Image Label', 1, 'albumArtLabelType'], 182 | ['Image Memory Limit MB (0 = default)', 0, 'memoryLimit'], 183 | ['Image No Artist Images', JSON.stringify([]), 'noArtistImages'], 184 | ['Image No Cover Images', JSON.stringify([]), 'noCoverImages'], 185 | ['Image Preload Images In Disk Cache', false, 'albumArtPreLoad'], 186 | ['Image Root Images', JSON.stringify([]), 'rootImages'], 187 | ['Image Show Album Art', false, 'albumArtShow'], 188 | ['Image Show Index Letter', true, 'albumArtLetter'], 189 | ['Image Show Index Number', 0, 'albumArtLetterNo'], 190 | ['Image Show Index Year Auto', true, 'albumArtYearAuto'], 191 | ['Image Show Options', true, 'albumArtOptionsShow'], 192 | ['Image Style [Front] Regular-0 Auto-Fill-1 Circular-2', 1, 'imgStyleFront'], 193 | ['Image Style [Back] Regular-0 Auto-Fill-1 Circular-2', 1, 'imgStyleBack'], 194 | ['Image Style [Disc] Regular-0 Auto-Fill-1 Circular-2', 1, 'imgStyleDisc'], 195 | ['Image Style [Icon] Regular-0 Auto-Fill-1 Circular-2', 1, 'imgStyleIcon'], 196 | ['Image Style [Artist] Regular-0 Auto-Fill-1 Circular-2', 2, 'imgStyleArtist'], 197 | ['Image Thumbnail Gap Standard', 0, 'thumbNailGapStnd'], 198 | ['Image Thumbnail Gap Compact', 3, 'thumbNailGapCompact'], 199 | ['Image Thumbnail Size', 2, 'thumbNailSize'], 200 | ['Image Type', 0, 'artId'], 201 | ['Image View By: Same As Tree', true, 'artTreeSameView'], 202 | 203 | ['Initial Load Filters', true, 'initialLoadFilters'], 204 | ['Initial Load Views', true, 'initialLoadViews'], 205 | ['Key: Send to Playlist', 0, 'keyAction'], 206 | ['Library Auto-Sync', true, 'libAutoSync'], 207 | ['Library Sort Date Before Album', true, 'yearBeforeAlbum'], 208 | 209 | ['Library Source', 1, 'libSource'], 210 | ['Library Source: Active Playlist Follow Focus', true, 'followPlaylistFocus'], 211 | ['Library Source: Fixed Playlist', false, 'fixedPlaylist'], 212 | ['Library Source: Fixed Playlist Name', '', 'fixedPlaylistName'], 213 | 214 | ['Limit Menu Expand: 10-6000', 500, 'treeExpandLimit'], 215 | ['Limit Tree Auto Expand: 10-1000', 350, 'autoExpandLimit'], 216 | ['Line Padding', 5, 'verticalPad'], 217 | ['Line Padding Album Art', 2, 'verticalAlbumArtPad'], 218 | ['Margin', Math.round(8 * $.scale), 'margin'], 219 | ['Margin Override Top/Bottom (No Top Bar)', Math.round(8 * $.scale), 'marginTopBottom'], 220 | ['Middle-Click Action', 1, 'mbtnClickAction'], 221 | ['Mouse: Always Pointer (no hand)', false, 'mousePointerOnly'], 222 | 223 | ['Node: Auto Collapse', false, 'autoCollapse'], 224 | ['Node: Highlight on Hover', true, 'highLightNode'], 225 | ['Node: Item Counts Align Right', true, 'countsRight'], 226 | ['Node: Item Counts Hide-0 Tracks-1 Sub-Items-2', 1, 'nodeCounts'], 227 | ['Node: Root Hide-0 All Music-1 View Name-2', 1, 'rootNode'], 228 | ['Node: Root Inline Style', true, 'inlineRoot'], 229 | ['Node: Root Show Source', false, 'showSource'], 230 | ['Node: Show Lines', true, 'nodeLines'], 231 | ['Node: Show Tracks', true, 'showTracks'], 232 | ['Node: Style', 2, 'nodeStyle'], 233 | ['Node [Squares]: Windows', false, 'winNode'], 234 | ['Node Custom Icon: +|-', '\uE013|\uE015', 'iconCustom'], 235 | ['Node Custom Icon: Vertical Offset (%)', -2, 'iconVerticalPad'], 236 | 237 | ['Nowplaying Highlight', false, 'highLightNowplaying'], 238 | ['Nowplaying Highlight Last', false, 'highLightNowplayinglast'], 239 | ['Nowplaying Indicator', false, 'nowPlayingIndicator'], 240 | ['Nowplaying Indicator Last', false, 'nowPlayingIndicatorLast'], 241 | ['Nowplaying Sidemarker', false, 'nowPlayingSidemarker'], 242 | ['Nowplaying Sidemarker Last', false, 'nowPlayingSidemarkerLast'], 243 | 244 | ['Play on Enter or Send from Menu', false, 'autoPlay'], 245 | ['Playlist: Custom Sort', '', 'customSort'], 246 | ['Playlist: Default', 'Library View', 'libPlaylist'], 247 | ['Playlist: Default Activate on Change', true, 'activateOnChange'], 248 | ['Playlist: Panel Selection', 'Library Tree Panel Selection', 'panelSelectionPlaylist'], 249 | ['Playlist: Last Panel Selection', 'Library Tree Panel Selection', 'lastPanelSelectionPlaylist'], 250 | ['Playlist: Send to Current', false, 'sendToCur'], 251 | ['Prefixes to Strip or Swap (| Separator)', 'A|The', 'prefix'], 252 | ['Preset: Load Current View', false, 'presetLoadCurView'], 253 | ['Remember.PreSearch', true, 'rememberPreSearch'], 254 | ['Remember.Proc', false, 'process'], 255 | ['Remember.Tree', true, 'rememberTree'], 256 | ['Remember.View', false, 'rememberView'], 257 | ['Reset Tree', false, 'reset'], 258 | ['Row Stripes', true, 'rowStripes'], 259 | 260 | ['Scroll Step 0-10 (0 = Page)', 3, 'scrollStep'], 261 | ['Scroll Smooth Duration 0-5000 msec (Max)', 500, 'durationScroll'], 262 | ['Scroll Touch Flick Distance 0-10', 0.8, 'flickDistance'], 263 | ['Scroll Touch Flick Duration 0-5000 msec (Max)', 3000, 'durationTouchFlick'], 264 | ['Scroll: Smooth Scroll', true, 'smooth'], 265 | ['Scrollbar Arrow Custom Icon', '\uE0A0', 'arrowSymbol'], 266 | ['Scrollbar Arrow Custom Icon: Vertical Offset (%)', -24, 'sbarButPad'], 267 | ['Scrollbar Arrow Width', Math.round(11 * $.scale), 'sbarArrowWidth'], 268 | ['Scrollbar Button Type', 0, 'sbarButType'], 269 | ['Scrollbar Colour Grey-0 Blend-1', 1, 'sbarCol'], 270 | ['Scrollbar Grip MinHeight', Math.round(20 * $.scale), 'sbarGripHeight'], 271 | ['Scrollbar Height Prefer Full', true, 'sbarFullHeight'], 272 | ['Scrollbar Narrow Bar Width (0 = Auto)', 0, 'narrowSbarWidth'], 273 | ['Scrollbar Padding', 0, 'sbarPad'], 274 | ['Scrollbar Show', 1, 'sbarShow'], 275 | ['Scrollbar Type Default-0 Styled-1 Windows-2', 1, 'sbarType'], 276 | ['Scrollbar Width', Math.round(11 * $.scale), 'sbarWidth'], 277 | ['Scrollbar Width Bar', 11, 'sbarBase_w'], 278 | ['Scrollbar Windows Metrics', false, 'sbarWinMetrics'], 279 | 280 | ['Search Enter', false, 'searchEnter'], 281 | ['Search History', JSON.stringify([]), 'searchHistory'], 282 | ['Search Send', 1, 'searchSend'], 283 | 284 | ['Show Filter', true, 'filterShow'], 285 | ['Show Panel Source Message', true, 'panelSourceMsg'], 286 | ['Show Search', true, 'searchShow'], 287 | ['Show Settings', true, 'settingsShow'], 288 | ['Side Marker Width', 0, 'sideMarkerWidth'], 289 | ['Single-Click Action', 1, 'clickAction'], 290 | ['Statistics Show', 0, 'itemShowStatistics'], 291 | ['Statistics Show Last', 0, 'itemShowStatisticsLast'], 292 | ['Statistics Label Show', true, 'labelStatistics'], 293 | ['Statistics Titleformat Added', '[$date(%added%)]', 'tfAdded'], 294 | ['Statistics Titleformat Date', '[$year(%date%)]', 'tfDate'], 295 | ['Statistics Titleformat First Played', '[$date(%first_played%)]', 'tfFirstPlayed'], 296 | ['Statistics Titleformat Last Played', '[$date(%last_played%)]', 'tfLastPlayed'], 297 | ['Statistics Titleformat Playcount DataPinningScheme|Field', '%artist%%album%%discnumber%%tracknumber%%title%|%play_count%', 'tfPc'], 298 | ['Statistics Titleformat Rating', '[%rating%]', 'tfRating'], 299 | ['Statistics Titleformat Popularity', '[$meta(Track Statistics Last.fm,5[score])]', 'tfPopularity'], 300 | ['Statistics Tooltips Show', true, 'tooltipStatistics'], 301 | 302 | ['Theme', 0, 'theme'], 303 | ['Theme Panel Source Use Received Item Image', false, 'recItemImage'], 304 | ['Theme Background Image', false, 'themeBgImage'], 305 | ['Theme Colour', 3, 'themeColour'], 306 | ['Theme Light', false, 'themeLight'], 307 | ['Themed', false, 'themed'], // reserved: don't enable 308 | ['Touch Step 1-10', 1, 'touchStep'], 309 | ['Tree Auto Expand', false, 'treeAutoExpand'], 310 | ['Tree Auto Expand Single Items', false, 'treeAutoExpandSingle'], 311 | ['Tree Indent', Math.round(19 * $.scale), 'treeIndent'], 312 | ['Touch Control', false, 'touchControl'], 313 | ['View By', 1, 'viewBy'], 314 | ['View By Album Art', 1, 'albumArtViewBy'], 315 | ['View By Tree', 1, 'treeViewBy'], 316 | ['Zoom Filter Size (%)', 100, 'zoomFilter'], 317 | ['Zoom Font Size (%)', 100, 'zoomFont'], 318 | ['Zoom Node Size (%)', 100, 'zoomNode'], 319 | ['Zoom Image Size (%)', 100, 'zoomImg'], 320 | ['Zoom Tooltip [Button] (%)', 100, 'zoomTooltipBut'] 321 | ]; 322 | 323 | const ppt = new PanelProperties; 324 | ppt.init('auto', properties); 325 | if (!$.file('C:\\check_local\\1450343922.txt')) ppt.themed = false; 326 | 327 | if (ppt.get('Tree List View')) { 328 | ppt.facetView = ppt.get('Tree List View'); 329 | ppt.set('Tree List View', null); 330 | } 331 | ppt.set('Image Pre-Load Images In Disk Cache', null); 332 | ppt.set('Image Root Collage', null); 333 | ppt.set('Image Show Index Number', null); 334 | ppt.set('Image Show Index Year Auto', null); 335 | ppt.set('Node: Item Show Duration', null); 336 | ppt.set('Node [Squares]: Windows 0 or 1', null); 337 | properties = undefined; -------------------------------------------------------------------------------- /scripts/scrollbar.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Scrollbar { 4 | constructor() { 5 | this.active = true; 6 | this.alpha = 255; 7 | this.alpha1 = this.alpha; 8 | this.alpha2 = 255; 9 | this.but_h = 11; 10 | this.clock = Date.now(); 11 | this.col = {}; 12 | this.count = -1; 13 | this.cur_active = true; 14 | this.cur_hover = false; 15 | this.delta = 0; 16 | this.drag_distance_per_row = 0; 17 | this.draw_timer = null; 18 | this.drawBar = true; 19 | this.elap = 0; 20 | this.event = 'scroll'; 21 | this.hover = false; 22 | this.init = true; 23 | this.inStep = 18; 24 | this.max_scroll = 0; 25 | this.ratio = 1; 26 | this.rows_drawn = 0; 27 | this.scroll = 0; 28 | this.scrollable_lines = 0; 29 | this.scrollIX = 0; 30 | this.scrollStep = 3; 31 | this.start = 0; 32 | this.timer_but = null; 33 | this.vertical = true; 34 | this.x = 0; 35 | this.y = 0; 36 | this.w = 0; 37 | this.h = 0; 38 | 39 | this.bar = { 40 | isDragging: false, 41 | h: 0, 42 | timer: null, 43 | y: 0 44 | } 45 | 46 | this.initial = { 47 | drag: { 48 | x: 0, 49 | y: 0 50 | }, 51 | scr: 1, 52 | x: -1, 53 | y: -1 54 | } 55 | 56 | this.narrow = { 57 | show: ppt.sbarShow == 1 ? true : false, 58 | x: 0 59 | } 60 | 61 | this.row = { 62 | count: 0, 63 | h: 0 64 | } 65 | 66 | this.scrollbar = { 67 | cur_zone: false, 68 | height: 0, 69 | travel: 0, 70 | zone: false 71 | } 72 | 73 | this.touch = { 74 | dn: false, 75 | end: 0, 76 | start: 0, 77 | amplitude: 0, 78 | counter: 0, 79 | frame: 0, 80 | lastDn: Date.now(), 81 | min: 10 * $.scale, 82 | diff: 2 * $.scale, 83 | offset: 0, 84 | reference: -1, 85 | startTime: 0, 86 | ticker: null, 87 | timestamp: 0, 88 | velocity: 1 89 | } 90 | 91 | this.duration = { 92 | drag: 200, 93 | inertia: ppt.durationTouchFlick, 94 | full: ppt.durationScroll 95 | } 96 | 97 | this.duration.scroll = Math.round(this.duration.full * 0.8) 98 | this.duration.step = Math.round(this.duration.full * 2 / 3) 99 | this.duration.bar = this.duration.full; 100 | this.duration.barFast = this.duration.step; 101 | 102 | this.pageThrottle = $.throttle(dir => this.checkScroll(Math.round((this.scroll + dir * -(this.rows_drawn - 1) * this.row.h) / this.row.h) * this.row.h, 'full'), 100); 103 | 104 | this.scrollThrottle = $.throttle(() => { 105 | this.delta = this.scroll; 106 | this.scrollTo(); 107 | }, 16); 108 | 109 | this.hideDebounce = $.debounce(() => { 110 | if ((ppt.countsRight || ppt.itemShowStatistics) && !panel.imgView && !ppt.facetView && (!ppt.rootNode || pop.inlineRoot)) return; 111 | if (this.scrollbar.zone) return; 112 | this.active = false; 113 | this.cur_active = this.active; 114 | this.hover = false; 115 | this.cur_hover = false; 116 | this.alpha = this.alpha1; 117 | panel.treePaint(); 118 | }, 5000); 119 | 120 | this.minimiseDebounce = $.debounce(() => { 121 | if (this.scrollbar.zone) return panel.treePaint(); 122 | this.narrow.show = true; 123 | if (ppt.sbarShow == 1) but.setScrollBtnsHide(true, true); 124 | this.scrollbar.cur_zone = this.scrollbar.zone; 125 | this.hover = false; 126 | this.cur_hover = false; 127 | this.alpha = this.alpha1; 128 | panel.treePaint(); 129 | }, 1000); 130 | 131 | this.updDebounce = $.debounce(() => lib.treeState(false, ppt.rememberTree), 400); 132 | 133 | this.setCol(); 134 | } 135 | 136 | // Methods 137 | 138 | but(dir) { 139 | this.checkScroll(Math.round((this.scroll + dir * -this.row.h) / this.row.h) * this.row.h, 'step'); 140 | if (!this.timer_but) { 141 | this.timer_but = setInterval(() => { 142 | if (this.count > 6) { 143 | this.checkScroll(this.scroll + dir * -this.row.h, 'step'); 144 | } else this.count++; 145 | }, 40); 146 | } 147 | } 148 | 149 | calcItem_y() { 150 | const ix = Math.round(this.delta / this.row.h + 0.4); 151 | panel.tree.x = Math.round(this.row.h * ix - this.delta); 152 | panel.tree.y = Math.round(this.row.h * ix + panel.search.h - this.delta); 153 | } 154 | 155 | checkScroll(new_scroll, type, memory) { 156 | const b = $.clamp(new_scroll, 0, this.max_scroll); 157 | if (b == this.scroll) return; 158 | this.scroll = b; 159 | if (ppt.smooth && !memory) { 160 | this.elap = 16; 161 | this.event = type || 'scroll'; 162 | panel.tree.x = 0; 163 | panel.tree.y = panel.search.h; 164 | this.start = this.delta; 165 | if (this.event != 'drag') { 166 | if (this.bar.isDragging && Math.abs(this.delta - this.scroll) > (!panel.imgView ? this.scrollbar.height : this.scrollbar.height * 3)) this.event = 'barFast'; 167 | this.clock = Date.now(); 168 | if (!this.draw_timer) { 169 | this.scrollTimer(); 170 | this.smoothScroll(); 171 | } 172 | } else this.scrollDrag(); 173 | } else { 174 | this.scrollThrottle(); 175 | this.updDebounce(); 176 | } 177 | } 178 | 179 | draw(gr) { 180 | if (!ppt.sbarShow) return; 181 | if (this.drawBar && this.active) { 182 | let sbar_x = this.x; 183 | let sbar_w = this.w; 184 | let sbar_y = this.y; 185 | let sbar_h = this.h; 186 | if (ppt.sbarShow == 1) { 187 | sbar_x = !this.narrow.show ? this.x : this.narrow.x; 188 | sbar_w = !this.narrow.show ? this.w : ui.sbar.narrowWidth; 189 | sbar_y = !this.narrow.show ? this.y : this.narrow.y; 190 | sbar_h = !this.narrow.show ? this.h : ui.sbar.narrowWidth; 191 | } 192 | switch (ui.sbar.type) { 193 | case 0: 194 | if (ppt.rowStripes && ppt.sbarShow == 2 && !this.vertical) gr.FillSolidRect(this.x, this.y, this.w, this.h, ui.col.bg1); 195 | if (this.vertical) gr.FillSolidRect(sbar_x, this.y + this.bar.y, sbar_w, this.bar.h, this.narrow.show ? this.col[this.alpha2] : !this.bar.isDragging ? this.col[this.alpha] : this.col['max']); 196 | else gr.FillSolidRect(this.x + this.bar.x, sbar_y, this.bar.h, sbar_h, this.narrow.show ? this.col[this.alpha2] : !this.bar.isDragging ? this.col[this.alpha] : this.col['max']); 197 | break; 198 | case 1: 199 | if (this.vertical) { 200 | if (!this.narrow.show || ppt.sbarShow != 1) gr.FillSolidRect(sbar_x, this.y - panel.sbar_o, this.w, this.h + panel.sbar_o * 2, this.col['bg']); 201 | gr.FillSolidRect(sbar_x, this.y + this.bar.y, sbar_w, this.bar.h, this.narrow.show ? this.col[this.alpha2] : !this.bar.isDragging ? this.col[this.alpha] : this.col['max']); 202 | } else { 203 | if (!this.narrow.show || ppt.sbarShow != 1) gr.FillSolidRect(this.x - panel.sbar_o, sbar_y, this.w + panel.sbar_o * 2, this.h, this.col['bg']); 204 | gr.FillSolidRect(this.x + this.bar.x, sbar_y, this.bar.h, sbar_h, this.narrow.show ? this.col[this.alpha2] : !this.bar.isDragging ? this.col[this.alpha] : this.col['max']); 205 | } 206 | break; 207 | case 2: 208 | if (this.vertical) { 209 | ui.theme.SetPartAndStateID(6, 1); 210 | if (!this.narrow.show || ppt.sbarShow != 1) ui.theme.DrawThemeBackground(gr, sbar_x, this.y, sbar_w, this.h); 211 | ui.theme.SetPartAndStateID(3, this.narrow.show ? 2 : !this.hover && !this.bar.isDragging ? 1 : this.hover && !this.bar.isDragging ? 2 : 3); 212 | ui.theme.DrawThemeBackground(gr, sbar_x, this.y + this.bar.y, sbar_w, this.bar.h); 213 | } else { 214 | ui.theme.SetPartAndStateID(4, 1); 215 | if (!this.narrow.show || ppt.sbarShow != 1) ui.theme.DrawThemeBackground(gr, this.x, sbar_y, this.w, sbar_h); 216 | ui.theme.SetPartAndStateID(2, this.narrow.show ? 2 : !this.hover && !this.bar.isDragging ? 1 : this.hover && !this.bar.isDragging ? 2 : 3); 217 | ui.theme.DrawThemeBackground(gr, this.x + this.bar.x, sbar_y, this.bar.h, sbar_h); 218 | } 219 | break; 220 | } 221 | 222 | if (!panel.imgView || !img.letter.show || !this.bar.isDragging) return; 223 | const ix = img.style.vertical ? (Math.ceil((panel.m.y + sbar.delta - img.panel.y) / img.row.h) - 1) * (!ppt.albumArtFlowMode ? img.columns : 1) : Math.ceil((panel.m.x + sbar.delta - img.panel.x) / img.columnWidth) - 1; 224 | if (ix < 0 || ix > pop.tree.length - 1) return; 225 | let letter = panel.lines == 1 || !ppt.albumArtFlipLabels ? pop.tree[ix].grp : pop.tree[ix].lot; 226 | if (panel.colMarker) letter = letter.replace(/@!#.*?@!#/g, ''); 227 | if (img.letter.no != 0) { 228 | if (img.letter.albumArtYearAuto) { 229 | let sub = letter.substring(0, 4); 230 | if (/\d{4}/.test(sub)) letter = sub; 231 | else { 232 | sub = letter.substring(0, 6); 233 | if (/(\[|\()\d{4}(\]|\))/.test(sub)) letter = sub; 234 | else { 235 | letter = letter.substring(0, img.letter.no); 236 | } 237 | } 238 | } else letter = letter.substring(0, img.letter.no); 239 | } 240 | const letter_w = gr.CalcTextWidth(letter, ui.font.main) + img.letter.w; 241 | const w1 = Math.min(letter_w, ui.w - img.panel.x - img.letter.w); 242 | const w2 = Math.min(letter_w, ui.w - img.panel.x) + 1; 243 | if (img.style.vertical) gr.FillSolidRect(0, this.y + this.bar.y + this.bar.h / 2 - img.text.h / 2, w2, img.text.h + 2, ui.col.bg6); 244 | if (img.style.vertical) gr.FillSolidRect(0, this.y + this.bar.y + this.bar.h / 2 - img.text.h / 2, w2, img.text.h + 2, ui.col.bg3); 245 | if (img.style.vertical) gr.GdiDrawText(letter, ui.font.main, ui.col.text, ui.l.w + img.letter.w / 2, this.y + this.bar.y + this.bar.h / 2 - img.text.h / 2, w1, img.text.h, panel.lc); 246 | else gr.GdiDrawText(letter, ui.font.main, ui.col.text, this.x + this.bar.x + this.bar.h / 2 - w1 / 2, sbar_y - img.text.h, w1, img.text.h, panel.cce); 247 | } 248 | } 249 | 250 | lbtn_dblclk(p_x, p_y) { 251 | const x = p_x - this.x; 252 | const y = p_y - this.y; 253 | let dir; 254 | switch (true) { 255 | case this.vertical: 256 | if (x < 0 || x > this.w || y < 0 || y > this.h || this.row.count <= this.rows_drawn) return; 257 | if (y < this.but_h || y > this.h - this.but_h) return; 258 | if (y < this.bar.y) dir = 1; // above bar 259 | else if (y > this.bar.y + this.bar.h) dir = -1; // below bar 260 | if (y < this.bar.y || y > this.bar.y + this.bar.h) this.shiftPage(dir, this.nearestY(y)); 261 | break; 262 | case !this.vertical: 263 | if (y < 0 || y > this.h || x < 0 || x > this.w || this.row.count <= this.rows_drawn) return; 264 | if (x < this.but_h || x > this.w - this.but_h) return; 265 | if (x < this.bar.x) dir = 1; // above bar 266 | else if (x > this.bar.x + this.bar.h) dir = -1; // below bar 267 | if (x < this.bar.x || x > this.bar.x + this.bar.h) this.shiftPage(dir, this.nearestX(x)); 268 | break; 269 | } 270 | } 271 | 272 | lbtn_dn(p_x, p_y) { 273 | if (!ppt.sbarShow && ppt.touchControl) return this.tap(p_x, p_y); 274 | const x = p_x - this.x; 275 | const y = p_y - this.y; 276 | let dir; 277 | switch (true) { 278 | case this.vertical: 279 | if (x > this.w || y < 0 || y > this.h || this.row.count <= this.rows_drawn) return; 280 | if (x < 0) { 281 | if (!ppt.touchControl) return; 282 | else return this.tap(p_x, p_y); 283 | } 284 | if (y < this.but_h || y > this.h - this.but_h) return; 285 | if (y < this.bar.y) dir = 1; // above bar 286 | else if (y > this.bar.y + this.bar.h) dir = -1; // below bar 287 | if (y < this.bar.y || y > this.bar.y + this.bar.h) this.shiftPage(dir, this.nearestY(y)); 288 | else { // on bar 289 | this.bar.isDragging = true; 290 | but.Dn = true; 291 | window.RepaintRect(this.x, this.y, this.w, this.h); 292 | this.initial.drag.y = y - this.bar.y + this.but_h; 293 | } 294 | break; 295 | case !this.vertical: 296 | if (y > this.h || x < 0 || x > this.w || this.row.count <= this.rows_drawn) return; 297 | if (y < 0) { 298 | if (!ppt.touchControl) return; 299 | else return this.tap(p_x, p_y); 300 | } 301 | if (x < this.but_h || x > this.w - this.but_h) return; 302 | if (x < this.bar.x) dir = 1; // above bar 303 | else if (x > this.bar.x + this.bar.h) dir = -1; // below bar 304 | if (x < this.bar.x || x > this.bar.x + this.bar.h) this.shiftPage(dir, this.nearestX(x)); 305 | else { // on bar 306 | this.bar.isDragging = true; 307 | but.Dn = true; 308 | window.RepaintRect(this.x, this.y, this.w, this.h); 309 | this.initial.drag.x = x - this.bar.x + this.but_h; 310 | } 311 | break; 312 | } 313 | } 314 | 315 | lbtn_up() { 316 | if (this.touch.dn) { 317 | this.touch.dn = false; 318 | clearInterval(this.touch.ticker); 319 | if (!this.touch.counter) this.track(true); 320 | if (Math.abs(this.touch.velocity) > this.touch.min && Date.now() - this.touch.startTime < 300) { 321 | this.touch.amplitude = ppt.flickDistance * this.touch.velocity * ppt.touchStep; 322 | this.touch.timestamp = Date.now(); 323 | this.checkScroll(Math.round((this.scroll + this.touch.amplitude) / this.row.h) * this.row.h, 'inertia'); 324 | } 325 | } 326 | if (!this.hover && this.bar.isDragging) this.paint(); 327 | else window.RepaintRect(this.x, this.y, this.w, this.h); 328 | if (this.bar.isDragging) { 329 | this.bar.isDragging = false; 330 | img.loadThrottle(); 331 | but.Dn = false; 332 | } 333 | this.initial.drag.x = 0; 334 | this.initial.drag.y = 0; 335 | if (this.timer_but) { 336 | clearTimeout(this.timer_but); 337 | this.timer_but = null; 338 | } 339 | this.count = -1; 340 | } 341 | 342 | leave() { 343 | if (this.touch.dn) this.touch.dn = false; 344 | if (!men.r_up) this.scrollbar.zone = false; 345 | if (this.bar.isDragging || ppt.sbarShow == 1) return; 346 | this.hover = !this.hover; 347 | this.paint(); 348 | this.hover = false; 349 | this.cur_hover = false; 350 | } 351 | 352 | logScroll() { 353 | this.scrollIX = $.clamp(Math.round(sbar.scroll / this.row.h + 0.4), 0, pop.tree.length - 1); 354 | } 355 | 356 | metrics(x, y, w, h, rows_drawn, row_h, vertical) { 357 | this.vertical = vertical; 358 | if (this.vertical) { 359 | this.x = x; 360 | this.y = Math.round(y); 361 | } else { 362 | this.x = Math.round(x); 363 | this.y = y; 364 | } 365 | this.w = w; 366 | this.h = h; 367 | this.rows_drawn = rows_drawn; 368 | this.row.h = row_h; 369 | this.but_h = ui.sbar.but_h; 370 | this.scrollStep = $.clamp(ppt.scrollStep, 0, 10); 371 | if (panel.imgView && this.scrollStep != 0) this.scrollStep = Math.max(Math.round(this.scrollStep /= 3), 1); 372 | // draw info 373 | this.scrollbar.height = this.vertical ? Math.round(this.h - this.but_h * 2) : Math.round(this.w - this.but_h * 2); 374 | this.bar.h = Math.max(Math.round(this.scrollbar.height * this.rows_drawn / this.row.count), $.clamp(this.scrollbar.height / 2, 5, ppt.sbarShow == 2 ? ppt.sbarGripHeight : ppt.sbarGripHeight * 2)); 375 | this.scrollbar.travel = this.scrollbar.height - this.bar.h; 376 | // scrolling info 377 | this.scrollable_lines = this.rows_drawn > 0 ? this.row.count - this.rows_drawn : 0; 378 | this.drawBar = this.scrollable_lines > 0 && this.scrollbar.height > 1; 379 | this.ratio = this.row.count / this.scrollable_lines; 380 | this.bar.x = this.bar.y = this.but_h + this.scrollbar.travel * (this.delta * this.ratio) / (this.row.count * this.row.h); 381 | this.drag_distance_per_row = this.scrollbar.travel / this.scrollable_lines; 382 | // panel info 383 | if (this.vertical) this.narrow.x = this.x + this.w - $.clamp(ui.sbar.narrowWidth, 5, this.w); 384 | else this.narrow.y = this.y + this.h - $.clamp(ui.sbar.narrowWidth, 5, this.h); 385 | panel.tree.w = ui.w - 386 | Math.max(ppt.sbarShow && this.scrollable_lines > 0 ? (!ppt.countsRight && !ppt.itemShowStatistics) || ppt.facetView ? ui.sbar.sp + ui.sz.sel : 387 | ppt.sbarShow == 2 ? ui.sbar.sp + ui.sz.margin : 388 | ppt.sbarShow == 1 ? (ui.w - this.narrow.x) + ui.sz.marginRight + Math.max(this.w - 11, 0) : ui.sz.sel : ui.sz.sel, ui.sz.margin); 389 | pop.id = ui.id.tree + ppt.fullLineSelection + panel.tree.w + panel.imgView + ppt.albumArtLabelType + ppt.albumArtFlipLabels + ppt.albumArtFlowMode; 390 | panel.tree.stripe.w = ppt.sbarShow == 2 && this.scrollable_lines > 0 ? ui.w - ui.sbar.sp - ui.sz.pad : ui.w; 391 | panel.tree.sel.w = ppt.sbarShow == 2 && this.scrollable_lines > 0 ? ui.w - ui.sbar.sp - ui.sz.pad * 2 : ui.w - ui.sz.pad * 2; 392 | this.max_scroll = this.scrollable_lines * this.row.h; 393 | if (panel.imgView && this.vertical && this.row.h > img.panel.h) this.max_scroll -= this.row.h; 394 | if (panel.imgView && !this.vertical && this.row.h > ui.w) this.max_scroll -= this.row.h; 395 | if (ppt.sbarShow != 1) but.setScrollBtnsHide(); 396 | } 397 | 398 | move(p_x, p_y) { 399 | this.active = true; 400 | if (p_x > this.x && p_y > this.y) { 401 | this.scrollbar.zone = true; 402 | this.narrow.show = false; 403 | if (ppt.sbarShow == 1 && this.scrollbar.zone != this.scrollbar.cur_zone) { 404 | but.setScrollBtnsHide(!this.scrollbar.zone || this.scrollable_lines < 1, true); 405 | this.scrollbar.cur_zone = this.scrollbar.zone; 406 | } 407 | } else this.scrollbar.zone = false; 408 | if (ppt.sbarShow == 1) { 409 | this.minimiseDebounce(); 410 | this.hideDebounce(); 411 | } 412 | if (ppt.touchControl) { 413 | const delta = this.touch.reference - (this.vertical ? p_y : p_x); 414 | if (delta > this.touch.diff || delta < -this.touch.diff) { 415 | this.touch.reference = this.vertical ? p_y : p_x; 416 | if (ppt.flickDistance) this.touch.offset = $.clamp(this.touch.offset + delta, 0, this.max_scroll); 417 | if (this.touch.dn) { 418 | ui.id.dragDrop = ui.id.touch_dn = -1; 419 | } 420 | } 421 | } 422 | if (this.touch.dn && !vk.k('zoom')) { 423 | const now = Date.now(); 424 | if (now - this.touch.startTime > 300) this.touch.startTime = now; 425 | this.touch.lastDn = now; 426 | this.checkScroll(this.initial.scr + (this.vertical ? this.initial.y - p_y : this.initial.x - p_x) * ppt.touchStep, ppt.touchStep == 1 ? 'drag' : 'scroll'); 427 | return; 428 | } 429 | const x = p_x - this.x; 430 | const y = p_y - this.y; 431 | if (this.vertical) { 432 | if (x < 0 || x > this.w || y > this.bar.y + this.bar.h || y < this.bar.y || but.Dn) this.hover = false; 433 | else this.hover = true; 434 | } else { 435 | if (y < 0 || y > this.h || x > this.bar.x + this.bar.h || x < this.bar.x || but.Dn) this.hover = false; 436 | else this.hover = true; 437 | } 438 | if (!this.bar.timer && (this.hover != this.cur_hover || this.active != this.cur_active)) { 439 | this.init = false; 440 | this.paint(); 441 | this.cur_active = this.active; 442 | } 443 | if (!this.bar.isDragging || this.row.count <= this.rows_drawn) return; 444 | this.checkScroll(Math.round(this.vertical ? y - this.initial.drag.y : x - this.initial.drag.x) / this.drag_distance_per_row * this.row.h, 'bar'); 445 | } 446 | 447 | nearestY(y) { 448 | y = (y - this.but_h) / this.scrollbar.height * this.max_scroll; 449 | y = y / this.row.h; 450 | y = Math.round(y) * this.row.h; 451 | return y; 452 | } 453 | 454 | nearestX(x) { 455 | x = (x - this.but_h) / this.scrollbar.height * this.max_scroll; 456 | x = x / this.row.h; 457 | x = Math.round(x) * this.row.h; 458 | return x; 459 | } 460 | 461 | paint() { 462 | if (this.init) return; 463 | this.alpha = this.hover ? this.alpha1 : this.alpha2; 464 | clearTimeout(this.bar.timer); 465 | this.bar.timer = null; 466 | this.bar.timer = setInterval(() => { 467 | this.alpha = this.hover ? Math.min(this.alpha += this.inStep, this.alpha2) : Math.max(this.alpha -= 3, this.alpha1); 468 | window.RepaintRect(this.x, this.y, this.w, this.h); 469 | if (this.hover && this.alpha == this.alpha2 || !this.hover && this.alpha == this.alpha1) { 470 | this.cur_hover = this.hover; 471 | clearTimeout(this.bar.timer); 472 | this.bar.timer = null; 473 | } 474 | }, 25); 475 | } 476 | 477 | position(Start, End, Elapsed, Duration, Event) { 478 | if (Elapsed > Duration) return End; 479 | if (Event == 'drag') return; 480 | const n = Elapsed / Duration; 481 | return Start + (End - Start) * ease[Event](n); 482 | } 483 | 484 | reset() { 485 | this.delta = this.scroll = 0; 486 | this.metrics(this.x, this.y, this.w, this.h, this.rows_drawn, this.row.h, this.vertical); 487 | lib.treeState(false, ppt.rememberTree); 488 | } 489 | 490 | resetAuto() { 491 | this.minimiseDebounce.cancel(); 492 | this.hideDebounce.cancel(); 493 | if (!ppt.sbarShow) but.setScrollBtnsHide(true); 494 | if (ppt.sbarShow == 1) { 495 | but.setScrollBtnsHide(true, true); 496 | this.narrow.show = true; 497 | this.scrollbar.cur_zone = false; 498 | } 499 | } 500 | 501 | scrollDrag() { 502 | this.delta = this.scroll; 503 | this.scrollTo(); 504 | this.calcItem_y(); 505 | this.updDebounce(); 506 | } 507 | 508 | scrollFinish() { 509 | if (!this.draw_timer) return; 510 | this.delta = this.scroll; 511 | 512 | if (this.vertical) this.bar.y = this.but_h + this.scrollbar.travel * (this.delta * this.ratio) / (this.row.count * this.row.h); 513 | else this.bar.x = this.but_h + this.scrollbar.travel * (this.delta * this.ratio) / (this.row.count * this.row.h); 514 | ppt.rememberTree ? lib.treeState(false, ppt.rememberTree) : panel.treePaint(); 515 | this.calcItem_y(); 516 | clearTimeout(this.draw_timer); 517 | this.draw_timer = null; 518 | } 519 | 520 | scrollRound() { 521 | if (this.vertical) { 522 | if (panel.tree.y == panel.search.h) return; 523 | this.checkScroll((panel.tree.y < panel.search.h ? Math.floor(this.scroll / this.row.h) : Math.ceil(this.scroll / this.row.h)) * this.row.h); 524 | } else { 525 | if (panel.tree.x == 0) return; 526 | this.checkScroll((panel.tree.x < 0 ? Math.floor(this.scroll / this.row.h) : Math.ceil(this.scroll / this.row.h)) * this.row.h); 527 | } 528 | } 529 | 530 | setRows(row_count) { 531 | if (!row_count) { 532 | panel.tree.x = 0; 533 | panel.tree.y = panel.search.h; 534 | } 535 | this.row.count = row_count; 536 | this.metrics(this.x, this.y, this.w, this.h, this.rows_drawn, this.row.h, this.vertical); 537 | } 538 | 539 | scrollMemory(h, j) { 540 | let scroll = !h ? j * this.row.h : (j - 3) * this.row.h; 541 | if (panel.imgView && img.style.vertical) { 542 | scroll /= img.columns; 543 | scroll = scroll - scroll % this.row.h; 544 | } 545 | this.checkScroll(scroll, 'full', true); 546 | } 547 | 548 | setScroll() { 549 | const b = $.clamp(this.scrollIX * this.row.h, 0, this.max_scroll); 550 | if (b == this.scroll) return; 551 | this.scroll = b; 552 | panel.tree.x = 0; 553 | panel.tree.y = panel.search.h; 554 | this.scrollThrottle(); 555 | this.updDebounce(); 556 | } 557 | 558 | scrollTimer() { 559 | this.draw_timer = setInterval(() => { 560 | this.smoothScroll(); 561 | }, 16); 562 | } 563 | 564 | scrollTo() { 565 | if (this.vertical) this.bar.y = this.but_h + this.scrollbar.travel * (this.delta * this.ratio) / (this.row.count * this.row.h); 566 | else this.bar.x = this.but_h + this.scrollbar.travel * (this.delta * this.ratio) / (this.row.count * this.row.h); 567 | panel.treePaint(); 568 | } 569 | 570 | scrollToEnd() { 571 | this.checkScroll(this.max_scroll, 'full'); 572 | } 573 | 574 | setCol() { 575 | let opaque = ui.getOpaque(); 576 | this.alpha = !ui.sbar.col ? 75 : (!ui.sbar.type ? 68 : 51); 577 | this.alpha1 = this.alpha; 578 | this.alpha2 = !ui.sbar.col ? 128 : (!ui.sbar.type ? 119 : 85); 579 | this.inStep = ui.sbar.type && ui.sbar.col ? 12 : 18; 580 | switch (ui.sbar.type) { 581 | case 0: 582 | switch (ui.sbar.col) { 583 | case 0: 584 | for (let i = 0; i < this.alpha2 - this.alpha + 1; i++) this.col[this.alpha + i] = opaque ? $.RGBAtoRGB(RGBA(ui.col.t, ui.col.t, ui.col.t, this.alpha + i), ui.col.bg) : RGBA(ui.col.t, ui.col.t, ui.col.t, this.alpha + i); 585 | this.col.max = opaque ? $.RGBAtoRGB(RGBA(ui.col.t, ui.col.t, ui.col.t, 192), ui.col.bg) : RGBA(ui.col.t, ui.col.t, ui.col.t, 192); 586 | break; 587 | case 1: 588 | for (let i = 0; i < this.alpha2 - this.alpha + 1; i++) this.col[this.alpha + i] = opaque ? $.RGBAtoRGB(ui.col.text & RGBA(255, 255, 255, this.alpha + i), ui.col.bg) : ui.col.text & RGBA(255, 255, 255, this.alpha + i); 589 | this.col.max = opaque ? $.RGBAtoRGB(ui.col.text & 0x99ffffff, ui.col.bg) : ui.col.text & 0x99ffffff; 590 | break; 591 | } 592 | break; 593 | case 1: 594 | switch (ui.sbar.col) { 595 | case 0: 596 | this.col.bg = opaque ? $.RGBAtoRGB(RGBA(ui.col.t, ui.col.t, ui.col.t, 15), ui.col.bg) : RGBA(ui.col.t, ui.col.t, ui.col.t, 15); 597 | for (let i = 0; i < this.alpha2 - this.alpha + 1; i++) this.col[this.alpha + i] = opaque ? $.RGBAtoRGB(RGBA(ui.col.t, ui.col.t, ui.col.t, this.alpha + i), ui.col.bg) : RGBA(ui.col.t, ui.col.t, ui.col.t, this.alpha + i); 598 | this.col.max = opaque ? $.RGBAtoRGB(RGBA(ui.col.t, ui.col.t, ui.col.t, 192), ui.col.bg) : RGBA(ui.col.t, ui.col.t, ui.col.t, 192); 599 | break; 600 | case 1: 601 | this.col.bg = opaque ? $.RGBAtoRGB(ui.col.text & 0x15ffffff, ui.col.bg) : ui.col.text & 0x15ffffff; 602 | for (let i = 0; i < this.alpha2 - this.alpha + 1; i++) this.col[this.alpha + i] = opaque ? $.RGBAtoRGB(ui.col.text & RGBA(255, 255, 255, this.alpha + i), ui.col.bg) : ui.col.text & RGBA(255, 255, 255, this.alpha + i); 603 | this.col.max = opaque ? $.RGBAtoRGB(ui.col.text & 0x99ffffff, ui.col.bg) : ui.col.text & 0x99ffffff; 604 | break; 605 | } 606 | break; 607 | } 608 | } 609 | 610 | shift(dir, nearest_y) { 611 | let target = Math.round((this.scroll + dir * -(((this.rows_drawn - 1) || 1) * this.row.h)) / this.row.h) * this.row.h; 612 | if (dir == 1) target = Math.max(target, nearest_y); 613 | else target = Math.min(target, nearest_y); 614 | return target; 615 | } 616 | 617 | shiftPage(dir, nearest_y) { 618 | this.checkScroll(this.shift(dir, nearest_y), 'full'); 619 | if (!this.timer_but) { 620 | this.timer_but = setInterval(() => { 621 | if (this.count > 1) { 622 | this.checkScroll(this.shift(dir, nearest_y), 'full'); 623 | } else this.count++; 624 | }, 100); 625 | } 626 | } 627 | 628 | smoothScroll() { 629 | this.delta = this.position(this.start, this.scroll, Date.now() - this.clock + this.elap, this.duration[this.event], this.event); 630 | if (Math.abs(this.scroll - this.delta) > 0.5) this.scrollTo(); 631 | else this.scrollFinish(); 632 | } 633 | 634 | tap(p_x, p_y) { 635 | if (this.touch.amplitude) { 636 | this.clock = 0; 637 | // this.scroll = this.delta; // stopping correct scroll on expanding bottom node after touch event 638 | } 639 | this.touch.counter = 0; 640 | this.initial.scr = this.scroll; 641 | this.touch.dn = true; 642 | if (this.vertical) { 643 | this.initial.y = this.touch.reference = p_y; 644 | if (!this.touch.offset) this.touch.offset = p_y; 645 | } else { 646 | this.initial.x = this.touch.reference = p_x; 647 | if (!this.touch.offset) this.touch.offset = p_x; 648 | } 649 | this.touch.velocity = this.touch.amplitude = 0; 650 | if (!ppt.flickDistance) return; 651 | this.touch.frame = this.touch.offset; 652 | this.touch.startTime = this.touch.timestamp = Date.now(); 653 | clearInterval(this.touch.ticker); 654 | this.touch.ticker = setInterval(() => this.track, 100); 655 | } 656 | 657 | track(initial) { 658 | let now, elapsed, delta, v; 659 | this.touch.counter++; 660 | now = Date.now(); 661 | if (now - this.touch.lastDn < 10000 && this.touch.counter == 4) { 662 | ui.id.touch_dn = -1; 663 | panel.last_pressed_coord = { 664 | x: -1, 665 | y: -1 666 | } 667 | } 668 | elapsed = now - this.touch.timestamp; 669 | if (initial) elapsed = Math.max(elapsed, 32); 670 | this.touch.timestamp = now; 671 | delta = this.touch.offset - this.touch.frame; 672 | this.touch.frame = this.touch.offset; 673 | v = 1000 * delta / (1 + elapsed); 674 | this.touch.velocity = 0.8 * v + 0.2 * this.touch.velocity; 675 | } 676 | 677 | wheel(step) { 678 | this.checkScroll(Math.round((this.scroll + step * -(!this.scrollStep ? this.rows_drawn - 1 : this.scrollStep) * this.row.h) / this.row.h) * this.row.h, this.scrollStep ? 'step' : 'full'); 679 | } 680 | } -------------------------------------------------------------------------------- /scripts/search.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Search { 4 | constructor() { 5 | this.cx = 0; 6 | this.end = 0; 7 | this.lbtnDn = false; 8 | this.lg = []; 9 | this.log = []; 10 | this.menu = $.jsonParse(ppt.searchHistory, []); 11 | this.offset = 0; 12 | this.paste = false; 13 | this.start = 0; 14 | this.shift = false; 15 | this.shift_x = 0; 16 | this.txt_w = 0; 17 | 18 | this.logHistory = $.debounce(() => { 19 | let item = -1; 20 | const itemPresent = this.menu.some((v, i) => { 21 | item = i; 22 | return v.search == panel.search.txt; 23 | }); 24 | if (itemPresent) { 25 | this.menu[item].accessed = Date.now(); 26 | return; 27 | } 28 | if (!panel.search.txt) return; 29 | this.menu.push({search: panel.search.txt, accessed: Date.now()}); 30 | if (this.menu.length > 25) { 31 | this.menu.sort((a, b) => b.accessed - a.accessed); 32 | this.menu.length = 25; 33 | } 34 | this.menu.sort((a, b) => pop.collator.compare(a.search, b.search)); 35 | ppt.searchHistory = JSON.stringify(this.menu); 36 | }, 3000); 37 | } 38 | 39 | // Methods 40 | 41 | calcText() { 42 | $.gr(1, 1, false, g => this.txt_w = g.CalcTextWidth(panel.search.txt.substr(this.offset), ui.font.main, true)); 43 | } 44 | 45 | clear() { 46 | if (!panel.search.txt) return; 47 | lib.time.Reset(); 48 | pop.cache.search = {}; 49 | this.offset = this.start = this.end = this.cx = 0; 50 | panel.search.txt = ''; 51 | but.setSearchBtnsHide(); 52 | panel.searchPaint(); 53 | lib.setNodes(); // comment out to always stop child panels clearing [if memory on & item selected that's used & so won't clear] 54 | pop.checkAutoHeight(); 55 | pop.notifySelection(); 56 | } 57 | 58 | draw(gr) { 59 | if (!ppt.searchShow) return; 60 | this.start = $.clamp(this.start, 0, panel.search.txt.length); 61 | this.end = $.clamp(this.end, 0, panel.search.txt.length); 62 | this.cx = $.clamp(this.cx, 0, panel.search.txt.length); 63 | if (ui.style.fill) gr.FillSolidRect(0, 1, ui.w, ui.row.h - 4, 0x60000000); 64 | if (ui.style.pen == 2) gr.DrawRoundRect(0, 2, ui.w - 1, ui.row.h - 4, 4, 4, 1, ui.style.pen_c); 65 | if (panel.search.txt) { 66 | this.drawSel(gr); 67 | this.getOffset(gr); 68 | gr.GdiDrawText(panel.search.txt.substr(this.offset), ui.font.main, ui.col.search, panel.search.x, 0, panel.search.w, panel.search.sp, panel.l); 69 | } else { 70 | if (!ui.img.blurDark) gr.GdiDrawText('Search', ui.font.search, ui.col.txt_box, panel.search.x, 0, panel.search.w, panel.search.sp, panel.l); 71 | else { 72 | gr.SetTextRenderingHint(5); 73 | gr.DrawString('Search', ui.font.search, ui.col.txt_box, panel.search.x, -1, panel.search.w, panel.search.sp, panel.s_lc); 74 | } 75 | } 76 | this.drawCursor(gr); 77 | } 78 | 79 | drawCursor(gr) { 80 | if (panel.search.active && panel.search.cursor && this.start == this.end && this.cx >= this.offset) { 81 | const lx = panel.search.x + this.get_cursor_x(this.cx); 82 | gr.DrawLine(lx, panel.search.sp * 0.1, lx, panel.search.sp * 0.85, ui.l.w, ui.col.text); 83 | } 84 | } 85 | 86 | drawSel(gr) { 87 | if (this.start == this.end) return; 88 | const clamp = panel.search.x + panel.search.w; 89 | gr.DrawLine(Math.min(panel.search.x + this.get_cursor_x(this.start), clamp), panel.search.sp / 2, Math.min(panel.search.x + this.get_cursor_x(this.end), clamp), panel.search.sp / 2, ui.row.h - 3, ui.col.searchSel); 90 | } 91 | 92 | get_cursor_x(pos) { 93 | let x = 0; 94 | $.gr(1, 1, false, g => { 95 | if (pos >= this.offset) x = g.CalcTextWidth(panel.search.txt.substr(this.offset, pos - this.offset), ui.font.main, true); 96 | }); 97 | return x; 98 | } 99 | 100 | getCursorChrPos(x) { 101 | let i = 0; 102 | $.gr(1, 1, false, g => { 103 | const nx = x - panel.search.x; 104 | let pos = 0; 105 | for (i = this.offset; i < panel.search.txt.length; i++) { 106 | pos += g.CalcTextWidth(panel.search.txt.substr(i, 1), ui.font.main, true); 107 | if (pos >= nx + 3) break; 108 | } 109 | }); 110 | return i; 111 | } 112 | 113 | getOffset(gr) { 114 | let j = 0; 115 | let tx = gr.CalcTextWidth(panel.search.txt.substr(this.offset, this.cx - this.offset), ui.font.main, true); 116 | while (tx >= panel.search.w && panel.search.w > 0 && j < 500) { 117 | j++; 118 | this.offset++; 119 | tx = gr.CalcTextWidth(panel.search.txt.substr(this.offset, this.cx - this.offset), ui.font.main, true); 120 | } 121 | } 122 | 123 | lbtn_dblclk(x, y) { 124 | if (y < panel.search.h && x > but.q.h + but.margin && x < panel.search.x + panel.search.w && panel.search.txt.length) { 125 | panel.search.cursor = false; 126 | this.start = 0; 127 | this.end = panel.search.txt.length; 128 | panel.searchPaint(); 129 | } 130 | } 131 | 132 | lbtn_dn(x, y) { 133 | panel.searchPaint(); 134 | this.lbtnDn = panel.search.active = (y < panel.search.h && x > but.q.x - but.margin / 2 + but.q.h + but.margin && x < panel.search.x + panel.search.w); 135 | if (!this.lbtnDn) { 136 | this.offset = this.start = this.end = this.cx = 0; 137 | timer.clear(timer.cursor); 138 | return; 139 | } else { 140 | if (this.shift) { 141 | this.start = this.cx; 142 | this.end = this.cx = this.getCursorChrPos(x); 143 | } else { 144 | this.cx = this.getCursorChrPos(x); 145 | this.start = this.end = this.cx; 146 | } 147 | timer.searchCursor(true); 148 | } 149 | panel.searchPaint(); 150 | } 151 | 152 | lbtn_up() { 153 | if (this.start != this.end) timer.clear(timer.cursor); 154 | this.lbtnDn = false; 155 | } 156 | 157 | move(x, y) { 158 | if (y > panel.search.h || !this.lbtnDn) return; 159 | const cursorChrPos = this.getCursorChrPos(x); 160 | const c_x = this.get_cursor_x(cursorChrPos); 161 | let l; 162 | this.calcText(); 163 | if (cursorChrPos < this.start) { 164 | if (cursorChrPos < this.end) { 165 | if (c_x < panel.search.x) 166 | if (this.offset > 0) this.offset--; 167 | } else if (cursorChrPos > this.end) { 168 | if (c_x + panel.search.x > panel.search.x + panel.search.w) { 169 | l = (this.txt_w > panel.search.w) ? this.txt_w - panel.search.w : 0; 170 | if (l > 0) this.offset++; 171 | } 172 | } 173 | this.end = cursorChrPos; 174 | } else if (cursorChrPos > this.start) { 175 | if (c_x + panel.search.x > panel.search.x + panel.search.w) { 176 | l = (this.txt_w > panel.search.w) ? this.txt_w - panel.search.w : 0; 177 | if (l > 0) this.offset++; 178 | } 179 | this.end = cursorChrPos; 180 | } 181 | this.cx = cursorChrPos; 182 | panel.searchPaint(); 183 | } 184 | 185 | on_char(code, force) { 186 | let searchDone = false; 187 | let text = String.fromCharCode(code) || ''; 188 | if (force) panel.search.active = true; 189 | if (!panel.search.active || code == 5 || code == 9 || code == 12) return; 190 | panel.search.cursor = false; 191 | panel.pos = -1; 192 | switch (code) { 193 | case vk.enter: 194 | if (ppt.searchEnter || ppt.searchSend == 1) { 195 | lib.upd_search = true; 196 | lib.time.Reset(); 197 | pop.cache.search = {}; 198 | lib.setNodes(); 199 | panel.setHeight(true); 200 | if (panel.search.txt.length > 2) window.NotifyOthers(window.Name, !lib.list.Count ? lib.list : panel.list); 201 | else if (!panel.search.txt.length) pop.notifySelection(); 202 | lib.search.cancel(); 203 | this.logHistory(); 204 | searchDone = true; 205 | } 206 | if (ppt.searchSend == 1 || ppt.searchEnter && ppt.searchSend == 2) pop.load(panel.list, false, false, pop.autoPlay.send, !ppt.sendToCur, false); 207 | break; 208 | case vk.escape: 209 | this.clear(); 210 | return; 211 | case vk.redo: 212 | this.lg.push(panel.search.txt); 213 | if (this.lg.length > 30) this.lg.shift(); 214 | if (this.log.length > 0) { 215 | panel.search.txt = this.log.pop() + ''; 216 | this.cx++ 217 | } 218 | break; 219 | case vk.undo: 220 | this.log.push(panel.search.txt); 221 | if (this.log.length > 30) this.lg.shift(); 222 | if (this.lg.length > 0) panel.search.txt = this.lg.pop() + ''; 223 | break; 224 | case vk.selAll: 225 | this.start = 0; 226 | this.end = panel.search.txt.length; 227 | break; 228 | case vk.copy: 229 | if (this.start != this.end) $.setClipboardData(panel.search.txt.substring(this.start, this.end)); 230 | break; 231 | case vk.cut: 232 | if (this.start != this.end) $.setClipboardData(panel.search.txt.substring(this.start, this.end)); // fall through 233 | case vk.back: 234 | this.record(); 235 | if (this.start == this.end) { 236 | if (this.cx > 0) { 237 | panel.search.txt = panel.search.txt.substr(0, this.cx - 1) + panel.search.txt.substr(this.cx, panel.search.txt.length - this.cx); 238 | if (this.offset > 0) this.offset--; 239 | this.cx--; 240 | } 241 | } else { 242 | if (this.end - this.start == panel.search.txt.length) { 243 | panel.search.txt = ''; 244 | this.cx = 0; 245 | } else { 246 | if (this.start > 0) { 247 | const st = this.start; 248 | const en = this.end; 249 | this.start = Math.min(st, en); 250 | this.end = Math.max(st, en); 251 | panel.search.txt = panel.search.txt.substring(0, this.start) + panel.search.txt.substring(this.end, panel.search.txt.length); 252 | this.cx = this.start; 253 | } else { 254 | panel.search.txt = panel.search.txt.substring(this.end, panel.search.txt.length); 255 | this.cx = this.start; 256 | } 257 | } 258 | } 259 | this.calcText(); 260 | this.offset = this.offset >= this.end - this.start ? this.offset - this.end + this.start : 0; 261 | this.start = this.cx; 262 | this.end = this.start; 263 | break; 264 | case vk.ctrlBackspace: 265 | this.record(); 266 | if (this.start != this.end) this.cx = this.end = this.start; 267 | if (this.cx > 0) { 268 | const initial = panel.search.txt.length; 269 | const leftSide = panel.search.txt.slice(0, this.cx).trimEnd(); 270 | let boundary = 0; 271 | for (let k = 0; k < leftSide.length; k++) { 272 | if (panel.search.txt[k] == ' ' && panel.search.txt[k + 1] != ' ') boundary = k + 1; 273 | } 274 | panel.search.txt = leftSide.slice(0, boundary) + panel.search.txt.slice(this.cx).trimStart() 275 | this.cx = boundary; 276 | 277 | if (this.offset > 0) { 278 | this.offset -= initial - panel.search.txt.length; 279 | } 280 | } 281 | this.calcText(); 282 | this.offset = this.offset >= this.end - this.start ? this.offset - this.end + this.start : 0; 283 | this.start = this.cx; 284 | this.end = this.start; 285 | break; 286 | case 'delete': 287 | this.record(); 288 | if (this.start == this.end) { 289 | if (this.cx < panel.search.txt.length) { 290 | panel.search.txt = panel.search.txt.substr(0, this.cx) + panel.search.txt.substr(this.cx + 1, panel.search.txt.length - this.cx - 1); 291 | } 292 | } else { 293 | if (this.end - this.start == panel.search.txt.length) { 294 | panel.search.txt = ''; 295 | this.cx = 0; 296 | } else { 297 | if (this.start > 0) { 298 | const st = this.start; 299 | const en = this.end; 300 | this.start = Math.min(st, en); 301 | this.end = Math.max(st, en); 302 | panel.search.txt = panel.search.txt.substring(0, this.start) + panel.search.txt.substring(this.end, panel.search.txt.length); 303 | this.cx = this.start; 304 | } else { 305 | panel.search.txt = panel.search.txt.substring(this.end, panel.search.txt.length); 306 | this.cx = this.start; 307 | } 308 | } 309 | } 310 | this.calcText(); 311 | this.offset = this.offset >= this.end - this.start ? this.offset - this.end + this.start : 0; 312 | this.start = this.cx; 313 | this.end = this.start; 314 | break; 315 | case vk.paste: 316 | text = $.getClipboardData() || ''; 317 | text = text.replace(/(\r\n|\n|\r)/gm, ' '); // fall through 318 | default: 319 | this.record(); 320 | if (this.start == this.end) { 321 | panel.search.txt = panel.search.txt.substring(0, this.cx) + text + panel.search.txt.substring(this.cx); 322 | this.cx += text.length; 323 | this.end = this.start = this.cx; 324 | } else if (this.end > this.start) { 325 | panel.search.txt = panel.search.txt.substring(0, this.start) + text + panel.search.txt.substring(this.end); 326 | this.calcText(); 327 | this.offset = this.offset >= this.end - this.start ? this.offset - this.end + this.start : 0; 328 | this.cx = this.start + text.length; 329 | this.end = this.start = this.cx; 330 | } else { 331 | panel.search.txt = panel.search.txt.substring(0, this.end) + text + panel.search.txt.substring(this.start); 332 | this.calcText(); 333 | this.offset = this.offset < this.end - this.start ? this.offset - this.end + this.start : 0; 334 | this.cx = this.end + text.length; 335 | this.end = this.start = this.cx; 336 | } 337 | break; 338 | } 339 | if (code == vk.copy || code == vk.selAll) return; 340 | if (!timer.cursor.id) timer.searchCursor(); 341 | but.setSearchBtnsHide(); 342 | panel.searchPaint(); 343 | if (ppt.searchEnter || searchDone) return; 344 | if ((ppt.searchSend == 2 || lib.list.Count > 200000) && panel.search.txt && panel.search.txt.length < 4) { 345 | lib.upd_search = true; 346 | lib.search500(); 347 | this.logHistory(); 348 | } else { 349 | lib.search500.cancel(); 350 | lib.upd_search = true; 351 | lib.search(); 352 | this.logHistory(); 353 | } 354 | } 355 | 356 | on_key_down(vkey) { 357 | if (!panel.search.active) return; 358 | switch (vkey) { 359 | case vk.ctrl: 360 | this.ctrl = true; 361 | break; 362 | case vk.left: 363 | case vk.right: 364 | if (vkey == vk.left) { 365 | if (!this.ctrl) { 366 | if (this.offset > 0) { 367 | if (this.cx <= this.offset) { 368 | this.offset--; 369 | this.cx--; 370 | } else this.cx--; 371 | } else if (this.cx > 0) this.cx--; 372 | } else { 373 | let boundary = 0; 374 | for (let k = this.cx - 1; k > 0; k--) { 375 | if (panel.search.txt[k] != ' ' && panel.search.txt[k - 1] == ' ') { 376 | boundary = k; 377 | break; 378 | } 379 | } 380 | if (this.offset > 0) { 381 | this.offset -= (this.cx - boundary); 382 | } 383 | this.cx = boundary; 384 | this.offset = this.offset >= this.end - this.start ? this.offset - this.end + this.start : 0; 385 | } 386 | } 387 | if (vkey == vk.right && this.cx < panel.search.txt.length) { 388 | if (!this.ctrl) this.cx++; 389 | else { 390 | let boundary = panel.search.txt.length; 391 | for (let k = this.cx; k < panel.search.txt.length; k++) { 392 | if (panel.search.txt[k] == ' ' && panel.search.txt[k + 1] != ' ') { 393 | boundary = k + 1; 394 | break; 395 | } 396 | } 397 | this.cx = boundary; 398 | } 399 | } 400 | this.start = this.end = this.cx; 401 | if (this.shift) { 402 | this.start = Math.min(this.cx, this.shift_x); 403 | this.end = Math.max(this.cx, this.shift_x); 404 | } 405 | panel.search.cursor = true; 406 | timer.searchCursor(true); 407 | break; 408 | case vk.home: 409 | case vk.end: 410 | if (vkey == vk.home) this.offset = this.start = this.end = this.cx = 0; 411 | else this.start = this.end = this.cx = panel.search.txt.length; 412 | if (this.shift) { 413 | this.start = Math.min(this.cx, this.shift_x); 414 | this.end = Math.max(this.cx, this.shift_x); 415 | } 416 | panel.search.cursor = true; 417 | timer.searchCursor(true); 418 | break; 419 | case vk.shift: 420 | this.shift = true; 421 | this.shift_x = this.cx; 422 | break; 423 | case vk.del: 424 | if (this.ctrl && !this.shift && this.start == this.end) { // ctrl + delete: delete next word 425 | this.record(); 426 | const initial = panel.search.txt.length; 427 | const leftSide = panel.search.txt.slice(0, this.cx); 428 | const rightSide = panel.search.txt.slice(this.cx, panel.search.txt.length).trimStart(); 429 | const idx = rightSide.search(/ \b/); 430 | const boundary = idx == -1 ? rightSide.length : idx + 1; 431 | let newRightSide = rightSide.slice(boundary); 432 | if (newRightSide.length && !/\s$/.test(leftSide) && !/^\s/.test(newRightSide)) newRightSide = ' ' + newRightSide; 433 | panel.search.txt = leftSide + newRightSide; 434 | this.cx = !/\s$/.test(leftSide) ? leftSide.length + 1 : leftSide.length; 435 | if (this.offset > 0) { 436 | this.offset -= initial - panel.search.txt.length; 437 | } 438 | this.calcText(); 439 | this.offset = this.offset >= this.end - this.start ? this.offset - this.end + this.start : 0; 440 | this.start = this.end = this.cx; 441 | } else this.on_char('delete'); 442 | break; 443 | } 444 | panel.searchPaint(); 445 | } 446 | 447 | on_key_up(vkey) { 448 | if (!panel.search.active) return; 449 | if (vkey == vk.ctrl) { 450 | this.ctrl = false; 451 | } 452 | if (vkey == vk.shift) { 453 | this.shift = false; 454 | this.shift_x = this.cx; 455 | } 456 | } 457 | 458 | rbtn_up(x, y) { 459 | this.paste = $.getClipboardData() ? true : false; 460 | searchMenu.load(x, y); 461 | } 462 | 463 | record() { 464 | this.lg.push(panel.search.txt); 465 | this.log = []; 466 | if (this.lg.length > 30) this.lg.shift(); 467 | } 468 | 469 | focus() { 470 | panel.searchPaint(); 471 | panel.search.active = true; 472 | this.shift = false; 473 | this.start = this.end = this.cx = panel.search.x; 474 | panel.search.cursor = true; 475 | timer.searchCursor(true); 476 | panel.searchPaint(); 477 | } 478 | } 479 | 480 | class Find { 481 | constructor() { 482 | this.arc1 = 5; 483 | this.arc2 = 4; 484 | this.j = { 485 | x: 5, 486 | y: 5, 487 | w: 50, 488 | h: 30 489 | }; 490 | this.jSearch = ''; 491 | this.jump_search = true; 492 | this.initials = null; 493 | } 494 | 495 | // Methods 496 | 497 | draw(gr) { 498 | if (this.jSearch) { 499 | gr.SetSmoothingMode(4); 500 | this.j.w = gr.CalcTextWidth(this.jSearch, ui.font.find) + 25; 501 | gr.FillRoundRect(this.j.x - this.j.w / 2, this.j.y, this.j.w, this.j.h, this.arc1, this.arc1, 0x96000000); 502 | gr.DrawRoundRect(this.j.x - this.j.w / 2, this.j.y, this.j.w, this.j.h, this.arc1, this.arc1, 1, 0x64000000); 503 | gr.DrawRoundRect(this.j.x - this.j.w / 2 + 1, this.j.y + 1, this.j.w - 2, this.j.h - 2, this.arc2, this.arc2, 1, 0x28ffffff); 504 | gr.GdiDrawText(this.jSearch, ui.font.find, RGB(0, 0, 0), this.j.x - this.j.w / 2 + 1, this.j.y + 1, this.j.w, this.j.h, panel.cc); 505 | gr.GdiDrawText(this.jSearch, ui.font.find, this.jump_search ? 0xfffafafa : 0xffff4646, this.j.x - this.j.w / 2, this.j.y, this.j.w, this.j.h, panel.cc); 506 | gr.SetSmoothingMode(0); 507 | } 508 | } 509 | 510 | on_char(code) { 511 | const text = String.fromCharCode(code); 512 | let advance = false; 513 | if (panel.pos >= 0 && panel.pos < pop.tree.length) { 514 | const char = pop.tree[panel.pos].name.replace(/@!#.*?@!#/g, '').charAt(0).toLowerCase(); 515 | if (pop.tree[panel.pos].sel && char == text) advance = true; 516 | } 517 | switch (true) { 518 | case advance: 519 | if (utils.IsKeyPressed(0x0A) || utils.IsKeyPressed(0x08) || utils.IsKeyPressed(0x09) || utils.IsKeyPressed(0x11) || utils.IsKeyPressed(0x1B) || utils.IsKeyPressed(0x6A) || utils.IsKeyPressed(0x6D)) return; 520 | if (!panel.search.active) { 521 | let init = ''; 522 | let cur = 'currentArr'; 523 | if (!this.initials) { // reset in buildTree 524 | this.initials = {} 525 | pop.tree.forEach((v, i) => { 526 | if (!v.root) { 527 | const nm = v.name.replace(/@!#.*?@!#/g, ''); 528 | init = nm.charAt().toLowerCase(); 529 | if (cur != init && !this.initials[init]) { 530 | this.initials[init] = [i]; 531 | cur = init; 532 | } else { 533 | this.initials[init].push(i); 534 | } 535 | } 536 | }); 537 | } 538 | 539 | this.jump_search = false; 540 | if (panel.pos >= 0 && panel.pos < pop.tree.length) { 541 | this.matches = this.initials[text]; 542 | this.ix = this.matches.indexOf(panel.pos); 543 | this.ix++; 544 | if (this.ix >= this.matches.length) this.ix = 0; 545 | panel.pos = this.matches[this.ix]; 546 | this.jump_search = true; 547 | } 548 | if (this.jump_search) { 549 | pop.clearSelected(); 550 | pop.sel_items = []; 551 | pop.tree[panel.pos].sel = true; 552 | pop.setPos(panel.pos); 553 | pop.getTreeSel(); 554 | lib.treeState(false, ppt.rememberTree); 555 | panel.treePaint(); 556 | if (panel.imgView) pop.showItem(panel.pos, 'focus'); 557 | else { 558 | const row = (panel.pos * ui.row.h - sbar.scroll) / ui.row.h; 559 | if (sbar.rows_drawn - row < 3 || row < 0) sbar.checkScroll((panel.pos + 3) * ui.row.h - sbar.rows_drawn * ui.row.h); 560 | } 561 | if (ppt.libSource) { 562 | if (pop.autoFill.key) pop.load(pop.sel_items, true, false, false, !ppt.sendToCur, false); 563 | pop.track(pop.autoFill.key); 564 | } else if (panel.pos >= 0 && panel.pos < pop.tree.length) pop.setPlaylistSelection(panel.pos, pop.tree[panel.pos]); 565 | } else { 566 | this.jSearch = text; 567 | panel.treePaint(); 568 | timer.clear(timer.jsearch2); 569 | timer.jsearch2.id = setTimeout(() => { 570 | this.jSearch = ''; 571 | panel.treePaint(); 572 | timer.jsearch2.id = null; 573 | }, 1200); 574 | } 575 | } 576 | break; 577 | case !advance: 578 | if (utils.IsKeyPressed(0x09) || utils.IsKeyPressed(0x11) || utils.IsKeyPressed(0x1B) || utils.IsKeyPressed(0x6A) || utils.IsKeyPressed(0x6D)) return; 579 | if (!panel.search.active) { 580 | let found = false; 581 | let pos = -1; 582 | switch (code) { 583 | case vk.back: 584 | this.jSearch = this.jSearch.substr(0, this.jSearch.length - 1); 585 | break; 586 | case vk.enter: 587 | this.jSearch = ''; 588 | return; 589 | default: 590 | this.jSearch += text; 591 | break; 592 | } 593 | pop.clearSelected(); 594 | if (!this.jSearch) return; 595 | pop.sel_items = []; 596 | this.jump_search = true; 597 | panel.treePaint(); 598 | timer.clear(timer.jsearch1); 599 | timer.jsearch1.id = setTimeout(() => { 600 | pop.tree.some((v, i) => { 601 | const name = v.name.replace(/@!#.*?@!#/g, ''); 602 | if (name != panel.rootName && name.substring(0, this.jSearch.length).toLowerCase() == this.jSearch.toLowerCase()) { 603 | found = true; 604 | pos = i; 605 | v.sel = true; 606 | pop.setPos(pos); 607 | pop.getTreeSel(); 608 | lib.treeState(false, ppt.rememberTree); 609 | return true; 610 | } 611 | }); 612 | if (!found) this.jump_search = false; 613 | panel.treePaint(); 614 | if (found) pop.showItem(pos, 'focus'); 615 | timer.jsearch1.id = null; 616 | }, 500); 617 | 618 | timer.clear(timer.jsearch2); 619 | timer.jsearch2.id = setTimeout(() => { 620 | if (found) { 621 | if (ppt.libSource) { 622 | if (pop.autoFill.key) pop.load(pop.sel_items, true, false, false, !ppt.sendToCur, false); 623 | pop.track(pop.autoFill.key); 624 | } else if (pos >= 0 && pos < pop.tree.length) pop.setPlaylistSelection(pos, pop.tree[pos]); 625 | } 626 | this.jSearch = ''; 627 | panel.treePaint(); 628 | timer.jsearch2.id = null; 629 | }, 1200); 630 | } 631 | } 632 | } 633 | 634 | on_size() { 635 | this.j.x = Math.round(ui.w / 2); 636 | this.j.h = Math.round(ui.row.h * 1.5); 637 | this.j.y = Math.round((ui.h - this.j.h) / 2); 638 | this.arc1 = Math.min(5, this.j.h / 2); 639 | this.arc2 = Math.min(4, (this.j.h - 2) / 2); 640 | } 641 | } -------------------------------------------------------------------------------- /scripts/timers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Timers { 4 | constructor() { 5 | ['cursor', 'jsearch1', 'jsearch2', 'tt'].forEach(v => this[v] = { 6 | id: null 7 | }); 8 | } 9 | 10 | // Methods 11 | 12 | clear(timer) { 13 | if (timer) clearTimeout(timer.id); 14 | timer.id = null; 15 | } 16 | 17 | searchCursor(clear) { 18 | if (clear) this.clear(this.cursor); 19 | if (!panel.search.cursor) panel.search.cursor = true; 20 | this.cursor.id = setInterval(() => { 21 | panel.search.cursor = !panel.search.cursor; 22 | panel.searchPaint(); 23 | }, 530); 24 | } 25 | 26 | tooltip() { 27 | this.clear(this.tt); 28 | this.tt.id = setTimeout(() => { 29 | pop.deactivateTooltip(); 30 | this.tt.id = null; 31 | }, 5000); 32 | } 33 | } -------------------------------------------------------------------------------- /scripts/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let my_utils = {} 4 | 5 | my_utils.scriptInfo = window.ScriptInfo; 6 | my_utils.packageInfo = utils.GetPackageInfo(my_utils.scriptInfo.PackageId); 7 | my_utils.packagePath = `${my_utils.packageInfo.Directories.Root}/`; 8 | 9 | my_utils.getAsset = assetFile => utils.ReadTextFile(`${my_utils.packageInfo.Directories.Assets}/${assetFile}`); 10 | my_utils.getImageAssets = assetFolder => utils.Glob(`${my_utils.packageInfo.Directories.Assets}/images/${assetFolder}/*`); 11 | my_utils.getScriptPath = `${my_utils.packageInfo.Directories.Scripts}/`; --------------------------------------------------------------------------------