# -*- coding: utf-8 -*-
# This software and supporting documentation are distributed by
# Institut Federatif de Recherche 49
# CEA/NeuroSpin, Batiment 145,
# 91191 Gif-sur-Yvette cedex
# France
#
# This software is governed by the CeCILL license version 2 under
# French law and abiding by the rules of distribution of free software.
# You can use, modify and/or redistribute the software under the
# terms of the CeCILL license version 2 as circulated by CEA, CNRS
# and INRIA at the following URL "http://www.cecill.info".
#
# As a counterpart to the access to the source code and rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty and the software's author, the holder of the
# economic rights, and the successive licensors have only limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading, using, modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean that it is complicated to manipulate, and that also
# therefore means that it is reserved for developers and experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and, more generally, to use and operate it in the
# same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL license version 2 and that you accept its terms.
from __future__ import absolute_import
import time
import os
from soma.qt_gui.qt_backend.Qt import QWidget, QVBoxLayout, QIcon, QSplitter, Qt, QSizePolicy, QSize, QTreeWidget, QTreeWidgetItem, QTreeWidgetItemIterator, QHBoxLayout, QPushButton, QObject, QFileDialog, QKeySequence, QInputDialog, QLineEdit
from soma.qt_gui.qt_backend.Qt import QApplication
from brainvisa.processing import neuroLog
from brainvisa.processing import neuroException
from brainvisa.configuration import neuroConfig
from soma.qtgui.api import TextEditWithSearch
from soma.qt_gui import qt_backend
import sys
import sip
import six
[docs]class LogItemsViewer(QWidget):
"""
Widget to visualize a list of :py:class:`neuroLog.LogFile.Item`.
It is compound of a splitter with the list of items displayed as a QTreeWidget at the left
and the text of the content of the selected item at the right side.
A contextual menu is available on the content panel to search a string in the text.
"""
def __init__(self, logitems=[], parent=None):
QWidget.__init__(self, parent)
layout = QVBoxLayout()
self.setLayout(layout)
self._pixmaps = {}
splitter = QSplitter(Qt.Horizontal)
splitter.setSizePolicy(
QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding))
layout.addWidget(splitter)
self._list = QTreeWidget(splitter)
self._list.setColumnCount(2)
self._list.setHeaderLabels([_t_('Description'), _t_('Date')])
self._list.setAllColumnsShowFocus(1)
self._list.setIconSize(QSize(32, 32))
self._list.setSizePolicy(
QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding))
self._list.setRootIsDecorated(1)
self.searchResults = None
self.searchText = ""
self._content = TextEditWithSearch(splitter) # QTextView( splitter )
self._content.setReadOnly(True)
self._content.setSizePolicy(
QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding))
splitter.setStretchFactor(0, 1)
splitter.setStretchFactor(1, 2)
self._list.currentItemChanged.connect(self._updateContent)
self.refresh(logitems)
firstItem = self._list.topLevelItem(0)
if firstItem:
self._updateContent(firstItem)
def refresh(self, logitems):
# Save list state
itemIndex = 0
currentItemIndex = -1
listState = []
it = QTreeWidgetItemIterator(self._list)
while it.value():
currentItem = it.value()
listState.append(currentItem.isExpanded())
if self._list.currentItem() is currentItem:
currentItemIndex = itemIndex
it += 1
itemIndex += 1
# Erase list
self._list.clear()
self._contents = {}
# Reread log and restore list state
after = None
currentItemList = []
itemIndex = -1
for item in logitems:
(after, itemIndex) = self._addLogItem(item, self._list, after,
itemIndex, listState, currentItemIndex, currentItemList)
if currentItemList:
self._list.setCurrentItem(currentItemList[0])
self._list.resizeColumnToContents(0)
def _addLogItem(self, item, parent, after, itemIndex, listState, currentItemIndex, currentItemList):
viewItem = QTreeWidgetItem(parent)
if viewItem.__class__.__hash__ is None:
viewItem.__class__.__hash__ = lambda self: sip.unwrapinstance(self)
viewItem.setText(0, item.what())
viewItem.setText(1, time.asctime(time.localtime(item.when())))
if item.icon():
pixmap = self._pixmaps.get(item.icon())
if pixmap is None:
pixmap = QIcon(
os.path.join(neuroConfig.iconPath, item.icon()))
self._pixmaps[item.icon()] = pixmap
viewItem.setIcon(0, pixmap)
content = item.html()
if content:
self._contents[viewItem] = content
itemIndex += 1
isOpen = 0
if itemIndex < len(listState):
isOpen = listState[itemIndex]
if itemIndex == currentItemIndex:
currentItemList.append(viewItem)
for child in item.children():
(after, itemIndex) = self._addLogItem(child, viewItem, after,
itemIndex, listState, currentItemIndex, currentItemList)
viewItem.setExpanded(isOpen)
return (viewItem, itemIndex)
def _updateContent(self, item):
self._content.setText(six.text_type(self._contents.get(item, '')))
[docs] def keyPressEvent(self, keyEvent):
if (self._list.hasFocus()):
if (keyEvent.matches(QKeySequence.Find)):
(res, ok) = QInputDialog.getText(
self, "Find", "Text to find :", QLineEdit.Normal, self.searchText)
if ok:
self.searchText = res
if self.searchText and ok:
self.searchResults = self.findItem(self.searchText)
if (self.searchResults):
try:
item = next(self.searchResults)
except StopIteration:
item = None
elif (keyEvent.matches(QKeySequence.FindNext)):
if (self.searchResults is not None):
if (self.searchResults):
try:
item = next(self.searchResults)
except StopIteration:
item = None
if item is None:
self.searchResults.close()
self.searchResults = None
else:
QWidget.keyPressEvent(self, keyEvent)
else:
QWidget.keyPressEvent(self, keyEvent)
[docs] def findItem(self, name):
"""
Find items that contain the string given in parameters in their name. Each found item is selected and yield (and replace previous selection).
Wide search.
@type name: string
@param name: string searched in items names.
@rtype: generator
@return: a generator
"""
it = QTreeWidgetItemIterator(self._list)
lastSelection = None
while it.value():
item = it.value()
if item.text(0).lower().find(name.lower()) >= 0:
self._list.setCurrentItem(item)
if lastSelection:
lastSelection.setSelected(False)
lastSelection = item
yield item
it += 1
yield None
[docs]class LogViewer(QWidget):
"""
A viewer for a log file. The file is read and its items are displayed in a LogItemsViewer.
Buttons are available at the bottom of the window to refresh the display,
close the window or open a new log file.
"""
def __init__(self, fileName, parent=None, name=None):
QWidget.__init__(self, parent)
if name:
self.setObjectName(name)
layout = QVBoxLayout()
self.setLayout(layout)
if getattr(LogViewer, 'pixIcon', None) is None:
setattr(LogViewer, 'pixIcon', QIcon(
os.path.join(neuroConfig.iconPath, 'icon_log.png')))
self.setWindowIcon(self.pixIcon)
self.logitems_viewer = LogItemsViewer([], self)
layout.addWidget(self.logitems_viewer)
hb = QHBoxLayout()
layout.addLayout(hb)
hb.setContentsMargins(5, 5, 5, 5)
btn = QPushButton(_t_('&Refresh'))
btn.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
hb.addWidget(btn)
btn.clicked.connect(self.refresh)
btn = QPushButton(_t_('&Close'))
hb.addWidget(btn)
btn.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
btn.clicked.connect(self.close)
btn = QPushButton(_t_('&Open...'))
hb.addWidget(btn)
btn.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
btn.clicked.connect(self.open)
neuroConfig.registerObject(self)
self.setLogFile(fileName)
if hasattr(self, 'screen'):
screen = self.screen()
else:
screen = QApplication.primaryScreen()
ds = screen.size()
self.resize(min(1200, ds.width()), min(800, ds.height()))
def setLogFile(self, fileName):
self._fileName = fileName
self.setWindowTitle(self._fileName)
self.refresh()
[docs] def closeEvent(self, event):
neuroConfig.unregisterObject(self)
QWidget.closeEvent(self, event)
event.accept()
def refresh(self):
try:
reader = neuroLog.LogFileReader(self._fileName)
except IOError:
neuroException.showException()
reader = None
# Reread log and restore list state
logitems = reader.read()
self.logitems_viewer.refresh(logitems)
def open(self):
# QFileDialog.getOpenFileName( QWidget * parent = 0, const QString & caption = QString(), const QString & dir = QString(), const QString & filter = QString(), QString * selectedFilter = 0, Options options = 0)
# workaround a bug in PyQt ? Param 5 doesn't work; try to use kwargs
import sipconfig
if sipconfig.Configuration().sip_version >= 0x040a00:
logFileName = six.text_type(qt_backend.getOpenFileName(
None, _t_('Open log file'), self._fileName, '', options=QFileDialog.DontUseNativeDialog))
else:
logFileName = six.text_type(qt_backend.getOpenFileName(
None, _t_('Open log file'), self._fileName, '', None, QFileDialog.DontUseNativeDialog))
if logFileName:
try:
self.setLogFile(logFileName)
except Exception:
neuroException.showException()
def showLog(fileName):
l = LogViewer(fileName)
l.show()