diff --git a/CHANGES b/CHANGES index 93e7c421..abddcaea 100644 --- a/CHANGES +++ b/CHANGES @@ -1,1272 +1,1274 @@ 18.07.70 [2018-03-24] * enabled support for high dpi displays (icon scaling) * show error message if no plugins were found and terminate * updated developer handbook + * cleanup: removed unused feature to attach meta data to lists + of tracks and ranges of samples 18.03 [2017-11-13] * do not reset undo/redo information when saving blocks or selection only * ask for confirmation (Continue/Cancel) before doing a revert * switched on MP3 support per default * bugfix: crash in pulseaudio playback * fixed some untranslated menu entries of plugins (missing context) * bugfix for sporadic fail of libaudiofile when seeking at EOF * improved C++11 compatibility 17.08.2 [2017-10-08] * new plugin: export to K3b project file * reduced flicker of position widget * bugfix: deleting labels per menu did not work * bugfix: wrong text in file progress dialog when saving * bugfix: compression type lookup did not work when using static functions * bugfix: distinguish ogg/vorbis and ogg/opus by mime type * changed license of audio samples to CC BY-SA 4.0 17.04.0 [2017-03-19] * use KDE versioning scheme * removed kwave.lsm and some now unneeded scripts (filter-pot.pl, get_lsm_entry.pl and set_version.sh) * simplified version number handling * eliminated cmake/FindOggVorbis.cmake * changed licenses of all cmake files to BSD license * bugfix: wrong assert in Stripe.cpp * OggOpus decoder: fixed unwanted data duplication (may lead to out-of-memory) * saveblocks: special handling for slashes in parts of a generated file name, e.g. title or meta data item - replace with similar unicode character * saveblocks: special handling for (multiple) whitespaces in parts of a generated file name - replace with single spaces * saveblocks: do not ask on every file in case that not all file properties are supported by the chosen encoder * saveblocks: use the block title (description of the left side label) as tile of saved block * bugfix: file was left in "modified" state after adding a label and canceling * re-activated the "label" menu * renamed command "add_label" -> "label:add" * renamed command "delete_label" -> "label:delete" * renamed command "edit_label" -> "label:edit" * implemented loading and saving of labels * allow special value -1 as index for label:delete(...) to delete _all_ labels * doc: split off developer sections from handbook into separate file, to reduce load of translators. new make target "html_doc_devel" (included in "apidoc") * label edit: avoid position from snapping too much away when adding a label in the gui while last edit was in percent mode * implemented moving of labels per mouse * show cursor line for orientation during drag&drop 16.12.0 [2016-11-28] * project moved from kdereview to kdemultimedia * added genre types 126..191 * record plugin: fixed crash when restarting record in MDI or tab mode * record plugin: reset did not work when clicking the "New" button shortly after starting the recording * record plugin: selection of default recording method did not work * revised use of signal handler in WorkerThread * WorkerThread: no longer use "bool" for signaling the wish to terminate, use QAtomicInt instead (also applies to Plugin class) * changed order of application object creation and setting up command line parser + about data * got rid of I18N_NOOP in main.cpp * bugfix: untranslated strings in menu * LICENSES file still mentioned Qt-4/KDE-4 libraries * CMakeLists.txt: remove RPATH settings * fixed small memory leak in menu group handling * fixed i18n of application AboutData * bugfix: internal name of "goto" plugin was wrong * bugfix: wrapper script did not work * fixed wrong encoding of error messages received via strerror and libs * bugfix: handling of File/Revert was wrong in case of newly created file 0.9.2 [2016-06-26] * recording via Qt Multimedia * using KDE service API for loading plugins * bugfix: screenshot function sometimes returned 1x1 image only (bug in Qt QWidget::frameGeometry is broken, but QWindow::frameGeometry works) * Gentoo ebuild updated to EAPI=6 * removed unneeded build and runtime dependencies * removed optimized memcpy for PowerPC * record plugin: improved handling of error messages * record plugin: added retry mechanism in case of device busy * added menu entries for recording and record setup * import of Core Audio Format (*.caf), using ALAC, A-LAW, U-LAW and IMA compression * import of Sample Vision Format (*.smp) * import of NIST SPHERE Audio File Format (*.nist) * import of Creative Voice files (*.voc) * import Audio Visual Research File Format (*.avr) * import of Amiga IFF/8SVX Sound File Format (*.8svx) * handbook: description of parameters of the record plugin was missing * handbook: fixed duplicate ":" in header of plugin parameter descriptions * made Kwave::SampleFormat and Kwave::Compression independent from libaudiofile 0.9.1 [2016-02-21] * ported to KDE Frameworks 5 (KF5) / Qt5 * playback via Qt Multimedia * bugfix: saved plugin parameter lists with escaped characters were not unescaped when loading again * compile fix for armv7l * codec_mp3: added missing "help" button to encoder setup dialog * fixed invocation of file dialog, as suggested by EBN * support for cmake > 3.3, fix for policy CMP0063 * cmdline option "--nofork" no longer exists * bugfix: multiple issues in context of switching the GUI type in scripts * bugfix: fixed issues in saveblocks plugin with special characters in filenames and patterns, format strings of second and later invocations of patterns were ignored * saveblocks: allow path separators in filename patterns to make it possible to create subdirectories * saveblocks: added patterns to include file info (file meta data) or the title of the current block * workaround for bug in KDE #345320 (missing translators in help/about dialog) * about plugin: added info about translation team * added screenshot of the edit_label dialog * bugfix: tooltips of the fileinfo dialog were not translated * new make target: "make msgstats" to show the progress of translations * compile fix for armv7l * playback: dropped Phonon support (was broken and no longer supported by KF5) * bugfix: hourglass cursor was not taken back in playback setup dialog * RPM: format of changelog has changed * changed plugin install directory and prefix/suffix * curve widget: use same colors as in frequency response widget, for better readability on bright color themes 0.9.0 [2015-05-25] * first version hosted on KDE (kdereview) and SourceForge * added command line parameter for selecting the GUI type * in MDI mode: new menu entry + function to arrange sub windows vertically * handbook: added text command reference * handbook: added plugin reference * enabled the "Help" buttons of all plugins and let them open the corresponding section in the handbook * make system: new target "update-handbook" (updates command, file info and plugin cross references) * make system: fixed dependency problem in translation * new commands: "window:sendkey", "window:screenshot", "window:close" and "window:resize" * built-in variable ${LANG} for kwave commands * support for delayed command execution * debug plugin: always compiled in, but only visible in debug build * new command sync(): wait for commands scheduled with delayed * bugfix: exporting a mono file as MP3 produced a stereo MP3 file * MP3: emphasis, copyrighted, original got lost during save/load * file info dialog: MPEG settings were not handled properly * bugfix: assert/numeric overflow in selectnextlabels() at end of file * new plugin: stringenter * bugfix: minimized windows were not migrated properly when switching GUI type * new command: window:minimize * bugfix: missing range check in noise generator (when used per script) * bugfix: saveblocks plugin did not work when omitting file extension * saveblocks plugin: use escaped strings for storing settings instead of base64 0.8.99-2 [2015-01-02] * bugfix: wrong sub window mode when switching to tab mode when having only one sub window (workaround for bug in Qt) * bugfix: recording did not work in MDI and Tab mode (wrong file context) * bugfix: fixed passing a text command as first command line parameter * new commands: delayed, window:click, window:sendkey, window:close, window:resize, window:screenshot 0.8.99 [2014-12-28] * GUI: implemented SDI, MDI and Tab GUI modes * bugfix: deadlock in class Track * bugfix: segfault during shutdown of logger * bugfix: segfault when unloading plugins (on some systems) * bugfix: "zoom to selection" was not disabled if nothing was selected * bugfix: toolbar buttons for cut/copy/erase/delete did not properly get enabled/disabled on change of selection * bugfix: overview widget did not properly refresh after deleting all tracks * bugfix: assert in vorbis decoder when opening file with bitrate -1 * menu subsystem: added support for lists within a menu * menu subsystem: show/hide toplevel menu entries * menu subsystem: added support for exclusive selection (radio buttons) * menu subsystem: let KDE chose shortcuts automatically * added menu entry to clear "recently opened files" list * implemented URL scheme for passing text commands from the command line example: kwave --iconic --disable-splashscreen test.wav \ kwave:plugin%3Aexecute?normalize \ kwave:save \ kwave:quit * using perl scripts creating for i18n from menus.config and for getting entries from lsm files, no longer need awk, sort, uniq * creating menu translation template directly per perl script instead of generated dummy cpp file (requires "msgcat") * menu translations: assign a context to each menu entry * bugfix: division through zero on ogg files with invalid bitrate info * manual: added section about GUI types * i18n: translations were missing in kwave.desktop 0.8.12 [2014-06-04] * recording via PulseAudio, by Joerg-Christan Boehme * bugfix: "Close" button of the record dialog did not save settings * bugfix: amplify free plugin: untranslated action names in progress bar * bugfix: sonagram plugin did not honor the windowing function parameter * bugfix: coherency problems in overview cache * bugfix: metadata got lost after cut/undo/redo * bugfix: save/as check against overwriting existing files failed * bugfix: undo/redo did not work after recording * bugfix: signal was "modified" after canceled record (empty) / done * bugfix: wrong calculation of zoom and window geometry at startup * bugfix: wrong scaling of overview in sonagram window * bugfix: playback pointer did not update synchronously across tracks * bugfix: brought back support for optimized memcpy (from xine-lib) * updated memcpy.c + cpu detection, including AVX assembler support * new command line option: "--logfile=" for logging to a file * brought back the horizontal scroll bar * support for swap files to store undo data * speedup: too many copy-on-write operations, use more const data * improved robustness against out of memory situations * memory manager: added statistics for debugging * memory settings: only use up to 25% of process address space * internal cleanups: renamed openSampleReader -> openReader, fixed signature of Signal/SignalManager::openWriter * speedup: use stripe list instead of raw data for saving undo data * debug plugin: added functions "labels_at_stripes", "sawtooth_verify" and "dump_metadata" * automatic defragmentation of stripes * sonagram plugin: use Qt Concurrent framework -> more than factor 40 faster on a quad core cpu * got rid of KDE ThreadWeaver, replaced with Qt Concurrent framework * debug plugin: added function "fm_sweep" * workaround for broken WAV files with zeroed fact chunk * fixed many 32/64 bit issues * new build target "make wrapper": creates a wrapper script to start Kwave for test/debug purpose * new build target "make dep": creates a binary debian package (for personal use and testing purposes) * mouse wheel + Ctrl: zoom in/out aligns signal to mouse position * record plugin: level meter is always enabled, simplified dialog * PulseAudio playback: fixed wrong timeout calculation * requires at least Qt-4.7 + FLAC-1.2.0 0.8.11 [2013-11-24] * added spanish translation, provided by Carlos R. * bugfix: file names were not properly escaped in context of file/open, file/openrecent and drag&drop * bugfix: saveblocks() did not abort properly when pressing cancel * unclean shutdown of the file progress dialog when saving * noise plugin: add noise (mix) instead of overwrite, with adjustable level in percent or dB * pause button: change tooltip to "continue" if paused * new command line option: "--disable-splashscreen" * new command line option: "--iconic" to start minimized * fixed quoting errors in CMakeLists.txt (cmake-2.8.12 complained) * bugfix: ASCII encoder: escape special characters in meta data * implementation of ASCII import * memory settings: raised default memory limits * bugfix: crash in file info dialog / auto generate keywords * MP3 plugin: use ID3 tag TSSE for software version * ASCII codec: implemented support for labels 0.8.10 [2013-02-09] * file name cleanup: removed "Kwave" prefix * bugfix: added range checks for track selection commands * reverted changes in sample writer due to problems in debug mode (commits ee54660d4380d264b7346a904eff9dd8d8d00a93 and 6fba04db879ea7ae1fdf79141dd93d47f9c1d403) * bugfix: unwanted termination if splash screen closed while the first toplevel widget still was starting up * moved code into namespace "Kwave" * cleanup: remove support for outdated FLAC API versions below 1.1.3 * removed unused code: libkwave/FileFormat.* * renamed source files with "Kwave" in the name * added subsystem prefix to inclusion of Qt header files * using bit types from qt (e.g. u_int32_t => quint32) * compile with DQT_NO_CAST_TO_ASCII and QT_NO_CAST_FROM_ASCII * bugfix: recording via OSS did not handle invalid devices properly * improved auto detect of svg-to-png conversion, added support for "rsvg-convert" (SF bug #38) * removed dependency to ImageMagick if "rsvg" is available * replaced libkwave/byteswap.h with generic Qt functions * replaced some Qt classes with their KDE equivalent: KLineEdit, KComboBox, KDialogButtonBox, KPushButton, KTabWidget, KTextEdit * using KDE standard buttons in dialogs * simplified plugin loading mechanism, do load/unload only at start/end of the program * delete plugin settings of old versions when detected * using QLibrary instead of functions from libdl * bugfix: shutdown sequence was incomplete * bugfix: keyboard shortcut for first menu entry did not work * bugfix: ambiguous keyboard shortcut for "File/New Window" * bugfix: undo of "modify label" caused loss of other labels * bugfix: use timeout for phonon playback, to avoid hang on unusable devices * using klocale for formating numbers of samples * added common base class for all codec plugins * reduced quality level of sample rate converter from "best" to "medium", to improve speed * refactored playback handling (controller vs. plugin) * workaround for bug in Phonon: no device names available in first call to Phonon::BackendCapabilities::availableAudioOutputDevices() * moved playback test into worker thread, for better GUI responsiveness * Phonon playback: changed to own mainloop with timeout support to avoid application hang on broken audio devices * bugfix: data loss in sample rate converter when processing streams * vorbis encoder: call to deprecated API (now use OV_ECTL_RATEMANAGE2_SET) * using estimated length for streaming file formats without length info * made sample rate conversion (libsamplerate) mandantory * increased default memory sizes * added toolbar buttons for "File/SaveAs" and "File/Close" * reordered toolbars 0.8.9 [2012-11-06] * new feature: MP3 export via external program "lame", "toolame" and "twolame", with configurable command line options * new feature: allow change of compression type via file info * fix for SF #3528848, removed -Wl,--add-needed from plugin LINK_FLAGS * speedup: improved performance of sample writer * wav import/export: support for some more meta data tags * bugfix: meta data lost when writing wav files that had meta data for product/album or subject/track at the same time * bugfix: broken signal/slot connection in SaveBlocks plugin * workaround for bug in id3lib, SF #3534143: ignore id3lib crc check result for MPEG Layer II files * bugfix: Gentoo ebuild lacked required svg use flag for media-gfx/imagemagick and media-gfx/graphicsmagick * bugfix: File/SaveAs now uses last recently used directory and extension together with the user defined file name * bugfix: PluginManager::sync caused application slowdown or stale GUI 0.8.8 [2012-05-20] * new feature: seek functionality for playback * new feature: added toolbar with record/playback/scroll functions * migration to GIT as source code management * documentation update * allowing zoom and scroll while a plugin is running * allow "close" and "quit" while playback is running * allow track selection change during playback * fix for namespace collision with libaudiofile * bugfix: mouse selection update with negative offset failed * bugfix: wrong focus of progress dialog when repairing damaged wav files * bugfix: missing updates of zoom selection combo box * bugfix: when viewing with full zoom, scroll by 1 sample was possible * bugfix: focus was wrong on program start (zoom combo box) * bugfix: wrong view when moving slider of overview widget to negative value * bugfix: playback pointer did not disappear after play - pause - stop * bugfix: creating a label without text was not possible * bugfix: saving WAV with G.711 and non-16bits/sample produced broken output * bugfix: handling of shortened tracks in encoders 0.8.7 [2011-11-27] * ebuild update for media-gfx/imagemagick <-> media-gfx/graphicsmagick (see gentoo bug #314325) * new feature: "insert at", paste clipboard at given position * fix for API change in libaudiofile v0.3.1 * speedup: loading ogg/mp3 is much faster now (up to factor 2) * bugfix: stream name of pulse audio playback used wrong encoding * update of the Kwave spec file (synced with OpenSuSE build service version) * new build target "distfiles" * updated version of the GPL v2 document (GNU-LICENSE) * support for visualization plugins 0.8.6 [2011-03-07] * bugfix: copy/paste with partial track selection failed * bugfix: labels update after undo of copy&paste failed on multitrack signals * string/i18n update from Panagiotis Papadopoulos * bugfix: invocation of xgettext was wrong, left untranslated strings * plugin API change: support for translateable short description * about plugin: use plugin info from PluginManager * bugfix: last directory of file dialogs sometimes got lost * bugfix: wrong message when canceling Ogg import * replaced sched_yield() with QThread::yieldCurrentThread() * added cmake parameter for disabling optimized memcpy support -D WITH_OPTIMIZED_MEMCPY=OFF, default is ON * integrated patch #3021795 for Qt-4.7 compatibility * bugfix: optimized memcpy for PPC (SF bug #3068664) * doc: upgrade to DocBook XML V4.2 / V1.1 * build fixes for qt-4.7 * no longer using QSplashScreen (has side effects, operates as modal window) * bugfix: startup as unique application did not work correctly * bugfix: potential crash in message loop of progress dialog * bugfix: handling of track selection was wrong in reverse plugin * workaround for bug in libaudiofile: some files have sampe rate zero, falling back to 8000 samples/sec in that case (audio/x-ircam, sun, BE) * bugfix: reverse failed on files smaller than the internal block size * using entities for URLs in handbook, to simplify maintenance * bugfix in cmake files: some invocations of STREQUAL lacked quotes 0.8.5 [2009-12-24] * new feature: playback via PulseAudio * applied kwave-0.8.2-nolinguas.patch (see gentoo bug #267702) * support for the Gentoo build system that steals .po files * no longer default to english language for documentation and gui l10n * fixed use count mismatch of plugins * bugfix: playback control: continuing after pause continued from start * bugfix: G.711 encoded wav files support only 16 bit signed format * new assignment for mouse wheel: - without modifier key: scroll left/right - with Shift: page left/right - with Ctrl: zoom in/out - with Alt: vertical zoom in/out * bugfix: support sysinfo.mem_unit when >= 4GB RAM are installed * bugfix: crash in progress dialog handling (crashed when closing a plugin after finishing it's work) * new ebuild for Gentoo 0.8.4 [2009-09-26] * new feature: support for primitive macros (batch files), playback only * new plugin: change sample rate * using libsamplerate (new dependency) * new feature: sample rate conversion on clipboard data * new feature: ability to set recording start time in advance (feature requested by John David Thompson) * bugfix: drag&drop of files on the main window was broken * workaround for bug in id3lib which crashed in ID3_Tag::GetSize() with some MP3 files (see id3lib upstream bug at SF #2821464) * bugfix: recording via ALSA, crash on snd_pcm_close(), see SF bug #2816544 * bugfix: playback plugin: infinite loop when switching from OSS to ALSA * bugfix: forcing clipboard and drag&drop data to uncompressed mode * bugfix: deadlock in progress bar handling * bugfix: crash when unloading plugins with queued events * help/about dialog: hide "translators" tab if no translator available * help/about dialog: hack to allow web addresses of translators * bugfix: selection was not set after "paste" and undo of other operations * bugfix: label handling in context of "delete" and "undo" was broken * bugfix: invalidation of overview cache after delete was not correct * bugfix: artifacts in track display in min/max overview mode * bugfix: add/delete/modify of labels did not set the state of the current file to "modified" * bugfix: record dialog caused shutdown to hang when closed while recording * bugfix: decoding 32bit/sample was broken * bugfix: recording level meter consumed 100% cpu * new make target: "make apidoc" for internal doxygen documentation * bugfix: some images and icons in non-english documentation were missing * volume plugin: preview was not updated on first use of plugin 0.8.3-2 [2009-07-04] * bugfix: re-enabled detection of optimized memcpy function * bugfix: deadlock in recording plugin and plugin management (see SF bug #2816544) * bugfix: ID3 tag import did not work * taking ID3 tag for "album" as "product" in wav meta data * taking ID3 tag for "track" as "subject" in wav meta data 0.8.3 [2009-06-28] * integrated 05-do-not-install-so-symlinks.diff from Debian (thanks to Aurelien) * cs i18n update from Pavel Fric * new plugin: normalize * progress bar in volume plugin did not work * flattened "Fx" menu, no submenus for amplify and filter * bugfix: workaround for libaudiofile bug produced wrong header in 24bit/sample mode * bugfix: "fade outro" was broken * bugfix: the dialog when playing the test sound in the playback setup dialog did not appear * replaced qreal with double (fixes build problems on arm) * show hourglass / progress bar when undo/redo is running * flattened "Calculate" menu, no submenus for "Frequencies" * wav encoder: auto-switch to unsigned format for <= 8bit and signed format for > 8 bit per sample * volume plugin: show a little "preview" for guessing the level * bugfix: after deleting a track, file info was not updated * about plugin: separate tab for translators * made plugin API version configurable per plugin * recognize mime type "audio/x-vorbis+ogg" (found in KDE-4) * updated czech gui translation and user manual from Pavel Fric * bugfix: crashes when deleting objects that still have event queued with Qt::QueuedConnection -> now using Qt::BlockingQueuedConnection * new plugin: reverse * speedup: limiting the number of progress bar updates per second * memory manager: fixed multithreading issues, improved OOM behavior * bugfix: received SIGBUS in SwapFile when disk was full * improved performance of memory management * require Qt4 v4.5.0 or newer 0.8.2 [2009-04-25] * bugfix: minor off-by-one bug in buffer handling * wav/RIFF parser: be more robust if the file has not been correctly padded * bugfix in wav encoder: padding for info and label chunk was missing * bugfix: if two markers were too close and displayed at the same pixel position they eliminated each other through XOR mode * bugfix: numeric overflow when trying to select labels in high zoom factors * bugfix: not all positions were selectable due to internal rounding errors * silence plugin now supports all modes * use "unsigned" sample format per default when creating new files with <= 8 bits/sample * bugfix: playback position was shown on startup * bugfix: show correct file size in progress dialog * bugfix: crash when deleting label from end of signal * bugfix: overview was wrong when deleted space after signal was visible * bugfix: overview was not always synchronized after delete/insert * bugfix: "modified" state got lost during undo * use ALSA per default for playback/record if nothing has been selected yet * fixed calculation of undo/redo sizes * undo/redo handling for sample range and track selection * processing updates of overview widget in a background thread * memory management: no longer evaluate RLIMIT_RSS, gives more available physical memory * portability fix: swapfile creation/destruction went wrong * feature: memory for undo/redo can now be configured * bugfix: handling of "continue without undo" produced wrong undo/redo states and asked several times * bugfix: file progress did not do GUI updates, cancel button did not work * bugfix: assert in record plugin if no valid sample rate available * speedup for generation of signal overviews in min/max mode * bugfix: MultiTrackWriter produced one extra sample (off by one error) * workaround for bug in libaudiofile: sometimes libaudiofile produces broken files as it uses 'float' for internal calculations (wrong size of 'data' and 'RIFF' chunk) => see ubuntu bug #327018 * implemented "debug" plugin, with internal functions for test and verification (quality improvement) * added czech gui translation from Pavel Fric * bugfix: after creating a new empty file, "revert" was possible * speed optimizations in buffer handling * speedup: limiting the rate of progress updates when loading and saving files * fixed displayed names of actions based on the "amplifyfree" plugin * bugfix: menu entry translation did not work correctly * bugfix: deleteLater on menu nodes did not work, implemented own garbage collector * speedup: use different block sizes for interactive and non-interactive mode * i18n fix: texts in help/about menu were untranslated * about plugin: new tab for translators 0.8.1 [2008-12-23] * replaced application icon, now using a scalable svg image * replaced GSL with FFTW3, which is license compatible with Kwave * use implicit sharing for Label class * new clipboard implementation, using the clipboard of KDE (X11) * fixed enable/disable of copy/paste functions depending on clipboard state * re-enabled function "flush clipboard" * re-enabled function "invert track selection" * re-enabled function "select all tracks" * implemented plugin for "go to position..." * added status bar item for current cursor position * show current playback position in status bar * show selection in overview widget * bugfix: mode switch in time selection widget did not work properly * bugfix: handle situation when adding or moving a label to a location that is already occupied by another label * show labels in overview widget * show current playback position in overview widget * overview widget: dimming parts that are out of view * no longer needing built in copy of libaudiofile, removed 0.8.0 [2008-09-27] * ported to KDE4 / Qt4 * dropped support for FLAC API v1.1.1 and older * support for ALSA lib API v1.0.16 * made MP3 decoder disabled per default due to legal issues * fixed bug in cue list parsing of .wav files * fixed bugs in recording plugin, recorded too much if recording time limit was activated or in prerecording mode * a much nicer splash screen * bugfix in label handling: support labels with zero-length names * re-arranged source files for cleaner library interfaces * re-enabled accelerator keys for 0..9 * using horizontal scrollbar instead of overview widget * implemented vertical zoom (Ctrl + MouseWheelUp/Down) * using more standard KDE keyboard shortcuts * nicer icons for the menus * using more icons from the crystal icon collection (to clearify the license situation) * removed aRts support * now also available through the openSUSE build service for various platforms * respect the LINGUAS environment variable to build only needed languages (defaulting to all) * removed changelog from online manual to simplify the work of translators * no longer dependent from "recode" * recording plugin: show current recording time in status bar * fixed infinite loop on undo/redo of channel selection * usage of GSL can be disabled through cmake parameter -DWITH_GSL=OFF * support for OSS v4 (integrated sf feature request #1870434) 0.7.11 [2007-12-09] * new internal streaming architecture, based on Qt instead of aRts * aRts support is now disabled per default * some minor bugfixes for x86_64 support * band pass plugin 0.7.10 [2007-08-08] * build system: using 'METASOURCES=AUTO' (which simplifies a lot) * ported the build system to cmake * support for newer APIs of FLAC v1.1.3 and v1.1.4 (closes SF bugs #1713655 and #1757716 + debian bugs #427747, #426668 and #431199) * replaced problematic code in libaudiofile with new code under the LGPL, contributed by Bertrand Songis (partially fixes debian bug #419124) * update of the online documentation to reflect the change of the make system 0.7.9 [2007-05-01] * playback via ALSA: offer the "default" device, if no devices found offer the "null" device * implemented import and export of labels, currently only for uncompressed wav files * new plugin for saving blocks between labels as separate files * new function: expand selection to labels * new function: select next/previous range between labels * bugfix: don't change the file name when saving only the selection * new configure option: --enable-doc=yes/no to enable/disable the generation of the online documentation (default=yes) 0.7.8 [2006-12-31] * bugfix: workaround for bug in ALSA, crashed when initializing the dsnoop plugin * bugfix: error in swap file handling, one sample was destroyed when resizing. Affects cut, delete, crop and many other functions. * fixed the incorrect usage of the word "loose" (thanks to J.T. Hundley) * bugfix: went back to old implementation of ThreadsafeX11Guard class in order to fix a deadlock (closes sourceforge bug #1623357) * documentation update: mention Subversion instead of CVS * zero plugin: new mode, support for inserting a range filled with silence * fixed the macro functions "Fade Leadin" and "Fade Leadout", using the new mode of the 'zero' plugin * export of ASCII format files 0.7.7 [2006-09-17] * new feature: implemented a small widget that shows the current selection position and the selection borders * new feature: context menu for the signal widget (right mouse button) * improved file open dialog: show "All Files" and "All Supported Files" * bugfix: error in handling of mouse selection * bugfix: recording only used the first channel (closes sourceforge bug #1551050) * install plugins kde_moduledir/plugins/kwave instead of kde_datadir/kwave/plugins 0.7.6 [2006-06-05] * bugfix: recording setup crashed when called for the first time * bugfix: do no longer crash when recording device is not present or opening failed * bugfix: fixed generation of rpm dependency for libmad * bugfix: update the size of the level meter if the dialog size has changed * record plugin: added a fancy status bar * record plugin: added autodetect/scanning for OSS devices * record plugin: added dsnoop plugin as ALSA source * record plugin: fewer annoying message boxes, instead show a short notice in the status bar for some seconds * record plugin: add logarithmic scale to the level meter and use 3 colors * playback plugin: added autodetect/scanning for OSS devices * playback plugin / ALSA: support for 18 and 20 bits/sample * playback plugin / ALSA: support for big endian 0.7.5 [2005-12-31] * draw signal in a different color set when not selected * bugfix: solved deadlock situation when starting a plugin while another plugin was still running * workaround for deadlock when trying to close the current file while a plugin is still running * bugfix: delete range only in selected tracks * thrown over board the idea of using gstreamer due to serious license issues, we will wait until KDEMM is out (KDE-4) instead. 0.7.4 [2005-10-16] * recording via ALSA * support for the silently changed API of libFLAC++ v1.1.2 (closes sourceforge bug #1243707 + debian bug #289953) * fixed support of MMX / SSE detection on X64_64 architecture (closes sourceforge bug #1244320 and debian bugs #288781 + #327501) * decided to support gstreamer as streaming engine in future versions (will make v0.8 if Kwave is aRts-free) * fixed some German translations (closes debian bug #313790 and bug #314000) 0.7.3 [2005-05-26] * playback via ALSA * completely new playback settings dialog, with support for aRts, ALSA and OSS * playback plugin: play a test sound * record plugin: detect when device is alread open, now no longer blocks. Show an error message. * smoother signal display in overview mode (no gaps) and improved polyline mode * replaced some of Kwave's multithreading classes with classes from Qt * compiles under SuSE-9.3 * ebuild file for Gentoo Linux 0.7.2 [2004-12-31] * big rework of the internal streaming/storage subsystem, support for multiple stripes. Makes a big speedup when handling large files! Creating an empty 512MB file before: over 350 sec, now: about 25 sec (on my system) * optimized versions of memcpy() for ix86 (using MMX, MMXEXT, 3DNOW, SSE, SSE2) and for PowerPC, copied from the xine project * some support for X86_64 * bugfix: in memory setup plugin, set virtual memory limit only if the limit has been enabled (checkbox is clicked) * bugfix: clipping in Ogg import filter was incorrect * speedups: import of Ogg and MP3 files improved * removed code copied from the GSL library, link against the shared library instead * added target "package-messages" to the toplevel Makefile, for translators 0.7.1 [2004-07-10] * FLAC (Free Lossness Audio Codec) import/export plugin * speedups for loading / saving files * removed our own copy of libmad from the source tree, now it should be available in all common distributions * implemented pre-recording * implemented recording time limit * bugfix: minor bug in the recording state machine * bugfix: solved some layout issues in the about- and sonagram plugins * bugfix: cancel while saving to .ogg works now * update of the online documentation, many screenshots * improved Makefile dependencies of the plugins, now parallel builds also work and speed up the creation of plugins 0.7.0 [2003-12-01] * first version with recording functionality (still alpha) * removed workaround for uic invocation * bugfix: handling of persistent and unique plugins was wrong, which caused playback to work only in the first main window instance * added project files for kdevelop-3 0.6.7 [2003-06-28] * new plugin: "pitch_shift" * new plugin: "lowpass" * new plugin: "notch_filter", contrinuted by Dave Flogeras * included a bugfixed version of the synth_pitch_shift aRts plugin * new feature: "pre-listen", first implementation in pitch_shift plugin * ported to work with Qt-3.1 without Qt-2 compatibility, also compiles with -DQT_NO_COMPAT -DQT_CLEAN_NAMESPACE 0.6.6 [2003-03-29] * works with KDE-3.1 * many improvements on the build system. Now compiles under Debian, Mandrake, RedHat, Gentoo and SuSE * starting up with last window size * Xt toolkit option for geometry works again, including workaround for bug in KDE3's geometry management. example: "kwave -geometry 800x600" * bugfix: select to left selected one sample less then needed * volume plugin: simple clipping * volume plugin: mode for "multiply with /divide through factor" * newsignal and selectrange plugin: got rid of KDoubleNumInput and it's weird display and entry behavior * selectrange plugin: also select start position of selection * can use libmad and libaudiofile from the host system if usable * show the fileinfo plugin when saving under a different mime type 0.6.5 [2002-11-09] * MP3 import with ID3 tag support through id3lib and libmad * Ogg/Vorbis import and export (only ABR mode) * new plugin "volume" * show selected range as time (feature requested by Christian Hollaender) * support for saving compressed .wav files * thrown away Qt2/KDE2 compatibility, now only supports Qt3/KDE3 * playback plugin: enabled the "select..." button for choosing other playback devices (feature requested by Len Ovens) * solved problem with name mangling in plugins and different gcc versions * works with gcc-3.2 / solved __dso_handle problem * stricter checks for programs in configure script 0.6.4 [2002-06-30] * support for different file formats / integrated libaudiofile * drag and drop bugfix: dropping into the same signal left from the selection removed wrong range * auto-repair for structurally damaged wav files * bugfix: save selection works again * integrated libkwavemt into libkwave * using time instead of zoom factor, e.g. set zoom to "1 minute" (feature requested by Gilles Caulier) * menu entries for playback control * some more icons in the menus * replaced KFileDialog with subclass KwaveFileDialog (works around some bugs in KDE) * added a little chapter about digital audio basics to online help * added "select range" plugin 0.6.3 [2002-03-01] * simple drag and drop * french translation * handling of "signal modified" * shows error message and aborts if loading failed 0.6.2 [2001-12-24] * new plugin "amplifyfree" * new plugin "noise" * >>> new aRts plugin adapter framework <<< now Kwave is able to use existing aRts plugins in it's own plugins for sound processing * changed documentation to XML / Docbook-4.1 * recovery of damaged files if non-zero file length but data length entry in the wav header is zero (e.g. happens when krecord crashes during recording) * bugfix: freeing virtual memory fixed in MemoryManager * bugfix: problem with TSS in TSS_Object cleanup 0.6.1-1 [2001-09-01] * bugfix: class Track made duplicate entry in stripe list when inserting signals into an empty track * fixed that weird layout behavior in dialogs, seems that Qt has problems with complex nested layouts :( 0.6.1 [2001-08-24] * >>> USE OF VIRTUAL MEMORY <<< * changed Makefiles: html docu stays in distribution due to too much trouble with the KDE documentation tools * when inserting from clipboard into a signal with a different number of tracks, the result will be mixed (still not optimized/slow) * fixed compile problem with gcc-2.96 / gcc-3.0 * fixed missing header file in NewSigDlg.ui * the RPM should be relocatable again * fixed bug in shutdown sequence, now clipboard is flushed before the application closes. 0.6.0 [2001-07-29] * >>> PORTED TO QT-2 AND KDE2 <<< * completely new internal architecture * plugins can be located in a user directory * libkwave is included and no longer supported (at least by me) as a separate package * playback via aRts * many more bugfixes, too many to mention here... 0.5.5-1 [2001-02-23] * bugfix: selection across end of file no longer possible * bugfix: no overflow in wav header when saving large wav files above 268MB (bug reported by Sven-Steffen Arndt, ssa29@gmx.de ) 0.5.5 [2000-12-01] * new playback handling, allows pause/continue * limited the playback buffer to be between 256 and 65536 bytes due to problems (system hang) with small playback buffers with 16..64 bytes (might be a hardware problem) * introduced a toolbar for some standard operations * fixed some bugs concerning selection with the mouse * rework of the overview widget (used in main window and sonagram) * fixed menu command "zoom selection" * sonagram: saving to file, auto-brightness adjust * replaced QFileDialog with KFileDialog * tested with AMD Athlon-optimized compiler (patched pegcs) * some fixes for safer multithreading * checking for much more header files at configure time (due to a problem reported by issiac@evcom.net ) 0.5.4-4 [2000-10-03] * added classes Mutex and MutexGuard * sonagram: set a transparent background for the image * added sizeHint() and minimumSize() to ScaleWidget and OverViewWidget * sonagram: removed (need for) SonagramContainer, using QGridLayout instead * moved SignalProxy to the mt subdirectory * fixed X11 synchronization problem with SignalProxy * added TSS (thread-specific-storage) support to the mt classes * added some multithreading support classes: Thread, AsynchObject, ... * removed the "get" prefix from all member functions. This is the new KDE/QT coding style. * updated the online documentation to point to the new Kwave homepage on http://kwave.sourceforge.net/ * class ImageView: always repaints (maybe image data has changed) * bugfix: selection and playpointer will not be drawn if no signal is loaded 0.5.4-3 [2000-09-09] * the sonagram window updates it's title if the signal's name changed * found a solution for the problem of synchronizing X11 and QT in a multithreaded environment * fixed bug in the "Halt" function (playback) 0.5.4-2 [2000-08-20] * geometry/layout management for the MainWidget * limited the displayed height of a signal. If not all signals fit onto the screen, a scrollbar appears on the right side of the signal. * limited the size of the TopWidget to a reasonable minimum size * automatic dependencies for the plugins work again 0.5.4-1 [2000-07-29] * fixed layout of playback dialog * started to implement a new plugin interface * geometry/layout management for the sonagram settings dialog * formatting of selection and file time (KwavePlugin::ms2string) * plugins can now consist of multiple source files 0.5.4 [2000-07-12] * some more minor changes to the Makefiles * split the documentation output into "de" and "en" part * made symbolic links to the english help directory from the "de" and "default" directory during make install and uninstall and also in the post and postun scripts of the specfile The user should at least get the english help... * alpha version of english documentation done * automatic update of the revision history in the docbook file if this file is modified (only english version) * CVS is up on sourceforge.net * changed some header lines in this file * started on writing a new documentation / online help using docbook 0.5.3 [2000-06-12] * if a file with invalid size (e.g. recorded by "arecord") is loaded, shows a message and truncates the input at the end of the file * found out that we need ALSA support for 24 and 32 bits/sample * >>> playback in stereo <<< * selected channels (x) are mixed to the output device's channels (y) at playback using a x:y translation matrix with linear scaling, all values for x and y except zero are allowed * playback only for selected channels * rework of the settings/playback dialog (plugin) * heavy reword on the playback code * fixed severe bug in SignalManager::readWavChunk(), chrashed if there was data after the wav chunk 0.5.2-12 [2000-06-02] * copied the AsyncSync class into libgui, should be used for threadsafe usage of the qt library * moved handling of the "selected" flag from class SignalManager to class Signal * fixed selection of channels if appended or deleted 0.5.2-11 [2000-05-28] * included config.h in each source file (except the plugins) * export to ASCII for multi-channel signal (multi-channel import has still to be done, currently only mono) * fixed many memory leaks and inconsistent delete operations (e.g. used "delete" instead of "delete[]") * included support for (and tested with) the error detection and memory debugging tool "Insure++ Lite 4.1" (./configure --enable-insure=yes ...) -> thanks to ParaSoft Corporation for making this limited version of the tool available (http://www.parasoft.com) * SignalWidget uses three layers for drawing, speeds up the redraws after mouse selection by about factor 14(!!!) on my system :-)) 0.5.2-10 [2000-05-21] * some minor bugfixes in the Makefiles * save the kwave.spec and include it into the source archive, this lets "rpm -ta kwave-x.x.x-x.tar.gz" work * wrote a new README file, moved Martin's version to README.OLD * RPM_OPT_FLAGS are appended to the compiler options, this lets pentium optimizations work :-) * shows a message box if loading of a file failed 0.5.2-9 [2000-05-19] * list of recent files is synchronized across all toplevel windows * fixed dozens of memory leaks, missing ASSERT constructions, missing variable initializations and possible divisions through zero * Help menu aligned to the right side (MenuRoot now is able to process the special command "#separator") 0.5.2-8 [2000-05-18] * replaced all occurances of sprintf with snprintf, strcpy with strncpy (in 92 places) ! * doesn't show any zoom factor if no signal is loaded * handling of channel add/delete: selection/speakers are shifted * changed some variables/parameters to "unsigned" (simplifies range checks) * beautified this file * beautified the whole source code according to my favorite coding style. -> thanks to the developers of the "Artistic Style" package, astyle-1.11.4-1 made good work :-) * fixed that annoying flicker in the help/about dialog * checking for sizes of char, short and int at configure time * globals.app will not be used (obsolete, should be removed from libkwave) * MessagePort will not be used (obsolete, should be removed from libkwave) * multiple toplevel windows are possible * made X toolkit parameters work (especially "-geometry") * bugfix concerning loading/saving 8 bit .wav-files (always unsigned !) * >>> COMPLETE REWORK OF THE INTERNAL COMMAND STRUCTURE <<< - made use of a combination of signals/slots and string messages - hierarchical processing: commands are are forwarded "upwards" until they reach a TopWidget - the TopWidget (highest level) dispatches the commands and forwards them to the lower levels 0.5.2 [2000-04-24] * rpm package should now be installable without conflicts and compile without any previous installation of kwave * removed the "${KDEDIR}/share/doc/HTML/default" directory from the rpm so that it doesn't conflict with the already existing one * shift+Home/shift+End selects from the current left/right position up to the start/end of the signal * bugfix in display of signal: signal is no longer inverted * selectrange() works now * the zoom factor combo box reflects the current "real" zoom factor * some bugfixes in menu handling / cleanups * complete rework of zoom and offset handling: - simple poly-lines instead of lowpass interpolation if zoom factor has less than 10 pixels per sample - lowpass interpolation if more than 10 pixels per sample * bugfix in KWaveApp: now sets globals.app to this if it was null before, now doesn't crash if it loads a file specified at cmdline 0.5.1-4 [2000-04-16] * >>> now compiles and runs under RedHat 6.1 / Halloween IV <<< >>> as well as under SuSE 6.2 <<< * version info of libkwavegui.so is set to the package's version * bugfix in plugins/template/Makefile.am: will not create .moc files on make distclean and other targets * compiler flags are passed through to plugin compilation * compiling with --no-rtti. This was necessary to compile against the kde libraries of RedHat that seem to contain no rtti. As a side effect all warnings on linking programs/libs disappeared :-) * configure-parameter --enable-debug has effect again * >>> ASCII import and export works now (mono only) <<< * bugfixes in some plugins, all compile now without warnings/errors * plugins are processed in alphabetical order * all plugins are automatically found and compiled * new target "make src.rpm" makes only the source rpm 0.5.1-3 [2000-04-03] * display will be scrolled left or zoomed if something from the end of the signal is deleted * curve parameters of fade in / fade out work again 0.5.1-2 [2000-03-16] * converted many "klocale->translate(...)"s into "i18n(...)" * target "make messages" works again * converter for menus.config, creates a dummy .cpp-file that is handled by i18n 0.5.1-1 [2000-03-13] * new target "make rpm" creates binary and source RPM packages * fixed the shared-library-problem in the build system 0.5.1 [2000-02-28] * menu items can belong to groups * renamed SignalWidget::info to "refresh" * many cleanups in the header-Files in src and libgui * the menu management has completely been rewritten: - Menu, NumberedMenu and MenuCommand classes are deleted - new classes: MenuNode, MenuItem, MenuSub, MenuToplevel and MenuRoot * menu items can have icons * menu nodes can have unique string ids * special menu commands start with a "#" * first attempts for internationalization * list of recent files is sorted by time of last usage 0.5.0-1 [1999-12-27] * moved my modifications from the Makefiles to Makefile.am * included targets "make release", "make patchlevel" and some scripts 1999-12-19 (by Thomas.Eschenbacher@gmx.de) * remade my modifications of some Makefiles and of the configure script that were lost during Martin's changes * changed shortcut for mixpaste from CTRL-SHIFT-X to CTRL-SHIFT-C * added the "crop" command to the edit menu * made the "mixpaste" command work * corrected the call of "delete", now really deletes instead of cutting and copying the selection to the clipboard (saves clipboard content) 1999-12-18 (by Martin Wilz ) * changed filenames to reflect class names * one class per file is now the standard * stripped leading "Kwave" in class names for most classes 1999-12-10 (by Thomas.Eschenbacher@gmx.de) * removed -Werror compiler option (caused trouble with configure script) * fixed a nasty bug in SignalManager::save that caused crashes on several positions in the program and libstdc++ 1999-12-09 (by Thomas.Eschenbacher@gmx.de) * gave the destructor of SignalManager some code, this fixes a huge memory leak ! * added a TODO file * added -Werror to the c++ compiler options when debugging enabled * some include file cleanups in libgui 1999-12-07 (by Thomas.Eschenbacher@gmx.de) * SignalManager::writeWavChunk uses buffers for writing (much faster!) * bugfix: caption of main window changes after "SaveAs" * bugfix: selected resolution takes effect * bugfix: caption of main window changes after "SaveAs" * bugfix: SignalManager::writeWavChunk now doesn't destroy the signal's data while saving * make distclean in the projekt root directory also removes zero-length files, *.orig, *.rej and *~ (just makes cleaner than before) * symbolic links to Makefile, Makefile.in and Makefile.am in the plugins directory are deleted with "make distclean" and rebuilt on "make" (changes in the referenced Makefiles in the template directory will not be reflected thousand times when creating a patch with diff) * cleanups, removed some old backup files * improved support for debugging accessible through * "configure --enable-debug=yes" (-g and -DDEBUG compiler flags) * rewritten big parts of Makefile.in for the plugins * added -O2 compiler optimization 1999-12-03 (by Thomas.Eschenbacher@gmx.de) * merged with Martin Wilz's version 1999-11-12 (by Martin Wilz ) * version numbering script donated by Thomas Eschenbacher * now using KTMainwindow for top level widgets * using timer to check message port -> alternative (threadsafe) message passing instead of signal/slot * labeling code rewritten, now incompatible with old releases * batch loading routines 1999-09-07 (by Martin Wilz ) * deleting channels works again * fixed savelabel dialog * corrected envelope dialog, string handling * connected many functions via the new string-based way of invocation * fixed some dependencies between old code and new classes (there's still more to do !) * saving should now work again * trimmed down Clipboard class * Color class as wrapper to QColor (may become independent later) * reworking Curve Class for creation via string, interpolation now uses curve objects * moved gui functions into a new library (libkwavegui) * new Classes: Parser DynamicLoader Filter (previously was a struct) * moved gui-independent functions into a library (libkwave) * rework of dialogs into single files and single plugins * new calling scheme via string commands. This will allow scripting and macro definitions. Threading is made a whole lot easier, because a only a string has to be passed and gets expanded into the needed set of parameters in each thread * io functions and playback adapted to SignalManager * Introduction of SignalManager class for multiple channel management 0.29.5 [1998-12-25] (by Martin Wilz ) * Just a fix for an annoying bug while zooming out 0.29.4 [1998-12-22] (by Martin Wilz ) * removed a memory leak in playback * triple-checked the missing Accelerators in the Menumanager. It seems to be a bug in qpopmenu, I'll wait for qt to be fixed, or until a workaround is known * recent Files are updated again, while the program is running * kwave now keeps track of last directory saved to; for user convenience * reestablished selection mechanism to match versions before 0.29.3 and corrected some otherwise screwed-up behavior * _now_ all parameters to destructors should have vanished 0.29.3 [1998-12-18] (by Martin Wilz ) * developement has slowed down a bit, this release is not as complete as I wished but since 0.29.2 has a destructor with parameters accidentally left in, and so does not compile on all systems -> here we go again... * reworked selection routines into a new class, code is still rather confusing, but seems to work * added checkmark functionality again * converted file-menus to new menu-scheme * added gui to mix channels together, the needed functions for mixing are still missing * Halt button by Gerhard Zintel 0.29.2 [1998-12-15] (by Martin Wilz ) * moved clipboard functionality to its own class * dynamic allocation of menu entries used by all classes but TopWidget * import of ascii data files 0.29.1 [1998-12-13] (by Martin Wilz ) * Bugfix for multichannel save. Now the saved files should work with other prgs * local snap to peak by Gerhard Zintel 0.29.0 [1998-11-12] (by Martin Wilz ) * changed version numbering and filename as suggested by Version 1.1 of "How To Name Things" from sunsite. The last digit will always mark be 0 for releases uploaded to ftp.kde.org. * added windowing (Hamming Hanning, Blackmann) functions * contribution from Gerhard Zintel displaying notes in fftview * Added pitch display window * implemented cursor and db scale for fft-view * added reselection and cursor change * added possibility of different display modes in Frequency representation * fixed severe quantization bug in saving 16Bit routine, reported by ? * mmaping support contributed by Juhana Kouhia * added as new Possibility to generate Signals: pulse trains * added wrapper for systems with no posixthreads -> still needs handling by configure script (change Makefile for not linking libpthread and doing a define) * First use of multiple threads (pthreads) in some functions * Pitch generation now independent of additive synthesis * added import function for ascii files * Ascii label saving now also by frequency * Label generation according to Period Detection (autocorellation) * Sonagram, FFTView and Distortion-Dialog now use ScaleWidgets * New ScaleWidget gives the user more information * Improved ProgressDialog and Interpolation class to allow multiple threads * fixed some minor bugs 0.28 [1998-07-15] (by Martin Wilz ) * changes in curvewidget (recent point has another pixmap). * sorting of labels now works without overwriting of QGList::compareItems. * bug fix for saving selection. * save Block function added. * signal finding function in markers.cpp: gui improved. * some smaller bug fixes. 0.27 [1998-07-14] (by Martin Wilz ) * first release, but never uploaded, because ftp.kde.org was down. diff --git a/libgui/SelectionItem.cpp b/libgui/SelectionItem.cpp index 93f21456..b7c67711 100644 --- a/libgui/SelectionItem.cpp +++ b/libgui/SelectionItem.cpp @@ -1,118 +1,117 @@ /*************************************************************************** * SelectionItem.cpp - selection item within a SignalView * ------------------- * begin : Sun Mar 12 2017 * copyright : (C) 2017 by Thomas Eschenbacher * email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include "libkwave/Drag.h" #include "libkwave/MultiTrackReader.h" #include "libkwave/SignalManager.h" #include "libkwave/String.h" #include "libkwave/Utils.h" #include "libkwave/undo/UndoTransactionGuard.h" #include "libgui/SelectionItem.h" #include "libgui/SignalView.h" //*************************************************************************** Kwave::SelectionItem::SelectionItem( Kwave::SignalView &view, Kwave::SignalManager &signal_manager ) :Kwave::ViewItem(view, signal_manager), m_first(signal_manager.selection().first()), m_last(signal_manager.selection().last()) { } //*************************************************************************** Kwave::SelectionItem::~SelectionItem() { } //*************************************************************************** Kwave::ViewItem::Flags Kwave::SelectionItem::flags() const { return Kwave::ViewItem::CanDragAndDrop; } //*************************************************************************** void Kwave::SelectionItem::startDragging() { const sample_index_t length = m_signal_manager.selection().length(); if (!length) return; Kwave::Drag *d = new(std::nothrow) Kwave::Drag(&m_view); Q_ASSERT(d); if (!d) return; const sample_index_t first = m_signal_manager.selection().first(); const sample_index_t last = m_signal_manager.selection().last(); const double rate = m_signal_manager.rate(); const unsigned int bits = m_signal_manager.bits(); Kwave::MultiTrackReader src(Kwave::SinglePassForward, m_signal_manager, m_signal_manager.selectedTracks(), first, last); // create the file info - Kwave::MetaDataList meta = m_signal_manager.metaData().selectByTracks( - m_signal_manager.selectedTracks()); + Kwave::MetaDataList meta = m_signal_manager.metaData(); Kwave::FileInfo info(meta); info.setLength(last - first + 1); info.setRate(rate); info.setBits(bits); info.setTracks(src.tracks()); meta.replace(Kwave::MetaDataList(info)); if (!d->encode(&m_view, src, meta)) { delete d; return; } // start drag&drop, mode is determined automatically Kwave::UndoTransactionGuard undo(m_signal_manager, i18n("Drag and Drop")); Qt::DropAction drop = d->exec(Qt::CopyAction | Qt::MoveAction); if (drop == Qt::MoveAction) { // deleting also affects the selection ! const sample_index_t f = m_signal_manager.selection().first(); const sample_index_t l = m_signal_manager.selection().last(); const sample_index_t len = l - f + 1; // special case: when dropping into the same widget, before // the previous selection, the previous range has already // been moved to the right ! sample_index_t src_pos = first; SignalView *target = qobject_cast(d->target()); if ( (f < src_pos) && target && (target->signalManager() == m_view.signalManager()) ) { src_pos += len; } m_signal_manager.deleteRange(src_pos, len, m_signal_manager.selectedTracks()); // restore the new selection m_signal_manager.selectRange((first < f) ? (f - len) : f, len); } } //*************************************************************************** //*************************************************************************** diff --git a/libkwave/MetaData.cpp b/libkwave/MetaData.cpp index 7affbdbb..3895ef46 100644 --- a/libkwave/MetaData.cpp +++ b/libkwave/MetaData.cpp @@ -1,323 +1,270 @@ /*************************************************************************** MetaData.cpp - base class for associated meta data ------------------- begin : Sat Jan 23 2010 copyright : (C) 2010 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include "libkwave/MetaData.h" #include "libkwave/String.h" //*************************************************************************** // initializers of the standard property names const QString Kwave::MetaData::STDPROP_TYPE( _("STDPROP_TYPE")); -const QString Kwave::MetaData::STDPROP_TRACKS( _("STDPROP_TRACKS")); -const QString Kwave::MetaData::STDPROP_START( _("STDPROP_START")); -const QString Kwave::MetaData::STDPROP_END( _("STDPROP_END")); const QString Kwave::MetaData::STDPROP_POS( _("STDPROP_POS")); const QString Kwave::MetaData::STDPROP_DESCRIPTION(_("STDPROP_DESCRIPTION")); //*************************************************************************** Kwave::MetaData::MetaData() :m_data(Q_NULLPTR) { } //*************************************************************************** Kwave::MetaData::MetaData(const Kwave::MetaData &other) :m_data(other.m_data) { } //*************************************************************************** Kwave::MetaData::MetaData(Scope scope) :m_data(new MetaDataPriv) { setScope(scope); } //*************************************************************************** Kwave::MetaData::~MetaData() { m_data = Q_NULLPTR; } //*************************************************************************** void Kwave::MetaData::clear() { if (m_data) m_data->m_properties.clear(); } //*************************************************************************** bool Kwave::MetaData::isNull() const { return (!m_data || (m_data->m_properties.isEmpty())); } //*************************************************************************** QString Kwave::MetaData::id() const { return (m_data) ? m_data->m_id : QString(); } //*************************************************************************** Kwave::MetaData::Scope Kwave::MetaData::scope() const { return (m_data) ? m_data->m_scope : None; } //*************************************************************************** void Kwave::MetaData::setScope(Kwave::MetaData::Scope scope) { if (m_data) m_data->m_scope = scope; } //*************************************************************************** void Kwave::MetaData::setProperty(const QString &p, const QVariant &value) { if (m_data) { if (value.isValid()) m_data->m_properties[p] = value; else m_data->m_properties.remove(p); } } //*************************************************************************** bool Kwave::MetaData::hasProperty(const QString &property) const { return (m_data && m_data->m_properties.contains(property)); } //*************************************************************************** QVariant Kwave::MetaData::property(const QString &p) const { if (m_data && m_data->m_properties.contains(p)) return m_data->m_properties[p]; else return QVariant(); } //*************************************************************************** QVariant &Kwave::MetaData::property(const QString &p) { if (m_data && m_data->m_properties.contains(p)) return m_data->m_properties[p]; else { static QVariant dummy; dummy.clear(); return dummy; } } //*************************************************************************** bool Kwave::MetaData::operator == (const Kwave::MetaData &other) const { if (!m_data && !other.m_data) return true; // both are null objects if ((!m_data) ^ (!other.m_data)) return false; // only one is a null object if (m_data->m_scope != other.m_data->m_scope) return false; // different scope if (m_data->m_properties != other.m_data->m_properties) return false; // properties differ return true; // no mismatch found } //*************************************************************************** QStringList Kwave::MetaData::keys() const { return (m_data) ? m_data->m_properties.keys() : QStringList(); } //*************************************************************************** QStringList Kwave::MetaData::positionBoundPropertyNames() { QStringList list; - list << Kwave::MetaData::STDPROP_START; - list << Kwave::MetaData::STDPROP_END; list << Kwave::MetaData::STDPROP_POS; return list; } //*************************************************************************** sample_index_t Kwave::MetaData::firstSample() const { // single position? if ((scope() & Kwave::MetaData::Position) && hasProperty(Kwave::MetaData::STDPROP_POS)) { bool ok = false; sample_index_t pos = property(Kwave::MetaData::STDPROP_POS).toULongLong(&ok); if (ok) return pos; } - // start sample index given - if ((scope() & Kwave::MetaData::Range) && - hasProperty(Kwave::MetaData::STDPROP_START)) { - bool ok = false; - sample_index_t start = static_cast( - property(Kwave::MetaData::STDPROP_START).toULongLong(&ok)); - if (ok) return start; - } - // fallback: start at zero return 0; } //*************************************************************************** sample_index_t Kwave::MetaData::lastSample() const { // single position? if ((scope() & Kwave::MetaData::Position) && hasProperty(Kwave::MetaData::STDPROP_POS)) { bool ok = false; sample_index_t pos = property(Kwave::MetaData::STDPROP_POS).toULongLong(&ok); if (ok) return pos; } - // bound to a scope - if (scope() & Kwave::MetaData::Range) { - // end sample index given - if (hasProperty(Kwave::MetaData::STDPROP_END)) { - bool ok = false; - sample_index_t end = static_cast( - property(Kwave::MetaData::STDPROP_END).toULongLong(&ok)); - if (ok) return end; - } - - // fallback: no end => use start - if (hasProperty(Kwave::MetaData::STDPROP_START)) { - bool ok = false; - sample_index_t start = static_cast( - property(Kwave::MetaData::STDPROP_START).toULongLong(&ok)); - if (ok) return start; - } - } - // fallback: infinite return SAMPLE_INDEX_MAX; } -//*************************************************************************** -QList Kwave::MetaData::boundTracks() const -{ - QList tracks; - if (hasProperty(Kwave::MetaData::STDPROP_TRACKS)) { - const QList v_track_list = - property(Kwave::MetaData::STDPROP_TRACKS).toList(); - foreach (const QVariant &v, v_track_list) { - bool ok = false; - unsigned int t = v.toUInt(&ok); - if (ok) tracks += t; - } - } - return tracks; -} - //*************************************************************************** void Kwave::MetaData::dump() const { QString scope_list; const Scope s = scope(); - if (s == All) - scope_list = _("all"); - else { - if (s & Signal) scope_list += _(" signal"); - if (s & Track) scope_list += _(" track"); - if (s & Range) scope_list += _(" range"); - if (s & Position) scope_list += _(" position"); - } + + if (s & Signal) scope_list += _(" signal"); + if (s & Position) scope_list += _(" position"); + qDebug(" scope =%s", DBG(scope_list)); const QStringList props = keys(); foreach (const QString &p, props) { QVariant prop = property(p); const QList v_vals = prop.toList(); QString value; if (!v_vals.isEmpty()) { foreach (QVariant v, v_vals) value += _("{") + v.toString() + _("'} "); } else { value += _("'") + prop.toString() + _("'"); } qDebug(" '%s' = %s", DBG(p), DBG(value)); } } //*************************************************************************** //*************************************************************************** /** static initializer: counter for unique id generation */ quint64 Kwave::MetaData::MetaDataPriv::m_id_counter = 0; /** static initializer: mutex for protecting the id generator */ QMutex Kwave::MetaData::MetaDataPriv::m_id_lock; //*************************************************************************** Kwave::MetaData::MetaDataPriv::MetaDataPriv() :QSharedData(), m_id(newUid()), m_scope(), m_properties() { } //*************************************************************************** Kwave::MetaData::MetaDataPriv::MetaDataPriv(const MetaDataPriv &other) :QSharedData(), m_id(other.m_id), m_scope(other.m_scope), m_properties(other.m_properties) { } //*************************************************************************** Kwave::MetaData::MetaDataPriv::~MetaDataPriv() { } //*************************************************************************** QString Kwave::MetaData::MetaDataPriv::newUid() { // create a new unique ID: // <64 bit number> - - - QMutexLocker _lock(&m_id_lock); QString uid; uid += QString::number(++m_id_counter, 16); uid += _("-"); uid += QDateTime::currentDateTime().toString(Qt::ISODate); uid += _("-"); uid += qApp->sessionKey(); uid += _("-"); uid += QUuid::createUuid().toString(); return uid; } //*************************************************************************** //*************************************************************************** diff --git a/libkwave/MetaData.h b/libkwave/MetaData.h index 0fe7ed52..0aab857a 100644 --- a/libkwave/MetaData.h +++ b/libkwave/MetaData.h @@ -1,257 +1,225 @@ /*************************************************************************** MetaData.h - base class for associated meta data ------------------- begin : Sat Jan 23 2010 copyright : (C) 2010 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef META_DATA_H #define META_DATA_H #include "config.h" #include #include #include #include #include #include #include #include #include #include "libkwave/Sample.h" namespace Kwave { class Q_DECL_EXPORT MetaData { public: /** standard property: type of the meta data object */ static const QString STDPROP_TYPE; - /** standard property: list of zero based track indices */ - static const QString STDPROP_TRACKS; - - /** standard property: start sample index (inclusive) */ - static const QString STDPROP_START; - - /** standard property: end sample index (inclusive) */ - static const QString STDPROP_END; - /** standard property: position [zero based sample index] */ static const QString STDPROP_POS; /** standard property: description (string) */ static const QString STDPROP_DESCRIPTION; typedef enum { /** no scope */ None = 0, /** whole signal */ Signal = (1 << 0), - /** - * bound to a (list of) tracks, requires the property - * "STDPROP_TRACKS" with a QVariant::List of track indices - * encoded as QVariant::UInt - */ - Track = (1 << 1), - - /** - * bound to a range of samples, requires the properties - * "STDPROP_START" and "STDPROP_END" with data type - * QVariant::ULongLong - */ - Range = (1 << 2), - /** * bound to a single sample, requires the property * "STDPROP_POS" with data type "QVariant::ULongLong" */ - Position = (1 << 3), - - /** can be used for selecting all the scopes above */ - All = ~0 + Position = (1 << 2), } Scope; /** List of metadata properties */ typedef QMap PropertyList; /** * default constructor, generates a metadata object * with a new ID */ MetaData(); /** * copy constructor * @param other the other meta data object to copy from */ MetaData(const MetaData &other); /** constructor */ explicit MetaData(Scope scope); /** destructor */ virtual ~MetaData(); /** removes all properties */ virtual void clear(); /** returns true if this is an empty record */ virtual bool isNull() const; /** returns the ID of the meta data */ QString id() const; /** returns the scope of the meta data */ Scope scope() const; /** * Sets the scope of the meta data * @param scope the new scope */ void setScope(Scope scope); /** * Sets a property to a new value. If the property already exists * it will be created and if it did not exist, a new one will be * created. If the value is not valid (null), the property will * be deleted. * @param p name of the property * @param value a QVariant with the property's data */ void setProperty(const QString &p, const QVariant &value); /** * Checks whether this metadata object contains a given property. * @param p name of the property * @return true if the property exists, false otherwise */ bool hasProperty(const QString &p) const; /** * Returns a QVariant with the copy of the value of a property * or an empty QVariant if the property does not exist. * @param p name of the property * @return value of the property or an empty QVariant */ QVariant property(const QString &p) const; /** Same as above, for using through the [] operator */ inline QVariant operator [] (const QString p) const { return property(p); } /** * Returns a mutable reference to an existing property (or the * reference to an empty dummy if it did not exist). * @param p name of the property * @return reference to the value of the property */ QVariant &property(const QString &p); /** Same as above, for using through the [] operator */ inline QVariant &operator [] (const QString p) { return property(p); } /** equal operator, compares by data (not by ID) */ bool operator == (const MetaData &other) const; /** not equal operator, compares by data (not by ID) */ inline bool operator != (const MetaData &other) const { return !(operator == (other)); } /** returns a list with all property names */ QStringList keys() const; /** returns a list of position bount property names */ static QStringList positionBoundPropertyNames(); /** * Returns the index of the first sample covered by a given * meta data item * @return index of the first sample */ sample_index_t firstSample() const; /** * Returns the index of the last sample covered by a given * meta data item * @return index of the last sample */ sample_index_t lastSample() const; - /** - * Returns a list of tracks a meta data item is bound to - * @return list of bound tracks or empty list if not bound - */ - QList boundTracks() const; - /** dump all properties to stdout, for debugging */ virtual void dump() const; private: /** internal container class with meta data */ class MetaDataPriv: public QSharedData { public: /** constructor */ MetaDataPriv(); /** copy constructor */ MetaDataPriv(const MetaDataPriv &other); /** destructor */ virtual ~MetaDataPriv(); /** id of the meta data */ QString m_id; /** scope of the meta data */ Scope m_scope; /** list of properties, user defined */ PropertyList m_properties; private: /** creates a new unique ID */ static QString newUid(); /** counter for unique id generation */ static quint64 m_id_counter; /** mutex for protecting the id generator */ static QMutex m_id_lock; }; /** pointer to the shared meta data */ QSharedDataPointer m_data; }; } #endif /* META_DATA_H */ //*************************************************************************** //*************************************************************************** diff --git a/libkwave/MetaDataList.cpp b/libkwave/MetaDataList.cpp index 69e445fe..76700751 100644 --- a/libkwave/MetaDataList.cpp +++ b/libkwave/MetaDataList.cpp @@ -1,1078 +1,401 @@ /*************************************************************************** MetaDataList.cpp - list with meta data objects ------------------- begin : Sat Mar 06 2010 copyright : (C) 2010 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include "libkwave/MetaDataList.h" #include "libkwave/String.h" #include "libkwave/Utils.h" //*************************************************************************** Kwave::MetaDataList::MetaDataList() :QMap() { } //*************************************************************************** Kwave::MetaDataList::MetaDataList(const Kwave::MetaData &meta) :QMap() { add(meta); } //*************************************************************************** Kwave::MetaDataList::~MetaDataList() { } //*************************************************************************** static bool isLessThan(const Kwave::MetaData &m1, const Kwave::MetaData &m2) { return m1.firstSample() < m2.firstSample(); } //*************************************************************************** QList Kwave::MetaDataList::toSortedList() const { QList list = this->values(); if (!list.isEmpty()) std::stable_sort(list.begin(), list.end(), isLessThan); return list; } //*************************************************************************** Kwave::MetaDataList Kwave::MetaDataList::selectByType(const QString &type) const { return selectByValue(Kwave::MetaData::STDPROP_TYPE, type); } -//*************************************************************************** -Kwave::MetaDataList Kwave::MetaDataList::selectByScope( - MetaData::Scope scope) const -{ - Kwave::MetaDataList list; - - Iterator it(*this); - while (it.hasNext()) { - it.next(); - const Kwave::MetaData &m = it.value(); - if (m.scope() == scope) - list.add(m); - } - return list; -} - -//*************************************************************************** -Kwave::MetaDataList Kwave::MetaDataList::selectByTracks( - const QList &tracks) const -{ - Kwave::MetaDataList list; - Iterator it(*this); - while (it.hasNext()) { - // iterate over all meta data items - it.next(); - const Kwave::MetaData &m = it.value(); - bool match = true; - if (m.hasProperty(Kwave::MetaData::STDPROP_TRACKS)) { - // iterate over the list of tracks of the item - match = false; - QList track_list = - m[Kwave::MetaData::STDPROP_TRACKS].toList(); - foreach (const QVariant &v, track_list) { - bool ok = false; - unsigned int t = v.toUInt(&ok); - Q_ASSERT(ok); - if (ok && (tracks.contains(t))) { - match = true; - break; - } - } - } - if (match) { - Q_ASSERT(!list.keys().contains(m.id())); - if (!list.keys().contains(m.id())) list.add(m); - } - } - return list; -} - //*************************************************************************** Kwave::MetaDataList Kwave::MetaDataList::selectByRange( sample_index_t first, sample_index_t last) const { Kwave::MetaDataList list; Iterator it(*this); while (it.hasNext()) { it.next(); const Kwave::MetaData &m = it.value(); - if (m.scope() == Kwave::MetaData::Range) { - if (m.hasProperty(Kwave::MetaData::STDPROP_START) && - m.hasProperty(Kwave::MetaData::STDPROP_END)) - { - // check for overlap with a meta data that has a range - bool start_ok = false, end_ok = false; - const sample_index_t start = - m[Kwave::MetaData::STDPROP_START].toULongLong(&start_ok); - const sample_index_t end = - m[Kwave::MetaData::STDPROP_END].toULongLong(&end_ok); - if (!start_ok || !end_ok) continue; - - if ((start <= last) && (end >= first)) - list.add(m); - } - } - else if (m.scope() == Kwave::MetaData::Position) { + if (m.scope() == Kwave::MetaData::Position) { if (m.hasProperty(Kwave::MetaData::STDPROP_POS)) { // check for position within the range bool pos_ok = false; const sample_index_t pos = m[Kwave::MetaData::STDPROP_POS].toLongLong(&pos_ok); if (pos_ok && (pos >= first) && (pos <= last)) list.add(m); } } } return list; } //*************************************************************************** Kwave::MetaDataList Kwave::MetaDataList::selectByPosition( sample_index_t pos) const { Kwave::MetaDataList list; Iterator it(*this); while (it.hasNext()) { it.next(); const Kwave::MetaData &m = it.value(); if (m.scope() == Kwave::MetaData::Position) { if (m.hasProperty(Kwave::MetaData::STDPROP_POS)) { // check for position within the range bool pos_ok = false; const sample_index_t p = m[Kwave::MetaData::STDPROP_POS].toLongLong(&pos_ok); if (pos_ok && (p == pos)) list.add(m); } } } return list; } //*************************************************************************** Kwave::MetaDataList Kwave::MetaDataList::selectByProperty( const QString &property) const { Kwave::MetaDataList list; Iterator it(*this); while (it.hasNext()) { it.next(); const Kwave::MetaData &m = it.value(); if (m.hasProperty(property)) list.add(m); } return list; } //*************************************************************************** Kwave::MetaDataList Kwave::MetaDataList::selectByValue( const QString &property, QVariant value) const { Kwave::MetaDataList list; Iterator it(*this); while (it.hasNext()) { it.next(); const Kwave::MetaData &m = it.value(); if (m.hasProperty(property) && (m[property] == value)) list.add(m); } return list; } //*************************************************************************** bool Kwave::MetaDataList::contains(const Kwave::MetaData &metadata) const { QString id = metadata.id(); Iterator it(*this); while (it.hasNext()) { it.next(); const Kwave::MetaData &m = it.value(); if (m.id() == id) return true; } return false; } //*************************************************************************** void Kwave::MetaDataList::replace(const Kwave::MetaDataList &list) { if (list.isEmpty()) return; // find out which meta data types are affected QStringList types; foreach (const Kwave::MetaData &meta, list) { QString type = meta[Kwave::MetaData::STDPROP_TYPE].toString(); if (!types.contains(type)) { // remember this type in our list types.append(type); // remove all elements of that type that are not in the new list MutableIterator it(*this); while (it.hasNext()) { it.next(); Kwave::MetaData &m = it.value(); if (m[Kwave::MetaData::STDPROP_TYPE] == type) { if (!list.contains(m)) { it.remove(); } } } } } // now the same as in add() has to be done add(list); } //*************************************************************************** void Kwave::MetaDataList::add(const Kwave::MetaData &metadata) { if (!metadata.isNull()) (*this)[metadata.id()] = metadata; else remove(metadata); } //*************************************************************************** void Kwave::MetaDataList::add(const Kwave::MetaDataList &list) { foreach (const Kwave::MetaData &metadata, list) add(metadata); } //*************************************************************************** void Kwave::MetaDataList::remove(const Kwave::MetaData &metadata) { if (contains(metadata)) QMap::remove(metadata.id()); } //*************************************************************************** void Kwave::MetaDataList::remove(const Kwave::MetaDataList &list) { foreach (const Kwave::MetaData &metadata, list) remove(metadata); } //*************************************************************************** void Kwave::MetaDataList::cropByRange(sample_index_t first, sample_index_t last) { MutableIterator it(*this); while (it.hasNext()) { it.next(); Kwave::MetaData &m = it.value(); if (m.scope() & Kwave::MetaData::Position) { // if the meta data is bound to a position, remove it if - // it is out of scope and adjust the position if it is - // within the selection + // it is out of scope const QVariant v_pos = m[Kwave::MetaData::STDPROP_POS]; bool ok = false; sample_index_t pos = static_cast( v_pos.toULongLong(&ok)); if (!ok) continue; if ((pos < first) || (pos > last)) { // out of the selected area -> remove it.remove(); - continue; - } - } else if (m.scope() & Kwave::MetaData::Range) { - // if the meta data is bound to a scope, remove it if - // it does not overlap with the current selection, - // otherwise clip it to the bounds of the current selection - const QVariant v_start = m[Kwave::MetaData::STDPROP_START]; - bool ok = false; - sample_index_t start = static_cast( - v_start.toULongLong(&ok)); - if (!ok) continue; - - const QVariant v_end = m[Kwave::MetaData::STDPROP_END]; - ok = false; - sample_index_t end = static_cast( - v_start.toULongLong(&ok)); - if (!ok) continue; - - if ((end < start) || (start >= last)) { - // out of the selected area -> remove - it.remove(); - continue; - } else { - // clip to the seleced range - if (start < first) start = first; - if (end > last) end = last; - - // adjust start and end - m[Kwave::MetaData::STDPROP_START] = start; - m[Kwave::MetaData::STDPROP_END] = end; - } - - } - } - -} - -//*************************************************************************** -void Kwave::MetaDataList::cropByTracks(const QList &tracks) -{ - MutableIterator it(*this); - while (it.hasNext()) { - it.next(); - const Kwave::MetaData &m = it.value(); - - if (m.scope() & Kwave::MetaData::Track) { - if (m.hasProperty(Kwave::MetaData::STDPROP_TRACKS)) { - // convert the track list into a usable list of unsigned int - QList v_track_list = - m[Kwave::MetaData::STDPROP_TRACKS].toList(); - QList bound_tracks; - foreach (const QVariant &v, v_track_list) { - bool ok = false; - unsigned int t = v.toUInt(&ok); - if (ok) bound_tracks += t; - } - - foreach (unsigned int t, bound_tracks) - if (!tracks.contains(t)) bound_tracks.removeAll(t); - if (bound_tracks.isEmpty()) { - // no overlapping track indices -> remove - it.remove(); - continue; - } - - // do the renumbering - v_track_list.clear(); - for (int i = 0; i < bound_tracks.count(); i++) - v_track_list.append(Kwave::toUint(i)); - - // set a new track list - m[Kwave::MetaData::STDPROP_TRACKS] = v_track_list; } } } } //*************************************************************************** Kwave::MetaDataList Kwave::MetaDataList::copy(sample_index_t offset, - sample_index_t length, const QList &tracks) const + sample_index_t length) const { if (!length) return Kwave::MetaDataList(); // no range selected - empty list Kwave::MetaDataList list(*this); list.cropByRange(offset, offset + length - 1); - list.cropByTracks(tracks); return list; } -//*************************************************************************** -void Kwave::MetaDataList::merge(const Kwave::MetaDataList &meta_data) -{ - if (meta_data.isEmpty()) - return; // shortcut: bail out if empty - - const QStringList position_bound_properties = - Kwave::MetaData::positionBoundPropertyNames(); - - foreach (const Kwave::MetaData &meta, meta_data) { - // check if some meta data with the same type already - // exists at an overlapping position - bool found = false; - if (meta.hasProperty(Kwave::MetaData::STDPROP_TYPE)) { - MutableIterator it(*this); - while (it.hasNext()) { - it.next(); - Kwave::MetaData &other = it.value(); - - /* --- analysis phase --- */ - - // check: both have the same type? - if (!other.hasProperty(Kwave::MetaData::STDPROP_TYPE)) - continue; - if (other[Kwave::MetaData::STDPROP_TYPE] != - meta[Kwave::MetaData::STDPROP_TYPE]) - continue; - - // check: sampe scope? - if (!(meta.scope() == other.scope())) - continue; - - // check: ranges overlap or touch? - sample_index_t meta_first = meta.firstSample(); - sample_index_t meta_last = meta.lastSample(); - sample_index_t other_first = other.firstSample(); - sample_index_t other_last = other.lastSample(); - if ((meta_last < other_first) && (meta_last + 1 != other_first)) - continue; - if ((meta_first > other_last) && (meta_first != other_last + 1)) - continue; - - // determine list of overlapping/non-overlapping tracks - QList overlapping_tracks; - QList non_overlapping_tracks; - if (meta.hasProperty(Kwave::MetaData::STDPROP_TRACKS) && - other.hasProperty(Kwave::MetaData::STDPROP_TRACKS)) - { - QList meta_tracks = meta.boundTracks(); - QList other_tracks = other.boundTracks(); - - foreach (unsigned int t, meta_tracks) { - if (other_tracks.contains(t)) - overlapping_tracks.append(t); - else - non_overlapping_tracks.append(t); - } - } - - // check: no overlapping tracks? - if (overlapping_tracks.isEmpty()) - continue; - - // check: all non-positional properties have to match - bool match = true; - foreach (const QString &p, meta.keys()) { - if (!other.hasProperty(p)) { - match = false; - break; - } - - // ignore internal properties - if (p == Kwave::MetaData::STDPROP_TRACKS) - continue; - if (position_bound_properties.contains(p)) - continue; - - if (meta[p] != other[p]) { - match = false; - break; - } - } - if (!match) continue; - - /* --- merge phase --- */ - - found = true; - - // split all data bound to non-overlapping tracks into - // a separate meta data object - if (!non_overlapping_tracks.isEmpty()) { - Kwave::MetaData copy = other; - - QVariantList list; - foreach (unsigned int t, non_overlapping_tracks) - list.append(QVariant(t)); - other.setProperty(Kwave::MetaData::STDPROP_TRACKS, list); - - list.clear(); - foreach (unsigned int t, overlapping_tracks) - list.append(QVariant(t)); - copy.setProperty(Kwave::MetaData::STDPROP_TRACKS, list); - - add(copy); - } - - // merge range - if (other.hasProperty(Kwave::MetaData::STDPROP_START)) { - other.setProperty( - Kwave::MetaData::STDPROP_START, - qMin(meta_first, other_first)); - } - if (other.hasProperty(Kwave::MetaData::STDPROP_END)) { - other.setProperty( - Kwave::MetaData::STDPROP_END, - qMax(meta_last, other_last)); - } - } - } - - // no matching meta data item for merging found => add as new one - if (!found) { - add(meta); - } - } -} - //*************************************************************************** void Kwave::MetaDataList::deleteRange(sample_index_t offset, - sample_index_t length, - const QList &tracks) + sample_index_t length) { const sample_index_t del_first = offset; const sample_index_t del_last = offset + length - 1; if (!length) return; MutableIterator it(*this); while (it.hasNext()) { it.next(); Kwave::MetaData &meta = it.value(); sample_index_t meta_first = meta.firstSample(); sample_index_t meta_last = meta.lastSample(); // check: range overlap? if ((meta_first > del_last) || (meta_last < del_first)) continue; - // only operate on the matching tracks: - if (!tracks.isEmpty() && - meta.hasProperty(Kwave::MetaData::STDPROP_TRACKS)) { - - // determine list of overlapping/non-overlapping tracks - QList overlapping_tracks; - QList non_overlapping_tracks; - QList meta_tracks = meta.boundTracks(); - - foreach (unsigned int t, meta_tracks) { - if (tracks.contains(t)) - overlapping_tracks.append(t); - else - non_overlapping_tracks.append(t); - } - - // skip if no overlap - if (overlapping_tracks.isEmpty()) - continue; - - // split all data bound to non-overlapping tracks into - // a separate meta data object - if (!non_overlapping_tracks.isEmpty()) { - Kwave::MetaData copy = meta; - - QVariantList list; - foreach (unsigned int t, overlapping_tracks) - list.append(QVariant(t)); - meta.setProperty(Kwave::MetaData::STDPROP_TRACKS, list); - - list.clear(); - foreach (unsigned int t, non_overlapping_tracks) - list.append(QVariant(t)); - copy.setProperty(Kwave::MetaData::STDPROP_TRACKS, list); - - add(copy); - } - } - - /* --- we have a position/range/track overlap --- */ - // position bound -> remove completely - if (meta.hasProperty(Kwave::MetaData::STDPROP_POS)) { - it.remove(); - continue; - } - - // complete overlap -> remove completely - if ((meta.scope() & Kwave::MetaData::Range) && - (meta_first >= del_first) && (meta_last <= del_last)) { + if (meta.hasProperty(Kwave::MetaData::STDPROP_POS)) it.remove(); - continue; - } - - // check: no range -> no adjustment - if (!meta.hasProperty(Kwave::MetaData::STDPROP_START) || - !meta.hasProperty(Kwave::MetaData::STDPROP_END)) { - continue; - } - - // cut out a piece from the middle -> adjust right - if ((del_first > meta_first) && (del_last < meta_last)) { - meta_last -= length; - meta[Kwave::MetaData::STDPROP_END] = QVariant(meta_last); - continue; - } - - // cut away a part from left - if (del_last < meta_last) { - meta[Kwave::MetaData::STDPROP_START] = QVariant(del_last + 1); - continue; - } - - // cut away a part from right - if (del_first > meta_first) { - meta[Kwave::MetaData::STDPROP_END] = QVariant(del_first - 1); - continue; - } - - Q_ASSERT(false); // we should never reach this, no overlap? } } //*************************************************************************** -void Kwave::MetaDataList::shiftLeft(sample_index_t offset, sample_index_t shift, - const QList &tracks) +void Kwave::MetaDataList::shiftLeft(sample_index_t offset, + sample_index_t shift) { MutableIterator it(*this); while (it.hasNext()) { it.next(); Kwave::MetaData &meta = it.value(); - sample_index_t meta_first = meta.firstSample(); - sample_index_t meta_last = meta.lastSample(); - // check: is it before the offset ? + sample_index_t meta_first = meta.firstSample(); if (meta_first < offset) continue; - // only operate on the matching tracks: - if (!tracks.isEmpty() && - meta.hasProperty(Kwave::MetaData::STDPROP_TRACKS)) { - - // determine list of overlapping/non-overlapping tracks - QList overlapping_tracks; - QList non_overlapping_tracks; - QList meta_tracks = meta.boundTracks(); - - foreach (unsigned int t, meta_tracks) { - if (tracks.contains(t)) - overlapping_tracks.append(t); - else - non_overlapping_tracks.append(t); - } - - // skip if no overlap - if (overlapping_tracks.isEmpty()) - continue; - - // split all data bound to non-overlapping tracks into - // a separate meta data object - if (!non_overlapping_tracks.isEmpty()) { - Kwave::MetaData copy = meta; - - QVariantList list; - foreach (unsigned int t, overlapping_tracks) - list.append(QVariant(t)); - meta.setProperty(Kwave::MetaData::STDPROP_TRACKS, list); - - list.clear(); - foreach (unsigned int t, non_overlapping_tracks) - list.append(QVariant(t)); - copy.setProperty(Kwave::MetaData::STDPROP_TRACKS, list); - - add(copy); - } - } - - /* --- we have a position/range/track overlap --- */ - // position bound -> move position if (meta.hasProperty(Kwave::MetaData::STDPROP_POS)) { bool ok = false; sample_index_t pos = static_cast( meta[Kwave::MetaData::STDPROP_POS].toULongLong(&ok)); if (!ok) continue; if (pos >= shift) { // shift position left meta[Kwave::MetaData::STDPROP_POS] = pos - shift; } else { // do not produce negative coordinates // -> moving into negative means deleting! it.remove(); } - continue; - } - - // check: no range -> no adjustment - if (!meta.hasProperty(Kwave::MetaData::STDPROP_START) || - !meta.hasProperty(Kwave::MetaData::STDPROP_END)) { - continue; - } - - // check: moving into negative - Q_ASSERT(meta_last >= shift); - if (meta_last < shift) { - it.remove(); - continue; } - - // move to the left, clip start to zero - Q_ASSERT(meta_first >= shift); - meta_first = (meta_first >= shift) ? (meta_first - shift) : 0; - meta_last -= shift; - - if (meta.hasProperty(Kwave::MetaData::STDPROP_START)) - meta[Kwave::MetaData::STDPROP_START] = QVariant(meta_first); - if (meta.hasProperty(Kwave::MetaData::STDPROP_END)) - meta[Kwave::MetaData::STDPROP_END] = QVariant(meta_last); } } //*************************************************************************** -void Kwave::MetaDataList::shiftRight(sample_index_t offset, sample_index_t shift, - const QList &tracks) +void Kwave::MetaDataList::shiftRight(sample_index_t offset, + sample_index_t shift) { MutableIterator it(*this); it.toBack(); while (it.hasPrevious()) { it.previous(); Kwave::MetaData &meta = it.value(); - sample_index_t meta_first = meta.firstSample(); - sample_index_t meta_last = meta.lastSample(); - // check: is it before the offset ? + sample_index_t meta_last = meta.lastSample(); if (meta_last < offset) continue; - // only operate on the matching tracks: - if (!tracks.isEmpty() && - meta.hasProperty(Kwave::MetaData::STDPROP_TRACKS)) { - - // determine list of overlapping/non-overlapping tracks - QList overlapping_tracks; - QList non_overlapping_tracks; - QList meta_tracks = meta.boundTracks(); - - foreach (unsigned int t, meta_tracks) { - if (tracks.contains(t)) - overlapping_tracks.append(t); - else - non_overlapping_tracks.append(t); - } - - // skip if no overlap - if (overlapping_tracks.isEmpty()) - continue; - - // split all data bound to non-overlapping tracks into - // a separate meta data object - if (!non_overlapping_tracks.isEmpty()) { - Kwave::MetaData copy = meta; - - QVariantList list; - foreach (unsigned int t, overlapping_tracks) - list.append(QVariant(t)); - meta.setProperty(Kwave::MetaData::STDPROP_TRACKS, list); - - list.clear(); - foreach (unsigned int t, non_overlapping_tracks) - list.append(QVariant(t)); - copy.setProperty(Kwave::MetaData::STDPROP_TRACKS, list); - - add(copy); - } - } - - /* --- we have a position/range/track overlap --- */ - // position bound -> move position if (meta.hasProperty(Kwave::MetaData::STDPROP_POS)) { bool ok = false; sample_index_t pos = static_cast( meta[Kwave::MetaData::STDPROP_POS].toULongLong(&ok)); if (!ok) continue; Q_ASSERT(pos + shift >= pos); if (pos + shift >= pos) { // shift position right meta[Kwave::MetaData::STDPROP_POS] = pos + shift; } else { // do not produce a coordinate overflow // -> moving outside range means deleting! it.remove(); } - continue; - } - - // check: no range -> no adjustment - if (!meta.hasProperty(Kwave::MetaData::STDPROP_START) || - !meta.hasProperty(Kwave::MetaData::STDPROP_END)) { - continue; } - - // check: range overflow - Q_ASSERT(meta_first + shift >= meta_first); - if (meta_first + shift < meta_first) { - it.remove(); - continue; - } - - // move to the right, clip end to maximum coordinate - Q_ASSERT(meta_last + shift >= meta_last); - meta_last = (meta_last + shift >= meta_last) ? - (meta_last + shift) : SAMPLE_INDEX_MAX; - if (meta_first >= offset) - meta_first += shift; - - if (meta.hasProperty(Kwave::MetaData::STDPROP_START)) - meta[Kwave::MetaData::STDPROP_START] = QVariant(meta_first); - if (meta.hasProperty(Kwave::MetaData::STDPROP_END)) - meta[Kwave::MetaData::STDPROP_END] = QVariant(meta_last); } } //*************************************************************************** -void Kwave::MetaDataList::scalePositions(double scale, - const QList &tracks) +void Kwave::MetaDataList::scalePositions(double scale) { MutableIterator it(*this); while (it.hasNext()) { it.next(); Kwave::MetaData &meta = it.value(); - sample_index_t meta_first = meta.firstSample(); - sample_index_t meta_last = meta.lastSample(); - - // only operate on the matching tracks: - if (!tracks.isEmpty() && - meta.hasProperty(Kwave::MetaData::STDPROP_TRACKS)) { - - // determine list of overlapping/non-overlapping tracks - QList overlapping_tracks; - QList non_overlapping_tracks; - QList meta_tracks = meta.boundTracks(); - - foreach (unsigned int t, meta_tracks) { - if (tracks.contains(t)) - overlapping_tracks.append(t); - else - non_overlapping_tracks.append(t); - } - - // skip if no overlap - if (overlapping_tracks.isEmpty()) - continue; - - // split all data bound to non-overlapping tracks into - // a separate meta data object - if (!non_overlapping_tracks.isEmpty()) { - Kwave::MetaData copy = meta; - - QVariantList list; - foreach (unsigned int t, overlapping_tracks) - list.append(QVariant(t)); - meta.setProperty(Kwave::MetaData::STDPROP_TRACKS, list); - - list.clear(); - foreach (unsigned int t, non_overlapping_tracks) - list.append(QVariant(t)); - copy.setProperty(Kwave::MetaData::STDPROP_TRACKS, list); - - add(copy); - } - } - - /* --- we have a position/range/track overlap --- */ - // position bound -> move position if (meta.hasProperty(Kwave::MetaData::STDPROP_POS)) { bool ok = false; sample_index_t pos = static_cast( meta[Kwave::MetaData::STDPROP_POS].toULongLong(&ok)); if (!ok) continue; if ((pos * scale) <= SAMPLE_INDEX_MAX) { // scale position meta[Kwave::MetaData::STDPROP_POS] = (pos * scale); } else { // do not produce a coordinate overflow // -> moving outside range means deleting! it.remove(); } - continue; } - - // check: no range -> no adjustment - if (!meta.hasProperty(Kwave::MetaData::STDPROP_START) || - !meta.hasProperty(Kwave::MetaData::STDPROP_END)) { - continue; - } - - // check: range overflow - if ((meta_first * scale) >= SAMPLE_INDEX_MAX) { - it.remove(); - continue; - } - - // scale, clip end to maximum coordinate - meta_last = static_cast( - qMin(SAMPLE_INDEX_MAX, meta_last * scale)); - meta_first = static_cast(meta_first * scale); - - if (meta.hasProperty(Kwave::MetaData::STDPROP_START)) - meta[Kwave::MetaData::STDPROP_START] = QVariant(meta_first); - if (meta.hasProperty(Kwave::MetaData::STDPROP_END)) - meta[Kwave::MetaData::STDPROP_END] = QVariant(meta_last); - } -} - -//*************************************************************************** -void Kwave::MetaDataList::insertTrack(unsigned int track) -{ - const QString prop = Kwave::MetaData::STDPROP_TRACKS; - MutableIterator it(*this); - while (it.hasNext()) { - it.next(); - const Kwave::MetaData &m = it.value(); - if (m.hasProperty(prop)) { - // iterate over the list of tracks - QList old_list = m[prop].toList(); - QList new_list; - foreach (const QVariant &v, old_list) { - bool ok = false; - unsigned int t = v.toUInt(&ok); - if (ok && (t >= track)) - new_list.append(QVariant(t + 1)); - else - new_list.append(v); - } - m[prop] = new_list; // set updated list of bound tracks - } - } -} - -//*************************************************************************** -void Kwave::MetaDataList::deleteTrack(unsigned int track) -{ - const QString prop = Kwave::MetaData::STDPROP_TRACKS; - MutableIterator it(*this); - while (it.hasNext()) { - it.next(); - Kwave::MetaData &m = it.value(); - if (m.hasProperty(prop)) { - // iterate over the list of tracks - QList old_list = m[prop].toList(); - QList new_list; - foreach (const QVariant &v, old_list) { - bool ok = false; - unsigned int t = v.toUInt(&ok); - Q_ASSERT(ok); - if (!ok) continue; - if (t < track) - new_list.append(v); - else if (t > track) - new_list.append(QVariant(t - 1)); - } - if (!new_list.isEmpty()) - m[prop] = new_list; // set updated list of bound tracks - else - it.remove(); // list is empty now -> delete whole item - } - } -} - -//*************************************************************************** -void Kwave::MetaDataList::split(sample_index_t offset, - const QList &tracks) -{ - // check: splitting at offset zero makes no sense, but is not forbidden - if (!offset) return; - - MutableIterator it(*this); - while (it.hasNext()) { - it.next(); - Kwave::MetaData &meta = it.value(); - - sample_index_t meta_first = meta.firstSample(); - sample_index_t meta_last = meta.lastSample(); - - // check: is the split done in our range? - if ((offset <= meta_first) || (offset > meta_last)) - continue; - - // check: no range -> no splitting - if (!meta.hasProperty(Kwave::MetaData::STDPROP_START) || - !meta.hasProperty(Kwave::MetaData::STDPROP_END)) { - continue; - } - - // only operate on the matching tracks: - if (!tracks.isEmpty() && - meta.hasProperty(Kwave::MetaData::STDPROP_TRACKS)) { - - // determine list of overlapping/non-overlapping tracks - QList overlapping_tracks; - QList non_overlapping_tracks; - QList meta_tracks = meta.boundTracks(); - - foreach (unsigned int t, meta_tracks) { - if (tracks.contains(t)) - overlapping_tracks.append(t); - else - non_overlapping_tracks.append(t); - } - - // skip if no overlap - if (overlapping_tracks.isEmpty()) - continue; - - // split all data bound to non-overlapping tracks into - // a separate meta data object - if (!non_overlapping_tracks.isEmpty()) { - Kwave::MetaData copy = meta; - - QVariantList list; - foreach (unsigned int t, overlapping_tracks) - list.append(QVariant(t)); - meta.setProperty(Kwave::MetaData::STDPROP_TRACKS, list); - - list.clear(); - foreach (unsigned int t, non_overlapping_tracks) - list.append(QVariant(t)); - copy.setProperty(Kwave::MetaData::STDPROP_TRACKS, list); - - add(copy); - } - } - - /* --- we have a range/track overlap --- */ - - Kwave::MetaData copy = meta; - copy[Kwave::MetaData::STDPROP_START] = QVariant(offset); - meta[Kwave::MetaData::STDPROP_END] = QVariant(offset - 1); - add(copy); } } //*************************************************************************** void Kwave::MetaDataList::dump() const { qDebug("--- meta data ---"); Iterator it(*this); while (it.hasNext()) { it.next(); const Kwave::MetaData &meta = it.value(); qDebug("* meta data #%s", DBG(it.key())); meta.dump(); } qDebug("-----------------"); } //*************************************************************************** //*************************************************************************** diff --git a/libkwave/MetaDataList.h b/libkwave/MetaDataList.h index ef7dfea1..e12f4e54 100644 --- a/libkwave/MetaDataList.h +++ b/libkwave/MetaDataList.h @@ -1,329 +1,242 @@ /*************************************************************************** MetaDataList.h - list with meta data objects ------------------- begin : Sat Mar 06 2010 copyright : (C) 2010 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef META_DATA_LIST_H #define META_DATA_LIST_H #include "config.h" #include #include #include #include #include #include #include #include "libkwave/MetaData.h" #include "libkwave/Sample.h" namespace Kwave { class Q_DECL_EXPORT MetaDataList: public QMap { public: /** const iterator for the meta data list */ typedef QMapIterator Iterator; /** mutable iterator for the meta data list */ typedef QMutableMapIterator MutableIterator; /** Default constructor */ MetaDataList(); /** * Constructor, creates a meta data list with only one single meta * data item. In some cases you need to pass a meta data list to a * function but you have only a single meta data item, so this might * be quite handy * @param meta const reference to a single meta data item */ explicit MetaDataList(const MetaData &meta); /** Destructor */ virtual ~MetaDataList(); /** * Create a simple list of meta data items, sorted by the position * of the first sample. All meta data items that do not correspond * to a position or a "first" sample are mapped to the start (zero). * @return a QList of meta data, sorted by position */ virtual QList toSortedList() const; /** * select elements from the meta data list that have the standard * property STDPROP_TYPE set to a specific value. * * @param type the type to select @see Kwave::MetaData::STDPROP_TYPE * @return list with found meta data objects */ virtual MetaDataList selectByType(const QString &type) const; - /** - * select elements from the meta data list that have a specific scope - * - * @param scope the scope to select @see Kwave::MetaData::Scope - * @return list with found meta data objects - */ - virtual MetaDataList selectByScope(MetaData::Scope scope) const; - - /** - * select elements from the meta data list that belong to - * a given track. - * - * @param tracks list of track indices to select - * @return list with found meta data objects - */ - virtual MetaDataList selectByTracks( - const QList &tracks) const; - /** * select elements from the meta data list that overlap a given * range of samples (selects elements with scope "Range" as well * as elements with scope "Position") * * @param first index of the first sample * @param last index of the last sample * @return list with found meta data objects */ virtual MetaDataList selectByRange( sample_index_t first, sample_index_t last) const; /** * select elements from the meta data list that are exactly at a * given position. * * @param pos index of the sample to select * @return list with found meta data objects */ virtual MetaDataList selectByPosition(sample_index_t pos) const; /** * select elements from the meta data list that contain a given * property. * * @param property the property to search for * @return list with found meta data objects */ virtual MetaDataList selectByProperty(const QString &property) const; /** * select elements from the meta data list that contain a given * property and a given value * * @param property the property to search for * @param value the value that the property should have * @return list with found meta data objects */ virtual MetaDataList selectByValue( const QString &property, QVariant value) const; /** * Checks whether a meta data object is contained in this list, * the check is based on the unique id of the meta data object. * * @param metadata the object to search * @return true if found, otherwise false */ virtual bool contains(const MetaData &metadata) const; /** * Replaces all meta data objects that have the same type as one * of the objects in the passed list with newer versions. If an * object did not exist, it will be created. If an object is not * in the passed list, it will be deleted. * * @param list listof meta data objects that should be replaced * @note affects only objects with a type that was found in the * passed list */ virtual void replace(const MetaDataList &list); /** * Adds a single meta data object to the list. If it is already * present, the old version will be silently replaced. * If the object is a null object and an object with the same * ID exists in the list, this will work as remove(). * * @param metadata the meta data object that should be added */ virtual void add(const MetaData &metadata); /** * Adds a list of meta data objects to the list. Old versions of * existing objects will be silently replaced. * * @param list list of meta data objects that should be added */ virtual void add(const MetaDataList &list); /** * Removes one meta data object from the list (if it exists). * * @param metadata the object that should be removed */ virtual void remove(const MetaData &metadata); /** * Removes a list of meta data objects from this list (if they exist). * * @param list the list of meta data objects to remove */ virtual void remove(const MetaDataList &list); /** * Crops this list to a given range of samples. All position aware * elements that are not covered by the given range will be removed, * all covered elements will be adjusted. * * @param first index of the first sample * @param last index of the last sample */ virtual void cropByRange(sample_index_t first, sample_index_t last); - /** - * Crops this list to a given set of tracks. All elements that are - * bound to a track or list of tracks which are not covered by the - * given selection will be removed. The tracks of the remaining - * elements will be re-numbered to start from zero and counted up - * without gaps. - * - * @param tracks list of track indices - */ - virtual void cropByTracks(const QList &tracks); - /** * copy elements from the meta data list that overlap a given * range of samples (selects elements with scope "Range" as well - * as elements with scope "Position") and have a binding to a - * track. + * as elements with scope "Position") * * @param offset index of the first sample * @param length number of samples of the range - * @param tracks list of track indices * @return list with a copy of found meta data objects */ virtual MetaDataList copy( sample_index_t offset, - sample_index_t length, - const QList &tracks + sample_index_t length ) const; - /** - * Merges a list of other meta data items - * @param meta_data list of meta data items - */ - void merge(const MetaDataList &meta_data); - /** * delete elements from the meta data list that overlap a given * range of samples (selects elements with scope "Range" as well * as elements with scope "Position") and have a binding to a * track. * * @param offset index of the first sample * @param length number of samples to delete - * @param tracks list of track indices, will be filled - * with copies of found meta data objects */ - virtual void deleteRange( - sample_index_t offset, - sample_index_t length, - const QList &tracks - ); + virtual void deleteRange(sample_index_t offset, sample_index_t length); /** * shift the positions or start/end of all elements that are after * a given offset to the left. * * @param offset index of the first sample * @param shift number of samples to shift left - * @param tracks list of track indices */ - virtual void shiftLeft( - sample_index_t offset, - sample_index_t shift, - const QList &tracks - ); + virtual void shiftLeft(sample_index_t offset, sample_index_t shift); /** * shift the positions or start/end of all elements that are after * a given offset to the right. * * @param offset index of the first sample * @param shift number of samples to shift right - * @param tracks list of track indices */ - virtual void shiftRight( - sample_index_t offset, - sample_index_t shift, - const QList &tracks - ); + virtual void shiftRight(sample_index_t offset, sample_index_t shift); /** * scale the positions or start/end of all elements by a given factor * * @param scale the factor that is applied to all positions - * @param tracks list of track indices - */ - virtual void scalePositions( - double scale, - const QList &tracks - ); - - /** - * makes place for a new track by adjusting the track indices of all - * meta data items after that track by plus one - * - * @param track index of the track that is inserted */ - virtual void insertTrack(unsigned int track); - - /** - * delete all meta data that was bound to a specific track and - * adjust the track indices of all meta data items after that - * track by minus one - * - * @param track index of the track that is to be deleted - */ - virtual void deleteTrack(unsigned int track); + virtual void scalePositions(double scale); /** dump all meta data to stdout (for debugging) */ virtual void dump() const; - protected: - - /** - * Splits the list at a given position. The given position will - * be the start of the new fragment(s), so that splitting multiple - * times at the same offset does not produce further fragments. - * @param offset index of the sample position before which the - * list should be split - * @param tracks list of track indices - */ - void split(sample_index_t offset, const QList &tracks); - }; } #endif /* META_DATA_LIST_H */ //*************************************************************************** //*************************************************************************** diff --git a/libkwave/MimeData.cpp b/libkwave/MimeData.cpp index ee45050e..ee41b246 100644 --- a/libkwave/MimeData.cpp +++ b/libkwave/MimeData.cpp @@ -1,440 +1,440 @@ /*************************************************************************** KwaveMimeData.cpp - mime data container for Kwave's audio data ------------------- begin : Oct 04 2008 copyright : (C) 2008 by Thomas Eschenbacher email : Thomas Eschenbacher ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include #include #include "libkwave/CodecManager.h" #include "libkwave/Compression.h" #include "libkwave/Connect.h" #include "libkwave/Decoder.h" #include "libkwave/Encoder.h" #include "libkwave/MimeData.h" #include "libkwave/MultiStreamWriter.h" #include "libkwave/MultiTrackReader.h" #include "libkwave/MultiTrackWriter.h" #include "libkwave/Sample.h" #include "libkwave/SampleReader.h" #include "libkwave/Signal.h" #include "libkwave/SignalManager.h" #include "libkwave/String.h" #include "libkwave/Utils.h" #include "libkwave/Writer.h" #include "libkwave/modules/ChannelMixer.h" #include "libkwave/modules/RateConverter.h" // RFC 2361: #define WAVE_FORMAT_PCM "audio/vnd.wave" // ; codec=001" /** block size in bytes used as increment when resizing the raw buffer */ #define BUFFER_BLOCK_SIZE (4 * 1024 * 1024) /* 4 MB */ //*************************************************************************** Kwave::MimeData::Buffer::Buffer() :QIODevice(), m_block(0), m_size(0), m_data() { } //*************************************************************************** Kwave::MimeData::Buffer::~Buffer() { close(); } //*************************************************************************** qint64 Kwave::MimeData::Buffer::readData(char *data, qint64 maxlen) { if (atEnd() || (pos() >= size())) return -1; if (pos() + maxlen > size()) maxlen = size() - pos(); Kwave::MemoryManager &mem = Kwave::MemoryManager::instance(); return mem.readFrom( m_block, Kwave::toUint(pos()), data, Kwave::toUint(maxlen) ); } //*************************************************************************** qint64 Kwave::MimeData::Buffer::writeData(const char *data, qint64 len) { Kwave::MemoryManager &mem = Kwave::MemoryManager::instance(); quint64 new_size = pos() + len; // clip the mime data buffer at the "unsigned int" border if (new_size > std::numeric_limits::max()) return -1; // round up the block size if it can no longer be considered to be a // small block (~ half of block size), avoid wasting too much memory // if the needed size is very small. if (new_size > (BUFFER_BLOCK_SIZE / 2)) new_size = Kwave::round_up(new_size, BUFFER_BLOCK_SIZE); if (!m_block) { // first call: allocate a new memory object m_block = mem.allocate(static_cast(new_size)); if (!m_block) return -1; // allocation failed } if ((pos() + len) > static_cast(mem.sizeOf(m_block))) { if (!mem.resize(m_block, static_cast(new_size))) return -1; // resize failed } // write to the memory block (may be physical or swap file) qint64 written = mem.writeTo( m_block, static_cast(pos()), data, static_cast(len) ); if (written < 0) return -1; // write failed: disk full? if (pos() + written > m_size) m_size = pos() + written ; // push the "m_size" return written; // write operation was successful } //*************************************************************************** bool Kwave::MimeData::Buffer::mapToByteArray() { // reset our QByteArray m_data.setRawData(Q_NULLPTR, 0); m_data.clear(); Kwave::MemoryManager &mem = Kwave::MemoryManager::instance(); const char *raw = (m_block) ? static_cast(mem.map(m_block)) : Q_NULLPTR; if (!raw) { // mapping failed: free the block here to avoid trouble // in close() mem.free(m_block); m_block = 0; qWarning("Kwave::MimeData::Buffer::mapToByteArray() failed"); return false; // mmap failed } // attach the mapped memory to our QByteArray const unsigned int len = Kwave::toUint(m_size); // qDebug("Kwave::MimeData::Buffer::mapToByteArray() - %p [%u]", raw, len); m_data.setRawData(raw, len); return true; } //*************************************************************************** void Kwave::MimeData::Buffer::close() { QIODevice::close(); // reset the byte array and it's connection to the block of memory m_data.setRawData(Q_NULLPTR, 0); m_data.clear(); // unmap and discard the mapped memory if (m_block) { Kwave::MemoryManager &mem = Kwave::MemoryManager::instance(); mem.unmap(m_block); mem.free(m_block); m_block = 0; } m_size = 0; } //*************************************************************************** //*************************************************************************** Kwave::MimeData::MimeData() :QMimeData(), m_buffer() { } //*************************************************************************** Kwave::MimeData::~MimeData() { } //*************************************************************************** bool Kwave::MimeData::encode(QWidget *widget, Kwave::MultiTrackReader &src, const Kwave::MetaDataList &meta_data) { // use our default encoder Kwave::Encoder *encoder = Kwave::CodecManager::encoder(_(WAVE_FORMAT_PCM)); Q_ASSERT(encoder); if (!encoder) return false; Q_ASSERT(src.tracks()); if (!src.tracks()) return false; // set hourglass cursor QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); sample_index_t first = src.first(); sample_index_t last = src.last(); Kwave::MetaDataList new_meta_data = meta_data.selectByRange(first, last); // move all meta data left, to start at the beginning of the selection - new_meta_data.shiftLeft(first, first, QList()); + new_meta_data.shiftLeft(first, first); // fix the length information in the new file info // and change to uncompressed mode Kwave::FileInfo info(meta_data); info.set(Kwave::INF_COMPRESSION, QVariant(Kwave::Compression::NONE)); info.setLength(last - first + 1); info.setTracks(src.tracks()); new_meta_data.replace(Kwave::MetaDataList(info)); // encode into the buffer m_buffer.close(); // discard old stuff encoder->encode(widget, src, m_buffer, new_meta_data); delete encoder; // set the mime data into this mime data container bool succeeded = m_buffer.mapToByteArray(); if (succeeded) { // mmap succeeded setData(_(WAVE_FORMAT_PCM), m_buffer.byteArray()); } else { // failed to map memory m_buffer.close(); } // remove hourglass QApplication::restoreOverrideCursor(); return succeeded; } //*************************************************************************** sample_index_t Kwave::MimeData::decode(QWidget *widget, const QMimeData *e, Kwave::SignalManager &sig, sample_index_t pos) { // decode, use the first format that matches sample_index_t decoded_length = 0; unsigned int decoded_tracks = 0; // try to find a suitable decoder foreach (const QString &format, e->formats()) { // skip all non-supported formats if (!Kwave::CodecManager::canDecode(format)) continue; Kwave::Decoder *decoder = Kwave::CodecManager::decoder(format); Q_ASSERT(decoder); if (!decoder) return 0; QByteArray raw_data = e->data(format); QBuffer src(&raw_data); // open the mime source and get header information bool ok = decoder->open(widget, src); if (!ok) { delete decoder; continue; } decoded_length = Kwave::FileInfo(decoder->metaData()).length(); decoded_tracks = Kwave::FileInfo(decoder->metaData()).tracks(); Q_ASSERT(decoded_length); Q_ASSERT(decoded_tracks); if (!decoded_length || !decoded_tracks) { delete decoder; continue; } // get sample rates of source and destination double src_rate = Kwave::FileInfo(decoder->metaData()).rate(); double dst_rate = sig.rate(); // if the sample rate has to be converted, adjust the length // right border if (!qFuzzyCompare(src_rate, dst_rate) && (dst_rate > 1) && sig.tracks()) decoded_length *= (dst_rate / src_rate); sample_index_t left = pos; sample_index_t right = left + decoded_length - 1; QList tracks = sig.selectedTracks(); if (tracks.isEmpty()) tracks = sig.allTracks(); // special case: destination is currently empty if (!sig.tracks()) { // encode into an empty window -> create tracks qDebug("Kwave::MimeData::decode(...) -> new signal"); dst_rate = src_rate; sig.newSignal(0, src_rate, Kwave::FileInfo(decoder->metaData()).bits(), decoded_tracks); ok = (sig.tracks() == decoded_tracks); if (!ok) { delete decoder; continue; } } const unsigned int dst_tracks = sig.selectedTracks().count(); // create the final sink Kwave::MultiTrackWriter dst(sig, sig.selectedTracks(), Kwave::Insert, left, right); // if the track count does not match, then we need a channel mixer Q_ASSERT(ok); Kwave::ChannelMixer *mixer = Q_NULLPTR; if (ok && (decoded_tracks != dst_tracks)) { qDebug("Kwave::MimeData::decode(...) -> mixing channels: %u -> %u", decoded_tracks, dst_tracks); mixer = new(std::nothrow) Kwave::ChannelMixer(decoded_tracks, dst_tracks); Q_ASSERT(mixer); ok &= (mixer) && mixer->init(); Q_ASSERT(ok); } Q_ASSERT(ok); // if the sample rates do not match, then we need a rate converter Kwave::StreamObject *rate_converter = Q_NULLPTR; if (ok && !qFuzzyCompare(src_rate, dst_rate)) { // create a sample rate converter qDebug("Kwave::MimeData::decode(...) -> rate conversion: "\ "%0.1f -> %0.1f", src_rate, dst_rate); rate_converter = new(std::nothrow) Kwave::MultiTrackSource( dst_tracks, widget); Q_ASSERT(rate_converter); if (rate_converter) rate_converter->setAttribute(SLOT(setRatio(QVariant)), QVariant(dst_rate / src_rate)); else ok = false; } Q_ASSERT(ok); // set hourglass cursor QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); if (ok && (rate_converter || mixer)) { // pass all data through a filter chain Kwave::MultiStreamWriter adapter(decoded_tracks); // pass the data through a sample rate converter // decoder -> adapter -> [mixer] -> [converter] -> dst Kwave::StreamObject *last_output = &adapter; if (ok && mixer) { // connect the channel mixer ok = Kwave::connect( *last_output, SIGNAL(output(Kwave::SampleArray)), *mixer, SLOT(input(Kwave::SampleArray)) ); last_output = mixer; } if (ok && rate_converter) { // connect the rate converter ok = Kwave::connect( *last_output, SIGNAL(output(Kwave::SampleArray)), *rate_converter, SLOT(input(Kwave::SampleArray)) ); last_output = rate_converter; } // connect the sink if (ok) { ok = Kwave::connect( *last_output, SIGNAL(output(Kwave::SampleArray)), dst, SLOT(input(Kwave::SampleArray)) ); } // this also starts the conversion automatically if (ok) ok = decoder->decode(widget, adapter); // flush all samples that are still in the adapter adapter.flush(); } else if (ok) { // decode directly without any filter ok = decoder->decode(widget, dst); } dst.flush(); // clean up the filter chain if (mixer) delete mixer; if (rate_converter) delete rate_converter; // remove hourglass QApplication::restoreOverrideCursor(); // failed :-( Q_ASSERT(ok); if (!ok) { delete decoder; decoded_length = 0; continue; } // take care of the meta data, shift all it by "left" and // add it to the signal Kwave::MetaDataList meta_data = decoder->metaData(); // adjust meta data position in case of different sample rate if (!qFuzzyCompare(src_rate, dst_rate)) - meta_data.scalePositions(dst_rate / src_rate, tracks); + meta_data.scalePositions(dst_rate / src_rate); - meta_data.shiftRight(0, left, tracks); + meta_data.shiftRight(0, left); // remove the file info, this must not be handled here, otherwise // this would overwrite the file info of the destination meta_data.remove(meta_data.selectByType( Kwave::FileInfo::metaDataType())); // add the remaining meta data (e.g. labels etc) sig.metaData().add(meta_data); delete decoder; break; } // qDebug("Kwave::MimeData::decode -> decoded_length=%u", decoded_length); return decoded_length; } //*************************************************************************** void Kwave::MimeData::clear() { m_buffer.close(); } //*************************************************************************** //*************************************************************************** diff --git a/libkwave/SignalManager.cpp b/libkwave/SignalManager.cpp index 0b0bd8d2..a7e9d3a4 100644 --- a/libkwave/SignalManager.cpp +++ b/libkwave/SignalManager.cpp @@ -1,2044 +1,2017 @@ /*************************************************************************** SignalManager.cpp - manager class for multi channel signals ------------------- begin : Sun Oct 15 2000 copyright : (C) 2000 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libkwave/ClipBoard.h" #include "libkwave/CodecManager.h" #include "libkwave/Decoder.h" #include "libkwave/Encoder.h" #include "libkwave/FileProgress.h" #include "libkwave/InsertMode.h" #include "libkwave/LabelList.h" #include "libkwave/MemoryManager.h" #include "libkwave/MessageBox.h" #include "libkwave/MultiTrackReader.h" #include "libkwave/MultiTrackWriter.h" #include "libkwave/Parser.h" #include "libkwave/Sample.h" #include "libkwave/Signal.h" #include "libkwave/SignalManager.h" #include "libkwave/String.h" #include "libkwave/Track.h" #include "libkwave/Utils.h" #include "libkwave/Writer.h" #include "libkwave/undo/UndoAction.h" #include "libkwave/undo/UndoAddMetaDataAction.h" #include "libkwave/undo/UndoDeleteAction.h" #include "libkwave/undo/UndoDeleteMetaDataAction.h" #include "libkwave/undo/UndoDeleteTrack.h" #include "libkwave/undo/UndoInsertAction.h" #include "libkwave/undo/UndoInsertTrack.h" #include "libkwave/undo/UndoModifyAction.h" #include "libkwave/undo/UndoModifyMetaDataAction.h" #include "libkwave/undo/UndoSelection.h" #include "libkwave/undo/UndoTransaction.h" #include "libkwave/undo/UndoTransactionGuard.h" #define CASE_COMMAND(x) } else if (parser.command() == _(x)) { //*************************************************************************** Kwave::SignalManager::SignalManager(QWidget *parent) :QObject(), m_parent_widget(parent), m_closed(true), m_empty(true), m_modified(false), m_modified_enabled(true), m_signal(), m_selection(0,0), m_last_selection(0,0), m_last_track_selection(), m_last_length(0), m_playback_controller(*this), m_undo_enabled(false), m_undo_buffer(), m_redo_buffer(), m_undo_transaction(Q_NULLPTR), m_undo_transaction_level(0), m_undo_transaction_lock(QMutex::Recursive), m_meta_data() { // connect to the track's signals Kwave::Signal *sig = &m_signal; connect(sig, SIGNAL(sigTrackInserted(uint,Kwave::Track*)), this, SLOT(slotTrackInserted(uint,Kwave::Track*))); connect(sig, SIGNAL(sigTrackDeleted(uint,Kwave::Track*)), this, SLOT(slotTrackDeleted(uint,Kwave::Track*))); connect(sig, SIGNAL(sigTrackSelectionChanged(bool)), this,SIGNAL(sigTrackSelectionChanged(bool))); connect(sig, SIGNAL(sigSamplesDeleted(unsigned int, sample_index_t, sample_index_t)), this, SLOT(slotSamplesDeleted(unsigned int, sample_index_t, sample_index_t))); connect(sig, SIGNAL(sigSamplesInserted(unsigned int, sample_index_t, sample_index_t)), this, SLOT(slotSamplesInserted(unsigned int, sample_index_t, sample_index_t))); connect(sig, SIGNAL(sigSamplesModified(unsigned int, sample_index_t, sample_index_t)), this, SLOT(slotSamplesModified(unsigned int, sample_index_t, sample_index_t))); } //*************************************************************************** Kwave::SignalManager::~SignalManager() { close(); } //*************************************************************************** int Kwave::SignalManager::loadFile(const QUrl &url) { int res = 0; Kwave::FileProgress *dialog = Q_NULLPTR; // take over the new file name, so that we have a valid signal // name during loading QString filename = url.path(); QFile src(filename); QFileInfo fi(src); { Kwave::FileInfo info(m_meta_data); info.set(Kwave::INF_FILENAME, fi.absoluteFilePath()); m_meta_data.replace(Kwave::MetaDataList(info)); } // work with a copy of meta data, to avoid flicker effects Kwave::MetaDataList meta_data(m_meta_data); // enter and stay in not modified state enableModifiedChange(true); setModified(false); enableModifiedChange(false); // disable undo (discards all undo/redo data) disableUndo(); QString mimetype = Kwave::CodecManager::mimeTypeOf(url); qDebug("SignalManager::loadFile(%s) - [%s]", DBG(url.toDisplayString()), DBG(mimetype)); Kwave::Decoder *decoder = Kwave::CodecManager::decoder(mimetype); while (decoder) { // be sure that the current signal is really closed m_signal.close(); // open the source file if (!(res = decoder->open(m_parent_widget, src))) { qWarning("unable to open source: '%s'", DBG(url.toDisplayString())); res = -EIO; break; } // get the initial meta data from the decoder meta_data = decoder->metaData(); Kwave::FileInfo info(meta_data); // take the preliminary meta data, needed for estimated length m_meta_data = meta_data; // detect stream mode. if so, use one sample as display bool streaming = (!info.length()); // we must change to open state to see the file while // it is loaded m_closed = false; m_empty = false; // create all tracks (empty) unsigned int track; const unsigned int tracks = info.tracks(); const sample_index_t length = info.length(); Q_ASSERT(tracks); if (!tracks) break; for (track = 0; track < tracks; ++track) { Kwave::Track *t = m_signal.appendTrack(length, Q_NULLPTR); Q_ASSERT(t); if (!t || (t->length() != length)) { qWarning("SignalManager::loadFile: out of memory"); res = -ENOMEM; break; } } if (track < tracks) break; // create the multitrack writer as destination // if length was zero -> append mode / decode a stream ? Kwave::InsertMode mode = (streaming) ? Kwave::Append : Kwave::Overwrite; Kwave::MultiTrackWriter writers(*this, allTracks(), mode, 0, (length) ? length-1 : 0); // try to calculate the resulting length, but if this is // not possible, we try to use the source length instead quint64 resulting_size = info.tracks() * info.length() * (info.bits() >> 3); bool use_src_size = (!resulting_size); if (use_src_size) resulting_size = src.size(); // prepare and show the progress dialog dialog = new Kwave::FileProgress(m_parent_widget, QUrl(filename), resulting_size, info.length(), info.rate(), info.bits(), info.tracks()); Q_ASSERT(dialog); if (dialog && use_src_size) { // use source size for progress / stream mode QObject::connect(decoder, SIGNAL(sourceProcessed(quint64)), dialog, SLOT(setBytePosition(quint64))); QObject::connect(&writers, SIGNAL(written(quint64)), dialog, SLOT(setLength(quint64))); } else { // use resulting size percentage for progress QObject::connect(&writers, SIGNAL(progress(qreal)), dialog, SLOT(setValue(qreal))); } QObject::connect(dialog, SIGNAL(canceled()), &writers, SLOT(cancel())); // now decode res = 0; if (!decoder->decode(m_parent_widget, writers)) { qWarning("decoding failed."); res = -EIO; } else { // read information back from the decoder, some settings // might have become available during the decoding process meta_data = decoder->metaData(); info = Kwave::FileInfo(meta_data); } decoder->close(); // check for length info in stream mode if (!res && streaming) { // source was opened in stream mode -> now we have the length writers.flush(); sample_index_t new_length = writers.last(); if (new_length) new_length++; info.setLength(new_length); } else { info.setLength(this->length()); info.setTracks(tracks); } // enter the filename/mimetype and size into the file info info.set(Kwave::INF_FILENAME, fi.absoluteFilePath()); info.set(Kwave::INF_FILESIZE, src.size()); if (!info.contains(Kwave::INF_MIMETYPE)) info.set(Kwave::INF_MIMETYPE, mimetype); // remove the estimated length again, it is no longer needed info.set(Kwave::INF_ESTIMATED_LENGTH, QVariant()); // take over the decoded and updated file info meta_data.replace(Kwave::MetaDataList(info)); m_meta_data = meta_data; // update the length info in the progress dialog if needed if (dialog && use_src_size) { dialog->setLength( quint64(info.length()) * quint64(info.tracks())); dialog->setBytePosition(src.size()); } break; } if (!decoder) { qWarning("unknown file type"); res = -EINVAL; } else { delete decoder; } // process any queued events of the writers, like "sigSamplesInserted" qApp->processEvents(QEventLoop::ExcludeUserInputEvents); // remember the last length and selection m_last_length = length(); rememberCurrentSelection(); // from now on, undo is enabled enableUndo(); // modified can change from now on enableModifiedChange(true); if (dialog) delete dialog; if (res) close(); m_meta_data.dump(); // we now have new meta data emit sigMetaDataChanged(m_meta_data); return res; } //*************************************************************************** int Kwave::SignalManager::save(const QUrl &url, bool selection) { int res = 0; sample_index_t ofs = 0; sample_index_t len = length(); unsigned int tracks = this->tracks(); unsigned int bits = this->bits(); if (selection) { // zero-length -> nothing to do ofs = m_selection.offset(); len = m_selection.length(); tracks = selectedTracks().count(); } if (!tracks || !len) { Kwave::MessageBox::error(m_parent_widget, i18n("Signal is empty, nothing to save.")); return 0; } QString mimetype_name; mimetype_name = Kwave::CodecManager::mimeTypeOf(url); qDebug("SignalManager::save(%s) - [%s] (%d bit, selection=%d)", DBG(url.toDisplayString()), DBG(mimetype_name), bits, selection); Kwave::Encoder *encoder = Kwave::CodecManager::encoder(mimetype_name); Kwave::FileInfo file_info(m_meta_data); if (encoder) { // maybe we now have a new mime type file_info.set(Kwave::INF_MIMETYPE, mimetype_name); // check if we lose information and ask the user if this would // be acceptable QList unsupported = encoder->unsupportedProperties( file_info.properties().keys()); if (!unsupported.isEmpty()) { QString list_of_lost_properties = _("\n"); foreach (const Kwave::FileProperty &p, unsupported) { list_of_lost_properties += i18n(UTF8(file_info.name(p))) + _("\n"); } // show a warning to the user and ask him if he wants to continue if (Kwave::MessageBox::warningContinueCancel(m_parent_widget, i18n("Saving in this format will lose the following " "additional file attribute(s):\n" "%1\n" "Do you still want to continue?", list_of_lost_properties), QString(), QString(), QString(), _("accept_lose_attributes_on_export") ) != KMessageBox::Continue) { delete encoder; return -1; } } // open the destination file QString filename = url.path(); QFile dst(filename); Kwave::MultiTrackReader src(Kwave::SinglePassForward, *this, (selection) ? selectedTracks() : allTracks(), ofs, ofs + len - 1); // update the file information file_info.setLength(len); file_info.setRate(rate()); file_info.setBits(bits); file_info.setTracks(tracks); if (!file_info.contains(Kwave::INF_SOFTWARE) && encoder->supportedProperties().contains(Kwave::INF_SOFTWARE)) { // add our Kwave Software tag const KAboutData about_data = KAboutData::applicationData(); QString software = about_data.displayName() + _("-") + about_data.version() + i18n("(built with KDE Frameworks %1)", _(KXMLGUI_VERSION_STRING)); file_info.set(Kwave::INF_SOFTWARE, software); } if (!file_info.contains(Kwave::INF_CREATION_DATE) && encoder->supportedProperties().contains(Kwave::INF_CREATION_DATE)) { // add a date tag QDate now(QDate::currentDate()); QString date; date = date.sprintf("%04d-%02d-%02d", now.year(), now.month(), now.day()); qDebug("adding date tag: '%s'", DBG(date)); file_info.set(Kwave::INF_CREATION_DATE, date); } // prepare and show the progress dialog Kwave::FileProgress *dialog = new Kwave::FileProgress(m_parent_widget, QUrl(filename), file_info.length() * file_info.tracks() * (file_info.bits() >> 3), file_info.length(), file_info.rate(), file_info.bits(), file_info.tracks()); Q_ASSERT(dialog); QObject::connect(&src, SIGNAL(progress(qreal)), dialog, SLOT(setValue(qreal)), Qt::QueuedConnection); QObject::connect(dialog, SIGNAL(canceled()), &src, SLOT(cancel())); // invoke the encoder... bool encoded = false; m_meta_data.replace(Kwave::MetaDataList(file_info)); if (selection) { // use a copy, don't touch the original ! Kwave::MetaDataList meta = m_meta_data; // we have to adjust all position aware meta data meta.cropByRange(ofs, ofs + len - 1); - // filter out all the track bound meta data that is not selected - meta.cropByTracks(selectedTracks()); - // set the filename in the copy of the fileinfo, the original // file which is currently open keeps it's name Kwave::FileInfo info(meta); info.set(Kwave::INF_FILENAME, filename); meta.replace(Kwave::MetaDataList(info)); encoded = encoder->encode(m_parent_widget, src, dst, meta); } else { // in case of a "save as" -> modify the current filename file_info.set(Kwave::INF_FILENAME, filename); m_meta_data.replace(Kwave::MetaDataList(file_info)); encoded = encoder->encode(m_parent_widget, src, dst, m_meta_data); } if (!encoded) { Kwave::MessageBox::error(m_parent_widget, i18n("An error occurred while saving the file.")); res = -1; } delete encoder; encoder = Q_NULLPTR; if (dialog) { qApp->processEvents(); if (dialog->isCanceled()) { // user really pressed cancel ! Kwave::MessageBox::error(m_parent_widget, i18n("The file has been truncated and " "might be corrupted.")); res = -EINTR; } delete dialog; dialog = Q_NULLPTR; } } else { Kwave::MessageBox::error(m_parent_widget, i18n("Sorry, the file type is not supported.")); res = -EINVAL; } if (!res && !selection) { // saved without error -> no longer modified flushUndoBuffers(); enableModifiedChange(true); setModified(false); } emit sigMetaDataChanged(m_meta_data); qDebug("SignalManager::save(): res=%d",res); return res; } //*************************************************************************** void Kwave::SignalManager::newSignal(sample_index_t samples, double rate, unsigned int bits, unsigned int tracks) { // enter and stay in modified state enableModifiedChange(true); setModified(true); enableModifiedChange(false); // disable undo (discards all undo/redo data) disableUndo(); m_meta_data.clear(); Kwave::FileInfo file_info(m_meta_data); file_info.setRate(rate); file_info.setBits(bits); file_info.setTracks(tracks); m_meta_data.replace(Kwave::MetaDataList(file_info)); // now the signal is considered not to be empty m_closed = false; m_empty = false; // add all empty tracks while (tracks) { m_signal.appendTrack(samples, Q_NULLPTR); tracks--; } // remember the last length m_last_length = samples; file_info.setLength(length()); m_meta_data.replace(Kwave::MetaDataList(file_info)); rememberCurrentSelection(); // from now on, undo is enabled enableUndo(); emit sigMetaDataChanged(m_meta_data); } //*************************************************************************** void Kwave::SignalManager::close() { // stop the playback m_playback_controller.playbackStop(); m_playback_controller.reset(); // fix the modified flag to false enableModifiedChange(true); setModified(false); enableModifiedChange(false); // reset the last length of the signal m_last_length = 0; // disable undo and discard all undo buffers // undo will be re-enabled when a signal is loaded or created disableUndo(); // for safety: flush all undo/redo buffers flushUndoBuffers(); flushRedoBuffer(); // reset the selection m_selection.clear(); m_empty = true; while (tracks()) deleteTrack(tracks() - 1); m_signal.close(); // clear all meta data m_meta_data.clear(); m_closed = true; rememberCurrentSelection(); emit sigMetaDataChanged(m_meta_data); } //*************************************************************************** QString Kwave::SignalManager::signalName() { // if a file is loaded -> path of the URL if it has one QUrl url; url = QUrl(Kwave::FileInfo(m_meta_data).get(Kwave::INF_FILENAME).toString()); if (url.isValid()) return url.path(); // we have something, but no name yet if (!isClosed()) return QString(NEW_FILENAME); // otherwise: closed, nothing loaded return _(""); } //*************************************************************************** const QList Kwave::SignalManager::selectedTracks() { QList list; const unsigned int tracks = this->tracks(); for (unsigned int track = 0; track < tracks; track++) { if (!m_signal.trackSelected(track)) continue; list.append(track); } return list; } //*************************************************************************** const QList Kwave::SignalManager::allTracks() { return m_signal.allTracks(); } //*************************************************************************** int Kwave::SignalManager::executeCommand(const QString &command) { sample_index_t offset = m_selection.offset(); sample_index_t length = m_selection.length(); if (!command.length()) return -EINVAL; Kwave::Parser parser(command); if (false) { // --- undo / redo --- CASE_COMMAND("undo") undo(); CASE_COMMAND("redo") redo(); CASE_COMMAND("undo_all") while (m_undo_enabled && !m_undo_buffer.isEmpty()) undo(); CASE_COMMAND("redo_all") while (!m_redo_buffer.isEmpty()) redo(); // --- copy & paste + clipboard --- CASE_COMMAND("copy") if (length) { Kwave::ClipBoard &clip = Kwave::ClipBoard::instance(); clip.copy( m_parent_widget, *this, selectedTracks(), offset, length ); // remember the last selection rememberCurrentSelection(); } CASE_COMMAND("insert_at") Kwave::ClipBoard &clip = Kwave::ClipBoard::instance(); if (clip.isEmpty()) return 0; if (!selectedTracks().size()) return 0; sample_index_t ofs = parser.toSampleIndex(); Kwave::UndoTransactionGuard undo(*this, i18n("Insert Clipboard at position")); selectRange(ofs, 0); clip.paste(m_parent_widget, *this, ofs, 0); CASE_COMMAND("paste") Kwave::ClipBoard &clip = Kwave::ClipBoard::instance(); if (clip.isEmpty()) return 0; if (!selectedTracks().size()) return 0; Kwave::UndoTransactionGuard undo(*this, i18n("Paste")); clip.paste(m_parent_widget, *this, offset, length); CASE_COMMAND("cut") if (length) { // remember the last selection rememberCurrentSelection(); Kwave::ClipBoard &clip = Kwave::ClipBoard::instance(); clip.copy( m_parent_widget, *this, selectedTracks(), offset, length ); Kwave::UndoTransactionGuard undo(*this, i18n("Cut")); deleteRange(offset, length); selectRange(m_selection.offset(), 0); } CASE_COMMAND("clipboard_flush") Kwave::ClipBoard::instance().clear(); CASE_COMMAND("crop") if (length) { Kwave::UndoTransactionGuard undo(*this, i18n("Crop")); sample_index_t rest = this->length() - offset; rest = (rest > length) ? (rest-length) : 0; QList tracks = selectedTracks(); if (saveUndoDelete(tracks, offset+length, rest) && saveUndoDelete(tracks, 0, offset)) { // remember the last selection rememberCurrentSelection(); unsigned int count = tracks.count(); while (count--) { m_signal.deleteRange(count, offset+length, rest); m_signal.deleteRange(count, 0, offset); } selectRange(0, length); } } CASE_COMMAND("delete") Kwave::UndoTransactionGuard undo(*this, i18n("Delete")); deleteRange(offset, length); selectRange(m_selection.offset(), 0); // CASE_COMMAND("mixpaste") // if (globals.clipboard) { // SignalManager *toinsert = globals.clipboard->getSignal(); // if (toinsert) { // unsigned int clipchan = toinsert->channels(); // unsigned int sourcechan = 0; // // /* ### check if the signal has to be re-sampled ### */ // // for (unsigned int i = 0; i < m_channels; i++) { // Q_ASSERT(signal.at(i)); // if (signal.at(i)) { // signal.at(i)->mixPaste( // toinsert->getSignal(sourcechan) // ); // } // sourcechan++; // sourcechan %= clipchan; // } // } // } CASE_COMMAND("label:delete") int index = parser.toInt(); deleteLabel(index, true); CASE_COMMAND("expandtolabel") Kwave::UndoTransactionGuard undo(*this, i18n("Expand Selection to Label")); sample_index_t selection_left = m_selection.first(); sample_index_t selection_right = m_selection.last(); Kwave::LabelList labels(m_meta_data); if (labels.isEmpty()) return false; // we need labels for this Kwave::Label label_left = Kwave::Label(); Kwave::Label label_right = Kwave::Label(); // the last label <= selection start -> label_left // the first label >= selection end -> label_right foreach (const Kwave::Label &label, labels) { sample_index_t lp = label.pos(); if (lp <= selection_left) label_left = label; if ((lp >= selection_right) && (label_right.isNull())) { label_right = label; break; // done } } // default left label = start of file selection_left = (label_left.isNull()) ? 0 : label_left.pos(); // default right label = end of file selection_right = (label_right.isNull()) ? (this->length() - 1) : label_right.pos(); sample_index_t len = selection_right - selection_left + 1; selectRange(selection_left, len); CASE_COMMAND("selectnextlabels") Kwave::UndoTransactionGuard undo(*this, i18n("Select Next Labels")); sample_index_t selection_left; sample_index_t selection_right = m_selection.last(); Kwave::Label label_left = Kwave::Label(); Kwave::Label label_right = Kwave::Label(); Kwave::LabelList labels(m_meta_data); if (labels.isEmpty()) return false; // we need labels for this // special case: nothing selected -> select up to the first label if (selection_right == 0) { label_right = labels.first(); selection_left = 0; } else { // find the first label starting after the current selection LabelListIterator it(labels); while (it.hasNext()) { Kwave::Label label = it.next(); if (label.pos() >= selection_right) { // take it as selection start label_left = label; // and it's next one as selection end (might be null) label_right = it.hasNext() ? it.next() : Kwave::Label(); break; } } // default selection start = last label if (label_left.isNull()) label_left = labels.last(); if (label_left.isNull()) return false; // no labels at all !? selection_left = label_left.pos(); } // default selection end = end of the file selection_right = (label_right.isNull()) ? (this->length() - 1) : label_right.pos(); sample_index_t len = (selection_right > selection_left) ? (selection_right - selection_left + 1) : 1; selectRange(selection_left, len); CASE_COMMAND("selectprevlabels") Kwave::UndoTransactionGuard undo(*this, i18n("Select Previous Labels")); sample_index_t selection_left = selection().first(); sample_index_t selection_right = selection().last(); Kwave::Label label_left = Kwave::Label(); Kwave::Label label_right = Kwave::Label(); Kwave::LabelList labels(m_meta_data); if (labels.isEmpty()) return false; // we need labels for this // find the last label before the start of the selection foreach (const Kwave::Label &label, labels) { if (label.pos() > selection_left) break; // done label_left = label_right; label_right = label; } // default selection start = start of file selection_left = (label_left.isNull()) ? 0 : label_left.pos(); // default selection end = first label if (label_right.isNull()) label_right = labels.first(); if (label_right.isNull()) return false; // no labels at all !? selection_right = label_right.pos(); sample_index_t len = selection_right - selection_left + 1; selectRange(selection_left, len); // --- track related functions --- CASE_COMMAND("add_track") appendTrack(); CASE_COMMAND("delete_track") Kwave::Parser p(command); unsigned int track = p.toUInt(); if (track >= tracks()) return -EINVAL; deleteTrack(track); CASE_COMMAND("insert_track") Kwave::Parser p(command); unsigned int track = p.toUInt(); insertTrack(track); // track selection CASE_COMMAND("select_track:all") Kwave::UndoTransactionGuard undo(*this, i18n("Select All Tracks")); foreach (unsigned int track, allTracks()) selectTrack(track, true); CASE_COMMAND("select_track:none") Kwave::UndoTransactionGuard undo(*this, i18n("Deselect all tracks")); foreach (unsigned int track, allTracks()) selectTrack(track, false); CASE_COMMAND("select_track:invert") Kwave::UndoTransactionGuard undo(*this, i18n("Invert Track Selection")); foreach (unsigned int track, allTracks()) selectTrack(track, !trackSelected(track)); CASE_COMMAND("select_track:on") unsigned int track = parser.toUInt(); if (track >= tracks()) return -EINVAL; Kwave::UndoTransactionGuard undo(*this, i18n("Select Track")); selectTrack(track, true); CASE_COMMAND("select_track:off") unsigned int track = parser.toUInt(); if (track >= tracks()) return -EINVAL; Kwave::UndoTransactionGuard undo(*this, i18n("Deselect Track")); selectTrack(track, false); CASE_COMMAND("select_track:toggle") unsigned int track = parser.toUInt(); if (track >= tracks()) return -EINVAL; Kwave::UndoTransactionGuard undo(*this, i18n("Toggle Track Selection")); selectTrack(track, !(trackSelected(track))); // playback control CASE_COMMAND("playback_start") m_playback_controller.playbackStart(); CASE_COMMAND("fileinfo") QString property = parser.firstParam(); QString value = parser.nextParam(); Kwave::FileInfo info(m_meta_data); bool found = false; foreach (Kwave::FileProperty p, info.allKnownProperties()) { if (info.name(p) == property) { if (value.length()) info.set(p, QVariant(value)); // add/modify else info.set(p, QVariant()); // delete found = true; break; } } if (found) { m_meta_data.replace(Kwave::MetaDataList(info)); // we now have new meta data emit sigMetaDataChanged(m_meta_data); } else return -EINVAL; CASE_COMMAND("dump_metadata") qDebug("DUMP OF META DATA => %s", DBG(parser.firstParam())); m_meta_data.dump(); } else { return -ENOSYS; } return 0; } //*************************************************************************** void Kwave::SignalManager::appendTrack() { Kwave::UndoTransactionGuard undo(*this, i18n("Append Track")); insertTrack(tracks()); } //*************************************************************************** void Kwave::SignalManager::insertTrack(unsigned int index) { Kwave::UndoTransactionGuard undo(*this, i18n("Insert Track")); const unsigned int count = tracks(); Q_ASSERT(index <= count); if (index > count) index = count; // undo action for the track insert if (m_undo_enabled && !registerUndoAction( new(std::nothrow) Kwave::UndoInsertTrack(m_signal, index))) return; // if the signal is currently empty, use the last // known length instead of the current one sample_index_t len = (count) ? length() : m_last_length; if (index >= count) { // do an "append" m_signal.appendTrack(len, Q_NULLPTR); } else { - if (m_undo_enabled) { - // undo action for the corresponding meta data change - QList tracks; - for (unsigned int t = index; t < count; t++) tracks.append(t); - Kwave::MetaDataList list = m_meta_data.selectByTracks(tracks); - if (!list.isEmpty() && !registerUndoAction( - new(std::nothrow) Kwave::UndoModifyMetaDataAction(list))) - return; - } - - // adjust the track bound meta data - m_meta_data.insertTrack(index); - // insert into the list m_signal.insertTrack(index, len, Q_NULLPTR); } // remember the last length m_last_length = length(); } //*************************************************************************** void Kwave::SignalManager::deleteTrack(unsigned int index) { Kwave::UndoTransactionGuard undo(*this, i18n("Delete Track")); const unsigned int count = tracks(); Q_ASSERT(index <= count); if (index > count) return; if (m_undo_enabled) { // undo action for the track deletion if (!registerUndoAction(new(std::nothrow) UndoDeleteTrack(m_signal, index))) return; - - // undo action for the corresponding meta data change - QList tracks; - for (unsigned int t = index; t < count; t++) tracks.append(t); - Kwave::MetaDataList list = m_meta_data.selectByTracks(tracks); - if (!list.isEmpty() && !registerUndoAction( - new(std::nothrow) Kwave::UndoModifyMetaDataAction(list))) - return; } - // adjust the track bound meta data - m_meta_data.deleteTrack(index); - setModified(true); m_signal.deleteTrack(index); } //*************************************************************************** void Kwave::SignalManager::slotTrackInserted(unsigned int index, Kwave::Track *track) { setModified(true); Kwave::FileInfo file_info(m_meta_data); file_info.setTracks(tracks()); m_meta_data.replace(Kwave::MetaDataList(file_info)); emit sigTrackInserted(index, track); emit sigMetaDataChanged(m_meta_data); } //*************************************************************************** void Kwave::SignalManager::slotTrackDeleted(unsigned int index, Kwave::Track *track) { setModified(true); Kwave::FileInfo file_info(m_meta_data); file_info.setTracks(tracks()); m_meta_data.replace(Kwave::MetaDataList(file_info)); emit sigTrackDeleted(index, track); emit sigMetaDataChanged(m_meta_data); } //*************************************************************************** void Kwave::SignalManager::slotSamplesInserted(unsigned int track, sample_index_t offset, sample_index_t length) { // remember the last known length m_last_length = m_signal.length(); setModified(true); // only adjust the meta data once per operation QList tracks = selectedTracks(); - if (track == tracks[0]) { - m_meta_data.shiftRight(offset, length, tracks); + if (track == tracks.first()) { + m_meta_data.shiftRight(offset, length); } emit sigSamplesInserted(track, offset, length); Kwave::FileInfo info(m_meta_data); info.setLength(m_last_length); m_meta_data.replace(Kwave::MetaDataList(info)); emit sigMetaDataChanged(m_meta_data); } //*************************************************************************** void Kwave::SignalManager::slotSamplesDeleted(unsigned int track, sample_index_t offset, sample_index_t length) { // remember the last known length m_last_length = m_signal.length(); setModified(true); // only adjust the meta data once per operation QList tracks = selectedTracks(); - if (track == tracks[0]) { - m_meta_data.shiftLeft(offset, length, tracks); + if (track == tracks.first()) { + m_meta_data.shiftLeft(offset, length); } emit sigSamplesDeleted(track, offset, length); Kwave::FileInfo info(m_meta_data); info.setLength(m_last_length); m_meta_data.replace(Kwave::MetaDataList(info)); emit sigMetaDataChanged(m_meta_data); } //*************************************************************************** void Kwave::SignalManager::slotSamplesModified(unsigned int track, sample_index_t offset, sample_index_t length) { setModified(true); emit sigSamplesModified(track, offset, length); } //*************************************************************************** bool Kwave::SignalManager::deleteRange(sample_index_t offset, sample_index_t length, const QList &track_list) { if (!length || track_list.isEmpty()) return true; // nothing to do // put the selected meta data into a undo action if (m_undo_enabled) { if (!registerUndoAction(new(std::nothrow) UndoDeleteMetaDataAction( - m_meta_data.copy(offset, length, track_list)))) + m_meta_data.copy(offset, length)))) { abortUndoTransaction(); return false; } - m_meta_data.deleteRange(offset, length, track_list); + m_meta_data.deleteRange(offset, length); // store undo data for all audio data (without meta data) if (!registerUndoAction(new(std::nothrow) UndoDeleteAction( m_parent_widget, track_list, offset, length))) { abortUndoTransaction(); return false; } } else { // delete without undo - m_meta_data.deleteRange(offset, length, track_list); + m_meta_data.deleteRange(offset, length); } // delete the ranges in all tracks // (this makes all metadata positions after the selected range invalid) foreach (unsigned int track, track_list) { m_signal.deleteRange(track, offset, length); } emit sigMetaDataChanged(m_meta_data); return true; } //*************************************************************************** bool Kwave::SignalManager::deleteRange(sample_index_t offset, sample_index_t length) { return deleteRange(offset, length, selectedTracks()); } //*************************************************************************** bool Kwave::SignalManager::insertSpace(sample_index_t offset, sample_index_t length, const QList &track_list) { if (!length) return true; // nothing to do Kwave::UndoTransactionGuard undo(*this, i18n("Insert Space")); unsigned int count = track_list.count(); if (!count) return true; // nothing to do // first store undo data for all tracks unsigned int track; if (m_undo_enabled) { if (!registerUndoAction(new(std::nothrow) Kwave::UndoInsertAction( m_parent_widget, track_list, offset, length))) return false; } // then insert space into all tracks foreach (track, track_list) { m_signal.insertSpace(track, offset, length); } return true; } //*************************************************************************** void Kwave::SignalManager::selectRange(sample_index_t offset, sample_index_t length) { // first do some range checking sample_index_t len = this->length(); if (length > len) length = len; if (offset >= len) offset = (len) ? (len - 1) : 0; if ((offset + length) > len) length = len - offset; m_selection.select(offset, length); } //*************************************************************************** void Kwave::SignalManager::selectTracks(QList &track_list) { unsigned int track; unsigned int n_tracks = tracks(); for (track = 0; track < n_tracks; track++) { bool old_select = m_signal.trackSelected(track); bool new_select = track_list.contains(track); if (new_select != old_select) { m_signal.selectTrack(track, new_select); } } } //*************************************************************************** void Kwave::SignalManager::selectTrack(unsigned int track, bool select) { bool old_select = m_signal.trackSelected(track); if (select != old_select) { m_signal.selectTrack(track, select); } } //*************************************************************************** QList Kwave::SignalManager::stripes( const QList &track_list, sample_index_t left, sample_index_t right) { QList stripes; foreach (unsigned int track, track_list) { Kwave::Stripe::List s = m_signal.stripes(track, left, right); if (s.isEmpty()) { stripes.clear(); // something went wrong -> abort break; } stripes.append(s); } return stripes; } //*************************************************************************** bool Kwave::SignalManager::mergeStripes( const QList &stripes, const QList &track_list) { Q_ASSERT(stripes.count() == track_list.count()); if (stripes.count() != track_list.count()) return false; QListIterator it_s(stripes); QListIterator it_t(track_list); while (it_s.hasNext() && it_t.hasNext()) { Kwave::Stripe::List s = it_s.next(); unsigned int t = it_t.next(); if (!m_signal.mergeStripes(s, t)) return false; // operation failed } return true; } //*************************************************************************** Kwave::PlaybackController &Kwave::SignalManager::playbackController() { return m_playback_controller; } //*************************************************************************** void Kwave::SignalManager::startUndoTransaction(const QString &name) { if (!m_undo_enabled) return; // undo is currently not enabled QMutexLocker lock(&m_undo_transaction_lock); // check for modified selection checkSelectionChange(); // increase recursion level m_undo_transaction_level++; // start/create a new transaction if none existing if (!m_undo_transaction) { // if a new action starts, discard all redo actions ! flushRedoBuffer(); m_undo_transaction = new(std::nothrow) Kwave::UndoTransaction(name); Q_ASSERT(m_undo_transaction); if (!m_undo_transaction) return; // give all registered undo handlers a chance to register their own // undo actions if (!m_undo_manager.startUndoTransaction(m_undo_transaction)) { delete m_undo_transaction; m_undo_transaction = Q_NULLPTR; } // if it is the start of the transaction, also create one // for the selection UndoAction *selection = new(std::nothrow) Kwave::UndoSelection(*this); Q_ASSERT(selection); if (selection && selection->store(*this)) { m_undo_transaction->append(selection); } else { // out of memory delete selection; delete m_undo_transaction; m_undo_transaction = Q_NULLPTR; } } } //*************************************************************************** void Kwave::SignalManager::closeUndoTransaction() { QMutexLocker lock(&m_undo_transaction_lock); // decrease recursion level if (!m_undo_transaction_level) return; // undo was not enabled ? m_undo_transaction_level--; if (!m_undo_transaction_level) { // append the current transaction to the undo buffer if // not empty if (m_undo_transaction) { if (!m_undo_transaction->isEmpty()) { // if the transaction has been aborted, undo all actions // that have currently been queued but do not // use the transaction any more, instead delete it. if (m_undo_transaction->isAborted()) { qDebug("SignalManager::closeUndoTransaction(): aborted"); while (!m_undo_transaction->isEmpty()) { UndoAction *undo_action; UndoAction *redo_action; // unqueue the undo action undo_action = m_undo_transaction->takeLast(); Q_ASSERT(undo_action); if (!undo_action) continue; // execute the undo operation redo_action = undo_action->undo(*this, false); // remove the old undo action if no longer used if (redo_action && (redo_action != undo_action)) delete redo_action; delete undo_action; } delete m_undo_transaction; m_undo_transaction = Q_NULLPTR; } else { m_undo_buffer.append(m_undo_transaction); } } else { qDebug("SignalManager::closeUndoTransaction(): empty"); delete m_undo_transaction; m_undo_transaction = Q_NULLPTR; } } // dump, for debugging // if (m_undo_transaction) // m_undo_transaction->dump("closed undo transaction: "); // declare the current transaction as "closed" rememberCurrentSelection(); m_undo_transaction = Q_NULLPTR; emitUndoRedoInfo(); } } //*************************************************************************** void Kwave::SignalManager::enableUndo() { m_undo_enabled = true; emitUndoRedoInfo(); } //*************************************************************************** void Kwave::SignalManager::disableUndo() { if (m_undo_transaction_level) qWarning("SignalManager::disableUndo(): undo transaction level=%u", m_undo_transaction_level); flushUndoBuffers(); m_undo_enabled = false; } //*************************************************************************** void Kwave::SignalManager::flushUndoBuffers() { QMutexLocker lock(&m_undo_transaction_lock); // if the signal was modified, it will stay in this state, it is // not possible to change to "non-modified" state through undo if ((!m_undo_buffer.isEmpty()) && (m_modified)) { enableModifiedChange(false); } // clear all buffers qDeleteAll(m_undo_buffer); qDeleteAll(m_redo_buffer); m_undo_buffer.clear(); m_redo_buffer.clear(); emitUndoRedoInfo(); } //*************************************************************************** void Kwave::SignalManager::abortUndoTransaction() { // abort the current transaction if (m_undo_transaction) m_undo_transaction->abort(); } //*************************************************************************** void Kwave::SignalManager::flushRedoBuffer() { qDeleteAll(m_redo_buffer); m_redo_buffer.clear(); emitUndoRedoInfo(); } //*************************************************************************** bool Kwave::SignalManager::continueWithoutUndo() { // undo has not been enabled before? if (!m_undo_transaction) return true; // transaction has been aborted before if (m_undo_transaction->isAborted()) return false; // transaction is empty -> must have been flushed before, otherwise // it would contain at least a undo action for the selection // => user already has pressed "continue" if (m_undo_transaction->isEmpty()) return true; if (Kwave::MessageBox::warningContinueCancel(m_parent_widget, _("") + i18n("Not enough memory for saving undo information.") + _("

