about dialog, toolbar, plot saving, many bugfixes
120
forms.py
@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
import wx
|
||||
import wx.gizmos
|
||||
import wx.grid
|
||||
@ -29,6 +30,10 @@ ID_ADD_MARKERS = wx.NewId()
|
||||
|
||||
ID_ABOUT = wx.NewId()
|
||||
|
||||
ID_EXPORT_CSV = wx.NewId()
|
||||
|
||||
ID_SAVE_PLOT = wx.NewId()
|
||||
|
||||
class TreeListCtrl(wx.gizmos.TreeListCtrl):
|
||||
def __iter__(self):
|
||||
return TreeListCtrlIterator(self)
|
||||
@ -137,8 +142,7 @@ class MainFrame(wx.Frame):
|
||||
|
||||
self.BuildContextMenu()
|
||||
|
||||
# tbar = self.BuildToolBar()
|
||||
# self.SetToolBar(tbar)
|
||||
self.BuildToolBar()
|
||||
|
||||
self.auimgr.Update()
|
||||
|
||||
@ -171,7 +175,7 @@ class MainFrame(wx.Frame):
|
||||
menu.Append(ID_ADD_MODEL_SELECTED, 'Append model to selected')
|
||||
menu.AppendSeparator()
|
||||
menu.Append(ID_DUPLICATE_MODEL, "&Duplicate\tCtrl+D")
|
||||
#menu.Append(ID_DUPLICATE_TREE, "&Duplicate with subitems\tCtrl+Shift+D")
|
||||
menu.Append(ID_DUPLICATE_TREE, "&Duplicate with subitems\tCtrl+Shift+D")
|
||||
menu.Append(ID_DELETE_MODEL, 'Delete\tCtrl+E')
|
||||
menu.AppendSeparator()
|
||||
menu.Append(ID_TEST, "&Test\tCtrl+T")
|
||||
@ -211,11 +215,41 @@ class MainFrame(wx.Frame):
|
||||
self.m_plots.Bind(wx.EVT_CONTEXT_MENU,
|
||||
lambda x: self.m_plots.PopupMenu(menu1))
|
||||
|
||||
menu2 = wx.Menu()
|
||||
menu2.Append(ID_SHOW_RESULT, 'Show report')
|
||||
menu2.AppendSeparator()
|
||||
menu2.Append(ID_ADD_CURVES, 'Add curves')
|
||||
menu2.Append(ID_ADD_MARKERS, 'Add markers')
|
||||
self.m_user_models.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK,
|
||||
lambda x: self.m_user_models.PopupMenu(menu2))
|
||||
|
||||
def BuildToolBar(self):
|
||||
tbar = wx.ToolBar(self, -1)
|
||||
tbar.AddLabelTool(ID_SHOW_PLOT, 'Plot', wx.Bitmap('share/show-plot.png'))
|
||||
tbar.Realize()
|
||||
return tbar
|
||||
tb1 = aui.AuiToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize,
|
||||
agwStyle = aui.AUI_TB_DEFAULT_STYLE | aui.AUI_TB_VERTICAL)
|
||||
tb1.SetToolBitmapSize(wx.Size(16, 16))
|
||||
|
||||
tb1.AddSimpleTool(ID_ADD_MODEL_SELECTED, "model-new", wx.Bitmap('share/model-add.png'),
|
||||
'Add spacification to selected model')
|
||||
tb1.AddSeparator()
|
||||
tb1.AddSimpleTool(ID_DUPLICATE_MODEL, "model-dup", wx.Bitmap('share/model-dup.png'),
|
||||
'Duplicate selected model')
|
||||
tb1.AddSimpleTool(ID_DELETE_MODEL, "model-del", wx.Bitmap('share/model-delete.png'),
|
||||
'Delete selected model')
|
||||
tb1.AddSeparator()
|
||||
tb1.AddSimpleTool(ID_PROCESS_MODEL, "model-go", wx.Bitmap('share/model-go.png'),
|
||||
'Start process selected model')
|
||||
tb1.AddSeparator()
|
||||
tb1.AddSimpleTool(ID_SHOW_PLOT, "plot-quick", wx.Bitmap('share/plot-line.png'),
|
||||
'Show quick plot for selected model')
|
||||
tb1.AddSimpleTool(ID_SHOW_RESULT, "report-show", wx.Bitmap('share/report-show.png'),
|
||||
'Show result data and table for selected model')
|
||||
tb1.AddSeparator()
|
||||
tb1.AddSimpleTool(ID_ABOUT, "app-about", wx.Bitmap('share/app-about.png'),
|
||||
'Show infomation about application')
|
||||
tb1.Realize()
|
||||
|
||||
self.auimgr.AddPane(tb1, aui.AuiPaneInfo().Name("tb1").Caption("Toolbar").
|
||||
ToolbarPane().Left().Floatable(False).Movable(False).Gripper(False))
|
||||
|
||||
class SelectModelDialog(wx.Dialog):
|
||||
def __init__(self, parent):
|
||||
@ -259,8 +293,8 @@ class ResultFrame(wx.Frame):
|
||||
menubar = wx.MenuBar()
|
||||
|
||||
menu = wx.Menu()
|
||||
menu.Append(wx.NewId(), 'CSV\tCtrl+E')
|
||||
menu.Append(wx.NewId(), 'TeX')
|
||||
menu.Append(ID_EXPORT_CSV, 'CSV\tCtrl+E')
|
||||
#menu.Append(wx.NewId(), 'TeX')
|
||||
menubar.Append(menu, 'Export to')
|
||||
|
||||
return menubar
|
||||
@ -287,6 +321,37 @@ class LineSelectDialog(wx.Dialog):
|
||||
self.Layout()
|
||||
self.Centre(wx.BOTH)
|
||||
|
||||
class SizeSelector(wx.Dialog):
|
||||
def __init__(self, parent):
|
||||
wx.Dialog.__init__(self, parent, -1, 'Image size', size = (200, 100))
|
||||
|
||||
bSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
self.width = wx.SpinCtrl(self)
|
||||
self.width.SetRange(1, 5000)
|
||||
self.width.SetValue(800)
|
||||
bSizer.Add(self.width, 1, wx.EXPAND | wx.LEFT, 5)
|
||||
|
||||
self.height = wx.SpinCtrl(self)
|
||||
self.height.SetRange(1, 5000)
|
||||
self.height.SetValue(600)
|
||||
bSizer.Add(self.height, 1, wx.EXPAND | wx.RIGHT, 5)
|
||||
|
||||
buttonsSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL)
|
||||
|
||||
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
sizer.AddStretchSpacer(1)
|
||||
sizer.Add(bSizer, 0, wx.EXPAND | wx.ALL, 0)
|
||||
sizer.AddStretchSpacer(1)
|
||||
sizer.Add(buttonsSizer, 0, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
self.SetSizer(sizer)
|
||||
self.Layout()
|
||||
self.Centre(wx.BOTH)
|
||||
|
||||
def GetValues(self):
|
||||
return self.width.GetValue(), self.height.GetValue()
|
||||
|
||||
class PlotFrame(wx.Frame):
|
||||
def __init__(self, parent, title):
|
||||
wx.Frame.__init__ (self, parent, -1, title, size = wx.Size(600, 400))
|
||||
@ -299,3 +364,40 @@ class PlotFrame(wx.Frame):
|
||||
self.plot.SetEnableLegend(True)
|
||||
|
||||
self.Centre(wx.BOTH)
|
||||
|
||||
menubar = wx.MenuBar()
|
||||
menu = wx.Menu()
|
||||
menu.Append(ID_SAVE_PLOT, 'Save to file\tCtrl+S')
|
||||
menubar.Append(menu, 'Plot')
|
||||
self.SetMenuBar(menubar)
|
||||
|
||||
|
||||
class AboutDialog(wx.Dialog):
|
||||
def __init__(self, parent):
|
||||
wx.Dialog.__init__(self, parent, title = 'About Opal', size = (300, 330))
|
||||
|
||||
title = 'Opal System'
|
||||
version = 'Aurora version'
|
||||
copyr = '(c) 2012 Anton Vakhrushev'
|
||||
|
||||
self.SetBackgroundColour(wx.Colour(42, 42, 40))
|
||||
|
||||
img = wx.StaticBitmap(self)
|
||||
img.SetBitmap(wx.Bitmap('share/opal_logo.png'))
|
||||
|
||||
st = wx.StaticText(self, -1, title,
|
||||
pos = (15, 170), size = (270, 100))
|
||||
st.SetForegroundColour(wx.Colour(245, 245, 0))
|
||||
st.SetFont(wx.Font(24, wx.SWISS, wx.NORMAL, wx.NORMAL, False, "Verdana"));
|
||||
|
||||
st = wx.StaticText(self, -1, version,
|
||||
pos = (25, 215), size = (250, 20))
|
||||
st.SetForegroundColour(wx.Colour(240, 240, 240))
|
||||
st.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, False, "Verdana"));
|
||||
|
||||
st = wx.StaticText(self, -1, copyr,
|
||||
pos = (25, 255), size = (250, 30))
|
||||
st.SetForegroundColour(wx.Colour(240, 240, 240))
|
||||
st.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, False, "Verdana"));
|
||||
|
||||
self.Centre(wx.BOTH)
|
||||
|
119
opal.py
@ -33,7 +33,7 @@ class ModelData:
|
||||
# если мы создаем набор данных из другого набора данных
|
||||
elif isinstance(model, ModelData):
|
||||
self.mdef = model.mdef.Copy()
|
||||
self.jid = server.CreateJob() if model.jid != None else None
|
||||
self.jid = server.CreateJob() if model.jid else None
|
||||
else:
|
||||
self.mdef = None
|
||||
self.jid = None
|
||||
@ -133,6 +133,9 @@ class MainFrame(forms.MainFrame):
|
||||
self.Bind(wx.EVT_MENU, self.OnAddMarkers,
|
||||
id = forms.ID_ADD_MARKERS)
|
||||
|
||||
self.Bind(wx.EVT_MENU, self.OnAbout,
|
||||
id = forms.ID_ABOUT)
|
||||
|
||||
# События приложения
|
||||
|
||||
self.Bind(wx.EVT_CLOSE, self.OnClose)
|
||||
@ -158,6 +161,11 @@ class MainFrame(forms.MainFrame):
|
||||
self.server.Stop()
|
||||
self.Destroy()
|
||||
|
||||
def OnAbout(self, event):
|
||||
self.do_nothing = True
|
||||
forms.AboutDialog(self).ShowModal()
|
||||
self.do_nothing = False
|
||||
|
||||
def OnIdle(self, event):
|
||||
pass
|
||||
|
||||
@ -226,6 +234,23 @@ class MainFrame(forms.MainFrame):
|
||||
except Exception, e:
|
||||
print 'Error in overseer: ', e
|
||||
|
||||
def item_protector(func):
|
||||
"""
|
||||
Защитный механизм, который ловит исключения при неправильном
|
||||
обращении к элементам деревьев (компоненты TreeCtrl, TreeListCtrl)
|
||||
|
||||
Возвращает None, если было поймано исключение.
|
||||
Использование с функциями, которые не являются обработчиками событий
|
||||
не желательно
|
||||
"""
|
||||
def Checker(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except ItemError:
|
||||
print 'Oops'
|
||||
|
||||
return Checker
|
||||
|
||||
# Функции создания модели, сохранения и загрузки
|
||||
|
||||
def BuildSpecs(self, model):
|
||||
@ -319,7 +344,7 @@ class MainFrame(forms.MainFrame):
|
||||
Добавляет пользовательскую модель или спецификацию
|
||||
в корень дерева моделей.
|
||||
"""
|
||||
|
||||
# строим список моделей, которые будут добавлены
|
||||
ms = []
|
||||
while model:
|
||||
ms.append(model)
|
||||
@ -342,6 +367,8 @@ class MainFrame(forms.MainFrame):
|
||||
um.SetPyData(item, data)
|
||||
if root:
|
||||
um.Expand(root)
|
||||
um.SelectItem(item)
|
||||
um.SetFocus()
|
||||
|
||||
def OnAddModelToRoot(self, event):
|
||||
model = self.GetSelectedData(self.m_specs)
|
||||
@ -364,7 +391,9 @@ class MainFrame(forms.MainFrame):
|
||||
new_data = ModelData(self.server, model, pmdef)
|
||||
um.SetPyData(child, new_data)
|
||||
um.SetItemImage(child, self.icons.mready)
|
||||
um.SetFocus()
|
||||
um.Expand(item)
|
||||
um.SelectItem(child)
|
||||
else:
|
||||
wx.MessageBox('It\'s impossible to append model', 'Error')
|
||||
|
||||
@ -429,17 +458,37 @@ class MainFrame(forms.MainFrame):
|
||||
pass
|
||||
|
||||
def OnParamChanged(self, event):
|
||||
|
||||
def Walk(item):
|
||||
um.SetItemImage(item, self.icons.mready)
|
||||
child, _ = um.GetFirstChild(item)
|
||||
while child.IsOk():
|
||||
Walk(child)
|
||||
child = um.GetNextSibling(child)
|
||||
|
||||
um = self.m_user_models
|
||||
prop = event.GetProperty()
|
||||
if not prop:
|
||||
return
|
||||
value = prop.GetValue()
|
||||
param = prop.GetClientData()
|
||||
item, data = self.GetSelectedItemData(self.m_user_models)
|
||||
item, data = self.GetSelectedItemData(um)
|
||||
data.mdef[param] = value
|
||||
self.m_user_models.SetItemImage(item, self.icons.mready)
|
||||
Walk(item)
|
||||
|
||||
|
||||
def OnTest(self, event):
|
||||
|
||||
def Walk(item):
|
||||
print um.GetItemText(item)
|
||||
um.SetItemImage(item, self.icons.mready)
|
||||
child, cookie = um.GetFirstChild(item)
|
||||
while child.IsOk():
|
||||
Walk(child)
|
||||
child = um.GetNextSibling(child)
|
||||
|
||||
um = self.m_user_models
|
||||
Walk(um.GetRootItem())
|
||||
|
||||
# Получение данных выбранной модели
|
||||
|
||||
@ -465,6 +514,15 @@ class MainFrame(forms.MainFrame):
|
||||
|
||||
# Дублирование модели
|
||||
|
||||
def Duplicate(self, item_src, item_dst):
|
||||
um = self.m_user_models
|
||||
data = um.GetPyData(item_src)
|
||||
title = um.GetItemText(item_src)
|
||||
new_data = ModelData(self.server, data)
|
||||
um.SetItemText(item_dst, self.GenerateName(title))
|
||||
um.SetPyData(item_dst, new_data)
|
||||
um.SetItemImage(item_dst, self.icons.mready)
|
||||
|
||||
def OnDuplicate(self, event):
|
||||
"""
|
||||
Обработчик события "дублирование модели"
|
||||
@ -474,17 +532,29 @@ class MainFrame(forms.MainFrame):
|
||||
Результаты модели-оригинала не копируются.
|
||||
"""
|
||||
um = self.m_user_models
|
||||
item, data = self.GetSelectedItemData(self.m_user_models)
|
||||
title = um.GetItemText(item)
|
||||
parent = um.GetItemParent(item)
|
||||
child = um.AppendItem(parent, self.GenerateName(title))
|
||||
new_data = ModelData(self.server, data)
|
||||
um.SetPyData(child, new_data)
|
||||
um.SetItemImage(child, self.icons.mready)
|
||||
self.SetStatusText('Copy for "{}" created'.format(title), 0)
|
||||
item_src = self.GetSelectedItem(um)
|
||||
parent = um.GetItemParent(item_src)
|
||||
item_dst = um.AppendItem(parent, 'new-item')
|
||||
self.Duplicate(item_src, item_dst)
|
||||
# self.SetStatusText('Copy for "{}" created'.format(title), 0)
|
||||
|
||||
def OnDuplicateTree(self, event):
|
||||
pass
|
||||
|
||||
def Walk(item_src, item_dst):
|
||||
self.Duplicate(item_src, item_dst)
|
||||
|
||||
child_src, _ = um.GetFirstChild(item_src)
|
||||
while child_src.IsOk():
|
||||
child_dst = um.AppendItem(item_dst, 'new-item')
|
||||
Walk(child_src, child_dst)
|
||||
child_src = um.GetNextSibling(child_src)
|
||||
|
||||
um = self.m_user_models
|
||||
item_src = self.GetSelectedItem(um)
|
||||
parent = um.GetItemParent(item_src)
|
||||
item_dst = um.AppendItem(parent, 'new-item')
|
||||
Walk(item_src, item_dst)
|
||||
um.Expand(item_dst)
|
||||
|
||||
# Удаление модели
|
||||
|
||||
@ -579,6 +649,7 @@ class MainFrame(forms.MainFrame):
|
||||
else:
|
||||
self.m_plots.SetItemImage(child, self.icons.pline)
|
||||
|
||||
@item_protector
|
||||
def OnAddCurves(self, event):
|
||||
self.AddLines(LINE_CURVE)
|
||||
|
||||
@ -749,7 +820,10 @@ class PlotFrame(forms.PlotFrame):
|
||||
def __init__(self, parent, title, lines):
|
||||
forms.PlotFrame.__init__(self, parent, title)
|
||||
|
||||
colours = ['red', 'blue', 'green']
|
||||
self.Bind(wx.EVT_MENU, self.OnSaveImage,
|
||||
id = forms.ID_SAVE_PLOT)
|
||||
|
||||
colours = ['red', 'blue', 'green', 'magenta', 'purple', 'brown', 'yellow']
|
||||
plot_lines = []
|
||||
for i, line in enumerate(lines):
|
||||
attr = {}
|
||||
@ -766,6 +840,23 @@ class PlotFrame(forms.PlotFrame):
|
||||
graph = wxplot.PlotGraphics(plot_lines)
|
||||
self.plot.Draw(graph)
|
||||
|
||||
def OnSaveImage(self, event):
|
||||
img_file = wx.FileSelector('Save plot',
|
||||
default_filename = 'plot.png',
|
||||
default_extension = 'png',
|
||||
wildcard = 'PNG files (*.png)|*.png',
|
||||
flags = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
|
||||
size_sel = forms.SizeSelector(self)
|
||||
if img_file and size_sel.ShowModal() == wx.ID_OK:
|
||||
self.plot.Freeze()
|
||||
w, h = size_sel.GetValues()
|
||||
old_size = self.plot.GetSize()
|
||||
self.plot.SetSize((w, h))
|
||||
self.plot.SaveFile(img_file)
|
||||
self.plot.SetSize(old_size)
|
||||
self.plot.Thaw()
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Приложение
|
||||
#-----------------------------------------------------------------------------
|
||||
|
24
server.py
@ -130,34 +130,26 @@ class LocalServer:
|
||||
def GetJobsCount(self):
|
||||
return len(self.jobs)
|
||||
|
||||
#def CheckJID(self, func):
|
||||
# def
|
||||
|
||||
def GetJobState(self, jid):
|
||||
job = self.jobs.get(jid)
|
||||
if job != None:
|
||||
if job:
|
||||
return job.GetState()
|
||||
|
||||
def IsJobChanged(self, jid):
|
||||
job = self.jobs.get(jid)
|
||||
if job != None:
|
||||
return job.IsChanged()
|
||||
else:
|
||||
False
|
||||
return job.IsChanged() if job else False
|
||||
|
||||
def GetJobResult(self, jid):
|
||||
job = self.jobs.get(jid)
|
||||
if job != None:
|
||||
return job.GetResult()
|
||||
return job.GetResult() if job else None
|
||||
|
||||
def GetJobTID(self, jid):
|
||||
job = self.jobs.get(jid)
|
||||
if job != None:
|
||||
return job.tid
|
||||
return job.tid if job else None
|
||||
|
||||
def LaunchJob(self, jid, data_def):
|
||||
job = self.jobs.get(jid)
|
||||
if job != None:
|
||||
if job:
|
||||
tid = data_def.DD.tid
|
||||
datadump = data_def.PackParams()
|
||||
job.Launch(tid, datadump)
|
||||
@ -165,12 +157,12 @@ class LocalServer:
|
||||
|
||||
def StopJob(self, jid):
|
||||
job = self.jobs.get(jid)
|
||||
if job != None:
|
||||
if job:
|
||||
job.Stop()
|
||||
|
||||
def DeleteJob(self, jid):
|
||||
job = self.jobs.get(jid)
|
||||
if job != None:
|
||||
if job:
|
||||
job.Stop()
|
||||
del self.jobs[jid]
|
||||
|
||||
@ -317,8 +309,8 @@ class Job:
|
||||
self.ChangeState()
|
||||
|
||||
def Stop(self):
|
||||
WriteToLog('Try to kill')
|
||||
if self.proc and self.proc.poll() == None:
|
||||
WriteToLog('Try to kill')
|
||||
self.proc.kill()
|
||||
self.ChangeState()
|
||||
WriteToLog('Job killed')
|
||||
|
BIN
share/app-about.png
Normal file
After Width: | Height: | Size: 809 B |
BIN
share/model-add.png
Normal file
After Width: | Height: | Size: 584 B |
BIN
share/model-delete.png
Normal file
After Width: | Height: | Size: 580 B |
BIN
share/model-dup.png
Normal file
After Width: | Height: | Size: 534 B |
BIN
share/model-go.png
Normal file
After Width: | Height: | Size: 664 B |
BIN
share/opal_logo.png
Normal file
After Width: | Height: | Size: 115 KiB |
BIN
share/report-show.png
Normal file
After Width: | Height: | Size: 652 B |