1#    Copyright (C) 2009 Jeremy S. Sanders
2#    Email: Jeremy Sanders <jeremy@jeremysanders.net>
3#
4#    This program is free software; you can redistribute it and/or modify
5#    it under the terms of the GNU General Public License as published by
6#    the Free Software Foundation; either version 2 of the License, or
7#    (at your option) any later version.
8#
9#    This program is distributed in the hope that it will be useful,
10#    but WITHOUT ANY WARRANTY; without even the implied warranty of
11#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12#    GNU General Public License for more details.
13#
14#    You should have received a copy of the GNU General Public License along
15#    with this program; if not, write to the Free Software Foundation, Inc.,
16#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17##############################################################################
18
19"""Veusz data capture dialog."""
20
21from __future__ import division
22from ..compat import citems, cstr, cstrerror
23from .. import qtall as qt
24from .. import setting
25from ..dataimport import capture, simpleread
26from .veuszdialog import VeuszDialog
27
28def _(text, disambiguation=None, context="CaptureDialog"):
29    """Translate text."""
30    return qt.QCoreApplication.translate(context, text, disambiguation)
31
32class CaptureDialog(VeuszDialog):
33    """Capture dialog.
34
35    This allows the user to set the various capture options."""
36
37    def __init__(self, document, mainwindow):
38        VeuszDialog.__init__(self, mainwindow, 'capture.ui')
39        self.document = document
40
41        # set values of edit controls from previous invocation (if any)
42        d = setting.settingdb
43
44        # Validate edit controls
45        validator = qt.QIntValidator(1, 65535, self)
46        self.portEdit.setValidator(validator)
47        validator = qt.QIntValidator(1, 1000000000, self)
48        self.numLinesStopEdit.setValidator(validator)
49        self.timeStopEdit.setValidator(validator)
50        self.tailEdit.setValidator(validator)
51
52        # floating point values for interval
53        self.updateIntervalsEdit.setValidator(
54            qt.QDoubleValidator(1e-2, 10000000, 2, self))
55
56        # add completion for filenames
57        c = self.filenamecompleter = qt.QCompleter(self)
58        model = qt.QDirModel(c)
59        c.setModel(model)
60        self.filenameEdit.setCompleter(c)
61
62        # get notification of change of capture method
63        self.methodBG = qt.QButtonGroup(self)
64        self.methodBG.addButton( self.captureFileButton, 0 )
65        self.methodBG.addButton( self.captureInternetButton, 1 )
66        self.methodBG.addButton( self.captureProgramButton, 2 )
67        self.methodBG.buttonClicked[int].connect(self.slotMethodChanged)
68        # restore previously clicked button
69        self.methodBG.button( d.get('CaptureDialog_method', 0) ).click()
70
71        # get notification of change of stop method
72        self.stopBG = qt.QButtonGroup(self)
73        self.stopBG.addButton( self.clickingStopButton, 0 )
74        self.stopBG.addButton( self.numLinesStopButton, 1 )
75        self.stopBG.addButton( self.timeStopButton, 2 )
76        self.stopBG.buttonClicked[int].connect(self.slotStopChanged)
77        self.stopBG.button( d.get('CaptureDialog_stop', 0) ).click()
78
79        # update interval
80        self.updateIntervalsCheck.toggled.connect(
81            self.updateIntervalsEdit.setEnabled)
82
83        # tail data
84        self.tailCheck.toggled.connect(self.tailEdit.setEnabled)
85
86        # user starts capture
87        self.captureButton = self.buttonBox.addButton(
88            _("Ca&pture"), qt.QDialogButtonBox.ApplyRole )
89
90        self.captureButton.clicked.connect(self.slotCaptureClicked)
91
92        # filename browse button clicked
93        self.browseButton.clicked.connect(self.slotBrowseClicked)
94
95    def done(self, r):
96        """Dialog is closed."""
97        VeuszDialog.done(self, r)
98
99        # record values for next time dialog is opened
100        d = setting.settingdb
101        d['CaptureDialog_method'] = self.methodBG.checkedId()
102        d['CaptureDialog_stop'] = self.stopBG.checkedId()
103
104    def slotMethodChanged(self, buttonid):
105        """Enable/disable correct controls in methodBG."""
106        # enable correct buttons
107        fc = buttonid==0
108        self.filenameEdit.setEnabled(fc)
109        self.browseButton.setEnabled(fc)
110
111        ic = buttonid==1
112        self.hostEdit.setEnabled(ic)
113        self.portEdit.setEnabled(ic)
114
115        xc = buttonid==2
116        self.commandLineEdit.setEnabled(xc)
117
118    def slotStopChanged(self, buttonid):
119        """Enable/disable correct controls in stopBG."""
120
121        ns = buttonid == 1
122        self.numLinesStopEdit.setEnabled(ns)
123
124        ts = buttonid == 2
125        self.timeStopEdit.setEnabled(ts)
126
127    def slotBrowseClicked(self):
128        """Browse for a data file."""
129
130        fd = qt.QFileDialog(self, 'Browse data file or socket')
131        fd.setFileMode( qt.QFileDialog.ExistingFile )
132
133        # update filename if changed
134        if fd.exec_() == qt.QDialog.Accepted:
135            self.filenameEdit.replaceAndAddHistory( fd.selectedFiles()[0] )
136
137    def slotCaptureClicked(self):
138        """User requested capture."""
139
140        # object to interpret data from stream
141        descriptor = self.descriptorEdit.text()
142        simprd = simpleread.SimpleRead(descriptor)
143
144        maxlines = None
145        timeout = None
146        updateinterval = None
147        tail = None
148        try:
149            stop = self.stopBG.checkedId()
150            if stop == 1:
151                # number of lines to read before stopping
152                maxlines = int( self.numLinesStopEdit.text() )
153            elif stop == 2:
154                # maximum time period before stopping
155                timeout = int( self.timeStopEdit.text() )
156
157            # whether to do an update periodically
158            if self.updateIntervalsCheck.isChecked():
159                updateinterval = float( self.updateIntervalsEdit.text() )
160
161            # whether to only retain N values
162            if self.tailCheck.isChecked():
163                tail = int( self.tailEdit.text() )
164
165        except ValueError:
166            qt.QMessageBox.critical(self, _("Invalid number"), _("Invalid number"))
167            return
168
169        # get method of getting data
170        method = self.methodBG.checkedId()
171        try:
172            # create stream
173            if method == 0:
174                # file/socket
175                stream = capture.FileCaptureStream(self.filenameEdit.text())
176            elif method == 1:
177                # internet socket
178                stream = capture.SocketCaptureStream(
179                    self.hostEdit.text(),
180                    int(self.portEdit.text()) )
181            elif method == 2:
182                # external program
183                stream = capture.CommandCaptureStream(
184                    self.commandLineEdit.text())
185        except EnvironmentError as e:
186            # problem opening stream
187            qt.QMessageBox.critical(
188                self, _("Cannot open input"),
189                _("Cannot open input:\n %s (error %i)") % (
190                    cstrerror(e), e.errno)
191            )
192            return
193
194        stream.maxlines = maxlines
195        stream.timeout = timeout
196        simprd.tail = tail
197        cd = CapturingDialog(self.document, simprd, stream, self,
198                             updateinterval=updateinterval)
199        self.mainwindow.showDialog(cd)
200
201########################################################################
202
203class CapturingDialog(VeuszDialog):
204    """Capturing data dialog.
205    Shows progress to user."""
206
207    def __init__(self, document, simprd, stream, parent,
208                 updateinterval = None):
209        """Initialse capture dialog:
210        document: document to send data to
211        simprd: object to interpret data
212        stream: capturestream to read data from
213        parent: parent widget
214        updateinterval: if set, interval of seconds to update data in doc
215        """
216
217        VeuszDialog.__init__(self, parent, 'capturing.ui')
218
219        self.document = document
220        self.simpleread = simprd
221        self.stream = stream
222
223        # connect buttons
224        self.finishButton.clicked.connect(self.slotFinish)
225        self.cancelButton.clicked.connect(self.slotCancel)
226
227        # timer which governs reading from source
228        self.readtimer = qt.QTimer(self)
229        self.readtimer.timeout.connect(self.slotReadTimer)
230
231        # record time capture started
232        self.starttime = qt.QTime()
233        self.starttime.start()
234
235        # sort tree by dataset name
236        self.datasetTreeWidget.sortItems(0, qt.Qt.AscendingOrder)
237
238        # timer for updating display
239        self.displaytimer = qt.QTimer(self)
240        self.displaytimer.timeout.connect(self.slotDisplayTimer)
241        self.sourceLabel.setText( self.sourceLabel.text() %
242                                  stream.name )
243        self.txt_statusLabel = self.statusLabel.text()
244        self.slotDisplayTimer() # initialise label
245
246        # timer to update document
247        self.updatetimer = qt.QTimer(self)
248        self.updateoperation = None
249        if updateinterval:
250            self.updatetimer.timeout.connect(self.slotUpdateTimer)
251            self.updatetimer.start( int(updateinterval*1000) )
252
253        # start display and read timers
254        self.displaytimer.start(1000)
255        self.readtimer.start(10)
256
257    def slotReadTimer(self):
258        """Time to read more data."""
259        try:
260            self.simpleread.readData(self.stream)
261        except capture.CaptureFinishException as e:
262            # stream tells us it's time to finish
263            self.streamCaptureFinished( cstr(e) )
264
265    def slotDisplayTimer(self):
266        """Time to update information about data source."""
267        self.statusLabel.setText( self.txt_statusLabel %
268                                  (self.stream.bytesread,
269                                   self.starttime.elapsed() // 1000) )
270
271        tree = self.datasetTreeWidget
272        cts = self.simpleread.getDatasetCounts()
273
274        # iterate over each dataset
275        for name, length in citems(cts):
276            find = tree.findItems(name, qt.Qt.MatchExactly, 0)
277            if find:
278                # if already in tree, update number of counts
279                find[0].setText(1, str(length))
280            else:
281                # add new item
282                tree.addTopLevelItem( qt.QTreeWidgetItem([name, str(length)]))
283
284    def slotUpdateTimer(self):
285        """Called to update document while data is being captured."""
286
287        # undo any previous update
288        if self.updateoperation:
289            self.updateoperation.undo(self.document)
290
291        # create new one
292        self.updateoperation = capture.OperationDataCaptureSet(
293            self.simpleread)
294
295        # apply it (bypass history here - urgh)
296        self.updateoperation.do(self.document)
297        self.document.setModified()
298
299    def streamCaptureFinished(self, message):
300        """Stop timers, close stream and display message
301        about finished stream."""
302
303        # stop reading / displaying
304        self.readtimer.stop()
305        self.displaytimer.stop()
306        self.updatetimer.stop()
307        if self.stream:
308            # update stats
309            self.slotDisplayTimer()
310            # close stream
311            self.stream.close()
312            self.stream = None
313        # show message from stream
314        self.statusLabel.setText(message)
315
316    def slotFinish(self):
317        """Finish capturing and save the results."""
318
319        # close down timers
320        self.streamCaptureFinished('')
321
322        # undo any in-progress update
323        if self.updateoperation:
324            self.updateoperation.undo(self.document)
325
326        # apply real document operation update
327        op = capture.OperationDataCaptureSet(self.simpleread)
328        self.document.applyOperation(op)
329
330        # close dialog
331        self.close()
332
333    def slotCancel(self):
334        """Cancel capturing."""
335
336        # close down timers
337        self.streamCaptureFinished('')
338
339        # undo any in-progress update
340        if self.updateoperation:
341            self.updateoperation.undo(self.document)
342            self.document.setModified()
343
344        # close dialog
345        self.close()
346
347
348