") + i18n("Do you want to continue without the possibility to undo?") + _("

") + i18n("Hint: you can configure the amount of memory
" "available for undo under '%1'/'%2'.", i18n("Settings"), i18n("Memory") + _("
"))) == KMessageBox::Continue) { // the signal was modified, it will stay in this state, it is // not possible to change to "non-modified" state through undo // from now on... setModified(true); enableModifiedChange(false); // flush the current undo transaction while (!m_undo_transaction->isEmpty()) { Kwave::UndoAction *undo_action = m_undo_transaction->takeLast(); if (undo_action) delete undo_action; } // flush all undo/redo buffers flushUndoBuffers(); return true; } // Set the undo transaction into "aborted" state. The final // closeUndoTransaction() will take care of the rest when // detecting that state and clean up... abortUndoTransaction(); return false; } //*************************************************************************** bool Kwave::SignalManager::registerUndoAction(Kwave::UndoAction *action) { QMutexLocker lock(&m_undo_transaction_lock); if (!action) return continueWithoutUndo(); // if undo is not enabled, this will fail -> discard the action if (!m_undo_enabled) { delete action; return true; } // check if the undo action is too large qint64 limit_mb = Kwave::MemoryManager::instance().undoLimit(); qint64 needed_size = action->undoSize(); qint64 needed_mb = needed_size >> 20; if (needed_mb > limit_mb) { delete action; return continueWithoutUndo(); } // undo has been aborted before ? if (!m_undo_transaction) return true; // transaction has been aborted before if (m_undo_transaction->isAborted()) { delete action; return true; } // make room... freeUndoMemory(needed_size); // now we might have enough place to append the undo action // and store all undo info if (!action->store(*this)) { delete action; return continueWithoutUndo(); } // everything went ok, register internally m_undo_transaction->append(action); return true; } //*************************************************************************** bool Kwave::SignalManager::saveUndoDelete(QList &track_list, sample_index_t offset, sample_index_t length) { if (!m_undo_enabled) return true; if (track_list.isEmpty()) return true; // create a undo action for deletion UndoDeleteAction *action = new(std::nothrow) UndoDeleteAction(m_parent_widget, track_list, offset, length); if (!registerUndoAction(action)) return false; return true; } //*************************************************************************** qint64 Kwave::SignalManager::usedUndoRedoMemory() { qint64 size = 0; foreach (Kwave::UndoTransaction *undo, m_undo_buffer) if (undo) size += undo->undoSize(); foreach (Kwave::UndoTransaction *redo, m_redo_buffer) if (redo) size += redo->undoSize(); return size; } //*************************************************************************** void Kwave::SignalManager::freeUndoMemory(qint64 needed) { qint64 size = usedUndoRedoMemory() + needed; qint64 undo_limit = Kwave::MemoryManager::instance().undoLimit() << 20; // remove old undo actions if not enough free memory while (!m_undo_buffer.isEmpty() && (size > undo_limit)) { Kwave::UndoTransaction *undo = m_undo_buffer.takeFirst(); if (!undo) continue; qint64 s = undo->undoSize(); size = (size >= s) ? (size - s) : 0; delete undo; // if the signal was modified, it will stay in this state, it is // not possible to change to "non-modified" state through undo if (m_modified) enableModifiedChange(false); } // remove old redo actions if still not enough memory while (!m_redo_buffer.isEmpty() && (size > undo_limit)) { Kwave::UndoTransaction *redo = m_redo_buffer.takeLast(); if (!redo) continue; qint64 s = redo->undoSize(); size = (size >= s) ? (size - s) : 0; delete redo; } } //*************************************************************************** void Kwave::SignalManager::emitUndoRedoInfo() { QString undo_name = QString(); QString redo_name = QString(); if (m_undo_enabled) { Kwave::UndoTransaction *transaction; // get the description of the last undo action if (!m_undo_buffer.isEmpty()) { transaction = m_undo_buffer.last(); if (transaction) undo_name = transaction->description(); if (!undo_name.length()) undo_name = i18n("Last Action"); } // get the description of the last redo action if (!m_redo_buffer.isEmpty()) { transaction = m_redo_buffer.first(); if (transaction) redo_name = transaction->description(); if (!redo_name.length()) redo_name = i18n("Last Action"); } } // now emit the undo/redo transaction names emit sigUndoRedoInfo(undo_name, redo_name); } //*************************************************************************** void Kwave::SignalManager::undo() { QMutexLocker lock(&m_undo_transaction_lock); // check for modified selection checkSelectionChange(); // remember the last selection rememberCurrentSelection(); // get the last undo transaction and abort if none present if (m_undo_buffer.isEmpty()) return; Kwave::UndoTransaction *undo_transaction = m_undo_buffer.takeLast(); if (!undo_transaction) return; // dump, for debugging // undo_transaction->dump("before undo: "); // temporarily disable undo while undo itself is running bool old_undo_enabled = m_undo_enabled; m_undo_enabled = false; // get free memory for redo qint64 undo_limit = Kwave::MemoryManager::instance().undoLimit() << 20; qint64 redo_size = undo_transaction->redoSize(); qint64 undo_size = undo_transaction->undoSize(); Kwave::UndoTransaction *redo_transaction = Q_NULLPTR; if ((redo_size > undo_size) && (redo_size - undo_size > undo_limit)) { // not enough memory for redo qWarning("SignalManager::undo(): not enough memory for redo !"); } else { // only free the memory if it will be used freeUndoMemory(redo_size); // create a new redo transaction QString name = undo_transaction->description(); redo_transaction = new(std::nothrow) Kwave::UndoTransaction(name); Q_ASSERT(redo_transaction); } // if *one* redo fails, all following redoes will also fail or // produce inconsistent data -> remove all of them ! if (!redo_transaction) { flushRedoBuffer(); qDebug("SignalManager::undo(): redo buffer flushed!"); } // set hourglass cursor QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); // execute all undo actions and store the resulting redo // actions into the redo transaction while (!undo_transaction->isEmpty()) { UndoAction *undo_action; UndoAction *redo_action; // unqueue the undo action undo_action = undo_transaction->takeLast(); Q_ASSERT(undo_action); if (!undo_action) continue; // execute the undo operation redo_action = undo_action->undo(*this, (redo_transaction != Q_NULLPTR)); // remove the old undo action if no longer used if (redo_action != undo_action) { delete undo_action; } // queue the action into the redo transaction if (redo_action) { if (redo_transaction) { redo_transaction->prepend(redo_action); } else { // redo is not usable :-( delete redo_action; } } } // now the undo_transaction should be empty -> get rid of it Q_ASSERT(undo_transaction->isEmpty()); delete undo_transaction; if (redo_transaction && (redo_transaction->count() < 1)) { // if there is no redo action -> no redo possible qWarning("SignalManager::undo(): no redo possible"); delete redo_transaction; redo_transaction = Q_NULLPTR; } // check whether the selection has changed, if yes: put a undo action // for this selection change at the end of the redo transaction if (redo_transaction) { bool range_modified = !(m_selection == m_last_selection); QList tracks = selectedTracks(); bool tracks_modified = !(tracks == m_last_track_selection); if (range_modified || tracks_modified) { UndoAction *redo_action = new(std::nothrow) Kwave::UndoSelection( *this, m_last_track_selection, m_last_selection.offset(), m_last_selection.length()); Q_ASSERT(redo_action); if (redo_action) redo_transaction->append(redo_action); } } // find out if there is still an action in the undo buffer // that has to do with modification of the signal if (m_modified) { bool stay_modified = false; foreach (Kwave::UndoTransaction *transaction, m_undo_buffer) { if (!transaction) continue; if (transaction->containsModification()) { stay_modified = true; break; } } if (!stay_modified) { // try to return to non-modified mode (might be a nop if // not enabled) setModified(false); } } // save "redo" information if possible if (redo_transaction) m_redo_buffer.prepend(redo_transaction); // remember the last selection rememberCurrentSelection(); // re-enable undo m_undo_enabled = old_undo_enabled; // finished / buffers have changed, emit new undo/redo info emitUndoRedoInfo(); // maybe the meta data has changed emit sigMetaDataChanged(m_meta_data); // remove hourglass QApplication::restoreOverrideCursor(); } //*************************************************************************** void Kwave::SignalManager::redo() { QMutexLocker lock(&m_undo_transaction_lock); // get the last redo transaction and abort if none present if (m_redo_buffer.isEmpty()) return; Kwave::UndoTransaction *redo_transaction = m_redo_buffer.takeFirst(); if (!redo_transaction) return; // check for modified selection checkSelectionChange(); // temporarily disable undo while redo is running bool old_undo_enabled = m_undo_enabled; m_undo_enabled = false; // get free memory for undo qint64 undo_limit = Kwave::MemoryManager::instance().undoLimit() << 20; qint64 undo_size = redo_transaction->undoSize(); qint64 redo_size = redo_transaction->redoSize(); Kwave::UndoTransaction *undo_transaction = Q_NULLPTR; if ((undo_size > redo_size) && (undo_size - redo_size > undo_limit)) { // not enough memory for undo qWarning("SignalManager::redo(): not enough memory for undo !"); } else { // only free the memory if it will be used freeUndoMemory(undo_size); // create a new undo transaction QString name = redo_transaction->description(); undo_transaction = new(std::nothrow) Kwave::UndoTransaction(name); Q_ASSERT(undo_transaction); } // if *one* undo fails, all following undoes will also fail or // produce inconsistent data -> remove all of them ! if (!undo_transaction) { qDeleteAll(m_undo_buffer); m_undo_buffer.clear(); } else { m_undo_buffer.append(undo_transaction); } // set hourglass cursor QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); // execute all redo actions and store the resulting undo // actions into the undo transaction bool modified = false; while (!redo_transaction->isEmpty()) { UndoAction *undo_action; UndoAction *redo_action; // unqueue the undo action redo_action = redo_transaction->takeFirst(); // execute the redo operation Q_ASSERT(redo_action); if (!redo_action) continue; modified |= redo_transaction->containsModification(); undo_action = redo_action->undo(*this, (undo_transaction != Q_NULLPTR)); // remove the old redo action if no longer used if (redo_action != undo_action) { delete redo_action; } // queue the action into the undo transaction if (undo_action) { if (undo_transaction) { undo_transaction->append(undo_action); } else { // undo is not usable :-( delete undo_action; } } } // now the redo_transaction should be empty -> get rid of it Q_ASSERT(redo_transaction->isEmpty()); delete redo_transaction; if (undo_transaction && (undo_transaction->count() < 1)) { // if there is no undo action -> no undo possible qWarning("SignalManager::redo(): no undo possible"); m_undo_buffer.removeAll(undo_transaction); delete undo_transaction; } // if the redo operation modified something, // we have to update the "modified" flag of the current signal if (modified) setModified(true); // remember the last selection rememberCurrentSelection(); // re-enable undo m_undo_enabled = old_undo_enabled; // finished / buffers have changed, emit new undo/redo info emitUndoRedoInfo(); // maybe the meta data has changed emit sigMetaDataChanged(m_meta_data); // remove hourglass QApplication::restoreOverrideCursor(); } //*************************************************************************** void Kwave::SignalManager::setModified(bool mod) { if (!m_modified_enabled) return; if (m_modified != mod) { m_modified = mod; // qDebug("SignalManager::setModified(%d)",mod); emit sigModified(); } } //*************************************************************************** void Kwave::SignalManager::enableModifiedChange(bool en) { m_modified_enabled = en; } //*************************************************************************** void Kwave::SignalManager::setFileInfo(const Kwave::FileInfo &new_info, bool with_undo) { if (m_undo_enabled && with_undo) { /* save data for undo */ Kwave::UndoTransactionGuard undo_transaction(*this, i18n("Modify File Info")); Kwave::FileInfo old_inf(m_meta_data); if (!registerUndoAction( new(std::nothrow) Kwave::UndoModifyMetaDataAction( Kwave::MetaDataList(old_inf)))) return; } m_meta_data.replace(Kwave::MetaDataList(new_info)); setModified(true); emitUndoRedoInfo(); emit sigMetaDataChanged(m_meta_data); } //*************************************************************************** Kwave::Label Kwave::SignalManager::findLabel(sample_index_t pos) { Kwave::LabelList labels(m_meta_data); foreach (const Kwave::Label &label, labels) { if (label.pos() == pos) return label; // found it } return Kwave::Label(); // nothing found } //*************************************************************************** int Kwave::SignalManager::labelIndex(const Kwave::Label &label) const { int index = 0; Kwave::LabelList labels(m_meta_data); foreach (const Kwave::Label &l, labels) { if (l == label) return index; // found it index++; } return -1; // nothing found*/ } //*************************************************************************** Kwave::Label Kwave::SignalManager::addLabel(sample_index_t pos, const QString &name) { // if there already is a label at the given position, do nothing if (!findLabel(pos).isNull()) return Kwave::Label(); // create a new label Kwave::Label label(pos, name); // register the undo action if (m_undo_enabled) { Kwave::UndoTransactionGuard undo(*this, i18n("Add Label")); if (!registerUndoAction(new(std::nothrow) UndoAddMetaDataAction( Kwave::MetaDataList(label)))) return Kwave::Label(); } // put the label into the list m_meta_data.add(label); // register this as a modification setModified(true); emit sigMetaDataChanged(m_meta_data); return label; } //*************************************************************************** void Kwave::SignalManager::deleteLabel(int index, bool with_undo) { Kwave::LabelList labels(m_meta_data); int count = Kwave::toInt(labels.count()); if (!count) return; if (index == -1) { // special handling for index == -1 -> delete all labels if (with_undo) startUndoTransaction(i18n("Delete All Labels")); for (index = count - 1; index >= 0; --index) { Kwave::MetaData label(labels.at(index)); if (with_undo) { if (!registerUndoAction(new(std::nothrow) UndoDeleteMetaDataAction(Kwave::MetaDataList(label)))) break; } m_meta_data.remove(label); } } else { // delete a single label if ((index < 0) || (index >= count)) return; Kwave::MetaData label(labels.at(index)); // register the undo action if (with_undo) { startUndoTransaction(i18n("Delete Label")); if (!registerUndoAction(new(std::nothrow) UndoDeleteMetaDataAction(Kwave::MetaDataList(label)))) { abortUndoTransaction(); return; } } m_meta_data.remove(label); } if (with_undo) closeUndoTransaction(); // register this as a modification setModified(true); emit sigMetaDataChanged(m_meta_data); } //*************************************************************************** bool Kwave::SignalManager::modifyLabel(int index, sample_index_t pos, const QString &name, bool with_undo) { Kwave::LabelList labels(m_meta_data); if ((index < 0) || (index >= Kwave::toInt(labels.count()))) return false; Kwave::Label label = labels.at(index); // check: if the label should be moved and there already is a label // at the new position -> fail if ((pos != label.pos()) && !findLabel(pos).isNull()) return false; // add a undo action if (with_undo && m_undo_enabled) { Kwave::UndoModifyMetaDataAction *undo_modify = new(std::nothrow) Kwave::UndoModifyMetaDataAction( Kwave::MetaDataList(label)); if (!registerUndoAction(undo_modify)) return false; } // now modify the label label.moveTo(pos); label.rename(name); m_meta_data.add(label); // register this as a modification setModified(true); emit sigMetaDataChanged(m_meta_data); return true; } //*************************************************************************** void Kwave::SignalManager::mergeMetaData(const Kwave::MetaDataList &meta_data) { - m_meta_data.merge(meta_data); + m_meta_data.add(meta_data); emit sigMetaDataChanged(m_meta_data); } //*************************************************************************** void Kwave::SignalManager::rememberCurrentSelection() { m_last_selection = m_selection; m_last_track_selection = selectedTracks(); } //*************************************************************************** void Kwave::SignalManager::checkSelectionChange() { if (m_undo_transaction_level) return; // detect sample selection change bool range_modified = !(m_selection == m_last_selection); // detect track selection change QList tracks = selectedTracks(); bool tracks_modified = !(tracks == m_last_track_selection); if (range_modified || tracks_modified) { // selection has changed since last undo/redo operation // qDebug("SignalManager::checkSelectionChange() => manually modified"); // qDebug(" before: [%8u...%8u]", m_last_selection.first(), m_last_selection.last()); // qDebug(" after : [%8u...%8u]", m_selection.first(), m_selection.last()); // temporarily activate the previous selection (last stored) Kwave::Selection new_selection(m_selection); m_selection = m_last_selection; selectTracks(m_last_track_selection); // save the last selection into a undo action if (tracks_modified && !range_modified) Kwave::UndoTransactionGuard undo(*this, i18n("Manual Track Selection")); else Kwave::UndoTransactionGuard undo(*this, i18n("Manual Selection")); // restore the current selection again m_selection = new_selection; selectTracks(tracks); } } //*************************************************************************** //*************************************************************************** diff --git a/libkwave/undo/UndoAddMetaDataAction.cpp b/libkwave/undo/UndoAddMetaDataAction.cpp index 404d9bc3..107b3bb8 100644 --- a/libkwave/undo/UndoAddMetaDataAction.cpp +++ b/libkwave/undo/UndoAddMetaDataAction.cpp @@ -1,195 +1,178 @@ /*************************************************************************** UndoAddMetaDataAction.cpp - Undo action for insertion of labels ------------------- begin : Wed Aug 16 2006 copyright : (C) 2006 by Thomas Eschenbacher email : Thomas Eschenbacher ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include "libkwave/MetaData.h" #include "libkwave/SignalManager.h" #include "libkwave/undo/UndoAddMetaDataAction.h" #include "libkwave/undo/UndoDeleteMetaDataAction.h" //*************************************************************************** Kwave::UndoAddMetaDataAction::UndoAddMetaDataAction( const Kwave::MetaDataList &meta_data) :Kwave::UndoAction(), m_description(), m_offset(0), - m_length(SAMPLE_INDEX_MAX), - m_tracks() + m_length(SAMPLE_INDEX_MAX) { sample_index_t first = SAMPLE_INDEX_MAX; sample_index_t last = 0; // sanity check: list should not be empty Q_ASSERT(!meta_data.isEmpty()); if (meta_data.isEmpty()) return; /* * loop over the list to find out the first/last sample offset * and the list of affected tracks */ foreach (const Kwave::MetaData &m, meta_data) { // search over a list of known properties which contain range/position QStringList properties = Kwave::MetaData::positionBoundPropertyNames(); foreach (const QString &tag, properties) { // check for a "start" property QVariant v = m[tag]; bool ok = false; sample_index_t pos = static_cast(v.toULongLong(&ok)); if (ok && (pos < first)) first = pos; if (ok && (pos > last)) last = pos; } - - // convert the track list into a usable list of unsigned int - const QList v_track_list = - m[Kwave::MetaData::STDPROP_TRACKS].toList(); - QList bound_tracks; - foreach (const QVariant &v, v_track_list) { - bool ok = false; - unsigned int t = v.toUInt(&ok); - if (ok) bound_tracks += t; - } - - foreach (unsigned int t, bound_tracks) - if (!m_tracks.contains(t)) m_tracks.append(t);; } // fix first/last in case that nothing was found, select everything if (first > last) { m_offset = SAMPLE_INDEX_MAX; m_length = 0; } else { m_offset = first; m_length = (last - first) + 1; } - if (!m_tracks.isEmpty()) - std::sort(m_tracks.begin(), - m_tracks.end(), std::greater()); /* * determine the description of the action */ for (;;) { QString name; QList values = meta_data.values(); Q_ASSERT(!values.isEmpty()); if (!values.isEmpty()) { const Kwave::MetaData &m = values.first(); if (m.hasProperty(Kwave::MetaData::STDPROP_TYPE)) name = m[Kwave::MetaData::STDPROP_TYPE].toString(); } // if the meta data list contains only one object: try to find // out the object's name if ((meta_data.count() == 1) && name.length()) { m_description = i18nc( "name of the undo action for inserting a meta data object", "Insert %1", name ); break; } // check if the list contains only objects of the same type bool all_same_type = true; foreach (const Kwave::MetaData &m, meta_data) { QString n = m[Kwave::MetaData::STDPROP_TYPE].toString(); if (!n.length() || (n != name)) { all_same_type = false; break; } } if (all_same_type) { m_description = i18nc( "name of the undo action for inserting multiple " "meta data objects of the same type: " "%1=number of elements, %2=name of one element in singular", "Insert %1 %2 objects", meta_data.count(), name ); break; } m_description = i18n("Insert Meta Data"); break; } } //*************************************************************************** Kwave::UndoAddMetaDataAction::~UndoAddMetaDataAction() { } //*************************************************************************** QString Kwave::UndoAddMetaDataAction::description() { return m_description; } //*************************************************************************** qint64 Kwave::UndoAddMetaDataAction::undoSize() { return sizeof(*this); } //*************************************************************************** qint64 Kwave::UndoAddMetaDataAction::redoSize() { return sizeof(UndoDeleteMetaDataAction); } //*************************************************************************** bool Kwave::UndoAddMetaDataAction::store(Kwave::SignalManager &) { // nothing to do, all data has already // been stored in the constructor return true; } //*************************************************************************** Kwave::UndoAction *Kwave::UndoAddMetaDataAction::undo( Kwave::SignalManager &manager, bool with_redo) { Kwave::UndoAction *redo = Q_NULLPTR; Kwave::MetaDataList meta_data = - manager.metaData().copy(m_offset, m_length, m_tracks); + manager.metaData().copy(m_offset, m_length); // store data for redo if (with_redo && !meta_data.isEmpty()) { redo = new(std::nothrow) Kwave::UndoDeleteMetaDataAction(meta_data); Q_ASSERT(redo); if (redo) redo->store(manager); } // remove the meta data from the signal manager - manager.metaData().deleteRange(m_offset, m_length, m_tracks); + manager.metaData().deleteRange(m_offset, m_length); return redo; } //*************************************************************************** //*************************************************************************** diff --git a/libkwave/undo/UndoAddMetaDataAction.h b/libkwave/undo/UndoAddMetaDataAction.h index 1cdfc2a9..db0a8e00 100644 --- a/libkwave/undo/UndoAddMetaDataAction.h +++ b/libkwave/undo/UndoAddMetaDataAction.h @@ -1,91 +1,88 @@ /*************************************************************************** UndoAddMetaDataAction.h - Undo action for insertion of meta data ------------------- begin : Wed Aug 16 2006 copyright : (C) 2006 by Thomas Eschenbacher email : Thomas Eschenbacher ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef UNDO_ADD_META_DATA_ACTION_H #define UNDO_ADD_META_DATA_ACTION_H #include "config.h" #include #include #include "libkwave/MetaData.h" #include "libkwave/MetaDataList.h" #include "libkwave/Sample.h" #include "libkwave/undo/UndoAction.h" namespace Kwave { class SignalManager; /** * Undo action for inserting meta data. */ class UndoAddMetaDataAction: public Kwave::UndoAction { public: /** * Constructor * @param meta_data reference to the meta data that has been inserted */ explicit UndoAddMetaDataAction(const Kwave::MetaDataList &meta_data); /** Destructor */ virtual ~UndoAddMetaDataAction() Q_DECL_OVERRIDE; /** * Returns a verbose short description of the action. */ QString description() Q_DECL_OVERRIDE; /** @see UndoAction::undoSize() */ virtual qint64 undoSize() Q_DECL_OVERRIDE; /** @see UndoAction::redoSize() */ virtual qint64 redoSize() Q_DECL_OVERRIDE; /** @see UndoAction::store() */ virtual bool store(SignalManager &manager) Q_DECL_OVERRIDE; /** @see UndoAction::undo() */ virtual Kwave::UndoAction *undo(Kwave::SignalManager &manager, bool with_redo) Q_DECL_OVERRIDE; protected: /** description of the action */ QString m_description; /** index of the first sample position */ sample_index_t m_offset; /** number of affected samples */ sample_index_t m_length; - /** list of affected track inidices */ - QList m_tracks; - }; } #endif /* UNDO_ADD_META_DATA_ACTION_H */ //*************************************************************************** //*************************************************************************** diff --git a/libkwave/undo/UndoDeleteAction.cpp b/libkwave/undo/UndoDeleteAction.cpp index 0f434980..e17e09dc 100644 --- a/libkwave/undo/UndoDeleteAction.cpp +++ b/libkwave/undo/UndoDeleteAction.cpp @@ -1,139 +1,139 @@ /*************************************************************************** UndoDeleteAction.cpp - UndoAction for deletion of a range of samples ------------------- begin : Jun 08 2001 copyright : (C) 2001 by Thomas Eschenbacher email : Thomas Eschenbacher ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include "libkwave/MultiTrackReader.h" #include "libkwave/SignalManager.h" #include "libkwave/undo/UndoAction.h" #include "libkwave/undo/UndoDeleteAction.h" #include "libkwave/undo/UndoDeleteMetaDataAction.h" #include "libkwave/undo/UndoInsertAction.h" //*************************************************************************** Kwave::UndoDeleteAction::UndoDeleteAction(QWidget *parent_widget, const QList &track_list, sample_index_t offset, sample_index_t length) :Kwave::UndoAction(), m_parent_widget(parent_widget), m_stripes(), m_meta_data(), m_track_list(track_list), m_offset(offset), m_length(length), m_undo_size(sizeof(*this)) { // undo size needed for samples m_undo_size += m_length * sizeof(sample_t) * m_track_list.count(); } //*************************************************************************** Kwave::UndoDeleteAction::~UndoDeleteAction() { m_stripes.clear(); } //*************************************************************************** QString Kwave::UndoDeleteAction::description() { return i18n("Delete"); } //*************************************************************************** qint64 Kwave::UndoDeleteAction::undoSize() { return m_undo_size; } //*************************************************************************** qint64 Kwave::UndoDeleteAction::redoSize() { return sizeof(Kwave::UndoInsertAction); } //*************************************************************************** bool Kwave::UndoDeleteAction::store(Kwave::SignalManager &manager) { if (!m_length) return true; // shortcut: this is an empty action // fork off a multi track stripe list for the selected range const sample_index_t left = m_offset; const sample_index_t right = m_offset + m_length - 1; m_stripes = manager.stripes(m_track_list, left, right); if (m_stripes.isEmpty()) return false; // retrieving the stripes failed // save the meta data - m_meta_data = manager.metaData().copy(m_offset, m_length, m_track_list); + m_meta_data = manager.metaData().copy(m_offset, m_length); return true; } //*************************************************************************** Kwave::UndoAction *Kwave::UndoDeleteAction::undo(Kwave::SignalManager &manager, bool with_redo) { Kwave::UndoAction *redo_action = Q_NULLPTR; // store data for redo if (with_redo) { redo_action = new(std::nothrow) Kwave::UndoInsertAction( m_parent_widget, m_track_list, m_offset, m_length ); Q_ASSERT(redo_action); if (!redo_action) return Q_NULLPTR; redo_action->store(manager); } if (!m_length) return redo_action; // shortcut: this is an empty action // insert space for the stripes if (!manager.insertSpace(m_offset, m_length, m_track_list)) { qWarning("UndoDeleteAction::undo() FAILED [insertSpace]"); delete redo_action; return Q_NULLPTR; } // merge the stripes back into the signal if (!manager.mergeStripes(m_stripes, m_track_list)) { qWarning("UndoDeleteAction::undo() FAILED [mergeStripes]"); delete redo_action; return Q_NULLPTR; } // restore the saved meta data - manager.metaData().merge(m_meta_data); + manager.metaData().add(m_meta_data); return redo_action; } //*************************************************************************** void Kwave::UndoDeleteAction::dump(const QString &indent) { qDebug("%sundo delete from [%lu ... %lu] (%lu)", DBG(indent), static_cast(m_offset), static_cast(m_offset + ((m_length) ? (m_length - 1) : m_length)), static_cast(m_length)); } //*************************************************************************** //*************************************************************************** diff --git a/libkwave/undo/UndoDeleteMetaDataAction.cpp b/libkwave/undo/UndoDeleteMetaDataAction.cpp index a44ff92d..169334b6 100644 --- a/libkwave/undo/UndoDeleteMetaDataAction.cpp +++ b/libkwave/undo/UndoDeleteMetaDataAction.cpp @@ -1,151 +1,151 @@ /*************************************************************************** UndoAddLabelAction.cpp - Undo action for deleting labels ------------------- begin : Wed Aug 16 2006 copyright : (C) 2006 by Thomas Eschenbacher email : Thomas Eschenbacher ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include "libkwave/SignalManager.h" #include "libkwave/undo/UndoAddMetaDataAction.h" #include "libkwave/undo/UndoDeleteMetaDataAction.h" //*************************************************************************** Kwave::UndoDeleteMetaDataAction::UndoDeleteMetaDataAction( const Kwave::MetaDataList &meta_data) :UndoAction(), m_meta_data(meta_data) { } //*************************************************************************** Kwave::UndoDeleteMetaDataAction::~UndoDeleteMetaDataAction() { } //*************************************************************************** QString Kwave::UndoDeleteMetaDataAction::description() { // sanity check: list should not be empty Q_ASSERT(!m_meta_data.isEmpty()); if (m_meta_data.isEmpty()) return _(""); QString name; { const Kwave::MetaData &m = m_meta_data.values().first(); if (m.hasProperty(Kwave::MetaData::STDPROP_TYPE)) name = m[Kwave::MetaData::STDPROP_TYPE].toString(); } // if the meta data list contains only one object: try to find // out the object's name if ((m_meta_data.count() == 1) && name.length()) { return i18nc( "name of the undo action for deleting a meta data object", "Delete %1", name ); } // check if the list contains only objects of the same type bool all_same_type = true; foreach (const Kwave::MetaData &m, m_meta_data) { QString n = m[Kwave::MetaData::STDPROP_TYPE].toString(); if (!n.length() || (n != name)) { all_same_type = false; break; } } if (all_same_type) { return i18nc( "name of the undo action for deleting multiple " "meta data objects of the same type: " "%1=number of elements, %2=name of one element in singular", "Delete %1 %2 objects", m_meta_data.count(), name ); } return i18n("Delete Meta Data"); } //*************************************************************************** qint64 Kwave::UndoDeleteMetaDataAction::undoSize() { return sizeof(*this); } //*************************************************************************** qint64 Kwave::UndoDeleteMetaDataAction::redoSize() { return sizeof(Kwave::UndoAddMetaDataAction); } //*************************************************************************** bool Kwave::UndoDeleteMetaDataAction::store(Kwave::SignalManager &) { // nothing to do, all data has already // been stored in the constructor return true; } //*************************************************************************** Kwave::UndoAction *Kwave::UndoDeleteMetaDataAction::undo( Kwave::SignalManager &manager, bool with_redo) { Q_ASSERT(!m_meta_data.isEmpty()); if (m_meta_data.isEmpty()) return Q_NULLPTR; Kwave::UndoAction *redo = Q_NULLPTR; // add the stored meta data to the signal managers' meta data - manager.metaData().merge(m_meta_data); + manager.metaData().add(m_meta_data); // store data for redo if (with_redo) { redo = new(std::nothrow) Kwave::UndoAddMetaDataAction(m_meta_data); Q_ASSERT(redo); if (redo) redo->store(manager); } return redo; } //*************************************************************************** void Kwave::UndoDeleteMetaDataAction::dump(const QString &indent) { foreach (const Kwave::MetaData &m, m_meta_data) { qDebug("%sundo delete meta data object '%s'", DBG(indent), DBG(m.id())); // dump all properties of the object foreach (const QString &key, m.keys()) { QVariant v = m[key]; QString value; if (v.type() == QVariant::List) { foreach (const QVariant &v1, v.toList()) value += _("'") + v1.toString() + _("' "); } else { value = v.toString(); } qDebug("%s '%s' = '%s", DBG(indent), DBG(key), DBG(value)); } } } //*************************************************************************** //*************************************************************************** diff --git a/plugins/samplerate/SampleRatePlugin.cpp b/plugins/samplerate/SampleRatePlugin.cpp index 283d2bbd..6ec3fdad 100644 --- a/plugins/samplerate/SampleRatePlugin.cpp +++ b/plugins/samplerate/SampleRatePlugin.cpp @@ -1,252 +1,252 @@ /*************************************************************************** SampleRatePlugin.cpp - sample rate conversion ------------------- begin : Tue Jul 07 2009 copyright : (C) 2009 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include // for the i18n macro #include #include #include #include "libkwave/Connect.h" #include "libkwave/FileInfo.h" #include "libkwave/MetaDataList.h" #include "libkwave/MultiTrackReader.h" #include "libkwave/MultiTrackWriter.h" #include "libkwave/PluginManager.h" #include "libkwave/SignalManager.h" #include "libkwave/Utils.h" #include "libkwave/Writer.h" #include "libkwave/modules/RateConverter.h" #include "libkwave/undo/UndoTransactionGuard.h" #include "SampleRatePlugin.h" KWAVE_PLUGIN(samplerate, SampleRatePlugin) //*************************************************************************** Kwave::SampleRatePlugin::SampleRatePlugin(QObject *parent, const QVariantList &args) :Kwave::Plugin(parent, args), m_params(), m_new_rate(0.0), m_whole_signal(false) { } //*************************************************************************** Kwave::SampleRatePlugin::~SampleRatePlugin() { } //*************************************************************************** int Kwave::SampleRatePlugin::interpreteParameters(QStringList ¶ms) { bool ok = false; QString param; // set defaults m_new_rate = 44100.0; m_whole_signal = false; // evaluate the parameter list if (params.count() < 1) return -EINVAL; param = params[0]; m_new_rate = param.toDouble(&ok); Q_ASSERT(ok); if (!ok) return -EINVAL; // check whether we should change the whole signal (optional) if (params.count() == 2) { if (params[1] != _("all")) return -EINVAL; m_whole_signal = true; } // all parameters accepted m_params = params; return 0; } //*************************************************************************** void Kwave::SampleRatePlugin::run(QStringList params) { Kwave::SignalManager &mgr = signalManager(); // parse parameters if (interpreteParameters(params) < 0) return; double old_rate = Kwave::FileInfo(signalManager().metaData()).rate(); if ((old_rate <= 0) || qFuzzyCompare(old_rate, m_new_rate)) return; Kwave::UndoTransactionGuard undo_guard(*this, i18n("Change sample rate")); // get the current selection and the list of affected tracks sample_index_t first = 0; sample_index_t last = 0; sample_index_t length; QList tracks; if (m_whole_signal) { length = signalLength(); last = (length) ? (length - 1) : 0; tracks = mgr.allTracks(); } else { length = selection(&tracks, &first, &last, true); if ((length == signalLength()) && (tracks.count() == Kwave::toInt(mgr.tracks()))) { // manually selected the whole signal m_whole_signal = true; } } qDebug("SampleRatePlugin: from %9lu - %9lu (%9lu)", static_cast(first), static_cast(last), static_cast(length)); if (!length || tracks.isEmpty()) return; // calculate the new length double ratio = m_new_rate / old_rate; sample_index_t new_length = static_cast(length * ratio); if ((new_length == length) || !new_length) return; // if the new length is bigger than the current length, // insert some space at the end if (new_length > length) { qDebug("SampleRatePlugin: inserting %lu at %lu", static_cast(new_length - length + 1), static_cast(last + 1)); mgr.insertSpace(last + 1, new_length - length + 1, tracks); } Kwave::MultiTrackReader source(Kwave::SinglePassForward, mgr, tracks, first, last); // connect the progress dialog connect(&source, SIGNAL(progress(qreal)), this, SLOT(updateProgress(qreal)), Qt::BlockingQueuedConnection); emit setProgressText( i18n("Changing sample rate from %1 kHz to %2 kHz...", (old_rate / 1E3), (m_new_rate / 1E3)) ); // create the converter Kwave::MultiTrackSource converter( tracks.count(), this); converter.setAttribute(SLOT(setRatio(QVariant)), QVariant(ratio)); // create the writer with the appropriate length Kwave::MultiTrackWriter sink(mgr, tracks, Kwave::Overwrite, first, first + new_length - 1); // connect the objects bool ok = true; if (ok) ok = Kwave::connect( source, SIGNAL(output(Kwave::SampleArray)), converter, SLOT(input(Kwave::SampleArray))); if (ok) ok = Kwave::connect( converter, SIGNAL(output(Kwave::SampleArray)), sink, SLOT(input(Kwave::SampleArray))); if (!ok) { return; } while (!shouldStop() && !source.eof()) { source.goOn(); converter.goOn(); } sink.flush(); // find out how many samples have been written and delete the leftovers sample_index_t written = sink[0]->position() - first; // qDebug("SampleRatePlugin: old=%u, expexted=%u, written=%u", // length, new_length, written); if (written < length) { sample_index_t to_delete = length - written; mgr.deleteRange(written, to_delete, tracks); } // adjust meta data locations - Kwave::MetaDataList meta = mgr.metaData().copy(first, last, tracks); + Kwave::MetaDataList meta = mgr.metaData().copy(first, last); if (!meta.isEmpty()) { // adjust all positions in the originally selected range // NOTE: if the ratio is > 1, work backwards, otherwise forward Kwave::MetaDataList::MutableIterator it(meta); (ratio > 1) ? it.toBack() : it.toFront(); while ((ratio > 1) ? it.hasPrevious() : it.hasNext()) { Kwave::MetaData &m = (ratio > 1) ? it.previous().value() : it.next().value(); QStringList properties = Kwave::MetaData::positionBoundPropertyNames(); foreach (const QString &property, properties) { if (!m.hasProperty(property)) continue; sample_index_t pos = static_cast( m[property].toULongLong()); if (pos < first) continue; if (pos > last) continue; // is in our range -> calculate new position pos -= first; pos *= ratio; pos += first; m[property] = pos; } } - mgr.metaData().deleteRange(first, last, tracks); - mgr.metaData().merge(meta); + mgr.metaData().deleteRange(first, last); + mgr.metaData().add(meta); } // update the selection if it was not empty length = selection(Q_NULLPTR, &first, &last, false); if (length) { if (m_whole_signal) { // if whole signal selected -> adjust start and end first *= ratio; last *= ratio; length = last - first + 1; } else { // only a portion selected -> adjust only length length *= ratio; } mgr.selectRange(first, length); } // set the sample rate if we modified the whole signal if (m_whole_signal) { Kwave::FileInfo info(signalManager().metaData()); info.setRate(m_new_rate); mgr.setFileInfo(info, false); } } //*************************************************************************** #include "SampleRatePlugin.moc" //*************************************************************************** //***************************************************************************