diff --git a/.github/workflows/download_cleanup.yml b/.github/workflows/download_cleanup.yml new file mode 100644 index 00000000000..397cffeb48e --- /dev/null +++ b/.github/workflows/download_cleanup.yml @@ -0,0 +1,34 @@ +name: Clean up downloads + +on: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-22.04 + + steps: + - name: "Set up SSH Agent" + if: env.SSH_PRIVATE_KEY != null + env: + SSH_AUTH_SOCK: /tmp/ssh_agent.sock + SSH_PRIVATE_KEY: ${{ secrets.DOWNLOADS_HOSTGATOR_DOT_MIXXX_DOT_ORG_KEY }} + SSH_HOST: downloads-hostgator.mixxx.org + run: | + ssh-agent -a $SSH_AUTH_SOCK > /dev/null + ssh-add - <<< "${SSH_PRIVATE_KEY}" + mkdir -p "${HOME}/.ssh" + ssh-keyscan "${SSH_HOST}" >> "${HOME}/.ssh/known_hosts" + echo "SSH_AUTH_SOCK=${SSH_AUTH_SOCK}" >> "${GITHUB_ENV}" + + - name: Delete obsolete files + if: env.SSH_AUTH_SOCK != null + run: | + mkdir empty_folder + echo build-checks-fix/ >> include_file.txt + echo build-checks-fix/* >> include_file.txt + rsync --verbose --archive --times --recursive --delete --include-from=include_file.txt --exclude=* "empty_folder/" "${SSH_USER}@${SSH_HOST}:${DESTDIR}/" + env: + DESTDIR: public_html/downloads/snapshots + SSH_HOST: downloads-hostgator.mixxx.org + SSH_USER: mixxx diff --git a/CHANGELOG.md b/CHANGELOG.md index 61c4d4c681d..7baa6c42512 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,6 @@ ### Features -* Sync Lock: End of track checking is not needed [#11035](https://github.com/mixxxdj/mixxx/pull/11035) * BaseExternalLibraryFeature: Add ability to import external playlists as crates [#11852](https://github.com/mixxxdj/mixxx/pull/11852) * Logging: Include timestamps in messages by default [#11861](https://github.com/mixxxdj/mixxx/pull/11861) * DlgPrefSound: Fix fetching of sample rate [#11951](https://github.com/mixxxdj/mixxx/pull/11951) [11949](https://github.com/mixxxdj/mixxx/issues/11949) @@ -14,16 +13,23 @@ * Playlist feature: add 'Shuffle playlist' sidebar action [#12498](https://github.com/mixxxdj/mixxx/pull/12498) * New built-in effect: Glitch [#11329](https://github.com/mixxxdj/mixxx/pull/11329) * Fullscreen toggle rework [#11566](https://github.com/mixxxdj/mixxx/pull/11566) +* Playlists: Update of playlist labels after adding tracks [#12866](https://github.com/mixxxdj/mixxx/pull/12866) [#12761](https://github.com/mixxxdj/mixxx/issues/12761) +* Tracks: Custom text color for played tracks (qss) [#12744](https://github.com/mixxxdj/mixxx/pull/12744) [#5911](https://github.com/mixxxdj/mixxx/issues/5911) [#12912](https://github.com/mixxxdj/mixxx/pull/12912) +* History: Show track count and duration in sidebar [#12811](https://github.com/mixxxdj/mixxx/pull/12811) [#12788](https://github.com/mixxxdj/mixxx/issues/12788) +* fixes around cratetablemodel, remove tracks + don't allow pasting tracks into locked playlists/crates or History [#12926](https://github.com/mixxxdj/mixxx/pull/12926) +* Tootips: Improve `rate_up/down` tooltips, pitch vs. speed [#12590](https://github.com/mixxxdj/mixxx/pull/12590) +* Shortkeys for track list management [#12020](https://github.com/mixxxdj/mixxx/pull/12020) +* Track menu: Rephrase "Reset" to "Clear" [#12955](https://github.com/mixxxdj/mixxx/pull/12955) ### Controller Mappings -* Traktor Kontrol S2 MK3: Add colored hotcues [#4637](https://github.com/mixxxdj/mixxx/pull/4637) * Pioneer DDJ-FLX4: mapping improvements [#12842](https://github.com/mixxxdj/mixxx/pull/12842) ### Controller Backend * Send sysex to all handlers [#12827](https://github.com/mixxxdj/mixxx/pull/12827) * Add control for showing a deck's track menu [#10825](https://github.com/mixxxdj/mixxx/pull/10825) +* Removed old examples HID keyboard and HID trackpad [#12977](https://github.com/mixxxdj/mixxx/pull/12977) ### Experimental QML Skin @@ -34,6 +40,8 @@ * fix(qml): Improve knobs by applying selective 4xMSAA on the Arc shape [#12541](https://github.com/mixxxdj/mixxx/pull/12541) * Add QML interceptor to auto reload on file change [#12795](https://github.com/mixxxdj/mixxx/pull/12795) * Add multi-sampling settings for QML [#12546](https://github.com/mixxxdj/mixxx/pull/12546) [#12794](https://github.com/mixxxdj/mixxx/pull/12794) [#12536](https://github.com/mixxxdj/mixxx/issues/12536) +* Install qml module on Windows [#12604](https://github.com/mixxxdj/mixxx/pull/12604) +* Add scrolling waveforms [#3967](https://github.com/mixxxdj/mixxx/pull/3967) ### Update to Qt6 @@ -59,6 +67,7 @@ * Fix track color background with Qt6 [#12380](https://github.com/mixxxdj/mixxx/pull/12380) * multi-line delegate: fix bg color, Qt6 on Linux [#12478](https://github.com/mixxxdj/mixxx/pull/12478) * Revert "BaseTrackPlayer: Remove references to WaveformWidgetRenderer when using Qt6" [#12342](https://github.com/mixxxdj/mixxx/pull/12342) +* Fix Tango waveform splitter [#12939](https://github.com/mixxxdj/mixxx/pull/12939) ### Experimental iOs support @@ -78,6 +87,20 @@ * MacOsVersion: Rename to AppleOsVersion [#12662](https://github.com/mixxxdj/mixxx/pull/12662) * macOS: Add more `Q_OS_MACOS` cond-compiles where appropriate [#12663](https://github.com/mixxxdj/mixxx/pull/12663) +### Experimental WebAssembly support + +* CMakeLists: Add support for targeting Emscripten/WebAssembly [#12918](https://github.com/mixxxdj/mixxx/pull/12918) +* CMakeLists: Emit better errors for exotic target platforms [#12910](https://github.com/mixxxdj/mixxx/pull/12910) +* Build: Add `PORTMIDI` flag for compiling with(out) PortMidi [#12913](https://github.com/mixxxdj/mixxx/pull/12913) +* DesktopHelper: Compile out process-spawning on WASM too [#12916](https://github.com/mixxxdj/mixxx/pull/12916) +* MixxxApplication: Use `QWasmIntegrationPlugin` when targeting WebAssembly [#12915](https://github.com/mixxxdj/mixxx/pull/12915) +* CMakeLists: Enable asyncify when targeting WASM [#12921](https://github.com/mixxxdj/mixxx/pull/12921) +* Resources: Bundle resources for preloading when targeting Emscripten/WASM [#12922](https://github.com/mixxxdj/mixxx/pull/12922) +* CMakeLists: Add `WASM_ASSERTIONS` option [#12931](https://github.com/mixxxdj/mixxx/pull/12931) +* VersionStore: Recognize Emscripten/WebAssembly [#12940](https://github.com/mixxxdj/mixxx/pull/12940) +* OpenGLWindow: Fix sizing on Wasm by setting `Qt::FramelessWindowHint` [#12945](https://github.com/mixxxdj/mixxx/pull/12945) +* CMakeLists: Require WebGL 2.0 when building for Wasm [#12952](https://github.com/mixxxdj/mixxx/pull/12952) + ### Misc Refactorings * Add missing `` include in `defs.h` [#11348](https://github.com/mixxxdj/mixxx/pull/11348) @@ -111,10 +134,21 @@ * fix/History: remove obsolete placeholder playlists [#12494](https://github.com/mixxxdj/mixxx/pull/12494) * Add missing Taglib dependency [#12830](https://github.com/mixxxdj/mixxx/pull/12830) * fix: typo ;) [#12726](https://github.com/mixxxdj/mixxx/pull/12726) +* refactor: Avoid temporary qlist allocation on midi sysex receive [#12843](https://github.com/mixxxdj/mixxx/pull/12843) +* SoundSourceMP3: fix unused function warning [#12847](https://github.com/mixxxdj/mixxx/pull/12847) +* update libkeyfinder to 2.2.8 [#12853](https://github.com/mixxxdj/mixxx/pull/12853) +* feat(ci): Add eslint rule prefer-template [#12889](https://github.com/mixxxdj/mixxx/pull/12889) +* Labeler: Add `qml` to labeler config [#12911](https://github.com/mixxxdj/mixxx/pull/12911) +* WTrackMenu: Add missing wcoverartlabel.h include [#12924](https://github.com/mixxxdj/mixxx/pull/12924) +* Fix clazy complains and naming [#12935](https://github.com/mixxxdj/mixxx/pull/12935) +* src/library: Sort files into sub-directories [#12956](https://github.com/mixxxdj/mixxx/pull/12956) +* CMakeLists: Fix deduplication trap with `--preload-file` [#12944](https://github.com/mixxxdj/mixxx/pull/12944) +* Add CI runner that allows cleaning up the download server [#12957](https://github.com/mixxxdj/mixxx/pull/12957) * Update to latest vcpkg dependencies [#11649](https://github.com/mixxxdj/mixxx/pull/11649) [#12512](https://github.com/mixxxdj/mixxx/pull/12512) [#12067](https://github.com/mixxxdj/mixxx/pull/12067) + [#12898](https://github.com/mixxxdj/mixxx/pull/12898) * GitHub actions updates [#11544](https://github.com/mixxxdj/mixxx/pull/11544) [#11508](https://github.com/mixxxdj/mixxx/pull/11508) diff --git a/CMakeLists.txt b/CMakeLists.txt index 95f63f4fa55..7c67775d256 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -888,8 +888,10 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/engine/sync/internalclock.cpp src/engine/sync/synccontrol.cpp src/errordialoghandler.cpp - src/library/analysisfeature.cpp - src/library/analysislibrarytablemodel.cpp + src/library/analysis/analysisfeature.cpp + src/library/analysis/analysislibrarytablemodel.cpp + src/library/analysis/dlganalysis.cpp + src/library/analysis/dlganalysis.ui src/library/autodj/autodjfeature.cpp src/library/autodj/autodjprocessor.cpp src/library/autodj/dlgautodj.cpp @@ -903,16 +905,13 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/library/basesqltablemodel.cpp src/library/basetrackcache.cpp src/library/basetracktablemodel.cpp - src/library/bpmdelegate.cpp src/library/browse/browsefeature.cpp src/library/browse/browsetablemodel.cpp src/library/browse/browsethread.cpp src/library/browse/foldertreemodel.cpp - src/library/colordelegate.cpp src/library/columncache.cpp src/library/coverart.cpp src/library/coverartcache.cpp - src/library/coverartdelegate.cpp src/library/coverartutils.cpp src/library/dao/analysisdao.cpp src/library/dao/autodjcratesdao.cpp @@ -923,14 +922,8 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/library/dao/settingsdao.cpp src/library/dao/trackdao.cpp src/library/dao/trackschema.cpp - src/library/dlganalysis.cpp - src/library/dlganalysis.ui src/library/dlgcoverartfullsize.cpp src/library/dlgcoverartfullsize.ui - src/library/dlghidden.cpp - src/library/dlghidden.ui - src/library/dlgmissing.cpp - src/library/dlgmissing.ui src/library/dlgtagfetcher.cpp src/library/dlgtagfetcher.ui src/library/dlgtrackinfo.cpp @@ -942,7 +935,6 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/library/export/trackexportwizard.cpp src/library/export/trackexportworker.cpp src/library/externaltrackcollection.cpp - src/library/hiddentablemodel.cpp src/library/itunes/itunesdao.cpp src/library/itunes/itunesfeature.cpp src/library/itunes/itunesimporter.cpp @@ -953,17 +945,18 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/library/librarycontrol.cpp src/library/libraryfeature.cpp src/library/librarytablemodel.cpp - src/library/locationdelegate.cpp - src/library/missingtablemodel.cpp + src/library/missing_hidden/dlghidden.cpp + src/library/missing_hidden/dlghidden.ui + src/library/missing_hidden/dlgmissing.cpp + src/library/missing_hidden/dlgmissing.ui + src/library/missing_hidden/hiddentablemodel.cpp + src/library/missing_hidden/missingtablemodel.cpp src/library/mixxxlibraryfeature.cpp - src/library/multilineeditdelegate.cpp src/library/parser.cpp src/library/parsercsv.cpp src/library/parserm3u.cpp src/library/parserpls.cpp - src/library/playcountdelegate.cpp src/library/playlisttablemodel.cpp - src/library/previewbuttondelegate.cpp src/library/proxytrackmodel.cpp src/library/recording/dlgrecording.cpp src/library/recording/dlgrecording.ui @@ -982,10 +975,17 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/library/serato/seratofeature.cpp src/library/serato/seratoplaylistmodel.cpp src/library/sidebarmodel.cpp - src/library/stardelegate.cpp - src/library/stareditor.cpp src/library/starrating.cpp - src/library/tableitemdelegate.cpp + src/library/tabledelegates/bpmdelegate.cpp + src/library/tabledelegates/colordelegate.cpp + src/library/tabledelegates/coverartdelegate.cpp + src/library/tabledelegates/locationdelegate.cpp + src/library/tabledelegates/multilineeditdelegate.cpp + src/library/tabledelegates/playcountdelegate.cpp + src/library/tabledelegates/previewbuttondelegate.cpp + src/library/tabledelegates/stardelegate.cpp + src/library/tabledelegates/stareditor.cpp + src/library/tabledelegates/tableitemdelegate.cpp src/library/trackcollection.cpp src/library/trackcollectioniterator.cpp src/library/trackcollectionmanager.cpp @@ -1141,6 +1141,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/track/trackref.cpp src/util/battery/battery.cpp src/util/cache.cpp + src/util/clipboard.cpp src/util/cmdlineargs.cpp src/util/color/color.cpp src/util/color/colorpalette.cpp @@ -1830,7 +1831,7 @@ if(EMSCRIPTEN) # This will generate a mixxx.data file containing all the resources. # See https://emscripten.org/docs/porting/files/packaging_files.html # TODO: Strip this down by only including what we need (i.e. no macOS/Linux packaging, ...) - target_link_options(mixxx-lib PUBLIC --preload-file "${CMAKE_CURRENT_SOURCE_DIR}/res@/res") + target_link_options(mixxx-lib PUBLIC "--preload-file=${CMAKE_CURRENT_SOURCE_DIR}/res@/res") endif() if(WIN32) diff --git a/res/Mixxx-Keyboard-Shortcuts.pdf b/res/Mixxx-Keyboard-Shortcuts.pdf index 38277941820..312a1810125 100644 Binary files a/res/Mixxx-Keyboard-Shortcuts.pdf and b/res/Mixxx-Keyboard-Shortcuts.pdf differ diff --git a/res/Mixxx-Keyboard-Shortcuts.png b/res/Mixxx-Keyboard-Shortcuts.png deleted file mode 100644 index a757fadfb12..00000000000 Binary files a/res/Mixxx-Keyboard-Shortcuts.png and /dev/null differ diff --git a/res/controllers/HID Keyboard.hid.xml.example b/res/controllers/HID Keyboard.hid.xml.example deleted file mode 100644 index c4be603bff7..00000000000 --- a/res/controllers/HID Keyboard.hid.xml.example +++ /dev/null @@ -1,18 +0,0 @@ - - - - Apple Bluetooth Keyboard - Ilkka Tuohela - HID example for Apple Bluetooth Keyboard - - - - - - - - - - - - diff --git a/res/controllers/HID Trackpad.hid.xml.example b/res/controllers/HID Trackpad.hid.xml.example deleted file mode 100644 index 89689f3a7d2..00000000000 --- a/res/controllers/HID Trackpad.hid.xml.example +++ /dev/null @@ -1,18 +0,0 @@ - - - - Apple Bluetooth Trackpad - Ilkka Tuohela - HID example for Apple Bluetooth Trackpad - - - - - - - - - - - - diff --git a/res/controllers/HID-Keyboard.js b/res/controllers/HID-Keyboard.js deleted file mode 100644 index 29efbc51838..00000000000 --- a/res/controllers/HID-Keyboard.js +++ /dev/null @@ -1,29 +0,0 @@ -// -// Demo script to print events from apple bluetooth keyboard on OS/X -// Copyright (C) 2012, Ilkka Tuohela -// Feel free to use whatever way wish1 -// - -HIDKeyboard = new HIDKeyboardDevice(); - -HIDKeyboard.init = function(id) { - HIDKeyboard.id = id; - HIDKeyboard.registerInputPackets(); - HIDKeyboard.registerOutputPackets(); - HIDKeyboard.registerScalers(); - HIDKeyboard.registerCallbacks(); - HIDDebug("HID Keyboard Initialized: " + HIDKeyboard.id); -} - -HIDKeyboard.shutdown = function() { - HIDDebug("HID Keyboard Shutdown: " + HIDKeyboard.id); -} - -HIDKeyboard.incomingData = function(data,length) { - var controller = HIDKeyboard.controller; - if (controller==undefined) { - HIDDebug("Error in script initialization: controller not found"); - return; - } - controller.parsePacket(data,length); -} diff --git a/res/controllers/HID-Trackpad.js b/res/controllers/HID-Trackpad.js deleted file mode 100644 index 2a0c48f0e11..00000000000 --- a/res/controllers/HID-Trackpad.js +++ /dev/null @@ -1,31 +0,0 @@ -// -// Demo script to print events from apple bluetooth trackpad on OS/X -// NOTE: the trackpad doesn't seem to actually send events to us. This -// is just a silly example anyway -// Copyright (C) 2012, Ilkka Tuohela -// Feel free to use whatever way wish1 -// - -HIDTrackpad = new HIDTrackpadDevice(); - -HIDTrackpad.init = function(id) { - HIDTrackpad.id = id; - HIDTrackpad.registerInputPackets(); - HIDTrackpad.registerOutputPackets(); - HIDTrackpad.registerScalers(); - HIDTrackpad.registerCallbacks(); - HIDDebug("HID Trackpad Initialized: " + HIDTrackpad.id); -} - -HIDTrackpad.shutdown = function() { - HIDDebug("HID Trackpad Shutdown: " + HIDTrackpad.id); -} - -HIDTrackpad.incomingData = function(data,length) { - var controller = HIDTrackpad.controller; - if (controller==undefined) { - HIDDebug("Error in script initialization: controller not found"); - return; - } - controller.parsePacket(data,length); -} diff --git a/res/controllers/common-hid-devices.js b/res/controllers/common-hid-devices.js deleted file mode 100644 index 6d4e917bf2b..00000000000 --- a/res/controllers/common-hid-devices.js +++ /dev/null @@ -1,76 +0,0 @@ -// Generic HID trackpad implementation -HIDTrackpadDevice = function() { - this.controller = new HIDController() - - this.registerInputPackets = function() { - // Example how to register a callback directly on a packet - packet = new HIDPacket("control", 0, this.mouseInput) - packet.addControl("hid", "byte_1", 3, "B") - packet.addControl("hid", "byte_2", 4, "B") - packet.addControl("hid", "byte_3", 5, "B") - packet.addControl("hid", "byte_4", 6, "B") - this.controller.registerInputPacket(packet) - } - - // HID trackpads have no output controls - this.registerOutputPackets = function() { } - - // No need to do scaling for keyboard presses - this.registerScalers = function() { } - - // No need for callbacks here, we bound whole input packet to mouseInput - this.registerCallbacks = function() { } - - // Example to process the mouse input packet all yourself - this.mouseInput = function(packet, delta) { - HIDDebug("Trackpad INPUT " + packet.name) - if (!delta.length) { - HIDDebug("No changed data received in HID packet") - return - } - for (var field_name in delta) { - var field = delta[field_name] - HIDDebug("FIELD " + field.id + " VALUE " + value) - } - } -} - -// Generic HID keyboard implementation -HIDKeyboardDevice = function() { - this.controller = new HIDController() - - this.registerInputPackets = function() { - packet = new HIDPacket("control", 0x1) - packet.addControl("hid", "keycode_1", 3, "B") - packet.addControl("hid", "keycode_2", 4, "B") - packet.addControl("hid", "keycode_3", 5, "B") - packet.addControl("hid", "keycode_4", 6, "B") - packet.addControl("hid", "keycode_5", 7, "B") - packet.addControl("hid", "keycode_6", 8, "B") - this.controller.registerInputPacket(packet) - } - - // HID keyboards have no output controls - this.registerOutputPackets = function() { } - - // No need to do scaling for keyboard presses - this.registerScalers = function() { } - - // Example to bind the bytes to a callback - this.registerCallbacks = function() { - this.controller.setCallback("control", "hid", "keycode_1", this.keyPress) - this.controller.setCallback("control", "hid", "keycode_2", this.keyPress) - this.controller.setCallback("control", "hid", "keycode_3", this.keyPress) - this.controller.setCallback("control", "hid", "keycode_4", this.keyPress) - this.controller.setCallback("control", "hid", "keycode_5", this.keyPress) - this.controller.setCallback("control", "hid", "keycode_6", this.keyPress) - } - - // Example to do something with the keycodes received - this.keyPress = function(field) { - if (field.value != 0) - HIDDebug("KEY PRESS " + field.id + " CODE " + field.value) - else - HIDDebug("KEY RELEASE " + field.id) - } -} diff --git a/res/images/templates/ic_template_keyboard_mapping_sheet.svg b/res/images/templates/ic_template_keyboard_mapping_sheet.svg index 948f5dfa139..8d3316c89b1 100644 --- a/res/images/templates/ic_template_keyboard_mapping_sheet.svg +++ b/res/images/templates/ic_template_keyboard_mapping_sheet.svg @@ -7,7 +7,7 @@ id="svg1180" sodipodi:docname="ic_template_keyboard_mapping_sheet.svg" inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)" - inkscape:export-filename="../../Mixxx-Keyboard-Shortcuts.png" + inkscape:export-filename="../../Mixxx-Keyboard-Shortcuts.pdf" inkscape:export-xdpi="148.322" inkscape:export-ydpi="148.322" xml:space="preserve" @@ -28,14 +28,14 @@ inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:zoom="4.7711688" - inkscape:cx="514.23459" - inkscape:cy="159.18531" + inkscape:cx="366.89123" + inkscape:cy="166.52104" inkscape:window-width="1920" inkscape:window-height="1056" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" - inkscape:current-layer="layer2" + inkscape:current-layer="layer1" showguides="true" />Mixxx Keyboard mapping sheet templateEach control inside Mixxx is identified by a unique string. These strings are used in the keyboard mappings, the MIDI mappings, and inside Mixxx to gain access to the controls. The following is a list of controls that can be used in any of the above contexts. * for de-de keyboard layout - für Deutsche Tastatur * for en-us keyboard layout * for en-us keyboard layoutMixxx Keyboard mapping sheet templateAutoDJAutoDJdouble + id="tspan4">double clickclickstopload+ playstopload+ - +

Features

    -
  • - Sync Lock: End of track checking is not needed - #11035 -
  • BaseExternalLibraryFeature: Add ability to import external playlists as crates #11852 @@ -143,15 +139,45 @@ Fullscreen toggle rework #11566
  • +
  • + Playlists: Update of playlist labels after adding tracks + #12866 + #12761 +
  • +
  • + Tracks: Custom text color for played tracks (qss) + #12744 + #5911 + #12912 +
  • +
  • + History: Show track count and duration in sidebar + #12811 + #12788 +
  • +
  • + fixes around cratetablemodel, remove tracks + don't allow pasting tracks into locked playlists/crates or History + #12926 +
  • +
  • + Tootips: Improve + rate_up/down + tooltips, pitch vs. speed + #12590 +
  • +
  • + Shortkeys for track list management + #12020 +
  • +
  • + Track menu: Rephrase "Reset" to "Clear" + #12955 +

Controller Mappings

    -
  • - Traktor Kontrol S2 MK3: Add colored hotcues - #4637 -
  • Pioneer DDJ-FLX4: mapping improvements #12842 @@ -169,6 +195,10 @@ Add control for showing a deck's track menu #10825
  • +
  • + Removed old examples HID keyboard and HID trackpad + #12977 +

Experimental QML Skin @@ -207,6 +237,14 @@ #12794 #12536 +

  • + Install qml module on Windows + #12604 +
  • +
  • + Add scrolling waveforms + #3967 +
  • Update to Qt6 @@ -300,6 +338,10 @@ Revert "BaseTrackPlayer: Remove references to WaveformWidgetRenderer when using Qt6" #12342 +

  • + Fix Tango waveform splitter + #12939 +
  • Experimental iOs support @@ -370,6 +412,62 @@ #12663 +

    + Experimental WebAssembly support +

    +
      +
    • + CMakeLists: Add support for targeting Emscripten/WebAssembly + #12918 +
    • +
    • + CMakeLists: Emit better errors for exotic target platforms + #12910 +
    • +
    • + Build: Add + PORTMIDI + flag for compiling with(out) PortMidi + #12913 +
    • +
    • + DesktopHelper: Compile out process-spawning on WASM too + #12916 +
    • +
    • + MixxxApplication: Use + QWasmIntegrationPlugin + when targeting WebAssembly + #12915 +
    • +
    • + CMakeLists: Enable asyncify when targeting WASM + #12921 +
    • +
    • + Resources: Bundle resources for preloading when targeting Emscripten/WASM + #12922 +
    • +
    • + CMakeLists: Add + WASM_ASSERTIONS + option + #12931 +
    • +
    • + VersionStore: Recognize Emscripten/WebAssembly + #12940 +
    • +
    • + OpenGLWindow: Fix sizing on Wasm by setting + Qt::FramelessWindowHint + #12945 +
    • +
    • + CMakeLists: Require WebGL 2.0 when building for Wasm + #12952 +
    • +

    Misc Refactorings

    @@ -519,11 +617,55 @@ fix: typo ;) #12726 +
  • + refactor: Avoid temporary qlist allocation on midi sysex receive + #12843 +
  • +
  • + SoundSourceMP3: fix unused function warning + #12847 +
  • +
  • + update libkeyfinder to 2.2.8 + #12853 +
  • +
  • + feat(ci): Add eslint rule prefer-template + #12889 +
  • +
  • + Labeler: Add + qml + to labeler config + #12911 +
  • +
  • + WTrackMenu: Add missing wcoverartlabel.h include + #12924 +
  • +
  • + Fix clazy complains and naming + #12935 +
  • +
  • + src/library: Sort files into sub-directories + #12956 +
  • +
  • + CMakeLists: Fix deduplication trap with + --preload-file + #12944 +
  • +
  • + Add CI runner that allows cleaning up the download server + #12957 +
  • Update to latest vcpkg dependencies #11649 #12512 #12067 + #12898
  • GitHub actions updates @@ -562,7 +704,7 @@ - +

    Controller Mappings diff --git a/src/coreservices.cpp b/src/coreservices.cpp index c0e1ccc10fe..3a66ab5decc 100644 --- a/src/coreservices.cpp +++ b/src/coreservices.cpp @@ -30,6 +30,7 @@ #include "skin/skincontrols.h" #include "soundio/soundmanager.h" #include "sources/soundsourceproxy.h" +#include "util/clipboard.h" #include "util/db/dbconnectionpooled.h" #include "util/font.h" #include "util/logger.h" @@ -335,6 +336,7 @@ void CoreServices::initialize(QApplication* pApp) { emit initializationProgressUpdate(50, tr("library")); CoverArtCache::createInstance(); + Clipboard::createInstance(); m_pTrackCollectionManager = std::make_shared( this, @@ -580,6 +582,8 @@ void CoreServices::finalize() { // CoverArtCache is fairly independent of everything else. CoverArtCache::destroy(); + Clipboard::destroy(); + // PlayerManager depends on Engine, SoundManager, VinylControlManager, and Config // The player manager has to be deleted before the library to ensure // that all modified track metadata of loaded tracks is saved. diff --git a/src/library/analysisfeature.cpp b/src/library/analysis/analysisfeature.cpp similarity index 98% rename from src/library/analysisfeature.cpp rename to src/library/analysis/analysisfeature.cpp index ebe954c551d..a713f57ddfc 100644 --- a/src/library/analysisfeature.cpp +++ b/src/library/analysis/analysisfeature.cpp @@ -1,11 +1,11 @@ -#include "library/analysisfeature.h" +#include "library/analysis/analysisfeature.h" #include #include #include "analyzer/analyzerscheduledtrack.h" #include "controllers/keyboard/keyboardeventfilter.h" -#include "library/dlganalysis.h" +#include "library/analysis/dlganalysis.h" #include "library/library.h" #include "library/trackcollectionmanager.h" #include "moc_analysisfeature.cpp" diff --git a/src/library/analysisfeature.h b/src/library/analysis/analysisfeature.h similarity index 100% rename from src/library/analysisfeature.h rename to src/library/analysis/analysisfeature.h diff --git a/src/library/analysislibrarytablemodel.cpp b/src/library/analysis/analysislibrarytablemodel.cpp similarity index 93% rename from src/library/analysislibrarytablemodel.cpp rename to src/library/analysis/analysislibrarytablemodel.cpp index 4eeccbf5e1b..574c1545b98 100644 --- a/src/library/analysislibrarytablemodel.cpp +++ b/src/library/analysis/analysislibrarytablemodel.cpp @@ -1,4 +1,4 @@ -#include "library/analysislibrarytablemodel.h" +#include "library/analysis/analysislibrarytablemodel.h" #include "moc_analysislibrarytablemodel.cpp" diff --git a/src/library/analysislibrarytablemodel.h b/src/library/analysis/analysislibrarytablemodel.h similarity index 100% rename from src/library/analysislibrarytablemodel.h rename to src/library/analysis/analysislibrarytablemodel.h diff --git a/src/library/dlganalysis.cpp b/src/library/analysis/dlganalysis.cpp similarity index 98% rename from src/library/dlganalysis.cpp rename to src/library/analysis/dlganalysis.cpp index 36bb12953b6..3f67fe0bbd8 100644 --- a/src/library/dlganalysis.cpp +++ b/src/library/analysis/dlganalysis.cpp @@ -1,7 +1,8 @@ -#include "library/dlganalysis.h" +#include "library/analysis/dlganalysis.h" #include "analyzer/analyzerprogress.h" #include "analyzer/analyzerscheduledtrack.h" +#include "library/analysis/ui_dlganalysis.h" #include "library/dao/trackschema.h" #include "library/library.h" #include "moc_dlganalysis.cpp" diff --git a/src/library/dlganalysis.h b/src/library/analysis/dlganalysis.h similarity index 95% rename from src/library/dlganalysis.h rename to src/library/analysis/dlganalysis.h index c4d90c56521..ca32cc02f84 100644 --- a/src/library/dlganalysis.h +++ b/src/library/analysis/dlganalysis.h @@ -4,13 +4,13 @@ #include "analyzer/analyzerprogress.h" #include "analyzer/analyzerscheduledtrack.h" -#include "library/analysislibrarytablemodel.h" +#include "library/analysis/analysislibrarytablemodel.h" +#include "library/analysis/ui_dlganalysis.h" #include "library/libraryview.h" -#include "library/ui_dlganalysis.h" #include "preferences/usersettings.h" -class WAnalysisLibraryTableView; class Library; +class WAnalysisLibraryTableView; class WLibrary; class QItemSelection; diff --git a/src/library/dlganalysis.ui b/src/library/analysis/dlganalysis.ui similarity index 100% rename from src/library/dlganalysis.ui rename to src/library/analysis/dlganalysis.ui diff --git a/src/library/autodj/autodjfeature.cpp b/src/library/autodj/autodjfeature.cpp index 59525f24a38..cdca01516af 100644 --- a/src/library/autodj/autodjfeature.cpp +++ b/src/library/autodj/autodjfeature.cpp @@ -14,6 +14,8 @@ #include "moc_autodjfeature.cpp" #include "sources/soundsourceproxy.h" #include "track/track.h" +#include "util/clipboard.h" +#include "util/dnd.h" #include "widget/wlibrary.h" #include "widget/wlibrarysidebar.h" @@ -161,6 +163,14 @@ void AutoDJFeature::activate() { emit enableCoverArtDisplay(true); } +void AutoDJFeature::clear() { + m_playlistDao.clearAutoDJQueue(); +} + +void AutoDJFeature::paste() { + emit pasteFromSidebar(); +} + bool AutoDJFeature::dropAccept(const QList& urls, QObject* pSource) { // If a track is dropped onto the Auto DJ tree node, but the track isn't in the // library, then add the track to the library before adding it to the diff --git a/src/library/autodj/autodjfeature.h b/src/library/autodj/autodjfeature.h index 8a9a9d2a4fd..70451a49d7a 100644 --- a/src/library/autodj/autodjfeature.h +++ b/src/library/autodj/autodjfeature.h @@ -32,6 +32,9 @@ class AutoDJFeature : public LibraryFeature { QVariant title() override; + void clear() override; + void paste() override; + bool dropAccept(const QList& urls, QObject* pSource) override; bool dragMoveAccept(const QUrl& url) override; diff --git a/src/library/autodj/dlgautodj.cpp b/src/library/autodj/dlgautodj.cpp index 4c8e3ced149..43c0dbb94fe 100644 --- a/src/library/autodj/dlgautodj.cpp +++ b/src/library/autodj/dlgautodj.cpp @@ -390,6 +390,10 @@ void DlgAutoDJ::setFocus() { m_pTrackTableView->setFocus(); } +void DlgAutoDJ::pasteFromSidebar() { + m_pTrackTableView->pasteFromSidebar(); +} + void DlgAutoDJ::keyPressEvent(QKeyEvent* pEvent) { // If we receive key events either the mode selector or the spinbox are focused. // Return, Enter and Escape move focus back to the previously focused diff --git a/src/library/autodj/dlgautodj.h b/src/library/autodj/dlgautodj.h index a9cc19a8f75..a2d976dc774 100644 --- a/src/library/autodj/dlgautodj.h +++ b/src/library/autodj/dlgautodj.h @@ -28,6 +28,7 @@ class DlgAutoDJ : public QWidget, public Ui::DlgAutoDJ, public LibraryView { void onShow() override; bool hasFocus() const override; void setFocus() override; + void pasteFromSidebar() override; void onSearch(const QString& text) override; void activateSelectedTrack() override; void loadSelectedTrackToGroup(const QString& group, bool play) override; diff --git a/src/library/basetracktablemodel.cpp b/src/library/basetracktablemodel.cpp index 274c206b4f1..b90bab1aea3 100644 --- a/src/library/basetracktablemodel.cpp +++ b/src/library/basetracktablemodel.cpp @@ -3,17 +3,17 @@ #include #include -#include "library/bpmdelegate.h" -#include "library/colordelegate.h" #include "library/coverartcache.h" -#include "library/coverartdelegate.h" #include "library/dao/trackschema.h" -#include "library/locationdelegate.h" -#include "library/multilineeditdelegate.h" -#include "library/playcountdelegate.h" -#include "library/previewbuttondelegate.h" -#include "library/stardelegate.h" #include "library/starrating.h" +#include "library/tabledelegates/bpmdelegate.h" +#include "library/tabledelegates/colordelegate.h" +#include "library/tabledelegates/coverartdelegate.h" +#include "library/tabledelegates/locationdelegate.h" +#include "library/tabledelegates/multilineeditdelegate.h" +#include "library/tabledelegates/playcountdelegate.h" +#include "library/tabledelegates/previewbuttondelegate.h" +#include "library/tabledelegates/stardelegate.h" #include "library/trackcollection.h" #include "library/trackcollectionmanager.h" #include "mixer/playerinfo.h" @@ -21,6 +21,7 @@ #include "moc_basetracktablemodel.cpp" #include "track/track.h" #include "util/assert.h" +#include "util/clipboard.h" #include "util/datetime.h" #include "util/db/sqlite.h" #include "util/logger.h" @@ -380,6 +381,59 @@ int BaseTrackTableModel::columnCount(const QModelIndex& parent) const { return countValidColumnHeaders(); } +void BaseTrackTableModel::cutTracks(const QModelIndexList& indices) { + copyTracks(indices); + removeTracks(indices); +} + +void BaseTrackTableModel::copyTracks(const QModelIndexList& indices) const { + Clipboard::start(); + for (const QModelIndex& index : indices) { + if (index.isValid()) { + Clipboard::add(QUrl::fromLocalFile(getTrackLocation(index))); + } + } + Clipboard::finish(); +} + +QList BaseTrackTableModel::pasteTracks(const QModelIndex& insertionIndex) { + // Don't paste into locked playlists and crates or into into History + if (isLocked() || !hasCapabilities(TrackModel::Capability::ReceiveDrops)) { + return QList{}; + } + + int insertionPos = 0; + const QList urls = Clipboard::urls(); + const QList trackIds = m_pTrackCollectionManager->resolveTrackIdsFromUrls(urls, true); + if (!trackIds.isEmpty()) { + addTracksWithTrackIds(insertionIndex, trackIds, &insertionPos); + } + + QList rows; + for (const auto& trackId : trackIds) { + const auto trackRows = getTrackRows(trackId); + for (int trackRow : trackRows) { + if (insertionPos == 0) { + rows.append(trackRow); + } else { + int pos = + index( + trackRow, + fieldIndex(ColumnCache:: + COLUMN_PLAYLISTTRACKSTABLE_POSITION)) + .data() + .toInt(); + // trackRows includes all instances in the table of the pasted + // tracks. We only want to select the ones we just inserted + if (pos >= insertionPos && pos < insertionPos + trackIds.size()) { + rows.append(trackRow); + } + } + } + } + return rows; +} + bool BaseTrackTableModel::isColumnHiddenByDefault( int column) { return column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ALBUMARTIST) || diff --git a/src/library/basetracktablemodel.h b/src/library/basetracktablemodel.h index b5d82568d1e..e46cc2569c8 100644 --- a/src/library/basetracktablemodel.h +++ b/src/library/basetracktablemodel.h @@ -81,6 +81,10 @@ class BaseTrackTableModel : public QAbstractTableModel, public TrackModel { return m_columnCache.fieldIndex(fieldName); } + void cutTracks(const QModelIndexList& indices) override; + void copyTracks(const QModelIndexList& indices) const override; + QList pasteTracks(const QModelIndex& index) override; + bool isColumnHiddenByDefault( int column) override; diff --git a/src/library/browse/browsetablemodel.cpp b/src/library/browse/browsetablemodel.cpp index 561b2a3ecac..f6245926421 100644 --- a/src/library/browse/browsetablemodel.cpp +++ b/src/library/browse/browsetablemodel.cpp @@ -6,7 +6,7 @@ #include "library/browse/browsetablemodel.h" #include "library/browse/browsethread.h" -#include "library/previewbuttondelegate.h" +#include "library/tabledelegates/previewbuttondelegate.h" #include "library/trackcollection.h" #include "library/trackcollectionmanager.h" #include "mixer/playerinfo.h" @@ -14,6 +14,7 @@ #include "moc_browsetablemodel.cpp" #include "recording/recordingmanager.h" #include "track/track.h" +#include "util/clipboard.h" #include "widget/wlibrarytableview.h" namespace { @@ -314,6 +315,20 @@ bool BrowseTableModel::isColumnHiddenByDefault(int column) { void BrowseTableModel::moveTrack(const QModelIndex&, const QModelIndex&) { } +void BrowseTableModel::copyTracks(const QModelIndexList& indices) const { + Clipboard::start(); + for (const QModelIndex& index : indices) { + if (index.isValid()) { + Clipboard::add(QUrl::fromLocalFile(getTrackLocation(index))); + } + } + Clipboard::finish(); + + // TODO Investigate if we can also implement cut and paste (via QFile + // operations) so mixxx could manage files in the filesystem, rather than + // having to go switch between mixxx and the system file browser. +} + void BrowseTableModel::removeTracks(const QModelIndexList&) { } diff --git a/src/library/browse/browsetablemodel.h b/src/library/browse/browsetablemodel.h index fb4b8dbc7f8..e770096eee5 100644 --- a/src/library/browse/browsetablemodel.h +++ b/src/library/browse/browsetablemodel.h @@ -71,6 +71,7 @@ class BrowseTableModel final : public QStandardItemModel, public virtual TrackMo const QString currentSearch() const override; bool isColumnInternal(int) override; void moveTrack(const QModelIndex&, const QModelIndex&) override; + void copyTracks(const QModelIndexList& indices) const override; bool isLocked() override { return false; } bool isColumnHiddenByDefault(int column) override; const QList& searchColumns() const override; diff --git a/src/library/dao/playlistdao.cpp b/src/library/dao/playlistdao.cpp index 192c0002ae4..ede2f4eb569 100644 --- a/src/library/dao/playlistdao.cpp +++ b/src/library/dao/playlistdao.cpp @@ -796,6 +796,16 @@ int PlaylistDAO::insertTracksIntoPlaylist(const QList& trackIds, return tracksAdded; } +void PlaylistDAO::clearAutoDJQueue() { + const int iAutoDJPlaylistId = getPlaylistIdFromName(AUTODJ_TABLE); + // If the first track is already loaded to the player, + // alter the playlist only below the first track + const int position = + (m_pAutoDJProcessor && m_pAutoDJProcessor->nextTrackLoaded()) ? 2 : 1; + + removeTracksFromPlaylist(iAutoDJPlaylistId, position); +} + void PlaylistDAO::addPlaylistToAutoDJQueue(const int playlistId, AutoDJSendLoc loc) { //qDebug() << "Adding tracks from playlist " << playlistId << " to the Auto-DJ Queue"; diff --git a/src/library/dao/playlistdao.h b/src/library/dao/playlistdao.h index 58660626c27..3eb6def5ec2 100644 --- a/src/library/dao/playlistdao.h +++ b/src/library/dao/playlistdao.h @@ -107,6 +107,8 @@ class PlaylistDAO : public QObject, public virtual DAO { bool insertTrackIntoPlaylist(TrackId trackId, int playlistId, int position); // Inserts a list of tracks into playlist int insertTracksIntoPlaylist(const QList& trackIds, const int playlistId, int position); + // Remove all tracks from the Auto-DJ Queue + void clearAutoDJQueue(); // Add a playlist to the Auto-DJ Queue void addPlaylistToAutoDJQueue(const int playlistId, AutoDJSendLoc loc); // Add a list of tracks to the Auto-DJ Queue diff --git a/src/library/library.cpp b/src/library/library.cpp index 77bd315e1b4..d1f73fee806 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -6,7 +6,7 @@ #include "control/controlobject.h" #include "controllers/keyboard/keyboardeventfilter.h" -#include "library/analysisfeature.h" +#include "library/analysis/analysisfeature.h" #include "library/autodj/autodjfeature.h" #include "library/banshee/bansheefeature.h" #include "library/browse/browsefeature.h" @@ -389,6 +389,10 @@ void Library::bindLibraryWidget( &Library::showTrackModel, pTrackTableView, &WTrackTableView::loadTrackModel); + connect(this, + &Library::pasteFromSidebar, + m_pLibraryWidget, + &WLibrary::pasteFromSidebar); connect(pTrackTableView, &WTrackTableView::loadTrack, this, @@ -399,6 +403,10 @@ void Library::bindLibraryWidget( &Library::slotLoadTrackToPlayer); m_pLibraryWidget->registerView(m_sTrackViewName, pTrackTableView); + connect(m_pLibraryWidget, + &WLibrary::setLibraryFocus, + m_pLibraryControl, + &LibraryControl::setLibraryFocus); connect(this, &Library::switchToView, m_pLibraryWidget, @@ -461,6 +469,10 @@ void Library::addFeature(LibraryFeature* feature) { } m_features.push_back(feature); m_pSidebarModel->addLibraryFeature(feature); + connect(feature, + &LibraryFeature::pasteFromSidebar, + this, + &Library::pasteFromSidebar); connect(feature, &LibraryFeature::showTrackModel, this, @@ -517,7 +529,7 @@ void Library::onPlayerManagerTrackAnalyzerIdle() { } void Library::slotShowTrackModel(QAbstractItemModel* model) { - //qDebug() << "Library::slotShowTrackModel" << model; + // qDebug() << "Library::slotShowTrackModel" << model; TrackModel* trackModel = dynamic_cast(model); VERIFY_OR_DEBUG_ASSERT(trackModel) { return; @@ -528,7 +540,7 @@ void Library::slotShowTrackModel(QAbstractItemModel* model) { } void Library::slotSwitchToView(const QString& view) { - //qDebug() << "Library::slotSwitchToView" << view; + // qDebug() << "Library::slotSwitchToView" << view; emit switchToView(view); } diff --git a/src/library/library.h b/src/library/library.h index fe9bf0056c1..3cd08cc23e5 100644 --- a/src/library/library.h +++ b/src/library/library.h @@ -124,6 +124,7 @@ class Library: public QObject { void restoreSearch(const QString&); void search(const QString& text); void disableSearch(); + void pasteFromSidebar(); // emit this signal to enable/disable the cover art widget void enableCoverArtDisplay(bool); void selectTrack(const TrackId&); diff --git a/src/library/libraryfeature.h b/src/library/libraryfeature.h index 5ba3ffa7da2..61972192dcd 100644 --- a/src/library/libraryfeature.h +++ b/src/library/libraryfeature.h @@ -68,6 +68,13 @@ class LibraryFeature : public QObject { return false; } + virtual void clear() { + } + virtual void paste() { + } + virtual void pasteChild(const QModelIndex& index) { + Q_UNUSED(index); + } // Reimplement this to register custom views with the library widget. virtual void bindLibraryWidget(WLibrary* /* libraryWidget */, KeyboardEventFilter* /* keyboard */) {} @@ -135,6 +142,7 @@ class LibraryFeature : public QObject { void restoreModelState(); void restoreSearch(const QString&); void disableSearch(); + void pasteFromSidebar(); // emit this signal before you parse a large music collection, e.g., iTunes, Traktor. // The second arg indicates if the feature should be "selected" when loading starts void featureIsLoading(LibraryFeature*, bool selectFeature); diff --git a/src/library/libraryview.h b/src/library/libraryview.h index eec640e636a..ac6061b5ac8 100644 --- a/src/library/libraryview.h +++ b/src/library/libraryview.h @@ -22,6 +22,9 @@ class LibraryView { /// Reimplement if LibraryView should be able to search virtual void onSearch(const QString& text) {Q_UNUSED(text);} + virtual void pasteFromSidebar() { + } + /// If applicable, requests that the LibraryView load the selected /// track. Does nothing otherwise. virtual void activateSelectedTrack() { diff --git a/src/library/dlghidden.cpp b/src/library/missing_hidden/dlghidden.cpp similarity index 97% rename from src/library/dlghidden.cpp rename to src/library/missing_hidden/dlghidden.cpp index a43bd67ce10..0943a182eb3 100644 --- a/src/library/dlghidden.cpp +++ b/src/library/missing_hidden/dlghidden.cpp @@ -1,10 +1,10 @@ -#include "library/dlghidden.h" +#include "library/missing_hidden/dlghidden.h" #include #include "controllers/keyboard/keyboardeventfilter.h" -#include "library/hiddentablemodel.h" #include "library/library.h" +#include "library/missing_hidden/hiddentablemodel.h" #include "moc_dlghidden.cpp" #include "util/assert.h" #include "widget/wlibrary.h" diff --git a/src/library/dlghidden.h b/src/library/missing_hidden/dlghidden.h similarity index 100% rename from src/library/dlghidden.h rename to src/library/missing_hidden/dlghidden.h diff --git a/src/library/dlghidden.ui b/src/library/missing_hidden/dlghidden.ui similarity index 100% rename from src/library/dlghidden.ui rename to src/library/missing_hidden/dlghidden.ui diff --git a/src/library/dlgmissing.cpp b/src/library/missing_hidden/dlgmissing.cpp similarity index 97% rename from src/library/dlgmissing.cpp rename to src/library/missing_hidden/dlgmissing.cpp index 24b4b3a42ee..736fb4842cd 100644 --- a/src/library/dlgmissing.cpp +++ b/src/library/missing_hidden/dlgmissing.cpp @@ -1,10 +1,10 @@ -#include "library/dlgmissing.h" +#include "library/missing_hidden/dlgmissing.h" #include #include "controllers/keyboard/keyboardeventfilter.h" #include "library/library.h" -#include "library/missingtablemodel.h" +#include "library/missing_hidden/missingtablemodel.h" #include "moc_dlgmissing.cpp" #include "util/assert.h" #include "widget/wlibrary.h" diff --git a/src/library/dlgmissing.h b/src/library/missing_hidden/dlgmissing.h similarity index 100% rename from src/library/dlgmissing.h rename to src/library/missing_hidden/dlgmissing.h diff --git a/src/library/dlgmissing.ui b/src/library/missing_hidden/dlgmissing.ui similarity index 100% rename from src/library/dlgmissing.ui rename to src/library/missing_hidden/dlgmissing.ui diff --git a/src/library/hiddentablemodel.cpp b/src/library/missing_hidden/hiddentablemodel.cpp similarity index 98% rename from src/library/hiddentablemodel.cpp rename to src/library/missing_hidden/hiddentablemodel.cpp index f54fc17ca0e..ff87e84387d 100644 --- a/src/library/hiddentablemodel.cpp +++ b/src/library/missing_hidden/hiddentablemodel.cpp @@ -1,4 +1,4 @@ -#include "library/hiddentablemodel.h" +#include "library/missing_hidden/hiddentablemodel.h" #include "library/dao/trackschema.h" #include "library/trackcollection.h" diff --git a/src/library/hiddentablemodel.h b/src/library/missing_hidden/hiddentablemodel.h similarity index 100% rename from src/library/hiddentablemodel.h rename to src/library/missing_hidden/hiddentablemodel.h diff --git a/src/library/missingtablemodel.cpp b/src/library/missing_hidden/missingtablemodel.cpp similarity index 98% rename from src/library/missingtablemodel.cpp rename to src/library/missing_hidden/missingtablemodel.cpp index 5906196c3b1..1b6c077e509 100644 --- a/src/library/missingtablemodel.cpp +++ b/src/library/missing_hidden/missingtablemodel.cpp @@ -1,4 +1,4 @@ -#include "library/missingtablemodel.h" +#include "library/missing_hidden/missingtablemodel.h" #include "library/dao/trackschema.h" #include "library/trackcollection.h" diff --git a/src/library/missingtablemodel.h b/src/library/missing_hidden/missingtablemodel.h similarity index 100% rename from src/library/missingtablemodel.h rename to src/library/missing_hidden/missingtablemodel.h diff --git a/src/library/mixxxlibraryfeature.cpp b/src/library/mixxxlibraryfeature.cpp index 84400b7a939..4ea52b1e6a1 100644 --- a/src/library/mixxxlibraryfeature.cpp +++ b/src/library/mixxxlibraryfeature.cpp @@ -7,10 +7,10 @@ #include "library/basetrackcache.h" #include "library/dao/trackschema.h" -#include "library/dlghidden.h" -#include "library/dlgmissing.h" #include "library/library.h" #include "library/librarytablemodel.h" +#include "library/missing_hidden/dlghidden.h" +#include "library/missing_hidden/dlgmissing.h" #include "library/parser.h" #include "library/queryutil.h" #include "library/trackcollection.h" diff --git a/src/library/playlisttablemodel.cpp b/src/library/playlisttablemodel.cpp index 3c563657d8d..ce7c937b717 100644 --- a/src/library/playlisttablemodel.cpp +++ b/src/library/playlisttablemodel.cpp @@ -189,30 +189,35 @@ void PlaylistTableModel::selectPlaylist(int playlistId) { setSort(defaultSortColumn(), defaultSortOrder()); } -int PlaylistTableModel::addTracks(const QModelIndex& index, - const QList& locations) { - if (locations.isEmpty()) { +int PlaylistTableModel::addTracksWithTrackIds(const QModelIndex& insertionIndex, + const QList& trackIds, + int* pOutInsertionPos) { + if (trackIds.isEmpty()) { return 0; } - QList trackIds = m_pTrackCollectionManager->resolveTrackIdsFromLocations( - locations); - const int positionColumn = fieldIndex(ColumnCache::COLUMN_PLAYLISTTRACKSTABLE_POSITION); - int position = index.sibling(index.row(), positionColumn).data().toInt(); + int position = index(insertionIndex.row(), positionColumn).data().toInt(); // Handle weird cases like a drag and drop to an invalid index if (position <= 0) { position = rowCount() + 1; } + if (pOutInsertionPos) { + *pOutInsertionPos = position; + } + int tracksAdded = m_pTrackCollectionManager->internalCollection()->getPlaylistDAO().insertTracksIntoPlaylist( trackIds, m_iPlaylistId, position); - if (locations.size() - tracksAdded > 0) { + if (trackIds.size() - tracksAdded > 0) { + QString playlistName = m_pTrackCollectionManager->internalCollection() + ->getPlaylistDAO() + .getPlaylistName(m_iPlaylistId); qDebug() << "PlaylistTableModel::addTracks could not add" - << locations.size() - tracksAdded - << "to playlist" << m_iPlaylistId; + << trackIds.size() - tracksAdded + << "to playlist id" << m_iPlaylistId << "name" << playlistName; } return tracksAdded; } diff --git a/src/library/playlisttablemodel.h b/src/library/playlisttablemodel.h index 0443bd53577..c663f88bd4c 100644 --- a/src/library/playlisttablemodel.h +++ b/src/library/playlisttablemodel.h @@ -25,7 +25,9 @@ class PlaylistTableModel final : public TrackSetTableModel { /// This function should only be used by AUTODJ void removeTracks(const QModelIndexList& indices) final; /// Returns the number of successful additions. - int addTracks(const QModelIndex& index, const QList& locations) final; + int addTracksWithTrackIds(const QModelIndex& index, + const QList& trackIds, + int* pOutInsertionPos) final; bool isLocked() final; /// Get the total duration of all tracks referenced by the given model indices diff --git a/src/library/proxytrackmodel.cpp b/src/library/proxytrackmodel.cpp index 73c374e2838..1d0398c6ffe 100644 --- a/src/library/proxytrackmodel.cpp +++ b/src/library/proxytrackmodel.cpp @@ -113,6 +113,17 @@ void ProxyTrackModel::removeTracks(const QModelIndexList& indices) { } } +void ProxyTrackModel::copyTracks(const QModelIndexList& indices) const { + QModelIndexList translatedList; + foreach (QModelIndex index, indices) { + QModelIndex indexSource = mapToSource(index); + translatedList.append(indexSource); + } + if (m_pTrackModel) { + m_pTrackModel->copyTracks(translatedList); + } +} + void ProxyTrackModel::moveTrack(const QModelIndex& sourceIndex, const QModelIndex& destIndex) { QModelIndex sourceIndexSource = mapToSource(sourceIndex); diff --git a/src/library/proxytrackmodel.h b/src/library/proxytrackmodel.h index d56ef77eeb5..561449a31a8 100644 --- a/src/library/proxytrackmodel.h +++ b/src/library/proxytrackmodel.h @@ -36,6 +36,7 @@ class ProxyTrackModel : public QSortFilterProxyModel, public TrackModel { bool isColumnInternal(int column) final; bool isColumnHiddenByDefault(int column) final; void removeTracks(const QModelIndexList& indices) final; + void copyTracks(const QModelIndexList& indices) const final; void moveTrack(const QModelIndex& sourceIndex, const QModelIndex& destIndex) final; QAbstractItemDelegate* delegateForColumn(const int i, QObject* pParent) final; QString getModelSetting(const QString& name) final; diff --git a/src/library/sidebarmodel.cpp b/src/library/sidebarmodel.cpp index 1dd2786fbc4..49a13dd9bce 100644 --- a/src/library/sidebarmodel.cpp +++ b/src/library/sidebarmodel.cpp @@ -148,6 +148,24 @@ QModelIndex SidebarModel::getFeatureRootIndex(LibraryFeature* pFeature) { return ind; } +void SidebarModel::clear(const QModelIndex& index) { + if (index.internalPointer() == this) { + m_sFeatures[index.row()]->clear(); + } +} + +void SidebarModel::paste(const QModelIndex& index) { + if (index.internalPointer() == this) { + m_sFeatures[index.row()]->paste(); + } else { + TreeItem* pTreeItem = (TreeItem*)index.internalPointer(); + if (pTreeItem) { + LibraryFeature* feature = pTreeItem->feature(); + feature->pasteChild(index); + } + } +} + QModelIndex SidebarModel::parent(const QModelIndex& index) const { //qDebug() << "SidebarModel::parent index=" << index.getData(); if (index.isValid()) { @@ -475,7 +493,7 @@ QModelIndex SidebarModel::translateIndex( } void SidebarModel::slotDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { - //qDebug() << "slotDataChanged topLeft:" << topLeft << "bottomRight:" << bottomRight; + // qDebug() << "slotDataChanged topLeft:" << topLeft << "bottomRight:" << bottomRight; QModelIndex topLeftTranslated = translateSourceIndex(topLeft); QModelIndex bottomRightTranslated = translateSourceIndex(bottomRight); emit dataChanged(topLeftTranslated, bottomRightTranslated); @@ -499,8 +517,8 @@ void SidebarModel::slotRowsInserted(const QModelIndex& parent, int start, int en Q_UNUSED(parent); Q_UNUSED(start); Q_UNUSED(end); - //qDebug() << "slotRowsInserted" << parent << start << end; - //QModelIndex newParent = translateSourceIndex(parent); + // qDebug() << "slotRowsInserted" << parent << start << end; + // QModelIndex newParent = translateSourceIndex(parent); endInsertRows(); } diff --git a/src/library/sidebarmodel.h b/src/library/sidebarmodel.h index c0d7a6ca063..c987dff72c7 100644 --- a/src/library/sidebarmodel.h +++ b/src/library/sidebarmodel.h @@ -47,6 +47,8 @@ class SidebarModel : public QAbstractItemModel { } QModelIndex getFeatureRootIndex(LibraryFeature* pFeature); + void clear(const QModelIndex& index); + void paste(const QModelIndex& index); public slots: void pressed(const QModelIndex& index); void clicked(const QModelIndex& index); diff --git a/src/library/bpmdelegate.cpp b/src/library/tabledelegates/bpmdelegate.cpp similarity index 99% rename from src/library/bpmdelegate.cpp rename to src/library/tabledelegates/bpmdelegate.cpp index 1c05f578218..9b54cde6027 100644 --- a/src/library/bpmdelegate.cpp +++ b/src/library/tabledelegates/bpmdelegate.cpp @@ -1,4 +1,4 @@ -#include "library/bpmdelegate.h" +#include "library/tabledelegates/bpmdelegate.h" #include #include diff --git a/src/library/bpmdelegate.h b/src/library/tabledelegates/bpmdelegate.h similarity index 88% rename from src/library/bpmdelegate.h rename to src/library/tabledelegates/bpmdelegate.h index f0251199255..38a398898dc 100644 --- a/src/library/bpmdelegate.h +++ b/src/library/tabledelegates/bpmdelegate.h @@ -1,6 +1,6 @@ #pragma once -#include "library/tableitemdelegate.h" +#include "library/tabledelegates/tableitemdelegate.h" class QCheckBox; diff --git a/src/library/colordelegate.cpp b/src/library/tabledelegates/colordelegate.cpp similarity index 94% rename from src/library/colordelegate.cpp rename to src/library/tabledelegates/colordelegate.cpp index 4808a0ee9ca..76400bd6ee8 100644 --- a/src/library/colordelegate.cpp +++ b/src/library/tabledelegates/colordelegate.cpp @@ -1,4 +1,4 @@ -#include "library/colordelegate.h" +#include "library/tabledelegates/colordelegate.h" #include #include diff --git a/src/library/colordelegate.h b/src/library/tabledelegates/colordelegate.h similarity index 86% rename from src/library/colordelegate.h rename to src/library/tabledelegates/colordelegate.h index e0b0b9889e1..7309b560a0e 100644 --- a/src/library/colordelegate.h +++ b/src/library/tabledelegates/colordelegate.h @@ -1,6 +1,6 @@ #pragma once -#include "library/tableitemdelegate.h" +#include "library/tabledelegates/tableitemdelegate.h" class QModelIndex; class QPainter; diff --git a/src/library/coverartdelegate.cpp b/src/library/tabledelegates/coverartdelegate.cpp similarity index 99% rename from src/library/coverartdelegate.cpp rename to src/library/tabledelegates/coverartdelegate.cpp index 7dbeeff33b5..781dbc48292 100644 --- a/src/library/coverartdelegate.cpp +++ b/src/library/tabledelegates/coverartdelegate.cpp @@ -1,4 +1,4 @@ -#include "library/coverartdelegate.h" +#include "library/tabledelegates/coverartdelegate.h" #include #include diff --git a/src/library/coverartdelegate.h b/src/library/tabledelegates/coverartdelegate.h similarity index 97% rename from src/library/coverartdelegate.h rename to src/library/tabledelegates/coverartdelegate.h index 84c69c5604a..f2e5b338915 100644 --- a/src/library/coverartdelegate.h +++ b/src/library/tabledelegates/coverartdelegate.h @@ -3,7 +3,7 @@ #include #include "library/coverart.h" -#include "library/tableitemdelegate.h" +#include "library/tabledelegates/tableitemdelegate.h" #include "track/track_decl.h" #include "util/cache.h" diff --git a/src/library/locationdelegate.cpp b/src/library/tabledelegates/locationdelegate.cpp similarity index 94% rename from src/library/locationdelegate.cpp rename to src/library/tabledelegates/locationdelegate.cpp index 43438d58e6e..9fa79339c8e 100644 --- a/src/library/locationdelegate.cpp +++ b/src/library/tabledelegates/locationdelegate.cpp @@ -1,4 +1,4 @@ -#include "library/locationdelegate.h" +#include "library/tabledelegates/locationdelegate.h" #include diff --git a/src/library/locationdelegate.h b/src/library/tabledelegates/locationdelegate.h similarity index 86% rename from src/library/locationdelegate.h rename to src/library/tabledelegates/locationdelegate.h index 9fb698dab21..43fd03cc79d 100644 --- a/src/library/locationdelegate.h +++ b/src/library/tabledelegates/locationdelegate.h @@ -1,7 +1,6 @@ #pragma once -#include "library/tableitemdelegate.h" - +#include "library/tabledelegates/tableitemdelegate.h" class LocationDelegate : public TableItemDelegate { Q_OBJECT diff --git a/src/library/multilineeditdelegate.cpp b/src/library/tabledelegates/multilineeditdelegate.cpp similarity index 99% rename from src/library/multilineeditdelegate.cpp rename to src/library/tabledelegates/multilineeditdelegate.cpp index e7c0fa7ae10..3dd6904fc8d 100644 --- a/src/library/multilineeditdelegate.cpp +++ b/src/library/tabledelegates/multilineeditdelegate.cpp @@ -1,4 +1,4 @@ -#include "library/multilineeditdelegate.h" +#include "library/tabledelegates/multilineeditdelegate.h" #include #include diff --git a/src/library/multilineeditdelegate.h b/src/library/tabledelegates/multilineeditdelegate.h similarity index 96% rename from src/library/multilineeditdelegate.h rename to src/library/tabledelegates/multilineeditdelegate.h index 692598cc422..d46bd048c26 100644 --- a/src/library/multilineeditdelegate.h +++ b/src/library/tabledelegates/multilineeditdelegate.h @@ -2,7 +2,7 @@ #include -#include "library/tableitemdelegate.h" +#include "library/tabledelegates/tableitemdelegate.h" /// A QPlainTextEdit to show all content lines in a scrollable view. /// * finish editing with Return key, like QLineEdit used for other text columns diff --git a/src/library/playcountdelegate.cpp b/src/library/tabledelegates/playcountdelegate.cpp similarity index 95% rename from src/library/playcountdelegate.cpp rename to src/library/tabledelegates/playcountdelegate.cpp index 4f25aaf8833..53bac452dc1 100644 --- a/src/library/playcountdelegate.cpp +++ b/src/library/tabledelegates/playcountdelegate.cpp @@ -1,4 +1,4 @@ -#include "library/playcountdelegate.h" +#include "library/tabledelegates/playcountdelegate.h" #include #include diff --git a/src/library/playcountdelegate.h b/src/library/tabledelegates/playcountdelegate.h similarity index 87% rename from src/library/playcountdelegate.h rename to src/library/tabledelegates/playcountdelegate.h index 87f3705ed57..ce791f733c2 100644 --- a/src/library/playcountdelegate.h +++ b/src/library/tabledelegates/playcountdelegate.h @@ -1,6 +1,6 @@ #pragma once -#include "library/tableitemdelegate.h" +#include "library/tabledelegates/tableitemdelegate.h" class QCheckBox; diff --git a/src/library/previewbuttondelegate.cpp b/src/library/tabledelegates/previewbuttondelegate.cpp similarity index 99% rename from src/library/previewbuttondelegate.cpp rename to src/library/tabledelegates/previewbuttondelegate.cpp index ca565cb185b..110b84d69fa 100644 --- a/src/library/previewbuttondelegate.cpp +++ b/src/library/tabledelegates/previewbuttondelegate.cpp @@ -1,4 +1,4 @@ -#include "library/previewbuttondelegate.h" +#include "library/tabledelegates/previewbuttondelegate.h" #include #include diff --git a/src/library/previewbuttondelegate.h b/src/library/tabledelegates/previewbuttondelegate.h similarity index 97% rename from src/library/previewbuttondelegate.h rename to src/library/tabledelegates/previewbuttondelegate.h index fdd1f0f06d2..6bb37e514f9 100644 --- a/src/library/previewbuttondelegate.h +++ b/src/library/tabledelegates/previewbuttondelegate.h @@ -2,7 +2,7 @@ #include -#include "library/tableitemdelegate.h" +#include "library/tabledelegates/tableitemdelegate.h" #include "track/track_decl.h" #include "util/parented_ptr.h" diff --git a/src/library/stardelegate.cpp b/src/library/tabledelegates/stardelegate.cpp similarity index 95% rename from src/library/stardelegate.cpp rename to src/library/tabledelegates/stardelegate.cpp index 2b1a2fb0c22..62743016af8 100644 --- a/src/library/stardelegate.cpp +++ b/src/library/tabledelegates/stardelegate.cpp @@ -1,10 +1,10 @@ -#include "library/stardelegate.h" +#include "library/tabledelegates/stardelegate.h" #include -#include "library/stareditor.h" #include "library/starrating.h" -#include "library/tableitemdelegate.h" +#include "library/tabledelegates/stareditor.h" +#include "library/tabledelegates/tableitemdelegate.h" #include "moc_stardelegate.cpp" StarDelegate::StarDelegate(QTableView* pTableView) diff --git a/src/library/stardelegate.h b/src/library/tabledelegates/stardelegate.h similarity index 95% rename from src/library/stardelegate.h rename to src/library/tabledelegates/stardelegate.h index 3ac6bd7fd3a..39d0f1457d9 100644 --- a/src/library/stardelegate.h +++ b/src/library/tabledelegates/stardelegate.h @@ -1,6 +1,6 @@ #pragma once -#include "library/tableitemdelegate.h" +#include "library/tabledelegates/tableitemdelegate.h" class StarDelegate : public TableItemDelegate { Q_OBJECT diff --git a/src/library/stareditor.cpp b/src/library/tabledelegates/stareditor.cpp similarity index 98% rename from src/library/stareditor.cpp rename to src/library/tabledelegates/stareditor.cpp index 42abedd7609..a4e1ba78a6c 100644 --- a/src/library/stareditor.cpp +++ b/src/library/tabledelegates/stareditor.cpp @@ -1,4 +1,4 @@ -#include "library/stareditor.h" +#include "library/tabledelegates/stareditor.h" #include #include @@ -6,7 +6,7 @@ #include #include "library/starrating.h" -#include "library/tableitemdelegate.h" +#include "library/tabledelegates/tableitemdelegate.h" #include "moc_stareditor.cpp" // We enable mouse tracking on the widget so we can follow the cursor even diff --git a/src/library/stareditor.h b/src/library/tabledelegates/stareditor.h similarity index 100% rename from src/library/stareditor.h rename to src/library/tabledelegates/stareditor.h diff --git a/src/library/tableitemdelegate.cpp b/src/library/tabledelegates/tableitemdelegate.cpp similarity index 98% rename from src/library/tableitemdelegate.cpp rename to src/library/tabledelegates/tableitemdelegate.cpp index f044e4a664c..229b08caa81 100644 --- a/src/library/tableitemdelegate.cpp +++ b/src/library/tabledelegates/tableitemdelegate.cpp @@ -1,4 +1,4 @@ -#include "library/tableitemdelegate.h" +#include "library/tabledelegates/tableitemdelegate.h" #include diff --git a/src/library/tableitemdelegate.h b/src/library/tabledelegates/tableitemdelegate.h similarity index 100% rename from src/library/tableitemdelegate.h rename to src/library/tabledelegates/tableitemdelegate.h diff --git a/src/library/trackmodel.h b/src/library/trackmodel.h index c811114709a..53bf53ddbc9 100644 --- a/src/library/trackmodel.h +++ b/src/library/trackmodel.h @@ -140,6 +140,16 @@ class TrackModel { virtual void removeTracks(const QModelIndexList& indices) { Q_UNUSED(indices); } + virtual void cutTracks(const QModelIndexList& indices) { + Q_UNUSED(indices); + } + virtual void copyTracks(const QModelIndexList& indices) const { + Q_UNUSED(indices); + } + virtual QList pasteTracks(const QModelIndex& index) { + Q_UNUSED(index); + return QList(); + } virtual void hideTracks(const QModelIndexList& indices) { Q_UNUSED(indices); } @@ -154,6 +164,14 @@ class TrackModel { Q_UNUSED(locations); return 0; } + virtual int addTracksWithTrackIds(const QModelIndex& index, + const QList& tracks, + int* pOutInsertionPos) { + Q_UNUSED(index); + Q_UNUSED(tracks); + Q_UNUSED(pOutInsertionPos); + return 0; + } virtual void moveTrack(const QModelIndex& sourceIndex, const QModelIndex& destIndex) { Q_UNUSED(sourceIndex); diff --git a/src/library/trackset/baseplaylistfeature.cpp b/src/library/trackset/baseplaylistfeature.cpp index 0b18a37b1ae..d26de9c0642 100644 --- a/src/library/trackset/baseplaylistfeature.cpp +++ b/src/library/trackset/baseplaylistfeature.cpp @@ -165,7 +165,7 @@ void BasePlaylistFeature::connectPlaylistDAO() { &BasePlaylistFeature::slotPlaylistTableRenamed); } -int BasePlaylistFeature::playlistIdFromIndex(const QModelIndex& index) { +int BasePlaylistFeature::playlistIdFromIndex(const QModelIndex& index) const { TreeItem* item = static_cast(index.internalPointer()); if (item == nullptr) { return kInvalidPlaylistId; diff --git a/src/library/trackset/baseplaylistfeature.h b/src/library/trackset/baseplaylistfeature.h index fbcbf16586a..273c052984e 100644 --- a/src/library/trackset/baseplaylistfeature.h +++ b/src/library/trackset/baseplaylistfeature.h @@ -86,7 +86,7 @@ class BasePlaylistFeature : public BaseTrackSetFeature { virtual void decorateChild(TreeItem* pChild, int playlistId) = 0; virtual void addToAutoDJ(PlaylistDAO::AutoDJSendLoc loc); - int playlistIdFromIndex(const QModelIndex& index); + int playlistIdFromIndex(const QModelIndex& index) const; // Get the QModelIndex of a playlist based on its id. Returns QModelIndex() // on failure. QModelIndex indexFromPlaylistId(int playlistId); diff --git a/src/library/trackset/basetracksetfeature.cpp b/src/library/trackset/basetracksetfeature.cpp index a50b9b94019..51f18f1f436 100644 --- a/src/library/trackset/basetracksetfeature.cpp +++ b/src/library/trackset/basetracksetfeature.cpp @@ -13,6 +13,10 @@ BaseTrackSetFeature::BaseTrackSetFeature( m_pSidebarModel(make_parented(this)) { } +void BaseTrackSetFeature::pasteChild(const QModelIndex&) { + emit pasteFromSidebar(); +} + void BaseTrackSetFeature::activate() { emit switchToView(m_rootViewName); emit disableSearch(); diff --git a/src/library/trackset/basetracksetfeature.h b/src/library/trackset/basetracksetfeature.h index 6869d97ac46..c647f9d046f 100644 --- a/src/library/trackset/basetracksetfeature.h +++ b/src/library/trackset/basetracksetfeature.h @@ -14,6 +14,7 @@ class BaseTrackSetFeature : public LibraryFeature { const QString& rootViewName, const QString& iconName); + void pasteChild(const QModelIndex& index) override; signals: void analyzeTracks(const QList&); diff --git a/src/library/trackset/crate/cratetablemodel.cpp b/src/library/trackset/crate/cratetablemodel.cpp index 28388f4bfb1..d574f90eb35 100644 --- a/src/library/trackset/crate/cratetablemodel.cpp +++ b/src/library/trackset/crate/cratetablemodel.cpp @@ -150,17 +150,22 @@ TrackModel::Capabilities CrateTableModel::getCapabilities() const { return caps; } -int CrateTableModel::addTracks( - const QModelIndex& index, const QList& locations) { +int CrateTableModel::addTracksWithTrackIds( + const QModelIndex& index, const QList& trackIds, int* pOutInsertionPos) { Q_UNUSED(index); + + if (pOutInsertionPos != nullptr) { + // crate insertion is not done by position, and no duplicates will be added,. + // 0 indicates this to the caller. + *pOutInsertionPos = 0; + } + // If a track is dropped but it isn't in the library, then add it because // the user probably dropped a file from outside Mixxx into this crate. - QList trackIds = - m_pTrackCollectionManager->resolveTrackIdsFromLocations(locations); if (!m_pTrackCollectionManager->internalCollection()->addCrateTracks( m_selectedCrate, trackIds)) { qWarning() << "CrateTableModel::addTracks could not add" - << locations.size() << "tracks to crate" << m_selectedCrate; + << trackIds.size() << "tracks to crate" << m_selectedCrate; return 0; } diff --git a/src/library/trackset/crate/cratetablemodel.h b/src/library/trackset/crate/cratetablemodel.h index fed1f1d4dc3..6e977b4a1d8 100644 --- a/src/library/trackset/crate/cratetablemodel.h +++ b/src/library/trackset/crate/cratetablemodel.h @@ -19,7 +19,9 @@ class CrateTableModel final : public TrackSetTableModel { void removeTracks(const QModelIndexList& indices) final; /// Returns the number of unsuccessful additions. - int addTracks(const QModelIndex& index, const QList& locations) final; + int addTracksWithTrackIds(const QModelIndex& index, + const QList& tracks, + int* pOutInsertionPos) final; bool isLocked() final; Capabilities getCapabilities() const final; diff --git a/src/library/trackset/tracksettablemodel.cpp b/src/library/trackset/tracksettablemodel.cpp index f27bd25efae..1e53c73ca60 100644 --- a/src/library/trackset/tracksettablemodel.cpp +++ b/src/library/trackset/tracksettablemodel.cpp @@ -1,5 +1,6 @@ #include "library/trackset/tracksettablemodel.h" +#include "library/trackcollectionmanager.h" #include "mixer/playermanager.h" #include "moc_tracksettablemodel.cpp" @@ -26,3 +27,15 @@ bool TrackSetTableModel::isColumnInternal(int column) { column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COVERART_DIGEST) || column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COVERART_HASH); } + +int TrackSetTableModel::addTracks(const QModelIndex& index, + const QList& locations) { + if (locations.isEmpty()) { + return 0; + } + + QList trackIds = m_pTrackCollectionManager->resolveTrackIdsFromLocations( + locations); + + return addTracksWithTrackIds(index, trackIds, nullptr); +} diff --git a/src/library/trackset/tracksettablemodel.h b/src/library/trackset/tracksettablemodel.h index 649d33ef325..94202c9a853 100644 --- a/src/library/trackset/tracksettablemodel.h +++ b/src/library/trackset/tracksettablemodel.h @@ -11,4 +11,6 @@ class TrackSetTableModel : public BaseSqlTableModel { const char* settingsNamespace); bool isColumnInternal(int column) override; + + int addTracks(const QModelIndex& index, const QList& locations) final; }; diff --git a/src/library/traktor/traktorfeature.cpp b/src/library/traktor/traktorfeature.cpp index 0cb61d2dd6d..d6773cf5fc3 100644 --- a/src/library/traktor/traktorfeature.cpp +++ b/src/library/traktor/traktorfeature.cpp @@ -10,7 +10,7 @@ #include "library/library.h" #include "library/librarytablemodel.h" -#include "library/missingtablemodel.h" +#include "library/missing_hidden/missingtablemodel.h" #include "library/queryutil.h" #include "library/trackcollection.h" #include "library/trackcollectionmanager.h" diff --git a/src/mixer/playermanager.cpp b/src/mixer/playermanager.cpp index 552873426aa..a4f0f1ea15e 100644 --- a/src/mixer/playermanager.cpp +++ b/src/mixer/playermanager.cpp @@ -696,6 +696,16 @@ void PlayerManager::slotLoadTrackToPlayer(TrackPointer pTrack, const QString& gr // so clone another playing deck instead of loading the selected track clone = true; } + } else if (isPreviewDeckGroup(group) && play) { + // This extends/overrides the behaviour of [PreviewDeckN],LoadSelectedTrackAndPlay: + // if the track is already loaded, toggle play/pause. + if (pTrack == pPlayer->getLoadedTrack()) { + auto* pPlay = + ControlObject::getControl(ConfigKey(group, QStringLiteral("play"))); + double newPlay = pPlay->toBool() ? 0.0 : 1.0; + pPlay->set(newPlay); + return; + } } if (clone) { diff --git a/src/skin/legacy/legacyskinparser.cpp b/src/skin/legacy/legacyskinparser.cpp index eeb9d716cfd..10f53326ab5 100644 --- a/src/skin/legacy/legacyskinparser.cpp +++ b/src/skin/legacy/legacyskinparser.cpp @@ -1081,17 +1081,20 @@ QWidget* LegacySkinParser::parseTrackProperty(const QDomElement& node) { return nullptr; } + bool isMainDeck = PlayerManager::isDeckGroup(group); WTrackProperty* pTrackProperty = new WTrackProperty( m_pParent, m_pConfig, m_pLibrary, - group); + group, + isMainDeck); setupLabelWidget(node, pTrackProperty); // Ensure 'show_track_menu' control is created for each main deck and // valueChangeRequest hook is set up. - // Only the first WTrackProperty that is created connects the signals. - if (PlayerManager::isDeckGroup(group)) { + if (isMainDeck) { + // Only the first WTrackProperty that is created connects the signals, + // for later attempts this returns false. if (pPlayer->isTrackMenuControlAvailable()) { connect(pPlayer, &BaseTrackPlayer::trackMenuChangeRequest, @@ -1143,7 +1146,8 @@ QWidget* LegacySkinParser::parseTrackWidgetGroup(const QDomElement& node) { m_pParent, m_pConfig, m_pLibrary, - group); + group, + PlayerManager::isDeckGroup(group)); commonWidgetSetup(node, pGroup); pGroup->setup(node, *m_pContext); pGroup->Init(); diff --git a/src/skin/legacy/tooltips.cpp b/src/skin/legacy/tooltips.cpp index 388f8b49da8..b0ad99e08fa 100644 --- a/src/skin/legacy/tooltips.cpp +++ b/src/skin/legacy/tooltips.cpp @@ -324,28 +324,44 @@ void Tooltips::addStandardTooltips() { << tr("Manual: Sets how much to reduce the music volume, when talkover is activated regardless of volume of microphone inputs."); QString changeAmount = tr("Change the step-size in the Preferences -> Decks menu."); + QString pitchAffected = tr("If keylock is disabled, pitch is also affected."); + add("rate_perm_up_rate_perm_up_small") - << tr("Raise Pitch") - << QString("%1: %2").arg(leftClick, tr("Sets the pitch higher.")) - << QString("%1: %2").arg(rightClick, tr("Sets the pitch higher in small steps.")) + << tr("Speed Up") + << QString("%1: %2").arg(leftClick, + tr("Raises the track playback speed (tempo).")) + << pitchAffected + << QString("%1: %2").arg( + rightClick, tr("Raises playback speed in small steps.")) << changeAmount; add("rate_perm_down_rate_perm_down_small") - << tr("Lower Pitch") - << QString("%1: %2").arg(leftClick, tr("Sets the pitch lower.")) - << QString("%1: %2").arg(rightClick, tr("Sets the pitch lower in small steps.")) + << tr("Slow Down") + << QString("%1: %2").arg(leftClick, + tr("Lowers the track playback speed (tempo).")) + << pitchAffected + << QString("%1: %2").arg( + rightClick, tr("Lowers playback speed in small steps.")) << changeAmount; add("rate_temp_up_rate_temp_up_small") - << tr("Raise Pitch Temporary (Nudge)") - << QString("%1: %2").arg(leftClick, tr("Holds the pitch higher while active.")) - << QString("%1: %2").arg(rightClick, tr("Holds the pitch higher (small amount) while active.")) + << tr("Speed Up Temporarily (Nudge)") + << QString("%1: %2").arg(leftClick, + tr("Holds playback speed higher while active (tempo).")) + << pitchAffected + << QString("%1: %2").arg(rightClick, + tr("Holds playback speed higher (small amount) while " + "active.")) << changeAmount; add("rate_temp_down_rate_temp_down_small") - << tr("Lower Pitch Temporary (Nudge)") - << QString("%1: %2").arg(leftClick, tr("Holds the pitch lower while active.")) - << QString("%1: %2").arg(rightClick, tr("Holds the pitch lower (small amount) while active.")) + << tr("Slow Down Temporarily (Nudge)") + << QString("%1: %2").arg(leftClick, + tr("Holds playback speed lower while active (tempo).")) + << pitchAffected + << QString("%1: %2").arg(rightClick, + tr("Holds playback speed lower (small amount) while " + "active.")) << changeAmount; add("filterLow") diff --git a/src/util/clipboard.cpp b/src/util/clipboard.cpp new file mode 100644 index 00000000000..f38c5f361c7 --- /dev/null +++ b/src/util/clipboard.cpp @@ -0,0 +1,44 @@ +#include "clipboard.h" + +#include +#include +#include + +namespace { + +QByteArray urlsToUtf8(const QList& urls) { + QByteArray result; + for (const QUrl& url : urls) { + result += url.toEncoded(); + result += '\n'; + } + if (!result.isEmpty()) { + result.chop(1); + } + return result; +} + +} // namespace + +void Clipboard::start() { + instance()->m_urls.clear(); +} + +void Clipboard::add(const QUrl& url) { + instance()->m_urls.append(url); +} + +void Clipboard::finish() { + QMimeData* pData = new QMimeData; + pData->setUrls(instance()->m_urls); + // "x-special/gnome-copied-files" is used for many file managers + // https://indigo.re/posts/2021-12-21-clipboard-data.html + pData->setData(QStringLiteral("x-special/gnome-copied-files"), + "copy\n" + urlsToUtf8(instance()->m_urls)); + QApplication::clipboard()->setMimeData(pData); +} + +QList Clipboard::urls() { + const QMimeData* data = QApplication::clipboard()->mimeData(); + return data ? data->urls() : QList(); +} diff --git a/src/util/clipboard.h b/src/util/clipboard.h new file mode 100644 index 00000000000..fee83879ede --- /dev/null +++ b/src/util/clipboard.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +#include "util/singleton.h" + +class Clipboard : public Singleton { + QList m_urls; + + public: + static QList urls(); + static void start(); + static void finish(); + static void add(const QUrl& url); +}; diff --git a/src/widget/wlibrary.cpp b/src/widget/wlibrary.cpp index e3aadc6f54d..b408c625c74 100644 --- a/src/widget/wlibrary.cpp +++ b/src/widget/wlibrary.cpp @@ -1,5 +1,6 @@ #include "widget/wlibrary.h" +#include #include #include "library/libraryview.h" @@ -31,20 +32,20 @@ void WLibrary::setup(const QDomNode& node, const SkinContext& context) { kMaxTrackTableBackgroundColorOpacity); } -bool WLibrary::registerView(const QString& name, QWidget* view) { +bool WLibrary::registerView(const QString& name, QWidget* pView) { //qDebug() << "WLibrary::registerView" << name; const auto lock = lockMutex(&m_mutex); if (m_viewMap.contains(name)) { return false; } - if (dynamic_cast(view) == nullptr) { + if (dynamic_cast(pView) == nullptr) { qDebug() << "WARNING: Attempted to register view" << name << "with WLibrary " << "which does not implement the LibraryView interface. " << "Ignoring."; return false; } - addWidget(view); - m_viewMap[name] = view; + addWidget(pView); + m_viewMap[name] = pView; return true; } @@ -52,41 +53,49 @@ void WLibrary::switchToView(const QString& name) { const auto lock = lockMutex(&m_mutex); //qDebug() << "WLibrary::switchToView" << name; - LibraryView* oldLibraryView = dynamic_cast( + LibraryView* pOldLibrartView = dynamic_cast( currentWidget()); - QWidget* widget = m_viewMap.value(name, nullptr); - if (widget != nullptr) { - LibraryView * lview = dynamic_cast(widget); - if (lview == nullptr) { + QWidget* pWidget = m_viewMap.value(name, nullptr); + if (pWidget != nullptr) { + LibraryView* pLibraryView = dynamic_cast(pWidget); + if (pLibraryView == nullptr) { qDebug() << "WARNING: Attempted to switch to view" << name << "with WLibrary " << "which does not implement the LibraryView interface. " << "Ignoring."; return; } - if (currentWidget() != widget) { - if (oldLibraryView) { - oldLibraryView->saveCurrentViewState(); + if (currentWidget() != pWidget) { + if (pOldLibrartView) { + pOldLibrartView->saveCurrentViewState(); } //qDebug() << "WLibrary::setCurrentWidget" << name; - setCurrentWidget(widget); - lview->onShow(); - lview->restoreCurrentViewState(); + setCurrentWidget(pWidget); + pLibraryView->onShow(); + pLibraryView->restoreCurrentViewState(); } } } +void WLibrary::pasteFromSidebar() { + QWidget* pCurrent = currentWidget(); + LibraryView* pView = dynamic_cast(pCurrent); + if (pView) { + pView->pasteFromSidebar(); + } +} + void WLibrary::search(const QString& name) { auto lock = lockMutex(&m_mutex); - QWidget* current = currentWidget(); - LibraryView* view = dynamic_cast(current); - if (view == nullptr) { + QWidget* pCurrent = currentWidget(); + LibraryView* pView = dynamic_cast(pCurrent); + if (pView == nullptr) { qDebug() << "WARNING: Attempted to search in view" << name << "with WLibrary " << "which does not implement the LibraryView interface. Ignoring."; return; } lock.unlock(); - view->onSearch(name); + pView->onSearch(name); } LibraryView* WLibrary::getActiveView() const { @@ -98,17 +107,17 @@ bool WLibrary::isTrackInCurrentView(const TrackId& trackId) { VERIFY_OR_DEBUG_ASSERT(trackId.isValid()) { return false; } - QWidget* current = currentWidget(); - WTrackTableView* tracksView = qobject_cast(current); - if (!tracksView) { + QWidget* pCurrent = currentWidget(); + WTrackTableView* pTracksView = qobject_cast(pCurrent); + if (!pTracksView) { // This view is no tracks view, but maybe a special tracks view with a // controls row (AutoDJ, Recording)? //qDebug() << " view is no tracks view. look for tracks view child"; - tracksView = current->findChild(); + pTracksView = pCurrent->findChild(); } - if (tracksView) { + if (pTracksView) { //qDebug() << " tracks view found"; - return tracksView->isTrackInCurrentView(trackId); + return pTracksView->isTrackInCurrentView(trackId); } else { // No tracks view, this is probably a root view WLibraryTextBrowser //qDebug() << " no tracks view found"; @@ -122,15 +131,15 @@ void WLibrary::slotSelectTrackInActiveTrackView(const TrackId& trackId) { return; } - QWidget* current = currentWidget(); - WTrackTableView* tracksView = qobject_cast(current); - if (!tracksView) { + QWidget* pCurrent = currentWidget(); + WTrackTableView* pTracksView = qobject_cast(pCurrent); + if (!pTracksView) { //qDebug() << " view is no tracks view. look for tracks view child"; - tracksView = current->findChild(); + pTracksView = pCurrent->findChild(); } - if (tracksView) { + if (pTracksView) { //qDebug() << " tracks view found"; - tracksView->slotSelectTrack(trackId); + pTracksView->slotSelectTrack(trackId); } else { //qDebug() << " no tracks view found"; } @@ -142,3 +151,10 @@ bool WLibrary::event(QEvent* pEvent) { } return QStackedWidget::event(pEvent); } + +void WLibrary::keyPressEvent(QKeyEvent* pEvent) { + if (pEvent->key() == Qt::Key_Left && pEvent->modifiers() & Qt::ControlModifier) { + emit setLibraryFocus(FocusWidget::Sidebar); + } + QStackedWidget::keyPressEvent(pEvent); +} diff --git a/src/widget/wlibrary.h b/src/widget/wlibrary.h index 531915975a6..0b99e6d90a7 100644 --- a/src/widget/wlibrary.h +++ b/src/widget/wlibrary.h @@ -4,6 +4,9 @@ #include #include +#include "library/library_decl.h" +#include "library/libraryview.h" +#include "skin/legacy/skincontext.h" #include "util/compatibility/qmutex.h" #include "widget/wbasewidget.h" @@ -48,17 +51,22 @@ class WLibrary : public QStackedWidget, public WBaseWidget { return m_bShowButtonText; } + signals: + FocusWidget setLibraryFocus(FocusWidget newFocus); + public slots: // Show the view registered with the given name. Does nothing if the current // view is the specified view, or if the name does not specify any // registered view. void switchToView(const QString& name); void slotSelectTrackInActiveTrackView(const TrackId& trackId); + void pasteFromSidebar(); void search(const QString&); protected: bool event(QEvent* pEvent) override; + void keyPressEvent(QKeyEvent* event) override; private: QT_RECURSIVE_MUTEX m_mutex; diff --git a/src/widget/wlibrarysidebar.cpp b/src/widget/wlibrarysidebar.cpp index c13ac5c7b48..663006112c6 100644 --- a/src/widget/wlibrarysidebar.cpp +++ b/src/widget/wlibrarysidebar.cpp @@ -239,12 +239,24 @@ void WLibrarySidebar::keyPressEvent(QKeyEvent* event) { // TODO(XXX) Should first keyEvent ensure previous item has focus? I.e. if the selected // item is not focused, require second press to perform the desired action. - // make the selected item the navigation starting point + SidebarModel* sidebarModel = qobject_cast(model()); + QModelIndexList selectedIndices = selectionModel()->selectedRows(); + if (sidebarModel && !selectedIndices.isEmpty()) { + QModelIndex index = selectedIndices.at(0); + if (event->matches(QKeySequence::Delete) || event->key() == Qt::Key_Backspace) { + sidebarModel->clear(index); + return; + } + if (event->matches(QKeySequence::Paste)) { + sidebarModel->paste(index); + return; + } + } + focusSelectedIndex(); switch (event->key()) { case Qt::Key_Return: - focusSelectedIndex(); toggleSelectedItem(); return; case Qt::Key_Down: @@ -268,6 +280,14 @@ void WLibrarySidebar::keyPressEvent(QKeyEvent* event) { emit pressed(selIndex); return; } + case Qt::Key_Right: { + if (event->modifiers() & Qt::ControlModifier) { + emit setLibraryFocus(FocusWidget::TracksTable); + } else { + QTreeView::keyPressEvent(event); + } + return; + } case Qt::Key_Left: { // If an expanded item is selected let QTreeView collapse it QModelIndex selIndex = selectedIndex(); diff --git a/src/widget/wlibrarytableview.cpp b/src/widget/wlibrarytableview.cpp index d4885207c3b..afdc6a42ce8 100644 --- a/src/widget/wlibrarytableview.cpp +++ b/src/widget/wlibrarytableview.cpp @@ -386,6 +386,14 @@ QModelIndex WLibraryTableView::moveCursor(CursorAction cursorAction, return pModel->index(pModel->rowCount() - 1, column); } } break; + case QAbstractItemView::MoveLeft: + case QAbstractItemView::MoveRight: + if (modifiers & Qt::ControlModifier) { + // Ignore, so it can be handled by WLibrary::keyEvent + // to navigate to the sidebar + return currentIndex(); + } + break; default: break; } diff --git a/src/widget/wlibrarytableview.h b/src/widget/wlibrarytableview.h index 7e0e8351dd3..04759b2a1f4 100644 --- a/src/widget/wlibrarytableview.h +++ b/src/widget/wlibrarytableview.h @@ -4,6 +4,7 @@ #include #include +#include "library/library_decl.h" #include "library/libraryview.h" #include "preferences/usersettings.h" #include "track/track_decl.h" @@ -61,6 +62,7 @@ class WLibraryTableView : public QTableView, public virtual LibraryView { void trackSelected(TrackPointer pTrack); void onlyCachedCoverArt(bool); void scrollValueChanged(int); + FocusWidget setLibraryFocus(FocusWidget newFocus); public slots: void setTrackTableFont(const QFont& font); diff --git a/src/widget/wlibrarytextbrowser.cpp b/src/widget/wlibrarytextbrowser.cpp index 82d35ed6e5b..ebc6484079e 100644 --- a/src/widget/wlibrarytextbrowser.cpp +++ b/src/widget/wlibrarytextbrowser.cpp @@ -1,5 +1,7 @@ #include "widget/wlibrarytextbrowser.h" +#include + #include "moc_wlibrarytextbrowser.cpp" WLibraryTextBrowser::WLibraryTextBrowser(QWidget* parent) @@ -13,3 +15,11 @@ bool WLibraryTextBrowser::hasFocus() const { void WLibraryTextBrowser::setFocus() { QWidget::setFocus(); } + +void WLibraryTextBrowser::keyPressEvent(QKeyEvent* event) { + if (event->key() == Qt::Key_Left && event->modifiers() & Qt::ControlModifier) { + event->ignore(); + return; + } + QTextBrowser::keyPressEvent(event); +} diff --git a/src/widget/wlibrarytextbrowser.h b/src/widget/wlibrarytextbrowser.h index 206664f0693..264ad90f2e6 100644 --- a/src/widget/wlibrarytextbrowser.h +++ b/src/widget/wlibrarytextbrowser.h @@ -2,6 +2,7 @@ #include +#include "library/library_decl.h" #include "library/libraryview.h" class WLibraryTextBrowser : public QTextBrowser, public LibraryView { @@ -11,4 +12,7 @@ class WLibraryTextBrowser : public QTextBrowser, public LibraryView { void onShow() override {} bool hasFocus() const override; void setFocus() override; + void keyPressEvent(QKeyEvent* event) override; + signals: + FocusWidget setLibraryFocus(FocusWidget newFocus); }; diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp index 8090dff4a29..889e7e646e8 100644 --- a/src/widget/wtrackmenu.cpp +++ b/src/widget/wtrackmenu.cpp @@ -205,7 +205,7 @@ void WTrackMenu::createMenus() { if (featureIsEnabled(Feature::Reset)) { m_pClearMetadataMenu = new QMenu(this); //: Reset metadata in right click track context menu in library - m_pClearMetadataMenu->setTitle(tr("Reset")); + m_pClearMetadataMenu->setTitle(tr("Clear")); } if (featureIsEnabled(Feature::Analyze)) { @@ -481,7 +481,7 @@ void WTrackMenu::createActions() { slotScaleBpm(mixxx::Beats::BpmScale::ThreeHalves); }); - m_pBpmResetAction = new QAction(tr("Reset BPM"), m_pBPMMenu); + m_pBpmResetAction = new QAction(tr("Clear BPM and Beatgrid"), m_pBPMMenu); connect(m_pBpmResetAction, &QAction::triggered, this, diff --git a/src/widget/wtrackproperty.cpp b/src/widget/wtrackproperty.cpp index 4ac2a10ed46..4e86d8e52de 100644 --- a/src/widget/wtrackproperty.cpp +++ b/src/widget/wtrackproperty.cpp @@ -15,11 +15,13 @@ WTrackProperty::WTrackProperty( QWidget* pParent, UserSettingsPointer pConfig, Library* pLibrary, - const QString& group) + const QString& group, + bool isMainDeck) : WLabel(pParent), m_group(group), m_pConfig(pConfig), - m_pLibrary(pLibrary) { + m_pLibrary(pLibrary), + m_isMainDeck(isMainDeck) { setAcceptDrops(true); } @@ -120,22 +122,28 @@ void WTrackProperty::contextMenuEvent(QContextMenuEvent* event) { } void WTrackProperty::ensureTrackMenuIsCreated() { - if (m_pTrackMenu.get() == nullptr) { - m_pTrackMenu = make_parented( - this, m_pConfig, m_pLibrary, WTrackMenu::kDeckTrackMenuFeatures); - - // When a track menu for this deck is shown/hidden via contextMenuEvent - // or pushbutton, it emits trackMenuVisible(bool). - // The pushbutton is created in BaseTrackPlayer which, on value change requests, - // also emits a signal which is connected to our slotShowTrackMenuChangeRequest(). - connect(m_pTrackMenu, - &WTrackMenu::trackMenuVisible, - this, - [this](bool visible) { - ControlObject::set(ConfigKey(m_group, kShowTrackMenuKey), - visible ? 1.0 : 0.0); - }); + if (m_pTrackMenu.get() != nullptr) { + return; + } + + m_pTrackMenu = make_parented( + this, m_pConfig, m_pLibrary, WTrackMenu::kDeckTrackMenuFeatures); + + // The show control exists only for main decks. + if (!m_isMainDeck) { + return; } + // When a track menu for this deck is shown/hidden via contextMenuEvent + // or pushbutton, it emits trackMenuVisible(bool). + // The pushbutton is created in BaseTrackPlayer which, on value change requests, + // also emits a signal which is connected to our slotShowTrackMenuChangeRequest(). + connect(m_pTrackMenu, + &WTrackMenu::trackMenuVisible, + this, + [this](bool visible) { + ControlObject::set(ConfigKey(m_group, kShowTrackMenuKey), + visible ? 1.0 : 0.0); + }); } /// This slot handles show/hide requests originating from both pushbutton changes diff --git a/src/widget/wtrackproperty.h b/src/widget/wtrackproperty.h index 286a2d539d7..fe80c93ab64 100644 --- a/src/widget/wtrackproperty.h +++ b/src/widget/wtrackproperty.h @@ -18,7 +18,8 @@ class WTrackProperty : public WLabel, public TrackDropTarget { QWidget* pParent, UserSettingsPointer pConfig, Library* pLibrary, - const QString& group); + const QString& group, + bool isMainDeck); ~WTrackProperty() override; void setup(const QDomNode& node, const SkinContext& context) override; @@ -48,6 +49,7 @@ class WTrackProperty : public WLabel, public TrackDropTarget { const QString m_group; const UserSettingsPointer m_pConfig; Library* m_pLibrary; + const bool m_isMainDeck; TrackPointer m_pCurrentTrack; QString m_property; diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index fff5254d98f..10b7b778233 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -115,7 +115,7 @@ void WTrackTableView::slotGuiTick50ms(double /*unused*/) { // slows down scrolling performance so we wait until the user has // stopped interacting first. if (m_selectionChangedSinceLastGuiTick) { - const QModelIndexList indices = selectionModel()->selectedRows(); + const QModelIndexList indices = getSelectedRows(); if (indices.size() == 1 && indices.first().isValid()) { // A single track has been selected TrackModel* trackModel = getTrackModel(); @@ -139,6 +139,11 @@ void WTrackTableView::slotGuiTick50ms(double /*unused*/) { } } +// slot +void WTrackTableView::pasteFromSidebar() { + pasteTracks(QModelIndex()); +} + // slot void WTrackTableView::loadTrackModel(QAbstractItemModel* model, bool restoreState) { qDebug() << "WTrackTableView::loadTrackModel()" << model; @@ -400,7 +405,7 @@ TrackModel::SortColumnId WTrackTableView::getColumnIdFromCurrentIndex() { } void WTrackTableView::assignPreviousTrackColor() { - QModelIndexList indices = selectionModel()->selectedRows(); + QModelIndexList indices = getSelectedRows(); if (indices.isEmpty()) { return; } @@ -421,7 +426,7 @@ void WTrackTableView::assignPreviousTrackColor() { } void WTrackTableView::assignNextTrackColor() { - QModelIndexList indices = selectionModel()->selectedRows(); + QModelIndexList indices = getSelectedRows(); if (indices.isEmpty()) { return; } @@ -442,7 +447,7 @@ void WTrackTableView::assignNextTrackColor() { } void WTrackTableView::slotPurge() { - QModelIndexList indices = selectionModel()->selectedRows(); + QModelIndexList indices = getSelectedRows(); if (indices.isEmpty()) { return; } @@ -456,7 +461,7 @@ void WTrackTableView::slotPurge() { } void WTrackTableView::slotDeleteTracksFromDisk() { - QModelIndexList indices = selectionModel()->selectedRows(); + QModelIndexList indices = getSelectedRows(); if (indices.isEmpty()) { return; } @@ -467,7 +472,7 @@ void WTrackTableView::slotDeleteTracksFromDisk() { } void WTrackTableView::slotUnhide() { - QModelIndexList indices = selectionModel()->selectedRows(); + QModelIndexList indices = getSelectedRows(); if (indices.isEmpty()) { return; } @@ -504,7 +509,7 @@ void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { } event->accept(); // Update track indices in context menu - QModelIndexList indices = selectionModel()->selectedRows(); + QModelIndexList indices = getSelectedRows(); m_pTrackMenu->loadTrackModelIndices(indices); saveCurrentIndex(); @@ -568,7 +573,7 @@ void WTrackTableView::mouseMoveEvent(QMouseEvent* pEvent) { //qDebug() << "MouseMoveEvent"; // Iterate over selected rows and append each item's location url to a list. QList locations; - const QModelIndexList indices = selectionModel()->selectedRows(); + const QModelIndexList indices = getSelectedRows(); for (const QModelIndex& index : indices) { if (!index.isValid()) { @@ -675,7 +680,7 @@ void WTrackTableView::dropEvent(QDropEvent * event) { // Save a list of row (just plain ints) so we don't get screwed over // when the QModelIndexes all become invalid (eg. after moveTrack() // or addTrack()) - const QModelIndexList indices = selectionModel()->selectedRows(); + const QModelIndexList indices = getSelectedRows(); QList selectedRows; for (const QModelIndex& idx : indices) { @@ -814,11 +819,87 @@ void WTrackTableView::dropEvent(QDropEvent * event) { verticalScrollBar()->setValue(vScrollBarPos); } +QModelIndexList WTrackTableView::getSelectedRows() const { + QItemSelectionModel* pSelectionModel = selectionModel(); + VERIFY_OR_DEBUG_ASSERT(pSelectionModel != nullptr) { + qWarning() << "No selection model available"; + return {}; + } + return pSelectionModel->selectedRows(); +} + TrackModel* WTrackTableView::getTrackModel() const { TrackModel* trackModel = dynamic_cast(model()); return trackModel; } +namespace { +QModelIndex calculateCutIndex(const QModelIndex& currentIndex, + const QModelIndexList& removedIndices) { + if (removedIndices.empty()) { + return QModelIndex(); + } + const int row = currentIndex.row(); + int rowAfterRemove = row; + for (const auto& removeIndex : removedIndices) { + if (removeIndex.row() < row) { + rowAfterRemove--; + } + } + return currentIndex.siblingAtRow(rowAfterRemove); +} +} // namespace + +void WTrackTableView::removeSelectedTracks() { + const QModelIndexList indices = getSelectedRows(); + const QModelIndex newIndex = calculateCutIndex(currentIndex(), indices); + getTrackModel()->removeTracks(indices); + setCurrentIndex(newIndex); +} + +void WTrackTableView::cutSelectedTracks() { + const QModelIndexList indices = getSelectedRows(); + const QModelIndex newIndex = calculateCutIndex(currentIndex(), indices); + getTrackModel()->cutTracks(indices); + setCurrentIndex(newIndex); +} + +void WTrackTableView::copySelectedTracks() { + const QModelIndexList indices = getSelectedRows(); + getTrackModel()->copyTracks(indices); +} + +void WTrackTableView::pasteTracks(const QModelIndex& index) { + TrackModel* trackModel = getTrackModel(); + if (!trackModel) { + return; + } + + const QList rows = trackModel->pasteTracks(index); + if (rows.empty()) { + return; + } + + updateGeometries(); + + const auto lastVisibleRow = rowAt(height()); + + // Use selectRow to scroll to the first or last pasted row. We would use + // scrollTo but this is broken. This solution was already used elsewhere + // in this way. + if (rows.back() > lastVisibleRow) { + selectRow(rows.back()); + } else { + selectRow(rows.front()); + } + + // Select all the rows that we pasted + for (const auto row : rows) { + selectionModel()->select(model()->index(row, 0), + QItemSelectionModel::Select | QItemSelectionModel::Rows); + } +} + void WTrackTableView::keyPressEvent(QKeyEvent* event) { switch (event->key()) { case kPropertiesShortcutKey: { @@ -833,22 +914,47 @@ void WTrackTableView::keyPressEvent(QKeyEvent* event) { if (event->modifiers().testFlag(Qt::NoModifier)) { slotMouseDoubleClicked(currentIndex()); } else if ((event->modifiers() & kPropertiesShortcutModifier)) { - QModelIndexList indices = selectionModel()->selectedRows(); + QModelIndexList indices = getSelectedRows(); if (indices.length() == 1) { m_pTrackMenu->loadTrackModelIndices(indices); m_pTrackMenu->slotShowDlgTrackInfo(); } } - } break; + return; + } case kHideRemoveShortcutKey: { if (event->modifiers() == kHideRemoveShortcutModifier) { hideOrRemoveSelectedTracks(); - return; } - } break; + return; + } default: - QTableView::keyPressEvent(event); + break; } + TrackModel* trackModel = getTrackModel(); + if (trackModel && !trackModel->isLocked()) { + if (event->matches(QKeySequence::Delete) || event->key() == Qt::Key_Backspace) { + removeSelectedTracks(); + return; + } + if (event->matches(QKeySequence::Cut)) { + cutSelectedTracks(); + return; + } + if (event->matches(QKeySequence::Copy)) { + copySelectedTracks(); + return; + } + if (event->matches(QKeySequence::Paste)) { + pasteTracks(currentIndex()); + return; + } + if (event->key() == Qt::Key_Escape) { + clearSelection(); + setCurrentIndex(QModelIndex()); + } + } + QTableView::keyPressEvent(event); } void WTrackTableView::resizeEvent(QResizeEvent* event) { @@ -893,7 +999,7 @@ void WTrackTableView::resizeEvent(QResizeEvent* event) { } void WTrackTableView::hideOrRemoveSelectedTracks() { - QModelIndexList indices = selectionModel()->selectedRows(); + QModelIndexList indices = getSelectedRows(); if (indices.isEmpty()) { return; } @@ -975,7 +1081,7 @@ void WTrackTableView::hideOrRemoveSelectedTracks() { } void WTrackTableView::activateSelectedTrack() { - auto indices = selectionModel()->selectedRows(); + auto indices = getSelectedRows(); if (indices.isEmpty()) { return; } @@ -983,7 +1089,7 @@ void WTrackTableView::activateSelectedTrack() { } void WTrackTableView::loadSelectedTrackToGroup(const QString& group, bool play) { - auto indices = selectionModel()->selectedRows(); + auto indices = getSelectedRows(); if (indices.isEmpty()) { return; } @@ -1005,19 +1111,17 @@ void WTrackTableView::loadSelectedTrackToGroup(const QString& group, bool play) m_pConfig->getValue(kConfigKeyAllowTrackLoadToPlayingDeck); } // If the track load override is disabled, check to see if a track is - // playing before trying to load it - if (!allowLoadTrackIntoPlayingDeck) { - // TODO(XXX): Check for other than just the first preview deck. - if (group != "[PreviewDeck1]" && - ControlObject::get(ConfigKey(group, "play")) > 0.0) { - return; - } + // playing before trying to load it. + // Always load to preview deck. + if (!allowLoadTrackIntoPlayingDeck && + !PlayerManager::isPreviewDeckGroup(group) && + ControlObject::get(ConfigKey(group, "play")) > 0.0) { + return; } auto index = indices.at(0); auto* trackModel = getTrackModel(); TrackPointer pTrack; - if (trackModel && - (pTrack = trackModel->getTrack(index))) { + if (trackModel && (pTrack = trackModel->getTrack(index))) { emit loadTrackToPlayer(pTrack, group, play); } } @@ -1025,19 +1129,13 @@ void WTrackTableView::loadSelectedTrackToGroup(const QString& group, bool play) QList WTrackTableView::getSelectedTrackIds() const { QList trackIds; - QItemSelectionModel* pSelectionModel = selectionModel(); - VERIFY_OR_DEBUG_ASSERT(pSelectionModel != nullptr) { - qWarning() << "No selected tracks available"; - return trackIds; - } - TrackModel* pTrackModel = getTrackModel(); VERIFY_OR_DEBUG_ASSERT(pTrackModel != nullptr) { qWarning() << "No selected tracks available"; return trackIds; } - const QModelIndexList rows = selectionModel()->selectedRows(); + const QModelIndexList rows = getSelectedRows(); trackIds.reserve(rows.size()); for (const QModelIndex& row: rows) { const TrackId trackId = pTrackModel->getTrackId(row); @@ -1212,12 +1310,23 @@ void WTrackTableView::doSortByColumn(int headerSection, Qt::SortOrder sortOrder) sortByColumn(headerSection, sortOrder); + selectTracksById(selectedTrackIds, prevColum); + + // This seems to be broken since at least Qt 5.12: no scrolling is issued + // scrollTo(first, QAbstractItemView::EnsureVisible); + horizontalScrollBar()->setValue(savedHScrollBarPos); +} + +void WTrackTableView::selectTracksById(const QList& trackIds, int prevColum) { + TrackModel* trackModel = getTrackModel(); + QAbstractItemModel* itemModel = model(); + QItemSelectionModel* currentSelection = selectionModel(); currentSelection->reset(); // remove current selection // Find previously selected tracks and store respective rows for reselection. QMap selectedRows; - for (const auto& trackId : selectedTrackIds) { + for (const auto& trackId : trackIds) { // TODO(rryan) slowly fixing the issues with BaseSqlTableModel. This // code is broken for playlists because it assumes each trackid is in // the table once. This will erroneously select all instances of the @@ -1256,10 +1365,6 @@ void WTrackTableView::doSortByColumn(int headerSection, Qt::SortOrder sortOrder) QModelIndex tl = itemModel->index(i.key(), 0); currentSelection->select(tl, QItemSelectionModel::Rows | QItemSelectionModel::Select); } - - // This seems to be broken since at least Qt 5.12: no scrolling is issued - //scrollTo(first, QAbstractItemView::EnsureVisible); - horizontalScrollBar()->setValue(savedHScrollBarPos); } void WTrackTableView::applySortingIfVisible() { @@ -1330,7 +1435,7 @@ bool WTrackTableView::hasFocus() const { } void WTrackTableView::setFocus() { - QWidget::setFocus(); + QWidget::setFocus(Qt::OtherFocusReason); } QString WTrackTableView::getModelStateKey() const { diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index a786f0d43c3..ea87853d3f7 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -34,6 +34,7 @@ class WTrackTableView : public WLibraryTableView { void onShow() override; bool hasFocus() const override; void setFocus() override; + void pasteFromSidebar() override; void keyPressEvent(QKeyEvent* event) override; void resizeEvent(QResizeEvent* event) override; void activateSelectedTrack() override; @@ -47,6 +48,12 @@ class WTrackTableView : public WLibraryTableView { TrackId getCurrentTrackId() const; bool setCurrentTrackId(const TrackId& trackId, int column = 0, bool scrollToTrack = false); + void removeSelectedTracks(); + void cutSelectedTracks(); + void copySelectedTracks(); + void pasteTracks(const QModelIndex& index); + void selectTracksById(const QList& tracks, int prevColumn); + double getBackgroundColorOpacity() const { return m_backgroundColorOpacity; } @@ -126,6 +133,9 @@ class WTrackTableView : public WLibraryTableView { // when dragging. void mouseMoveEvent(QMouseEvent *pEvent) override; + // Returns the list of selected rows, or an empty list if none are selected. + QModelIndexList getSelectedRows() const; + // Returns the current TrackModel, or returns NULL if none is set. TrackModel* getTrackModel() const; diff --git a/src/widget/wtrackwidgetgroup.cpp b/src/widget/wtrackwidgetgroup.cpp index d3d2a66eb46..e7e817b6e1a 100644 --- a/src/widget/wtrackwidgetgroup.cpp +++ b/src/widget/wtrackwidgetgroup.cpp @@ -18,12 +18,14 @@ constexpr int kDefaultTrackColorAlpha = 255; WTrackWidgetGroup::WTrackWidgetGroup(QWidget* pParent, UserSettingsPointer pConfig, Library* pLibrary, - const QString& group) + const QString& group, + bool isMainDeck) : WWidgetGroup(pParent), m_group(group), m_pConfig(pConfig), m_pLibrary(pLibrary), - m_trackColorAlpha(kDefaultTrackColorAlpha) { + m_trackColorAlpha(kDefaultTrackColorAlpha), + m_isMainDeck(isMainDeck) { setAcceptDrops(true); } @@ -116,17 +118,22 @@ void WTrackWidgetGroup::contextMenuEvent(QContextMenuEvent* event) { } void WTrackWidgetGroup::ensureTrackMenuIsCreated() { - if (m_pTrackMenu.get() == nullptr) { - m_pTrackMenu = make_parented( - this, m_pConfig, m_pLibrary, WTrackMenu::kDeckTrackMenuFeatures); - - // See WTrackProperty for info - connect(m_pTrackMenu, - &WTrackMenu::trackMenuVisible, - this, - [this](bool visible) { - ControlObject::set(ConfigKey(m_group, kShowTrackMenuKey), - visible ? 1.0 : 0.0); - }); + if (m_pTrackMenu.get() != nullptr) { + return; } + m_pTrackMenu = make_parented( + this, m_pConfig, m_pLibrary, WTrackMenu::kDeckTrackMenuFeatures); + + // The show control exists onlyfor main decks. + // See WTrackProperty for info + if (!m_isMainDeck) { + return; + } + connect(m_pTrackMenu, + &WTrackMenu::trackMenuVisible, + this, + [this](bool visible) { + ControlObject::set(ConfigKey(m_group, kShowTrackMenuKey), + visible ? 1.0 : 0.0); + }); } diff --git a/src/widget/wtrackwidgetgroup.h b/src/widget/wtrackwidgetgroup.h index 698aada2fba..a7be56a429b 100644 --- a/src/widget/wtrackwidgetgroup.h +++ b/src/widget/wtrackwidgetgroup.h @@ -15,7 +15,8 @@ class WTrackWidgetGroup : public WWidgetGroup, public TrackDropTarget { WTrackWidgetGroup(QWidget* pParent, UserSettingsPointer pConfig, Library* pLibrary, - const QString& group); + const QString& group, + bool isMainDeck); ~WTrackWidgetGroup() override; void setup(const QDomNode& node, const SkinContext& context) override; @@ -49,6 +50,7 @@ class WTrackWidgetGroup : public WWidgetGroup, public TrackDropTarget { TrackPointer m_pCurrentTrack; QColor m_trackColor; int m_trackColorAlpha; + const bool m_isMainDeck; parented_ptr m_pTrackMenu; };