Skip to content

Commit 1795cac

Browse files
committed
fix larray-project#88 : (viewer) added AbstractEditor and a menu bar
1 parent 19e1b15 commit 1795cac

1 file changed

Lines changed: 212 additions & 63 deletions

File tree

‎larray/viewer.py‎

Lines changed: 212 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@
7979

8080
from qtpy.QtWidgets import (QApplication, QHBoxLayout, QTableView, QItemDelegate,
8181
QListWidget, QSplitter, QListWidgetItem,
82-
QLineEdit, QCheckBox, QGridLayout,
82+
QLineEdit, QCheckBox, QGridLayout, QFileDialog,
8383
QDialog, QDialogButtonBox, QPushButton,
84-
QMessageBox, QMenu,
84+
QMessageBox, QMenu, QMenuBar, QMainWindow,
8585
QLabel, QSpinBox, QWidget, QVBoxLayout,
8686
QAction, QStyle, QToolTip, QShortcut)
8787

@@ -189,7 +189,7 @@ def keybinding(attr):
189189
return QKeySequence.keyBindings(ks)[0]
190190

191191

192-
def create_action(parent, text, icon=None, triggered=None, shortcut=None):
192+
def create_action(parent, text, icon=None, triggered=None, shortcut=None, statustip=None):
193193
"""Create a QAction"""
194194
action = QAction(text, parent)
195195
if triggered is not None:
@@ -198,6 +198,8 @@ def create_action(parent, text, icon=None, triggered=None, shortcut=None):
198198
action.setIcon(icon)
199199
if shortcut is not None:
200200
action.setShortcut(shortcut)
201+
if statustip is not None:
202+
action.setStatusTip(statustip)
201203
action.setShortcutContext(Qt.WidgetShortcut)
202204
return action
203205

@@ -1837,10 +1839,16 @@ def larray_to_array_and_labels(data):
18371839
return data, xlabels, ylabels
18381840

18391841

1840-
class ArrayEditor(QDialog):
1842+
class AbstractEditor(QMainWindow):
18411843
"""Array Editor Dialog"""
1844+
1845+
MAX_RECENT_FILES = 5
1846+
18421847
def __init__(self, parent=None):
1843-
QDialog.__init__(self, parent)
1848+
QMainWindow.__init__(self, parent)
1849+
self.recentFileActs = [QAction(self) for _ in range(self.MAX_RECENT_FILES)]
1850+
self.recentOpenedFiles = []
1851+
self.currentFile = None
18441852

18451853
# Destroying the C++ object right after closing the dialog box,
18461854
# otherwise it may be garbage-collected in another QThread
@@ -1851,8 +1859,150 @@ def __init__(self, parent=None):
18511859
self.data = None
18521860
self.arraywidget = None
18531861

