diff --git a/scripts/piped-work/docimages_all.py b/scripts/piped-work/docimages_all.py index c68340c2b..06489d7a5 100644 --- a/scripts/piped-work/docimages_all.py +++ b/scripts/piped-work/docimages_all.py @@ -13,3 +13,4 @@ exec( open("docimages_after.py" ).read() ) exec( open("docimages_envelopes.py" ).read() ) exec( open("docimages_cut_n_paste.py" ).read() ) exec( open("docimages_clip_boundaries.py" ).read() ) +exec( open("docimages_oddments.py" ).read() ) diff --git a/scripts/piped-work/docimages_oddments.py b/scripts/piped-work/docimages_oddments.py new file mode 100644 index 000000000..e17ffcfec --- /dev/null +++ b/scripts/piped-work/docimages_oddments.py @@ -0,0 +1,82 @@ +# docimages_oddments.py +# Sends commands to get images for the manual. +# Image oddments that don't fit the other categories. + +# Make sure Audacity is running first and that mod-script-pipe is enabled +# before running this script. + +#load and run the common core. +exec( open("docimages_core.py" ).read() ) + +import time + + +# 11 2KHz tones, of decreasing amplitude. +# With label track to annotate it. +def makeStepper(): + makeWayForTracks() + do( 'NewMonoTrack' ) + do( 'Select: Start=0 End=22') + do( 'Silence' ) #Just so we can watch. + do( 'FitInWindow') + for i in range( 0, 11 ): + do( 'Select: Start='+str(i*2)+' End='+str(i*2+2) ) + do( 'Chirp: StartFreq=2000 EndFreq=2000 StartAmp=' + str( (400**(0.1 * (10-i)))/400 )+' EndAmp=' + str( (400**(0.1 * (10-i) ))/400 )) + do( 'Select: Start=0 End=22') + do( 'Join' ) + do( 'FitInWindow') + do( 'AddLabelTrack' ) + for i in range( 0, 11 ): + do( 'Select: Start='+str(i*2)+' End='+str(i*2+2) ) + do( 'AddLabel' ) + do( 'SetLabel: Label=' + str(i)+' Selected=0 Text='+str( -(i*10) )) + do( 'Select: Start=0 End=0') + + +def spectro_image1and2() : + loadStereoTracks(1) + # A stereo track + capture( 'Spectral001.png', 'First_Track' ) + # As spectrogram. + do( 'SetTrack: Track=0 Display=Spectrogram') + do( 'Select: Start=55 End=70 First=0 Last=1') + capture( 'Spectral002.png', 'First_Track' ) + # Half spectrogram, half wave. + do( 'SetTrack: Channel=1 Display=Waveform') + capture( 'MixedMode.png', 'First_Track' ) + +def spectro_image3and4(): + makeStepper(); + # Stepper tone, viewed in dB. + do( 'SetTrack: Scale=dB') + capture( 'Spectral003.png', 'First_Two_Tracks' ) + # As spectrogram. + do( 'SetTrack: Display=Spectrogram') + capture( 'Spectral004.png', 'First_Two_Tracks' ) + +def oddments_imagesA(): + for name in ["Select","Envelope","Draw","Zoom","TimeShift","Multi"] : + do( name + "Tool" ) + capture( name + "Tool.png" , 'Tools' ); + #A track is needed for the buttons to be active. + loadMonoTracks(1) + for id in range( 11000, 11006 ): + do( "Drag: Id="+str( id) + " FromX=10 FromY=10" ) + capture( "Button" + str(id) +"Hover.png", "Transport" ) + do( "Drag: Id="+str( id) + " FromX=1000 FromY=10" ) + + +def oddments_imagesB(): + loadMonoTracks(1) + #Bring window to top now + capture( "Dummy.png", "Ruler" ) + #We hope nothing else gets focus before the next capture, so + #that we actually get to see something! + do( "Drag: Window=Timeline FromX=200 FromY=10 ToX=600 ToY=10" ) + time.sleep(3.0) + #Disable bringing to top, so as not to destroy quick play. + capture( "QuikPlay001.png", "First_Track_Plus ToTop=0" ) + +#oddments_imagesA() +oddments_imagesB() + diff --git a/scripts/piped-work/pipeclient.py b/scripts/piped-work/pipeclient.py new file mode 100644 index 000000000..3cf939501 --- /dev/null +++ b/scripts/piped-work/pipeclient.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Automate Audacity via mod-script-pipe. + +Pipe Client may be used as a command-line script to send commands to +Audacity via the mod-script-pipe interface, or loaded as a module. +Requires Python 2.7 or later. Python 3 recommended. + +====================== +Command Line Interface +====================== + + usage: pipeclient.py [-h] [-t] [-s ] [-d] + +Arguments +--------- + -h,--help: optional + show short help and exit + -t, --timeout: float, optional + timeout for reply in seconds (default: 10) + -s, --show-time: bool, optional + show command execution time (default: True) + -d, --docs: optional + show this documentation and exit + +Example +------- + $ python3 pipeclient.py -t 20 -s False + + Launches command line interface with 20 second time-out for + returned message, and don't show the execution time. + + When prompted, enter the command to send (not quoted), or 'Q' to quit. + + $ Enter command or 'Q' to quit: GetInfo: Type=Tracks Format=LISP + +============ +Module Usage +============ + +Note that on a critical error (such as broken pipe), the module just exits. +If a more graceful shutdown is required, replace the sys.exit()'s with +exceptions. + +Example +------- + + # Import the module: + >>> import pipeclient + + # Create a client instance: + >>> client = pipeclient.PipeClient() + + # Send a command: + >>> client.write("Command", timer=True) + + # Read the last reply: + >>> print(client.read()) + +See Also +-------- +PipeClient.write : Write a command to _write_pipe. +PipeClient.read : Read Audacity's reply from pipe. + +Copyright Steve Daulton 2018 +Released under terms of the GNU General Public License version 2: + + +""" + +import os +import sys +import threading +import time +import errno +import argparse + + +if sys.version_info[0] < 3 and sys.version_info[1] < 7: + sys.exit('PipeClient Error: Python 2.7 or later required') + +# Platform specific constants +if sys.platform == 'win32': + WRITE_NAME = '\\\\.\\pipe\\ToSrvPipe' + READ_NAME = '\\\\.\\pipe\\FromSrvPipe' + EOL = '\r\n\0' +else: + # Linux or Mac + PIPE_BASE = '/tmp/audacity_script_pipe.' + WRITE_NAME = PIPE_BASE + 'to.' + str(os.getuid()) + READ_NAME = PIPE_BASE + 'from.' + str(os.getuid()) + EOL = '\n' + + +class PipeClient(object): + """Write / read client access to Audacity via named pipes. + + Normally there should be just one instance of this class. If + more instances are created, they all share the same state. + + __init__ calls _write_thread_start() and _read_thread_start() on + first instantiation. + + Parameters + ---------- + None + + Attributes + ---------- + reader_pipe_broken : event object + Set if pipe reader fails. Audacity may have crashed + reply_ready : event object + flag cleared when command sent and set when response received + timer : bool + When true, time the command execution (default False) + reply : string + message received when Audacity completes the command + + See Also + -------- + write : Write a command to _write_pipe. + read : Read Audacity's reply from pipe. + + """ + + reader_pipe_broken = threading.Event() + reply_ready = threading.Event() + + _shared_state = {} + + def __new__(cls, *p, **k): + self = object.__new__(cls, *p, **k) + self.__dict__ = cls._shared_state + return self + + def __init__(self): + self.timer = False + self._start_time = 0 + self._write_pipe = None + self.reply = '' + if not self._write_pipe: + self._write_thread_start() + self._read_thread_start() + + def _write_thread_start(self): + """Start _write_pipe thread""" + # Pipe is opened in a new thread so that we don't + # freeze if Audacity is not running. + write_thread = threading.Thread(target=self._write_pipe_open) + write_thread.daemon = True + write_thread.start() + # Allow a little time for connection to be made. + time.sleep(0.1) + if not self._write_pipe: + sys.exit('PipeClientError: Write pipe cannot be opened.') + + def _write_pipe_open(self): + """Open _write_pipe.""" + self._write_pipe = open(WRITE_NAME, 'w') + + def _read_thread_start(self): + """Start read_pipe thread.""" + read_thread = threading.Thread(target=self._reader) + read_thread.daemon = True + read_thread.start() + + def write(self, command, timer=False): + """Write a command to _write_pipe. + + Parameters + ---------- + command : string + The command to send to Audacity + timer : bool, optional + If true, time the execution of the command + + Example + ------- + write("GetInfo: Type=Labels", timer=True): + + """ + self.timer = timer + print('Sending command:', command) + self._write_pipe.write(command + EOL) + # Check that read pipe is alive + if PipeClient.reader_pipe_broken.isSet(): + sys.exit('PipeClient: Read-pipe error.') + try: + self._write_pipe.flush() + if self.timer: + self._start_time = time.time() + self.reply = '' + PipeClient.reply_ready.clear() + except IOError as err: + if err.errno == errno.EPIPE: + sys.exit('PipeClient: Write-pipe error.') + else: + raise + + def _reader(self): + """Read FIFO in worker thread.""" + # Thread will wait at this read until it connects. + # Connection should occur as soon as _write_pipe has connected. + read_pipe = open(READ_NAME, 'r') + message = '' + while True: + line = read_pipe.readline() + # Stop timer as soon as we get first line of response. + stop_time = time.time() + while line != EOL: + message += line + line = read_pipe.readline() + if line == '': + # No data in read_pipe indicates that the pipe is broken + # (Audacity may have crashed). + PipeClient.reader_pipe_broken.set() + if self.timer: + xtime = (stop_time - self._start_time) * 1000 + message += 'Execution time: {0:.2f}ms'.format(xtime) + self.reply = message + PipeClient.reply_ready.set() + message = '' + read_pipe.close() + + def read(self): + """Read Audacity's reply from pipe. + + Returns + ------- + string + The reply from the last command sent to Audacity, or null string + if reply not received. Null string usually indicates that Audacity + is still processing the last command. + + """ + if not PipeClient.reply_ready.isSet(): + return '' + else: + return self.reply + + +def bool_from_string(strval): + """Return boolean value from string""" + if strval.lower() in ('true', 't', '1', 'yes', 'y'): + return True + elif strval.lower() in ('false', 'f', '0', 'no', 'n'): + return False + else: + raise argparse.ArgumentTypeError('Boolean value expected.') + +def main(): + """Interactive command-line for PipeClient""" + + parser = argparse.ArgumentParser() + parser.add_argument('-t', '--timeout', type=float, metavar='', default=10, + help="timeout for reply in seconds (default: 10") + parser.add_argument('-s', '--show-time', metavar='True/False', + nargs='?', type=bool_from_string, + const='t', default='t', dest='show', + help='show command execution time (default: True)') + parser.add_argument('-d', '--docs', action='store_true', + help='show documentation and exit') + args = parser.parse_args() + + if args.docs: + print(__doc__) + sys.exit(0) + + client = PipeClient() + while True: + reply = '' + if sys.version_info[0] < 3: + #pylint: disable=undefined-variable + message = raw_input("\nEnter command or 'Q' to quit: ") + else: + message = input( #pylint: disable=bad-builtin + "\nEnter command or 'Q' to quit: ") + start = time.time() + if message.upper() == 'Q': + sys.exit(0) + elif message == '': + pass + else: + client.write(message, timer=args.show) + while reply == '': + time.sleep(0.1) # allow time for reply + if time.time() - start > args.timeout: + reply = 'PipeClient: Reply timed-out.' + else: + reply = client.read() + print(reply) + + +if __name__ == '__main__': + main() diff --git a/src/commands/DragCommand.cpp b/src/commands/DragCommand.cpp index 638655a7c..00eafcc32 100644 --- a/src/commands/DragCommand.cpp +++ b/src/commands/DragCommand.cpp @@ -48,7 +48,7 @@ static const wxString kCoordTypeStrings[nCoordTypes] = bool DragCommand::DefineParams( ShuttleParams & S ){ wxArrayString coords( nCoordTypes, kCoordTypeStrings ); - S.OptionalN( bHasId ).Define( mId, wxT("Id"), 0.0, 11000.0, 1000000.0); + S.OptionalN( bHasId ).Define( mId, wxT("Id"), 11000.0, -100000.0, 1000000.0); S.OptionalY( bHasWinName ).Define( mWinName, wxT("Window"), "Timeline"); S.OptionalY( bHasFromX ).Define( mFromX, wxT("FromX"), 200.0, 0.0, 1000000.0); S.OptionalY( bHasFromY ).Define( mFromY, wxT("FromY"), 10.0, 0.0, 1000000.0); diff --git a/src/commands/ScreenshotCommand.cpp b/src/commands/ScreenshotCommand.cpp index dcbd95263..a8ff7c6f1 100644 --- a/src/commands/ScreenshotCommand.cpp +++ b/src/commands/ScreenshotCommand.cpp @@ -139,9 +139,10 @@ static const wxString kBackgroundStrings[nBackgrounds] = bool ScreenshotCommand::DefineParams( ShuttleParams & S ){ wxArrayString whats(nCaptureWhats, kCaptureWhatStrings); wxArrayString backs(nBackgrounds, kBackgroundStrings); - S.Define( mPath, wxT("Path"), wxT("")); - S.DefineEnum( mWhat, wxT("CaptureWhat"), wxT("Window"), whats ); - S.OptionalN(bHasBackground).DefineEnum( mBack, wxT("Background"), wxT("None"), backs ); + S.Define( mPath, wxT("Path"), wxT("")); + S.DefineEnum( mWhat, wxT("CaptureWhat"), wxT("Window"), whats ); + S.OptionalN(bHasBackground).DefineEnum( mBack, wxT("Background"), wxT("None"), backs ); + S.OptionalN(bHasBringToTop).Define( mbBringToTop, wxT("ToTop"), true ); return true; }; @@ -153,9 +154,10 @@ void ScreenshotCommand::PopulateOrExchange(ShuttleGui & S) S.StartMultiColumn(2, wxALIGN_CENTER); { - S.TieTextBox( _("Path:"), mPath); - S.TieChoice( _("Capture What:"), mWhat, &whats); - S.TieChoice( _("Background:"), mBack, &backs); + S.TieTextBox( _("Path:"), mPath); + S.TieChoice( _("Capture What:"), mWhat, &whats); + S.TieChoice( _("Background:"), mBack, &backs); + S.TieCheckBox( _("Bring To Top:"), mbBringToTop); } S.EndMultiColumn(); } @@ -247,16 +249,18 @@ bool ScreenshotCommand::Capture( int height = r.height; if( r.width == 0 ) return false; - if (window) { - if (window->IsTopLevel()) { - window->Raise(); - } - else { - wxGetTopLevelParent(window)->Raise(); + if (window ) { + wxWindow * win = window; + wxTopLevelWindow * top_win= nullptr; + if( !window->IsTopLevel()) + win = wxGetTopLevelParent(window); + top_win = dynamic_cast( win ); + if( (!bHasBringToTop || mbBringToTop) && (!top_win || !top_win->IsActive()) ){ + win->Raise(); + Yield(); } } - Yield(); int screenW, screenH; wxDisplaySize(&screenW, &screenH); diff --git a/src/commands/ScreenshotCommand.h b/src/commands/ScreenshotCommand.h index 53ad6f41f..c481a4c57 100644 --- a/src/commands/ScreenshotCommand.h +++ b/src/commands/ScreenshotCommand.h @@ -33,6 +33,7 @@ class CommandContext; class ScreenshotCommand : public AudacityCommand { public: + ScreenshotCommand(){ mbBringToTop=true;}; // CommandDefinitionInterface overrides wxString GetSymbol() override {return SCREENSHOT_PLUGIN_SYMBOL;}; wxString GetDescription() override {return _("Takes screenshots.");}; @@ -46,7 +47,9 @@ private: wxString mWhat; wxString mBack; wxString mPath; + bool mbBringToTop; bool bHasBackground; + bool bHasBringToTop; friend class ScreenshotCommand; friend class ScreenFrame;