opal/opal.py
anwinged a6c16d3683
2012-05-28 02:52:38 +00:00

1243 lines
48 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#-------------------------------------------------------------------------------
# Name: opal.py
# Purpose:
#
# Author: Anton Vakhrushev
#
# Created: 14.03.2012
# Copyright: (c) Anton Vakhrushev 2012
# Licence: LGPL
#-------------------------------------------------------------------------------
#!/usr/bin/env python#!/usr/bin/env python
#! coding: utf-8
import server
import task
import wx
import forms
wxpg = forms.wxpg
wxplot = forms.wxplot
from wx.lib.embeddedimage import PyEmbeddedImage
import time
import threading
import re
import json
import zlib
from pprint import pprint
import gettext
_ = gettext.gettext
# состояния модели, унаследованные от состояния задачи
MODEL_READY = server.JOB_READY
MODEL_RUNNING = server.JOB_RUNNING
MODEL_STOPPED = server.JOB_STOPPED
MODEL_COMPLETED = server.JOB_COMPLETED
# собственные состояния модели
MODEL_NO_EXEC = 101
# --------------------------------------------------------------------------
# Данные о пользовательской модели
# --------------------------------------------------------------------------
class ModelData:
def __init__(self, server, model, parent_data = None):
# если мы создаем новый набор данных из описания модели
if isinstance(model, task.DataDescription):
self.mdef = task.DataDefinition(model, parent_data)
self.jid = server.CreateJob() if model.IsExecutable() else None
# если мы создаем набор данных из другого набора данных
elif isinstance(model, ModelData):
self.mdef = model.mdef.Copy()
self.jid = server.CreateJob() if model.jid else None
else:
self.mdef = None
self.jid = None
assert self.mdef
self.res = None # результаты выполнения работы
if self.jid:
self.state = MODEL_READY # состояние модели
else:
self.state = MODEL_NO_EXEC # состояние модели
# --------------------------------------------------------------------------
# Данные о линии в графике
# --------------------------------------------------------------------------
LINE_CURVE = 1
LINE_MARKER = 2
LINE_HISTOGRAM = 3
class LineData:
"""
Данные одной линии для графика
Предназначен для использования совместно с графическим компонентом,
поэтому не имеет собственного значения названия. Вместо этого
название берется из графического компонента.
"""
def __init__(self, ums_ptr, plots_ptr,
type, columns, colour = None, style = None):
self.ums_ptr = ums_ptr # (ptr, item) указатель на компонент с моделями
# и индекс в этом компоненте
self.plots_ptr = plots_ptr # (ptr, item) указатель на компонент с графиками
# и индекс в этом компоненте
self.type = type # тип графика
self.columns = columns # пара (x, y)
self.colour = colour # цвет: если не задан выбирается из списка
self.style = style # стиль: если не задан, еспользуется по умолчанию
def GetModelTitle(self):
container, item = self.ums_ptr
return container.GetItemText(item)
def GetTitle(self):
# если есть указатель на компонент с графиками,
# извлекаем оттуда название
if self.plots_ptr:
container, item = self.plots_ptr
return container.GetItemText(item)
# иначе формируем название на основе имени модели и
# имен выбранных столбцов
else:
container, item = self.ums_ptr
title = container.GetItemText(item)
data = container.GetPyData(item)
assert data.res # если результата нет, то а-та-та
colx, coly = self.columns
title_x = data.res.columns[colx].GetTitle()
title_y = data.res.columns[coly].GetTitle()
return "{}: {}({})".format(title, title_y, title_x)
def GetPoints(self):
container, item = self.ums_ptr
data = container.GetPyData(item)
assert data.res # если результата нет, то а-та-та
return data.res.Zip(*self.columns)
# --------------------------------------------------------------------------
# Ошибки доступа к элементам в контейнерах
# --------------------------------------------------------------------------
class ItemError(Exception):
pass
#-----------------------------------------------------------------------------
# Главная форма
#-----------------------------------------------------------------------------
class MainFrame(forms.MainFrame):
def __init__(self):
forms.MainFrame.__init__(self, None)
global _
_ = self.lang.ugettext
self.model = None
self.name_id = 1
conf = self.settings['conf']
workers = int(self.settings['workers'])
s = server.LocalServer(conf=conf, workers=workers)
s.LoadModels()
self.models = s.GetModels()
s.Start()
self.server = s
# События компонентов
self.m_user_models.Bind(wx.EVT_TREE_SEL_CHANGED,
self.OnModelSelected)
self.m_user_models.Bind(wx.EVT_TREE_DELETE_ITEM,
self.OnDeleteModelsItem)
self.m_params.Bind(wxpg.EVT_PG_CHANGING,
self.OnParamChanging)
self.m_params.Bind(wxpg.EVT_PG_CHANGED,
self.OnParamChanged)
self.m_specs.Bind(wx.EVT_TREE_ITEM_ACTIVATED,
self.OnAddModelToSelected)
self.m_user_models.Bind(wx.EVT_TREE_ITEM_ACTIVATED,
self.OnModelProcess)
self.m_plots.Bind(wx.EVT_TREE_ITEM_ACTIVATED,
self.OnPlotProcess)
self.m_plots.Bind(wx.EVT_CHAR,
self.OnPlotsKeyPressed)
# События меню
self.Bind(wx.EVT_MENU, self.OnNewProject,
id = forms.ID_NEW)
self.Bind(wx.EVT_MENU, self.OnOpenProject,
id = forms.ID_OPEN)
self.Bind(wx.EVT_MENU, self.OnSaveProject,
id = forms.ID_SAVE)
self.Bind(wx.EVT_MENU, self.OnTest,
id = forms.ID_TEST)
self.Bind(wx.EVT_MENU, self.OnAddModelToRoot,
id = forms.ID_ADD_MODEL_ROOT)
self.Bind(wx.EVT_MENU, self.OnAddModelToSelected,
id = forms.ID_ADD_MODEL_SELECTED)
self.Bind(wx.EVT_MENU, self.OnDuplicate,
id = forms.ID_DUPLICATE_MODEL)
self.Bind(wx.EVT_MENU, self.OnDuplicateTree,
id = forms.ID_DUPLICATE_TREE)
self.Bind(wx.EVT_MENU, self.OnDeleteModel,
id = forms.ID_DELETE_MODEL)
self.Bind(wx.EVT_MENU, self.OnModelProcess,
id = forms.ID_PROCESS_MODEL)
self.Bind(wx.EVT_MENU, self.OnModelStop,
id = forms.ID_STOP_MODEL)
self.Bind(wx.EVT_MENU, self.OnShowResult,
id = forms.ID_SHOW_RESULT)
self.Bind(wx.EVT_MENU, self.OnQuickShowPlot,
id = forms.ID_SHOW_PLOT)
self.Bind(wx.EVT_MENU, self.OnAddPlot,
id = forms.ID_ADD_PLOT)
self.Bind(wx.EVT_MENU, self.OnAddCurves,
id = forms.ID_ADD_CURVES)
self.Bind(wx.EVT_MENU, self.OnAddMarkers,
id = forms.ID_ADD_MARKERS)
self.Bind(wx.EVT_MENU, lambda x: self.ChangeLocale('en_EN'),
id = forms.ID_ENGLISH_LANG)
self.Bind(wx.EVT_MENU, lambda x: self.ChangeLocale('ru_RU'),
id = forms.ID_RUSSIAN_LANG)
self.Bind(wx.EVT_MENU, self.OnAbout,
id = forms.ID_ABOUT)
# События приложения
self.Bind(wx.EVT_CLOSE, self.OnClose)
self.Bind(wx.EVT_IDLE, self.OnIdle)
# если установлен в True, то обработчик состояний работ
# будет работать вхолостую, чтобы не создать deadlock
# проблема возникает в том, что при одновременной блокировке
# GUI и вызова модального диалога, последний весит все приложение напрочь
# в момент своего закрытия. а так как диалог все равно модальный,
# форме не обязательно обновляться в тот момент, когда он открыт
self.do_nothing = False
ov = threading.Thread(target = self.Overseer)
ov.daemon = True
ov.start()
# self.NewProject(self.models[0])
# Функции приложения и обработки сервера
def ChangeLocale(self, locale):
self.settings['locale'] = locale
wx.MessageBox(_('Locale changed. Restart application to apply settings'), _('Information'))
self.lang = gettext.translation('opal', './locale', languages=[locale], fallback=True)
self.lang.install(unicode=True)
def OnClose(self, event):
self.server.Stop()
self.SaveSettings()
self.Destroy()
def OnAbout(self, event):
self.do_nothing = True
forms.AboutDialog(self).ShowModal()
self.do_nothing = False
def OnIdle(self, event):
pass
def Overseer(self):
"""
Функция-надсмотрщик, которая периодически проверяет состояние
всех пользовательских моделей, в зависимости от этого изменяет
состояние окружения, выводит информацию, подгружает результаты
выполнения работ и др.
"""
try:
um = self.m_user_models
cycle_count = 0
while True:
time.sleep(0.1)
# если нужно подождать, то мы подождем
if self.do_nothing:
continue
wx.MutexGuiEnter()
try:
# print 'cycle{:-8}'.format(cycle_count)
cycle_count += 1
# просматриваем всю иерархию моделей
for item in um:
data = um.GetPyData(item)
if not data:
continue
jid = data.jid
if jid and self.server.IsJobChanged(jid):
# таким образом, тут мы обрабатываем новое состояние
# работы (модели)
state, percent, comment = self.server.GetJobState(jid)
data.state = state
self.SetModelState(item, data.state)
p = _('Unknown') if percent < 0 else '{:%}'.format(percent)
um.SetItemText(item, p, 2)
um.SetItemText(item, comment, 3)
print 'JID', jid, (state, percent, comment)
# завершающие действия по окончанию выполнения работы
if state == server.JOB_COMPLETED:
# получаем результаты выполнения
data.res = self.server.GetJobResult(jid)
# если завершившаяся задача в данный момент выделена
# то сразу же показываем этот результат
if um.IsSelected(item):
self.ShowQuickResult(data.res)
finally:
wx.MutexGuiLeave()
pass
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):
"""
Выстраивает иерархию спецификаций для выбранной модели
"""
def DoItem(item, model):
sp.SetPyData(item, model)
for label, spec in model.GetSpecs().iteritems():
child = sp.AppendItem(item, spec.GetTitle())
DoItem(child, spec)
sp = self.m_specs
sp.DeleteAllItems()
root = sp.AddRoot(model.GetTitle())
DoItem(root, model)
sp.ExpandAll()
sp.SortChildren(root)
def NewProject(self, model, create_default = True):
"""
Начать новый проект:
0. Очичтить все компоненты
1. Построить дерево спецификаций
2. Создать одну пользовательскую модель (по умолчанию)
3. Сделать заготовки для графиков/отчетов/прочего
"""
self.m_specs.DeleteAllItems()
self.m_user_models.DeleteAllItems()
self.m_params.Clear()
self.m_quick_result.Clear()
self.m_plots.DeleteAllItems()
# фиксируем выбранную модель
self.model = model
# Строим спецификации
self.BuildSpecs(model)
# Очищаем окно пользовательских моделей
# и создаем там одну
um = self.m_user_models
um.DeleteAllItems()
um.AddRoot('root')
if create_default:
self.AddModelToRoot(model)
# Создаем корневой элемент для окна с графиками
self.m_plots.AddRoot('root')
self.SetStatusText(_('Model "{}" selected').format(model.GetTitle()), 0)
return True # Project(model)
def OnNewProject(self, event):
self.do_nothing = True
f = SelectModelDialog(self, self.models)
if f.ShowModal() == wx.ID_OK:
model = f.GetSelectedModel()
if model:
self.NewProject(model)
else:
print 'Empty model'
self.do_nothing = False
def OnOpenProject(self, event):
def WalkModels(source, root, models, model_def = None):
# for имя-модели, параметры-модели
for mname, value in source.iteritems():
label = value['model']
if label not in models:
raise KeyError, 'no "{}"'.format(label)
data = ModelData(self.server, models[label], model_def)
data.mdef.params = value['data']
data.state = value['state']
if 'result' in value:
data.res = task.ResultData(value['result'])
# тут надо проверить все добавленные параметры
item = self.AddModel(root, mname, data)
model_items[mname] = item
WalkModels(value['um'], item, models[label].GetSpecs(), data)
def WalkPlots(source, root):
for plot in source:
item = self.m_plots.AppendItem(root, plot[0])
self.m_plots.SetPyData(item, 'plot')
self.m_plots.SetItemImage(item, self.icons.porg)
for line in plot[1]:
model_label = line['model']
data = LineData(
ums_ptr = (um, model_items[model_label]),
plots_ptr = None,
type = line['type'],
columns = (line['colx'], line['coly'])
)
self.AddLine(item, line['title'], data)
try:
wx.BeginBusyCursor()
self.do_nothing = True
selector = wx.FileDialog(
self,
_('Select file to load project'),
'',
'',
'Opal files (*.opl)|*.opl|Text files (*.txt)|*.txt',
wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
if selector.ShowModal() == wx.ID_OK:
filename = selector.GetPath()
data = {}
if selector.GetFilterIndex() == 0:
with open(filename, 'rb') as f:
data = json.loads(zlib.decompress(f.read()))
else:
with open(filename, 'r') as f:
data = json.loads(f.read())
tid = data['tid']
model_label = data['model']
model = self.server.CheckModel(tid, model_label)
if not model:
raise ValueError
self.NewProject(model, False)
um = self.m_user_models
model_items = {}
root = um.GetRootItem()
WalkModels(data['um'], root, {model.GetLabel(): model})
um.ExpandAll(root)
root = self.m_plots.GetRootItem()
WalkPlots(data['plots'], root)
self.m_plots.ExpandAll()
except Exception, e:
wx.MessageBox(_("Can't load saved file"), _('Error'), wx.ICON_ERROR | wx.OK)
print 'Oops', type(e), e
finally:
wx.EndBusyCursor()
self.do_nothing = False
def OnSaveProject(self, event):
def WalkModels(item, dest):
"""
Сохраняем информацию о каждой модели
"""
if item != um.GetRootItem():
data = um.GetPyData(item)
title = um.GetItemText(item)
mdef = data.mdef
dest[title] = {
'model': mdef.DD.GetLabel(),
'data': mdef.params,
'um': {},
'state': data.state,
}
if data.res:
dest[title]['result'] = data.res.DumpData()
dest = dest[title]['um']
child, _ = um.GetFirstChild(item)
while child.IsOk():
WalkModels(child, dest)
child = um.GetNextSibling(child)
def WalkPlots(root, dest):
"""
Сохраняем информацию о каждом графике
"""
# по всеи элементам первого уровня
item1, _ = self.m_plots.GetFirstChild(root)
while item1.IsOk():
# по всем элементам второго уровня
item2, _ = self.m_plots.GetFirstChild(item1)
lines = []
while item2.IsOk():
line = self.m_plots.GetPyData(item2)
data = {
'title': self.m_plots.GetItemText(item2),
'colx': line.columns[0],
'coly': line.columns[1],
'model': line.GetModelTitle(),
'type': line.type,
}
lines.append(data)
item2 = self.m_plots.GetNextSibling(item2)
title = self.m_plots.GetItemText(item1)
dest.append([title, lines])
item1 = self.m_plots.GetNextSibling(item1)
try:
wx.BeginBusyCursor()
self.do_nothing = True
selector = wx.FileDialog(
self,
_('Select file to save project'),
'',
self.model.GetTitle() + ' project',
'Opal files (*.opl)|*.opl|Text files (*.txt)|*.txt',
wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
if selector.ShowModal() == wx.ID_OK:
data = {}
data['tid'] = self.model.GetTaskId()
data['model'] = self.model.GetLabel()
um = self.m_user_models
data['um'] = {}
WalkModels(um.GetRootItem(), data['um'])
data['plots'] = []
WalkPlots(self.m_plots.GetRootItem(), data['plots'])
# pprint(data)
dump = json.dumps(data, indent = 2)
filename = selector.GetPath()
# сохраняем в упакованный бинарный формат
if selector.GetFilterIndex() == 0:
with open(filename, 'wb') as f:
f.write(zlib.compress(dump, 9))
# сохраняем в простой текстовый формат
else:
with open(filename, 'w') as f:
f.write(dump)
except Exception as e:
wx.MessageBox(_("Can't save the project"), _('Error'), wx.ICON_ERROR | wx.OK)
print e
finally:
wx.EndBusyCursor()
self.do_nothing = False
# Функции непосредственной работы с моделями:
# создание, изменение, дублирование и прочее
# Работа с именами моделей
def CheckName(self, name):
"""
Проверяет имя на уникальность в иерархии пользовательских моделей.
Возвращает True, если имя уникально, иначе False.
"""
um = self.m_user_models
for item in um:
item_name = um.GetItemText(item)
if item_name == name:
return False
return True
def GenerateName(self, name):
"""
На основе переданного имени генерирует новое имя модели таким образом,
чтобы оно осталось уникальным в рамках существующей иерархии моделей.
"""
m = re.match(r'^(.+)\s+\d*$', name, re.UNICODE)
basename = m.group(1) if m else name
while True:
name = basename + ' ' + str(self.name_id)
if self.CheckName(name):
return name
self.name_id += 1
# Добавление новых моделей
def SetModelState(self, item, state):
if state == MODEL_READY:
icon = self.icons.mready
text = _('Ready')
elif state == MODEL_RUNNING:
icon = self.icons.mrun
text = _('Running')
elif state == MODEL_COMPLETED:
icon = self.icons.mcomplete
text = _('Completed')
elif state == MODEL_STOPPED:
icon = self.icons.mstopped
text = _('Stopped')
else:
icon = self.icons.mnoexec
text = _('No executable')
self.m_user_models.SetItemImage(item, icon)
self.m_user_models.SetItemText(item, text, 1)
def AddModel(self, item, title, model_data):
"""
Добавляет модель к указанной,
устанавливает имя, данные, состояние, иконку
"""
um = self.m_user_models
item = um.AppendItem(item, title)
um.SetPyData(item, model_data)
self.SetModelState(item, model_data.state)
return item
def AddModelToRoot(self, model):
"""
Добавляет пользовательскую модель или спецификацию
в корень дерева моделей.
"""
# строим список моделей, которые будут добавлены
ms = []
while model:
ms.append(model)
model = model.GetParent()
ms.reverse()
# ms: [root-model, child, child-of-child1, ..., model]
um = self.m_user_models
item = um.GetRootItem()
defparent = None
root = None
for i, m in enumerate(ms):
name = self.GenerateName(m.GetTitle())
if not i:
root = item
data = ModelData(self.server, m, defparent)
item = self.AddModel(item, name, data)
defparent = data.mdef
if root:
um.Expand(root)
um.SelectItem(item)
um.SetFocus()
def OnAddModelToRoot(self, event):
model = self.GetSelectedData(self.m_specs)
self.AddModelToRoot(model)
def OnAddModelToSelected(self, event):
"""
Добавляет пользовательскую спецификацию к указанной модели
"""
# получаем модель, которая будет добавлена к пользовательским
model = self.GetSelectedData(self.m_specs)
# получаем пользовательскую модель, к которой хотим присоединить новую
item, data = self.GetSelectedItemData(self.m_user_models)
pmdef = data.mdef
um = self.m_user_models
# если новая модель может быть присоединена...
if pmdef.DD == model.parent:
name = self.GenerateName(model.GetTitle())
new_data = ModelData(self.server, model, pmdef)
child = self.AddModel(item, name, new_data)
um.SetFocus()
um.Expand(item)
um.SelectItem(child)
else:
wx.MessageBox(_("It's impossible to append model"), _('Error'))
# Реакция на выбор модели
def SelectUserModel(self, model_def):
def SelectProperty(param_type):
"""
По указанному имени типа возвращает "свойство" для списка "свойств"
Смотри руководство пользователя для того, чтобы получить полную
информацию о всех типах данных, используемых в Opal.
"""
if param_type == 'bool' or param_type == 'boolean':
return wxpg.BoolProperty
elif param_type == 'int':
return wxpg.IntProperty
elif param_type == 'float' or param_type == 'double':
return wxpg.FloatProperty
elif param_type == 'str' or param_type == 'string':
return wxpg.StringProperty
elif param_type == 'list':
return wxpg.ArrayStringProperty
else:
# очень плохо, если это произошло
raise KeyError()
pg = self.m_params
pg.Clear()
for label, value in model_def.params.iteritems():
param = model_def.DD[label]
title = param.GetTitle()
prop = SelectProperty(param.GetType())
pid = pg.Append(prop(title, value = value))
pg.SetPropertyClientData(pid, label)
pg.SetPropertyHelpString(pid, param.GetComment())
pg.Sort()
def ShowQuickResult(self, result):
if not result:
return
pg = self.m_quick_result
pg.Clear()
for label, param in result.data.iteritems():
pg.Append(wxpg.StringProperty(label, value = str(param.GetValue())))
pg.SetSplitterLeft()
def OnModelSelected(self, event):
item = event.GetItem()
data = self.m_user_models.GetPyData(item)
if data:
self.SelectUserModel(data.mdef)
self.ShowQuickResult(data.res)
# Изменение параметров модели
def OnParamChanging(self, event):
#value = event.GetValue()
#print repr(value)
#wx.MessageBox(value, 'changing')
#event.Veto()
pass
def OnParamChanged(self, event):
def Walk(item):
data = um.GetPyData(item)
if data.state != MODEL_NO_EXEC:
data.state = MODEL_READY
self.SetModelState(item, data.state)
child, _null = 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(um)
data.mdef[param] = value
# так как значение параметра изменилось,
# то все субмодели должны быть пересчитаны
Walk(item)
def OnTest(self, event):
pass
# Получение данных выбранной модели
def GetSelectedItem(self, source):
item = source.GetSelection()
if not item.IsOk():
raise ItemError(_('Invalid item'))
return item
def GetSelectedData(self, source):
item = self.GetSelectedItem(source)
data = source.GetPyData(item)
if not data:
raise ItemError(_('Empty data'))
return data
def GetSelectedItemData(self, source):
item = self.GetSelectedItem(source)
data = source.GetPyData(item)
if not data:
raise ItemError(_('Empty data'))
return (item, data)
# Дублирование модели
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)
self.SetModelState(item_dst, new_data.state)
def OnDuplicate(self, event):
"""
Обработчик события "дублирование модели"
Когда модель дублируется, ее параметры копируются в новую модель,
при неоходимости выделяется слот для работ на сервере.
Результаты модели-оригинала не копируются.
"""
um = self.m_user_models
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):
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)
# Удаление модели
def OnDeleteModelsItem(self, event):
item = event.GetItem()
data = self.m_user_models.GetPyData(item)
if data:
self.server.DeleteJob(data.jid)
def OnDeleteModel(self, event):
item = self.GetSelectedItem(self.m_user_models)
self.m_user_models.Delete(item)
# Функции запуска модели на выполнение и управления очередью
def OnModelProcess(self, event):
um = self.m_user_models
for i in um.GetSelections():
data = um.GetItemPyData(i)
if data.jid:
self.server.LaunchJob(data.jid, data.mdef)
def OnModelStop(self, event):
um = self.m_user_models
for i in um.GetSelections():
data = um.GetItemPyData(i)
if data.jid:
self.server.StopJob(data.jid)
# Функции управления таблицами и отчетами
def OnShowResult(self, event):
item, data = self.GetSelectedItemData(self.m_user_models)
title = self.m_user_models.GetItemText(item)
title = _('Result for model "{}"').format(title)
rframe = ResultFrame(self, title, data.res)
rframe.Show()
# Функции управления графиками
def OnAddPlot(self, event):
root = self.m_plots.GetRootItem()
child = self.m_plots.AppendItem(root, _('New plot'))
self.m_plots.SetPyData(child, 'plot')
self.m_plots.SetItemImage(child, self.icons.porg)
self.m_plots.SelectItem(child)
def GetLines(self, line_type):
"""
Возвращает набор линий, которые пользователь указал для
построения графика к выбранной модели.
Возвращает список экземпляров LineData
"""
def CreateLineSelectDialog(parent, title, model_data):
f = LineSelectDialog(parent, title)
for index, col in enumerate(model_data.res.columns):
row_title = col.GetTitle()
row_data = index
f.Add(row_title, row_data)
f.SetSelections()
return f
def GetLinesFromUser(select_dialog):
lines = []
self.do_nothing = True
try:
if select_dialog.ShowModal() == wx.ID_OK:
for xy in select_dialog.GetLineColumns():
line_data = LineData(
(um, item), # указатель на модель
None, # указатель на график
line_type, xy) # тип линии, колонки и прочее
lines.append(line_data)
f.Destroy()
finally:
self.do_nothing = False
return lines
um = self.m_user_models
lines = []
items = um.GetSelections()
count = len(items)
for index, item in enumerate(items, 1):
data = um.GetPyData(item)
title = um.GetItemText(item)
msg = _('Line(s) for "{}" ({}/{})').format(title, index, count)
if not data.res:
wx.MessageBox(
_('There is no any result data for model!'),
msg, wx.OK | wx.ICON_EXCLAMATION)
else:
f = CreateLineSelectDialog(self, msg, data)
lines += GetLinesFromUser(f)
return lines
@item_protector
def AddLines(self, line_type):
"""
Добавляет линии в выделенный график
(компонента с графиками m_plots)
"""
# получаем указатель на индекс и данные элемента, который выделен
item, data = self.GetSelectedItemData(self.m_plots)
# если это на контейнер с графиками, то выходим
if data != 'plot':
return
# получаем указанные пользователем линии
lines = self.GetLines(line_type)
for line in lines:
self.AddLine(item, line.GetTitle(), line)
def AddLine(self, root, title, line_data):
item = self.m_plots.AppendItem(root, title)
self.m_plots.SetPyData(item, line_data)
line_data.plots_ptr = (self.m_plots, item)
if line_data.type == LINE_MARKER:
self.m_plots.SetItemImage(item, self.icons.pmarker)
else:
self.m_plots.SetItemImage(item, self.icons.pline)
self.m_plots.Expand(root)
return item
def OnAddCurves(self, event):
self.AddLines(LINE_CURVE)
def OnAddMarkers(self, event):
self.AddLines(LINE_MARKER)
def ShowPlot(self, lines, plot_title = ''):
if lines:
p = PlotFrame(self, _('Plot'), lines)
wx.FutureCall(20, p.Show)
# p.Show()
def OnQuickShowPlot(self, event):
lines = self.GetLines(LINE_CURVE)
um = self.m_user_models
item = self.GetSelectedItem(um)
title = um.GetItemText(item)
self.ShowPlot(lines, title)
def OnPlotProcess(self, event):
item = self.m_plots.GetSelection()
data = self.m_plots.GetItemPyData(item)
lines = []
if data == 'plot':
child, cookie = self.m_plots.GetFirstChild(item)
while child.IsOk():
title = self.m_plots.GetItemText(child)
line_data = self.m_plots.GetItemPyData(child)
line_data.title = title
lines.append(line_data)
child, cookie = self.m_plots.GetNextChild(item, cookie)
else:
title = self.m_plots.GetItemText(item)
data.title = title
lines = [ data ]
self.ShowPlot(lines)
def OnPlotsKeyPressed(self, event):
keycode = event.GetKeyCode()
item = self.GetSelectedItem(self.m_plots)
if keycode == wx.WXK_DELETE:
self.m_plots.Delete(item)
event.Skip()
#-----------------------------------------------------------------------------
# Форма с выбором модели из представленного списка
#-----------------------------------------------------------------------------
class SelectModelDialog(forms.SelectModelDialog):
def __init__(self, parent, models):
forms.SelectModelDialog.__init__(self, parent)
self.ilist = wx.ImageList(32, 32)
self.mlist.SetImageList(self.ilist, wx.IMAGE_LIST_NORMAL)
self.data_list = {}
for index, model in enumerate(models):
item = wx.ListItem()
item.SetId(index)
item.SetText(model.GetTitle())
self.data_list[index] = model
img_data = model.GetImage()
if img_data:
img = PyEmbeddedImage(img_data)
index = self.ilist.Add(img.GetBitmap())
item.SetImage(index)
self.mlist.InsertItem(item)
def GetSelectedModel(self):
index = self.mlist.GetNextItem(-1, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED)
return self.data_list.get(index)
#-----------------------------------------------------------------------------
# Форма с результатами выполнения работы
#-----------------------------------------------------------------------------
class ResultFrame(forms.ResultFrame):
def __init__(self, parent, title, result):
forms.ResultFrame.__init__(self, parent, title)
self.result = result
self.UpdateResults()
self.Bind(wx.EVT_MENU, self.OnExportToCSV,
id = forms.ID_EXPORT_CSV)
def UpdateResults(self):
self.scalar.Clear()
self.table.ClearGrid()
if not self.result:
return
cols = len(self.result.columns)
rows = len(self.result.rows)
self.table.CreateGrid(rows, cols)
#
for i, col in enumerate(self.result.columns):
label = "{} ({} {})".format(col.GetTitle(), col.GetType(), col.GetLabel())
self.table.SetColLabelValue(i, label)
#
for i, row in enumerate(self.result.rows):
for j, value in enumerate(row):
self.table.SetCellValue(i, j, str(value))
self.table.AutoSize()
pg = self.scalar
data = self.result.data
if not data:
pg.Show(0)
else:
for label, param in data.iteritems():
pg.Append(wxpg.StringProperty(label,
value = str(param.GetValue())))
def OnExportToCSV(self, event):
if not self.result or not self.result.table:
return
text_file = wx.FileSelector(
_('Save table to CSV'),
default_filename = 'table.csv',
wildcard = 'PNG files (*.csv)|*.csv|Text files (*.txt)|*.txt',
flags = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
if not text_file:
return
tl = self.table.GetSelectionBlockTopLeft() # [(t, l)]
br = self.table.GetSelectionBlockBottomRight() # [(b, r)]
if not tl:
tl = (0, 0)
else:
tl = tl[0]
if not br:
br = (len(self.result.rows), len(self.result.columns))
else:
x, y = br[0]
br = x + 1, y + 1
with open(text_file, 'w') as f:
for i in xrange(tl[0], br[0]):
s = []
for j in xrange(tl[1], br[1]):
s.append(repr(self.result.GetCell(i, j)))
f.write('; '.join(s) + '\n')
#-----------------------------------------------------------------------------
# Форма с выбором наборов значений для построения графика
#-----------------------------------------------------------------------------
class LineSelectDialog(forms.LineSelectDialog):
def __init__(self, parent, title):
forms.LineSelectDialog.__init__(self, parent, title)
def Add(self, title, data):
self.left.Append(title, data)
self.right.Append(title, data)
def SetSelections(self):
"""
Выделяет первую строку в левом столбце колонок
и все, кроме первой, во втором.
Таким образом по умолчанию предлагается построить зависимость
каждого значения от первого. Это логично, поскольку первым
обычно идет независимый параметр.
"""
# выделяем первую строку слева
# (первый столбец результата)
if self.left.GetCount():
self.left.Select(0)
# выделяем все, кроме первой, строки справа
# (второй столбец результата)
for i in xrange(1, self.right.GetCount()):
self.right.Select(i)
def GetLineColumns(self):
"""
Возвращает список пар колонок, которые были выбраны
"""
item = self.left.GetSelection()
x = self.left.GetClientData(item)
items = self.right.GetSelections()
ys = [ self.right.GetClientData(i) for i in items ]
return [ (x, y) for y in ys ]
#-----------------------------------------------------------------------------
# Форма с изображением графика
#-----------------------------------------------------------------------------
class PlotFrame(forms.PlotFrame):
def __init__(self, parent, title, lines):
forms.PlotFrame.__init__(self, parent, title)
self.lines = lines # список объектор LineData
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 = {}
if line.type == LINE_MARKER:
handle = wxplot.PolyMarker
attr['size'] = 1
else:
handle = wxplot.PolyLine
points = line.GetPoints()
attr['colour'] = line.colour or colours[i % len(colours)]
attr['legend'] = line.GetTitle() or 'Unknown line'
plot_lines.append(handle(points, **attr))
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()
#-----------------------------------------------------------------------------
# Приложение
#-----------------------------------------------------------------------------
class ThisApp(wx.App):
def OnInit(self):
# Создание главного окна
frame = MainFrame()
self.SetTopWindow(frame)
frame.Show(True)
return True
#-----------------------------------------------------------------------------
# Запуск приложения
#-----------------------------------------------------------------------------
def main():
app = ThisApp(redirect = False)
app.MainLoop()
if __name__ == "__main__":
main()