1854-
def setup_and_check(self, data, title='', readonly=False,
1855-
xlabels=None, ylabels=None,
1862+
self.setup_menu_bar()
1863+
1864+
def _setup_and_check(self, data, title, readonly):
1865+
"""Setup editor"""
1866+
self.data = data
1867+
1868+
icon = self.style().standardIcon(QStyle.SP_ComputerIcon)
1869+
if icon is not None:
1870+
self.setWindowIcon(icon)
1871+
1872+
if not title:
1873+
title = 'Session' if self.__class__ == MappingEditor else 'Array'
1874+
title = _(title + " viewer") if readonly else _(title + " editor")
1875+
if readonly:
1876+
title += ' (' + _('read only') + ')'
1877+
self.setWindowTitle(title)
1878+
1879+
widget = QWidget()
1880+
self.setCentralWidget(widget)
1881+
1882+
layout = QVBoxLayout()
1883+
widget.setLayout(layout)
1884+
1885+
return layout
1886+
1887+
def setup_menu_bar(self):
1888+
"""Setup menu bar"""
1889+
menu_bar = self.menuBar()
1890+
file_menu = menu_bar.addMenu('File')
1891+
1892+
file_menu.addAction(create_action(self, _('New'), shortcut=QKeySequence.New,
1893+
icon=None, triggered=self.new))
1894+
file_menu.addAction(create_action(self, _('Open'), shortcut=QKeySequence.Open,
1895+
icon=None, triggered=self.open,
1896+
statustip=_('Load session from file')))
1897+
file_menu.addAction(create_action(self, _('Save'), shortcut=QKeySequence.Save,
1898+
icon=None, triggered=self.save,
1899+
statustip=_('Save all arrays and executed commands in new directory')))
1900+
file_menu.addAction(create_action(self, _('Save Data'), shortcut=QKeySequence("Shift+S"),
1901+
icon=None, triggered=self.saveData,
1902+
statustip=_('Save all current arrays in a Session file')))
1903+
file_menu.addAction(create_action(self, _('Save As'), shortcut=QKeySequence.SaveAs,
1904+
icon=None, triggered=self.saveAs,
1905+
statustip=_('Save all arrays and executed commands in new directory')))
1906+
1907+
recentFilesMenu = file_menu.addMenu("Open Recent")
1908+
for action in self.recentFileActs:
1909+
action.setVisible(False)
1910+
action.triggered.connect(self.openRecentFile)
1911+
recentFilesMenu.addAction(action)
1912+
self.updateRecentFileActions()
1913+
1914+
file_menu.addSeparator()
1915+
file_menu.addAction(create_action(self, _('Quit'), shortcut=QKeySequence("Ctrl+Q"),
1916+
icon=None, triggered=self.close))
1917+
1918+
@Slot()
1919+
def new(self):
1920+
raise NotImplementedError()
1921+
1922+
def _openFile(self, filepath):
1923+
raise NotImplementedError()
1924+
1925+
@Slot()
1926+
def open(self):
1927+
filepath = QFileDialog.getOpenFileName(self)
1928+
if filepath:
1929+
self._openFile(filepath)
1930+
else:
1931+
messageBox = QMessageBox()
1932+
messageBox.warning(self, "Warning", "No file selected")
1933+
1934+
@Slot()
1935+
def openRecentFile(self):
1936+
action = self.sender()
1937+
if (action):
1938+
filepath = action.data()
1939+
self._openFile(filepath)
1940+
1941+
def _checkBeforeSave(self):
1942+
raise NotImplementedError()
1943+
1944+
def _saveData(self):
1945+
raise NotImplementedError()
1946+
1947+
@Slot()
1948+
def save(self):
1949+
if self._checkBeforeSave():
1950+
self.saveData()
1951+
1952+
@Slot()
1953+
def saveData(self):
1954+
if self._checkBeforeSave():
1955+
if self.currentFile is not None:
1956+
self._saveData(self.currentFile)
1957+
else:
1958+
self.saveAs()
1959+
1960+
@Slot()
1961+
def saveAs(self):
1962+
if self._checkBeforeSave():
1963+
dialog = QFileDialog(self)
1964+
dialog.setWindowModality(Qt.WindowModal)
1965+
dialog.setAcceptMode(QFileDialog.AcceptSave)
1966+
if (dialog.exec() != QDialog.Accepted):
1967+
messageBox = QMessageBox()
1968+
messageBox.critical(self, "Error", "Current session could not be saved")
1969+
else:
1970+
self._saveData(dialog.selectedFiles()[0])
1971+
1972+
def setCurrentFile(self, filepath):
1973+
self.currentFile = filepath
1974+
if filepath is not None:
1975+
# XXX : use QSettings instead of self.recentOpenedFiles attribute?
1976+
# settings = QSettings()
1977+
# files = settings.value("recentFileList")
1978+
if filepath in self.recentOpenedFiles:
1979+
self.recentOpenedFiles.remove(filepath)
1980+
self.recentOpenedFiles.insert(0, filepath)
1981+
# settings.setValue("recentFileList", files)
1982+
self.updateRecentFileActions()
1983+
1984+
def updateRecentFileActions(self):
1985+
# XXX : use QSettings instead of self.recentOpenedFiles attribute?
1986+
# settings = QSettings()
1987+
# files = settings.value("recentFileList")
1988+
numRecentFiles = min(len(self.recentOpenedFiles), self.MAX_RECENT_FILES)
1989+
1990+
for i in range(numRecentFiles):
1991+
filepath = self.recentOpenedFiles[i]
1992+
text = os.path.basename(filepath)
1993+
self.recentFileActs[i].setText(text)
1994+
self.recentFileActs[i].setData(filepath)
1995+
self.recentFileActs[i].setVisible(True)
1996+
for i in range(numRecentFiles, self.MAX_RECENT_FILES):
1997+
self.recentFileActs[i].setVisible(False)
1998+
1999+
2000+
class ArrayEditor(AbstractEditor):
2001+
"""Array Editor Dialog"""
2002+
def __init__(self, parent=None):
2003+
AbstractEditor.__init__(self, parent)
2004+
2005+
def setup_and_check(self, data, title='', readonly=False, xlabels=None, ylabels=None,
18562006
minvalue=None, maxvalue=None):
18572007
"""
18582008
Setup ArrayEditor:
@@ -1866,19 +2016,8 @@ def setup_and_check(self, data, title='', readonly=False,
18662016
in zip(data.axes.display_names, data.axes))
18672017
title = (title + ': ' + axes_info) if title else axes_info
18682018

1869-
self.data = data
1870-
layout = QGridLayout()
1871-
self.setLayout(layout)
1872-
1873-
icon = self.style().standardIcon(QStyle.SP_ComputerIcon)
1874-
if icon is not None:
1875-
self.setWindowIcon(icon)
2019+
layout = self._setup_and_check(data, title, readonly)
18762020

1877-
if not title:
1878-
title = _("Array viewer") if readonly else _("Array editor")
1879-
if readonly:
1880-
title += ' (' + _('read only') + ')'
1881-
self.setWindowTitle(title)
18822021
self.resize(800, 600)
18832022
self.setMinimumSize(400, 300)
18842023

@@ -1949,44 +2088,24 @@ def get_value(self):
19492088
DISPLAY_IN_GRID = (la.LArray, np.ndarray)
19502089

19512090

1952-
class MappingEditor(QDialog):
2091+
class MappingEditor(AbstractEditor):
19532092
"""Session Editor Dialog"""
19542093
def __init__(self, parent=None):
1955-
QDialog.__init__(self, parent)
1956-
1957-
# Destroying the C++ object right after closing the dialog box,
1958-
# otherwise it may be garbage-collected in another QThread
1959-
# (e.g. the editor's analysis thread in Spyder), thus leading to
1960-
# a segmentation fault on UNIX or an application crash on Windows
1961-
self.setAttribute(Qt.WA_DeleteOnClose)
2094+
AbstractEditor.__init__(self, parent)
19622095

1963-
self.data = None
19642096
self._listwidget = None
1965-
self.arraywidget = None
19662097
self.eval_box = None
19672098
self.expressions = {}
19682099
self.kernel = None
19692100

1970-
def setup_and_check(self, data, title='', readonly=False,
1971-
minvalue=None, maxvalue=None):
2101+
def setup_and_check(self, data, title='', readonly=False, minvalue=None, maxvalue=None):
19722102
"""
19732103
Setup MappingEditor:
19742104
return False if data is not supported, True otherwise
19752105
"""
1976-
self.data = data
1977-
1978-
icon = self.style().standardIcon(QStyle.SP_ComputerIcon)
1979-
if icon is not None:
1980-
self.setWindowIcon(icon)
1981-
1982-
if not title:
1983-
title = _("Session viewer") if readonly else _("Session editor")
1984-
if readonly:
1985-
title += ' (' + _('read only') + ')'
1986-
self.setWindowTitle(title)
1987-
1988-
layout = QVBoxLayout()
1989-
self.setLayout(layout)
2106+
if not isinstance(data, la.Session):
2107+
data = la.Session(data)
2108+
layout = self._setup_and_check(data, title, readonly)
19902109

19912110
self._listwidget = QListWidget(self)
19922111
arrays = [k for k, v in self.data.items() if self._display_in_grid(k, v)]
@@ -2079,8 +2198,7 @@ def void_formatter(array, *args, **kwargs):
20792198
# the problem is that the qlineedit (when ipython not present) does
20802199
# not eat the enter key, so it gets handled by the buttons below
20812200
# and this closes the window.
2082-
# FIXME: not having the buttons is a bit radical but I am out of time
2083-
# for this.
2201+
# FIXME: not having the buttons is a bit radical but I am out of time for this.
20842202
if qtconsole_available:
20852203
# Buttons configuration
20862204
btn_layout = QHBoxLayout()
@@ -2229,18 +2347,19 @@ def ipython_cell_executed(self):
22292347
main.show()
22302348

22312349
def on_item_changed(self, curr, prev):
2232-
name = str(curr.text())
2233-
array = self.data[name]
2234-
self.set_widget_array(array, name)
2235-
expr = self.expressions.get(name, name)
2236-
if qtconsole_available:
2237-
# this does not work because it updates the NEXT input, not the
2238-
# current one (it is supposed to be called from within the console)
2239-
# self.kernel.shell.set_next_input(expr, replace=True)
2240-
# self.kernel_client.input(expr)
2241-
pass
2242-
else:
2243-
self.eval_box.setText(expr)
2350+
if curr is not None:
2351+
name = str(curr.text())
2352+
array = self.data[name]
2353+
self.set_widget_array(array, name)
2354+
expr = self.expressions.get(name, name)
2355+
if qtconsole_available:
2356+
# this does not work because it updates the NEXT input, not the
2357+
# current one (it is supposed to be called from within the console)
2358+
# self.kernel.shell.set_next_input(expr, replace=True)
2359+
# self.kernel_client.input(expr)
2360+
pass
2361+
else:
2362+
self.eval_box.setText(expr)
22442363

22452364
def set_widget_array(self, array, title):
22462365
if isinstance(array, la.LArray):
@@ -2252,6 +2371,36 @@ def set_widget_array(self, array, title):
22522371
self.setWindowTitle(title)
22532372
self.arraywidget.set_data(array)
22542373

2374+
def new(self):
2375+
self._listwidget.clear()
2376+
self.data = la.Session()
2377+
self.arraywidget.set_data(la.zeros(0))
2378+
self.setCurrentFile(None)
2379+
self.statusBar().showMessage("Viewer has been reset", 2000)
2380+
2381+
def _openFile(self, filepath):
2382+
# XXX : clear console history ?
2383+
self._listwidget.clear()
2384+
self.data = la.Session(filepath)
2385+
arrays = [k for k, v in self.data.items() if self._display_in_grid(k, v)]
2386+
self.add_list_items(arrays)
2387+
self._listwidget.setCurrentRow(0)
2388+
self.setCurrentFile(filepath)
2389+
self.statusBar().showMessage("File {} loaded".format(os.path.basename(filepath)), 2000)
2390+
2391+
def _checkBeforeSave(self):
2392+
if len(self.data) == 0:
2393+
messageBox = QMessageBox()
2394+
messageBox.warning(self, "Warning", "No Data to be saved")
2395+
return False
2396+
else:
2397+
return True
2398+
2399+
def _saveData(self, filepath):
2400+
self.data.dump(filepath)
2401+
self.setCurrentFile(filepath)
2402+
self.statusBar().showMessage("Arrays saved in file {}".format(filepath), 2000)
2403+
22552404
def apply_changes(self):
22562405
self.arraywidget.accept_changes()
22572406

@@ -2617,11 +2766,11 @@ def edit(obj=None, title='', minvalue=None, maxvalue=None, readonly=False, depth
26172766

26182767
dlg = MappingEditor(parent) if hasattr(obj, 'keys') else ArrayEditor(parent)
26192768
if dlg.setup_and_check(obj, title=title, minvalue=minvalue, maxvalue=maxvalue, readonly=readonly):
2620-
if parent:
2621-
dlg.show()
2622-
else:
2623-
dlg.exec_()
2769+
if isinstance(obj, str):
2770+
dlg.updateRecentFileActions(obj)
2771+
dlg.show()
26242772

2773+
_app.exec()
26252774

26262775
def view(obj=None, title='', depth=0):
26272776
"""

0 commit comments

Comments
 (0)