1- from javax .swing import JTabbedPane , JPanel , JButton , JLabel , SwingConstants , JOptionPane , GroupLayout , JCheckBox , JSplitPane
1+ from javax .swing import JTabbedPane , JPanel , JButton , JLabel , SwingConstants , BorderFactory , JOptionPane , GroupLayout , JCheckBox , JSplitPane , JRadioButton , ButtonGroup , JFileChooser
22from javax .swing .event import ChangeListener , DocumentListener
33from javax .swing .LayoutStyle .ComponentPlacement import RELATED , UNRELATED
4- from java .awt import BorderLayout , Font , Component
4+ from java .awt import BorderLayout , Font , Component , Color
55from java .beans import PropertyChangeListener
66from org .python .core .util import StringUtil
7+ from burp import IExtensionStateListener
78from uicomponents import BurpUI , TabComponent , TabComponentEditableTabMixin , TabComponentCloseableMixin , TabComponentCloseListener , TabComponentTitleChangedListener
89from models import ObservableCollection , Script
910from utils import EditorFileAdapter
@@ -173,8 +174,8 @@ def _can_compile(self, event):
173174 self .compileButton .enabled = self .script .requires_compile
174175
175176
176- class ScriptOutputPanel (JPanel , PropertyChangeListener ):
177-
177+ class ScriptOutputPanel (JPanel , PropertyChangeListener , IExtensionStateListener ):
178+
178179 def __init__ (self , callbacks , script ):
179180 super (ScriptOutputPanel , self ).__init__ ()
180181 self .callbacks = callbacks
@@ -189,37 +190,93 @@ def __init__(self, callbacks, script):
189190 self .add (self .tabbedPane , BorderLayout .CENTER )
190191 self .script .stdout = EditorFileAdapter (self .outputEditor )
191192 self .script .stderr = EditorFileAdapter (self .errorEditor )
193+ self .output_file = None
194+
195+ # register to be notified when the extension is unloaded so if a output_file ref is in use it can be closed
196+ callbacks .registerExtensionStateListener (self )
192197
193198 def clear_stderr (self , event ):
194199 self .errorEditor .text = ''
195200
196201 def clear_stdout (self , event ):
197202 self .outputEditor .text = ''
198203
204+ def save_file_output (self , event ):
205+ self .outputFileBrowseButton .enabled = True
206+ if self .output_file : # already have an output_file
207+ self .script .stdout = self .output_file
208+ return
209+
210+ if not self .set_output_file ():
211+ # didn't choose a file then revert to using UI
212+ self .outputUIRadioButton .selected = True
213+
214+ def view_ui_output (self , event ):
215+ self .outputFileBrowseButton .enabled = False
216+ self .script .stdout = EditorFileAdapter (self .outputEditor )
217+
218+ def set_output_file (self , event = None ):
219+ file_chooser = JFileChooser ()
220+ choice = file_chooser .showSaveDialog (None )
221+ if choice == JFileChooser .APPROVE_OPTION :
222+ self .outputFileLabel .text = file_chooser .selectedFile .path
223+ self .output_file = open (file_chooser .selectedFile .path , 'a' )
224+ self .script .stdout = self .output_file
225+ return True
226+
227+ return False
228+
199229 def propertyChange (self , event ):
200230 if event .propertyName == Script .Properties .IS_COMPILED :
201231 if event .newValue :
202232 self .errorEditor .text = ''
203233 elif event .propertyName == Script .Properties .COMPILATION_ERROR :
204234 self .errorEditor .text = event .newValue
205- self .tabbedPane .selectedIndex = 1
235+ self .tabbedPane .selectedIndex = 1
236+
237+ def extensionUnloaded (self ):
238+ if self .output_file : # if we have a file ref then close it
239+ print ('Closing output file reference' )
240+ self .output_file .close ()
206241
207242 def _create_output_panel (self ):
208243 self .outputPanel = JPanel ()
209244 self .outputEditor = self .callbacks .createTextEditor ()
210245 self .outputEditor .editable = False
211246 self .outputText = self .outputEditor .component
212247 self .clearOutputButton = JButton ('Clear' , actionPerformed = self .clear_stdout )
248+ self .outputButtonGroup = ButtonGroup ()
249+ self .outputFileRadioButton = JRadioButton ('Save to File:' , actionPerformed = self .save_file_output )
250+ self .outputUIRadioButton = JRadioButton ('Show in UI:' , selected = True , actionPerformed = self .view_ui_output )
251+ self .outputFileLabel = JLabel ()
252+ self .outputFileBrowseButton = JButton ('Browse...' , enabled = False , actionPerformed = self .set_output_file )
253+
254+ self .outputButtonGroup .add (self .outputFileRadioButton )
255+ self .outputButtonGroup .add (self .outputUIRadioButton )
213256
214257 outputLayout = GroupLayout (self .outputPanel , autoCreateGaps = True , autoCreateContainerGaps = True )
215258 outputLayout .setHorizontalGroup (outputLayout .createParallelGroup ()
259+ .addGroup (
260+ outputLayout .createSequentialGroup ()
261+ .addComponent (self .outputFileRadioButton )
262+ .addComponent (self .outputFileLabel )
263+ .addComponent (self .outputFileBrowseButton )
264+ )
265+ .addComponent (self .outputUIRadioButton )
216266 .addComponent (self .outputText )
217267 .addComponent (self .clearOutputButton )
218268 )
219269
220270 outputLayout .setVerticalGroup (outputLayout .createSequentialGroup ()
221- .addComponent (self .outputText )
222- .addComponent (self .clearOutputButton )
271+ .addGroup (
272+ outputLayout .createParallelGroup ()
273+ .addComponent (self .outputFileRadioButton )
274+ .addComponent (self .outputFileLabel )
275+ .addComponent (self .outputFileBrowseButton )
276+ )
277+ .addComponent (self .outputUIRadioButton )
278+ .addComponent (self .outputText )
279+ .addComponent (self .clearOutputButton )
223280 )
224281 self .outputPanel .layout = outputLayout
225282
@@ -241,7 +298,6 @@ def _create_error_panel(self):
241298 )
242299 self .errorPanel .layout = errorLayout
243300
244-
245301class ScriptPanel (JPanel ):
246302
247303 def __init__ (self , script , callbacks ):
0 commit comments