mirror of https://gitlab.com/pamhyr/pamhyr2
Refactoring: Use ui file in Geometry profile edit window.
parent
44ccf99d48
commit
11a7486a9c
|
|
@ -56,6 +56,9 @@ class Profile(object):
|
||||||
|
|
||||||
self._profile_type = _type
|
self._profile_type = _type
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._points)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def number_points(self):
|
def number_points(self):
|
||||||
return len(self._points)
|
return len(self._points)
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from tools import timer, trace
|
from tools import timer, trace
|
||||||
from View.Plot.APlot import APlot
|
from View.Tools.PamhyrPlot import PamhyrPlot
|
||||||
from View.Plot.mpl_canvas_onpick_event import OnpickEvent
|
from View.Tools.Plot.OnPickEvent import OnpickEvent
|
||||||
|
|
||||||
from PyQt5.QtCore import (
|
from PyQt5.QtCore import (
|
||||||
QCoreApplication
|
QCoreApplication
|
||||||
|
|
@ -30,12 +30,15 @@ _translate = QCoreApplication.translate
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
|
||||||
class Plot(APlot):
|
class Plot(PamhyrPlot):
|
||||||
def __init__(self, canvas=None, data=None, toolbar=None, table=None):
|
def __init__(self, canvas=None, trad=None, data=None, toolbar=None,
|
||||||
|
table=None, parent=None):
|
||||||
super(Plot, self).__init__(
|
super(Plot, self).__init__(
|
||||||
canvas=canvas,
|
canvas = canvas,
|
||||||
data=data,
|
trad = trad,
|
||||||
toolbar=toolbar
|
data = data,
|
||||||
|
toolbar = toolbar,
|
||||||
|
parent = parent
|
||||||
)
|
)
|
||||||
|
|
||||||
self._table = table
|
self._table = table
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,11 @@ from PyQt5.QtWidgets import (
|
||||||
QMessageBox, QStyledItemDelegate, QLineEdit
|
QMessageBox, QStyledItemDelegate, QLineEdit
|
||||||
)
|
)
|
||||||
from PyQt5.QtCore import (
|
from PyQt5.QtCore import (
|
||||||
QModelIndex, Qt, QAbstractTableModel,
|
QModelIndex, Qt, QVariant, QCoreApplication
|
||||||
QVariant, QCoreApplication
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from View.Tools.PamhyrTable import PamhyrTableModel
|
||||||
|
|
||||||
from Model.Geometry.PointXYZ import PointXYZ
|
from Model.Geometry.PointXYZ import PointXYZ
|
||||||
from Model.Geometry.ProfileXYZ import ProfileXYZ
|
from Model.Geometry.ProfileXYZ import ProfileXYZ
|
||||||
|
|
||||||
|
|
@ -43,43 +44,22 @@ logger = logging.getLogger()
|
||||||
_translate = QCoreApplication.translate
|
_translate = QCoreApplication.translate
|
||||||
|
|
||||||
|
|
||||||
class TableEditableModel(QAbstractTableModel):
|
class GeometryProfileTableModel(PamhyrTableModel):
|
||||||
def __init__(self, profile: ProfileXYZ, header=None, undo=None):
|
|
||||||
QAbstractTableModel.__init__(self)
|
|
||||||
|
|
||||||
self._undo_stack = undo
|
|
||||||
self._profile = profile
|
|
||||||
|
|
||||||
if header is None:
|
|
||||||
self._header = [
|
|
||||||
"X (m)", "Y (m)", "Z (m)",
|
|
||||||
_translate("MainWindowProfile", "Nom"),
|
|
||||||
_translate("MainWindowProfile", "Abs en travers (m)")
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
self._header = header
|
|
||||||
|
|
||||||
def rowCount(self, parent=QModelIndex()):
|
|
||||||
return self._profile.number_points
|
|
||||||
|
|
||||||
def columnCount(self, parent=QModelIndex()):
|
|
||||||
return len(self._header)
|
|
||||||
|
|
||||||
def data(self, index, role=Qt.DisplayRole):
|
def data(self, index, role=Qt.DisplayRole):
|
||||||
if index.isValid():
|
if index.isValid():
|
||||||
if role == Qt.DisplayRole:
|
if role == Qt.DisplayRole:
|
||||||
value = ""
|
value = ""
|
||||||
|
|
||||||
if index.column() == 0:
|
if index.column() == 0:
|
||||||
value = self._profile.point(index.row()).x
|
value = self._data.point(index.row()).x
|
||||||
elif index.column() == 1:
|
elif index.column() == 1:
|
||||||
value = self._profile.point(index.row()).y
|
value = self._data.point(index.row()).y
|
||||||
elif index.column() == 2:
|
elif index.column() == 2:
|
||||||
value = self._profile.point(index.row()).z
|
value = self._data.point(index.row()).z
|
||||||
elif index.column() == 3:
|
elif index.column() == 3:
|
||||||
value = self._profile.point(index.row()).name
|
value = self._data.point(index.row()).name
|
||||||
elif index.column() == 4:
|
elif index.column() == 4:
|
||||||
station = self._profile.get_station()
|
station = self._data.get_station()
|
||||||
if station is None:
|
if station is None:
|
||||||
return "-"
|
return "-"
|
||||||
else:
|
else:
|
||||||
|
|
@ -95,25 +75,25 @@ class TableEditableModel(QAbstractTableModel):
|
||||||
return Qt.AlignHCenter | Qt.AlignVCenter
|
return Qt.AlignHCenter | Qt.AlignVCenter
|
||||||
|
|
||||||
if index.column() == 2:
|
if index.column() == 2:
|
||||||
value = self._profile.point(index.row()).z
|
value = self._data.point(index.row()).z
|
||||||
if role == Qt.ForegroundRole:
|
if role == Qt.ForegroundRole:
|
||||||
if value == self._profile.z_min():
|
if value == self._data.z_min():
|
||||||
return QColor("red")
|
return QColor("red")
|
||||||
elif value == self._profile.z_max():
|
elif value == self._data.z_max():
|
||||||
return QColor("blue")
|
return QColor("blue")
|
||||||
|
|
||||||
if role == Qt.ToolTipRole:
|
if role == Qt.ToolTipRole:
|
||||||
if value == self._profile.z_min():
|
if value == self._data.z_min():
|
||||||
return _translate("MainWindowProfile",
|
return _translate("MainWindowProfile",
|
||||||
"La cote du fond",
|
"La cote du fond",
|
||||||
"Z minimale")
|
"Z minimale")
|
||||||
elif value == self._profile.z_max():
|
elif value == self._data.z_max():
|
||||||
return _translate("MainWindowProfile",
|
return _translate("MainWindowProfile",
|
||||||
"La cote maximale",
|
"La cote maximale",
|
||||||
"Z maximale")
|
"Z maximale")
|
||||||
|
|
||||||
if index.column() == 3:
|
if index.column() == 3:
|
||||||
value = self._profile.point(index.row()).name
|
value = self._data.point(index.row()).name
|
||||||
|
|
||||||
if value.strip().upper() in ["RG", "RD"]:
|
if value.strip().upper() in ["RG", "RD"]:
|
||||||
if role == Qt.FontRole:
|
if role == Qt.FontRole:
|
||||||
|
|
@ -132,21 +112,6 @@ class TableEditableModel(QAbstractTableModel):
|
||||||
|
|
||||||
return QVariant()
|
return QVariant()
|
||||||
|
|
||||||
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
|
||||||
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
|
|
||||||
return self._header[section]
|
|
||||||
elif orientation == Qt.Vertical and role == Qt.DisplayRole:
|
|
||||||
return str(section + 1)
|
|
||||||
|
|
||||||
if role == Qt.ToolTipRole and section == 4:
|
|
||||||
return _translate(
|
|
||||||
"MainWindowProfile",
|
|
||||||
"Abscisse en travers calculée en projétant les points"
|
|
||||||
" \nsur le plan défini par les deux points nommés extrêmes "
|
|
||||||
)
|
|
||||||
|
|
||||||
return QVariant()
|
|
||||||
|
|
||||||
def setData(self, index, value, role=Qt.EditRole):
|
def setData(self, index, value, role=Qt.EditRole):
|
||||||
row = index.row()
|
row = index.row()
|
||||||
column = index.column()
|
column = index.column()
|
||||||
|
|
@ -154,34 +119,34 @@ class TableEditableModel(QAbstractTableModel):
|
||||||
if role == Qt.EditRole:
|
if role == Qt.EditRole:
|
||||||
try:
|
try:
|
||||||
if column == 0:
|
if column == 0:
|
||||||
self._undo_stack.push(
|
self._undo.push(
|
||||||
SetXCommand(
|
SetXCommand(
|
||||||
self._profile, row,
|
self._data, row,
|
||||||
self._profile.point(row).x,
|
self._data.point(row).x,
|
||||||
value
|
value
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif column == 1:
|
elif column == 1:
|
||||||
self._undo_stack.push(
|
self._undo.push(
|
||||||
SetYCommand(
|
SetYCommand(
|
||||||
self._profile, row,
|
self._data, row,
|
||||||
self._profile.point(row).y,
|
self._data.point(row).y,
|
||||||
value
|
value
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif column == 2:
|
elif column == 2:
|
||||||
self._undo_stack.push(
|
self._undo.push(
|
||||||
SetZCommand(
|
SetZCommand(
|
||||||
self._profile, row,
|
self._data, row,
|
||||||
self._profile.point(row).z,
|
self._data.point(row).z,
|
||||||
value
|
value
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif column == 3:
|
elif column == 3:
|
||||||
self._undo_stack.push(
|
self._undo.push(
|
||||||
SetNameCommand(
|
SetNameCommand(
|
||||||
self._profile, row,
|
self._data, row,
|
||||||
self._profile.point(row).name,
|
self._data.point(row).name,
|
||||||
value
|
value
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -213,9 +178,9 @@ class TableEditableModel(QAbstractTableModel):
|
||||||
def insert_row(self, row, parent=QModelIndex()):
|
def insert_row(self, row, parent=QModelIndex()):
|
||||||
self.beginInsertRows(parent, row, row - 1)
|
self.beginInsertRows(parent, row, row - 1)
|
||||||
|
|
||||||
self._undo_stack.push(
|
self._undo.push(
|
||||||
AddCommand(
|
AddCommand(
|
||||||
self._profile, row
|
self._data, row
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -225,9 +190,9 @@ class TableEditableModel(QAbstractTableModel):
|
||||||
def remove_rows(self, rows, parent=QModelIndex()):
|
def remove_rows(self, rows, parent=QModelIndex()):
|
||||||
self.beginRemoveRows(parent, rows[0], rows[-1])
|
self.beginRemoveRows(parent, rows[0], rows[-1])
|
||||||
|
|
||||||
self._undo_stack.push(
|
self._undo.push(
|
||||||
DelCommand(
|
DelCommand(
|
||||||
self._profile, rows
|
self._data, rows
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -239,15 +204,15 @@ class TableEditableModel(QAbstractTableModel):
|
||||||
|
|
||||||
reverse = (order != Qt.AscendingOrder)
|
reverse = (order != Qt.AscendingOrder)
|
||||||
|
|
||||||
self._undo_stack.push(
|
self._undo.push(
|
||||||
SortCommand(
|
SortCommand(
|
||||||
self._profile, column, reverse
|
self._data, column, reverse
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.layoutChanged.emit()
|
self.layoutChanged.emit()
|
||||||
|
|
||||||
def move_row_up(self, row, parent=QModelIndex()):
|
def move_up(self, row, parent=QModelIndex()):
|
||||||
if row <= 0:
|
if row <= 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -255,26 +220,26 @@ class TableEditableModel(QAbstractTableModel):
|
||||||
|
|
||||||
self.beginMoveRows(parent, row - 1, row - 1, parent, target)
|
self.beginMoveRows(parent, row - 1, row - 1, parent, target)
|
||||||
|
|
||||||
self._undo_stack.push(
|
self._undo.push(
|
||||||
MoveCommand(
|
MoveCommand(
|
||||||
self._profile, "up", row
|
self._data, "up", row
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.endMoveRows()
|
self.endMoveRows()
|
||||||
self.layoutChanged.emit()
|
self.layoutChanged.emit()
|
||||||
|
|
||||||
def move_row_down(self, row_to_move, parent=QModelIndex()):
|
def move_down(self, row_to_move, parent=QModelIndex()):
|
||||||
if row > self._profile.number_points:
|
if row > self._data.number_points:
|
||||||
return
|
return
|
||||||
|
|
||||||
target = row
|
target = row
|
||||||
|
|
||||||
self.beginMoveRows(parent, row + 1, row + 1, parent, target)
|
self.beginMoveRows(parent, row + 1, row + 1, parent, target)
|
||||||
|
|
||||||
self._undo_stack.push(
|
self._undo.push(
|
||||||
MoveCommand(
|
MoveCommand(
|
||||||
self._profile, "down", row
|
self._data, "down", row
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -282,7 +247,7 @@ class TableEditableModel(QAbstractTableModel):
|
||||||
self.layoutChanged.emit()
|
self.layoutChanged.emit()
|
||||||
|
|
||||||
def paste(self, row, header, data):
|
def paste(self, row, header, data):
|
||||||
if row > self._profile.number_points:
|
if row > self._data.number_points:
|
||||||
return
|
return
|
||||||
|
|
||||||
if len(data) == 0:
|
if len(data) == 0:
|
||||||
|
|
@ -290,12 +255,12 @@ class TableEditableModel(QAbstractTableModel):
|
||||||
|
|
||||||
self.layoutAboutToBeChanged.emit()
|
self.layoutAboutToBeChanged.emit()
|
||||||
|
|
||||||
self._undo_stack.push(
|
self._undo.push(
|
||||||
PasteCommand(
|
PasteCommand(
|
||||||
self._profile, row,
|
self._data, row,
|
||||||
list(
|
list(
|
||||||
map(
|
map(
|
||||||
lambda d: self._profile.point_from_data(header, d),
|
lambda d: self._data.point_from_data(header, d),
|
||||||
data
|
data
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -306,32 +271,9 @@ class TableEditableModel(QAbstractTableModel):
|
||||||
self.layoutChanged.emit()
|
self.layoutChanged.emit()
|
||||||
|
|
||||||
def undo(self):
|
def undo(self):
|
||||||
self._undo_stack.undo()
|
self._undo.undo()
|
||||||
self.layoutChanged.emit()
|
self.layoutChanged.emit()
|
||||||
|
|
||||||
def redo(self):
|
def redo(self):
|
||||||
self._undo_stack.redo()
|
self._undo.redo()
|
||||||
self.layoutChanged.emit()
|
self.layoutChanged.emit()
|
||||||
|
|
||||||
|
|
||||||
class Delegate(QStyledItemDelegate):
|
|
||||||
def __init__(self, parent=None, setModelDataEvent=None):
|
|
||||||
super(Delegate, self).__init__(parent)
|
|
||||||
self.setModelDataEvent = setModelDataEvent
|
|
||||||
|
|
||||||
def createEditor(self, parent, option, index):
|
|
||||||
index.model().data(index, Qt.DisplayRole)
|
|
||||||
return QLineEdit(parent)
|
|
||||||
|
|
||||||
def setEditorData(self, editor, index):
|
|
||||||
value = index.model().data(index, Qt.DisplayRole)
|
|
||||||
editor.setText(str(value))
|
|
||||||
|
|
||||||
def setModelData(self, editor, model, index):
|
|
||||||
model.setData(index, editor.text())
|
|
||||||
|
|
||||||
if not self.setModelDataEvent is None:
|
|
||||||
self.setModelDataEvent()
|
|
||||||
|
|
||||||
def updateEditorGeometry(self, editor, option, index):
|
|
||||||
editor.setGeometry(option.rect)
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Translate.py -- Pamhyr
|
||||||
|
# Copyright (C) 2023 INRAE
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QCoreApplication
|
||||||
|
|
||||||
|
from View.Tools.PamhyrTranslate import PamhyrTranslate
|
||||||
|
from View.Geometry.Translate import GeometryTranslate
|
||||||
|
|
||||||
|
_translate = QCoreApplication.translate
|
||||||
|
|
||||||
|
class GeometryProfileTranslate(GeometryTranslate):
|
||||||
|
def __init__(self):
|
||||||
|
super(GeometryProfileTranslate, self).__init__()
|
||||||
|
|
||||||
|
self._sub_dict["table_headers"] = {
|
||||||
|
"x": _translate("Geometry", "X (m)"),
|
||||||
|
"y": _translate("Geometry", "Y (m)"),
|
||||||
|
"z": _translate("Geometry", "Z (m)"),
|
||||||
|
"name": _translate("Geometry", "Name"),
|
||||||
|
"abs": _translate("Geometry", "Traversal abs (m)"),
|
||||||
|
}
|
||||||
|
|
@ -31,206 +31,184 @@ from PyQt5.QtCore import (
|
||||||
)
|
)
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QApplication, QMainWindow, QFileDialog, QCheckBox,
|
QApplication, QMainWindow, QFileDialog, QCheckBox,
|
||||||
QUndoStack, QShortcut,
|
QUndoStack, QShortcut, QTableView, QAbstractItemView,
|
||||||
|
QHeaderView, QVBoxLayout, QAction,
|
||||||
)
|
)
|
||||||
|
|
||||||
from Model.Geometry.Reach import Reach
|
from Model.Geometry.Reach import Reach
|
||||||
from Model.Geometry.ProfileXYZ import ProfileXYZ
|
from Model.Geometry.ProfileXYZ import ProfileXYZ
|
||||||
|
|
||||||
from View.Tools.ASubWindow import ASubMainWindow
|
from View.Tools.PamhyrWindow import PamhyrWindow
|
||||||
|
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
|
||||||
|
from View.Tools.Plot.PamhyrCanvas import MplCanvas
|
||||||
|
|
||||||
from View.Geometry.Profile.mainwindow_ui_profile import Ui_MainWindow
|
|
||||||
from View.Geometry.Profile.Plot import Plot
|
from View.Geometry.Profile.Plot import Plot
|
||||||
from View.Geometry.Profile.Table import *
|
from View.Geometry.Profile.Table import GeometryProfileTableModel
|
||||||
|
from View.Geometry.Profile.Translate import GeometryProfileTranslate
|
||||||
|
|
||||||
_translate = QCoreApplication.translate
|
_translate = QCoreApplication.translate
|
||||||
|
|
||||||
|
|
||||||
class ProfileWindow(ASubMainWindow):
|
class ProfileWindow(PamhyrWindow):
|
||||||
def __init__(self, profile=None, title="Profile", parent=None):
|
_pamhyr_ui = "GeometryCrossSection"
|
||||||
self._title = title
|
_pamhyr_name = "Geometry cross-section"
|
||||||
self.parent = parent
|
|
||||||
super(ProfileWindow, self).__init__(
|
|
||||||
name=self._title,
|
|
||||||
parent=self.parent
|
|
||||||
)
|
|
||||||
|
|
||||||
self.ui = Ui_MainWindow()
|
|
||||||
self.ui.setupUi(self)
|
|
||||||
|
|
||||||
|
def __init__(self, profile=None, study=None, config=None, parent=None):
|
||||||
self._profile = profile
|
self._profile = profile
|
||||||
self._model = None
|
|
||||||
|
|
||||||
self.setup_window()
|
name = f"{self._pamhyr_name} - {self._profile.name} {self._profile.kp}"
|
||||||
self.setup_sc()
|
super(ProfileWindow, self).__init__(
|
||||||
self.setup_model()
|
title = name,
|
||||||
self.setup_connections()
|
study = study,
|
||||||
self.plot()
|
config = config,
|
||||||
|
trad = GeometryProfileTranslate(),
|
||||||
self._model.dataChanged.connect(self.update_plot)
|
parent = parent
|
||||||
self.fileName = None
|
|
||||||
|
|
||||||
# self.ui.tableView.installEventFilter(self)
|
|
||||||
# self._model.dataChanged.connect(self.tableview_is_modified)
|
|
||||||
|
|
||||||
# self.ui.btn_go_back.setEnabled(False)
|
|
||||||
# self.ui.btn_check.setEnabled(False)
|
|
||||||
# self._model.dataChanged.connect(self.set_enable_cancel_btn)
|
|
||||||
# self._model.dataChanged.connect(self.set_enable_validate_changes_btn)
|
|
||||||
# self.ui.btn_reset.setEnabled(False)
|
|
||||||
# self._model.dataChanged.connect(self.set_enable_go_back_initial_state_btn)
|
|
||||||
|
|
||||||
def setup_window(self):
|
|
||||||
header = _translate("MainWindowProfile", "Profile")
|
|
||||||
|
|
||||||
name = self._profile.name
|
|
||||||
if (name is None) or (name == ""):
|
|
||||||
name = _translate("MainWindowProfile", "(no name)")
|
|
||||||
|
|
||||||
self._title = (
|
|
||||||
header + " - " +
|
|
||||||
f"{self._profile.reach.name}" + " - " +
|
|
||||||
f"{name} ({self._profile.kp})"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.setWindowTitle(self._title)
|
self.setup_table()
|
||||||
|
self.setup_plot()
|
||||||
|
self.setup_connections()
|
||||||
|
|
||||||
def setup_sc(self):
|
def setup_table(self):
|
||||||
self._undo_stack = QUndoStack()
|
table_headers = self._trad.get_dict("table_headers")
|
||||||
|
|
||||||
self.undo_sc = QShortcut(QKeySequence.Undo, self)
|
table = self.find(QTableView, "tableView")
|
||||||
self.redo_sc = QShortcut(QKeySequence.Redo, self)
|
self._tablemodel = GeometryProfileTableModel(
|
||||||
self.copy_sc = QShortcut(QKeySequence.Copy, self)
|
table_view = table,
|
||||||
self.paste_sc = QShortcut(QKeySequence.Paste, self)
|
table_headers = table_headers,
|
||||||
|
editable_headers = ["name", "x", "y", "z"],
|
||||||
def setup_model(self):
|
data = self._profile,
|
||||||
self._model = TableEditableModel(
|
|
||||||
profile = self._profile,
|
|
||||||
undo = self._undo_stack
|
undo = self._undo_stack
|
||||||
)
|
)
|
||||||
|
table.setModel(self._tablemodel)
|
||||||
|
table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||||
|
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||||
|
table.setAlternatingRowColors(True)
|
||||||
|
|
||||||
self.ui.tableView.setModel(self._model)
|
def setup_plot(self):
|
||||||
self.ui.tableView.setItemDelegate(Delegate())
|
self._tablemodel.blockSignals(True)
|
||||||
|
|
||||||
def setup_connections(self):
|
self._canvas = MplCanvas(width=3, height=4, dpi=100)
|
||||||
self.ui.btn_sort_asc_x.triggered.connect(self.sort_X_ascending)
|
self._canvas.setObjectName("canvas")
|
||||||
self.ui.btn_sort_desc_x.triggered.connect(self.sort_X_descending)
|
self._toolbar = PamhyrPlotToolbar(
|
||||||
self.ui.btn_sort_asc_y.triggered.connect(self.sort_Y_ascending)
|
self._canvas, self,
|
||||||
self.ui.btn_sort_desc_y.triggered.connect(self.sort_Y_descending)
|
items = ["home", "zoom", "save", "iso", "back/forward", "move"]
|
||||||
self.ui.btn_move_up.triggered.connect(self.move_row_up)
|
)
|
||||||
self.ui.btn_move_down.triggered.connect(self.move_row_down)
|
self._plot_layout = self.find(QVBoxLayout, "verticalLayout")
|
||||||
self.ui.btn_export.triggered.connect(self.handleSave)
|
self._plot_layout.addWidget(self._toolbar)
|
||||||
self.ui.btn_add.triggered.connect(self.insert_row)
|
self._plot_layout.addWidget(self._canvas)
|
||||||
self.ui.btn_delete.triggered.connect(self.delete_row)
|
|
||||||
# self.ui.btn_copy.clicked.connect(self.copyTable)
|
|
||||||
# self.ui.btn_paste.clicked.connect(self.pasteTable)
|
|
||||||
# self.ui.btn_check.clicked.connect(self.validate_changes)
|
|
||||||
# self.ui.btn_go_back.clicked.connect(self.cancel_validate_changes)
|
|
||||||
# self.ui.btn_reset.clicked.connect(self.go_back_to_initial_state)
|
|
||||||
|
|
||||||
self.undo_sc.activated.connect(self.undo)
|
|
||||||
self.redo_sc.activated.connect(self.redo)
|
|
||||||
self.copy_sc.activated.connect(self.copy)
|
|
||||||
self.paste_sc.activated.connect(self.paste)
|
|
||||||
|
|
||||||
def plot(self):
|
|
||||||
self.ui.tableView.model().blockSignals(True)
|
|
||||||
|
|
||||||
self._plot = Plot(
|
self._plot = Plot(
|
||||||
canvas = self.ui.canvas,
|
canvas = self._canvas,
|
||||||
data = self._profile,
|
data = self._profile,
|
||||||
toolbar = None,
|
toolbar = self._toolbar,
|
||||||
table = self.ui.tableView,
|
table = self.find(QTableView, "tableView")
|
||||||
)
|
)
|
||||||
self._plot.draw()
|
self._plot.draw()
|
||||||
|
|
||||||
self.ui.tableView.model().blockSignals(False)
|
self._tablemodel.blockSignals(False)
|
||||||
|
|
||||||
|
def setup_connections(self):
|
||||||
|
actions = {
|
||||||
|
"action_sort_asc": self.sort_X_ascending,
|
||||||
|
"action_sort_des": self.sort_X_descending,
|
||||||
|
"action_up": self.move_up,
|
||||||
|
"action_down": self.move_down,
|
||||||
|
"action_add": self.add,
|
||||||
|
"action_delete": self.delete,
|
||||||
|
}
|
||||||
|
|
||||||
|
for action in actions:
|
||||||
|
self.find(QAction, action)\
|
||||||
|
.triggered.connect(actions[action])
|
||||||
|
|
||||||
|
self._tablemodel.dataChanged.connect(self.update_plot)
|
||||||
|
|
||||||
def update_plot(self):
|
def update_plot(self):
|
||||||
self.ui.tableView.model().blockSignals(True)
|
self._tablemodel.blockSignals(True)
|
||||||
|
|
||||||
# TODO: Do not rebuild all graph
|
# TODO: Do not rebuild all graph
|
||||||
self._plot.update()
|
self._plot.update()
|
||||||
|
|
||||||
self.ui.tableView.model().blockSignals(False)
|
self._tablemodel.blockSignals(False)
|
||||||
|
|
||||||
def index_selected_row(self):
|
def index_selected_row(self):
|
||||||
rows = self.ui.tableView\
|
rows = self._tablemodel\
|
||||||
.selectionModel()\
|
.selectionModel()\
|
||||||
.selectedRows()
|
.selectedRows()
|
||||||
if len(rows) == 0:
|
if len(rows) == 0:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
return self.ui.tableView\
|
return self._tablemodel\
|
||||||
.selectionModel()\
|
.selectionModel()\
|
||||||
.selectedRows()[0]\
|
.selectedRows()[0]\
|
||||||
.row()
|
.row()
|
||||||
|
|
||||||
def insert_row(self):
|
def add(self):
|
||||||
if len(self.ui.tableView.selectedIndexes()) == 0:
|
if len(self._tablemodel.selectedIndexes()) == 0:
|
||||||
self._model.insert_row(self._model.rowCount())
|
self._tablemodel.insert_row(self._tablemodel.rowCount())
|
||||||
else:
|
else:
|
||||||
row = self.index_selected_row()
|
row = self.index_selected_row()
|
||||||
self._model.insert_row(row + 1)
|
self._tablemodel.insert_row(row + 1)
|
||||||
self.update_plot()
|
self.update_plot()
|
||||||
|
|
||||||
def delete_row(self):
|
def delete(self):
|
||||||
rows = sorted(
|
rows = sorted(
|
||||||
list(
|
list(
|
||||||
set(
|
set(
|
||||||
[index.row() for index in self.ui.tableView.selectedIndexes()]
|
[index.row() for index in self._tablemodel.selectedIndexes()]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(rows) > 0:
|
if len(rows) > 0:
|
||||||
self._model.remove_rows(rows)
|
self._tablemodel.remove_rows(rows)
|
||||||
self.update_plot()
|
self.update_plot()
|
||||||
|
|
||||||
def sort_X_ascending(self):
|
def sort_X_ascending(self):
|
||||||
self._model.sort('x', order=Qt.AscendingOrder)
|
self._tablemodel.sort('x', order=Qt.AscendingOrder)
|
||||||
self.update_plot()
|
self.update_plot()
|
||||||
|
|
||||||
def sort_X_descending(self):
|
def sort_X_descending(self):
|
||||||
self._model.sort('x', order=Qt.DescendingOrder)
|
self._tablemodel.sort('x', order=Qt.DescendingOrder)
|
||||||
self.update_plot()
|
self.update_plot()
|
||||||
|
|
||||||
def sort_Y_ascending(self):
|
def sort_Y_ascending(self):
|
||||||
self._model.sort('y', order=Qt.AscendingOrder)
|
self._tablemodel.sort('y', order=Qt.AscendingOrder)
|
||||||
self.update_plot()
|
self.update_plot()
|
||||||
|
|
||||||
def sort_Y_descending(self):
|
def sort_Y_descending(self):
|
||||||
self._model.sort('y', order=Qt.DescendingOrder)
|
self._tablemodel.sort('y', order=Qt.DescendingOrder)
|
||||||
self.update_plot()
|
self.update_plot()
|
||||||
|
|
||||||
def move_row_down(self):
|
def move_down(self):
|
||||||
rows = list(
|
rows = list(
|
||||||
set(
|
set(
|
||||||
[index.row() for index in self.ui.tableView.selectedIndexes()]
|
[index.row() for index in self._tablemodel.selectedIndexes()]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if row < self._model.rowCount() - 1:
|
if row < self._tablemodel.rowCount() - 1:
|
||||||
self._model.move_row_down(row)
|
self._tablemodel.move_down(row)
|
||||||
|
|
||||||
self.update_plot()
|
self.update_plot()
|
||||||
|
|
||||||
def move_row_up(self):
|
def move_up(self):
|
||||||
rows = list(
|
rows = list(
|
||||||
set(
|
set(
|
||||||
[index.row() for index in self.ui.tableView.selectedIndexes()]
|
[index.row() for index in self._tablemodel.selectedIndexes()]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if 0 < row:
|
if 0 < row:
|
||||||
self._model.move_row_up(row)
|
self._tablemodel.move_up(row)
|
||||||
|
|
||||||
self.update_plot()
|
self.update_plot()
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
rows = self.ui.tableView\
|
rows = self._tablemodel\
|
||||||
.selectionModel()\
|
.selectionModel()\
|
||||||
.selectedRows()
|
.selectedRows()
|
||||||
table = []
|
table = []
|
||||||
|
|
@ -258,136 +236,13 @@ class ProfileWindow(ASubMainWindow):
|
||||||
row.append(self._profile)
|
row.append(self._profile)
|
||||||
|
|
||||||
row = self.index_selected_row()
|
row = self.index_selected_row()
|
||||||
self._model.paste(row, header, data)
|
self._tablemodel.paste(row, header, data)
|
||||||
self.update_plot()
|
self.update_plot()
|
||||||
|
|
||||||
def undo(self):
|
def undo(self):
|
||||||
self._model.undo()
|
self._tablemodel.undo()
|
||||||
self.update_plot()
|
self.update_plot()
|
||||||
|
|
||||||
def redo(self):
|
def redo(self):
|
||||||
self._model.redo()
|
self._tablemodel.redo()
|
||||||
self.update_plot()
|
self.update_plot()
|
||||||
|
|
||||||
def handleSave(self):
|
|
||||||
if self.fileName is None or self.fileName == '':
|
|
||||||
self.fileName, self.filters = QFileDialog.getSaveFileName(
|
|
||||||
self, filter="CSV files (*.csv)"
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.fileName != '':
|
|
||||||
with open(self.fileName, 'w') as stream:
|
|
||||||
csvout = csv.writer(stream, delimiter='\t', quotechar=' ',
|
|
||||||
escapechar=None,
|
|
||||||
quoting=csv.QUOTE_NONNUMERIC,
|
|
||||||
lineterminator='\n')
|
|
||||||
|
|
||||||
for row in range(self._model.rowCount(QModelIndex())):
|
|
||||||
rowdata = []
|
|
||||||
for column in range(self._model.columnCount(QModelIndex())):
|
|
||||||
item = self._model.index(
|
|
||||||
row, column, QModelIndex()
|
|
||||||
).data(Qt.DisplayRole)
|
|
||||||
|
|
||||||
if item is not None:
|
|
||||||
rowdata.append(item)
|
|
||||||
|
|
||||||
if item == 'nan':
|
|
||||||
rowdata.remove(item)
|
|
||||||
|
|
||||||
csvout.writerow(rowdata)
|
|
||||||
|
|
||||||
def handleOpen(self):
|
|
||||||
self.fileName, self.filterName = QFileDialog.getOpenFileName(self)
|
|
||||||
|
|
||||||
if self.fileName != '':
|
|
||||||
with open(self.fileName, 'r') as f:
|
|
||||||
reader = csv.reader(f, delimiter='\t')
|
|
||||||
header = next(reader)
|
|
||||||
|
|
||||||
buf = []
|
|
||||||
for row in reader:
|
|
||||||
row[0] = QCheckBox("-")
|
|
||||||
buf.append(row)
|
|
||||||
|
|
||||||
self._model = None
|
|
||||||
self._model = TableEditableModel(buf)
|
|
||||||
self.ui.tableView.setModel(self._model)
|
|
||||||
self.fileName = ''
|
|
||||||
|
|
||||||
def set_enable_validate_changes_btn(self):
|
|
||||||
self.ui.btn_check.setEnabled(True)
|
|
||||||
|
|
||||||
def set_enable_cancel_btn(self):
|
|
||||||
self.ui.btn_go_back.setEnabled(True)
|
|
||||||
|
|
||||||
def set_enable_go_back_initial_state_btn(self):
|
|
||||||
self.ui.btn_reset.setEnabled(True)
|
|
||||||
|
|
||||||
def delete_empty_rows(self):
|
|
||||||
if self._model.data_contains_nan():
|
|
||||||
buttonReply = QtWidgets.QMessageBox.question(
|
|
||||||
self,
|
|
||||||
_translate("MainWindowProfile",
|
|
||||||
"Suppression les lignes incomplètes"),
|
|
||||||
_translate("MainWindowProfile",
|
|
||||||
"Supprimer les lignes des cellules"
|
|
||||||
" non renseignées ?"),
|
|
||||||
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No
|
|
||||||
)
|
|
||||||
|
|
||||||
if buttonReply == QtWidgets.QMessageBox.Yes:
|
|
||||||
self._model.delete_empty_rows()
|
|
||||||
|
|
||||||
if buttonReply == QtWidgets.QMessageBox.No:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def remove_duplicates_point_names(self):
|
|
||||||
counter_list = []
|
|
||||||
list_deleted_names = []
|
|
||||||
|
|
||||||
for ind, name_point in enumerate(self._model.name):
|
|
||||||
if name_point not in counter_list:
|
|
||||||
counter_list.append(name_point)
|
|
||||||
elif len(name_point.strip()) > 0 and name_point in counter_list:
|
|
||||||
if name_point not in list_deleted_names:
|
|
||||||
list_deleted_names.append(name_point)
|
|
||||||
|
|
||||||
if list_deleted_names:
|
|
||||||
self.msg_box_check_duplication_names(list_deleted_names)
|
|
||||||
|
|
||||||
def msg_box_check_duplication_names(self, list_deleted_names):
|
|
||||||
if len(list_deleted_names) == 1:
|
|
||||||
text = _translate("MainWindowProfile",
|
|
||||||
"Le nom ''{}'' est dupliqué."
|
|
||||||
" \n\nYes : Ne garder que la première occurrence. \nNo :"
|
|
||||||
" Annuler la suppression.".format(list_deleted_names[0]))
|
|
||||||
else:
|
|
||||||
text = _translate("MainWindowProfile",
|
|
||||||
"Les noms suivants : \n{} sont dupliqués"
|
|
||||||
" \n\nYes : Ne garder que la première occurrence de "
|
|
||||||
"chaque nom. \nNo :"
|
|
||||||
" Annuler la suppression.".format(list_deleted_names))
|
|
||||||
|
|
||||||
buttonReply = QtWidgets.QMessageBox.question(
|
|
||||||
self, _translate("MainWindowProfile",
|
|
||||||
"Suppression des noms répétés"),
|
|
||||||
text,
|
|
||||||
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No
|
|
||||||
)
|
|
||||||
|
|
||||||
if buttonReply == QtWidgets.QMessageBox.Yes:
|
|
||||||
self._model.remove_duplicates_names()
|
|
||||||
|
|
||||||
def ask_quit(self):
|
|
||||||
choice = QtWidgets.QMessageBox.question(
|
|
||||||
self,
|
|
||||||
_translate("MainWindowProfile", "Quittez ?"),
|
|
||||||
_translate("MainWindowProfile",
|
|
||||||
"Etes-vous sûr de vouloir quitter ?"),
|
|
||||||
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No
|
|
||||||
)
|
|
||||||
return choice == QtWidgets.QMessageBox.Yes
|
|
||||||
|
|
||||||
def initial_state_model(self, profile_initial_state):
|
|
||||||
return profile_initial_state
|
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ logger = logging.getLogger()
|
||||||
_translate = QCoreApplication.translate
|
_translate = QCoreApplication.translate
|
||||||
|
|
||||||
|
|
||||||
class TableEditableModel(PamhyrTableModel):
|
class GeometryReachTableModel(PamhyrTableModel):
|
||||||
def data(self, index, role=Qt.DisplayRole):
|
def data(self, index, role=Qt.DisplayRole):
|
||||||
if not index.isValid():
|
if not index.isValid():
|
||||||
return QVariant()
|
return QVariant()
|
||||||
|
|
@ -223,62 +223,3 @@ class TableEditableModel(PamhyrTableModel):
|
||||||
|
|
||||||
self.layoutAboutToBeChanged.emit()
|
self.layoutAboutToBeChanged.emit()
|
||||||
self.layoutChanged.emit()
|
self.layoutChanged.emit()
|
||||||
|
|
||||||
# TODO: Delete useless delegate
|
|
||||||
class Delegate(QStyledItemDelegate):
|
|
||||||
def __init__(self, parent=None, setModelDataEvent=None):
|
|
||||||
super(Delegate, self).__init__(parent)
|
|
||||||
self.setModelDataEvent = setModelDataEvent
|
|
||||||
|
|
||||||
def createEditor(self, parent, option, index):
|
|
||||||
index.model().data(index, Qt.DisplayRole)
|
|
||||||
return QLineEdit(parent)
|
|
||||||
|
|
||||||
def setEditorData(self, editor, index):
|
|
||||||
value = index.model().data(index, Qt.DisplayRole)
|
|
||||||
editor.setText(str(value))
|
|
||||||
|
|
||||||
def setModelData(self, editor, model, index):
|
|
||||||
model.setData(index, editor.text())
|
|
||||||
|
|
||||||
if not self.setModelDataEvent is None:
|
|
||||||
self.setModelDataEvent()
|
|
||||||
|
|
||||||
def updateEditorGeometry(self, editor, option, index):
|
|
||||||
editor.setGeometry(option.rect)
|
|
||||||
|
|
||||||
class Delegate1(QStyledItemDelegate):
|
|
||||||
def __init__(self, owner, choices):
|
|
||||||
super().__init__(owner)
|
|
||||||
self.items = choices
|
|
||||||
|
|
||||||
def paint(self, painter, option, index):
|
|
||||||
if isinstance(self.parent(), QAbstractItemView):
|
|
||||||
self.parent().openPersistentEditor(index)
|
|
||||||
super(Delegate1, self).paint(painter, option, index)
|
|
||||||
|
|
||||||
def createEditor(self, parent, option, index):
|
|
||||||
editor = QComboBox(parent)
|
|
||||||
# editor.currentIndexChanged.connect(self.commit_editor)
|
|
||||||
editor.addItems(self.items)
|
|
||||||
return editor
|
|
||||||
|
|
||||||
def setEditorData(self, editor, index):
|
|
||||||
editor.blockSignals(True)
|
|
||||||
text = index.model().data(index, Qt.DisplayRole)
|
|
||||||
try:
|
|
||||||
i = self.items.index(text)
|
|
||||||
except ValueError:
|
|
||||||
i = 0
|
|
||||||
editor.setCurrentIndex(i)
|
|
||||||
editor.blockSignals(False)
|
|
||||||
|
|
||||||
def setModelData(self, editor, model, index):
|
|
||||||
model.setData(index, editor.currentText(), Qt.DisplayRole)
|
|
||||||
|
|
||||||
def updateEditorGeometry(self, editor, option, index):
|
|
||||||
editor.setGeometry(option.rect)
|
|
||||||
|
|
||||||
@pyqtSlot()
|
|
||||||
def currentIndexChanged(self):
|
|
||||||
self.commitData.emit(self.sender())
|
|
||||||
|
|
|
||||||
|
|
@ -39,17 +39,16 @@ from PyQt5.QtWidgets import (
|
||||||
QLabel,
|
QLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
from View.Geometry.PlotXY import PlotXY
|
|
||||||
from View.Geometry.PlotKPZ import PlotKPZ
|
|
||||||
from View.Geometry.PlotAC import PlotAC
|
|
||||||
|
|
||||||
from View.Tools.PamhyrWindow import PamhyrWindow
|
from View.Tools.PamhyrWindow import PamhyrWindow
|
||||||
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
|
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
|
||||||
from View.Tools.Plot.PamhyrCanvas import MplCanvas
|
from View.Tools.Plot.PamhyrCanvas import MplCanvas
|
||||||
|
|
||||||
from View.Geometry.Table import *
|
from View.Geometry.Table import GeometryReachTableModel
|
||||||
|
from View.Geometry.PlotXY import PlotXY
|
||||||
|
from View.Geometry.PlotAC import PlotAC
|
||||||
|
from View.Geometry.PlotKPZ import PlotKPZ
|
||||||
from View.Geometry.Translate import GeometryTranslate
|
from View.Geometry.Translate import GeometryTranslate
|
||||||
# from View.Geometry.Profile.Window import ProfileWindow
|
from View.Geometry.Profile.Window import ProfileWindow
|
||||||
|
|
||||||
_translate = QCoreApplication.translate
|
_translate = QCoreApplication.translate
|
||||||
|
|
||||||
|
|
@ -87,7 +86,7 @@ class GeometryWindow(PamhyrWindow):
|
||||||
table_headers = self._trad.get_dict("table_headers")
|
table_headers = self._trad.get_dict("table_headers")
|
||||||
|
|
||||||
table = self.find(QTableView, "tableView")
|
table = self.find(QTableView, "tableView")
|
||||||
self._tablemodel = TableEditableModel(
|
self._tablemodel = GeometryReachTableModel(
|
||||||
table_view = table,
|
table_view = table,
|
||||||
table_headers = table_headers,
|
table_headers = table_headers,
|
||||||
editable_headers = ["name", "kp"],
|
editable_headers = ["name", "kp"],
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,8 @@
|
||||||
</attribute>
|
</attribute>
|
||||||
<addaction name="action_add"/>
|
<addaction name="action_add"/>
|
||||||
<addaction name="action_delete"/>
|
<addaction name="action_delete"/>
|
||||||
<addaction name="action_sort"/>
|
<addaction name="action_sort_asc"/>
|
||||||
<addaction name="action_resort"/>
|
<addaction name="action_sort_des"/>
|
||||||
<addaction name="action_up"/>
|
<addaction name="action_up"/>
|
||||||
<addaction name="action_down"/>
|
<addaction name="action_down"/>
|
||||||
</widget>
|
</widget>
|
||||||
|
|
@ -104,25 +104,25 @@
|
||||||
<string>Mode down selected point(s)</string>
|
<string>Mode down selected point(s)</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="action_sort">
|
<action name="action_sort_asc">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset>
|
||||||
<normaloff>ressources/gtk-sort-ascending.png</normaloff>ressources/gtk-sort-ascending.png</iconset>
|
<normaloff>ressources/gtk-sort-ascending.png</normaloff>ressources/gtk-sort-ascending.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>sort</string>
|
<string>sort_asc</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Sort points by nearest neighbor</string>
|
<string>Sort points by nearest neighbor</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="action_resort">
|
<action name="action_sort_des">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset>
|
||||||
<normaloff>ressources/gtk-sort-descending.png</normaloff>ressources/gtk-sort-descending.png</iconset>
|
<normaloff>ressources/gtk-sort-descending.png</normaloff>ressources/gtk-sort-descending.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>resort</string>
|
<string>sort_des</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Sort reversed points by nearest neighbor</string>
|
<string>Sort reversed points by nearest neighbor</string>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue