7979
8080from 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):
19492088DISPLAY_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
26262775def view (obj = None , title = '' , depth = 0 ):
26272776 """
0 commit comments