Merge branch 'master' into qt6_cmake_joshua

This commit is contained in:
joshua
2026-07-31 21:15:21 +02:00
parent f16cf7dac8
commit e6d29cf345
297 changed files with 82691 additions and 27463 deletions
@@ -143,8 +143,12 @@ ElementsLocation ECHSFileToFile::copyElement(ElementsLocation &source, ElementsL
//On windows when user drag and drop an element from the common elements collection
//to the custom elements collection, the element file stay in read only mode, and so
//user can't save the element
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
QNtfsPermissionCheckGuard ntfs_guard;
#else
extern Q_CORE_EXPORT int qt_ntfs_permission_lookup;
qt_ntfs_permission_lookup++;
#endif
QFile file(destination.fileSystemPath() % "/" % new_elmt_name);
if (!file.isWritable()) {
if (!file.setPermissions(file.permissions() | QFileDevice::WriteUser)) {
@@ -152,7 +156,9 @@ ElementsLocation ECHSFileToFile::copyElement(ElementsLocation &source, ElementsL
<< " in ECHSFileToFile::copyElement";
}
}
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
qt_ntfs_permission_lookup--;
#endif
#endif
return ElementsLocation (destination.fileSystemPath() % "/" % new_elmt_name);
}
@@ -469,7 +475,7 @@ bool ElementCollectionHandler::setNames(ElementsLocation &location,
root.appendChild(name_list.toXml(document));
QString filepath = location.fileSystemPath()
+ "/qet_directory";
% "/qet_directory";
if (!QET::writeXmlFile(document, filepath)) {
qDebug() << "ElementCollectionHandler::setNames : write qet-directory file failed";
return false;
@@ -533,6 +533,16 @@ QList<QETProject *> ElementsCollectionModel::project() const
*/
void ElementsCollectionModel::highlightUnusedElement()
{
//Reset only the items currently highlighted in red, so elements that
//are no longer unused lose the highlight. Scoping to the red
//Dense4Pattern avoids touching other backgrounds (e.g. the amber
//"show this dir" highlight) and avoids needless updates on big
//collections (issue #159).
for (ElementCollectionItem *eci : items())
if (eci->background().style() == Qt::Dense4Pattern &&
eci->background().color() == Qt::red)
eci->setBackground(QBrush());
QList <ElementsLocation> unused;
foreach (QETProject *project, m_project_list)
@@ -19,6 +19,7 @@
#include "../editor/ui/qetelementeditor.h"
#include "../elementscategoryeditor.h"
#include "../import/edz/edzimporter.h"
#include "../newelementwizard.h"
#include "../qetapp.h"
#include "../qetdiagrameditor.h"
@@ -32,8 +33,14 @@
#include "fileelementcollectionitem.h"
#include "xmlprojectelementcollectionitem.h"
#include <QCheckBox>
#include <QDesktopServices>
#include <QDialog>
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QLabel>
#include <QMenu>
#include <QPushButton>
#include <QTimer>
#include <QUrl>
#include <QVBoxLayout>
@@ -158,6 +165,8 @@ void ElementsCollectionWidget::setUpAction()
tr("Nouveau dossier"), this);
m_new_element = new QAction(QET::Icons::ElementNew,
tr("Nouvel élément"), this);
m_import_edz = new QAction(QET::Icons::ElementNew,
tr("Importer une pièce EPLAN (.edz)…"), this);
m_show_this_dir = new QAction(QET::Icons::FolderOnlyThis,
tr("Afficher uniquement ce dossier"),
this);
@@ -247,6 +256,8 @@ void ElementsCollectionWidget::setUpConnection()
this, &ElementsCollectionWidget::newDirectory);
connect(m_new_element, &QAction::triggered,
this, &ElementsCollectionWidget::newElement);
connect(m_import_edz, &QAction::triggered,
this, &ElementsCollectionWidget::importEdz);
connect(m_show_this_dir, &QAction::triggered,
this, &ElementsCollectionWidget::showThisDir);
connect(m_show_all_dir, &QAction::triggered,
@@ -329,6 +340,7 @@ void ElementsCollectionWidget::customContextMenu(const QPoint &point)
{
if (!feci->isMacrosCollection()) {
m_context_menu->addAction(m_new_element);
m_context_menu->addAction(m_import_edz);
}
m_context_menu->addAction(m_new_directory);
if (!feci->isCollectionRoot())
@@ -599,6 +611,108 @@ void ElementsCollectionWidget::newElement()
&ElementsCollectionWidget::locationWasSaved);
}
/**
@brief ElementsCollectionWidget::confirmEdzImportTerms
Show the EPLAN (.edz) import warning dialog. The user must tick the
acknowledgement checkbox before the Import button is enabled.
@return true if the user accepted the terms, false otherwise.
*/
bool ElementsCollectionWidget::confirmEdzImportTerms()
{
QDialog dialog(this);
dialog.setWindowTitle(tr("Avertissement — Importation d'un fichier EPLAN (.edz)"));
QLabel *text = new QLabel(
tr("Le format .edz peut provenir de deux sources différentes :\n"
"\n"
"• Le portail EPLAN Data Portal (dataportal.eplan.com), soumis "
"aux conditions d'utilisation de l'environnement EPLAN Cloud ;\n"
"• Le site d'un fabricant de composants (ou d'un distributeur) "
"qui met ses fichiers .edz à disposition directement, selon ses "
"propres conditions.\n"
"\n"
"QElectroTech ne peut pas déterminer automatiquement l'origine "
"du fichier que vous importez, ni les conditions qui s'y "
"appliquent.\n"
"\n"
"En important ce fichier, vous confirmez que :\n"
"\n"
"• vous connaissez son origine et êtes autorisé à l'utiliser "
"dans ce contexte, au regard des conditions applicables à cette "
"source ;\n"
"• cette importation est effectuée à vos propres risques et "
"responsabilité ;\n"
"• ni QElectroTech, ni ses mainteneurs, ni ses contributeurs ne "
"peuvent être tenus responsables d'une utilisation non conforme "
"de ces données."),
&dialog);
text->setWordWrap(true);
QCheckBox *accept_box = new QCheckBox(
tr("J'ai lu et j'accepte ces conditions."), &dialog);
QDialogButtonBox *buttons = new QDialogButtonBox(
QDialogButtonBox::Cancel, &dialog);
QPushButton *import_button = buttons->addButton(
tr("Importer"), QDialogButtonBox::AcceptRole);
import_button->setDefault(true);
import_button->setEnabled(false);
connect(accept_box, &QCheckBox::toggled,
import_button, &QPushButton::setEnabled);
connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
QVBoxLayout *layout = new QVBoxLayout(&dialog);
layout->addWidget(text);
layout->addWidget(accept_box);
layout->addWidget(buttons);
return dialog.exec() == QDialog::Accepted;
}
/**
@brief ElementsCollectionWidget::importEdz
Import an EPLAN Data Portal part (.edz) as a QET element into the directory
pointed at by the context menu.
*/
void ElementsCollectionWidget::importEdz()
{
ElementCollectionItem *eci = elementCollectionItemForIndex(
m_index_at_context_menu);
if (!eci || eci->type() != FileElementCollectionItem::Type) {
return;
}
FileElementCollectionItem *feci =
static_cast<FileElementCollectionItem*>(eci);
if (feci->isCommonCollection() || !feci->isDir()) {
return;
}
if (!confirmEdzImportTerms()) {
return;
}
const QString edz_path = QFileDialog::getOpenFileName(
this, tr("Importer une pièce EPLAN"), QString(),
tr("Pièces EPLAN (*.edz)"));
if (edz_path.isEmpty()) {
return;
}
EdzImporter importer;
if (!importer.importToDirectory(edz_path, feci->fileSystemPath())) {
QET::QetMessageBox::critical(
this, tr("Import EPLAN"),
tr("Impossible d'importer cette pièce :\n%1")
.arg(importer.errorString()));
return;
}
reload();
}
/**
@brief ElementsCollectionWidget::showThisDir
Hide all directories except the pointed dir;
@@ -830,7 +944,7 @@ void ElementsCollectionWidget::search()
}
//start the search when text have at least 3 letters.
if (text.count() < 3) {
if (text.size() < 3) {
return;
}
@@ -72,6 +72,8 @@ class ElementsCollectionWidget : public QWidget
void editDirectory();
void newDirectory();
void newElement();
bool confirmEdzImportTerms();
void importEdz();
void showThisDir();
void resetShowThisDir();
void dirProperties();
@@ -112,6 +114,7 @@ class ElementsCollectionWidget : public QWidget
*m_edit_dir,
*m_new_directory,
*m_new_element,
*m_import_edz,
*m_show_this_dir,
*m_show_all_dir,
*m_dir_propertie;
@@ -523,7 +523,9 @@ bool ElementsLocation::isCompanyCollection() const
*/
bool ElementsLocation::isCustomCollection() const
{
return fileSystemPath().startsWith(QETApp::customElementsDirN());
const QString dir = QETApp::customElementsDirN();
const QString path = fileSystemPath();
return path == dir || path.startsWith(dir + QLatin1Char('/'));
}
/**
@@ -701,11 +703,11 @@ pugi::xml_document ElementsLocation::pugiXml() const
if (!m_project)
{
#ifndef Q_OS_LINUX
if (docu.load_file(m_file_system_path.toStdString().c_str())) {
if (docu.load_file(m_file_system_path.toStdWString().c_str())) {
docu.save(m_string_stream);
}
#else
docu.load_file(m_file_system_path.toStdString().c_str());
docu.load_file(m_file_system_path.toStdWString().c_str());
#endif
}
else
@@ -802,6 +804,7 @@ bool ElementsLocation::setXml(const QDomDocument &xml_document) const
QString path_ = collectionPath(false);
QRegularExpression rx("^(.*)/(.*\\.elmt)$");
QRegularExpressionMatch match = rx.match(path_);
if (auto regex_match = rx.match(path_); regex_match.hasMatch())
{
@@ -138,7 +138,7 @@ QString FileElementCollectionItem::localName()
{
QString str(fileSystemPath() % "/qet_directory");
pugi::xml_document docu;
if(docu.load_file(str.toStdString().c_str()))
if(docu.load_file(str.toStdWString().c_str()))
{
if (QString(docu.document_element().name())
== "qet-directory")
@@ -274,7 +274,9 @@ bool FileElementCollectionItem::isCompanyCollection() const
*/
bool FileElementCollectionItem::isCustomCollection() const
{
return fileSystemPath().startsWith(QETApp::customElementsDirN());
const QString dir = QETApp::customElementsDirN();
const QString path = fileSystemPath();
return path == dir || path.startsWith(dir + QLatin1Char('/'));
}
/**
@@ -325,6 +327,26 @@ void FileElementCollectionItem::setUpData()
{ search_list.append(context.value(key).toString()); }
search_list.append(localName(loc));
setData(search_list.join(" "));
// Tooltip: show what a truncated tree label can't - the full
// localized name and the descriptive element information.
// Reuses the location/context already parsed for the search
// index above; the collection path stays as the last line
// (it used to be the whole tooltip).
QStringList tip;
tip << localName(loc);
for (const auto &key : { QStringLiteral("description"),
QStringLiteral("manufacturer"),
QStringLiteral("manufacturer_reference") })
{
const QString value = context.value(key).toString();
if (!value.isEmpty())
tip << value;
}
tip << collectionPath();
tip.removeDuplicates();
setToolTip(tip.join(QLatin1Char('\n')));
return;
}
}
@@ -186,23 +186,22 @@ QDomElement XmlElementCollection::child(const QDomElement &parent_element,
if (parent_element.ownerDocument() != m_dom_document)
return QDomElement();
//Get all childs element of parent_element
QDomNodeList child_list = parent_element.childNodes();
QString tag_name(child_name.endsWith(".elmt")? "element" : "category");
QList <QDomElement> found_dom_element;
for (int i=0 ; i<child_list.length() ; i++)
/* Walk the siblings directly instead of the previous
* childNodes()+item(i) double pass: QDomNodeList::item(i) restarts
* from the first child every call, which made this loop quadratic
* in the number of children. This lookup runs several times per
* element instance while loading a project, on the "import"
* category that holds every embedded definition. */
const QString tag_name = child_name.endsWith(QLatin1String(".elmt"))
? QStringLiteral("element") : QStringLiteral("category");
for (QDomElement child_element = parent_element.firstChildElement(tag_name) ;
!child_element.isNull() ;
child_element = child_element.nextSiblingElement(tag_name))
{
QDomElement child_element = child_list.item(i).toElement();
if (child_element.tagName() == tag_name)
found_dom_element << child_element;
if (child_element.attribute(QStringLiteral("name")) == child_name)
return child_element;
}
if (found_dom_element.isEmpty()) return QDomElement();
foreach (QDomElement elmt, found_dom_element)
if (elmt.attribute("name") == child_name)
return elmt;
return QDomElement();
}
+7
View File
@@ -263,6 +263,13 @@ QString NamesList::name(const QString &fallback_name) const
QString system_language = QETApp::langFromSetting();
if (! map_names[system_language].isEmpty())
return (map_names[system_language]);
// langFromSetting() may return a full locale (e.g. "de_DE") while element
// and folder names are keyed by the 2-letter language code ("de"). Try the
// base language before falling back to English, mirroring what setLanguage()
// does for the UI translations.
const QString base_language = system_language.section('_', 0, 0);
if (base_language != system_language && ! map_names[base_language].isEmpty())
return (map_names[base_language]);
if (! map_names["en"].isEmpty()) return (map_names["en"]);
if (! fallback_name.isEmpty()) return (fallback_name);
if (map_names.count()) return (map_names.begin().value());
@@ -16,7 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "searchandreplaceworker.h"
#include "../qetproject.h"
#include "../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../diagram.h"
#include "../diagramcommands.h"
@@ -1,4 +1,4 @@
/*
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
@@ -475,7 +475,7 @@ void SearchAndReplaceWidget::setUpConenctions()
connect(ui->m_tree_widget, &QTreeWidget::customContextMenuRequested, [this](const QPoint &pos)
{
if (m_diagram_hash.keys().contains(ui->m_tree_widget->currentItem()))
if (m_diagram_hash.contains(ui->m_tree_widget->currentItem()))
{
QMenu *menu = new QMenu(ui->m_tree_widget);
menu->addAction(m_select_elements);
@@ -951,28 +951,28 @@ void SearchAndReplaceWidget::on_m_tree_widget_itemDoubleClicked(
{
Q_UNUSED(column)
if (m_diagram_hash.keys().contains(item))
if (m_diagram_hash.contains(item))
{
QPointer<Diagram> diagram = m_diagram_hash.value(item);
if(diagram) {
diagram.data()->showMe();
}
}
else if (m_element_hash.keys().contains(item))
else if (m_element_hash.contains(item))
{
QPointer<Element> elmt = m_element_hash.value(item);
if (elmt && elmt->diagram()) {
elmt.data()->diagram()->showMe();
}
}
else if (m_text_hash.keys().contains(item))
else if (m_text_hash.contains(item))
{
QPointer<IndependentTextItem> text = m_text_hash.value(item);
if (text && text->diagram()) {
text.data()->diagram()->showMe();
}
}
else if (m_conductor_hash.keys().contains(item))
else if (m_conductor_hash.contains(item))
{
QPointer<Conductor> cond = m_conductor_hash.value(item);
if (cond && cond->diagram()) {
@@ -1013,7 +1013,7 @@ void SearchAndReplaceWidget::on_m_tree_widget_currentItemChanged(
m_last_selected.data()->setSelected(false);
}
if (m_element_hash.keys().contains(current))
if (m_element_hash.contains(current))
{
QPointer<Element> elmt = m_element_hash.value(current);
if (elmt)
@@ -1022,7 +1022,7 @@ void SearchAndReplaceWidget::on_m_tree_widget_currentItemChanged(
elmt.data()->setHighlighted(true);
}
}
else if (m_text_hash.keys().contains(current))
else if (m_text_hash.contains(current))
{
QPointer<IndependentTextItem> text = m_text_hash.value(current);
if (text)
@@ -1031,7 +1031,7 @@ void SearchAndReplaceWidget::on_m_tree_widget_currentItemChanged(
m_last_selected = text;
}
}
else if (m_conductor_hash.keys().contains(current))
else if (m_conductor_hash.contains(current))
{
QPointer<Conductor> cond = m_conductor_hash.value(current);
if (cond)
@@ -1144,7 +1144,7 @@ void SearchAndReplaceWidget::on_m_replace_pb_clicked()
&& qtwi->checkState(0) == Qt::Checked)
{
if (ui->m_folio_pb->text().endsWith(tr(" [édité]")) &&
m_diagram_hash.keys().contains(qtwi))
m_diagram_hash.contains(qtwi))
{
QPointer<Diagram> d = m_diagram_hash.value(qtwi);
if (d) {
@@ -1152,7 +1152,7 @@ void SearchAndReplaceWidget::on_m_replace_pb_clicked()
}
}
else if (ui->m_element_pb->text().endsWith(tr(" [édité]")) &&
m_element_hash.keys().contains(qtwi))
m_element_hash.contains(qtwi))
{
QPointer<Element> e = m_element_hash.value(qtwi);
if (e) {
@@ -1160,7 +1160,7 @@ void SearchAndReplaceWidget::on_m_replace_pb_clicked()
}
}
else if (!ui->m_replace_le->text().isEmpty() &&
m_text_hash.keys().contains(qtwi))
m_text_hash.contains(qtwi))
{
m_worker.m_indi_text = ui->m_replace_le->text();
QPointer<IndependentTextItem> t =
@@ -1171,7 +1171,7 @@ void SearchAndReplaceWidget::on_m_replace_pb_clicked()
}
else if (ui->m_conductor_pb->text().endsWith(tr(" [édité]")) &&
m_conductor_hash.keys().contains(qtwi))
m_conductor_hash.contains(qtwi))
{
QPointer<Conductor> c = m_conductor_hash.value(qtwi);
if (c) {
@@ -1187,7 +1187,7 @@ void SearchAndReplaceWidget::on_m_replace_pb_clicked()
QList <IndependentTextItem *>tl;
QList <Conductor *>cl;
if (m_diagram_hash.keys().contains(qtwi))
if (m_diagram_hash.contains(qtwi))
{
QPointer<Diagram> d =
m_diagram_hash.value(qtwi);
@@ -1195,7 +1195,7 @@ void SearchAndReplaceWidget::on_m_replace_pb_clicked()
dl.append(d.data());
}
}
else if (m_element_hash.keys().contains(qtwi))
else if (m_element_hash.contains(qtwi))
{
QPointer<Element> e =
m_element_hash.value(qtwi);
@@ -1203,7 +1203,7 @@ void SearchAndReplaceWidget::on_m_replace_pb_clicked()
el.append(e.data());
}
}
else if (m_text_hash.keys().contains(qtwi))
else if (m_text_hash.contains(qtwi))
{
QPointer<IndependentTextItem> t =
m_text_hash.value(qtwi);
@@ -1211,7 +1211,7 @@ void SearchAndReplaceWidget::on_m_replace_pb_clicked()
tl.append(t.data());
}
}
else if (m_conductor_hash.keys().contains(qtwi))
else if (m_conductor_hash.contains(qtwi))
{
QPointer<Conductor> c =
m_conductor_hash.value(qtwi);
@@ -266,13 +266,13 @@ void TerminalStripDrawer::paint(QPainter *painter)
painter->restore();
//Draw the bridges
for (const auto &points_ : qAsConst(bridges_anchor_points))
for (const auto &points_ : std::as_const(bridges_anchor_points))
{
painter->save();
auto pen_{painter->pen()};
pen_.setWidth(2);
painter->setPen(pen_);
painter->drawPolyline(QPolygonF{points_});
painter->drawPolyline(QPolygonF(points_));
painter->restore();
}
}
@@ -16,7 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "terminalstripitem.h"
#include "../../qetproject.h"
#include "../diagram.h"
#include "../../project/projectpropertieshandler.h"
@@ -126,7 +126,7 @@ void RemoveTerminalFromStripCommand::redo()
if (m_strip)
{
QVector<QSharedPointer<RealTerminal>> real_t;
for (const auto &real_t_vector : qAsConst(m_terminals)) {
for (const auto &real_t_vector : std::as_const(m_terminals)) {
real_t.append(real_t_vector);
}
@@ -68,7 +68,7 @@ void UnGroupTerminalsCommand::undo()
m_terminal_strip->groupTerminals(key, m_physical_real_H.value(key));
}
//Second, set level.
for (const auto &pair : qAsConst(m_real_t_level)) {
for (const auto &pair : std::as_const(m_real_t_level)) {
m_terminal_strip->setLevel(pair.first, pair.second);
}
}
@@ -78,7 +78,7 @@ void UnGroupTerminalsCommand::redo()
{
if (m_terminal_strip)
{
for (const auto &value : qAsConst(m_physical_real_H)) {
for (const auto &value : std::as_const(m_physical_real_H)) {
m_terminal_strip->unGroupTerminals(value);
}
}
+5 -5
View File
@@ -466,7 +466,7 @@ QSharedPointer<RealTerminal> TerminalStrip::realTerminalForUuid(const QUuid &uui
QVector<QSharedPointer<RealTerminal>> TerminalStrip::realTerminals() const
{
QVector<QSharedPointer<RealTerminal>> vector_;
for (const auto &phy : qAsConst(m_physical_terminals)) {
for (const auto &phy : std::as_const(m_physical_terminals)) {
vector_.append(phy->realTerminals());
}
return vector_;
@@ -639,7 +639,7 @@ bool TerminalStrip::isBridgeable(const QVector<QSharedPointer<RealTerminal>> &re
// Get the physical terminal and pos
auto first_physical_terminal = first_real_terminal->physicalTerminal();
QVector<shared_physical_terminal> physical_vector{first_physical_terminal};
QVector<int> pos_vector{m_physical_terminals.indexOf(first_physical_terminal)};
QVector<int> pos_vector{static_cast<int>(m_physical_terminals.indexOf(first_physical_terminal))};
auto bridge_ = isBridged(first_real_terminal);
//bool to know at the end of this function if at least one terminal is not bridged
@@ -856,7 +856,7 @@ QSharedPointer<TerminalStripBridge> TerminalStrip::isBridged(const QSharedPointe
{
if (real_terminal)
{
for (const auto &bridge_ : qAsConst(m_bridge)) {
for (const auto &bridge_ : std::as_const(m_bridge)) {
if (bridge_->realTerminals().contains(real_terminal))
return bridge_;
}
@@ -983,7 +983,7 @@ QDomElement TerminalStrip::toXml(QDomDocument &parent_document)
}
root_elmt.appendChild(xml_layout);
for (const auto &bridge_ : qAsConst(m_bridge)) {
for (const auto &bridge_ : std::as_const(m_bridge)) {
root_elmt.appendChild(bridge_->toXml(parent_document));
}
@@ -1024,7 +1024,7 @@ bool TerminalStrip::fromXml(QDomElement &xml_element)
for (auto &xml_real : QETXML::findInDomElement(xml_physical, RealTerminal::xmlTagName()))
{
const auto uuid_ = QUuid(xml_real.attribute(QStringLiteral("element_uuid")));
for (auto terminal_elmt : qAsConst(free_terminals))
for (auto terminal_elmt : std::as_const(free_terminals))
{
if (terminal_elmt->uuid() == uuid_)
{
@@ -85,7 +85,7 @@ QDomElement TerminalStripBridge::toXml(QDomDocument &parent_document) const
root_elmt.setAttribute(QStringLiteral("color"), m_color.name());
auto terminals_elmt = parent_document.createElement(QStringLiteral("real_terminals"));
for (const auto &real_t : qAsConst(m_real_terminals))
for (const auto &real_t : std::as_const(m_real_terminals))
{
if (real_t)
{
@@ -106,7 +106,7 @@ void TerminalStripBridge::fromXml(const QDomElement &dom_element)
}
m_uuid = QUuid(dom_element.attribute(QStringLiteral("uuid"), m_uuid.toString()));
m_color.setNamedColor(dom_element.attribute(QStringLiteral("color")));
m_color = QColor(dom_element.attribute(QStringLiteral("color")));
const auto real_t_vector = QETXML::subChild(dom_element,
QStringLiteral("real_terminals"),
@@ -17,7 +17,7 @@
*/
#include "addterminalstripitemdialog.h"
#include "ui_addterminalstripitemdialog.h"
#include "../../qetproject.h"
#include "../../undocommand/addgraphicsobjectcommand.h"
#include "../terminalstrip.h"
#include "../GraphicsItem/terminalstripitem.h"
@@ -535,7 +535,7 @@ modelRealTerminalData TerminalStripModel::dataAtRow(int row) const
else
{
auto current_row = 0;
for (const auto &physical_data : qAsConst(m_physical_data))
for (const auto &physical_data : std::as_const(m_physical_data))
{
for (const auto &real_data : physical_data.real_data)
{
@@ -567,7 +567,7 @@ void TerminalStripModel::replaceDataAtRow(modelRealTerminalData data, int row)
auto current_row = 0;
auto current_physical = 0;
for (const auto &physical_data : qAsConst(m_physical_data))
for (const auto &physical_data : std::as_const(m_physical_data))
{
auto current_real = 0;
for (int i=0 ; i<physical_data.real_data.count() ; ++i)
@@ -606,7 +606,7 @@ modelPhysicalTerminalData TerminalStripModel::physicalDataAtIndex(int index) con
int current_phy = -1;
bool match_ = false;
for (const auto &ptd_ : qAsConst(m_physical_data))
for (const auto &ptd_ : std::as_const(m_physical_data))
{
current_checked_index += ptd_.real_data.size();
++current_phy;
@@ -637,9 +637,9 @@ modelRealTerminalData TerminalStripModel::realDataAtIndex(int index) const
int current_checked_index = -1;
for (const auto & ptd_ : qAsConst(m_physical_data))
for (const auto & ptd_ : std::as_const(m_physical_data))
{
for (const auto & rtd_ : qAsConst(ptd_.real_data)) {
for (const auto & rtd_ : std::as_const(ptd_.real_data)) {
++current_checked_index;
if (current_checked_index == index) {
return rtd_;
@@ -29,9 +29,15 @@
#include "modelTerminalData.h"
//Code to use QColor as key for QHash
inline uint qHash(const QColor &key, uint seed) {
return qHash(key.name(), seed);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
inline size_t qHash(const QColor &key, size_t seed = 0) {
return qHash(key.rgba(), seed);
}
#else
inline uint qHash(const QColor &key, uint seed) {
return qHash(key.rgba(), seed);
}
#endif
//needed to use QPointer<Element> as key of QHash
inline uint qHash(const QPointer<Element> &key, uint seed) {
@@ -81,7 +81,7 @@ void TerminalStripTreeDockWidget::reload()
m_uuid_terminal_H.clear();
m_uuid_strip_H.clear();
for (const auto &connection_ : qAsConst(m_strip_changed_connection)) {
for (const auto &connection_ : std::as_const(m_strip_changed_connection)) {
disconnect(connection_);
}
m_strip_changed_connection.clear();
@@ -242,7 +242,7 @@ void TerminalStripTreeDockWidget::buildTree()
return a->name() < b->name();
});
for (const auto &ts : qAsConst(ts_vector)) {
for (const auto &ts : std::as_const(ts_vector)) {
addTerminalStrip(ts);
}
addFreeTerminal();
@@ -344,7 +344,7 @@ void TerminalStripTreeDockWidget::addFreeTerminal()
auto free_terminal_item = ui->m_tree_view->topLevelItem(1);
for (const auto terminal : qAsConst(vector_))
for (const auto terminal : std::as_const(vector_))
{
QUuid uuid_ = terminal->uuid();
QStringList strl{terminal->actualLabel()};
+7 -1
View File
@@ -23,7 +23,7 @@
#include "../qetgraphicsitem/conductor.h"
#include "../qetgraphicsitem/element.h"
#include "../qetxml.h"
#include "../qetproject.h"
#include <QStringList>
#include <QVariant>
#include <utility>
@@ -275,6 +275,12 @@ namespace autonum
str.replace("%{void}", QString());
str.replace("%{plc_type}", dc.value("plc_type").toString());
str.replace("%{plc_address}", dc.value("plc_address").toString());
str.replace("%{plc_function}", dc.value("plc_function").toString());
str.replace("%{plc_comment}", dc.value("plc_comment").toString());
str.replace("%{plc_crossref}", dc.value("plc_crossref").toString());
return str;
}
+2 -2
View File
@@ -57,9 +57,9 @@ bool NumerotationContext::addValue(const QString &type,
const QVariant &value,
const int increase,
const int initialvalue) {
if (!keyIsAcceptable(type) && !value.canConvert(QVariant::String))
if (!keyIsAcceptable(type) && !value.canConvert<QString>())
return false;
if (keyIsNumber(type) && !value.canConvert(QVariant::Int))
if (keyIsNumber(type) && !value.canConvert<int>())
return false;
QString valuestr = value.toString();
+5 -1
View File
@@ -894,9 +894,13 @@ void BorderTitleBlock::updateDiagramContextForTitleBlock(
const DiagramContext &initial_context) {
// Our final DiagramContext is the initial one (which is supposed to bring
// project-wide properties), overridden by the "additional fields" one...
// An empty page-level value means the variable was auto-added to the
// folio's Custom tab (#495) but never actually set by the user, so it
// must not shadow a real project-level value of the same name (#531).
DiagramContext context = initial_context;
foreach (QString key, additional_fields_.keys()) {
context.addValue(key, additional_fields_[key]);
if (!additional_fields_[key].toString().isEmpty())
context.addValue(key, additional_fields_[key]);
}
// ... overridden by the historical and/or dynamically generated fields
+837
View File
@@ -0,0 +1,837 @@
/*
Copyright 2006-2025 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech 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 2 of the License, or
(at your option) any later version.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "cli_export.h"
#include "bordertitleblock.h"
#include "conductornumexport.h"
#include "conductorproperties.h"
#include "dataBase/projectdatabase.h"
#include "diagram.h"
#include "diagramcontext.h"
#include "pdf_links.h"
#include "qetgraphicsitem/conductor.h"
#include "qetgraphicsitem/element.h"
#include "qetgraphicsitem/terminal.h"
#include "qetproject.h"
#include "titleblockproperties.h"
#include "wiringlistexport.h"
// Private Qt PDF engine for drawHyperlink() — see pdf_links / projectprintwindow.
#include <private/qpdf_p.h>
#include <QDir>
#include <QDirIterator>
#include <QDomDocument>
#include <QDate>
#include <QFile>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMap>
#include <QPageLayout>
#include <QPair>
#include <QPainter>
#include <QPdfWriter>
#include <QSet>
#include <QSqlError>
#include <QSqlQuery>
#include <QSvgGenerator>
#include <QTextStream>
#include <QTransform>
namespace {
QTextStream out(stdout);
QTextStream err(stderr);
/// All CLI option flags, mapped to a short format name.
const QHash<QString, QString> &exportFlags()
{
static const QHash<QString, QString> flags {
{"--export-pdf", "pdf"},
{"--export-png", "png"},
{"--export-svg", "svg"},
{"--export-cables", "cables"},
{"--export-wires", "wires"},
{"--export-bom", "bom"},
{"--export-nets", "nets"},
{"--export-links", "links"},
{"--info", "info"},
{"--check-elements", "check"},
{"--resave", "resave"},
{"--set-titleblock", "settb"},
};
return flags;
}
/// Device tag of an element ("K1", "Q55"), falling back to its name.
QString elementLabel(Element *element)
{
const QString label = element->elementInformations()["label"].toString();
return label.isEmpty() ? element->name() : label;
}
/// Pixel rect of a diagram's border + title block (the printable page area).
QRect diagramRect(Diagram *diagram)
{
QRectF r = diagram->border_and_titleblock.borderAndTitleBlockRect();
r.adjust(0, 0, 1, 1); // include the 1px border line
return r.toAlignedRect();
}
/// A filesystem-safe per-diagram file stem: "01_Title".
QString diagramStem(Diagram *diagram, int index)
{
QString title = diagram->title();
title.replace(QRegularExpression("[^\\w \\-]"), "_");
title = title.simplified();
if (title.isEmpty())
title = "diagram";
return QStringLiteral("%1_%2")
.arg(index, 2, 10, QChar('0'))
.arg(title);
}
/// Render @p diagram into @p painter, fitting @p target to the page rect.
void renderDiagram(Diagram *diagram, QPainter &painter, const QRectF &target)
{
const QRect source = diagramRect(diagram);
// Export without the editor grid: drawBackground() only paints it when
// draw_grid_ is set (default true), so toggle it off around the render
// and restore it afterwards.
const bool was_drawing_grid = diagram->displayGrid();
const bool was_drawing_guides = diagram->displayGuides();
diagram->setDisplayGrid(false);
diagram->setDisplayGuides(false);
diagram->render(&painter, target, source, Qt::KeepAspectRatio);
diagram->setDisplayGrid(was_drawing_grid);
diagram->setDisplayGuides(was_drawing_guides);
}
int exportPdf(QETProject &project, const QString &output)
{
const QList<Diagram *> diagrams = project.diagrams();
if (diagrams.isEmpty()) {
err << "No diagrams to export.\n";
return 1;
}
// Page numbers (1-based) for cross-reference hyperlink targets: each
// diagram is exactly one page in the CLI export (no tiling).
QMap<Diagram *, int> pageMap;
for (int i = 0; i < diagrams.size(); ++i)
pageMap.insert(diagrams.at(i), i + 1);
QPdfWriter writer(output);
writer.setCreator("QElectroTech");
writer.setResolution(96);
QPainter painter;
bool first = true;
for (Diagram *diagram : diagrams) {
const QRect r = diagramRect(diagram);
// Match the page to the diagram (in points: 1px @ 96dpi = 0.75pt).
const QPageSize page(QSizeF(r.width() * 72.0 / 96.0,
r.height() * 72.0 / 96.0),
QPageSize::Point);
writer.setPageSize(page);
writer.setPageMargins(QMarginsF(0, 0, 0, 0));
if (first) {
if (!painter.begin(&writer)) {
err << "Cannot open '" << output << "' for writing.\n";
return 1;
}
first = false;
} else {
writer.newPage();
}
const QRectF target(0, 0,
writer.width(), writer.height());
renderDiagram(diagram, painter, target);
// Inject clickable cross-reference / folio-report hyperlinks for this
// page. The geometry is rebuilt from the QPdfWriter (not a QPrinter):
// render() anchors the diagram top-left with KeepAspectRatio, and the
// page is sized to the diagram so the scale is ~1.
if (auto *engine = dynamic_cast<QPdfEngine *>(painter.paintEngine())) {
const QRectF source(r);
const qreal s = qMin(target.width() / source.width(),
target.height() / source.height());
QTransform fit;
fit.translate(target.x(), target.y());
fit.scale(s, s);
fit.translate(-source.x(), -source.y());
// Device pixels -> PDF points, replicating the engine's page matrix
// (72/resolution scale + Y flip; zero margins -> no paint offset).
const qreal pt_scale = 72.0 / writer.resolution();
const qreal fullH_pt = writer.pageLayout().fullRectPoints().height();
const bool fullPageMode =
(writer.pageLayout().mode() == QPageLayout::FullPageMode);
const QRect paintPx =
writer.pageLayout().paintRectPixels(writer.resolution());
PdfLinks::PageGeometry geom;
geom.sceneToDevice = fit;
geom.target = target;
geom.pageBounds = QRectF(0, 0, target.width(), target.height());
geom.devToPdf = [=](const QPointF &d) -> QPointF {
qreal dx = d.x(), dy = d.y();
if (!fullPageMode) { dx += paintPx.left(); dy += paintPx.top(); }
return QPointF(pt_scale * dx, fullH_pt - pt_scale * dy);
};
geom.sourceRectOf = [](Diagram *dg) {
return QRectF(diagramRect(dg));
};
PdfLinks::injectCrossRefLinks(engine, diagram, geom, pageMap, output);
}
}
painter.end();
// Rewrite the URI link annotations into native internal GoTo actions, so
// the cross-references jump inside the document in any PDF viewer.
PdfLinks::convertUriToGoTo(output);
out << "Exported " << diagrams.size() << " page(s) -> " << output << "\n";
return 0;
}
int exportImages(QETProject &project, const QString &format,
const QString &out_dir)
{
const QList<Diagram *> diagrams = project.diagrams();
if (diagrams.isEmpty()) {
err << "No diagrams to export.\n";
return 1;
}
QDir().mkpath(out_dir);
int index = 0;
for (Diagram *diagram : diagrams) {
++index;
const QRect r = diagramRect(diagram);
const QString path = QDir(out_dir).filePath(
diagramStem(diagram, index) + "." + format);
if (format == "svg") {
QSvgGenerator gen;
gen.setFileName(path);
gen.setSize(r.size());
gen.setViewBox(QRect(0, 0, r.width(), r.height()));
gen.setTitle(diagram->title());
QPainter painter(&gen);
renderDiagram(diagram, painter, QRectF(QPointF(0, 0), r.size()));
painter.end();
} else { // png
QImage image(r.size(), QImage::Format_ARGB32);
image.fill(Qt::white);
QPainter painter(&image);
painter.setRenderHint(QPainter::Antialiasing, true);
renderDiagram(diagram, painter, QRectF(QPointF(0, 0), r.size()));
painter.end();
if (!image.save(path)) {
err << "Failed to write '" << path << "'.\n";
return 1;
}
}
out << " " << path << "\n";
}
out << "Exported " << diagrams.size() << " diagram(s) -> " << out_dir << "\n";
return 0;
}
int exportCsv(QETProject &project, const QString &format, const QString &output)
{
QString csv;
if (format == "cables") {
WiringListExport wle(&project, nullptr);
csv = wle.toCsvString();
} else { // wires
ConductorNumExport cne(&project, nullptr);
csv = cne.wiresNum();
}
if (csv.isEmpty()) {
err << "Nothing to export (empty list).\n";
return 1;
}
QFile file(output);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
err << "Cannot open '" << output << "' for writing.\n";
return 1;
}
QTextStream fout(&file);
fout << csv;
file.close();
out << "Exported " << format << " list -> " << output << "\n";
return 0;
}
/// Quote a field for CSV output (RFC-4180 style, ';' delimiter).
QString csvField(const QString &value)
{
if (value.contains(';') || value.contains('"')
|| value.contains('\n') || value.contains('\r')) {
QString v = value;
v.replace('"', "\"\"");
return '"' % v % '"';
}
return value;
}
/// Bill of materials: one row per element, key component-data fields.
/// Pulls from QET's own project database (the same source as the GUI BOM
/// export), so the output matches what the editor produces.
int exportBom(QETProject &project, const QString &output)
{
// The project database is built lazily; force a (re)build before querying.
project.dataBase()->updateDB();
static const QStringList columns {
"label", "designation", "manufacturer", "manufacturer_reference",
"quantity", "location", "function", "title", "folio"
};
QSqlQuery query = project.dataBase()->newQuery(
"SELECT " % columns.join(", ") %
" FROM element_nomenclature_view ORDER BY label");
if (!query.exec()) {
err << "BOM query failed: " << query.lastError().text() << "\n";
return 1;
}
QString csv = columns.join(";") % "\n";
int rows = 0;
while (query.next()) {
QStringList values;
for (int i = 0; i < columns.size(); ++i)
values << csvField(query.value(i).toString());
csv += values.join(";") % "\n";
++rows;
}
QFile file(output);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
err << "Cannot open '" << output << "' for writing.\n";
return 1;
}
QTextStream fout(&file);
fout << csv;
file.close();
out << "Exported " << rows << " component(s) -> " << output << "\n";
return 0;
}
/// Count terminals on @p element that no conductor connects to.
int freeTerminals(Element *element)
{
int free = 0;
const QList<Terminal *> terminals = element->terminals();
for (Terminal *t : terminals)
if (t->conductorsCount() == 0)
++free;
return free;
}
/// Structural ground-truth dump of a project, as JSON, to stdout (or a file).
/// Uses QET's own loaded model, so it reports what the editor actually sees:
/// per-page element / conductor counts and unconnected terminals.
int exportInfo(QETProject &project, const QString &output)
{
const QList<Diagram *> diagrams = project.diagrams();
int total_elements = 0, total_conductors = 0, total_free = 0;
QJsonArray pages;
int index = 0;
for (Diagram *diagram : diagrams) {
++index;
const QList<Element *> elements = diagram->elements();
const int conductors = diagram->conductors().size();
int page_free = 0;
for (Element *e : elements)
page_free += freeTerminals(e);
const QRect r = diagramRect(diagram);
QJsonObject page;
page["index"] = index;
page["title"] = diagram->title();
page["folio"] = QStringLiteral("%1 of %2")
.arg(index).arg(diagrams.size());
page["width_px"] = r.width();
page["height_px"] = r.height();
page["elements"] = elements.size();
page["conductors"] = conductors;
page["free_terminals"] = page_free;
pages.append(page);
total_elements += elements.size();
total_conductors += conductors;
total_free += page_free;
}
QJsonObject root;
root["project"] = project.title();
root["diagrams"] = diagrams.size();
root["elements"] = total_elements;
root["conductors"] = total_conductors;
root["free_terminals"] = total_free;
root["pages"] = pages;
const QByteArray json =
QJsonDocument(root).toJson(QJsonDocument::Indented);
if (output.isEmpty()) {
out << QString::fromUtf8(json);
} else {
QFile file(output);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
err << "Cannot open '" << output << "' for writing.\n";
return 1;
}
file.write(json);
file.close();
out << "Wrote project info -> " << output << "\n";
}
return 0;
}
/// Validate one .elmt file against QET's element schema.
/// @return 0 = OK, 1 = warning (loads but suspicious), 2 = failure.
int checkOneElement(const QString &path)
{
QFile file(path);
if (!file.open(QIODevice::ReadOnly)) {
out << "FAIL " << path << " (cannot open)\n";
return 2;
}
QDomDocument doc;
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
if (const auto result = doc.setContent(&file); !result) {
file.close();
out << "FAIL " << path << " (XML error line "
<< result.errorLine << ": " << result.errorMessage << ")\n";
return 2;
}
#else
QString error;
int line = 0;
if (!doc.setContent(&file, &error, &line)) {
file.close();
out << "FAIL " << path << " (XML error line "
<< line << ": " << error << ")\n";
return 2;
}
#endif
file.close();
const QDomElement root = doc.documentElement();
if (root.tagName() != "definition" || root.attribute("type") != "element") {
out << "FAIL " << path << " (root is not <definition type=\"element\">)\n";
return 2;
}
bool w_ok = false, h_ok = false;
const double w = root.attribute("width").toDouble(&w_ok);
const double h = root.attribute("height").toDouble(&h_ok);
if (!w_ok || !h_ok || w == 0 || h == 0) {
out << "FAIL " << path << " (missing/zero bounding box "
<< root.attribute("width") << "x"
<< root.attribute("height") << ")\n";
return 2;
}
const int terminals = root.elementsByTagName("terminal").count();
// Negative dimensions are malformed but QET still loads them; surface as a
// warning rather than a failure so this agrees with QET's own loader.
if (w < 0 || h < 0) {
out << "WARN " << path << " (negative bounding box "
<< w << "x" << h << ", " << terminals << " terminals)\n";
return 1;
}
if (terminals == 0) {
out << "WARN " << path << " (loads, but 0 terminals)\n";
return 1;
}
out << "OK " << path << " (" << terminals << " terminals)\n";
return 0;
}
/// Validate a single .elmt file or every .elmt under a directory.
int checkElements(const QString &path)
{
QStringList files;
const QFileInfo info(path);
if (info.isDir()) {
QDirIterator it(path, {"*.elmt"}, QDir::Files,
QDirIterator::Subdirectories);
while (it.hasNext())
files << it.next();
files.sort();
} else if (info.isFile()) {
files << path;
} else {
err << "Not found: " << path << "\n";
return 2;
}
if (files.isEmpty()) {
err << "No .elmt files found under: " << path << "\n";
return 2;
}
int warnings = 0, failures = 0;
for (const QString &f : files) {
const int r = checkOneElement(f);
if (r == 1) ++warnings;
else if (r == 2) ++failures;
}
out << files.size() << " file(s), " << warnings
<< " warning(s), " << failures << " failure(s)\n";
return failures > 0 ? 1 : 0;
}
/// Map every element in the project to its 1-based folio (page) position.
QHash<Element *, int> folioIndex(QETProject &project)
{
QHash<Element *, int> folio;
int index = 0;
const QList<Diagram *> diagrams = project.diagrams();
for (Diagram *diagram : diagrams) {
++index;
const QList<Element *> elements = diagram->elements();
for (Element *e : elements)
folio.insert(e, index);
}
return folio;
}
/// Electrical nets: groups of terminals joined into one potential.
/// Walks QET's own potential graph, so each net is a connected component
/// of terminals across all folios. The ground truth for connectivity.
int exportNets(QETProject &project, const QString &output)
{
const QHash<Element *, int> folio = folioIndex(project);
QList<Conductor *> all_conductors;
const QList<Diagram *> diagrams = project.diagrams();
for (Diagram *diagram : diagrams)
all_conductors << diagram->conductors();
QSet<Conductor *> visited;
QJsonArray nets;
int net_no = 0;
for (Conductor *c : all_conductors) {
if (visited.contains(c))
continue;
// The whole potential this conductor belongs to. relatedPotential-
// Conductors() also fills t_list with every terminal in the net
// (following folio reports and terminal blocks too).
QList<Terminal *> t_list;
QSet<Conductor *> group = c->relatedPotentialConductors(true, &t_list);
group.insert(c);
for (Conductor *g : group)
visited.insert(g);
if (c->terminal1) t_list << c->terminal1;
if (c->terminal2) t_list << c->terminal2;
// Wire number: smallest non-empty conductor text (deterministic).
QStringList wire_nos;
for (Conductor *g : group)
if (!g->properties().text.isEmpty())
wire_nos << g->properties().text;
wire_nos.sort();
++net_no;
QJsonArray terminals;
QSet<Terminal *> seen;
for (Terminal *t : t_list) {
if (!t || seen.contains(t))
continue;
seen.insert(t);
Element *pe = t->parentElement();
QJsonObject to;
to["element"] = pe ? elementLabel(pe) : QString();
to["terminal"] = t->name();
to["folio"] = pe ? folio.value(pe, 0) : 0;
terminals.append(to);
}
QJsonObject net;
net["net"] = net_no;
net["wire_no"] = wire_nos.value(0);
net["terminals"] = terminals;
nets.append(net);
}
QJsonObject root;
root["project"] = project.title();
root["nets"] = nets.size();
root["list"] = nets;
QFile file(output);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
err << "Cannot open '" << output << "' for writing.\n";
return 1;
}
file.write(QJsonDocument(root).toJson(QJsonDocument::Indented));
file.close();
out << "Exported " << nets.size() << " net(s) -> " << output << "\n";
return 0;
}
/// Cross-references: each linkable element (coil / contact / report) and the
/// elements it links to, flagging masters/slaves with no link as unresolved.
int exportLinks(QETProject &project, const QString &output)
{
const QHash<Element *, int> folio = folioIndex(project);
QString csv("element;link_type;linked_to;folio;status\n");
int linkable = 0, unresolved = 0;
const QList<Diagram *> diagrams = project.diagrams();
for (Diagram *diagram : diagrams) {
const QList<Element *> elements = diagram->elements();
for (Element *e : elements) {
if (e->linkType() == Element::Simple)
continue;
++linkable;
const QList<Element *> linked = e->linkedElements();
QStringList names;
for (Element *le : linked)
names << elementLabel(le) % "(f"
% QString::number(folio.value(le, 0)) % ")";
QString status = "linked";
if ((e->linkType() == Element::Master
|| e->linkType() == Element::Slave)
&& linked.isEmpty()) {
status = "UNRESOLVED";
++unresolved;
}
csv += csvField(elementLabel(e)) % ";"
% e->linkTypeToString() % ";"
% csvField(names.join(", ")) % ";"
% QString::number(folio.value(e, 0)) % ";"
% status % "\n";
}
}
QFile file(output);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
err << "Cannot open '" << output << "' for writing.\n";
return 1;
}
QTextStream fout(&file);
fout << csv;
file.close();
out << "Exported " << linkable << " linkable element(s), "
<< unresolved << " unresolved -> " << output << "\n";
return 0;
}
/// Round-trip: load the project and write its XML back out, so an external
/// diff can reveal markup QET silently normalises (tolerated-but-invalid XML).
int resaveProject(QETProject &project, const QString &output)
{
const QDomDocument doc = project.toXml();
QFile file(output);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
err << "Cannot open '" << output << "' for writing.\n";
return 1;
}
QTextStream fout(&file);
fout << doc.toString(4);
file.close();
out << "Re-saved project -> " << output << "\n";
return 0;
}
/// Stamp title-block fields onto every folio (and the project default), then
/// save. Each assignment is "key=value". Standard keys map to the documented
/// title-block fields; "date=today" uses the current date; any other key is
/// stored as a custom title-block field. Aimed at CI/revision workflows
/// (e.g. set revision + date before exporting a new revision).
int setTitleBlock(QETProject &project, const QString &output,
const QStringList &assignments)
{
if (assignments.isEmpty()) {
err << "No field assignments given (expected key=value).\n";
return 2;
}
// Parse "key=value" assignments up front so a bad one fails before writing.
QList<QPair<QString, QString>> fields;
for (const QString &a : assignments) {
const int eq = a.indexOf('=');
if (eq <= 0) {
err << "Bad assignment '" << a << "' (expected key=value).\n";
return 2;
}
const QString key = a.left(eq);
const QString val = a.mid(eq + 1);
if (key.compare("date", Qt::CaseInsensitive) == 0
&& val.compare("today", Qt::CaseInsensitive) != 0
&& !QDate::fromString(val, Qt::ISODate).isValid()) {
err << "Bad date '" << val << "' (expected YYYY-MM-DD or 'today').\n";
return 2;
}
fields << qMakePair(key, val);
}
auto apply = [&](TitleBlockProperties &p) {
for (const auto &f : fields) {
const QString k = f.first.toLower();
const QString &v = f.second;
if (k == "title") p.title = v;
else if (k == "author") p.author = v;
else if (k == "filename") p.filename = v;
else if (k == "plant") p.plant = v;
else if (k == "location") p.locmach = v;
else if (k == "revision") p.indexrev = v;
else if (k == "version") p.version = v;
else if (k == "date") {
p.date = (v.compare("today", Qt::CaseInsensitive) == 0)
? QDate::currentDate()
: QDate::fromString(v, Qt::ISODate);
// An explicit date is only honoured when the folio is in
// "use the date value" mode (not "now"/"null").
p.useDate = TitleBlockProperties::UseDateValue;
}
else // unknown key -> custom title-block field
p.context.addValue(f.first, v);
}
};
// Project default (the template applied to new folios).
TitleBlockProperties def = project.defaultTitleBlockProperties();
apply(def);
project.setDefaultTitleBlockProperties(def);
// Every existing folio's own title block.
int folios = 0;
const QList<Diagram *> diagrams = project.diagrams();
for (Diagram *diagram : diagrams) {
TitleBlockProperties p =
diagram->border_and_titleblock.exportTitleBlock();
apply(p);
diagram->border_and_titleblock.importTitleBlock(p);
++folios;
}
const QDomDocument doc = project.toXml();
QFile file(output);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
err << "Cannot open '" << output << "' for writing.\n";
return 1;
}
QTextStream fout(&file);
fout << doc.toString(4);
file.close();
out << "Stamped " << fields.size() << " field(s) on "
<< folios << " folio(s) -> " << output << "\n";
return 0;
}
} // anonymous namespace
namespace CLIExport {
bool isExportRequest(const QStringList &args)
{
for (const QString &a : args)
if (exportFlags().contains(a))
return true;
return false;
}
int run(const QStringList &args)
{
QString flag;
QStringList rest;
for (int i = 0; i < args.size(); ++i) {
if (exportFlags().contains(args.at(i))) {
flag = args.at(i);
for (int j = i + 1; j < args.size(); ++j)
rest << args.at(j);
break;
}
}
const QString format = exportFlags().value(flag);
// --check-elements operates on an element file/directory, not a project.
if (format == "check") {
if (rest.isEmpty()) {
err << "Usage: qelectrotech --check-elements "
"<element.elmt | directory>\n";
return 2;
}
return checkElements(rest.at(0));
}
const QString input = rest.value(0);
if (input.isEmpty()) {
err << "Usage: qelectrotech " << flag << " <project.qet> <output>\n";
return 2;
}
if (!QFileInfo::exists(input)) {
err << "Project not found: " << input << "\n";
return 2;
}
QETProject project(input);
if (project.state() != QETProject::Ok) {
err << "Failed to open project: " << input
<< " (state " << project.state() << ")\n";
return 1;
}
// --info writes JSON to stdout, or to an optional output file.
if (format == "info")
return exportInfo(project, rest.value(1));
const QString output = rest.value(1);
if (output.isEmpty()) {
err << "Usage: qelectrotech " << flag
<< " <project.qet> <output>\n";
return 2;
}
if (format == "pdf")
return exportPdf(project, output);
if (format == "cables" || format == "wires")
return exportCsv(project, format, output);
if (format == "bom")
return exportBom(project, output);
if (format == "nets")
return exportNets(project, output);
if (format == "links")
return exportLinks(project, output);
if (format == "resave")
return resaveProject(project, output);
if (format == "settb")
return setTitleBlock(project, output, rest.mid(2));
return exportImages(project, format, output);
}
} // namespace CLIExport
+79
View File
@@ -0,0 +1,79 @@
/*
Copyright 2006-2025 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech 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 2 of the License, or
(at your option) any later version.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CLI_EXPORT_H
#define CLI_EXPORT_H
#include <QStringList>
/**
@brief Headless command-line export.
Implements the long-requested batch/headless export
(qelectrotech.org bugtracker #171, GitHub #309): render a project's
diagrams to files without opening the GUI.
Detected and handled in main() before the GUI is created.
*/
namespace CLIExport {
/**
@brief True if @p args request a CLI export
(i.e. contain one of the export options).
*/
bool isExportRequest(const QStringList &args);
/**
@brief Run the CLI export described by @p args.
@return process exit code (0 on success).
Usage:
qelectrotech --export-pdf <project.qet> <output.pdf>
qelectrotech --export-png <project.qet> <output_dir>
qelectrotech --export-svg <project.qet> <output_dir>
qelectrotech --export-cables <project.qet> <output.csv>
qelectrotech --export-wires <project.qet> <output.csv>
qelectrotech --export-bom <project.qet> <output.csv>
qelectrotech --export-nets <project.qet> <output.json>
qelectrotech --export-links <project.qet> <output.csv>
qelectrotech --info <project.qet> [output.json]
qelectrotech --check-elements <element.elmt | directory>
qelectrotech --resave <project.qet> <output.qet>
qelectrotech --set-titleblock <project.qet> <output.qet> key=value...
PDF: one multi-page document (one diagram per page).
PNG/SVG: one file per diagram, named <output_dir>/<NN>_<title>.<ext>.
cables: wiring list (one row per conductor) as CSV.
wires: list of distinct wire numbers as CSV.
bom: bill of materials (one row per element) as CSV.
nets: electrical nets (connected-terminal groups) as JSON.
links: element cross-references (coil/contact) as CSV, with
unresolved links flagged.
info: structural project summary as JSON (stdout, or a file) —
per-page element / conductor counts and unconnected terminals.
check-elements: validate .elmt file(s) against the element schema.
resave: load and rewrite the project XML (round-trip integrity).
set-titleblock: stamp title-block fields onto every folio, then save.
Keys: title, author, date (or date=today), plant, location,
revision, version, filename; any other key becomes a custom
field. E.g. --set-titleblock in.qet out.qet revision=B date=today
*/
int run(const QStringList &args);
}
#endif // CLI_EXPORT_H
+1 -1
View File
@@ -16,7 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "conductorautonumerotation.h"
#include "qetproject.h"
#include "QPropertyUndoCommand/qpropertyundocommand.h"
#include "autoNum/assignvariables.h"
#include "autoNum/numerotationcontextcommands.h"
+1 -1
View File
@@ -16,7 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "conductornumexport.h"
#include "qetproject.h"
#include "qetapp.h"
#include "diagram.h"
#include "diagramcontent.h"
+10 -5
View File
@@ -114,6 +114,11 @@ QSqlQuery projectDataBase::newQuery(const QString &query) {
*/
void projectDataBase::addElement(Element *element)
{
if (!element || !element->diagram()) {
qDebug() << "projectDataBase::addElement: null element or diagram";
return;
}
m_insert_elements_query.bindValue(":uuid", element->uuid().toString());
m_insert_elements_query.bindValue(":diagram_uuid", element->diagram()->uuid().toString());
m_insert_elements_query.bindValue(":pos", element->diagram()->convertPosition(element->scenePos()).toString());
@@ -253,9 +258,9 @@ bool projectDataBase::createDataBase()
return false;
}
m_data_base.exec("PRAGMA temp_store = MEMORY");
m_data_base.exec("PRAGMA journal_mode = MEMORY");
m_data_base.exec("PRAGMA synchronous = OFF");
QSqlQuery(m_data_base).exec("PRAGMA temp_store = MEMORY");
QSqlQuery(m_data_base).exec("PRAGMA journal_mode = MEMORY");
QSqlQuery(m_data_base).exec("PRAGMA synchronous = OFF");
QSqlQuery query_(m_data_base);
bool first_ = true;
@@ -383,7 +388,7 @@ void projectDataBase::createElementNomenclatureView()
"ei.supplier_auxiliary4 AS supplier_auxiliary4,"
"ei.quantity_auxiliary4 AS quantity_auxiliary4,"
"ei.unity_auxiliary4 AS unity_auxiliary4,"
"ei.exclude_from_bom AS exclude_from_bom,"
"d.pos AS diagram_position,"
"e.type AS element_type,"
@@ -392,7 +397,7 @@ void projectDataBase::createElementNomenclatureView()
"di.folio AS folio,"
"e.pos AS position "
" FROM element_info ei, diagram_info di, element e, diagram d"
" WHERE ei.element_uuid = e.uuid AND e.diagram_uuid = d.uuid AND di.diagram_uuid = d.uuid");
" WHERE ei.element_uuid = e.uuid AND e.diagram_uuid = d.uuid AND di.diagram_uuid = d.uuid AND (ei.exclude_from_bom IS NOT 'true')");
QSqlQuery query(m_data_base);
if (!query.exec(create_view)) {
+6 -1
View File
@@ -367,6 +367,11 @@ QString ElementQueryWidget::queryStr() const
where.clear();
}
QString exclude_condition = "(exclude_from_bom IS NULL OR exclude_from_bom != '1')";
filter_ += " AND " + exclude_condition;
// -------------------------------------------------------------
if (where.isEmpty() && !filter_.isEmpty()) {
filter_.remove(0, 4); //Remove the first " AND" of filter.
filter_.prepend( " WHERE");
@@ -450,7 +455,7 @@ void ElementQueryWidget::setUpItems()
{
for(QString key : QETInformation::elementInfoKeys())
{
if (key == "formula")
if (key == "formula" || key == "exclude_from_bom")
continue;
auto item = new QListWidgetItem(QETInformation::translatedInfoKey(key), ui->m_var_list);
+87 -23
View File
@@ -39,7 +39,7 @@
#include "qetxml.h"
#include "undocommand/addelementtextcommand.h"
#include "qetinformation.h"
#include "qetproject.h"
#include <cassert>
#include <math.h>
@@ -65,7 +65,6 @@ QColor Diagram::background_color = Qt::white;
Diagram::Diagram(QETProject *project) :
QGraphicsScene (project),
m_project (project),
draw_grid_ (true),
use_border_ (true),
draw_terminals_ (true),
draw_colored_conductors_ (true),
@@ -73,6 +72,11 @@ Diagram::Diagram(QETProject *project) :
m_freeze_new_elements (false),
m_freeze_new_conductors_ (false)
{
QSettings settings;
draw_grid_ = settings.value(QStringLiteral("diagrameditor/grid_display_startup"), true).toBool();
draw_guides_ = settings.value(QStringLiteral("diagrameditor/guides_display_startup"), false).toBool();
setItemIndexMethod(QGraphicsScene::NoIndex);
/* Set to no index,
* because they can be the source of the crash with conductor and shape ghost.
@@ -121,8 +125,30 @@ Diagram::Diagram(QETProject *project) :
connect(this, &Diagram::diagramActivated,
this, &Diagram::loadCndFolioSeq);
adjustSceneRect();
}
m_guides_list.clear();
if (m_project) {
for (const auto &pg : m_project->defaultGuides()) {
Diagram::Guide g;
g.orientation = static_cast<Diagram::Guide::Orientation>(pg.orientation);
g.position = pg.position;
g.color = pg.color;
m_guides_list.append(g);
}
} else {
QSettings settings;
int size = settings.beginReadArray(QStringLiteral("diagrameditor/defaultguides"));
for (int i = 0; i < size; ++i) {
settings.setArrayIndex(i);
Diagram::Guide g;
g.orientation = static_cast<Diagram::Guide::Orientation>(settings.value(QStringLiteral("orientation"), 0).toInt());
g.position = settings.value(QStringLiteral("position"), 0.0).toReal();
g.color = QColor(settings.value(QStringLiteral("color"), QStringLiteral("#ff0000")).toString());
m_guides_list.append(g);
}
settings.endArray();
}
}
/**
@brief Diagram::~Diagram
Destructor
@@ -152,7 +178,7 @@ Diagram::~Diagram()
continue;
deletable_items.append(qgi);
}
for (const auto &item : qAsConst(deletable_items))
for (const auto &item : std::as_const(deletable_items))
{
removeItem(item);
delete item;
@@ -186,6 +212,14 @@ void Diagram::drawBackground(QPainter *p, const QRectF &r) {
p -> setBrush(Diagram::background_color);
p -> drawRect(r);
QSettings settings;
QRectF rect = settings.value(
QStringLiteral("diagrameditor/zoom-out-beyond-of-folio"),
false).toBool() ? r
: border_and_titleblock
.insideBorderRect()
.intersected(r);
if (draw_grid_) {
/* Draw the points of the grid
* if background color is black,
@@ -200,19 +234,10 @@ void Diagram::drawBackground(QPainter *p, const QRectF &r) {
p -> setBrush(Qt::NoBrush);
// If user allow zoom out beyond of folio,
// we draw grid outside of border.
QSettings settings;
int xGrid = settings.value(QStringLiteral("diagrameditor/Xgrid"),
Diagram::xGrid).toInt();
int yGrid = settings.value(QStringLiteral("diagrameditor/Ygrid"),
Diagram::yGrid).toInt();
QRectF rect = settings.value(
QStringLiteral("diagrameditor/zoom-out-beyond-of-folio"),
false).toBool() ? r
: border_and_titleblock
.insideBorderRect()
.intersected(r);
qreal limit_x = rect.x() + rect.width();
qreal limit_y = rect.y() + rect.height();
@@ -252,7 +277,23 @@ void Diagram::drawBackground(QPainter *p, const QRectF &r) {
p -> drawPoints(points);
}
if (use_border_) border_and_titleblock.draw(p);
if (draw_guides_) {
for (const Diagram::Guide &guide : m_guides_list) {
QPen guidePen(guide.color, 1, Qt::DashLine);
guidePen.setCosmetic(true);
p->setPen(guidePen);
if (guide.orientation == Diagram::Guide::Vertical) {
p->drawLine(guide.position, rect.top(), guide.position, rect.bottom());
} else {
p->drawLine(rect.left(), guide.position, rect.right(), guide.position);
}
}
}
if (use_border_) {
border_and_titleblock.draw(p);
}
p -> restore();
}
@@ -1458,12 +1499,12 @@ bool Diagram::fromXml(QDomElement &document,
if (position != QPointF())
{
QVector <QGraphicsItem *> added_items;
for (auto element : qAsConst(added_elements )) added_items << element;
for (auto shape : qAsConst(added_shapes )) added_items << shape;
for (auto text : qAsConst(added_texts )) added_items << text;
for (auto image : qAsConst(added_images )) added_items << image;
for (auto table : qAsConst(added_tables )) added_items << table;
for (const auto &strip : qAsConst(added_strips)) added_items << strip;
for (auto element : std::as_const(added_elements )) added_items << element;
for (auto shape : std::as_const(added_shapes )) added_items << shape;
for (auto text : std::as_const(added_texts )) added_items << text;
for (auto image : std::as_const(added_images )) added_items << image;
for (auto table : std::as_const(added_tables )) added_items << table;
for (const auto &strip : std::as_const(added_strips)) added_items << strip;
//Get the top left corner of the rectangle that contain all added items
QRectF items_rect;
@@ -1590,11 +1631,11 @@ void Diagram::refreshContents()
conductor->refreshText();
}
for (auto &table : qAsConst(dc_.m_tables)) {
for (auto &table : std::as_const(dc_.m_tables)) {
table->initLink();
}
for (auto &strip :qAsConst(dc_.m_terminal_strip)) {
for (auto &strip :std::as_const(dc_.m_terminal_strip)) {
strip->refreshPending();
}
}
@@ -1779,7 +1820,7 @@ void Diagram::invertSelection()
item_list << item;
}
}
for (auto item : qAsConst(item_list)) {
for (auto item : std::as_const(item_list)) {
item -> setSelected(!item -> isSelected());
}
@@ -2265,6 +2306,7 @@ ExportProperties Diagram::applyProperties(
// exporte les options de rendu en cours
ExportProperties old_properties;
old_properties.draw_grid = displayGrid();
old_properties.draw_guides = displayGuides();
old_properties.draw_border = border_and_titleblock.borderIsDisplayed();
old_properties.draw_titleblock = border_and_titleblock.titleBlockIsDisplayed();
old_properties.draw_terminals = drawTerminals();
@@ -2278,6 +2320,7 @@ ExportProperties Diagram::applyProperties(
setDrawTerminals (new_properties.draw_terminals);
setDrawColoredConductors (new_properties.draw_colored_conductors);
setDisplayGrid (new_properties.draw_grid);
setDisplayGuides (new_properties.draw_guides);
border_and_titleblock.displayBorder(new_properties.draw_border);
border_and_titleblock.displayTitleBlock (new_properties.draw_titleblock);
@@ -2626,3 +2669,24 @@ void Diagram::restoreText(Element* elmt)
}
}
}
QUndoStack &Diagram::undoStack() {
return *(project()->undoStack());
}
/**
* @brief Diagram::updateProjectGuides
* Aktualisiert die internen Hilfslinien dieses Schaltplans
* basierend auf den Projekt-Einstellungen und erzwingt ein Neuzeichnen.
*/
void Diagram::updateProjectGuides(const QList<GuideProperties> &guides) {
m_guides_list.clear();
for (const GuideProperties &pg : guides) {
Diagram::Guide g;
g.orientation = static_cast<Diagram::Guide::Orientation>(pg.orientation);
g.position = pg.position;
g.color = pg.color;
m_guides_list.append(g);
}
update();
}
+26 -13
View File
@@ -24,7 +24,6 @@
#include "elementtextsmover.h"
#include "exportproperties.h"
#include "properties/xrefproperties.h"
#include "qetproject.h"
#include "qgimanager.h"
#include <QHash>
@@ -39,12 +38,11 @@ class DiagramPosition;
class DiagramTextItem;
class Element;
class ElementsLocation;
class QETProject;
class Terminal;
class DiagramImageItem;
class DiagramEventInterface;
class DiagramFolioList;
class QETProject;
struct GuideProperties;
/**
@brief The Diagram class
@@ -67,6 +65,13 @@ class Diagram : public QGraphicsScene
// ATTRIBUTES
public:
struct Guide {
enum Orientation { Horizontal, Vertical };
Orientation orientation;
qreal position;
QColor color;
};
/**
@brief The BorderOptions enum
Represents available options when rendering a particular diagram:
@@ -119,6 +124,8 @@ class Diagram : public QGraphicsScene
bool draw_grid_;
bool use_border_;
bool draw_guides_;
QList<Diagram::Guide> m_guides_list;
bool draw_terminals_;
bool draw_colored_conductors_;
@@ -142,10 +149,11 @@ class Diagram : public QGraphicsScene
void wheelEvent (QGraphicsSceneWheelEvent *event) override;
void keyPressEvent (QKeyEvent *event) override;
void keyReleaseEvent (QKeyEvent *) override;
void correctTextPos(Element* elmt);
void restoreText(Element* elmt);
public:
void correctTextPos(Element* elmt);
void restoreText(Element* elmt);
QUuid uuid();
void setEventInterface (DiagramEventInterface *event_interface);
void clearEventInterface();
@@ -206,6 +214,9 @@ class Diagram : public QGraphicsScene
ExportProperties applyProperties(const ExportProperties &);
void setDisplayGrid(bool);
bool displayGrid();
void setDisplayGuides(bool);
bool displayGuides();
void updateProjectGuides(const QList<GuideProperties> &guides);
void setUseBorder(bool);
bool useBorder();
void setBorderOptions(BorderOptions);
@@ -340,6 +351,16 @@ inline bool Diagram::displayGrid() {
return(draw_grid_);
}
inline void Diagram::setDisplayGuides(bool dg) {
if (draw_guides_ != dg) {
draw_guides_ = dg;
update();
}
}
inline bool Diagram::displayGuides() {
return(draw_guides_);
}
/**
@brief Diagram::setUseBorder
Set whether the diagram border (including rows/columns headers and the title
@@ -388,14 +409,6 @@ inline Diagram::BorderOptions Diagram::borderOptions() {
return(options);
}
/**
@brief Diagram::undoStack
@return the diagram undo stack
*/
inline QUndoStack &Diagram::undoStack() {
return *(project()->undoStack());
}
/**
@brief Diagram::qgiManager
@return the diagram graphics item manager
+4 -4
View File
@@ -55,7 +55,7 @@ DiagramContent::DiagramContent(Diagram *diagram, bool selected) :
item_list = diagram->items();
}
for (const auto &item : qAsConst(item_list))
for (const auto &item : std::as_const(item_list))
{
switch (item->type())
{
@@ -399,10 +399,10 @@ QList<QGraphicsItem *> DiagramContent::items(int filter) const
if (filter & ElementTextFields) for(auto qgi : m_element_texts) items_list << qgi;
if (filter & TextGroup) for(auto qgi : m_texts_groups) items_list << qgi;
if (filter & Tables) for(auto qgi : m_tables) items_list << qgi;
if (filter & TerminalStrip) for(const auto qgi : qAsConst(m_terminal_strip)) items_list << qgi;
if (filter & TerminalStrip) for(const auto qgi : std::as_const(m_terminal_strip)) items_list << qgi;
if (filter & SelectedOnly) {
for(const auto &qgi : qAsConst(items_list)) {
for(const auto &qgi : std::as_const(items_list)) {
if (!qgi -> isSelected()) items_list.removeOne(qgi);
}
}
@@ -428,7 +428,7 @@ int DiagramContent::count(int filter) const
if (filter & ElementTextFields) for(auto deti : m_element_texts) { if (deti -> isSelected()) ++ count; }
if (filter & TextGroup) for(auto etig : m_texts_groups) { if (etig -> isSelected()) ++ count; }
if (filter & Tables) for(auto table : m_tables) { if (table -> isSelected()) ++ count; }
if (filter & TerminalStrip) for(const auto &strip : qAsConst(m_terminal_strip)) {if (strip->isSelected()) ++ count;}
if (filter & TerminalStrip) for(const auto &strip : std::as_const(m_terminal_strip)) {if (strip->isSelected()) ++ count;}
}
else {
if (filter & Elements) count += m_elements.count();
@@ -16,7 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "diagrameventaddelement.h"
#include "../qetproject.h"
#include "../conductorautonumerotation.h"
#include "../diagram.h"
#include "../undocommand/addgraphicsobjectcommand.h"
+1 -1
View File
@@ -16,7 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "diagramview.h"
#include "qetproject.h"
#include "QPropertyUndoCommand/qpropertyundocommand.h"
#include "diagramcommands.h"
#include "diagramevent/diagrameventaddelement.h"
+52 -12
View File
@@ -31,6 +31,7 @@
#include "graphicspart/partline.h"
#include "graphicspart/partpolygon.h"
#include "graphicspart/partrectangle.h"
#include "graphicspart/partplctable.h"
#include "graphicspart/partterminal.h"
#include "graphicspart/parttext.h"
#include "ui/qetelementeditor.h"
@@ -84,12 +85,15 @@ ElementData ElementScene::elementData() {
void ElementScene::setElementData(ElementData data)
{
bool emit_ = m_element_data.m_informations != data.m_informations;
bool emit_info = (m_element_data != data);
bool type_changed = m_element_data.m_type != data.m_type;
m_element_data = data;
if (emit_)
if (emit_info)
emit elementInfoChanged();
if (type_changed)
emit elementTypeChanged();
}
/**
@@ -107,6 +111,9 @@ ElementScene::~ElementScene()
if (m_decorator)
delete m_decorator;
if (m_paste_area && !m_paste_area->scene())
delete m_paste_area;
}
/**
@@ -748,7 +755,7 @@ void ElementScene::addItems(QVector<QGraphicsItem *> items)
*/
void ElementScene::removeItems(QVector<QGraphicsItem *> items)
{
const int previous_selected_count{selectedItems().size()};
const int previous_selected_count = static_cast<int>(selectedItems().size());
//block signal to avoid multiple emit of selection changed,
//we emit this signal only once at the end of this function.
@@ -920,9 +927,41 @@ void ElementScene::slot_editProperties()
if (m_element_data != epew.editedData())
{
ElementData new_data = epew.editedData();
// Check PLC state BEFORE pushing (push calls redo which changes m_element_data)
bool old_plc = (m_element_data.m_type == ElementData::Master &&
m_element_data.m_master_type == ElementData::PLC);
bool new_plc = (new_data.m_type == ElementData::Master &&
new_data.m_master_type == ElementData::PLC);
undoStack().push(new changeElementDataCommand(this,
m_element_data,
epew.editedData()));
new_data));
if (new_plc && !old_plc) {
// Switched TO PLC: create table if not present
bool has_plc = false;
for (QGraphicsItem *item : items()) {
if (dynamic_cast<PartPlcTable *>(item)) {
has_plc = true;
break;
}
}
if (!has_plc) {
PartPlcTable *pt = new PartPlcTable(m_element_editor);
addItem(pt);
}
} else if (!new_plc && old_plc) {
// Switched FROM PLC: remove table
for (QGraphicsItem *item : items()) {
if (PartPlcTable *pt = dynamic_cast<PartPlcTable *>(item)) {
removeItem(pt);
delete pt;
break;
}
}
}
}
}
@@ -1166,15 +1205,16 @@ ElementContent ElementScene::loadContent(const QDomDocument &xml_document)
CustomElementPart *cep = nullptr;
PartDynamicTextField *pdtf = nullptr;
if (qde.tagName() == "line") cep = new PartLine (m_element_editor);
else if (qde.tagName() == "rect") cep = new PartRectangle(m_element_editor);
else if (qde.tagName() == "ellipse") cep = new PartEllipse (m_element_editor);
else if (qde.tagName() == "circle") cep = new PartEllipse (m_element_editor);
else if (qde.tagName() == "polygon") cep = new PartPolygon (m_element_editor);
else if (qde.tagName() == "terminal") cep = new PartTerminal (m_element_editor);
else if (qde.tagName() == "text") cep = new PartText (m_element_editor);
else if (qde.tagName() == "arc") cep = new PartArc (m_element_editor);
if (qde.tagName() == "line") cep = new PartLine (m_element_editor);
else if (qde.tagName() == "rect") cep = new PartRectangle (m_element_editor);
else if (qde.tagName() == "ellipse") cep = new PartEllipse (m_element_editor);
else if (qde.tagName() == "circle") cep = new PartEllipse (m_element_editor);
else if (qde.tagName() == "polygon") cep = new PartPolygon (m_element_editor);
else if (qde.tagName() == "terminal") cep = new PartTerminal (m_element_editor);
else if (qde.tagName() == "text") cep = new PartText (m_element_editor);
else if (qde.tagName() == "arc") cep = new PartArc (m_element_editor);
else if (qde.tagName() == "dynamic_text") cep = new PartDynamicTextField (m_element_editor);
else if (qde.tagName() == "plc_table") cep = new PartPlcTable (m_element_editor);
//For the input (aka the old text field) we try to convert it to the new partDynamicTextField
else if (qde.tagName() == "input") cep = pdtf = new PartDynamicTextField(m_element_editor);
else continue;
+2
View File
@@ -177,6 +177,8 @@ class ElementScene : public QGraphicsScene
/// Signal emitted when need zoomFit
void needZoomFit();
void elementInfoChanged();
/// Signal emitted when the element type changes
void elementTypeChanged();
};
Q_DECLARE_OPERATORS_FOR_FLAGS(ElementScene::ItemOptions)
@@ -20,6 +20,7 @@
#include "../../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../elementscene.h"
#include <QApplication>
#include <QRegularExpression>
/**
@@ -39,7 +40,8 @@ CustomElementGraphicPart::CustomElementGraphicPart(QETElementEditor *editor,
_lineweight(NormalWeight),
_filling(NoneFilling),
_color(BlackColor),
_antialiased(false)
_antialiased(false),
m_first_move (false)
{
setFlags(QGraphicsItem::ItemIsSelectable
| QGraphicsItem::ItemIsMovable
@@ -1325,26 +1327,24 @@ void CustomElementGraphicPart::mousePressEvent(QGraphicsSceneMouseEvent *event)
void CustomElementGraphicPart::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
//m_first_move is used to avoid an unwanted behavior
//when the properties dock widget is displayed :
//1 there is no selection
//2 the dock widget width is set to minimum
//3 select a part, the dock widget gain new widgets used to edit
//the current selected part and the width of the dock grow
//so the width of the QGraphicsView is reduced and cause a mouse move event.
//When this case occur the part is moved but they should not. This bool fix it.
if (Q_UNLIKELY(m_first_move)) {
if (m_first_move) {
// Suppress spurious move events fired when the properties dock
// widget expands on first selection of a new item type, causing
// the QGraphicsView to shrink and re-map coordinates. Screen
// coordinates are stable across viewport changes; scene coords
// are not — so use screenPos() for the threshold check.
const QPointF d = event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton);
if (d.manhattanLength() < QApplication::startDragDistance())
return;
m_first_move = false;
return;
}
if((event->buttons() & Qt::LeftButton) && (flags() & QGraphicsItem::ItemIsMovable))
{
if ((event->buttons() & Qt::LeftButton) && (flags() & QGraphicsItem::ItemIsMovable)) {
QPointF pos = event->scenePos() + (m_origin_pos - event->buttonDownScenePos(Qt::LeftButton));
event->modifiers() == Qt::ControlModifier ? setPos(pos) : setPos(elementScene()->snapToGrid(pos));
}
else
} else {
QGraphicsObject::mouseMoveEvent(event);
}
}
void CustomElementGraphicPart::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
@@ -20,6 +20,8 @@
#include "../../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../../qetapp.h"
#include "../elementscene.h"
#include "../../utils/qetutils.h"
#include <QApplication>
#include <QColor>
#include <QFont>
@@ -141,7 +143,7 @@ const QDomElement PartDynamicTextField::toXml(QDomDocument &dom_doc) const
root_element.setAttribute("y", QString::number(y));
root_element.setAttribute("z", QString::number(zValue()));
root_element.setAttribute("rotation", QString::number(QET::correctAngle(rot)));
root_element.setAttribute("font", font().toString());
root_element.setAttribute("font", QETUtils::fontToString(font()));
root_element.setAttribute("uuid", m_uuid.toString());
root_element.setAttribute("frame", m_frame? "true" : "false");
root_element.setAttribute("text_width", QString::number(m_text_width));
@@ -213,7 +215,7 @@ void PartDynamicTextField::fromXml(const QDomElement &dom_elmt) {
if (dom_elmt.hasAttribute("font")) {
QFont font_;
font_.fromString(dom_elmt.attribute("font"));
QETUtils::fontFromString(font_, dom_elmt.attribute("font"));
setFont(font_);
}
else if (dom_elmt.hasAttribute("font_size")) {
@@ -495,12 +497,16 @@ bool PartDynamicTextField::keepVisualRotation() const {
@param event
*/
void PartDynamicTextField::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
if((event -> buttons() & Qt::LeftButton) && (flags() & QGraphicsItem::ItemIsMovable)) {
QPointF pos = event -> scenePos() + (m_origin_pos - event -> buttonDownScenePos(Qt::LeftButton));
event -> modifiers() == Qt::ControlModifier ? setPos(pos) : setPos(elementScene() -> snapToGrid(pos));
}
else
if ((event->buttons() & Qt::LeftButton) && (flags() & QGraphicsItem::ItemIsMovable)) {
// Suppress spurious moves from the properties dock resizing the viewport.
const QPointF d = event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton);
if (d.manhattanLength() < QApplication::startDragDistance())
return;
QPointF pos = event->scenePos() + (m_origin_pos - event->buttonDownScenePos(Qt::LeftButton));
event->modifiers() == Qt::ControlModifier ? setPos(pos) : setPos(elementScene()->snapToGrid(pos));
} else {
QGraphicsObject::mouseMoveEvent(event);
}
}
/**
@@ -0,0 +1,696 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech 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 2 of the License, or
(at your option) any later version.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "partplctable.h"
#include "../../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../../QetGraphicsItemModeler/qetgraphicshandleritem.h"
#include "../../QetGraphicsItemModeler/qetgraphicshandlerutility.h"
#include "../../properties/elementdata.h"
#include "../elementscene.h"
#include "../editorcommands.h"
#include "../ui/qetelementeditor.h"
#include <QPen>
#include <algorithm>
/**
@brief PartPlcTable::PartPlcTable
Constructor
@param editor the QETElementEditor of this item
@param parent parent item
*/
PartPlcTable::PartPlcTable(QETElementEditor *editor, QGraphicsItem *parent) :
CustomElementGraphicPart(editor, parent)
{
}
/**
@brief PartPlcTable::~PartPlcTable
*/
PartPlcTable::~PartPlcTable()
{
removeHandler();
}
/**
@brief PartPlcTable::calculateTableSize
Calculate the table size from PLC data in the element scene.
@return the calculated size in item coordinates
*/
QSizeF PartPlcTable::calculateTableSize() const
{
if (!elementEditor() || !elementScene())
return QSizeF(100, 50);
ElementData ed = elementScene()->elementData();
if (ed.m_master_type != ElementData::PLC)
return QSizeF(100, 50);
const auto &plc_data = ed.plcMasterData();
if (plc_data.ios.isEmpty())
return QSizeF(100, 50);
const int COL_COUNT = 5;
// Build list of visible columns
QList<int> visible_cols;
if (!plc_data.columnOrder.isEmpty()) {
for (int logical : plc_data.columnOrder) {
if (logical >= 0 && logical < COL_COUNT
&& plc_data.colVisible.value(logical, true)
&& !visible_cols.contains(logical))
visible_cols.append(logical);
}
for (int i = 0; i < COL_COUNT; ++i) {
if (plc_data.colVisible.value(i, true) && !visible_cols.contains(i))
visible_cols.append(i);
}
} else {
for (int i = 0; i < COL_COUNT; ++i) {
if (plc_data.colVisible.value(i, true))
visible_cols.append(i);
}
}
if (visible_cols.isEmpty())
visible_cols << 0 << 1 << 2; // fallback: Type, Address, Function
// Default column widths
QMap<int, qreal> col_widths;
for (int col : visible_cols) {
if (plc_data.colWidths.contains(col) && plc_data.colWidths[col] > 0)
col_widths[col] = plc_data.colWidths[col];
else {
switch (col) {
case 0: col_widths[col] = 35; break; // Type
case 1: col_widths[col] = 25; break; // Address
case 2: col_widths[col] = 50; break; // Function
case 3: col_widths[col] = 40; break; // Comment
case 5: col_widths[col] = 30; break; // CrossRef
default: col_widths[col] = 30; break;
}
}
}
qreal row_h = plc_data.rowHeight > 0 ? plc_data.rowHeight : 8.0;
qreal header_h = plc_data.showHeaders ? (row_h + 2.0) : 0;
qreal total_width = 0;
for (int col : visible_cols)
total_width += col_widths[col];
int total_ios = plc_data.ios.size();
// Collect active break positions (sorted)
QList<int> breaks;
for (int bp : plc_data.breakPositions) {
if (bp > 0 && bp < total_ios && !breaks.contains(bp))
breaks.append(bp);
}
std::sort(breaks.begin(), breaks.end());
// Build block boundaries
QList<int> block_starts;
block_starts.append(0);
for (int bp : breaks)
block_starts.append(bp);
qreal total_height;
int block_count = block_starts.size();
if (block_count > 1) {
// Find the tallest block (most rows)
int max_rows = 0;
for (int b = 0; b < block_count; ++b) {
int start = block_starts.at(b);
int end = (b + 1 < block_starts.size()) ? block_starts.at(b + 1) : total_ios;
max_rows = qMax(max_rows, end - start);
}
total_height = header_h + max_rows * row_h;
total_width = total_width * block_count + (block_count - 1) * 3;
} else {
total_height = header_h + total_ios * row_h;
}
return QSizeF(total_width, total_height);
}
/**
@brief PartPlcTable::paint
Draw this PLC table
@param painter
@param options
@param widget
*/
void PartPlcTable::paint(QPainter *painter, const QStyleOptionGraphicsItem *options, QWidget *widget)
{
Q_UNUSED(widget);
Q_UNUSED(options);
// Auto-size from PLC data
QSizeF table_size = calculateTableSize();
if (m_rect.size() != table_size) {
prepareGeometryChange();
QPointF top_left = m_rect.topLeft();
m_rect = QRectF(top_left, table_size);
}
applyStylesToQPainter(*painter);
QPen t = painter->pen();
t.setCosmetic(options && options->levelOfDetailFromTransform(painter->worldTransform()) < 1.0);
if (isSelected())
t.setColor(Qt::red);
t.setJoinStyle(Qt::MiterJoin);
if (!m_rect.width() || !m_rect.height())
t.setWidth(0);
painter->setPen(t);
// Get PLC data
ElementData ed = (elementEditor() && elementScene()) ? elementScene()->elementData() : ElementData();
if (ed.m_master_type != ElementData::PLC) {
// Draw placeholder
painter->setBrush(QColor(255, 255, 200));
painter->drawRect(m_rect);
painter->drawText(m_rect, Qt::AlignCenter, QObject::tr("Table PLC"));
return;
}
const auto &plc_data = ed.plcMasterData();
if (plc_data.ios.isEmpty()) {
painter->setBrush(QColor(255, 255, 200));
painter->drawRect(m_rect);
painter->drawText(m_rect, Qt::AlignCenter, QObject::tr("Table PLC (vide)"));
return;
}
const int COL_TYPE = 0;
const int COL_ADDRESS = 1;
const int COL_FUNCTION = 2;
const int COL_COMMENT = 3;
const int COL_CROSSREF = 4;
const int COL_COUNT = 5;
QMap<int, QString> headers;
headers[COL_TYPE] = QObject::tr("Type");
headers[COL_ADDRESS] = QObject::tr("Adresse");
headers[COL_FUNCTION] = QObject::tr("Fonction");
headers[COL_COMMENT] = QObject::tr("Commentaire");
headers[COL_CROSSREF] = QObject::tr("Réf. croisée");
// Override with custom column names if set
if (!plc_data.columnNames.isEmpty()) {
QList<int> all_cols;
all_cols << COL_TYPE << COL_ADDRESS << COL_FUNCTION << COL_COMMENT << COL_CROSSREF;
for (int i = 0; i < qMin(plc_data.columnNames.size(), all_cols.size()); ++i) {
if (!plc_data.columnNames.at(i).isEmpty())
headers[all_cols.at(i)] = plc_data.columnNames.at(i);
}
}
QList<int> visible_cols;
if (!plc_data.columnOrder.isEmpty()) {
for (int logical : plc_data.columnOrder) {
if (logical >= 0 && logical < COL_COUNT
&& plc_data.colVisible.value(logical, true)
&& !visible_cols.contains(logical))
visible_cols.append(logical);
}
for (int i = 0; i < COL_COUNT; ++i) {
if (plc_data.colVisible.value(i, true) && !visible_cols.contains(i))
visible_cols.append(i);
}
} else {
for (int i = 0; i < COL_COUNT; ++i) {
if (plc_data.colVisible.value(i, true))
visible_cols.append(i);
}
}
if (visible_cols.isEmpty())
return;
QMap<int, qreal> col_widths;
for (int col : visible_cols) {
if (plc_data.colWidths.contains(col) && plc_data.colWidths[col] > 0)
col_widths[col] = plc_data.colWidths[col];
else {
switch (col) {
case COL_TYPE: col_widths[col] = 35; break;
case COL_ADDRESS: col_widths[col] = 25; break;
case COL_FUNCTION: col_widths[col] = 50; break;
case COL_COMMENT: col_widths[col] = 40; break;
case COL_CROSSREF: col_widths[col] = 30; break;
default: col_widths[col] = 30; break;
}
}
}
qreal row_h = plc_data.rowHeight > 0 ? plc_data.rowHeight : 8.0;
qreal header_h = plc_data.showHeaders ? (row_h + 2.0) : 0;
int total_ios = plc_data.ios.size();
// Collect active break positions (sorted)
QList<int> breaks;
for (int bp : plc_data.breakPositions) {
if (bp > 0 && bp < total_ios && !breaks.contains(bp))
breaks.append(bp);
}
std::sort(breaks.begin(), breaks.end());
// Build block boundaries: start, break1, break2, ..., end
QList<int> block_starts;
block_starts.append(0);
for (int bp : breaks)
block_starts.append(bp);
int block_count = block_starts.size();
// Draw background
painter->save();
painter->setPen(Qt::NoPen);
painter->setBrush(Qt::white);
painter->drawRect(m_rect);
painter->restore();
// Draw outer border
QPen border_pen(Qt::black, 0.5);
painter->setPen(border_pen);
painter->setBrush(Qt::NoBrush);
painter->drawRect(m_rect);
// Draw each block
for (int block = 0; block < block_count; ++block) {
qreal block_w = 0;
for (int col : visible_cols)
block_w += col_widths[col];
qreal block_x = m_rect.x() + block * (block_w + 3);
qreal cx = block_x;
// Draw column headers
QFont header_font = plc_data.headerFont.family().isEmpty()
? painter->font() : plc_data.headerFont;
header_font.setBold(true);
painter->setFont(header_font);
for (int col : visible_cols) {
QRectF header_rect(cx, m_rect.y(), col_widths[col], header_h);
painter->fillRect(header_rect, QColor(220, 220, 220));
painter->setPen(border_pen);
painter->drawRect(header_rect);
QString header_text = headers.value(col, QString());
painter->drawText(header_rect, Qt::AlignCenter, header_text);
cx += col_widths[col];
}
// Draw IO rows
QFont cell_font = plc_data.cellFont.family().isEmpty()
? painter->font() : plc_data.cellFont;
painter->setFont(cell_font);
int start_idx = block_starts.at(block);
int end_idx = (block + 1 < block_starts.size())
? block_starts.at(block + 1) : total_ios;
for (int row = 0; row < (end_idx - start_idx); ++row) {
int io_idx = start_idx + row;
const ElementData::PlcIO &io = plc_data.ios.at(io_idx);
qreal ry = m_rect.y() + header_h + row * row_h;
cx = block_x;
for (int col : visible_cols) {
QRectF cell_rect(cx, ry, col_widths[col], row_h);
painter->setPen(border_pen);
painter->drawRect(cell_rect);
QString cell_text;
switch (col) {
case COL_TYPE: cell_text = ElementData::translatedPlcIOType(io.type); break;
case COL_ADDRESS: cell_text = io.address; break;
case COL_FUNCTION: cell_text = io.functionText; break;
case COL_COMMENT: cell_text = io.comment; break;
case COL_CROSSREF: cell_text = io.crossRef; break;
}
QRectF text_rect = cell_rect.adjusted(1, 0, -1, 0);
painter->drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, cell_text);
cx += col_widths[col];
}
}
}
if (m_hovered)
drawShadowShape(painter);
if (isSelected())
drawCross(m_rect.center(), painter);
}
/**
@brief PartPlcTable::toXml
Export this PLC table part in xml
@param xml_document : Xml document to use for create the xml element.
@return an xml element that describe this part
*/
const QDomElement PartPlcTable::toXml(QDomDocument &xml_document) const
{
QDomElement xml_element = xml_document.createElement("plc_table");
qreal x = qRound(m_rect.x() * 100.0) / 100.0;
qreal y = qRound(m_rect.y() * 100.0) / 100.0;
xml_element.setAttribute("x", QString::number(x));
xml_element.setAttribute("y", QString::number(y));
stylesToXml(xml_element);
return xml_element;
}
/**
@brief PartPlcTable::fromXml
Import the properties of this PLC table part from a xml element.
@param qde : Xml document to use.
*/
void PartPlcTable::fromXml(const QDomElement &qde)
{
stylesFromXml(qde);
qreal x = qde.attribute("x", "0").toDouble();
qreal y = qde.attribute("y", "0").toDouble();
setPos(mapFromScene(x, y));
// Auto-size from PLC data
QSizeF table_size = calculateTableSize();
prepareGeometryChange();
m_rect = QRectF(QPointF(0, 0), table_size);
update();
}
/**
@brief PartPlcTable::rect
@return : Returns the item's rectangle.
*/
QRectF PartPlcTable::rect() const
{
return m_rect;
}
/**
@brief PartPlcTable::setRect
Sets the item's rectangle to be the given rectangle.
@param rect
*/
void PartPlcTable::setRect(const QRectF &rect)
{
if (rect == m_rect) return;
prepareGeometryChange();
m_rect = rect;
adjustHandlerPos();
update();
}
/**
@brief PartPlcTable::sceneGeometricRect
@return the minimum, margin-less rectangle this part can fit into, in scene
coordinates.
*/
QRectF PartPlcTable::sceneGeometricRect() const
{
return(mapToScene(m_rect).boundingRect());
}
/**
@brief PartPlcTable::shape
@return the shape of this item
*/
QPainterPath PartPlcTable::shape() const
{
QPainterPath fill;
fill.addRect(m_rect);
QPainterPath stroke;
stroke.addRect(m_rect);
QPainterPathStroker pps;
pps.setWidth(m_hovered? penWeight()+SHADOWS_HEIGHT : penWeight());
stroke = pps.createStroke(stroke);
return fill.united(stroke);
}
QPainterPath PartPlcTable::shadowShape() const
{
QPainterPath shape;
shape.addRect(m_rect);
QPainterPathStroker pps;
pps.setWidth(penWeight());
return (pps.createStroke(shape));
}
/**
@brief PartPlcTable::boundingRect
@return Bounding rectangle this part can fit into
*/
QRectF PartPlcTable::boundingRect() const
{
qreal adjust = (SHADOWS_HEIGHT + penWeight()) / 2;
if (penWeight() == 0) adjust += 0.5;
QRectF r = m_rect.normalized();
r.adjust(-adjust, -adjust, adjust, adjust);
return(r);
}
/**
@brief PartPlcTable::isUseless
@return true if this part is irrelevant and does not deserve to be saved.
A PLC table part is always relevant.
*/
bool PartPlcTable::isUseless() const
{
return false;
}
/**
@brief PartPlcTable::startUserTransformation
@param initial_selection_rect
*/
void PartPlcTable::startUserTransformation(const QRectF &initial_selection_rect)
{
Q_UNUSED(initial_selection_rect)
saved_points_.clear();
saved_points_ << mapToScene(m_rect.topLeft()) << mapToScene(m_rect.bottomRight());
}
/**
@brief PartPlcTable::handleUserTransformation
@param initial_selection_rect
@param new_selection_rect
*/
void PartPlcTable::handleUserTransformation(const QRectF &initial_selection_rect, const QRectF &new_selection_rect)
{
QList<QPointF> mapped_points = mapPoints(initial_selection_rect, new_selection_rect, saved_points_);
setRect(QRectF(mapFromScene(mapped_points.at(0)), mapFromScene(mapped_points.at(1))));
}
/**
@brief PartPlcTable::mouseReleaseEvent
*/
void PartPlcTable::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
CustomElementGraphicPart::mouseReleaseEvent(event);
}
/**
@brief PartPlcTable::itemChange
@param change
@param value
@return
*/
QVariant PartPlcTable::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == ItemPositionHasChanged)
{
adjustHandlerPos();
}
else if (change == ItemSceneChange)
{
setSelected(false);
}
else if (change == ItemSceneHasChanged)
{
if (ElementScene *es = elementScene()) {
connect(es, &ElementScene::elementInfoChanged,
this, [this]() {
QSizeF table_size = calculateTableSize();
if (m_rect.size() != table_size) {
QPointF top_left = m_rect.topLeft();
setRect(QRectF(top_left, table_size));
}
update();
});
}
}
return QGraphicsItem::itemChange(change, value);
}
/**
@brief PartPlcTable::sceneEventFilter
@param watched
@param event
@return
*/
bool PartPlcTable::sceneEventFilter(QGraphicsItem *watched, QEvent *event)
{
if(watched->type() == QetGraphicsHandlerItem::Type)
{
QetGraphicsHandlerItem *qghi = qgraphicsitem_cast<QetGraphicsHandlerItem *>(watched);
if(m_handler_vector.contains(qghi))
{
m_vector_index = m_handler_vector.indexOf(qghi);
if (m_vector_index != -1)
{
if(event->type() == QEvent::GraphicsSceneMousePress)
{
handlerMousePressEvent(qghi, static_cast<QGraphicsSceneMouseEvent *>(event));
return true;
}
else if(event->type() == QEvent::GraphicsSceneMouseMove)
{
handlerMouseMoveEvent(qghi, static_cast<QGraphicsSceneMouseEvent *>(event));
return true;
}
else if (event->type() == QEvent::GraphicsSceneMouseRelease)
{
handlerMouseReleaseEvent(qghi, static_cast<QGraphicsSceneMouseEvent *>(event));
return true;
}
}
}
}
return false;
}
/**
@brief PartPlcTable::switchResizeMode
*/
void PartPlcTable::switchResizeMode()
{
if (m_resize_mode == 1)
{
m_resize_mode = 2;
for (QetGraphicsHandlerItem *qghi : m_handler_vector)
qghi->setColor(Qt::darkGreen);
}
else
{
m_resize_mode = 1;
qDeleteAll(m_handler_vector);
m_handler_vector.clear();
addHandler();
for (QetGraphicsHandlerItem *qghi : m_handler_vector) {
qghi->setColor(Qt::blue);
}
}
}
/**
@brief PartPlcTable::adjustHandlerPos
*/
void PartPlcTable::adjustHandlerPos()
{
if (m_handler_vector.isEmpty())
return;
QVector<QPointF> points_vector = QetGraphicsHandlerUtility::pointsForRect(m_rect);
if (m_handler_vector.size() == points_vector.size())
{
points_vector = mapToScene(points_vector);
for (int i = 0 ; i < points_vector.size() ; ++i)
m_handler_vector.at(i)->setPos(points_vector.at(i));
}
else
{
qDeleteAll(m_handler_vector);
m_handler_vector.clear();
addHandler();
}
}
void PartPlcTable::handlerMousePressEvent(QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event)
{
Q_UNUSED(qghi)
Q_UNUSED(event)
m_old_rect = m_rect;
}
void PartPlcTable::handlerMouseMoveEvent(QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event)
{
Q_UNUSED(qghi)
QPointF new_pos = event->scenePos();
if (event->modifiers() != Qt::ControlModifier)
new_pos = elementScene()->snapToGrid(event->scenePos());
new_pos = mapFromScene(new_pos);
setRect(QetGraphicsHandlerUtility::rectForPosAtIndex(m_rect, new_pos, m_vector_index));
adjustHandlerPos();
}
void PartPlcTable::handlerMouseReleaseEvent(QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event)
{
Q_UNUSED(qghi)
Q_UNUSED(event)
QUndoCommand *undo = new QUndoCommand("Modifier une table PLC");
if (m_old_rect != m_rect) {
QPropertyUndoCommand *u = new QPropertyUndoCommand(this, "rect", QVariant(m_old_rect.normalized()), QVariant(m_rect.normalized()), undo);
u->setAnimated(true, false);
}
elementScene()->undoStack().push(undo);
m_vector_index = -1;
}
/**
@brief PartPlcTable::addHandler
Do not add resize handlers - table size is data-driven.
Move is handled by the base class QGraphicsItem drag behavior.
*/
void PartPlcTable::addHandler()
{
// No resize handlers - size comes from PLC data
}
/**
@brief PartPlcTable::removeHandler
Remove the handlers of this item
*/
void PartPlcTable::removeHandler()
{
if (!m_handler_vector.isEmpty())
{
qDeleteAll(m_handler_vector);
m_handler_vector.clear();
}
}
@@ -0,0 +1,89 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech 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 2 of the License, or
(at your option) any later version.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef PARTPLCTABLE_H
#define PARTPLCTABLE_H
#include "customelementgraphicpart.h"
#include "../../QetGraphicsItemModeler/qetgraphicshandleritem.h"
#include <QVector>
/**
@brief The PartPlcTable class
This class represents a PLC I/O table preview in the element editor.
It shows the user where the PLC table will appear at runtime, so they
can position other element parts (rectangles, terminals, etc.) around it.
The actual PLC data is read from ElementScene::elementData().
*/
class PartPlcTable : public CustomElementGraphicPart
{
Q_OBJECT
Q_PROPERTY(QRectF rect READ rect WRITE setRect)
public:
PartPlcTable(QETElementEditor *editor, QGraphicsItem *parent = nullptr);
~PartPlcTable() override;
enum { Type = UserType + 1120 };
int type () const override { return Type; }
void paint (QPainter *, const QStyleOptionGraphicsItem *, QWidget * = nullptr) override;
QString name () const override { return QObject::tr("table PLC", "element part name"); }
QString xmlName () const override { return QString("plc_table"); }
const QDomElement toXml (QDomDocument &) const override;
void fromXml (const QDomElement &) override;
QRectF rect() const;
void setRect(const QRectF &rect);
QRectF sceneGeometricRect() const override;
QPainterPath shape () const override;
QPainterPath shadowShape() const override;
QRectF boundingRect() const override;
bool isUseless() const override;
void startUserTransformation(const QRectF &) override;
void handleUserTransformation(const QRectF &, const QRectF &) override;
void addHandler() override;
void removeHandler() override;
protected:
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
bool sceneEventFilter(QGraphicsItem *watched, QEvent *event) override;
private:
void switchResizeMode();
void adjustHandlerPos();
void handlerMousePressEvent (QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event);
void handlerMouseMoveEvent (QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event);
void handlerMouseReleaseEvent (QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event);
QSizeF calculateTableSize() const;
private:
QRectF m_rect,
m_old_rect;
QList<QPointF> saved_points_;
int m_resize_mode = 1,
m_vector_index = -1;
QVector<QetGraphicsHandlerItem *> m_handler_vector;
};
#endif // PARTPLCTABLE_H
+245 -1
View File
@@ -17,6 +17,8 @@
*/
#include "partterminal.h"
#include "../elementscene.h"
#include "../../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../../qetgraphicsitem/terminal.h"
/**
@@ -100,6 +102,60 @@ void PartTerminal::paint(
if (m_hovered)
drawShadowShape(painter);
if (d->m_show_name && !d->m_name.isEmpty()) {
painter->save();
painter->setFont(d->m_label_font);
painter->setPen(d->m_label_color);
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setRenderHint(QPainter::TextAntialiasing, true);
QPointF label_pos = d->m_label_pos;
QRectF text_rect;
QFontMetrics fm(d->m_label_font);
QSizeF text_size = fm.size(Qt::TextSingleLine, d->m_name);
auto compute_rect = [&]() {
qreal dx = 0, dy = 0;
if (d->m_label_halignment & Qt::AlignLeft) dx = 0;
else if (d->m_label_halignment & Qt::AlignHCenter) dx = -text_size.width() / 2.0;
else if (d->m_label_halignment & Qt::AlignRight) dx = -text_size.width();
if (d->m_label_valignment & Qt::AlignTop) dy = 0;
else if (d->m_label_valignment & Qt::AlignVCenter) dy = -text_size.height() / 2.0;
else if (d->m_label_valignment & Qt::AlignBottom) dy = -text_size.height();
return QRectF(label_pos + QPointF(dx, dy), text_size);
};
if (d->m_label_rotation != 0.0) {
painter->translate(label_pos);
painter->rotate(d->m_label_rotation);
text_rect = QRectF(-text_size.width()/2.0, -text_size.height()/2.0,
text_size.width(), text_size.height());
if (d->m_label_frame) {
painter->drawRect(text_rect.adjusted(-1, -1, 1, 1));
}
painter->drawText(text_rect, static_cast<int>(d->m_label_halignment | d->m_label_valignment), d->m_name);
} else {
text_rect = compute_rect();
if (d->m_label_frame) {
painter->drawRect(text_rect.adjusted(-1, -1, 1, 1));
}
painter->drawText(text_rect, static_cast<int>(Qt::AlignLeft | Qt::AlignTop), d->m_name);
}
if (isSelected() || m_hovered) {
QPen outline_pen(Qt::darkBlue, 0, Qt::DashLine);
painter->setPen(outline_pen);
painter->setBrush(Qt::NoBrush);
if (d->m_label_rotation != 0.0) {
painter->drawRect(text_rect.adjusted(-1, -1, 1, 1));
} else {
painter->drawRect(text_rect.adjusted(-1, -1, 1, 1));
}
}
painter->restore();
}
}
/**
@@ -114,7 +170,27 @@ QPainterPath PartTerminal::shape() const
QPainterPathStroker pps;
pps.setWidth(1);
return (pps.createStroke(shape));
QPainterPath path = pps.createStroke(shape);
if (d->m_show_name && !d->m_name.isEmpty()) {
path.addRect(labelRect());
}
return path;
}
/**
@brief PartTerminal::shadowShape
@return the hover outline shape (terminal line only, no label rect)
*/
QPainterPath PartTerminal::shadowShape() const
{
QPainterPath shape;
shape.lineTo(d -> m_second_point);
QPainterPathStroker pps;
pps.setWidth(1);
return pps.createStroke(shape);
}
/**
@@ -128,9 +204,40 @@ QRectF PartTerminal::boundingRect() const
qreal adjust = (SHADOWS_HEIGHT + 1) / 2;
br.adjust(-adjust, -adjust, adjust, adjust);
if (d->m_show_name && !d->m_name.isEmpty()) {
br = br.united(labelRect());
}
return(br);
}
/**
@brief PartTerminal::labelRect
@return the rectangle of the label text (in item coordinates),
or an empty rect if the label is not shown
*/
QRectF PartTerminal::labelRect() const
{
if (!d->m_show_name || d->m_name.isEmpty())
return QRectF();
QFontMetrics fm(d->m_label_font);
QSizeF text_size = fm.size(Qt::TextSingleLine, d->m_name);
QPointF label_pos = d->m_label_pos;
qreal dx = 0, dy = 0;
if (d->m_label_halignment & Qt::AlignLeft) dx = 0;
else if (d->m_label_halignment & Qt::AlignHCenter) dx = -text_size.width() / 2.0;
else if (d->m_label_halignment & Qt::AlignRight) dx = -text_size.width();
if (d->m_label_valignment & Qt::AlignTop) dy = 0;
else if (d->m_label_valignment & Qt::AlignVCenter) dy = -text_size.height() / 2.0;
else if (d->m_label_valignment & Qt::AlignBottom) dy = -text_size.height();
return QRectF(label_pos + QPointF(dx, dy), text_size).adjusted(-3, -3, 3, 3);
}
/**
Definit l'orientation de la borne
@@ -282,6 +389,93 @@ void PartTerminal::setNewUuid()
d -> m_uuid = QUuid::createUuid();
}
void PartTerminal::setShowName(bool show)
{
if (d->m_show_name == show) return;
prepareGeometryChange();
d->m_show_name = show;
update();
emit showNameChanged();
}
void PartTerminal::setLabelPos(QPointF pos)
{
if (d->m_label_pos == pos) return;
prepareGeometryChange();
d->m_label_pos = pos;
update();
emit labelPosChanged();
}
void PartTerminal::setLabelFont(QFont font)
{
if (d->m_label_font == font) return;
prepareGeometryChange();
d->m_label_font = font;
update();
emit labelFontChanged();
}
void PartTerminal::setLabelRotation(qreal rotation)
{
if (qFuzzyCompare(d->m_label_rotation, rotation)) return;
prepareGeometryChange();
d->m_label_rotation = rotation;
update();
emit labelRotationChanged();
}
void PartTerminal::setLabelHAlignment(Qt::Alignment align)
{
if (d->m_label_halignment == align) return;
prepareGeometryChange();
d->m_label_halignment = align;
update();
emit labelHAlignmentChanged();
}
void PartTerminal::setLabelVAlignment(Qt::Alignment align)
{
if (d->m_label_valignment == align) return;
prepareGeometryChange();
d->m_label_valignment = align;
update();
emit labelVAlignmentChanged();
}
void PartTerminal::setLabelFrame(bool frame)
{
if (d->m_label_frame == frame) return;
prepareGeometryChange();
d->m_label_frame = frame;
update();
emit labelFrameChanged();
}
void PartTerminal::setLabelColor(QColor color)
{
if (d->m_label_color == color) return;
d->m_label_color = color;
update();
emit labelColorChanged();
}
void PartTerminal::setUseMasterLabel(bool use)
{
if (d->m_use_master_label == use) return;
d->m_use_master_label = use;
update();
emit useMasterLabelChanged();
}
void PartTerminal::setMasterLabelIndex(int index)
{
if (d->m_master_label_index == index) return;
d->m_master_label_index = index;
update();
emit masterLabelIndexChanged();
}
/**
Updates the position of the second point according to the position
and orientation of the terminal.
@@ -338,3 +532,53 @@ void PartTerminal::handleUserTransformation(const QRectF &initial_selection_rect
initial_selection_rect, new_selection_rect, QList<QPointF>() << saved_position_).first();
setPos(mapped_point);
}
void PartTerminal::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() == Qt::LeftButton && d->m_show_name && !d->m_name.isEmpty()) {
QPointF local_click = event->pos();
// Only start label drag when click is on label AND NOT on terminal line
if (!shadowShape().contains(local_click) && labelRect().contains(local_click)) {
m_dragging_label = true;
m_original_label_pos = d->m_label_pos;
}
}
CustomElementGraphicPart::mousePressEvent(event);
}
void PartTerminal::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (m_dragging_label) {
QPointF delta = event->pos() - event->buttonDownPos(Qt::LeftButton);
QPointF new_pos = m_original_label_pos + delta;
if (!(event->modifiers() & Qt::ControlModifier)) {
ElementScene *scene = elementScene();
if (scene) {
QPointF scene_pos = mapToScene(new_pos);
new_pos = mapFromScene(scene->snapToGrid(scene_pos));
}
}
setLabelPos(new_pos);
update();
event->accept();
return;
}
CustomElementGraphicPart::mouseMoveEvent(event);
}
void PartTerminal::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (m_dragging_label) {
m_dragging_label = false;
if (m_original_label_pos != d->m_label_pos) {
auto undo = new QPropertyUndoCommand(this, "label_pos",
QVariant(m_original_label_pos), QVariant(d->m_label_pos));
undo->setText(tr("Déplacer le label d'une borne"));
undo->enableAnimation();
elementScene()->undoStack().push(undo);
}
event->accept();
return;
}
CustomElementGraphicPart::mouseReleaseEvent(event);
}
+60 -1
View File
@@ -34,6 +34,16 @@ class PartTerminal : public CustomElementGraphicPart
Q_PROPERTY(Qet::Orientation orientation READ orientation WRITE setOrientation)
Q_PROPERTY(QString terminal_name READ terminalName WRITE setTerminalName)
Q_PROPERTY(TerminalData::Type terminal_type READ terminalType WRITE setTerminalType)
Q_PROPERTY(bool show_name READ showName WRITE setShowName)
Q_PROPERTY(QPointF label_pos READ labelPos WRITE setLabelPos)
Q_PROPERTY(QFont label_font READ labelFont WRITE setLabelFont)
Q_PROPERTY(qreal label_rotation READ labelRotation WRITE setLabelRotation)
Q_PROPERTY(Qt::Alignment label_halignment READ labelHAlignment WRITE setLabelHAlignment)
Q_PROPERTY(Qt::Alignment label_valignment READ labelVAlignment WRITE setLabelVAlignment)
Q_PROPERTY(bool label_frame READ labelFrame WRITE setLabelFrame)
Q_PROPERTY(QColor label_color READ labelColor WRITE setLabelColor)
Q_PROPERTY(bool use_master_label READ useMasterLabel WRITE setUseMasterLabel)
Q_PROPERTY(int master_label_index READ masterLabelIndex WRITE setMasterLabelIndex)
public:
// constructors, destructor
@@ -46,6 +56,16 @@ class PartTerminal : public CustomElementGraphicPart
void orientationChanged();
void nameChanged();
void terminalTypeChanged();
void showNameChanged();
void labelPosChanged();
void labelFontChanged();
void labelRotationChanged();
void labelHAlignmentChanged();
void labelVAlignmentChanged();
void labelFrameChanged();
void labelColorChanged();
void useMasterLabelChanged();
void masterLabelIndexChanged();
// methods
public:
@@ -64,7 +84,7 @@ class PartTerminal : public CustomElementGraphicPart
QWidget *) override;
QPainterPath shape() const override;
QPainterPath shadowShape() const override {return shape();}
QPainterPath shadowShape() const override;
QRectF boundingRect() const override;
bool isUseless() const override;
QRectF sceneGeometricRect() const override;
@@ -90,13 +110,52 @@ class PartTerminal : public CustomElementGraphicPart
TerminalData::Type terminalType() const {return d->m_type;}
void setTerminalType(TerminalData::Type type);
bool showName() const { return d->m_show_name; }
void setShowName(bool show);
QPointF labelPos() const { return d->m_label_pos; }
void setLabelPos(QPointF pos);
QFont labelFont() const { return d->m_label_font; }
void setLabelFont(QFont font);
qreal labelRotation() const { return d->m_label_rotation; }
void setLabelRotation(qreal rotation);
Qt::Alignment labelHAlignment() const { return d->m_label_halignment; }
void setLabelHAlignment(Qt::Alignment align);
Qt::Alignment labelVAlignment() const { return d->m_label_valignment; }
void setLabelVAlignment(Qt::Alignment align);
bool labelFrame() const { return d->m_label_frame; }
void setLabelFrame(bool frame);
QColor labelColor() const { return d->m_label_color; }
void setLabelColor(QColor color);
bool useMasterLabel() const { return d->m_use_master_label; }
void setUseMasterLabel(bool use);
int masterLabelIndex() const { return d->m_master_label_index; }
void setMasterLabelIndex(int index);
void setNewUuid();
QRectF labelRect() const;
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
private:
void updateSecondPoint();
TerminalData* d; // pointer to the terminal data
private:
QPointF saved_position_;
bool m_dragging_label = false;
QPointF m_original_label_pos;
};
#endif
+149 -8
View File
@@ -18,10 +18,12 @@
#include "parttext.h"
#include "../../QPropertyUndoCommand/qpropertyundocommand.h"
#include <QApplication>
#include "../../qetapp.h"
#include "../elementprimitivedecorator.h"
#include "../elementscene.h"
#include "../ui/texteditor.h"
#include "../../utils/qetutils.h"
/**
Constructeur
@@ -124,12 +126,26 @@ void PartText::fromXml(const QDomElement &xml_element) {
}
else if (xml_element.hasAttribute("font")) {
QFont font_;
font_.fromString(xml_element.attribute("font"));
QETUtils::fontFromString(font_, xml_element.attribute("font"));
setFont(font_);
}
setDefaultTextColor(QColor(xml_element.attribute("color", "#000000")));
setPlainText(xml_element.attribute("text"));
// Optional alignment (absent = historical behaviour: top-left anchor,
// left-aligned lines), same attributes as the dynamic text fields.
Qt::Alignment alignment_ = Qt::AlignTop | Qt::AlignLeft;
QMetaEnum me = QMetaEnum::fromType<Qt::Alignment>();
if (xml_element.hasAttribute("Halignment"))
alignment_ = Qt::Alignment(
me.keyToValue(xml_element.attribute("Halignment").toStdString().data()));
if (xml_element.hasAttribute("Valignment"))
alignment_ = Qt::Alignment(
me.keyToValue(xml_element.attribute("Valignment").toStdString().data()))
| (alignment_ & Qt::AlignHorizontal_Mask);
setAlignment(alignment_);
setPos(xml_element.attribute("x").toDouble(),
xml_element.attribute("y").toDouble());
QGraphicsObject::setRotation(QET::correctAngle(xml_element.attribute("rotation", QString::number(0)).toDouble()));
@@ -150,10 +166,22 @@ const QDomElement PartText::toXml(QDomDocument &xml_document) const
xml_element.setAttribute("x", QString::number(x));
xml_element.setAttribute("y", QString::number(y));
xml_element.setAttribute("text", toPlainText());
xml_element.setAttribute("font", font().toString());
xml_element.setAttribute("font", QETUtils::fontToString(font()));
xml_element.setAttribute("rotation", QString::number(rot));
xml_element.setAttribute("color", defaultTextColor().name());
// Only written when different from the historical behaviour, so
// existing .elmt files round-trip byte-identical.
QMetaEnum me = QMetaEnum::fromType<Qt::Alignment>();
if (m_alignment & Qt::AlignRight)
xml_element.setAttribute("Halignment", me.valueToKey(Qt::AlignRight));
else if (m_alignment & Qt::AlignHCenter)
xml_element.setAttribute("Halignment", me.valueToKey(Qt::AlignHCenter));
if (m_alignment & Qt::AlignBottom)
xml_element.setAttribute("Valignment", me.valueToKey(Qt::AlignBottom));
else if (m_alignment & Qt::AlignVCenter)
xml_element.setAttribute("Valignment", me.valueToKey(Qt::AlignVCenter));
return(xml_element);
}
@@ -305,24 +333,126 @@ void PartText::setDefaultTextColor(const QColor &color) {
void PartText::setPlainText(const QString &text) {
if (text != this -> toPlainText()) {
prepareAlignment();
QGraphicsTextItem::setPlainText(text);
applyLineAlignment();
finishAlignment();
emit plainTextChanged(text);
}
}
/**
@brief PartText::setAlignment
Set how this text is anchored to its position: when the content later
changes, the given corner/center of the bounding rect keeps its place
(the historical behaviour, and the default, is top-left). The
horizontal part also aligns the lines of a multi-line text relative
to each other. Changing the alignment never moves the text itself.
@param alignment
*/
void PartText::setAlignment(const Qt::Alignment &alignment)
{
if (alignment == m_alignment)
return;
m_alignment = alignment;
applyLineAlignment();
emit alignmentChanged(m_alignment);
}
/**
@brief PartText::applyLineAlignment
Align the lines of a multi-line text relative to each other according
to the horizontal part of the alignment property. QGraphicsTextItem
only honors the document text option when a text width is set, hence
the idealWidth() dance; -1 restores the free (historical) layout.
*/
void PartText::applyLineAlignment()
{
QTextOption option = document()->defaultTextOption();
option.setAlignment(m_alignment & Qt::AlignHorizontal_Mask);
document()->setDefaultTextOption(option);
setTextWidth(-1);
if (m_alignment & (Qt::AlignHCenter | Qt::AlignRight))
setTextWidth(document()->idealWidth());
}
/**
@brief PartText::prepareAlignment
Call before a change of the bounding rect (see finishAlignment).
*/
void PartText::prepareAlignment()
{
m_alignment_rect = boundingRect();
}
/**
@brief PartText::finishAlignment
Call after a change of the bounding rect: moves the text so that the
point selected by the alignment property stays where it was (same
logic as DiagramTextItem::finishAlignment).
*/
void PartText::finishAlignment()
{
QTransform transform;
transform.rotate(rotation());
qreal x, xa, y, ya;
x = xa = 0;
y = ya = 0;
if (m_alignment & Qt::AlignRight)
{
x = m_alignment_rect.right();
xa = boundingRect().right();
}
else if (m_alignment & Qt::AlignHCenter)
{
x = m_alignment_rect.center().x();
xa = boundingRect().center().x();
}
if (m_alignment & Qt::AlignBottom)
{
y = m_alignment_rect.bottom();
ya = boundingRect().bottom();
}
else if (m_alignment & Qt::AlignVCenter)
{
y = m_alignment_rect.center().y();
ya = boundingRect().center().y();
}
QPointF p = transform.map(QPointF(x, y));
QPointF pa = transform.map(QPointF(xa, ya));
setPos(pos() - (pa - p));
}
void PartText::setFont(const QFont &font) {
if (font != this -> font()) {
prepareAlignment();
QGraphicsTextItem::setFont(font);
applyLineAlignment();
finishAlignment();
// Re-anchor: the item's position transform is -margin(), and margin()
// depends on the font ascent. Without re-running this on a font change,
// the transform keeps the previous font's ascent — so the text renders
// at a different spot after save/reopen (the position recomputes from
// the saved font on load). See #158.
adjustItemPosition();
emit fontChanged(font);
}
}
void PartText::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
if((event -> buttons() & Qt::LeftButton) && (flags() & QGraphicsItem::ItemIsMovable)) {
QPointF pos = event -> scenePos() + (m_origin_pos - event -> buttonDownScenePos(Qt::LeftButton));
event -> modifiers() == Qt::ControlModifier ? setPos(pos) : setPos(elementScene() -> snapToGrid(pos));
}
else {
if ((event->buttons() & Qt::LeftButton) && (flags() & QGraphicsItem::ItemIsMovable)) {
// Suppress spurious moves from the properties dock resizing the viewport.
const QPointF d = event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton);
if (d.manhattanLength() < QApplication::startDragDistance())
return;
QPointF pos = event->scenePos() + (m_origin_pos - event->buttonDownScenePos(Qt::LeftButton));
event->modifiers() == Qt::ControlModifier ? setPos(pos) : setPos(elementScene()->snapToGrid(pos));
} else {
QGraphicsObject::mouseMoveEvent(event);
}
}
@@ -387,6 +517,11 @@ void PartText::startEdition()
{
// !previous_text.isNull() means the text is being edited
previous_text = toPlainText();
// Anchor the aligned point across the whole inline edition; free the
// text width so typing is not wrapped at the previous block width.
prepareAlignment();
setTextWidth(-1);
}
/**
@@ -395,7 +530,8 @@ void PartText::startEdition()
*/
void PartText::endEdition()
{
if (!previous_text.isNull()) {
const bool was_editing = !previous_text.isNull();
if (was_editing) {
// the text was being edited
QString new_text = toPlainText();
if (previous_text != new_text) {
@@ -412,4 +548,9 @@ void PartText::endEdition()
setTextCursor(qtc);
setEditable(false);
if (was_editing) {
applyLineAlignment();
finishAlignment();
}
}
+9
View File
@@ -34,11 +34,13 @@ class PartText : public QGraphicsTextItem, public CustomElementPart {
Q_PROPERTY(QColor color READ defaultTextColor WRITE setDefaultTextColor NOTIFY colorChanged)
Q_PROPERTY(QString text READ toPlainText WRITE setPlainText NOTIFY plainTextChanged)
Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged)
Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged)
signals:
void fontChanged(const QFont &font);
void colorChanged(const QColor &color);
void plainTextChanged(const QString &text);
void alignmentChanged(Qt::Alignment alignment);
// constructors, destructor
public:
@@ -77,6 +79,8 @@ class PartText : public QGraphicsTextItem, public CustomElementPart {
void setDefaultTextColor(const QColor &color);
void setPlainText(const QString &text);
void setFont(const QFont &font);
void setAlignment(const Qt::Alignment &alignment);
Qt::Alignment alignment() const {return m_alignment;}
public slots:
void adjustItemPosition(int = 0);
@@ -97,11 +101,16 @@ class PartText : public QGraphicsTextItem, public CustomElementPart {
private:
QPointF margin() const;
void applyLineAlignment();
void prepareAlignment();
void finishAlignment();
QString previous_text;
qreal real_font_size_;
QPointF saved_point_;
qreal saved_font_size_;
QGraphicsItem *decorator_;
QPointF m_origin_pos;
Qt::Alignment m_alignment = (Qt::AlignTop | Qt::AlignLeft);
QRectF m_alignment_rect;
};
#endif
+1 -1
View File
@@ -386,7 +386,7 @@ StyleEditor::StyleEditor(QETElementEditor *editor, CustomElementGraphicPart *p,
outline_color->setSizeAdjustPolicy(QComboBox::AdjustToContents);
filling_color->setSizeAdjustPolicy(QComboBox::AdjustToContents);
auto grid_layout = new QGridLayout(this);
auto grid_layout = new QGridLayout();
grid_layout->addWidget(new QLabel(tr("Contour :")), 0,0, Qt::AlignRight);
grid_layout->addWidget(outline_color, 0, 1);
grid_layout->addWidget(new QLabel(tr("Remplissage :")), 1, 0, Qt::AlignRight);
+41 -3
View File
@@ -163,7 +163,7 @@ void DynamicTextFieldEditor::updateForm()
}
}
on_m_text_from_cb_activated(ui -> m_text_from_cb -> currentIndex()); //For enable the good widget
updateTextFromWidgetsEnabled(ui -> m_text_from_cb -> currentIndex()); //For enable the good widget
}
}
@@ -197,6 +197,10 @@ void DynamicTextFieldEditor::setUpConnections()
m_connection_list << connect(m_text_field.data(), &PartDynamicTextField::textWidthChanged, this, [=](){this -> updateForm();});
m_connection_list << connect(m_text_field.data(), &PartDynamicTextField::compositeTextChanged,this, [=](){this -> updateForm();});
m_connection_list << connect(m_text_field.data(), &PartDynamicTextField::keepVisualRotationChanged, this, [=](){this -> updateForm();});
// Refresh info combo when element data changes (e.g. type switched to PLC-Slave)
m_connection_list << connect(elementEditor()->elementScene(), &ElementScene::elementInfoChanged,
this, &DynamicTextFieldEditor::fillInfoComboBox);
}
void DynamicTextFieldEditor::disconnectConnections()
@@ -218,13 +222,34 @@ void DynamicTextFieldEditor::fillInfoComboBox()
ui -> m_elmt_info_cb -> clear();
QStringList strl;
auto type = elementEditor()->elementScene()->elementData().m_type;
auto ed = elementEditor()->elementScene()->elementData();
auto type = ed.m_type;
if((type & ElementData::AllReport) || (type == ElementData::ConductorDefinition)) {
strl = QETInformation::folioReportInfoKeys();
}
else {
strl = QETInformation::elementInfoKeys();
bool is_plc_slave = (type == ElementData::Slave
&& ed.m_slave_type == ElementData::PLCSlave);
if (is_plc_slave) {
QStringList plc_keys = {
QETInformation::ELMT_PLC_TYPE,
QETInformation::ELMT_PLC_ADDRESS,
QETInformation::ELMT_PLC_FUNCTION,
QETInformation::ELMT_PLC_COMMENT,
QETInformation::ELMT_PLC_CROSSREF
};
strl = plc_keys + strl;
} else {
strl.removeAll(QETInformation::ELMT_PLC_TYPE);
strl.removeAll(QETInformation::ELMT_PLC_ADDRESS);
strl.removeAll(QETInformation::ELMT_PLC_FUNCTION);
strl.removeAll(QETInformation::ELMT_PLC_COMMENT);
strl.removeAll(QETInformation::ELMT_PLC_CROSSREF);
}
}
for (int i=0; i<strl.size();++i) {
@@ -327,7 +352,16 @@ void DynamicTextFieldEditor::on_m_elmt_info_cb_activated(const QString &arg1) {
}
}
void DynamicTextFieldEditor::on_m_text_from_cb_activated(int index) {
/**
@brief DynamicTextFieldEditor::updateTextFromWidgetsEnabled
Enable the widget matching @p index (the "text from" combo box's current
index) and disable the other two. Purely cosmetic: called both from the
real user-activated slot below and from updateForm() when the form is
(re)filled for a part/selection, so it must never touch m_parts's data —
see on_m_text_from_cb_activated() for the part-mutating counterpart.
*/
void DynamicTextFieldEditor::updateTextFromWidgetsEnabled(int index)
{
ui -> m_user_text_le -> setDisabled(true);
ui -> m_elmt_info_cb -> setDisabled(true);
ui -> m_composite_text_pb -> setDisabled(true);
@@ -341,6 +375,10 @@ void DynamicTextFieldEditor::on_m_text_from_cb_activated(int index) {
else {
ui->m_composite_text_pb->setEnabled(true);
}
}
void DynamicTextFieldEditor::on_m_text_from_cb_activated(int index) {
updateTextFromWidgetsEnabled(index);
DynamicElementTextItem::TextFrom tf;
if(index == 0) {
@@ -52,6 +52,7 @@ class DynamicTextFieldEditor : public ElementItemEditor {
void fillInfoComboBox();
void setUpConnections();
void disconnectConnections();
void updateTextFromWidgetsEnabled(int index);
private slots:
void on_m_x_sb_editingFinished();
File diff suppressed because it is too large Load Diff
@@ -24,6 +24,13 @@
#include <QAbstractButton>
#include <QDialog>
class QTableWidget;
class QSpinBox;
class QCheckBox;
class QGroupBox;
class QPushButton;
class QLineEdit;
namespace Ui {
class ElementPropertiesEditorWidget;
}
@@ -49,16 +56,45 @@ class ElementPropertiesEditorWidget : public QDialog
void setUpInterface();
void updateTree();
void populateTree();
void populateSlaveGroupsTable();
void readSlaveGroupsFromTable();
void createPlcConfigWidgets();
void populatePlcTable();
void readPlcTable();
//SLOTS
private slots:
void on_m_buttonBox_accepted();
void on_m_base_type_cb_currentIndexChanged(int index);
void on_m_slave_groups_checkbox_toggled(bool checked);
void on_max_slaves_checkbox_toggled(bool checked);
void plcAddRow();
void plcRemoveRow();
void plcPasteFromClipboard();
void plcTerminalCountChanged(int row, int count);
void plcSelectHeaderFont();
void plcSelectCellFont();
//ATTRIBUTES
private:
Ui::ElementPropertiesEditorWidget *ui;
ElementData m_data;
// PLC configuration widgets (created programmatically)
QGroupBox *m_plc_gb = nullptr;
QTableWidget *m_plc_table = nullptr;
QTableWidget *m_plc_terminal_table = nullptr;
QCheckBox *m_plc_break_checkboxes[4] = {nullptr, nullptr, nullptr, nullptr};
QSpinBox *m_plc_break_spinboxes[4] = {nullptr, nullptr, nullptr, nullptr};
QSpinBox *m_plc_row_height_spinbox = nullptr;
QPushButton *m_plc_header_font_btn = nullptr;
QPushButton *m_plc_cell_font_btn = nullptr;
QCheckBox *m_plc_show_headers_cb = nullptr;
QFont m_plc_header_font;
QFont m_plc_cell_font;
QList<QCheckBox *> m_plc_col_visibility_checkboxes;
QList<QSpinBox *> m_plc_col_width_spinboxes;
QList<QLineEdit *> m_plc_col_name_edits;
};
#endif // ELEMENTPROPERTIESEDITORWIDGET_H
@@ -93,17 +93,17 @@
<property name="title">
<string>Élément maître</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Type concret</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="m_master_type_cb"/>
</item>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Type concret</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="m_master_type_cb"/>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="max_slaves_checkbox">
<property name="text">
@@ -121,6 +121,55 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="m_slave_groups_checkbox">
<property name="text">
<string>Définir les éléments esclave</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QTableWidget" name="m_slave_groups_table">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>150</height>
</size>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="columnCount">
<number>4</number>
</property>
<column>
<property name="text">
<string>Type</string>
</property>
</column>
<column>
<property name="text">
<string>Contact</string>
</property>
</column>
<column>
<property name="text">
<string>Nb. contacts</string>
</property>
</column>
<column>
<property name="text">
<string>Nb. bornes</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</item>
+51 -47
View File
@@ -480,15 +480,11 @@ void QETElementEditor::fillPartsList()
}
}
QListWidgetItem *qlwi = new QListWidgetItem(part_desc);
QVariant v;
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove
v.setValue<QGraphicsItem *>(qgi);
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 6 or later")
#endif
qDebug()<<"Help code for QT 6 or later";
#endif
// Qt declares the QGraphicsItem* metatype itself, so this
// works on Qt 5 and Qt 6 alike. Without the stored pointer
// the parts list loses its item association and selecting
// a part no longer selects it on the canvas.
QVariant v = QVariant::fromValue(qgi);
qlwi -> setData(42, v);
m_parts_list -> addItem(qlwi);
qlwi -> setSelected(qgi -> isSelected());
@@ -736,11 +732,13 @@ bool QETElementEditor::checkElement()
QList<QETWarning> warnings;
QList<QETWarning> errors;
// Warning #1: Element haven't got terminal
// Warning #1: Element does not have (enough) terminals
// (except for report and conductor definition, because they must have one terminal and this checking is done below)
// (another exception: "thumbnails" aka "front-views" may/should not have terminals)
if (!m_elmt_scene -> containsTerminals() &&
!(m_elmt_scene->elementData().m_type & ElementData::AllReport) &&
m_elmt_scene->elementData().m_type != ElementData::ConductorDefinition) {
m_elmt_scene->elementData().m_type != ElementData::ConductorDefinition &&
m_elmt_scene->elementData().m_type != ElementData::Thumbnail) {
warnings << qMakePair(
tr("Absence de borne", "warning title"),
tr(
@@ -749,50 +747,50 @@ bool QETElementEditor::checkElement()
"warning description"
)
);
}
}
// Check folio report element
if (m_elmt_scene->elementData().m_type & ElementData::AllReport)
{
int terminal =0;
// Check folio report element
if (m_elmt_scene->elementData().m_type & ElementData::AllReport)
{
int terminal =0;
for(auto qgi : m_elmt_scene -> items()) {
if (qgraphicsitem_cast<PartTerminal *>(qgi)) {
terminal ++;
}
}
//Error folio report must have only one terminal
if (terminal != 1) {
errors << qMakePair (tr("Absence de borne"),
tr("<br><b>Erreur</b> :"
"<br>Les reports de folio doivent posséder une seul borne."
"<br><b>Solution</b> :"
"<br>Verifier que l'élément ne possède qu'une seul borne"));
for(auto qgi : m_elmt_scene -> items()) {
if (qgraphicsitem_cast<PartTerminal *>(qgi)) {
terminal ++;
}
}
// Check conductor definition element
if (m_elmt_scene->elementData().m_type == ElementData::ConductorDefinition)
{
int terminal =0;
//Error folio report must have only one terminal
if (terminal != 1) {
errors << qMakePair (tr("Absence de borne"),
tr("<br><b>Erreur</b> :"
"<br>Les reports de folio doivent posséder une seul borne."
"<br><b>Solution</b> :"
"<br>Verifier que l'élément ne possède qu'une seul borne"));
}
}
for(auto qgi : m_elmt_scene -> items()) {
if (qgraphicsitem_cast<PartTerminal *>(qgi)) {
terminal ++;
}
}
// Check conductor definition element
if (m_elmt_scene->elementData().m_type == ElementData::ConductorDefinition)
{
int terminal =0;
// Error: Conductor definition must have exactly one terminal
if (terminal != 1) {
errors << qMakePair (tr("Nombre de bornes incorrect"),
tr("<br><b>Erreur</b> :"
"<br>Les définitions de conducteur ne peuvent posséder qu'une seule borne."
"<br><b>Solution</b> :"
"<br>Vérifier que l'élément ne possède qu'une seule borne"));
for(auto qgi : m_elmt_scene -> items()) {
if (qgraphicsitem_cast<PartTerminal *>(qgi)) {
terminal ++;
}
}
// Error: Conductor definition must have exactly one terminal
if (terminal != 1) {
errors << qMakePair (tr("Nombre de bornes incorrect"),
tr("<br><b>Erreur</b> :"
"<br>Les définitions de conducteur ne peuvent posséder qu'une seule borne."
"<br><b>Solution</b> :"
"<br>Vérifier que l'élément ne possède qu'une seule borne"));
}
}
if (!errors.count() && !warnings.count()) {
return(true);
}
@@ -1091,7 +1089,7 @@ void QETElementEditor::updateAction()
<< ui->m_revert_selection_action
<< ui->m_paste_from_file_action
<< ui->m_paste_from_element_action;
for (auto action : qAsConst(ro_list)) {
for (auto action : std::as_const(ro_list)) {
action->setDisabled(m_read_only);
}
@@ -1106,7 +1104,7 @@ void QETElementEditor::updateAction()
<< ui->m_flip_action
<< ui->m_mirror_action;
auto items_selected = !m_read_only && m_elmt_scene->selectedItems().count();
for (auto action : qAsConst(select_list)) {
for (auto action : std::as_const(select_list)) {
action->setEnabled(items_selected);
}
@@ -1190,6 +1188,12 @@ void QETElementEditor::initGui()
updateInformations();
fillPartsList();
// When the element type changes, update the terminal editor master label visibility
connect(m_elmt_scene, &ElementScene::elementTypeChanged, this, [this]() {
auto *te = static_cast<TerminalEditor *>(m_editors["terminal"]);
if (te) te->refreshMasterLabelVisibility();
});
statusBar()->showMessage(tr("Éditeur d'éléments", "status bar message"));
}
+333 -1
View File
@@ -21,6 +21,12 @@
#include "../../qet.h"
#include "../graphicspart/partterminal.h"
#include "../../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../../ui/alignmenttextdialog.h"
#include <QColorDialog>
#include <QFontDialog>
#include "../elementscene.h"
#include "qetelementeditor.h"
/**
* @brief TerminalEditor::TerminalEditor
@@ -33,6 +39,22 @@ TerminalEditor::TerminalEditor(QETElementEditor *editor, QWidget *parent) :
ui(new Ui::TerminalEditor)
{
ui->setupUi(this);
#ifdef BUILD_WITHOUT_KF5
m_color_pb = new QPushButton(this);
m_color_pb->setMinimumSize(40, 24);
connect(m_color_pb, &QPushButton::clicked, this, &TerminalEditor::labelColorClicked);
#else
m_color_pb = new KColorButton(this);
m_color_pb->setMinimumSize(40, 24);
connect(m_color_pb, &KColorButton::changed, this, &TerminalEditor::labelColorClicked);
#endif
QLayout *layout = ui->m_color_widget->parentWidget()->layout();
layout->replaceWidget(ui->m_color_widget, m_color_pb);
delete ui->m_color_widget;
ui->m_color_widget = nullptr;
init();
}
@@ -63,6 +85,44 @@ void TerminalEditor::updateForm()
ui->m_name_le->setText(m_part->terminalName());
ui->m_type_cb->setCurrentIndex(ui->m_type_cb->findData(m_part->terminalType()));
ui->m_show_name_cb->setChecked(m_part->showName());
ui->m_label_x_dsb->setValue(m_part->labelPos().x());
ui->m_label_y_dsb->setValue(m_part->labelPos().y());
ui->m_font_pb->setText(m_part->labelFont().family());
ui->m_label_size_sb->setValue(m_part->labelFont().pointSize());
ui->m_label_rotation_sb->setValue(static_cast<int>(m_part->labelRotation()));
ui->m_label_frame_cb->setChecked(m_part->labelFrame());
#ifdef BUILD_WITHOUT_KF5
QPixmap px(16, 16);
px.fill(m_part->labelColor());
m_color_pb->setIcon(QIcon(px));
#else
m_color_pb->setColor(m_part->labelColor());
#endif
ui->m_text_props_gb->setEnabled(m_part->showName());
// Update master label fields
bool is_slave = updateMasterLabelVisibility();
if (is_slave) {
PartTerminal *pt = m_part;
if (pt) {
ui->m_use_master_label_cb->setChecked(pt->useMasterLabel());
ui->m_master_label_cb->setEnabled(pt->useMasterLabel());
ui->m_name_le->setEnabled(!pt->useMasterLabel());
int idx = ui->m_master_label_cb->findData(pt->masterLabelIndex());
if (idx >= 0) {
ui->m_master_label_cb->setCurrentIndex(idx);
}
// Show T-label in name field when master label is active
if (pt->useMasterLabel()) {
int label_idx = pt->masterLabelIndex();
ui->m_name_le->setText(tr("T%1").arg(label_idx + 1));
}
}
}
activeConnections(true);
}
@@ -125,6 +185,16 @@ void TerminalEditor::init()
ui->m_type_cb->addItem(tr("NO (contact SW)"), TerminalData::No);
ui->m_type_cb->addItem(tr("NC (contact SW)"), TerminalData::Nc);
ui->m_type_cb->addItem(tr("Commun (contact SW)"), TerminalData::Common);
ui->m_text_props_gb->setEnabled(false);
// Populate master label dropdown (T1-T20)
for (int i = 1; i <= 20; ++i) {
ui->m_master_label_cb->addItem(tr("T%1").arg(i), i - 1);
}
// Check if parent element is a Slave to show/hide master label group
updateMasterLabelVisibility();
}
/**
@@ -218,6 +288,146 @@ void TerminalEditor::typeEdited()
* and method of this class.
* @param active
*/
void TerminalEditor::showNameEdited()
{
if (m_locked) return;
m_locked = true;
bool show = ui->m_show_name_cb->isChecked();
if (m_part->showName() != show) {
auto undo = new QPropertyUndoCommand(m_part, "show_name", m_part->showName(), show);
undo->setText(tr("Afficher/cacher le nom du terminal"));
undoStack().push(undo);
}
ui->m_text_props_gb->setEnabled(show);
m_locked = false;
}
void TerminalEditor::labelPosEdited()
{
if (m_locked) return;
m_locked = true;
QPointF new_pos(ui->m_label_x_dsb->value(), ui->m_label_y_dsb->value());
if (m_part->labelPos() != new_pos) {
auto undo = new QPropertyUndoCommand(m_part, "label_pos", m_part->labelPos(), new_pos);
undo->setText(tr("Modifier la position du label"));
undoStack().push(undo);
}
m_locked = false;
}
void TerminalEditor::labelFontClicked()
{
if (m_locked) return;
m_locked = true;
bool ok;
QFont font = QFontDialog::getFont(&ok, m_part->labelFont(), this);
if (ok && font != m_part->labelFont()) {
ui->m_font_pb->setText(font.family());
ui->m_label_size_sb->blockSignals(true);
ui->m_label_size_sb->setValue(font.pointSize());
ui->m_label_size_sb->blockSignals(false);
auto undo = new QPropertyUndoCommand(m_part, "label_font", m_part->labelFont(), font);
undo->setText(tr("Modifier la police du label"));
undoStack().push(undo);
}
m_locked = false;
}
void TerminalEditor::labelSizeEdited()
{
if (m_locked) return;
m_locked = true;
QFont new_font = m_part->labelFont();
new_font.setPointSize(ui->m_label_size_sb->value());
if (m_part->labelFont() != new_font) {
auto undo = new QPropertyUndoCommand(m_part, "label_font", m_part->labelFont(), new_font);
undo->setText(tr("Modifier la taille de police du label"));
undoStack().push(undo);
}
m_locked = false;
}
void TerminalEditor::labelRotationEdited()
{
if (m_locked) return;
m_locked = true;
qreal rot = static_cast<qreal>(ui->m_label_rotation_sb->value());
if (!qFuzzyCompare(m_part->labelRotation(), rot)) {
auto undo = new QPropertyUndoCommand(m_part, "label_rotation", m_part->labelRotation(), rot);
undo->setText(tr("Modifier la rotation du label"));
undoStack().push(undo);
}
m_locked = false;
}
void TerminalEditor::labelAlignClicked()
{
Qt::Alignment align = m_part->labelHAlignment() | m_part->labelVAlignment();
AlignmentTextDialog dialog(align, this);
if (dialog.exec() == QDialog::Accepted) {
Qt::Alignment new_align = dialog.alignment();
Qt::Alignment new_h = new_align & Qt::AlignHorizontal_Mask;
Qt::Alignment new_v = new_align & Qt::AlignVertical_Mask;
if (new_h != m_part->labelHAlignment()) {
auto undo = new QPropertyUndoCommand(m_part, "label_halignment",
QVariant::fromValue(m_part->labelHAlignment()), QVariant::fromValue(new_h));
undo->setText(tr("Modifier l'alignement du label"));
undoStack().push(undo);
}
if (new_v != m_part->labelVAlignment()) {
auto undo = new QPropertyUndoCommand(m_part, "label_valignment",
QVariant::fromValue(m_part->labelVAlignment()), QVariant::fromValue(new_v));
undo->setText(tr("Modifier l'alignement du label"));
undoStack().push(undo);
}
}
}
void TerminalEditor::labelFrameEdited()
{
if (m_locked) return;
m_locked = true;
bool frame = ui->m_label_frame_cb->isChecked();
if (m_part->labelFrame() != frame) {
auto undo = new QPropertyUndoCommand(m_part, "label_frame", m_part->labelFrame(), frame);
undo->setText(tr("Afficher/cacher le cadre du label"));
undoStack().push(undo);
}
m_locked = false;
}
void TerminalEditor::labelColorClicked()
{
if (m_locked) return;
m_locked = true;
#ifdef BUILD_WITHOUT_KF5
QColor new_color = QColorDialog::getColor(m_part->labelColor(), this);
if (new_color.isValid() && m_part->labelColor() != new_color) {
auto undo = new QPropertyUndoCommand(m_part, "label_color", m_part->labelColor(), new_color);
undo->setText(tr("Modifier la couleur du label"));
undoStack().push(undo);
}
#else
QColor new_color = m_color_pb->color();
if (new_color.isValid() && m_part->labelColor() != new_color) {
auto undo = new QPropertyUndoCommand(m_part, "label_color", m_part->labelColor(), new_color);
undo->setText(tr("Modifier la couleur du label"));
undoStack().push(undo);
}
#endif
m_locked = false;
}
void TerminalEditor::activeConnections(bool active)
{
if (active) {
@@ -231,8 +441,28 @@ void TerminalEditor::activeConnections(bool active)
this, &TerminalEditor::nameEdited);
m_editor_connections << connect(ui->m_type_cb, QOverload<int>::of(&QComboBox::activated),
this, &TerminalEditor::typeEdited);
m_editor_connections << connect(ui->m_show_name_cb, &QCheckBox::toggled,
this, &TerminalEditor::showNameEdited);
m_editor_connections << connect(ui->m_label_x_dsb, QOverload<qreal>::of(&QDoubleSpinBox::valueChanged),
[this]() { TerminalEditor::labelPosEdited(); ui->m_label_x_dsb->setFocus(); });
m_editor_connections << connect(ui->m_label_y_dsb, QOverload<qreal>::of(&QDoubleSpinBox::valueChanged),
[this]() { TerminalEditor::labelPosEdited(); ui->m_label_y_dsb->setFocus(); });
m_editor_connections << connect(ui->m_font_pb, &QPushButton::clicked,
this, &TerminalEditor::labelFontClicked);
m_editor_connections << connect(ui->m_label_size_sb, QOverload<int>::of(&QSpinBox::valueChanged),
[this]() { TerminalEditor::labelSizeEdited(); ui->m_label_size_sb->setFocus(); });
m_editor_connections << connect(ui->m_label_rotation_sb, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
[this]() { TerminalEditor::labelRotationEdited(); ui->m_label_rotation_sb->setFocus(); });
m_editor_connections << connect(ui->m_align_pb, &QPushButton::clicked,
this, &TerminalEditor::labelAlignClicked);
m_editor_connections << connect(ui->m_label_frame_cb, &QCheckBox::toggled,
this, &TerminalEditor::labelFrameEdited);
m_editor_connections << connect(ui->m_use_master_label_cb, &QCheckBox::toggled,
this, &TerminalEditor::useMasterLabelEdited);
m_editor_connections << connect(ui->m_master_label_cb, QOverload<int>::of(&QComboBox::activated),
this, &TerminalEditor::masterLabelIndexEdited);
} else {
for (auto const & con : qAsConst(m_editor_connections)) {
for (auto const & con : std::as_const(m_editor_connections)) {
QObject::disconnect(con);
}
m_editor_connections.clear();
@@ -248,6 +478,16 @@ void TerminalEditor::activeChangeConnections(bool active)
m_change_connections << connect(m_part, &PartTerminal::orientationChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::nameChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::terminalTypeChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::showNameChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::labelPosChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::labelFontChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::labelRotationChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::labelHAlignmentChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::labelVAlignmentChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::labelFrameChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::labelColorChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::useMasterLabelChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::masterLabelIndexChanged, this, &TerminalEditor::updateForm);
} else {
for (auto &con : m_change_connections) {
QObject::disconnect(con);
@@ -255,3 +495,95 @@ void TerminalEditor::activeChangeConnections(bool active)
m_change_connections.clear();
}
}
void TerminalEditor::useMasterLabelEdited()
{
if (m_locked) return;
m_locked = true;
bool use = ui->m_use_master_label_cb->isChecked();
ui->m_master_label_cb->setEnabled(use);
QSignalBlocker name_blocker(ui->m_name_le);
if (m_part->useMasterLabel() != use) {
auto undo = new QPropertyUndoCommand(m_part, "use_master_label",
m_part->useMasterLabel(), use);
undo->setText(tr("Modifier l'étiquette du maître"));
undoStack().push(undo);
}
if (use) {
int idx = ui->m_master_label_cb->currentData().toInt();
QString t_label = tr("T%1").arg(idx + 1);
ui->m_name_le->setText(t_label);
ui->m_name_le->setEnabled(false);
if (m_part->terminalName() != t_label) {
auto undo = new QPropertyUndoCommand(m_part, "terminal_name",
m_part->terminalName(), t_label);
undo->setText(tr("Modifier le nom de la borne"));
undoStack().push(undo);
}
} else {
ui->m_name_le->setEnabled(true);
if (!m_part->terminalName().isEmpty()) {
auto undo = new QPropertyUndoCommand(m_part, "terminal_name",
m_part->terminalName(), QString());
undo->setText(tr("Modifier le nom de la borne"));
undoStack().push(undo);
}
ui->m_name_le->clear();
}
m_locked = false;
}
void TerminalEditor::masterLabelIndexEdited()
{
if (m_locked) return;
m_locked = true;
int idx = ui->m_master_label_cb->currentData().toInt();
if (m_part->masterLabelIndex() != idx) {
auto undo = new QPropertyUndoCommand(m_part, "master_label_index",
m_part->masterLabelIndex(), idx);
undo->setText(tr("Modifier l'index de l'étiquette du maître"));
undoStack().push(undo);
}
if (ui->m_use_master_label_cb->isChecked()) {
QString t_label = tr("T%1").arg(idx + 1);
ui->m_name_le->setText(t_label);
if (m_part->terminalName() != t_label) {
auto undo = new QPropertyUndoCommand(m_part, "terminal_name",
m_part->terminalName(), t_label);
undo->setText(tr("Modifier le nom de la borne"));
undoStack().push(undo);
}
}
m_locked = false;
}
bool TerminalEditor::updateMasterLabelVisibility()
{
QETElementEditor *editor = elementEditor();
if (!editor || !editor->elementScene()) {
ui->m_master_label_gb->setVisible(false);
return false;
}
ElementData data = editor->elementScene()->elementData();
bool is_slave = (data.m_type == ElementData::Slave);
ui->m_master_label_gb->setVisible(is_slave);
return is_slave;
}
void TerminalEditor::refreshMasterLabelVisibility()
{
updateMasterLabelVisibility();
if (m_part) {
updateForm();
}
}
+26 -2
View File
@@ -21,6 +21,12 @@
#include <QWidget>
#include "../elementitemeditor.h"
#ifdef BUILD_WITHOUT_KF5
#include <QPushButton>
#else
#include <KColorButton>
#endif
namespace Ui {
class TerminalEditor;
}
@@ -43,6 +49,8 @@ class TerminalEditor : public ElementItemEditor
bool setPart(CustomElementPart *new_part) override;
CustomElementPart *currentPart() const override;
QList<CustomElementPart *> currentParts() const override {return QList<CustomElementPart *>();}
public slots:
void refreshMasterLabelVisibility();
private:
void init();
@@ -50,8 +58,19 @@ class TerminalEditor : public ElementItemEditor
void orientationEdited();
void nameEdited();
void typeEdited();
void activeConnections(bool active);
void activeChangeConnections(bool active);
void showNameEdited();
void labelPosEdited();
void labelFontClicked();
void labelSizeEdited();
void labelRotationEdited();
void labelAlignClicked();
void labelFrameEdited();
void labelColorClicked();
void activeConnections(bool active);
void activeChangeConnections(bool active);
void useMasterLabelEdited();
void masterLabelIndexEdited();
bool updateMasterLabelVisibility();
private:
Ui::TerminalEditor *ui;
@@ -59,6 +78,11 @@ class TerminalEditor : public ElementItemEditor
m_change_connections;
PartTerminal *m_part = nullptr;
bool m_locked = false;
#ifdef BUILD_WITHOUT_KF5
QPushButton *m_color_pb;
#else
KColorButton *m_color_pb;
#endif
};
#endif // TERMINALEDITOR_H
+160 -11
View File
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>511</width>
<height>236</height>
<height>500</height>
</rect>
</property>
<property name="windowTitle">
@@ -78,18 +78,167 @@
<item row="3" column="1">
<widget class="QComboBox" name="m_type_cb"/>
</item>
<item row="5" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<item row="5" column="0" colspan="2">
<widget class="QGroupBox" name="m_label_gb">
<property name="title">
<string>Nom de la borne</string>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="m_show_name_cb">
<property name="text">
<string>Afficher le nom</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="m_text_props_gb">
<property name="title">
<string>Propriétés du texte</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QPushButton" name="m_font_pb">
<property name="text">
<string>Police</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="m_label_size_sb">
<property name="minimum">
<number>4</number>
</property>
<property name="maximum">
<number>50</number>
</property>
<property name="value">
<number>9</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>X :</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="m_label_x_dsb">
<property name="minimum">
<double>-5000.000000000000000</double>
</property>
<property name="maximum">
<double>5000.000000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Y :</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="m_label_y_dsb">
<property name="minimum">
<double>-5000.000000000000000</double>
</property>
<property name="maximum">
<double>5000.000000000000000</double>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Rotation :</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="m_label_rotation_sb">
<property name="wrapping">
<bool>true</bool>
</property>
<property name="suffix">
<string>°</string>
</property>
<property name="decimals">
<number>0</number>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>359.000000000000000</double>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QPushButton" name="m_align_pb">
<property name="text">
<string>Alignement</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Couleur :</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QWidget" name="m_color_widget" native="true">
<property name="minimumSize">
<size>
<width>40</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QCheckBox" name="m_label_frame_cb">
<property name="text">
<string>Encadrer le texte</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QGroupBox" name="m_master_label_gb">
<property name="title">
<string>Étiquette du maître</string>
</property>
</spacer>
<property name="visible">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QCheckBox" name="m_use_master_label_cb">
<property name="text">
<string>Reprendre du maître</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="m_master_label_cb">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
+29 -2
View File
@@ -18,6 +18,7 @@
#include "texteditor.h"
#include "../../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../../ui/alignmenttextdialog.h"
#include "../graphicspart/parttext.h"
#include <cassert>
@@ -84,7 +85,7 @@ void TextEditor::setUpChangeConnection(QPointer<PartText> part)
void TextEditor::disconnectChangeConnection()
{
for (const auto &connection : qAsConst(m_change_connection)) {
for (const auto &connection : std::as_const(m_change_connection)) {
disconnect(connection);
}
m_change_connection.clear();
@@ -371,8 +372,34 @@ void TextEditor::setUpWidget(QWidget *parent)
gridLayout->addWidget(m_font_pb, 2, 2, 1, 2);
QPushButton *alignment_pb = new QPushButton(tr("Alignement"), parent);
alignment_pb->setToolTip(tr("Point d'ancrage du texte et alignement"
" des lignes entre elles"));
connect(alignment_pb, &QPushButton::clicked, [this]() {
if (m_text.isNull()) {
return;
}
AlignmentTextDialog atd(m_text->alignment(), this);
if (atd.exec() != QDialog::Accepted) {
return;
}
for (int i = 0; i < m_parts.length(); i++) {
PartText *part_text = m_parts[i];
if (atd.alignment() != part_text->alignment()) {
QPropertyUndoCommand *undo = new QPropertyUndoCommand(
part_text, "alignment",
QVariant(part_text->alignment()),
QVariant(atd.alignment()));
undo->setText(tr("Modifier l'alignement d'un champ texte"));
undoStack().push(undo);
}
}
});
gridLayout->addWidget(alignment_pb, 3, 0, 1, 2);
QSpacerItem *verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
gridLayout->addItem(verticalSpacer, 3, 2, 1, 1);
gridLayout->addItem(verticalSpacer, 4, 2, 1, 1);
setLayout(gridLayout);
}
+11 -1
View File
@@ -124,7 +124,17 @@ void ElementDialog::setUpWidget()
} else if (m_mode == SaveTemplate) {
m_text_field->setPlaceholderText(tr("Nom du nouveau template"));
} else {
m_text_field->setPlaceholderText(tr("Nom du nouvel élément"));
// This is the element's file name, not its display name: the field
// only accepts file-name characters (QFileNameEdit). The visible
// element name is edited separately in the element properties.
m_text_field->setPlaceholderText(
tr("Nom de fichier de l'élément",
"placeholder: the element's file name, not its display name"));
m_text_field->setToolTip(
tr("Nom de fichier de l'élément : chiffres, minuscules, « - », "
"« _ » et « . » uniquement.\nLe nom affiché de l'élément se "
"modifie séparément dans les propriétés de l'élément.",
"tooltip for the element file-name field"));
}
layout->addWidget(m_text_field);
+4 -4
View File
@@ -59,11 +59,11 @@ QVector <QPointer<Element>> ElementProvider::freeElement(ElementData::Types filt
QList<Element *> elmt_list;
//search in all diagram
for (const auto &diagram_ : qAsConst(m_diagram_list))
for (const auto &diagram_ : std::as_const(m_diagram_list))
{
//get all element in diagram d
elmt_list = diagram_->elements();
for (const auto &elmt_ : qAsConst(elmt_list))
for (const auto &elmt_ : std::as_const(elmt_list))
{
if (filter & elmt_->elementData().m_type &&
elmt_->isFree())
@@ -106,7 +106,7 @@ QList <Element *> ElementProvider::fromUuids(QList<QUuid> uuid_list) const
QVector<QPointer<Element>> ElementProvider::find(ElementData::Types elmt_type) const
{
QVector<QPointer<Element>> returned_vector;
for (const auto &diagram_ : qAsConst(m_diagram_list))
for (const auto &diagram_ : std::as_const(m_diagram_list))
{
const auto elmt_list = diagram_->elements();
for (const auto &elmt_ : elmt_list)
@@ -198,7 +198,7 @@ QVector<TerminalElement *> ElementProvider::freeTerminal() const
{
QVector<TerminalElement *> vector_;
for (const auto &diagram : qAsConst(m_diagram_list))
for (const auto &diagram : std::as_const(m_diagram_list))
{
const auto elmt_list{diagram->elements()};
+8 -8
View File
@@ -45,18 +45,18 @@ ElementsCollectionCache::ElementsCollectionCache(const QString &database_path, Q
qDebug() << "Unable to open the SQLite database " << database_path << " as " << connection_name << ": " << cache_db_.lastError();
else
{
cache_db_.exec("PRAGMA temp_store = MEMORY");
cache_db_.exec("PRAGMA journal_mode = MEMORY");
cache_db_.exec("PRAGMA page_size = 4096");
cache_db_.exec("PRAGMA cache_size = 16384");
cache_db_.exec("PRAGMA locking_mode = EXCLUSIVE");
cache_db_.exec("PRAGMA synchronous = OFF");
QSqlQuery(cache_db_).exec("PRAGMA temp_store = MEMORY");
QSqlQuery(cache_db_).exec("PRAGMA journal_mode = MEMORY");
QSqlQuery(cache_db_).exec("PRAGMA page_size = 4096");
QSqlQuery(cache_db_).exec("PRAGMA cache_size = 16384");
QSqlQuery(cache_db_).exec("PRAGMA locking_mode = EXCLUSIVE");
QSqlQuery(cache_db_).exec("PRAGMA synchronous = OFF");
#if TODO_LIST
#pragma message("@TODO the tables could already exist, handle that case.")
#endif
//@TODO the tables could already exist, handle that case.
cache_db_.exec("CREATE TABLE names"
QSqlQuery(cache_db_).exec("CREATE TABLE names"
"("
"path VARCHAR(512) NOT NULL,"
"locale VARCHAR(2) NOT NULL,"
@@ -65,7 +65,7 @@ ElementsCollectionCache::ElementsCollectionCache(const QString &database_path, Q
"PRIMARY KEY(path, locale)"
");");
cache_db_.exec("CREATE TABLE pixmaps"
QSqlQuery(cache_db_).exec("CREATE TABLE pixmaps"
"("
"path VARCHAR(512) NOT NULL UNIQUE,"
"uuid VARCHAR(512) NOT NULL,"
+1 -1
View File
@@ -16,7 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "elementsmover.h"
#include "qetproject.h"
#include "conductorautonumerotation.h"
#include "diagram.h"
#include "qetgraphicsitem/conductor.h"
+66 -3
View File
@@ -16,7 +16,6 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "elementspanelwidget.h"
#include "diagram.h"
#include "editor/ui/qetelementeditor.h"
#include "elementscategoryeditor.h"
@@ -26,6 +25,7 @@
#include "titleblock/templatedeleter.h"
#include <QFileInfo>
#include <QMessageBox>
#include "qetgraphicsitem/element.h"
/*
When the ENABLE_PANEL_WIDGET_DND_CHECKS flag is set, the panel
@@ -59,7 +59,8 @@ ElementsPanelWidget::ElementsPanelWidget(QWidget *parent) : QWidget(parent) {
prj_close = new QAction(QET::Icons::DocumentClose, tr("Fermer ce projet"), this);
prj_edit_prop = new QAction(QET::Icons::DialogInformation, tr("Propriétés du projet"), this);
prj_prop_diagram = new QAction(QET::Icons::DialogInformation, tr("Propriétés du folio"), this);
prj_add_diagram = new QAction(QET::Icons::DiagramAdd, tr("Ajouter un folio"), this);
prj_add_diagram = new QAction(QET::Icons::DiagramAdd, tr("Ajouter un folio"), this);
prj_duplicate_diagram = new QAction(QET::Icons::IC_CopyFile, tr("Copier et coller"), this);
prj_del_diagram = new QAction(QET::Icons::DiagramDelete, tr("Supprimer ce folio"), this);
prj_move_diagram_up = new QAction(QET::Icons::GoUp, tr("Remonter ce folio"), this);
prj_move_diagram_down = new QAction(QET::Icons::GoDown, tr("Abaisser ce folio"), this);
@@ -100,6 +101,7 @@ ElementsPanelWidget::ElementsPanelWidget(QWidget *parent) : QWidget(parent) {
connect(prj_prop_diagram, SIGNAL(triggered()), this, SLOT(editDiagramProperties()));
connect(prj_add_diagram, SIGNAL(triggered()), this, SLOT(newDiagram()));
connect(prj_del_diagram, SIGNAL(triggered()), this, SLOT(deleteDiagram()));
connect(prj_duplicate_diagram, SIGNAL(triggered()), this, SLOT(duplicateDiagram()));
connect(prj_move_diagram_up, SIGNAL(triggered()), this, SLOT(moveDiagramUp()));
connect(prj_move_diagram_down, SIGNAL(triggered()), this, SLOT(moveDiagramDown()));
connect(prj_move_diagram_top, SIGNAL(triggered()), this, SLOT(moveDiagramUpTop()));
@@ -447,7 +449,8 @@ void ElementsPanelWidget::updateButtons()
}
prj_del_diagram -> setEnabled(is_writable);
prj_move_diagram_up -> setEnabled(is_writable && min_position > 0);
prj_duplicate_diagram -> setEnabled(is_writable);
prj_move_diagram_up -> setEnabled(is_writable && min_position > 0);
prj_move_diagram_down -> setEnabled(is_writable && max_position < project_diagrams_count - 1);
prj_move_diagram_top -> setEnabled(is_writable && min_position > 0);
@@ -501,6 +504,7 @@ void ElementsPanelWidget::handleContextMenu(const QPoint &pos) {
case QET::Diagram:
context_menu -> addAction(prj_prop_diagram);
context_menu -> addAction(prj_del_diagram);
context_menu -> addAction(prj_duplicate_diagram);
context_menu -> addAction(prj_move_diagram_top);
context_menu -> addAction(prj_move_diagram_upx10);
context_menu -> addAction(prj_move_diagram_upx100);
@@ -593,3 +597,62 @@ void ElementsPanelWidget::keyPressEvent(QKeyEvent *e) {
break;
}
}
/**
* Duplicates the selected folios (pages) along with their content
* and properties, and cleanly resolves cross-references.
*/
void ElementsPanelWidget::duplicateDiagram()
{
QList<Diagram *> diagrams_to_duplicate = elements_panel->selectedDiagrams();
if (diagrams_to_duplicate.isEmpty()) return;
QETProject *project = diagrams_to_duplicate.first()->project();
if (!project || project->isReadOnly()) return;
for (Diagram *source_diagram : diagrams_to_duplicate) {
Diagram *new_diagram = project->addNewDiagram();
if (!new_diagram) continue;
QString template_name = source_diagram->border_and_titleblock.titleBlockTemplateName();
new_diagram->setTitleBlockTemplate(template_name);
TitleBlockProperties tbp = source_diagram->border_and_titleblock.exportTitleBlock();
new_diagram->border_and_titleblock.importTitleBlock(tbp);
BorderProperties bp = source_diagram->border_and_titleblock.exportBorder();
new_diagram->border_and_titleblock.importBorder(bp);
for (QGraphicsItem *item : source_diagram->items()) {
if (Element *elmt = dynamic_cast<Element *>(item)) {
source_diagram->correctTextPos(elmt);
}
}
QDomDocument doc = source_diagram->toXml();
QDomElement diagram_elmt = doc.documentElement();
for (QGraphicsItem *item : source_diagram->items()) {
if (Element *elmt = dynamic_cast<Element *>(item)) {
source_diagram->restoreText(elmt);
}
}
new_diagram->fromXml(diagram_elmt, QPointF(0, 0), false, nullptr);
for (QGraphicsItem *item : new_diagram->items()) {
if (Element *elmt = dynamic_cast<Element *>(item)) {
// The XML round-trip kept the source elements' uuids. Give the
// copies their own identity (as PasteDiagramCommand::redo()
// does for on-diagram paste): element.uuid is the PRIMARY KEY
// of the project database, so duplicates fail to insert and
// silently vanish from nomenclature/summary tables.
elmt->newUuid();
new_diagram->restoreText(elmt);
}
}
}
elements_panel->reload();
}
+2
View File
@@ -47,6 +47,7 @@ class ElementsPanelWidget : public QWidget {
*prj_prop_diagram,
*prj_add_diagram,
*prj_del_diagram,
*prj_duplicate_diagram,
*prj_move_diagram_up,
*prj_move_diagram_top,
*prj_move_diagram_down,
@@ -88,6 +89,7 @@ class ElementsPanelWidget : public QWidget {
void editDiagramProperties();
void newDiagram();
void deleteDiagram();
void duplicateDiagram();
void moveDiagramUp();
void moveDiagramDown();
void moveDiagramUpTop();
+1
View File
@@ -112,6 +112,7 @@ ExportDialog::ExportDialog(
*/
ExportDialog::~ExportDialog()
{
qDeleteAll(diagram_lines_);
}
/**
+5
View File
@@ -30,6 +30,7 @@ ExportProperties::ExportProperties() :
destination_directory(QETApp::documentDir()),
format("PNG"),
draw_grid(false),
draw_guides(false),
draw_border(true),
draw_titleblock(true),
draw_terminals(false),
@@ -61,6 +62,8 @@ void ExportProperties::toSettings(QSettings &settings,
format);
settings.setValue(prefix % "drawgrid",
draw_grid);
settings.setValue(prefix % "drawguides",
draw_guides);
settings.setValue(prefix % "drawborder",
draw_border);
settings.setValue(prefix % "drawtitleblock",
@@ -94,6 +97,8 @@ void ExportProperties::fromSettings(QSettings &settings,
draw_grid = settings.value(prefix % "drawgrid",
false).toBool();
draw_guides = settings.value(prefix % "drawguides",
false).toBool();
draw_border = settings.value(prefix % "drawborder",
true ).toBool();
draw_titleblock = settings.value(prefix % "drawtitleblock",
+1
View File
@@ -43,6 +43,7 @@ class ExportProperties {
QDir destination_directory; ///< Target directory for generated files
QString format; ///< Image format of generated files
bool draw_grid; ///< Whether to render the diagram grid
bool draw_guides; ///< Whether to render the diagram guides
bool draw_border; ///< Whether to render the border (along with rows/columns headers)
bool draw_titleblock; ///< Whether to render the title block
bool draw_terminals; ///< Whether to render terminals
+8 -4
View File
@@ -42,10 +42,14 @@ Element * ElementFactory::createElement(const ElementsLocation &location, QGraph
return nullptr;
}
auto doc = location.pugiXml();
if (doc.document_element().attribute("link_type"))
//Read the link_type from the (cached) QDom definition instead of
//running a second, pugixml parse of the same definition: this
//dispatch runs once per element instance on project load, and the
//pugixml detour was ~4 % of the whole load time of a big project.
const QString link_type =
location.xml().attribute(QStringLiteral("link_type"));
if (!link_type.isEmpty())
{
QString link_type(doc.document_element().attribute("link_type").as_string());
if (link_type == QLatin1String("next_report") || link_type == QLatin1String("previous_report"))
return (new ReportElement(location, link_type, qgi, state));
if (link_type == QLatin1String("master"))
@@ -55,7 +59,7 @@ Element * ElementFactory::createElement(const ElementsLocation &location, QGraph
if (link_type == QLatin1String("terminal"))
return (new TerminalElement (location, qgi, state));
}
//default if nothing match for link_type
return (new SimpleElement(location, qgi, state));
}
+265 -6
View File
@@ -19,8 +19,10 @@
#include "../ElementsCollection/elementslocation.h"
#include "../editor/graphicspart/partline.h"
#include "../properties/elementdata.h"
#include "../qetapp.h"
#include "../qetversion.h"
#include "../utils/qetutils.h"
#include <QAbstractTextDocumentLayout>
#include <QDomElement>
@@ -30,6 +32,7 @@
#include <QRegularExpression>
#include <QTextDocument>
#include <iostream>
#include <algorithm>
ElementPictureFactory* ElementPictureFactory::m_factory = nullptr;
@@ -54,7 +57,7 @@ void ElementPictureFactory::getPictures(const ElementsLocation &location, QPictu
return;
}
if(m_pictures_H.keys().contains(uuid))
if(m_pictures_H.contains(uuid))
{
picture = m_pictures_H.value(uuid);
low_picture = m_low_pictures_H.value(uuid);
@@ -206,7 +209,25 @@ bool ElementPictureFactory::build(const ElementsLocation &location,
tmp.setCosmetic(true);
low_painter.setPen(tmp);
//scroll of the Children of the Definition: Parts of the Drawing
//scroll of the Children of the Definition: Parts of the Drawing
// Extract PLC master data for rendering plc_table parts
QDomElement plc_master_data;
for (QDomNode node = dom.firstChild() ; !node.isNull() ; node = node.nextSibling())
{
QDomElement elmts = node.toElement();
if (elmts.isNull()) continue;
if (elmts.tagName() == "kindInformations") {
for (QDomNode ki = elmts.firstChild(); !ki.isNull(); ki = ki.nextSibling()) {
QDomElement ki_el = ki.toElement();
if (!ki_el.isNull() && ki_el.tagName() == "plcMasterData") {
plc_master_data = ki_el;
break;
}
}
break;
}
}
for (QDomNode node = dom.firstChild() ; !node.isNull() ; node = node.nextSibling())
{
QDomElement elmts = node.toElement();
@@ -223,9 +244,17 @@ bool ElementPictureFactory::build(const ElementsLocation &location,
if (qde.isNull()) {
continue;
}
parseElement(qde, painter, primitives_);
primitives fake_prim;
parseElement(qde, low_painter, fake_prim);
if (qde.tagName() == "plc_table") {
// Skip - PLC table is rendered at runtime by
// Element::drawPlcTable() from live ElementData.
// Rendering into the cached QPicture here causes
// intermittent crashes when drawPicture() replays
// complex font/text operations.
} else {
parseElement(qde, painter, primitives_);
primitives fake_prim;
parseElement(qde, low_painter, fake_prim);
}
}
}
}
@@ -503,7 +532,7 @@ void ElementPictureFactory::parseText(const QDomElement &dom, QPainter &painter,
font_ = QETApp::diagramTextsFont(dom.attribute("size").toDouble());
}
else if (dom.hasAttribute("font")) {
font_.fromString(dom.attribute("font"));
QETUtils::fontFromString(font_, dom.attribute("font"));
}
QColor text_color(dom.attribute("color", "#000000"));
@@ -534,6 +563,22 @@ void ElementPictureFactory::parseText(const QDomElement &dom, QPainter &painter,
//adjusts the offset by the margin of the text document
text_document.setDocumentMargin(0.0);
//Optional line alignment of multi-line texts (the anchor behaviour
//of the alignment is handled in the element editor; the saved x/y
//always stay the baseline-left of the text block). The document
//only honors the text option once a text width is set.
if (dom.hasAttribute("Halignment")) {
const QMetaEnum me = QMetaEnum::fromType<Qt::Alignment>();
const Qt::Alignment h_alignment = Qt::Alignment(
me.keyToValue(dom.attribute("Halignment").toStdString().data()));
if (h_alignment & (Qt::AlignHCenter | Qt::AlignRight)) {
QTextOption option = text_document.defaultTextOption();
option.setAlignment(h_alignment & Qt::AlignHorizontal_Mask);
text_document.setDefaultTextOption(option);
text_document.setTextWidth(text_document.idealWidth());
}
}
painter.translate(qpainter_offset);
// force the palette used to render the QTextDocument
@@ -1081,3 +1126,217 @@ void ElementPictureFactory::setPainterStyle(const QDomElement &dom, QPainter &pa
painter.setPen(pen);
painter.setBrush(brush);
}
void ElementPictureFactory::parsePlcTable(const QDomElement &dom, const QDomElement &plc_data, QPainter &painter) const
{
qreal pos_x = dom.attribute("x", "0").toDouble();
qreal pos_y = dom.attribute("y", "0").toDouble();
if (plc_data.isNull()) return;
const int COL_COUNT = 5;
const int COL_TYPE = 0;
const int COL_ADDRESS = 1;
const int COL_FUNCTION = 2;
const int COL_COMMENT = 3;
const int COL_CROSSREF = 4;
qreal rowHeight = plc_data.attribute("rowHeight", "8.0").toDouble();
// Parse showHeaders
bool showHeaders = true;
auto xml_sh = plc_data.firstChildElement("showHeaders");
if (!xml_sh.isNull())
showHeaders = (xml_sh.text().trimmed() != "0");
qreal header_h = showHeaders ? (rowHeight + 2.0) : 0;
// Parse column widths
QMap<int, qreal> col_widths;
auto xml_cw = plc_data.firstChildElement("columnWidths");
for (auto xml_col = xml_cw.firstChildElement("column"); !xml_col.isNull();
xml_col = xml_col.nextSiblingElement("column")) {
int idx = xml_col.attribute("index").toInt();
qreal w = xml_col.attribute("width").toDouble();
if (w > 0) col_widths[idx] = w;
}
if (col_widths.isEmpty()) {
col_widths[0] = 35; col_widths[1] = 25; col_widths[2] = 50;
col_widths[3] = 40; col_widths[4] = 30;
}
// Parse column visibility
QMap<int, bool> col_visible;
auto xml_cv = plc_data.firstChildElement("columnVisibility");
for (auto xml_c = xml_cv.firstChildElement("column"); !xml_c.isNull();
xml_c = xml_c.nextSiblingElement("column")) {
int idx = xml_c.attribute("index").toInt();
QString vis = xml_c.attribute("visible", "true");
col_visible[idx] = (vis == "true");
}
// Parse column order
QList<int> column_order;
auto xml_ord = plc_data.firstChildElement("columnOrder");
if (!xml_ord.isNull()) {
QStringList parts = xml_ord.text().split(",", Qt::SkipEmptyParts);
for (const QString &s : parts) {
int idx = s.trimmed().toInt();
if (idx >= 0 && idx < COL_COUNT)
column_order.append(idx);
}
}
// Parse custom column names
QMap<int, QString> column_names;
auto xml_names = plc_data.firstChildElement("columnNames");
for (auto xml_n = xml_names.firstChildElement("column"); !xml_n.isNull();
xml_n = xml_n.nextSiblingElement("column")) {
int idx = xml_n.attribute("index").toInt();
QString name = xml_n.text().trimmed();
if (!name.isEmpty())
column_names[idx] = name;
}
// Parse fonts
QFont header_font;
header_font.setPointSize(7);
header_font.setBold(true);
auto xml_hfont = plc_data.firstChildElement("headerFont");
if (!xml_hfont.isNull()) {
if (!xml_hfont.attribute("family").isEmpty())
header_font.setFamily(xml_hfont.attribute("family"));
if (xml_hfont.attribute("size").toInt() > 0)
header_font.setPointSize(xml_hfont.attribute("size").toInt());
header_font.setBold(xml_hfont.attribute("bold") == "true");
}
QFont cell_font;
cell_font.setPointSize(7);
auto xml_cfont = plc_data.firstChildElement("cellFont");
if (!xml_cfont.isNull()) {
if (!xml_cfont.attribute("family").isEmpty())
cell_font.setFamily(xml_cfont.attribute("family"));
if (xml_cfont.attribute("size").toInt() > 0)
cell_font.setPointSize(xml_cfont.attribute("size").toInt());
cell_font.setBold(xml_cfont.attribute("bold") == "true");
}
// Parse IOs
QVector<QMap<int, QString>> ios;
auto xml_ios = plc_data.firstChildElement("plcIOs");
for (auto xml_io = xml_ios.firstChildElement("plcIO"); !xml_io.isNull();
xml_io = xml_io.nextSiblingElement("plcIO")) {
QMap<int, QString> io_data;
io_data[COL_TYPE] = ElementData::translatedPlcIOType(
ElementData::plcIOTypeFromString(xml_io.attribute("type")));
io_data[COL_ADDRESS] = xml_io.attribute("address");
io_data[COL_FUNCTION] = xml_io.attribute("functionText");
io_data[COL_COMMENT] = xml_io.attribute("comment");
io_data[COL_CROSSREF] = xml_io.attribute("crossRef");
ios.append(io_data);
}
// Build visible columns (respect column order)
QList<int> visible_cols;
if (!column_order.isEmpty()) {
for (int logical : column_order) {
if (logical >= 0 && logical < COL_COUNT
&& col_visible.value(logical, true)
&& !visible_cols.contains(logical))
visible_cols.append(logical);
}
for (int i = 0; i < COL_COUNT; ++i) {
if (col_visible.value(i, true) && !visible_cols.contains(i))
visible_cols.append(i);
}
} else {
for (int i = 0; i < COL_COUNT; ++i) {
if (col_visible.value(i, true))
visible_cols.append(i);
}
}
if (visible_cols.isEmpty())
visible_cols << COL_TYPE << COL_ADDRESS << COL_FUNCTION;
// Build header labels
QMap<int, QString> headers;
headers[COL_TYPE] = QObject::tr("Type");
headers[COL_ADDRESS] = QObject::tr("Adresse");
headers[COL_FUNCTION] = QObject::tr("Fonction");
headers[COL_COMMENT] = QObject::tr("Commentaire");
headers[COL_CROSSREF] = QObject::tr("Réf. croisée");
for (auto it = column_names.constBegin(); it != column_names.constEnd(); ++it)
headers[it.key()] = it.value();
// Parse break positions
QList<int> breaks;
auto xml_breaks = plc_data.firstChildElement("breakPositions");
for (auto xml_bp = xml_breaks.firstChildElement("break"); !xml_bp.isNull();
xml_bp = xml_bp.nextSiblingElement("break")) {
int bp = xml_bp.text().toInt();
if (bp > 0 && bp < ios.size() && !breaks.contains(bp))
breaks.append(bp);
}
std::sort(breaks.begin(), breaks.end());
QList<int> block_starts;
block_starts.append(0);
for (int bp : breaks)
block_starts.append(bp);
int block_count = block_starts.size();
qreal total_width = 0;
for (int col : visible_cols)
total_width += col_widths.value(col, 30);
QPen border_pen(Qt::black, 0.5);
int total_ios = ios.size();
for (int block = 0; block < block_count; ++block) {
qreal bx = pos_x + block * (total_width + 3);
qreal cx = bx;
// Draw headers
if (showHeaders) {
painter.fillRect(QRectF(cx, pos_y, total_width, header_h), QColor(220, 220, 220));
painter.setPen(border_pen);
painter.setBrush(Qt::NoBrush);
painter.setFont(header_font);
for (int col : visible_cols) {
QRectF hr(cx, pos_y, col_widths.value(col, 30), header_h);
painter.drawRect(hr);
QRectF text_rect = hr.adjusted(1, 0, -1, 0);
painter.drawText(text_rect, Qt::AlignCenter, headers.value(col, QString()));
cx += col_widths.value(col, 30);
}
}
// Draw IO rows
painter.setFont(cell_font);
int start_idx = block_starts.at(block);
int end_idx = (block + 1 < block_starts.size()) ? block_starts.at(block + 1) : total_ios;
for (int row = 0; row < (end_idx - start_idx); ++row) {
int io_idx = start_idx + row;
if (io_idx >= ios.size()) break;
const auto &io = ios.at(io_idx);
qreal ry = pos_y + header_h + row * rowHeight;
cx = bx;
for (int col : visible_cols) {
QRectF cr(cx, ry, col_widths.value(col, 30), rowHeight);
painter.setPen(border_pen);
painter.setBrush(Qt::NoBrush);
painter.drawRect(cr);
QString cell_text = io.value(col, QString());
QRectF text_rect = cr.adjusted(1, 0, -1, 0);
painter.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, cell_text);
cx += col_widths.value(col, 30);
}
}
}
}
+1
View File
@@ -101,6 +101,7 @@ class ElementPictureFactory
void parseArc (const QDomElement &dom, QPainter &painter, primitives &prim) const;
void parsePolygon(const QDomElement &dom, QPainter &painter, primitives &prim) const;
void parseText (const QDomElement &dom, QPainter &painter, primitives &prim) const;
void parsePlcTable(const QDomElement &dom, const QDomElement &plc_data, QPainter &painter) const;
void setPainterStyle(const QDomElement &dom, QPainter &painter) const;
QHash<QUuid, QPicture> m_pictures_H;
+1 -1
View File
@@ -25,7 +25,7 @@
#include "../qetgraphicsitem/ViewItem/qetgraphicstableitem.h"
#include "../utils/qetutils.h"
#include "ui/addtabledialog.h"
#include "../qetproject.h"
#include <QDialog>
QetGraphicsTableFactory::QetGraphicsTableFactory()
+8 -6
View File
@@ -221,12 +221,12 @@ void AddTableDialog::saveConfig()
header_object.insert("margins", QETUtils::marginsToString(this->headerMargins()));
auto me = QMetaEnum::fromType<Qt::Alignment>();
header_object.insert("alignment", me.valueToKey(int(this->headerAlignment())));
header_object.insert("font", this->headerFont().toString());
header_object.insert("font", QETUtils::fontToString(this->headerFont()));
QJsonObject table_object;
table_object.insert("margins", QETUtils::marginsToString(this->tableMargins()));
table_object.insert("alignment", me.valueToKey(int(this->tableAlignment())));
table_object.insert("font", this->tableFont().toString());
table_object.insert("font", QETUtils::fontToString(this->tableFont()));
QJsonObject config_object;
config_object.insert("header", header_object);
@@ -268,13 +268,14 @@ void AddTableDialog::loadConfig()
case Qt::AlignLeft :
ui->m_header_alignment_cb->setCurrentIndex(0);
break;
case Qt::AlignCenter :
case Qt::AlignHCenter :
case Qt::AlignCenter : // accept AlignCenter in case it was hand-edited by the user
ui->m_header_alignment_cb->setCurrentIndex(1);
break;
default:
ui->m_header_alignment_cb->setCurrentIndex(2);
}
m_header_font.fromString(header_object.value("font").toString());
QETUtils::fontFromString(m_header_font, header_object.value("font").toString());
ui->m_header_font_pb->setText(m_header_font.family());
//Table
@@ -284,13 +285,14 @@ void AddTableDialog::loadConfig()
case Qt::AlignLeft :
ui->m_table_alignment_cb->setCurrentIndex(0);
break;
case Qt::AlignCenter :
case Qt::AlignHCenter :
case Qt::AlignCenter : // accept AlignCenter in case it was hand-edited by the user
ui->m_table_alignment_cb->setCurrentIndex(1);
break;
default:
ui->m_table_alignment_cb->setCurrentIndex(2);
}
m_table_font.fromString(table_object.value("font").toString());
QETUtils::fontFromString(m_table_font, table_object.value("font").toString());
ui->m_table_font_pb->setText(m_table_font.family());
}
+4 -2
View File
@@ -321,9 +321,11 @@ QTreeWidgetItem *GenericPanel::getItemForDiagram(Diagram *diagram,
if (created) *created = false;
return(diagram_qtwi);
}
if (!created) return(nullptr);
diagram_qtwi = makeItem(QET::Diagram);
if (created) *created = true;
*created = true;
return(diagram_qtwi);
}
+70
View File
@@ -0,0 +1,70 @@
# EPLAN `.edz` part import
Imports a part from the **EPLAN Data Portal** (`.edz`) into a QElectroTech
element collection. The EPLAN Data Portal is the de-facto parts catalogue for
electrical CAD (ifm, Phoenix Contact, Siemens, Rittal, Weidmüller, Schneider…),
and components ship as `.edz`; this lets a QET user drop one straight into a
project.
## Using it
Right-click a writable collection folder in the elements panel →
**Import an EPLAN part (.edz)…** → choose the file. The generated element is
written into that folder and the panel reloads.
## What is imported
A `.edz` is a 7-Zip archive containing a part definition (`*.part.xml`), an EPLAN
macro (`*.ema`), a product image and metadata. Only the **portable** part data is
used:
- **Terminals** — each `<functiontemplate>` with a physical connection
designation becomes a QET terminal, labelled from its connection description.
- **Element information** — manufacturer, order number, designation and comment
fill the QET element-information fields used by the BOM / nomenclature.
- **Localized names** — one `<name lang="xx">` per language the part provides, so
the element labels itself in any QET UI language.
- **Symbol** — a generic symbol is generated (a body rectangle with one
west-facing terminal per pin). The EPLAN `.ema` macro geometry is **not**
reproduced: it is tokenised EPLAN PXF that references EPLAN's own symbol
libraries. A generic symbol is sufficient for wiring and BOM; swap in a nicer
symbol in the element editor afterwards if desired.
## Structure
| File | Role |
|------|------|
| `edzarchive.*` | Extract the `.edz` to a temp dir; locate `part.xml` |
| `edzsevenzip.*` | 7-Zip extraction via the bundled LZMA SDK (no external 7-Zip) |
| `edzpart.*` | Parse `part.xml` into a portable model |
| `edzelementbuilder.*` | Build the `.elmt` element from the model |
| `edzimporter.*` | Orchestrate the above and write into a collection folder |
| `lzma/` | Vendored public-domain LZMA SDK (decode-only subset) |
The non-UI classes are deliberately decoupled from the widget so they can be
tested headless.
## Terms of Use / Legal notice
The `.edz` files themselves are downloaded from the **EPLAN Data Portal**
(data.eplan.com), which is operated by EPLAN Software & Service GmbH & Co. KG.
The Data Portal Terms of Use (§ 5.4 at time of writing) restrict use of
downloaded data to EPLAN products.
**QElectroTech does not endorse or encourage violation of those terms.**
Whether use of an `.edz` outside EPLAN products is permitted under your
specific licence depends on the agreement you have with EPLAN and the
individual manufacturer. You are solely responsible for ensuring that your use
of `.edz` data complies with the applicable Terms of Use and any applicable
law. If in doubt, contact EPLAN or the part manufacturer directly.
QET's `.edz` reader parses only the portable, factual part data
(pin designations, order numbers, manufacturer names). It does not reproduce
any EPLAN-proprietary geometry or symbol data.
## Bundled LZMA SDK
`lzma/` contains the decode-only subset of Igor Pavlov's **LZMA SDK** (public
domain — see `lzma/LZMA-SDK-LICENSE.txt`), so a `.edz` can be unpacked without an
external 7-Zip install. Only 7-Zip-format `.edz` files are supported; zip-format
packages are detected and reported but not yet handled.
+107
View File
@@ -0,0 +1,107 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech 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 2 of the License, or
(at your option) any later version.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "edzarchive.h"
#include "edzsevenzip.h"
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>
#include <QTemporaryDir>
EdzArchive::EdzArchive() = default;
EdzArchive::~EdzArchive() = default;
/**
Extract the .edz at @a edz_path into a fresh temporary directory.
@return true on success; extractedDir()/partXmlPath() are then valid.
*/
bool EdzArchive::extract(const QString &edz_path)
{
m_error.clear();
const QFileInfo fi(edz_path);
if (!fi.exists() || !fi.isFile()) {
m_error = tr("File not found: %1").arg(edz_path);
return false;
}
// A .edz is a 7-Zip archive (magic "7z\xBC\xAF\x27\x1C"). Some EPLAN exports
// are instead zip-format ("PK\x03\x04"); the bundled reader only does 7z, so
// detect that up front and say so clearly rather than failing opaquely.
QFile probe(fi.absoluteFilePath());
if (!probe.open(QIODevice::ReadOnly)) {
m_error = tr("Cannot read %1").arg(edz_path);
return false;
}
const QByteArray magic = probe.read(6);
probe.close();
static const QByteArray k7z("7z\xBC\xAF\x27\x1C", 6);
if (!magic.startsWith(k7z)) {
if (magic.startsWith(QByteArray("PK\x03\x04", 4))) {
m_error = tr(
"This .edz is a zip-format package, which is not yet "
"supported (only 7-Zip .edz files can be imported).");
} else {
m_error = tr(
"Not a valid .edz package (unrecognised archive format).");
}
return false;
}
m_dir.reset(new QTemporaryDir);
if (!m_dir->isValid()) {
m_error = tr("Could not create a temporary directory: %1")
.arg(m_dir->errorString());
return false;
}
if (!sevenZipExtract(fi.absoluteFilePath(), m_dir->path(), m_error)) {
return false;
}
if (partXmlPath().isEmpty()) {
m_error = tr("No *.part.xml found inside the .edz");
return false;
}
return true;
}
/** @return the temporary folder holding the extracted tree (empty if none). */
QString EdzArchive::extractedDir() const
{
return m_dir ? m_dir->path() : QString();
}
/** @return absolute path to the first *.part.xml in the extracted tree. */
QString EdzArchive::partXmlPath() const
{
const QString root = extractedDir();
if (root.isEmpty()) {
return QString();
}
QDirIterator it(root, QStringList{QStringLiteral("*.part.xml")},
QDir::Files, QDirIterator::Subdirectories);
return it.hasNext() ? it.next() : QString();
}
QString EdzArchive::errorString() const
{
return m_error;
}
+59
View File
@@ -0,0 +1,59 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech 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 2 of the License, or
(at your option) any later version.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EDZARCHIVE_H
#define EDZARCHIVE_H
#include <QCoreApplication>
#include <QString>
#include <QScopedPointer>
class QTemporaryDir;
/**
@brief Extracts an EPLAN Data Portal package (.edz) to a temporary folder.
A .edz is a 7-Zip archive holding a part definition (part.xml), an EPLAN
macro, a product image and metadata. EdzArchive unpacks it so the rest of the
import pipeline can read the (portable) part.xml.
Extraction uses the bundled (public-domain) LZMA SDK 7-Zip reader, so no
external 7-Zip is required at runtime (see edzsevenzip).
The extracted tree lives in a QTemporaryDir owned by this object and is
removed when the EdzArchive is destroyed.
*/
class EdzArchive
{
Q_DECLARE_TR_FUNCTIONS(EdzArchive)
public:
EdzArchive();
~EdzArchive();
bool extract(const QString &edz_path);
QString extractedDir() const;
QString partXmlPath() const;
QString errorString() const;
private:
QScopedPointer<QTemporaryDir> m_dir;
QString m_error;
};
#endif // EDZARCHIVE_H
+308
View File
@@ -0,0 +1,308 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech 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 2 of the License, or
(at your option) any later version.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "edzelementbuilder.h"
#include "edzpart.h"
#include <QMap>
#include <QSet>
#include <QUuid>
#include <QVector>
#include <algorithm>
#include <cmath>
namespace {
const QString STYLE =
QStringLiteral("line-style:normal;line-weight:normal;filling:none;color:black");
const QString FONT =
QStringLiteral("Liberation Sans,5,-1,5,50,0,0,0,0,0,Regular");
const QString TITLE_FONT =
QStringLiteral("Liberation Sans,5,-1,5,75,0,0,0,0,0,Bold");
const QString LABEL_FONT =
QStringLiteral("Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular");
QString uuidStr()
{
return QUuid::createUuid().toString(); // "{....}"
}
QDomElement makeText(QDomDocument &doc, int x, int y, const QString &text,
const QString &font)
{
QDomElement e = doc.createElement(QStringLiteral("text"));
e.setAttribute(QStringLiteral("x"), x);
e.setAttribute(QStringLiteral("y"), y);
e.setAttribute(QStringLiteral("text"), text);
e.setAttribute(QStringLiteral("rotation"), QStringLiteral("0"));
e.setAttribute(QStringLiteral("font"), font);
e.setAttribute(QStringLiteral("color"), QStringLiteral("#000000"));
return e;
}
int ceilTo10(int v)
{
if (v <= 0) {
return 0;
}
return ((v + 9) / 10) * 10;
}
} // namespace
QDomDocument EdzElementBuilder::build(const EdzPart &part)
{
QList<EdzPin> pins = part.pins();
if (pins.isEmpty()) {
pins.append(EdzPin{QStringLiteral("1"), QString(), QString()});
}
const int n = pins.size();
const int pitch = 10;
const int group_gap = 10; // one full slot between named connector groups
// Pins arrive sorted by functional group then by designation within each
// group (see EdzPart::parse). A group break occurs when the
// functiondefinition block changes; fall back to designation comparison for
// parts that carry no functiondefinition information.
// No gap is inserted for power/busbar pins that carry no group label —
// they flow consecutively so a 3-phase AC source stays connected.
auto groupKey = [](const EdzPin &p) {
return p.group.isEmpty() ? p.designation : p.group;
};
QVector<bool> is_group_break(n, false);
for (int i = 1; i < n; ++i)
is_group_break[i] = (groupKey(pins.at(i)) != groupKey(pins.at(i - 1)))
&& (!pins.at(i).group.isEmpty());
// Place every terminal on a multiple-of-10 y coordinate so they align
// cleanly with QET's default grid. The gap slot is also 10 px, giving
// one empty grid row between connector groups.
QVector<int> pin_y(n);
int cur_y = 0;
for (int i = 0; i < n; ++i) {
if (is_group_break[i])
cur_y += group_gap;
pin_y[i] = cur_y;
cur_y += pitch;
}
const int body_left = 0, body_right = 90, body_top = -20;
const int body_bottom = cur_y; // one pitch below the last pin
// Coordinate accumulators for the bounding box (origin always included).
QList<int> xs{0}, ys{0};
xs << body_left << body_right;
ys << body_top << body_bottom;
xs << (body_left + 6); // title x
ys << (body_top + 7); // title y
for (int i = 0; i < n; ++i) {
xs << 6 << 0; // pin label x, terminal x
ys << (pin_y[i] + 2) << pin_y[i]; // pin label y, terminal y
}
const int pad = 10;
const int min_x = *std::min_element(xs.begin(), xs.end()) - pad;
const int max_x = *std::max_element(xs.begin(), xs.end()) + pad;
const int min_y = *std::min_element(ys.begin(), ys.end()) - pad;
const int max_y = *std::max_element(ys.begin(), ys.end()) + pad;
const int width = std::max(10, ceilTo10(max_x - min_x));
const int height = std::max(10, ceilTo10(max_y - min_y));
const int hotspot_x = -min_x;
const int hotspot_y = -min_y;
QDomDocument doc;
doc.appendChild(doc.createProcessingInstruction(
QStringLiteral("xml"),
QStringLiteral("version=\"1.0\" encoding=\"utf-8\"")));
QDomElement defn = doc.createElement(QStringLiteral("definition"));
defn.setAttribute(QStringLiteral("version"), QStringLiteral("0.100.0"));
defn.setAttribute(QStringLiteral("type"), QStringLiteral("element"));
defn.setAttribute(QStringLiteral("link_type"), QStringLiteral("simple"));
defn.setAttribute(QStringLiteral("width"), width);
defn.setAttribute(QStringLiteral("height"), height);
defn.setAttribute(QStringLiteral("hotspot_x"), hotspot_x);
defn.setAttribute(QStringLiteral("hotspot_y"), hotspot_y);
doc.appendChild(defn);
QDomElement uuid_el = doc.createElement(QStringLiteral("uuid"));
uuid_el.setAttribute(QStringLiteral("uuid"), uuidStr());
defn.appendChild(uuid_el);
// Localized names: fr/en first, then the rest alphabetically.
QDomElement names = doc.createElement(QStringLiteral("names"));
QMap<QString, QString> name_map = part.names();
const QString fallback = !part.orderNumber().isEmpty() ? part.orderNumber()
: (!part.partNumber().isEmpty() ? part.partNumber()
: QStringLiteral("part"));
if (name_map.isEmpty()) {
name_map.insert(QStringLiteral("en"),
!part.description().isEmpty() ? part.description()
: fallback);
}
QStringList ordered;
for (const QString &c : {QStringLiteral("fr"), QStringLiteral("en")}) {
if (name_map.contains(c)) ordered << c;
}
QStringList rest;
for (const QString &c : name_map.keys()) {
if (c != QLatin1String("fr") && c != QLatin1String("en")) rest << c;
}
std::sort(rest.begin(), rest.end());
ordered << rest;
for (const QString &lang : ordered) {
QDomElement nm = doc.createElement(QStringLiteral("name"));
nm.setAttribute(QStringLiteral("lang"), lang);
nm.appendChild(doc.createTextNode(name_map.value(lang)));
names.appendChild(nm);
}
defn.appendChild(names);
QDomElement infos = doc.createElement(QStringLiteral("informations"));
infos.appendChild(doc.createTextNode(
QStringLiteral("Imported from EPLAN .edz by edz2qet (%1)")
.arg(part.partNumber())));
defn.appendChild(infos);
// Element information -> QET BOM / nomenclature.
QDomElement einfos = doc.createElement(QStringLiteral("elementInformations"));
const QList<QPair<QString, QString>> info_map{
{QStringLiteral("manufacturer"), part.manufacturer()},
{QStringLiteral("manufacturer_reference"), part.orderNumber()},
{QStringLiteral("designation"), part.description()},
{QStringLiteral("comment"), part.comment()},
};
for (const auto &kv : info_map) {
if (kv.second.isEmpty()) {
continue;
}
QDomElement e = doc.createElement(QStringLiteral("elementInformation"));
e.setAttribute(QStringLiteral("name"), kv.first);
e.setAttribute(QStringLiteral("show"), QStringLiteral("1"));
e.appendChild(doc.createTextNode(kv.second));
einfos.appendChild(e);
}
defn.appendChild(einfos);
QDomElement desc = doc.createElement(QStringLiteral("description"));
// Body rectangle.
QDomElement rect = doc.createElement(QStringLiteral("rect"));
rect.setAttribute(QStringLiteral("x"), body_left);
rect.setAttribute(QStringLiteral("y"), body_top);
rect.setAttribute(QStringLiteral("width"), body_right - body_left);
rect.setAttribute(QStringLiteral("height"), body_bottom - body_top);
rect.setAttribute(QStringLiteral("style"), STYLE);
rect.setAttribute(QStringLiteral("antialias"), QStringLiteral("false"));
desc.appendChild(rect);
// Title (order number) in its own band at the top.
const QString title = !part.orderNumber().isEmpty() ? part.orderNumber()
: (!part.partNumber().isEmpty() ? part.partNumber()
: QStringLiteral("PART"));
desc.appendChild(makeText(doc, body_left + 6, body_top + 7, title, TITLE_FONT));
// Per-pin labels.
for (int i = 0; i < n; ++i) {
QString label = pins.at(i).designation;
if (!pins.at(i).description.isEmpty()) {
label += QStringLiteral(" ") + pins.at(i).description;
}
desc.appendChild(makeText(doc, 6, pin_y[i] + 2, label, FONT));
}
// Connector group header labels — one per named group, in the gap above
// the group's first pin so the electrician can see the terminal block name
// (e.g. "XDI", "XPOW") without having to read individual terminal names.
for (int i = 0; i < n; ++i) {
if (pins.at(i).group.isEmpty()) continue;
if (i > 0 && groupKey(pins.at(i)) == groupKey(pins.at(i - 1))) continue;
// Place the label centred in the gap slot above the group's first pin.
const int label_y = (i == 0) ? (pin_y[0] - group_gap / 2)
: (pin_y[i] - group_gap / 2);
desc.appendChild(makeText(doc, body_left + 2, label_y,
pins.at(i).group, FONT));
}
// Device-tag label (per-instance, above the body).
QDomElement dyn = doc.createElement(QStringLiteral("dynamic_text"));
dyn.setAttribute(QStringLiteral("x"), min_x + pad);
dyn.setAttribute(QStringLiteral("y"), min_y - 9);
dyn.setAttribute(QStringLiteral("z"), QStringLiteral("5"));
dyn.setAttribute(QStringLiteral("text_width"), QStringLiteral("-1"));
dyn.setAttribute(QStringLiteral("Halignment"), QStringLiteral("AlignLeft"));
dyn.setAttribute(QStringLiteral("Valignment"), QStringLiteral("AlignTop"));
dyn.setAttribute(QStringLiteral("frame"), QStringLiteral("false"));
dyn.setAttribute(QStringLiteral("rotation"), QStringLiteral("0"));
dyn.setAttribute(QStringLiteral("keep_visual_rotation"), QStringLiteral("false"));
dyn.setAttribute(QStringLiteral("text_from"), QStringLiteral("ElementInfo"));
dyn.setAttribute(QStringLiteral("uuid"), uuidStr());
dyn.setAttribute(QStringLiteral("font"), LABEL_FONT);
dyn.appendChild(doc.createElement(QStringLiteral("text")));
QDomElement info_name = doc.createElement(QStringLiteral("info_name"));
info_name.appendChild(doc.createTextNode(QStringLiteral("label")));
dyn.appendChild(info_name);
desc.appendChild(dyn);
// Build unique terminal names that an electrician can read directly on
// a wiring list. When a terminalNr connector label is present, prefix it:
// "XDI.2" (terminal block XDI, position 2)
// "XRO1.3" (relay output block XRO1, position 3)
// Power/busbar connections that carry no terminalNr use their designation
// as-is — these are already globally unique ("L1/U1", "UDC+", "PE", …).
// A numeric suffix is appended only if a collision still occurs, which
// should not happen with well-formed EPLAN data.
QSet<QString> used_names;
QVector<QString> terminal_names(n);
for (int i = 0; i < n; ++i) {
const EdzPin &pin = pins.at(i);
QString name = pin.group.isEmpty()
? pin.designation
: pin.group + QLatin1Char('.') + pin.designation;
// Collision guard (malformed data safety net).
if (used_names.contains(name)) {
int suffix = 2;
while (used_names.contains(name + QLatin1Char('_') + QString::number(suffix)))
++suffix;
name += QLatin1Char('_') + QString::number(suffix);
}
terminal_names[i] = name;
used_names.insert(name);
}
// Terminals.
for (int i = 0; i < n; ++i) {
QDomElement t = doc.createElement(QStringLiteral("terminal"));
t.setAttribute(QStringLiteral("uuid"), uuidStr());
t.setAttribute(QStringLiteral("name"), terminal_names[i]);
t.setAttribute(QStringLiteral("x"), 0);
t.setAttribute(QStringLiteral("y"), pin_y[i]);
t.setAttribute(QStringLiteral("orientation"), QStringLiteral("w"));
t.setAttribute(QStringLiteral("type"), QStringLiteral("Generic"));
desc.appendChild(t);
}
defn.appendChild(desc);
return doc;
}
QString EdzElementBuilder::toElmtString(const QDomDocument &doc)
{
return doc.toString(0);
}
+40
View File
@@ -0,0 +1,40 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech 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 2 of the License, or
(at your option) any later version.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EDZELEMENTBUILDER_H
#define EDZELEMENTBUILDER_H
#include <QDomDocument>
class EdzPart;
/**
@brief Builds a QElectroTech element (.elmt) from an EdzPart.
Generates a generic symbol: a body rectangle with one west-facing terminal
per pin (stacked on the left edge, on the 10px wiring grid), per-pin labels,
localized names and the element-information fields QET uses for the BOM.
Faithful port of the edz2qet.py prototype's builder.
*/
class EdzElementBuilder
{
public:
static QDomDocument build(const EdzPart &part);
static QString toElmtString(const QDomDocument &doc);
};
#endif // EDZELEMENTBUILDER_H
+83
View File
@@ -0,0 +1,83 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech 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 2 of the License, or
(at your option) any later version.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "edzimporter.h"
#include "edzarchive.h"
#include "edzpart.h"
#include "edzelementbuilder.h"
#include <QDir>
#include <QDomDocument>
#include <QFile>
#include <QRegularExpression>
/**
Import the .edz at @a edz_path, writing the generated .elmt into the
filesystem folder @a dest_dir. @return true on success; writtenPath() then
holds the created file. On failure errorString() explains why.
*/
bool EdzImporter::importToDirectory(const QString &edz_path,
const QString &dest_dir)
{
m_written.clear();
m_error.clear();
EdzArchive archive;
if (!archive.extract(edz_path)) {
m_error = archive.errorString();
return false;
}
EdzPart part;
if (!part.parse(archive.partXmlPath())) {
m_error = part.errorString();
return false;
}
const QDomDocument doc = EdzElementBuilder::build(part);
// Name the file from the order number (fall back to part number / source).
QString base = part.orderNumber();
if (base.isEmpty()) {
base = part.partNumber();
}
if (base.isEmpty()) {
base = QFileInfo(edz_path).completeBaseName();
}
base.replace(QRegularExpression(QStringLiteral("[^A-Za-z0-9._-]")),
QStringLiteral("_"));
const QDir dir(dest_dir);
if (!dir.exists() && !dir.mkpath(QStringLiteral("."))) {
m_error = QStringLiteral("Destination folder does not exist: %1")
.arg(dest_dir);
return false;
}
const QString out_path = dir.filePath(base + QStringLiteral(".elmt"));
QFile file(out_path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
m_error = QStringLiteral("Cannot write %1").arg(out_path);
return false;
}
file.write(EdzElementBuilder::toElmtString(doc).toUtf8());
file.close();
m_written = out_path;
return true;
}
+45
View File
@@ -0,0 +1,45 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech 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 2 of the License, or
(at your option) any later version.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EDZIMPORTER_H
#define EDZIMPORTER_H
#include <QString>
/**
@brief Orchestrates importing an EPLAN .edz package as a QET element.
Ties together EdzArchive (extract) -> EdzPart (parse) -> EdzElementBuilder
(build) and writes the resulting .elmt into a destination collection folder.
UI-decoupled so it stays unit-testable headless; the collection widget calls
importToDirectory() and then refreshes the panel.
*/
class EdzImporter
{
public:
bool importToDirectory(const QString &edz_path,
const QString &dest_dir);
QString writtenPath() const { return m_written; }
QString errorString() const { return m_error; }
private:
QString m_written;
QString m_error;
};
#endif // EDZIMPORTER_H
+237
View File
@@ -0,0 +1,237 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech 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 2 of the License, or
(at your option) any later version.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "edzpart.h"
#include <QDomDocument>
#include <QFile>
#include <QFileInfo>
#include <QPair>
#include <QRegularExpression>
namespace {
/**
Parse EPLAN's `de_DE@..;en_EN@..;..` multilingual blob into ordered
(full_code, text) pairs. Values may contain `;;` as an in-text separator, so
we split on language *markers* (xx_YY@) rather than on bare `;`.
*/
QList<QPair<QString, QString>> langDict(const QString &value)
{
QList<QPair<QString, QString>> out;
if (value.isEmpty()) {
return out;
}
static const QRegularExpression re(QStringLiteral("[a-z]{2}_[A-Z]{2}@"));
QList<QRegularExpressionMatch> marks;
auto it = re.globalMatch(value);
while (it.hasNext()) {
marks.append(it.next());
}
for (int i = 0; i < marks.size(); ++i) {
const QRegularExpressionMatch &m = marks.at(i);
const QString code = value.mid(m.capturedStart(),
m.capturedLength() - 1); // drop '@'
const int text_start = m.capturedEnd();
const int text_end = (i + 1 < marks.size())
? marks.at(i + 1).capturedStart()
: value.size();
QString text = value.mid(text_start, text_end - text_start);
while (!text.isEmpty() && text.back().isSpace()) {
text.chop(1);
}
while (!text.isEmpty() && text.back() == QLatin1Char(';')) {
text.chop(1);
}
text.replace(QStringLiteral(";;"), QStringLiteral("; "));
text = text.trimmed();
if (!text.isEmpty()) {
out.append({code, text});
}
}
return out;
}
/** Pick one language: preferred English variants, then any English, then first. */
QString pickLang(const QString &value)
{
if (value.isEmpty()) {
return QString();
}
const QList<QPair<QString, QString>> d = langDict(value);
if (d.isEmpty()) {
return value.trimmed();
}
for (const QString &code : {QStringLiteral("en_EN"), QStringLiteral("en_US"),
QStringLiteral("en_GB")}) {
for (const auto &p : d) {
if (p.first == code && !p.second.isEmpty()) {
return p.second;
}
}
}
for (const auto &p : d) {
if (p.first.startsWith(QStringLiteral("en_")) && !p.second.isEmpty()) {
return p.second;
}
}
return d.first().second;
}
/** {2-letter QET lang -> text}; first variant of each language wins. */
QMap<QString, QString> multilangNames(const QString &value)
{
QMap<QString, QString> names;
for (const auto &p : langDict(value)) {
const QString s = p.first.left(2).toLower();
if (!names.contains(s)) {
names.insert(s, p.second);
}
}
return names;
}
/** Tokenise a designation into (is-text, number, text) runs for natural sort. */
struct NatTok { int type; qlonglong num; QString str; };
QList<NatTok> natKey(const QString &s)
{
QList<NatTok> out;
static const QRegularExpression re(QStringLiteral("\\d+|\\D+"));
auto it = re.globalMatch(s);
while (it.hasNext()) {
const QString t = it.next().captured();
bool all_digit = !t.isEmpty();
for (const QChar c : t) {
if (!c.isDigit()) { all_digit = false; break; }
}
if (all_digit) {
out.append({0, t.toLongLong(), QString()});
} else {
out.append({1, 0, t.toLower()});
}
}
return out;
}
bool natLess(const QString &a, const QString &b)
{
const QList<NatTok> ka = natKey(a), kb = natKey(b);
const int n = qMin(ka.size(), kb.size());
for (int i = 0; i < n; ++i) {
if (ka[i].type != kb[i].type) {
return ka[i].type < kb[i].type;
}
if (ka[i].type == 0) {
if (ka[i].num != kb[i].num) return ka[i].num < kb[i].num;
} else {
if (ka[i].str != kb[i].str) return ka[i].str < kb[i].str;
}
}
return ka.size() < kb.size();
}
} // namespace
/** Parse the part.xml at @a part_xml_path into this object. */
bool EdzPart::parse(const QString &part_xml_path)
{
m_error.clear();
QFile file(part_xml_path);
if (!file.open(QIODevice::ReadOnly)) {
m_error = QStringLiteral("Cannot open %1").arg(part_xml_path);
return false;
}
QDomDocument doc;
QString parse_err;
int line = 0, col = 0;
if (!doc.setContent(&file, &parse_err, &line, &col)) {
m_error = QStringLiteral("Malformed part.xml (line %1): %2")
.arg(line).arg(parse_err);
return false;
}
file.close();
const QDomElement part =
doc.documentElement().firstChildElement(QStringLiteral("part"));
if (part.isNull()) {
m_error = QStringLiteral("No <part> element in part.xml");
return false;
}
m_part_number = part.attribute(QStringLiteral("P_ARTICLE_PARTNR"));
m_manufacturer = part.attribute(QStringLiteral("P_ARTICLE_MANUFACTURER"));
m_order_number = part.attribute(QStringLiteral("P_ARTICLE_ORDERNR"));
m_type_number = part.attribute(QStringLiteral("P_ARTICLE_TYPENR"));
const QString descr1 = part.attribute(QStringLiteral("P_ARTICLE_DESCR1"));
const QString descr2 = part.attribute(QStringLiteral("P_ARTICLE_DESCR2"));
m_description = pickLang(descr1);
m_comment = pickLang(descr2);
m_names = multilangNames(descr1);
const QString pic = part.attribute(QStringLiteral("P_ARTICLE_PICTUREFILE"));
if (!pic.isEmpty()) {
m_picture = QFileInfo(QString(pic).replace('\\', '/')).fileName();
}
// Pins = function templates that carry a physical connection designation.
const QDomNodeList fts =
part.elementsByTagName(QStringLiteral("functiontemplate"));
for (int i = 0; i < fts.size(); ++i) {
const QDomElement ft = fts.at(i).toElement();
const QString desig =
ft.attribute(QStringLiteral("connectionDesignation")).trimmed();
if (desig.isEmpty()) {
continue;
}
EdzPin pin;
pin.designation = desig;
pin.description =
ft.attribute(QStringLiteral("connectiondescription")).trimmed();
// terminalNr identifies the physical connector socket (X01, X31, …) and
// is the preferred group key. Older EPLAN formats may omit it and carry
// a text functiondefinition block name (FINP, MOUT, …) instead, either on
// the <functiontemplate> itself or on its parent wrapper element.
pin.group = ft.attribute(QStringLiteral("terminalNr")).trimmed();
if (pin.group.isEmpty()) {
pin.group = ft.attribute(QStringLiteral("functiondefinition")).trimmed();
if (pin.group.isEmpty())
pin.group = ft.parentNode().toElement()
.attribute(QStringLiteral("functiondefinition")).trimmed();
}
m_pins.append(pin);
}
// Sort: preserve the XML order of functional groups (first-seen wins), and
// within each group sort by designation using natural sort so pins stack
// numerically (1, 2, 3 …) on the symbol.
QMap<QString, int> group_order;
for (const EdzPin &p : m_pins) {
if (!group_order.contains(p.group))
group_order.insert(p.group, group_order.size());
}
std::stable_sort(m_pins.begin(), m_pins.end(),
[&group_order](const EdzPin &a, const EdzPin &b) {
const int ga = group_order.value(a.group, 0);
const int gb = group_order.value(b.group, 0);
if (ga != gb) return ga < gb;
return natLess(a.designation, b.designation);
});
return true;
}
+69
View File
@@ -0,0 +1,69 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech 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 2 of the License, or
(at your option) any later version.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EDZPART_H
#define EDZPART_H
#include <QString>
#include <QList>
#include <QMap>
/** One physical connection of an EPLAN part (a pin/terminal). */
struct EdzPin {
QString designation; ///< terminal id, e.g. "1", "PE"
QString description; ///< function label, e.g. "L+"
QString group; ///< connector group: terminalNr if present, else functiondefinition
};
/**
@brief The portable data of an EPLAN part, read from its part.xml.
Mirrors the fields the standalone edz2qet.py prototype maps: identity and
metadata, localized names (2-letter language -> text) and the connection
list. Geometry from the EPLAN macro is intentionally ignored the importer
generates a generic symbol from the pin list instead.
*/
class EdzPart
{
public:
bool parse(const QString &part_xml_path);
QString errorString() const { return m_error; }
QString partNumber() const { return m_part_number; }
QString manufacturer() const { return m_manufacturer; }
QString orderNumber() const { return m_order_number; }
QString typeNumber() const { return m_type_number; }
QString description() const { return m_description; }
QString comment() const { return m_comment; }
QString picture() const { return m_picture; }
QMap<QString, QString> names() const { return m_names; }
QList<EdzPin> pins() const { return m_pins; }
private:
QString m_error;
QString m_part_number;
QString m_manufacturer;
QString m_order_number;
QString m_type_number;
QString m_description; ///< DESCR1, preferred English (for element-info)
QString m_comment; ///< DESCR2, preferred English
QString m_picture; ///< picture file name, if any
QMap<QString, QString> m_names; ///< 2-letter lang -> localized name
QList<EdzPin> m_pins;
};
#endif // EDZPART_H
+155
View File
@@ -0,0 +1,155 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech 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 2 of the License, or
(at your option) any later version.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "edzsevenzip.h"
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <vector>
// Bundled public-domain LZMA SDK 7-Zip reader (decode only). The headers
// self-guard with EXTERN_C_BEGIN, so they are included directly from C++.
#include "lzma/7z.h"
#include "lzma/7zAlloc.h"
#include "lzma/7zCrc.h"
#include "lzma/7zFile.h"
#include "lzma/7zTypes.h"
namespace {
const size_t kInputBufSize = (size_t)1 << 18;
} // namespace
bool sevenZipExtract(const QString &archivePath, const QString &destDir,
QString &error)
{
error.clear();
ISzAlloc allocImp = { SzAlloc, SzFree };
ISzAlloc allocTempImp = { SzAllocTemp, SzFreeTemp };
CFileInStream archiveStream;
#ifdef _WIN32
if (InFile_OpenW(&archiveStream.file,
reinterpret_cast<const wchar_t *>(archivePath.utf16())) != 0)
#else
const QByteArray apath = QFile::encodeName(archivePath);
if (InFile_Open(&archiveStream.file, apath.constData()) != 0)
#endif
{
error = QStringLiteral("Cannot open archive: %1").arg(archivePath);
return false;
}
FileInStream_CreateVTable(&archiveStream);
archiveStream.wres = 0;
CLookToRead2 lookStream;
LookToRead2_CreateVTable(&lookStream, False);
lookStream.buf = nullptr;
SRes res = SZ_OK;
lookStream.buf = static_cast<Byte *>(ISzAlloc_Alloc(&allocImp, kInputBufSize));
if (!lookStream.buf) {
res = SZ_ERROR_MEM;
} else {
lookStream.bufSize = kInputBufSize;
lookStream.realStream = &archiveStream.vt;
LookToRead2_INIT(&lookStream)
}
CrcGenerateTable();
CSzArEx db;
SzArEx_Init(&db);
if (res == SZ_OK) {
res = SzArEx_Open(&db, &lookStream.vt, &allocImp, &allocTempImp);
}
bool ok = (res == SZ_OK);
if (ok) {
const QDir dest(destDir);
// Cache shared across entries of the same solid block (see SzArEx_Extract).
UInt32 blockIndex = 0xFFFFFFFF;
Byte *outBuffer = nullptr;
size_t outBufferSize = 0;
std::vector<UInt16> name;
for (UInt32 i = 0; i < db.NumFiles && res == SZ_OK; ++i) {
const BoolInt is_dir = SzArEx_IsDir(&db, i);
const size_t len = SzArEx_GetFileNameUtf16(&db, i, nullptr);
name.resize(len ? len : 1);
SzArEx_GetFileNameUtf16(&db, i, name.data());
QString rel = QString::fromUtf16(
reinterpret_cast<const char16_t *>(name.data()));
rel.replace(QLatin1Char('\\'), QLatin1Char('/'));
if (rel.isEmpty()) {
continue;
}
if (is_dir) {
dest.mkpath(rel);
continue;
}
const QString out_path = dest.filePath(rel);
dest.mkpath(QFileInfo(out_path).path());
size_t offset = 0, outSizeProcessed = 0;
res = SzArEx_Extract(&db, &lookStream.vt, i, &blockIndex,
&outBuffer, &outBufferSize,
&offset, &outSizeProcessed,
&allocImp, &allocTempImp);
if (res != SZ_OK) {
break;
}
QFile f(out_path);
if (!f.open(QIODevice::WriteOnly)) {
error = QStringLiteral("Cannot write %1").arg(out_path);
res = SZ_ERROR_FAIL;
break;
}
const qint64 want = static_cast<qint64>(outSizeProcessed);
if (f.write(reinterpret_cast<const char *>(outBuffer + offset),
want) != want) {
error = QStringLiteral("Short write to %1").arg(out_path);
res = SZ_ERROR_FAIL;
f.close();
break;
}
f.close();
}
ISzAlloc_Free(&allocImp, outBuffer);
}
SzArEx_Free(&db, &allocImp);
ISzAlloc_Free(&allocImp, lookStream.buf);
File_Close(&archiveStream.file);
if (res != SZ_OK) {
if (error.isEmpty()) {
error = QStringLiteral("7z decode failed (code %1)")
.arg(static_cast<int>(res));
}
return false;
}
return true;
}
+31
View File
@@ -0,0 +1,31 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech 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 2 of the License, or
(at your option) any later version.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EDZSEVENZIP_H
#define EDZSEVENZIP_H
#include <QString>
/**
Extract every entry of the 7-Zip archive at @a archivePath into @a destDir,
using the bundled (public-domain) LZMA SDK no external 7-Zip needed.
@return true on success; on failure @a error explains why.
*/
bool sevenZipExtract(const QString &archivePath, const QString &destDir,
QString &error);
#endif // EDZSEVENZIP_H
+204
View File
@@ -0,0 +1,204 @@
/* 7z.h -- 7z interface
2023-04-02 : Igor Pavlov : Public domain */
#ifndef ZIP7_INC_7Z_H
#define ZIP7_INC_7Z_H
#include "7zTypes.h"
EXTERN_C_BEGIN
#define k7zStartHeaderSize 0x20
#define k7zSignatureSize 6
extern const Byte k7zSignature[k7zSignatureSize];
typedef struct
{
const Byte *Data;
size_t Size;
} CSzData;
/* CSzCoderInfo & CSzFolder support only default methods */
typedef struct
{
size_t PropsOffset;
UInt32 MethodID;
Byte NumStreams;
Byte PropsSize;
} CSzCoderInfo;
typedef struct
{
UInt32 InIndex;
UInt32 OutIndex;
} CSzBond;
#define SZ_NUM_CODERS_IN_FOLDER_MAX 4
#define SZ_NUM_BONDS_IN_FOLDER_MAX 3
#define SZ_NUM_PACK_STREAMS_IN_FOLDER_MAX 4
typedef struct
{
UInt32 NumCoders;
UInt32 NumBonds;
UInt32 NumPackStreams;
UInt32 UnpackStream;
UInt32 PackStreams[SZ_NUM_PACK_STREAMS_IN_FOLDER_MAX];
CSzBond Bonds[SZ_NUM_BONDS_IN_FOLDER_MAX];
CSzCoderInfo Coders[SZ_NUM_CODERS_IN_FOLDER_MAX];
} CSzFolder;
SRes SzGetNextFolderItem(CSzFolder *f, CSzData *sd);
typedef struct
{
UInt32 Low;
UInt32 High;
} CNtfsFileTime;
typedef struct
{
Byte *Defs; /* MSB 0 bit numbering */
UInt32 *Vals;
} CSzBitUi32s;
typedef struct
{
Byte *Defs; /* MSB 0 bit numbering */
// UInt64 *Vals;
CNtfsFileTime *Vals;
} CSzBitUi64s;
#define SzBitArray_Check(p, i) (((p)[(i) >> 3] & (0x80 >> ((i) & 7))) != 0)
#define SzBitWithVals_Check(p, i) ((p)->Defs && ((p)->Defs[(i) >> 3] & (0x80 >> ((i) & 7))) != 0)
typedef struct
{
UInt32 NumPackStreams;
UInt32 NumFolders;
UInt64 *PackPositions; // NumPackStreams + 1
CSzBitUi32s FolderCRCs; // NumFolders
size_t *FoCodersOffsets; // NumFolders + 1
UInt32 *FoStartPackStreamIndex; // NumFolders + 1
UInt32 *FoToCoderUnpackSizes; // NumFolders + 1
Byte *FoToMainUnpackSizeIndex; // NumFolders
UInt64 *CoderUnpackSizes; // for all coders in all folders
Byte *CodersData;
UInt64 RangeLimit;
} CSzAr;
UInt64 SzAr_GetFolderUnpackSize(const CSzAr *p, UInt32 folderIndex);
SRes SzAr_DecodeFolder(const CSzAr *p, UInt32 folderIndex,
ILookInStreamPtr stream, UInt64 startPos,
Byte *outBuffer, size_t outSize,
ISzAllocPtr allocMain);
typedef struct
{
CSzAr db;
UInt64 startPosAfterHeader;
UInt64 dataPos;
UInt32 NumFiles;
UInt64 *UnpackPositions; // NumFiles + 1
// Byte *IsEmptyFiles;
Byte *IsDirs;
CSzBitUi32s CRCs;
CSzBitUi32s Attribs;
// CSzBitUi32s Parents;
CSzBitUi64s MTime;
CSzBitUi64s CTime;
UInt32 *FolderToFile; // NumFolders + 1
UInt32 *FileToFolder; // NumFiles
size_t *FileNameOffsets; /* in 2-byte steps */
Byte *FileNames; /* UTF-16-LE */
} CSzArEx;
#define SzArEx_IsDir(p, i) (SzBitArray_Check((p)->IsDirs, i))
#define SzArEx_GetFileSize(p, i) ((p)->UnpackPositions[(i) + 1] - (p)->UnpackPositions[i])
void SzArEx_Init(CSzArEx *p);
void SzArEx_Free(CSzArEx *p, ISzAllocPtr alloc);
UInt64 SzArEx_GetFolderStreamPos(const CSzArEx *p, UInt32 folderIndex, UInt32 indexInFolder);
int SzArEx_GetFolderFullPackSize(const CSzArEx *p, UInt32 folderIndex, UInt64 *resSize);
/*
if dest == NULL, the return value specifies the required size of the buffer,
in 16-bit characters, including the null-terminating character.
if dest != NULL, the return value specifies the number of 16-bit characters that
are written to the dest, including the null-terminating character. */
size_t SzArEx_GetFileNameUtf16(const CSzArEx *p, size_t fileIndex, UInt16 *dest);
/*
size_t SzArEx_GetFullNameLen(const CSzArEx *p, size_t fileIndex);
UInt16 *SzArEx_GetFullNameUtf16_Back(const CSzArEx *p, size_t fileIndex, UInt16 *dest);
*/
/*
SzArEx_Extract extracts file from archive
*outBuffer must be 0 before first call for each new archive.
Extracting cache:
If you need to decompress more than one file, you can send
these values from previous call:
*blockIndex,
*outBuffer,
*outBufferSize
You can consider "*outBuffer" as cache of solid block. If your archive is solid,
it will increase decompression speed.
If you use external function, you can declare these 3 cache variables
(blockIndex, outBuffer, outBufferSize) as static in that external function.
Free *outBuffer and set *outBuffer to 0, if you want to flush cache.
*/
SRes SzArEx_Extract(
const CSzArEx *db,
ILookInStreamPtr inStream,
UInt32 fileIndex, /* index of file */
UInt32 *blockIndex, /* index of solid block */
Byte **outBuffer, /* pointer to pointer to output buffer (allocated with allocMain) */
size_t *outBufferSize, /* buffer size for output buffer */
size_t *offset, /* offset of stream for required file in *outBuffer */
size_t *outSizeProcessed, /* size of file in *outBuffer */
ISzAllocPtr allocMain,
ISzAllocPtr allocTemp);
/*
SzArEx_Open Errors:
SZ_ERROR_NO_ARCHIVE
SZ_ERROR_ARCHIVE
SZ_ERROR_UNSUPPORTED
SZ_ERROR_MEM
SZ_ERROR_CRC
SZ_ERROR_INPUT_EOF
SZ_ERROR_FAIL
*/
SRes SzArEx_Open(CSzArEx *p, ILookInStreamPtr inStream,
ISzAllocPtr allocMain, ISzAllocPtr allocTemp);
EXTERN_C_END
#endif
+89
View File
@@ -0,0 +1,89 @@
/* 7zAlloc.c -- Allocation functions for 7z processing
2023-03-04 : Igor Pavlov : Public domain */
#include "Precomp.h"
#include <stdlib.h>
#include "7zAlloc.h"
/* #define SZ_ALLOC_DEBUG */
/* use SZ_ALLOC_DEBUG to debug alloc/free operations */
#ifdef SZ_ALLOC_DEBUG
/*
#ifdef _WIN32
#include "7zWindows.h"
#endif
*/
#include <stdio.h>
static int g_allocCount = 0;
static int g_allocCountTemp = 0;
static void Print_Alloc(const char *s, size_t size, int *counter)
{
const unsigned size2 = (unsigned)size;
fprintf(stderr, "\n%s count = %10d : %10u bytes; ", s, *counter, size2);
(*counter)++;
}
static void Print_Free(const char *s, int *counter)
{
(*counter)--;
fprintf(stderr, "\n%s count = %10d", s, *counter);
}
#endif
void *SzAlloc(ISzAllocPtr p, size_t size)
{
UNUSED_VAR(p)
if (size == 0)
return 0;
#ifdef SZ_ALLOC_DEBUG
Print_Alloc("Alloc", size, &g_allocCount);
#endif
return malloc(size);
}
void SzFree(ISzAllocPtr p, void *address)
{
UNUSED_VAR(p)
#ifdef SZ_ALLOC_DEBUG
if (address)
Print_Free("Free ", &g_allocCount);
#endif
free(address);
}
void *SzAllocTemp(ISzAllocPtr p, size_t size)
{
UNUSED_VAR(p)
if (size == 0)
return 0;
#ifdef SZ_ALLOC_DEBUG
Print_Alloc("Alloc_temp", size, &g_allocCountTemp);
/*
#ifdef _WIN32
return HeapAlloc(GetProcessHeap(), 0, size);
#endif
*/
#endif
return malloc(size);
}
void SzFreeTemp(ISzAllocPtr p, void *address)
{
UNUSED_VAR(p)
#ifdef SZ_ALLOC_DEBUG
if (address)
Print_Free("Free_temp ", &g_allocCountTemp);
/*
#ifdef _WIN32
HeapFree(GetProcessHeap(), 0, address);
return;
#endif
*/
#endif
free(address);
}
+19
View File
@@ -0,0 +1,19 @@
/* 7zAlloc.h -- Allocation functions
2023-03-04 : Igor Pavlov : Public domain */
#ifndef ZIP7_INC_7Z_ALLOC_H
#define ZIP7_INC_7Z_ALLOC_H
#include "7zTypes.h"
EXTERN_C_BEGIN
void *SzAlloc(ISzAllocPtr p, size_t size);
void SzFree(ISzAllocPtr p, void *address);
void *SzAllocTemp(ISzAllocPtr p, size_t size);
void SzFreeTemp(ISzAllocPtr p, void *address);
EXTERN_C_END
#endif
File diff suppressed because it is too large Load Diff
+36
View File
@@ -0,0 +1,36 @@
/* 7zBuf.c -- Byte Buffer
2017-04-03 : Igor Pavlov : Public domain */
#include "Precomp.h"
#include "7zBuf.h"
void Buf_Init(CBuf *p)
{
p->data = 0;
p->size = 0;
}
int Buf_Create(CBuf *p, size_t size, ISzAllocPtr alloc)
{
p->size = 0;
if (size == 0)
{
p->data = 0;
return 1;
}
p->data = (Byte *)ISzAlloc_Alloc(alloc, size);
if (p->data)
{
p->size = size;
return 1;
}
return 0;
}
void Buf_Free(CBuf *p, ISzAllocPtr alloc)
{
ISzAlloc_Free(alloc, p->data);
p->data = 0;
p->size = 0;
}
+35
View File
@@ -0,0 +1,35 @@
/* 7zBuf.h -- Byte Buffer
2023-03-04 : Igor Pavlov : Public domain */
#ifndef ZIP7_INC_7Z_BUF_H
#define ZIP7_INC_7Z_BUF_H
#include "7zTypes.h"
EXTERN_C_BEGIN
typedef struct
{
Byte *data;
size_t size;
} CBuf;
void Buf_Init(CBuf *p);
int Buf_Create(CBuf *p, size_t size, ISzAllocPtr alloc);
void Buf_Free(CBuf *p, ISzAllocPtr alloc);
typedef struct
{
Byte *data;
size_t size;
size_t pos;
} CDynBuf;
void DynBuf_Construct(CDynBuf *p);
void DynBuf_SeekToBeg(CDynBuf *p);
int DynBuf_Write(CDynBuf *p, const Byte *buf, size_t size, ISzAllocPtr alloc);
void DynBuf_Free(CDynBuf *p, ISzAllocPtr alloc);
EXTERN_C_END
#endif
+340
View File
@@ -0,0 +1,340 @@
/* 7zCrc.c -- CRC32 calculation and init
2023-04-02 : Igor Pavlov : Public domain */
#include "Precomp.h"
#include "7zCrc.h"
#include "CpuArch.h"
#define kCrcPoly 0xEDB88320
#ifdef MY_CPU_LE
#define CRC_NUM_TABLES 8
#else
#define CRC_NUM_TABLES 9
UInt32 Z7_FASTCALL CrcUpdateT1_BeT4(UInt32 v, const void *data, size_t size, const UInt32 *table);
UInt32 Z7_FASTCALL CrcUpdateT1_BeT8(UInt32 v, const void *data, size_t size, const UInt32 *table);
#endif
#ifndef MY_CPU_BE
UInt32 Z7_FASTCALL CrcUpdateT4(UInt32 v, const void *data, size_t size, const UInt32 *table);
UInt32 Z7_FASTCALL CrcUpdateT8(UInt32 v, const void *data, size_t size, const UInt32 *table);
#endif
/*
extern
CRC_FUNC g_CrcUpdateT4;
CRC_FUNC g_CrcUpdateT4;
*/
extern
CRC_FUNC g_CrcUpdateT8;
CRC_FUNC g_CrcUpdateT8;
extern
CRC_FUNC g_CrcUpdateT0_32;
CRC_FUNC g_CrcUpdateT0_32;
extern
CRC_FUNC g_CrcUpdateT0_64;
CRC_FUNC g_CrcUpdateT0_64;
extern
CRC_FUNC g_CrcUpdate;
CRC_FUNC g_CrcUpdate;
UInt32 g_CrcTable[256 * CRC_NUM_TABLES];
UInt32 Z7_FASTCALL CrcUpdate(UInt32 v, const void *data, size_t size)
{
return g_CrcUpdate(v, data, size, g_CrcTable);
}
UInt32 Z7_FASTCALL CrcCalc(const void *data, size_t size)
{
return g_CrcUpdate(CRC_INIT_VAL, data, size, g_CrcTable) ^ CRC_INIT_VAL;
}
#if CRC_NUM_TABLES < 4 \
|| (CRC_NUM_TABLES == 4 && defined(MY_CPU_BE)) \
|| (!defined(MY_CPU_LE) && !defined(MY_CPU_BE))
#define CRC_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8))
UInt32 Z7_FASTCALL CrcUpdateT1(UInt32 v, const void *data, size_t size, const UInt32 *table);
UInt32 Z7_FASTCALL CrcUpdateT1(UInt32 v, const void *data, size_t size, const UInt32 *table)
{
const Byte *p = (const Byte *)data;
const Byte *pEnd = p + size;
for (; p != pEnd; p++)
v = CRC_UPDATE_BYTE_2(v, *p);
return v;
}
#endif
/* ---------- hardware CRC ---------- */
#ifdef MY_CPU_LE
#if defined(MY_CPU_ARM_OR_ARM64)
// #pragma message("ARM*")
#if defined(_MSC_VER)
#if defined(MY_CPU_ARM64)
#if (_MSC_VER >= 1910)
#ifndef __clang__
#define USE_ARM64_CRC
#include <intrin.h>
#endif
#endif
#endif
#elif (defined(__clang__) && (__clang_major__ >= 3)) \
|| (defined(__GNUC__) && (__GNUC__ > 4))
#if !defined(__ARM_FEATURE_CRC32)
#define __ARM_FEATURE_CRC32 1
#if defined(__clang__)
#if defined(MY_CPU_ARM64)
#define ATTRIB_CRC __attribute__((__target__("crc")))
#else
#define ATTRIB_CRC __attribute__((__target__("armv8-a,crc")))
#endif
#else
#if defined(MY_CPU_ARM64)
#define ATTRIB_CRC __attribute__((__target__("+crc")))
#else
#define ATTRIB_CRC __attribute__((__target__("arch=armv8-a+crc")))
#endif
#endif
#endif
#if defined(__ARM_FEATURE_CRC32)
#define USE_ARM64_CRC
#include <arm_acle.h>
#endif
#endif
#else
// no hardware CRC
// #define USE_CRC_EMU
#ifdef USE_CRC_EMU
#pragma message("ARM64 CRC emulation")
Z7_FORCE_INLINE
UInt32 __crc32b(UInt32 v, UInt32 data)
{
const UInt32 *table = g_CrcTable;
v = CRC_UPDATE_BYTE_2(v, (Byte)data);
return v;
}
Z7_FORCE_INLINE
UInt32 __crc32w(UInt32 v, UInt32 data)
{
const UInt32 *table = g_CrcTable;
v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
return v;
}
Z7_FORCE_INLINE
UInt32 __crc32d(UInt32 v, UInt64 data)
{
const UInt32 *table = g_CrcTable;
v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
return v;
}
#endif // USE_CRC_EMU
#endif // defined(MY_CPU_ARM64) && defined(MY_CPU_LE)
#if defined(USE_ARM64_CRC) || defined(USE_CRC_EMU)
#define T0_32_UNROLL_BYTES (4 * 4)
#define T0_64_UNROLL_BYTES (4 * 8)
#ifndef ATTRIB_CRC
#define ATTRIB_CRC
#endif
// #pragma message("USE ARM HW CRC")
ATTRIB_CRC
UInt32 Z7_FASTCALL CrcUpdateT0_32(UInt32 v, const void *data, size_t size, const UInt32 *table);
ATTRIB_CRC
UInt32 Z7_FASTCALL CrcUpdateT0_32(UInt32 v, const void *data, size_t size, const UInt32 *table)
{
const Byte *p = (const Byte *)data;
UNUSED_VAR(table);
for (; size != 0 && ((unsigned)(ptrdiff_t)p & (T0_32_UNROLL_BYTES - 1)) != 0; size--)
v = __crc32b(v, *p++);
if (size >= T0_32_UNROLL_BYTES)
{
const Byte *lim = p + size;
size &= (T0_32_UNROLL_BYTES - 1);
lim -= size;
do
{
v = __crc32w(v, *(const UInt32 *)(const void *)(p));
v = __crc32w(v, *(const UInt32 *)(const void *)(p + 4)); p += 2 * 4;
v = __crc32w(v, *(const UInt32 *)(const void *)(p));
v = __crc32w(v, *(const UInt32 *)(const void *)(p + 4)); p += 2 * 4;
}
while (p != lim);
}
for (; size != 0; size--)
v = __crc32b(v, *p++);
return v;
}
ATTRIB_CRC
UInt32 Z7_FASTCALL CrcUpdateT0_64(UInt32 v, const void *data, size_t size, const UInt32 *table);
ATTRIB_CRC
UInt32 Z7_FASTCALL CrcUpdateT0_64(UInt32 v, const void *data, size_t size, const UInt32 *table)
{
const Byte *p = (const Byte *)data;
UNUSED_VAR(table);
for (; size != 0 && ((unsigned)(ptrdiff_t)p & (T0_64_UNROLL_BYTES - 1)) != 0; size--)
v = __crc32b(v, *p++);
if (size >= T0_64_UNROLL_BYTES)
{
const Byte *lim = p + size;
size &= (T0_64_UNROLL_BYTES - 1);
lim -= size;
do
{
v = __crc32d(v, *(const UInt64 *)(const void *)(p));
v = __crc32d(v, *(const UInt64 *)(const void *)(p + 8)); p += 2 * 8;
v = __crc32d(v, *(const UInt64 *)(const void *)(p));
v = __crc32d(v, *(const UInt64 *)(const void *)(p + 8)); p += 2 * 8;
}
while (p != lim);
}
for (; size != 0; size--)
v = __crc32b(v, *p++);
return v;
}
#undef T0_32_UNROLL_BYTES
#undef T0_64_UNROLL_BYTES
#endif // defined(USE_ARM64_CRC) || defined(USE_CRC_EMU)
#endif // MY_CPU_LE
void Z7_FASTCALL CrcGenerateTable(void)
{
UInt32 i;
for (i = 0; i < 256; i++)
{
UInt32 r = i;
unsigned j;
for (j = 0; j < 8; j++)
r = (r >> 1) ^ (kCrcPoly & ((UInt32)0 - (r & 1)));
g_CrcTable[i] = r;
}
for (i = 256; i < 256 * CRC_NUM_TABLES; i++)
{
const UInt32 r = g_CrcTable[(size_t)i - 256];
g_CrcTable[i] = g_CrcTable[r & 0xFF] ^ (r >> 8);
}
#if CRC_NUM_TABLES < 4
g_CrcUpdate = CrcUpdateT1;
#elif defined(MY_CPU_LE)
// g_CrcUpdateT4 = CrcUpdateT4;
#if CRC_NUM_TABLES < 8
g_CrcUpdate = CrcUpdateT4;
#else // CRC_NUM_TABLES >= 8
g_CrcUpdateT8 = CrcUpdateT8;
/*
#ifdef MY_CPU_X86_OR_AMD64
if (!CPU_Is_InOrder())
#endif
*/
g_CrcUpdate = CrcUpdateT8;
#endif
#else
{
#ifndef MY_CPU_BE
UInt32 k = 0x01020304;
const Byte *p = (const Byte *)&k;
if (p[0] == 4 && p[1] == 3)
{
#if CRC_NUM_TABLES < 8
// g_CrcUpdateT4 = CrcUpdateT4;
g_CrcUpdate = CrcUpdateT4;
#else // CRC_NUM_TABLES >= 8
g_CrcUpdateT8 = CrcUpdateT8;
g_CrcUpdate = CrcUpdateT8;
#endif
}
else if (p[0] != 1 || p[1] != 2)
g_CrcUpdate = CrcUpdateT1;
else
#endif // MY_CPU_BE
{
for (i = 256 * CRC_NUM_TABLES - 1; i >= 256; i--)
{
const UInt32 x = g_CrcTable[(size_t)i - 256];
g_CrcTable[i] = Z7_BSWAP32(x);
}
#if CRC_NUM_TABLES <= 4
g_CrcUpdate = CrcUpdateT1;
#elif CRC_NUM_TABLES <= 8
// g_CrcUpdateT4 = CrcUpdateT1_BeT4;
g_CrcUpdate = CrcUpdateT1_BeT4;
#else // CRC_NUM_TABLES > 8
g_CrcUpdateT8 = CrcUpdateT1_BeT8;
g_CrcUpdate = CrcUpdateT1_BeT8;
#endif
}
}
#endif // CRC_NUM_TABLES < 4
#ifdef MY_CPU_LE
#ifdef USE_ARM64_CRC
if (CPU_IsSupported_CRC32())
{
g_CrcUpdateT0_32 = CrcUpdateT0_32;
g_CrcUpdateT0_64 = CrcUpdateT0_64;
g_CrcUpdate =
#if defined(MY_CPU_ARM)
CrcUpdateT0_32;
#else
CrcUpdateT0_64;
#endif
}
#endif
#ifdef USE_CRC_EMU
g_CrcUpdateT0_32 = CrcUpdateT0_32;
g_CrcUpdateT0_64 = CrcUpdateT0_64;
g_CrcUpdate = CrcUpdateT0_64;
#endif
#endif
}
#undef kCrcPoly
#undef CRC64_NUM_TABLES
#undef CRC_UPDATE_BYTE_2
+27
View File
@@ -0,0 +1,27 @@
/* 7zCrc.h -- CRC32 calculation
2023-04-02 : Igor Pavlov : Public domain */
#ifndef ZIP7_INC_7Z_CRC_H
#define ZIP7_INC_7Z_CRC_H
#include "7zTypes.h"
EXTERN_C_BEGIN
extern UInt32 g_CrcTable[];
/* Call CrcGenerateTable one time before other CRC functions */
void Z7_FASTCALL CrcGenerateTable(void);
#define CRC_INIT_VAL 0xFFFFFFFF
#define CRC_GET_DIGEST(crc) ((crc) ^ CRC_INIT_VAL)
#define CRC_UPDATE_BYTE(crc, b) (g_CrcTable[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8))
UInt32 Z7_FASTCALL CrcUpdate(UInt32 crc, const void *data, size_t size);
UInt32 Z7_FASTCALL CrcCalc(const void *data, size_t size);
typedef UInt32 (Z7_FASTCALL *CRC_FUNC)(UInt32 v, const void *data, size_t size, const UInt32 *table);
EXTERN_C_END
#endif
+117
View File
@@ -0,0 +1,117 @@
/* 7zCrcOpt.c -- CRC32 calculation
2023-04-02 : Igor Pavlov : Public domain */
#include "Precomp.h"
#include "CpuArch.h"
#ifndef MY_CPU_BE
#define CRC_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8))
UInt32 Z7_FASTCALL CrcUpdateT4(UInt32 v, const void *data, size_t size, const UInt32 *table);
UInt32 Z7_FASTCALL CrcUpdateT4(UInt32 v, const void *data, size_t size, const UInt32 *table)
{
const Byte *p = (const Byte *)data;
for (; size > 0 && ((unsigned)(ptrdiff_t)p & 3) != 0; size--, p++)
v = CRC_UPDATE_BYTE_2(v, *p);
for (; size >= 4; size -= 4, p += 4)
{
v ^= *(const UInt32 *)(const void *)p;
v =
(table + 0x300)[((v ) & 0xFF)]
^ (table + 0x200)[((v >> 8) & 0xFF)]
^ (table + 0x100)[((v >> 16) & 0xFF)]
^ (table + 0x000)[((v >> 24))];
}
for (; size > 0; size--, p++)
v = CRC_UPDATE_BYTE_2(v, *p);
return v;
}
UInt32 Z7_FASTCALL CrcUpdateT8(UInt32 v, const void *data, size_t size, const UInt32 *table);
UInt32 Z7_FASTCALL CrcUpdateT8(UInt32 v, const void *data, size_t size, const UInt32 *table)
{
const Byte *p = (const Byte *)data;
for (; size > 0 && ((unsigned)(ptrdiff_t)p & 7) != 0; size--, p++)
v = CRC_UPDATE_BYTE_2(v, *p);
for (; size >= 8; size -= 8, p += 8)
{
UInt32 d;
v ^= *(const UInt32 *)(const void *)p;
v =
(table + 0x700)[((v ) & 0xFF)]
^ (table + 0x600)[((v >> 8) & 0xFF)]
^ (table + 0x500)[((v >> 16) & 0xFF)]
^ (table + 0x400)[((v >> 24))];
d = *((const UInt32 *)(const void *)p + 1);
v ^=
(table + 0x300)[((d ) & 0xFF)]
^ (table + 0x200)[((d >> 8) & 0xFF)]
^ (table + 0x100)[((d >> 16) & 0xFF)]
^ (table + 0x000)[((d >> 24))];
}
for (; size > 0; size--, p++)
v = CRC_UPDATE_BYTE_2(v, *p);
return v;
}
#endif
#ifndef MY_CPU_LE
#define CRC_UINT32_SWAP(v) Z7_BSWAP32(v)
#define CRC_UPDATE_BYTE_2_BE(crc, b) (table[(((crc) >> 24) ^ (b))] ^ ((crc) << 8))
UInt32 Z7_FASTCALL CrcUpdateT1_BeT4(UInt32 v, const void *data, size_t size, const UInt32 *table)
{
const Byte *p = (const Byte *)data;
table += 0x100;
v = CRC_UINT32_SWAP(v);
for (; size > 0 && ((unsigned)(ptrdiff_t)p & 3) != 0; size--, p++)
v = CRC_UPDATE_BYTE_2_BE(v, *p);
for (; size >= 4; size -= 4, p += 4)
{
v ^= *(const UInt32 *)(const void *)p;
v =
(table + 0x000)[((v ) & 0xFF)]
^ (table + 0x100)[((v >> 8) & 0xFF)]
^ (table + 0x200)[((v >> 16) & 0xFF)]
^ (table + 0x300)[((v >> 24))];
}
for (; size > 0; size--, p++)
v = CRC_UPDATE_BYTE_2_BE(v, *p);
return CRC_UINT32_SWAP(v);
}
UInt32 Z7_FASTCALL CrcUpdateT1_BeT8(UInt32 v, const void *data, size_t size, const UInt32 *table)
{
const Byte *p = (const Byte *)data;
table += 0x100;
v = CRC_UINT32_SWAP(v);
for (; size > 0 && ((unsigned)(ptrdiff_t)p & 7) != 0; size--, p++)
v = CRC_UPDATE_BYTE_2_BE(v, *p);
for (; size >= 8; size -= 8, p += 8)
{
UInt32 d;
v ^= *(const UInt32 *)(const void *)p;
v =
(table + 0x400)[((v ) & 0xFF)]
^ (table + 0x500)[((v >> 8) & 0xFF)]
^ (table + 0x600)[((v >> 16) & 0xFF)]
^ (table + 0x700)[((v >> 24))];
d = *((const UInt32 *)(const void *)p + 1);
v ^=
(table + 0x000)[((d ) & 0xFF)]
^ (table + 0x100)[((d >> 8) & 0xFF)]
^ (table + 0x200)[((d >> 16) & 0xFF)]
^ (table + 0x300)[((d >> 24))];
}
for (; size > 0; size--, p++)
v = CRC_UPDATE_BYTE_2_BE(v, *p);
return CRC_UINT32_SWAP(v);
}
#endif
+648
View File
@@ -0,0 +1,648 @@
/* 7zDec.c -- Decoding from 7z folder
2023-04-02 : Igor Pavlov : Public domain */
#include "Precomp.h"
#include <string.h>
/* #define Z7_PPMD_SUPPORT */
#include "7z.h"
#include "7zCrc.h"
#include "Bcj2.h"
#include "Bra.h"
#include "CpuArch.h"
#include "Delta.h"
#include "LzmaDec.h"
#include "Lzma2Dec.h"
#ifdef Z7_PPMD_SUPPORT
#include "Ppmd7.h"
#endif
#define k_Copy 0
#ifndef Z7_NO_METHOD_LZMA2
#define k_LZMA2 0x21
#endif
#define k_LZMA 0x30101
#define k_BCJ2 0x303011B
#if !defined(Z7_NO_METHODS_FILTERS)
#define Z7_USE_BRANCH_FILTER
#endif
#if !defined(Z7_NO_METHODS_FILTERS) || \
defined(Z7_USE_NATIVE_BRANCH_FILTER) && defined(MY_CPU_ARM64)
#define Z7_USE_FILTER_ARM64
#ifndef Z7_USE_BRANCH_FILTER
#define Z7_USE_BRANCH_FILTER
#endif
#define k_ARM64 0xa
#endif
#if !defined(Z7_NO_METHODS_FILTERS) || \
defined(Z7_USE_NATIVE_BRANCH_FILTER) && defined(MY_CPU_ARMT)
#define Z7_USE_FILTER_ARMT
#ifndef Z7_USE_BRANCH_FILTER
#define Z7_USE_BRANCH_FILTER
#endif
#define k_ARMT 0x3030701
#endif
#ifndef Z7_NO_METHODS_FILTERS
#define k_Delta 3
#define k_BCJ 0x3030103
#define k_PPC 0x3030205
#define k_IA64 0x3030401
#define k_ARM 0x3030501
#define k_SPARC 0x3030805
#endif
#ifdef Z7_PPMD_SUPPORT
#define k_PPMD 0x30401
typedef struct
{
IByteIn vt;
const Byte *cur;
const Byte *end;
const Byte *begin;
UInt64 processed;
BoolInt extra;
SRes res;
ILookInStreamPtr inStream;
} CByteInToLook;
static Byte ReadByte(IByteInPtr pp)
{
Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CByteInToLook)
if (p->cur != p->end)
return *p->cur++;
if (p->res == SZ_OK)
{
size_t size = (size_t)(p->cur - p->begin);
p->processed += size;
p->res = ILookInStream_Skip(p->inStream, size);
size = (1 << 25);
p->res = ILookInStream_Look(p->inStream, (const void **)&p->begin, &size);
p->cur = p->begin;
p->end = p->begin + size;
if (size != 0)
return *p->cur++;
}
p->extra = True;
return 0;
}
static SRes SzDecodePpmd(const Byte *props, unsigned propsSize, UInt64 inSize, ILookInStreamPtr inStream,
Byte *outBuffer, SizeT outSize, ISzAllocPtr allocMain)
{
CPpmd7 ppmd;
CByteInToLook s;
SRes res = SZ_OK;
s.vt.Read = ReadByte;
s.inStream = inStream;
s.begin = s.end = s.cur = NULL;
s.extra = False;
s.res = SZ_OK;
s.processed = 0;
if (propsSize != 5)
return SZ_ERROR_UNSUPPORTED;
{
unsigned order = props[0];
UInt32 memSize = GetUi32(props + 1);
if (order < PPMD7_MIN_ORDER ||
order > PPMD7_MAX_ORDER ||
memSize < PPMD7_MIN_MEM_SIZE ||
memSize > PPMD7_MAX_MEM_SIZE)
return SZ_ERROR_UNSUPPORTED;
Ppmd7_Construct(&ppmd);
if (!Ppmd7_Alloc(&ppmd, memSize, allocMain))
return SZ_ERROR_MEM;
Ppmd7_Init(&ppmd, order);
}
{
ppmd.rc.dec.Stream = &s.vt;
if (!Ppmd7z_RangeDec_Init(&ppmd.rc.dec))
res = SZ_ERROR_DATA;
else if (!s.extra)
{
Byte *buf = outBuffer;
const Byte *lim = buf + outSize;
for (; buf != lim; buf++)
{
int sym = Ppmd7z_DecodeSymbol(&ppmd);
if (s.extra || sym < 0)
break;
*buf = (Byte)sym;
}
if (buf != lim)
res = SZ_ERROR_DATA;
else if (!Ppmd7z_RangeDec_IsFinishedOK(&ppmd.rc.dec))
{
/* if (Ppmd7z_DecodeSymbol(&ppmd) != PPMD7_SYM_END || !Ppmd7z_RangeDec_IsFinishedOK(&ppmd.rc.dec)) */
res = SZ_ERROR_DATA;
}
}
if (s.extra)
res = (s.res != SZ_OK ? s.res : SZ_ERROR_DATA);
else if (s.processed + (size_t)(s.cur - s.begin) != inSize)
res = SZ_ERROR_DATA;
}
Ppmd7_Free(&ppmd, allocMain);
return res;
}
#endif
static SRes SzDecodeLzma(const Byte *props, unsigned propsSize, UInt64 inSize, ILookInStreamPtr inStream,
Byte *outBuffer, SizeT outSize, ISzAllocPtr allocMain)
{
CLzmaDec state;
SRes res = SZ_OK;
LzmaDec_CONSTRUCT(&state)
RINOK(LzmaDec_AllocateProbs(&state, props, propsSize, allocMain))
state.dic = outBuffer;
state.dicBufSize = outSize;
LzmaDec_Init(&state);
for (;;)
{
const void *inBuf = NULL;
size_t lookahead = (1 << 18);
if (lookahead > inSize)
lookahead = (size_t)inSize;
res = ILookInStream_Look(inStream, &inBuf, &lookahead);
if (res != SZ_OK)
break;
{
SizeT inProcessed = (SizeT)lookahead, dicPos = state.dicPos;
ELzmaStatus status;
res = LzmaDec_DecodeToDic(&state, outSize, (const Byte *)inBuf, &inProcessed, LZMA_FINISH_END, &status);
lookahead -= inProcessed;
inSize -= inProcessed;
if (res != SZ_OK)
break;
if (status == LZMA_STATUS_FINISHED_WITH_MARK)
{
if (outSize != state.dicPos || inSize != 0)
res = SZ_ERROR_DATA;
break;
}
if (outSize == state.dicPos && inSize == 0 && status == LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK)
break;
if (inProcessed == 0 && dicPos == state.dicPos)
{
res = SZ_ERROR_DATA;
break;
}
res = ILookInStream_Skip(inStream, inProcessed);
if (res != SZ_OK)
break;
}
}
LzmaDec_FreeProbs(&state, allocMain);
return res;
}
#ifndef Z7_NO_METHOD_LZMA2
static SRes SzDecodeLzma2(const Byte *props, unsigned propsSize, UInt64 inSize, ILookInStreamPtr inStream,
Byte *outBuffer, SizeT outSize, ISzAllocPtr allocMain)
{
CLzma2Dec state;
SRes res = SZ_OK;
Lzma2Dec_CONSTRUCT(&state)
if (propsSize != 1)
return SZ_ERROR_DATA;
RINOK(Lzma2Dec_AllocateProbs(&state, props[0], allocMain))
state.decoder.dic = outBuffer;
state.decoder.dicBufSize = outSize;
Lzma2Dec_Init(&state);
for (;;)
{
const void *inBuf = NULL;
size_t lookahead = (1 << 18);
if (lookahead > inSize)
lookahead = (size_t)inSize;
res = ILookInStream_Look(inStream, &inBuf, &lookahead);
if (res != SZ_OK)
break;
{
SizeT inProcessed = (SizeT)lookahead, dicPos = state.decoder.dicPos;
ELzmaStatus status;
res = Lzma2Dec_DecodeToDic(&state, outSize, (const Byte *)inBuf, &inProcessed, LZMA_FINISH_END, &status);
lookahead -= inProcessed;
inSize -= inProcessed;
if (res != SZ_OK)
break;
if (status == LZMA_STATUS_FINISHED_WITH_MARK)
{
if (outSize != state.decoder.dicPos || inSize != 0)
res = SZ_ERROR_DATA;
break;
}
if (inProcessed == 0 && dicPos == state.decoder.dicPos)
{
res = SZ_ERROR_DATA;
break;
}
res = ILookInStream_Skip(inStream, inProcessed);
if (res != SZ_OK)
break;
}
}
Lzma2Dec_FreeProbs(&state, allocMain);
return res;
}
#endif
static SRes SzDecodeCopy(UInt64 inSize, ILookInStreamPtr inStream, Byte *outBuffer)
{
while (inSize > 0)
{
const void *inBuf;
size_t curSize = (1 << 18);
if (curSize > inSize)
curSize = (size_t)inSize;
RINOK(ILookInStream_Look(inStream, &inBuf, &curSize))
if (curSize == 0)
return SZ_ERROR_INPUT_EOF;
memcpy(outBuffer, inBuf, curSize);
outBuffer += curSize;
inSize -= curSize;
RINOK(ILookInStream_Skip(inStream, curSize))
}
return SZ_OK;
}
static BoolInt IS_MAIN_METHOD(UInt32 m)
{
switch (m)
{
case k_Copy:
case k_LZMA:
#ifndef Z7_NO_METHOD_LZMA2
case k_LZMA2:
#endif
#ifdef Z7_PPMD_SUPPORT
case k_PPMD:
#endif
return True;
}
return False;
}
static BoolInt IS_SUPPORTED_CODER(const CSzCoderInfo *c)
{
return
c->NumStreams == 1
/* && c->MethodID <= (UInt32)0xFFFFFFFF */
&& IS_MAIN_METHOD((UInt32)c->MethodID);
}
#define IS_BCJ2(c) ((c)->MethodID == k_BCJ2 && (c)->NumStreams == 4)
static SRes CheckSupportedFolder(const CSzFolder *f)
{
if (f->NumCoders < 1 || f->NumCoders > 4)
return SZ_ERROR_UNSUPPORTED;
if (!IS_SUPPORTED_CODER(&f->Coders[0]))
return SZ_ERROR_UNSUPPORTED;
if (f->NumCoders == 1)
{
if (f->NumPackStreams != 1 || f->PackStreams[0] != 0 || f->NumBonds != 0)
return SZ_ERROR_UNSUPPORTED;
return SZ_OK;
}
#if defined(Z7_USE_BRANCH_FILTER)
if (f->NumCoders == 2)
{
const CSzCoderInfo *c = &f->Coders[1];
if (
/* c->MethodID > (UInt32)0xFFFFFFFF || */
c->NumStreams != 1
|| f->NumPackStreams != 1
|| f->PackStreams[0] != 0
|| f->NumBonds != 1
|| f->Bonds[0].InIndex != 1
|| f->Bonds[0].OutIndex != 0)
return SZ_ERROR_UNSUPPORTED;
switch ((UInt32)c->MethodID)
{
#if !defined(Z7_NO_METHODS_FILTERS)
case k_Delta:
case k_BCJ:
case k_PPC:
case k_IA64:
case k_SPARC:
case k_ARM:
#endif
#ifdef Z7_USE_FILTER_ARM64
case k_ARM64:
#endif
#ifdef Z7_USE_FILTER_ARMT
case k_ARMT:
#endif
break;
default:
return SZ_ERROR_UNSUPPORTED;
}
return SZ_OK;
}
#endif
if (f->NumCoders == 4)
{
if (!IS_SUPPORTED_CODER(&f->Coders[1])
|| !IS_SUPPORTED_CODER(&f->Coders[2])
|| !IS_BCJ2(&f->Coders[3]))
return SZ_ERROR_UNSUPPORTED;
if (f->NumPackStreams != 4
|| f->PackStreams[0] != 2
|| f->PackStreams[1] != 6
|| f->PackStreams[2] != 1
|| f->PackStreams[3] != 0
|| f->NumBonds != 3
|| f->Bonds[0].InIndex != 5 || f->Bonds[0].OutIndex != 0
|| f->Bonds[1].InIndex != 4 || f->Bonds[1].OutIndex != 1
|| f->Bonds[2].InIndex != 3 || f->Bonds[2].OutIndex != 2)
return SZ_ERROR_UNSUPPORTED;
return SZ_OK;
}
return SZ_ERROR_UNSUPPORTED;
}
static SRes SzFolder_Decode2(const CSzFolder *folder,
const Byte *propsData,
const UInt64 *unpackSizes,
const UInt64 *packPositions,
ILookInStreamPtr inStream, UInt64 startPos,
Byte *outBuffer, SizeT outSize, ISzAllocPtr allocMain,
Byte *tempBuf[])
{
UInt32 ci;
SizeT tempSizes[3] = { 0, 0, 0};
SizeT tempSize3 = 0;
Byte *tempBuf3 = 0;
RINOK(CheckSupportedFolder(folder))
for (ci = 0; ci < folder->NumCoders; ci++)
{
const CSzCoderInfo *coder = &folder->Coders[ci];
if (IS_MAIN_METHOD((UInt32)coder->MethodID))
{
UInt32 si = 0;
UInt64 offset;
UInt64 inSize;
Byte *outBufCur = outBuffer;
SizeT outSizeCur = outSize;
if (folder->NumCoders == 4)
{
const UInt32 indices[] = { 3, 2, 0 };
const UInt64 unpackSize = unpackSizes[ci];
si = indices[ci];
if (ci < 2)
{
Byte *temp;
outSizeCur = (SizeT)unpackSize;
if (outSizeCur != unpackSize)
return SZ_ERROR_MEM;
temp = (Byte *)ISzAlloc_Alloc(allocMain, outSizeCur);
if (!temp && outSizeCur != 0)
return SZ_ERROR_MEM;
outBufCur = tempBuf[1 - ci] = temp;
tempSizes[1 - ci] = outSizeCur;
}
else if (ci == 2)
{
if (unpackSize > outSize) /* check it */
return SZ_ERROR_PARAM;
tempBuf3 = outBufCur = outBuffer + (outSize - (size_t)unpackSize);
tempSize3 = outSizeCur = (SizeT)unpackSize;
}
else
return SZ_ERROR_UNSUPPORTED;
}
offset = packPositions[si];
inSize = packPositions[(size_t)si + 1] - offset;
RINOK(LookInStream_SeekTo(inStream, startPos + offset))
if (coder->MethodID == k_Copy)
{
if (inSize != outSizeCur) /* check it */
return SZ_ERROR_DATA;
RINOK(SzDecodeCopy(inSize, inStream, outBufCur))
}
else if (coder->MethodID == k_LZMA)
{
RINOK(SzDecodeLzma(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain))
}
#ifndef Z7_NO_METHOD_LZMA2
else if (coder->MethodID == k_LZMA2)
{
RINOK(SzDecodeLzma2(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain))
}
#endif
#ifdef Z7_PPMD_SUPPORT
else if (coder->MethodID == k_PPMD)
{
RINOK(SzDecodePpmd(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain))
}
#endif
else
return SZ_ERROR_UNSUPPORTED;
}
else if (coder->MethodID == k_BCJ2)
{
const UInt64 offset = packPositions[1];
const UInt64 s3Size = packPositions[2] - offset;
if (ci != 3)
return SZ_ERROR_UNSUPPORTED;
tempSizes[2] = (SizeT)s3Size;
if (tempSizes[2] != s3Size)
return SZ_ERROR_MEM;
tempBuf[2] = (Byte *)ISzAlloc_Alloc(allocMain, tempSizes[2]);
if (!tempBuf[2] && tempSizes[2] != 0)
return SZ_ERROR_MEM;
RINOK(LookInStream_SeekTo(inStream, startPos + offset))
RINOK(SzDecodeCopy(s3Size, inStream, tempBuf[2]))
if ((tempSizes[0] & 3) != 0 ||
(tempSizes[1] & 3) != 0 ||
tempSize3 + tempSizes[0] + tempSizes[1] != outSize)
return SZ_ERROR_DATA;
{
CBcj2Dec p;
p.bufs[0] = tempBuf3; p.lims[0] = tempBuf3 + tempSize3;
p.bufs[1] = tempBuf[0]; p.lims[1] = tempBuf[0] + tempSizes[0];
p.bufs[2] = tempBuf[1]; p.lims[2] = tempBuf[1] + tempSizes[1];
p.bufs[3] = tempBuf[2]; p.lims[3] = tempBuf[2] + tempSizes[2];
p.dest = outBuffer;
p.destLim = outBuffer + outSize;
Bcj2Dec_Init(&p);
RINOK(Bcj2Dec_Decode(&p))
{
unsigned i;
for (i = 0; i < 4; i++)
if (p.bufs[i] != p.lims[i])
return SZ_ERROR_DATA;
if (p.dest != p.destLim || !Bcj2Dec_IsMaybeFinished(&p))
return SZ_ERROR_DATA;
}
}
}
#if defined(Z7_USE_BRANCH_FILTER)
else if (ci == 1)
{
#if !defined(Z7_NO_METHODS_FILTERS)
if (coder->MethodID == k_Delta)
{
if (coder->PropsSize != 1)
return SZ_ERROR_UNSUPPORTED;
{
Byte state[DELTA_STATE_SIZE];
Delta_Init(state);
Delta_Decode(state, (unsigned)(propsData[coder->PropsOffset]) + 1, outBuffer, outSize);
}
continue;
}
#endif
#ifdef Z7_USE_FILTER_ARM64
if (coder->MethodID == k_ARM64)
{
UInt32 pc = 0;
if (coder->PropsSize == 4)
pc = GetUi32(propsData + coder->PropsOffset);
else if (coder->PropsSize != 0)
return SZ_ERROR_UNSUPPORTED;
z7_BranchConv_ARM64_Dec(outBuffer, outSize, pc);
continue;
}
#endif
#if !defined(Z7_NO_METHODS_FILTERS) || defined(Z7_USE_FILTER_ARMT)
{
if (coder->PropsSize != 0)
return SZ_ERROR_UNSUPPORTED;
#define CASE_BRA_CONV(isa) case k_ ## isa: Z7_BRANCH_CONV_DEC(isa)(outBuffer, outSize, 0); break; // pc = 0;
switch (coder->MethodID)
{
#if !defined(Z7_NO_METHODS_FILTERS)
case k_BCJ:
{
UInt32 state = Z7_BRANCH_CONV_ST_X86_STATE_INIT_VAL;
z7_BranchConvSt_X86_Dec(outBuffer, outSize, 0, &state); // pc = 0
break;
}
CASE_BRA_CONV(PPC)
CASE_BRA_CONV(IA64)
CASE_BRA_CONV(SPARC)
CASE_BRA_CONV(ARM)
#endif
#if !defined(Z7_NO_METHODS_FILTERS) || defined(Z7_USE_FILTER_ARMT)
CASE_BRA_CONV(ARMT)
#endif
default:
return SZ_ERROR_UNSUPPORTED;
}
continue;
}
#endif
} // (c == 1)
#endif
else
return SZ_ERROR_UNSUPPORTED;
}
return SZ_OK;
}
SRes SzAr_DecodeFolder(const CSzAr *p, UInt32 folderIndex,
ILookInStreamPtr inStream, UInt64 startPos,
Byte *outBuffer, size_t outSize,
ISzAllocPtr allocMain)
{
SRes res;
CSzFolder folder;
CSzData sd;
const Byte *data = p->CodersData + p->FoCodersOffsets[folderIndex];
sd.Data = data;
sd.Size = p->FoCodersOffsets[(size_t)folderIndex + 1] - p->FoCodersOffsets[folderIndex];
res = SzGetNextFolderItem(&folder, &sd);
if (res != SZ_OK)
return res;
if (sd.Size != 0
|| folder.UnpackStream != p->FoToMainUnpackSizeIndex[folderIndex]
|| outSize != SzAr_GetFolderUnpackSize(p, folderIndex))
return SZ_ERROR_FAIL;
{
unsigned i;
Byte *tempBuf[3] = { 0, 0, 0};
res = SzFolder_Decode2(&folder, data,
&p->CoderUnpackSizes[p->FoToCoderUnpackSizes[folderIndex]],
p->PackPositions + p->FoStartPackStreamIndex[folderIndex],
inStream, startPos,
outBuffer, (SizeT)outSize, allocMain, tempBuf);
for (i = 0; i < 3; i++)
ISzAlloc_Free(allocMain, tempBuf[i]);
if (res == SZ_OK)
if (SzBitWithVals_Check(&p->FolderCRCs, folderIndex))
if (CrcCalc(outBuffer, outSize) != p->FolderCRCs.Vals[folderIndex])
res = SZ_ERROR_CRC;
return res;
}
}
+443
View File
@@ -0,0 +1,443 @@
/* 7zFile.c -- File IO
2023-04-02 : Igor Pavlov : Public domain */
#include "Precomp.h"
#include "7zFile.h"
#ifndef USE_WINDOWS_FILE
#include <errno.h>
#ifndef USE_FOPEN
#include <stdio.h>
#include <fcntl.h>
#ifdef _WIN32
#include <io.h>
typedef int ssize_t;
typedef int off_t;
#else
#include <unistd.h>
#endif
#endif
#else
/*
ReadFile and WriteFile functions in Windows have BUG:
If you Read or Write 64MB or more (probably min_failure_size = 64MB - 32KB + 1)
from/to Network file, it returns ERROR_NO_SYSTEM_RESOURCES
(Insufficient system resources exist to complete the requested service).
Probably in some version of Windows there are problems with other sizes:
for 32 MB (maybe also for 16 MB).
And message can be "Network connection was lost"
*/
#endif
#define kChunkSizeMax (1 << 22)
void File_Construct(CSzFile *p)
{
#ifdef USE_WINDOWS_FILE
p->handle = INVALID_HANDLE_VALUE;
#elif defined(USE_FOPEN)
p->file = NULL;
#else
p->fd = -1;
#endif
}
#if !defined(UNDER_CE) || !defined(USE_WINDOWS_FILE)
static WRes File_Open(CSzFile *p, const char *name, int writeMode)
{
#ifdef USE_WINDOWS_FILE
p->handle = CreateFileA(name,
writeMode ? GENERIC_WRITE : GENERIC_READ,
FILE_SHARE_READ, NULL,
writeMode ? CREATE_ALWAYS : OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
return (p->handle != INVALID_HANDLE_VALUE) ? 0 : GetLastError();
#elif defined(USE_FOPEN)
p->file = fopen(name, writeMode ? "wb+" : "rb");
return (p->file != 0) ? 0 :
#ifdef UNDER_CE
2; /* ENOENT */
#else
errno;
#endif
#else
int flags = (writeMode ? (O_CREAT | O_EXCL | O_WRONLY) : O_RDONLY);
#ifdef O_BINARY
flags |= O_BINARY;
#endif
p->fd = open(name, flags, 0666);
return (p->fd != -1) ? 0 : errno;
#endif
}
WRes InFile_Open(CSzFile *p, const char *name) { return File_Open(p, name, 0); }
WRes OutFile_Open(CSzFile *p, const char *name)
{
#if defined(USE_WINDOWS_FILE) || defined(USE_FOPEN)
return File_Open(p, name, 1);
#else
p->fd = creat(name, 0666);
return (p->fd != -1) ? 0 : errno;
#endif
}
#endif
#ifdef USE_WINDOWS_FILE
static WRes File_OpenW(CSzFile *p, const WCHAR *name, int writeMode)
{
p->handle = CreateFileW(name,
writeMode ? GENERIC_WRITE : GENERIC_READ,
FILE_SHARE_READ, NULL,
writeMode ? CREATE_ALWAYS : OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
return (p->handle != INVALID_HANDLE_VALUE) ? 0 : GetLastError();
}
WRes InFile_OpenW(CSzFile *p, const WCHAR *name) { return File_OpenW(p, name, 0); }
WRes OutFile_OpenW(CSzFile *p, const WCHAR *name) { return File_OpenW(p, name, 1); }
#endif
WRes File_Close(CSzFile *p)
{
#ifdef USE_WINDOWS_FILE
if (p->handle != INVALID_HANDLE_VALUE)
{
if (!CloseHandle(p->handle))
return GetLastError();
p->handle = INVALID_HANDLE_VALUE;
}
#elif defined(USE_FOPEN)
if (p->file != NULL)
{
int res = fclose(p->file);
if (res != 0)
{
if (res == EOF)
return errno;
return res;
}
p->file = NULL;
}
#else
if (p->fd != -1)
{
if (close(p->fd) != 0)
return errno;
p->fd = -1;
}
#endif
return 0;
}
WRes File_Read(CSzFile *p, void *data, size_t *size)
{
size_t originalSize = *size;
*size = 0;
if (originalSize == 0)
return 0;
#ifdef USE_WINDOWS_FILE
do
{
const DWORD curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : (DWORD)originalSize;
DWORD processed = 0;
const BOOL res = ReadFile(p->handle, data, curSize, &processed, NULL);
data = (void *)((Byte *)data + processed);
originalSize -= processed;
*size += processed;
if (!res)
return GetLastError();
// debug : we can break here for partial reading mode
if (processed == 0)
break;
}
while (originalSize > 0);
#elif defined(USE_FOPEN)
do
{
const size_t curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : originalSize;
const size_t processed = fread(data, 1, curSize, p->file);
data = (void *)((Byte *)data + (size_t)processed);
originalSize -= processed;
*size += processed;
if (processed != curSize)
return ferror(p->file);
// debug : we can break here for partial reading mode
if (processed == 0)
break;
}
while (originalSize > 0);
#else
do
{
const size_t curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : originalSize;
const ssize_t processed = read(p->fd, data, curSize);
if (processed == -1)
return errno;
if (processed == 0)
break;
data = (void *)((Byte *)data + (size_t)processed);
originalSize -= (size_t)processed;
*size += (size_t)processed;
// debug : we can break here for partial reading mode
// break;
}
while (originalSize > 0);
#endif
return 0;
}
WRes File_Write(CSzFile *p, const void *data, size_t *size)
{
size_t originalSize = *size;
*size = 0;
if (originalSize == 0)
return 0;
#ifdef USE_WINDOWS_FILE
do
{
const DWORD curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : (DWORD)originalSize;
DWORD processed = 0;
const BOOL res = WriteFile(p->handle, data, curSize, &processed, NULL);
data = (const void *)((const Byte *)data + processed);
originalSize -= processed;
*size += processed;
if (!res)
return GetLastError();
if (processed == 0)
break;
}
while (originalSize > 0);
#elif defined(USE_FOPEN)
do
{
const size_t curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : originalSize;
const size_t processed = fwrite(data, 1, curSize, p->file);
data = (void *)((Byte *)data + (size_t)processed);
originalSize -= processed;
*size += processed;
if (processed != curSize)
return ferror(p->file);
if (processed == 0)
break;
}
while (originalSize > 0);
#else
do
{
const size_t curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : originalSize;
const ssize_t processed = write(p->fd, data, curSize);
if (processed == -1)
return errno;
if (processed == 0)
break;
data = (const void *)((const Byte *)data + (size_t)processed);
originalSize -= (size_t)processed;
*size += (size_t)processed;
}
while (originalSize > 0);
#endif
return 0;
}
WRes File_Seek(CSzFile *p, Int64 *pos, ESzSeek origin)
{
#ifdef USE_WINDOWS_FILE
DWORD moveMethod;
UInt32 low = (UInt32)*pos;
LONG high = (LONG)((UInt64)*pos >> 16 >> 16); /* for case when UInt64 is 32-bit only */
// (int) to eliminate clang warning
switch ((int)origin)
{
case SZ_SEEK_SET: moveMethod = FILE_BEGIN; break;
case SZ_SEEK_CUR: moveMethod = FILE_CURRENT; break;
case SZ_SEEK_END: moveMethod = FILE_END; break;
default: return ERROR_INVALID_PARAMETER;
}
low = SetFilePointer(p->handle, (LONG)low, &high, moveMethod);
if (low == (UInt32)0xFFFFFFFF)
{
WRes res = GetLastError();
if (res != NO_ERROR)
return res;
}
*pos = ((Int64)high << 32) | low;
return 0;
#else
int moveMethod; // = origin;
switch ((int)origin)
{
case SZ_SEEK_SET: moveMethod = SEEK_SET; break;
case SZ_SEEK_CUR: moveMethod = SEEK_CUR; break;
case SZ_SEEK_END: moveMethod = SEEK_END; break;
default: return EINVAL;
}
#if defined(USE_FOPEN)
{
int res = fseek(p->file, (long)*pos, moveMethod);
if (res == -1)
return errno;
*pos = ftell(p->file);
if (*pos == -1)
return errno;
return 0;
}
#else
{
off_t res = lseek(p->fd, (off_t)*pos, moveMethod);
if (res == -1)
return errno;
*pos = res;
return 0;
}
#endif // USE_FOPEN
#endif // USE_WINDOWS_FILE
}
WRes File_GetLength(CSzFile *p, UInt64 *length)
{
#ifdef USE_WINDOWS_FILE
DWORD sizeHigh;
DWORD sizeLow = GetFileSize(p->handle, &sizeHigh);
if (sizeLow == 0xFFFFFFFF)
{
DWORD res = GetLastError();
if (res != NO_ERROR)
return res;
}
*length = (((UInt64)sizeHigh) << 32) + sizeLow;
return 0;
#elif defined(USE_FOPEN)
long pos = ftell(p->file);
int res = fseek(p->file, 0, SEEK_END);
*length = ftell(p->file);
fseek(p->file, pos, SEEK_SET);
return res;
#else
off_t pos;
*length = 0;
pos = lseek(p->fd, 0, SEEK_CUR);
if (pos != -1)
{
const off_t len2 = lseek(p->fd, 0, SEEK_END);
const off_t res2 = lseek(p->fd, pos, SEEK_SET);
if (len2 != -1)
{
*length = (UInt64)len2;
if (res2 != -1)
return 0;
}
}
return errno;
#endif
}
/* ---------- FileSeqInStream ---------- */
static SRes FileSeqInStream_Read(ISeqInStreamPtr pp, void *buf, size_t *size)
{
Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CFileSeqInStream)
const WRes wres = File_Read(&p->file, buf, size);
p->wres = wres;
return (wres == 0) ? SZ_OK : SZ_ERROR_READ;
}
void FileSeqInStream_CreateVTable(CFileSeqInStream *p)
{
p->vt.Read = FileSeqInStream_Read;
}
/* ---------- FileInStream ---------- */
static SRes FileInStream_Read(ISeekInStreamPtr pp, void *buf, size_t *size)
{
Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CFileInStream)
const WRes wres = File_Read(&p->file, buf, size);
p->wres = wres;
return (wres == 0) ? SZ_OK : SZ_ERROR_READ;
}
static SRes FileInStream_Seek(ISeekInStreamPtr pp, Int64 *pos, ESzSeek origin)
{
Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CFileInStream)
const WRes wres = File_Seek(&p->file, pos, origin);
p->wres = wres;
return (wres == 0) ? SZ_OK : SZ_ERROR_READ;
}
void FileInStream_CreateVTable(CFileInStream *p)
{
p->vt.Read = FileInStream_Read;
p->vt.Seek = FileInStream_Seek;
}
/* ---------- FileOutStream ---------- */
static size_t FileOutStream_Write(ISeqOutStreamPtr pp, const void *data, size_t size)
{
Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CFileOutStream)
const WRes wres = File_Write(&p->file, data, &size);
p->wres = wres;
return size;
}
void FileOutStream_CreateVTable(CFileOutStream *p)
{
p->vt.Write = FileOutStream_Write;
}
+92
View File
@@ -0,0 +1,92 @@
/* 7zFile.h -- File IO
2023-03-05 : Igor Pavlov : Public domain */
#ifndef ZIP7_INC_FILE_H
#define ZIP7_INC_FILE_H
#ifdef _WIN32
#define USE_WINDOWS_FILE
// #include <windows.h>
#endif
#ifdef USE_WINDOWS_FILE
#include "7zWindows.h"
#else
// note: USE_FOPEN mode is limited to 32-bit file size
// #define USE_FOPEN
// #include <stdio.h>
#endif
#include "7zTypes.h"
EXTERN_C_BEGIN
/* ---------- File ---------- */
typedef struct
{
#ifdef USE_WINDOWS_FILE
HANDLE handle;
#elif defined(USE_FOPEN)
FILE *file;
#else
int fd;
#endif
} CSzFile;
void File_Construct(CSzFile *p);
#if !defined(UNDER_CE) || !defined(USE_WINDOWS_FILE)
WRes InFile_Open(CSzFile *p, const char *name);
WRes OutFile_Open(CSzFile *p, const char *name);
#endif
#ifdef USE_WINDOWS_FILE
WRes InFile_OpenW(CSzFile *p, const WCHAR *name);
WRes OutFile_OpenW(CSzFile *p, const WCHAR *name);
#endif
WRes File_Close(CSzFile *p);
/* reads max(*size, remain file's size) bytes */
WRes File_Read(CSzFile *p, void *data, size_t *size);
/* writes *size bytes */
WRes File_Write(CSzFile *p, const void *data, size_t *size);
WRes File_Seek(CSzFile *p, Int64 *pos, ESzSeek origin);
WRes File_GetLength(CSzFile *p, UInt64 *length);
/* ---------- FileInStream ---------- */
typedef struct
{
ISeqInStream vt;
CSzFile file;
WRes wres;
} CFileSeqInStream;
void FileSeqInStream_CreateVTable(CFileSeqInStream *p);
typedef struct
{
ISeekInStream vt;
CSzFile file;
WRes wres;
} CFileInStream;
void FileInStream_CreateVTable(CFileInStream *p);
typedef struct
{
ISeqOutStream vt;
CSzFile file;
WRes wres;
} CFileOutStream;
void FileOutStream_CreateVTable(CFileOutStream *p);
EXTERN_C_END
#endif
+199
View File
@@ -0,0 +1,199 @@
/* 7zStream.c -- 7z Stream functions
2023-04-02 : Igor Pavlov : Public domain */
#include "Precomp.h"
#include <string.h>
#include "7zTypes.h"
SRes SeqInStream_ReadMax(ISeqInStreamPtr stream, void *buf, size_t *processedSize)
{
size_t size = *processedSize;
*processedSize = 0;
while (size != 0)
{
size_t cur = size;
const SRes res = ISeqInStream_Read(stream, buf, &cur);
*processedSize += cur;
buf = (void *)((Byte *)buf + cur);
size -= cur;
if (res != SZ_OK)
return res;
if (cur == 0)
return SZ_OK;
}
return SZ_OK;
}
/*
SRes SeqInStream_Read2(ISeqInStreamPtr stream, void *buf, size_t size, SRes errorType)
{
while (size != 0)
{
size_t processed = size;
RINOK(ISeqInStream_Read(stream, buf, &processed))
if (processed == 0)
return errorType;
buf = (void *)((Byte *)buf + processed);
size -= processed;
}
return SZ_OK;
}
SRes SeqInStream_Read(ISeqInStreamPtr stream, void *buf, size_t size)
{
return SeqInStream_Read2(stream, buf, size, SZ_ERROR_INPUT_EOF);
}
*/
SRes SeqInStream_ReadByte(ISeqInStreamPtr stream, Byte *buf)
{
size_t processed = 1;
RINOK(ISeqInStream_Read(stream, buf, &processed))
return (processed == 1) ? SZ_OK : SZ_ERROR_INPUT_EOF;
}
SRes LookInStream_SeekTo(ILookInStreamPtr stream, UInt64 offset)
{
Int64 t = (Int64)offset;
return ILookInStream_Seek(stream, &t, SZ_SEEK_SET);
}
SRes LookInStream_LookRead(ILookInStreamPtr stream, void *buf, size_t *size)
{
const void *lookBuf;
if (*size == 0)
return SZ_OK;
RINOK(ILookInStream_Look(stream, &lookBuf, size))
memcpy(buf, lookBuf, *size);
return ILookInStream_Skip(stream, *size);
}
SRes LookInStream_Read2(ILookInStreamPtr stream, void *buf, size_t size, SRes errorType)
{
while (size != 0)
{
size_t processed = size;
RINOK(ILookInStream_Read(stream, buf, &processed))
if (processed == 0)
return errorType;
buf = (void *)((Byte *)buf + processed);
size -= processed;
}
return SZ_OK;
}
SRes LookInStream_Read(ILookInStreamPtr stream, void *buf, size_t size)
{
return LookInStream_Read2(stream, buf, size, SZ_ERROR_INPUT_EOF);
}
#define GET_LookToRead2 Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CLookToRead2)
static SRes LookToRead2_Look_Lookahead(ILookInStreamPtr pp, const void **buf, size_t *size)
{
SRes res = SZ_OK;
GET_LookToRead2
size_t size2 = p->size - p->pos;
if (size2 == 0 && *size != 0)
{
p->pos = 0;
p->size = 0;
size2 = p->bufSize;
res = ISeekInStream_Read(p->realStream, p->buf, &size2);
p->size = size2;
}
if (*size > size2)
*size = size2;
*buf = p->buf + p->pos;
return res;
}
static SRes LookToRead2_Look_Exact(ILookInStreamPtr pp, const void **buf, size_t *size)
{
SRes res = SZ_OK;
GET_LookToRead2
size_t size2 = p->size - p->pos;
if (size2 == 0 && *size != 0)
{
p->pos = 0;
p->size = 0;
if (*size > p->bufSize)
*size = p->bufSize;
res = ISeekInStream_Read(p->realStream, p->buf, size);
size2 = p->size = *size;
}
if (*size > size2)
*size = size2;
*buf = p->buf + p->pos;
return res;
}
static SRes LookToRead2_Skip(ILookInStreamPtr pp, size_t offset)
{
GET_LookToRead2
p->pos += offset;
return SZ_OK;
}
static SRes LookToRead2_Read(ILookInStreamPtr pp, void *buf, size_t *size)
{
GET_LookToRead2
size_t rem = p->size - p->pos;
if (rem == 0)
return ISeekInStream_Read(p->realStream, buf, size);
if (rem > *size)
rem = *size;
memcpy(buf, p->buf + p->pos, rem);
p->pos += rem;
*size = rem;
return SZ_OK;
}
static SRes LookToRead2_Seek(ILookInStreamPtr pp, Int64 *pos, ESzSeek origin)
{
GET_LookToRead2
p->pos = p->size = 0;
return ISeekInStream_Seek(p->realStream, pos, origin);
}
void LookToRead2_CreateVTable(CLookToRead2 *p, int lookahead)
{
p->vt.Look = lookahead ?
LookToRead2_Look_Lookahead :
LookToRead2_Look_Exact;
p->vt.Skip = LookToRead2_Skip;
p->vt.Read = LookToRead2_Read;
p->vt.Seek = LookToRead2_Seek;
}
static SRes SecToLook_Read(ISeqInStreamPtr pp, void *buf, size_t *size)
{
Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CSecToLook)
return LookInStream_LookRead(p->realStream, buf, size);
}
void SecToLook_CreateVTable(CSecToLook *p)
{
p->vt.Read = SecToLook_Read;
}
static SRes SecToRead_Read(ISeqInStreamPtr pp, void *buf, size_t *size)
{
Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CSecToRead)
return ILookInStream_Read(p->realStream, buf, size);
}
void SecToRead_CreateVTable(CSecToRead *p)
{
p->vt.Read = SecToRead_Read;
}
+597
View File
@@ -0,0 +1,597 @@
/* 7zTypes.h -- Basic types
2023-04-02 : Igor Pavlov : Public domain */
#ifndef ZIP7_7Z_TYPES_H
#define ZIP7_7Z_TYPES_H
#ifdef _WIN32
/* #include <windows.h> */
#else
#include <errno.h>
#endif
#include <stddef.h>
#ifndef EXTERN_C_BEGIN
#ifdef __cplusplus
#define EXTERN_C_BEGIN extern "C" {
#define EXTERN_C_END }
#else
#define EXTERN_C_BEGIN
#define EXTERN_C_END
#endif
#endif
EXTERN_C_BEGIN
#define SZ_OK 0
#define SZ_ERROR_DATA 1
#define SZ_ERROR_MEM 2
#define SZ_ERROR_CRC 3
#define SZ_ERROR_UNSUPPORTED 4
#define SZ_ERROR_PARAM 5
#define SZ_ERROR_INPUT_EOF 6
#define SZ_ERROR_OUTPUT_EOF 7
#define SZ_ERROR_READ 8
#define SZ_ERROR_WRITE 9
#define SZ_ERROR_PROGRESS 10
#define SZ_ERROR_FAIL 11
#define SZ_ERROR_THREAD 12
#define SZ_ERROR_ARCHIVE 16
#define SZ_ERROR_NO_ARCHIVE 17
typedef int SRes;
#ifdef _MSC_VER
#if _MSC_VER > 1200
#define MY_ALIGN(n) __declspec(align(n))
#else
#define MY_ALIGN(n)
#endif
#else
/*
// C11/C++11:
#include <stdalign.h>
#define MY_ALIGN(n) alignas(n)
*/
#define MY_ALIGN(n) __attribute__ ((aligned(n)))
#endif
#ifdef _WIN32
/* typedef DWORD WRes; */
typedef unsigned WRes;
#define MY_SRes_HRESULT_FROM_WRes(x) HRESULT_FROM_WIN32(x)
// #define MY_HRES_ERROR_INTERNAL_ERROR MY_SRes_HRESULT_FROM_WRes(ERROR_INTERNAL_ERROR)
#else // _WIN32
// #define ENV_HAVE_LSTAT
typedef int WRes;
// (FACILITY_ERRNO = 0x800) is 7zip's FACILITY constant to represent (errno) errors in HRESULT
#define MY_FACILITY_ERRNO 0x800
#define MY_FACILITY_WIN32 7
#define MY_FACILITY_WRes MY_FACILITY_ERRNO
#define MY_HRESULT_FROM_errno_CONST_ERROR(x) ((HRESULT)( \
( (HRESULT)(x) & 0x0000FFFF) \
| (MY_FACILITY_WRes << 16) \
| (HRESULT)0x80000000 ))
#define MY_SRes_HRESULT_FROM_WRes(x) \
((HRESULT)(x) <= 0 ? ((HRESULT)(x)) : MY_HRESULT_FROM_errno_CONST_ERROR(x))
// we call macro HRESULT_FROM_WIN32 for system errors (WRes) that are (errno)
#define HRESULT_FROM_WIN32(x) MY_SRes_HRESULT_FROM_WRes(x)
/*
#define ERROR_FILE_NOT_FOUND 2L
#define ERROR_ACCESS_DENIED 5L
#define ERROR_NO_MORE_FILES 18L
#define ERROR_LOCK_VIOLATION 33L
#define ERROR_FILE_EXISTS 80L
#define ERROR_DISK_FULL 112L
#define ERROR_NEGATIVE_SEEK 131L
#define ERROR_ALREADY_EXISTS 183L
#define ERROR_DIRECTORY 267L
#define ERROR_TOO_MANY_POSTS 298L
#define ERROR_INTERNAL_ERROR 1359L
#define ERROR_INVALID_REPARSE_DATA 4392L
#define ERROR_REPARSE_TAG_INVALID 4393L
#define ERROR_REPARSE_TAG_MISMATCH 4394L
*/
// we use errno equivalents for some WIN32 errors:
#define ERROR_INVALID_PARAMETER EINVAL
#define ERROR_INVALID_FUNCTION EINVAL
#define ERROR_ALREADY_EXISTS EEXIST
#define ERROR_FILE_EXISTS EEXIST
#define ERROR_PATH_NOT_FOUND ENOENT
#define ERROR_FILE_NOT_FOUND ENOENT
#define ERROR_DISK_FULL ENOSPC
// #define ERROR_INVALID_HANDLE EBADF
// we use FACILITY_WIN32 for errors that has no errno equivalent
// Too many posts were made to a semaphore.
#define ERROR_TOO_MANY_POSTS ((HRESULT)0x8007012AL)
#define ERROR_INVALID_REPARSE_DATA ((HRESULT)0x80071128L)
#define ERROR_REPARSE_TAG_INVALID ((HRESULT)0x80071129L)
// if (MY_FACILITY_WRes != FACILITY_WIN32),
// we use FACILITY_WIN32 for COM errors:
#define E_OUTOFMEMORY ((HRESULT)0x8007000EL)
#define E_INVALIDARG ((HRESULT)0x80070057L)
#define MY_E_ERROR_NEGATIVE_SEEK ((HRESULT)0x80070083L)
/*
// we can use FACILITY_ERRNO for some COM errors, that have errno equivalents:
#define E_OUTOFMEMORY MY_HRESULT_FROM_errno_CONST_ERROR(ENOMEM)
#define E_INVALIDARG MY_HRESULT_FROM_errno_CONST_ERROR(EINVAL)
#define MY_E_ERROR_NEGATIVE_SEEK MY_HRESULT_FROM_errno_CONST_ERROR(EINVAL)
*/
#define TEXT(quote) quote
#define FILE_ATTRIBUTE_READONLY 0x0001
#define FILE_ATTRIBUTE_HIDDEN 0x0002
#define FILE_ATTRIBUTE_SYSTEM 0x0004
#define FILE_ATTRIBUTE_DIRECTORY 0x0010
#define FILE_ATTRIBUTE_ARCHIVE 0x0020
#define FILE_ATTRIBUTE_DEVICE 0x0040
#define FILE_ATTRIBUTE_NORMAL 0x0080
#define FILE_ATTRIBUTE_TEMPORARY 0x0100
#define FILE_ATTRIBUTE_SPARSE_FILE 0x0200
#define FILE_ATTRIBUTE_REPARSE_POINT 0x0400
#define FILE_ATTRIBUTE_COMPRESSED 0x0800
#define FILE_ATTRIBUTE_OFFLINE 0x1000
#define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x2000
#define FILE_ATTRIBUTE_ENCRYPTED 0x4000
#define FILE_ATTRIBUTE_UNIX_EXTENSION 0x8000 /* trick for Unix */
#endif
#ifndef RINOK
#define RINOK(x) { const int _result_ = (x); if (_result_ != 0) return _result_; }
#endif
#ifndef RINOK_WRes
#define RINOK_WRes(x) { const WRes _result_ = (x); if (_result_ != 0) return _result_; }
#endif
typedef unsigned char Byte;
typedef short Int16;
typedef unsigned short UInt16;
#ifdef Z7_DECL_Int32_AS_long
typedef long Int32;
typedef unsigned long UInt32;
#else
typedef int Int32;
typedef unsigned int UInt32;
#endif
#ifndef _WIN32
typedef int INT;
typedef Int32 INT32;
typedef unsigned int UINT;
typedef UInt32 UINT32;
typedef INT32 LONG; // LONG, ULONG and DWORD must be 32-bit for _WIN32 compatibility
typedef UINT32 ULONG;
#undef DWORD
typedef UINT32 DWORD;
#define VOID void
#define HRESULT LONG
typedef void *LPVOID;
// typedef void VOID;
// typedef ULONG_PTR DWORD_PTR, *PDWORD_PTR;
// gcc / clang on Unix : sizeof(long==sizeof(void*) in 32 or 64 bits)
typedef long INT_PTR;
typedef unsigned long UINT_PTR;
typedef long LONG_PTR;
typedef unsigned long DWORD_PTR;
typedef size_t SIZE_T;
#endif // _WIN32
#define MY_HRES_ERROR_INTERNAL_ERROR ((HRESULT)0x8007054FL)
#ifdef Z7_DECL_Int64_AS_long
typedef long Int64;
typedef unsigned long UInt64;
#else
#if (defined(_MSC_VER) || defined(__BORLANDC__)) && !defined(__clang__)
typedef __int64 Int64;
typedef unsigned __int64 UInt64;
#else
#if defined(__clang__) || defined(__GNUC__)
#include <stdint.h>
typedef int64_t Int64;
typedef uint64_t UInt64;
#else
typedef long long int Int64;
typedef unsigned long long int UInt64;
// #define UINT64_CONST(n) n ## ULL
#endif
#endif
#endif
#define UINT64_CONST(n) n
#ifdef Z7_DECL_SizeT_AS_unsigned_int
typedef unsigned int SizeT;
#else
typedef size_t SizeT;
#endif
/*
#if (defined(_MSC_VER) && _MSC_VER <= 1200)
typedef size_t MY_uintptr_t;
#else
#include <stdint.h>
typedef uintptr_t MY_uintptr_t;
#endif
*/
typedef int BoolInt;
/* typedef BoolInt Bool; */
#define True 1
#define False 0
#ifdef _WIN32
#define Z7_STDCALL __stdcall
#else
#define Z7_STDCALL
#endif
#ifdef _MSC_VER
#if _MSC_VER >= 1300
#define Z7_NO_INLINE __declspec(noinline)
#else
#define Z7_NO_INLINE
#endif
#define Z7_FORCE_INLINE __forceinline
#define Z7_CDECL __cdecl
#define Z7_FASTCALL __fastcall
#else // _MSC_VER
#if (defined(__GNUC__) && (__GNUC__ >= 4)) \
|| (defined(__clang__) && (__clang_major__ >= 4)) \
|| defined(__INTEL_COMPILER) \
|| defined(__xlC__)
#define Z7_NO_INLINE __attribute__((noinline))
#define Z7_FORCE_INLINE __attribute__((always_inline)) inline
#else
#define Z7_NO_INLINE
#define Z7_FORCE_INLINE
#endif
#define Z7_CDECL
#if defined(_M_IX86) \
|| defined(__i386__)
// #define Z7_FASTCALL __attribute__((fastcall))
// #define Z7_FASTCALL __attribute__((cdecl))
#define Z7_FASTCALL
#elif defined(MY_CPU_AMD64)
// #define Z7_FASTCALL __attribute__((ms_abi))
#define Z7_FASTCALL
#else
#define Z7_FASTCALL
#endif
#endif // _MSC_VER
/* The following interfaces use first parameter as pointer to structure */
// #define Z7_C_IFACE_CONST_QUAL
#define Z7_C_IFACE_CONST_QUAL const
#define Z7_C_IFACE_DECL(a) \
struct a ## _; \
typedef Z7_C_IFACE_CONST_QUAL struct a ## _ * a ## Ptr; \
typedef struct a ## _ a; \
struct a ## _
Z7_C_IFACE_DECL (IByteIn)
{
Byte (*Read)(IByteInPtr p); /* reads one byte, returns 0 in case of EOF or error */
};
#define IByteIn_Read(p) (p)->Read(p)
Z7_C_IFACE_DECL (IByteOut)
{
void (*Write)(IByteOutPtr p, Byte b);
};
#define IByteOut_Write(p, b) (p)->Write(p, b)
Z7_C_IFACE_DECL (ISeqInStream)
{
SRes (*Read)(ISeqInStreamPtr p, void *buf, size_t *size);
/* if (input(*size) != 0 && output(*size) == 0) means end_of_stream.
(output(*size) < input(*size)) is allowed */
};
#define ISeqInStream_Read(p, buf, size) (p)->Read(p, buf, size)
/* try to read as much as avail in stream and limited by (*processedSize) */
SRes SeqInStream_ReadMax(ISeqInStreamPtr stream, void *buf, size_t *processedSize);
/* it can return SZ_ERROR_INPUT_EOF */
// SRes SeqInStream_Read(ISeqInStreamPtr stream, void *buf, size_t size);
// SRes SeqInStream_Read2(ISeqInStreamPtr stream, void *buf, size_t size, SRes errorType);
SRes SeqInStream_ReadByte(ISeqInStreamPtr stream, Byte *buf);
Z7_C_IFACE_DECL (ISeqOutStream)
{
size_t (*Write)(ISeqOutStreamPtr p, const void *buf, size_t size);
/* Returns: result - the number of actually written bytes.
(result < size) means error */
};
#define ISeqOutStream_Write(p, buf, size) (p)->Write(p, buf, size)
typedef enum
{
SZ_SEEK_SET = 0,
SZ_SEEK_CUR = 1,
SZ_SEEK_END = 2
} ESzSeek;
Z7_C_IFACE_DECL (ISeekInStream)
{
SRes (*Read)(ISeekInStreamPtr p, void *buf, size_t *size); /* same as ISeqInStream::Read */
SRes (*Seek)(ISeekInStreamPtr p, Int64 *pos, ESzSeek origin);
};
#define ISeekInStream_Read(p, buf, size) (p)->Read(p, buf, size)
#define ISeekInStream_Seek(p, pos, origin) (p)->Seek(p, pos, origin)
Z7_C_IFACE_DECL (ILookInStream)
{
SRes (*Look)(ILookInStreamPtr p, const void **buf, size_t *size);
/* if (input(*size) != 0 && output(*size) == 0) means end_of_stream.
(output(*size) > input(*size)) is not allowed
(output(*size) < input(*size)) is allowed */
SRes (*Skip)(ILookInStreamPtr p, size_t offset);
/* offset must be <= output(*size) of Look */
SRes (*Read)(ILookInStreamPtr p, void *buf, size_t *size);
/* reads directly (without buffer). It's same as ISeqInStream::Read */
SRes (*Seek)(ILookInStreamPtr p, Int64 *pos, ESzSeek origin);
};
#define ILookInStream_Look(p, buf, size) (p)->Look(p, buf, size)
#define ILookInStream_Skip(p, offset) (p)->Skip(p, offset)
#define ILookInStream_Read(p, buf, size) (p)->Read(p, buf, size)
#define ILookInStream_Seek(p, pos, origin) (p)->Seek(p, pos, origin)
SRes LookInStream_LookRead(ILookInStreamPtr stream, void *buf, size_t *size);
SRes LookInStream_SeekTo(ILookInStreamPtr stream, UInt64 offset);
/* reads via ILookInStream::Read */
SRes LookInStream_Read2(ILookInStreamPtr stream, void *buf, size_t size, SRes errorType);
SRes LookInStream_Read(ILookInStreamPtr stream, void *buf, size_t size);
typedef struct
{
ILookInStream vt;
ISeekInStreamPtr realStream;
size_t pos;
size_t size; /* it's data size */
/* the following variables must be set outside */
Byte *buf;
size_t bufSize;
} CLookToRead2;
void LookToRead2_CreateVTable(CLookToRead2 *p, int lookahead);
#define LookToRead2_INIT(p) { (p)->pos = (p)->size = 0; }
typedef struct
{
ISeqInStream vt;
ILookInStreamPtr realStream;
} CSecToLook;
void SecToLook_CreateVTable(CSecToLook *p);
typedef struct
{
ISeqInStream vt;
ILookInStreamPtr realStream;
} CSecToRead;
void SecToRead_CreateVTable(CSecToRead *p);
Z7_C_IFACE_DECL (ICompressProgress)
{
SRes (*Progress)(ICompressProgressPtr p, UInt64 inSize, UInt64 outSize);
/* Returns: result. (result != SZ_OK) means break.
Value (UInt64)(Int64)-1 for size means unknown value. */
};
#define ICompressProgress_Progress(p, inSize, outSize) (p)->Progress(p, inSize, outSize)
typedef struct ISzAlloc ISzAlloc;
typedef const ISzAlloc * ISzAllocPtr;
struct ISzAlloc
{
void *(*Alloc)(ISzAllocPtr p, size_t size);
void (*Free)(ISzAllocPtr p, void *address); /* address can be 0 */
};
#define ISzAlloc_Alloc(p, size) (p)->Alloc(p, size)
#define ISzAlloc_Free(p, a) (p)->Free(p, a)
/* deprecated */
#define IAlloc_Alloc(p, size) ISzAlloc_Alloc(p, size)
#define IAlloc_Free(p, a) ISzAlloc_Free(p, a)
#ifndef MY_offsetof
#ifdef offsetof
#define MY_offsetof(type, m) offsetof(type, m)
/*
#define MY_offsetof(type, m) FIELD_OFFSET(type, m)
*/
#else
#define MY_offsetof(type, m) ((size_t)&(((type *)0)->m))
#endif
#endif
#ifndef Z7_container_of
/*
#define Z7_container_of(ptr, type, m) container_of(ptr, type, m)
#define Z7_container_of(ptr, type, m) CONTAINING_RECORD(ptr, type, m)
#define Z7_container_of(ptr, type, m) ((type *)((char *)(ptr) - offsetof(type, m)))
#define Z7_container_of(ptr, type, m) (&((type *)0)->m == (ptr), ((type *)(((char *)(ptr)) - MY_offsetof(type, m))))
*/
/*
GCC shows warning: "perhaps the 'offsetof' macro was used incorrectly"
GCC 3.4.4 : classes with constructor
GCC 4.8.1 : classes with non-public variable members"
*/
#define Z7_container_of(ptr, type, m) \
((type *)(void *)((char *)(void *) \
(1 ? (ptr) : &((type *)NULL)->m) - MY_offsetof(type, m)))
#define Z7_container_of_CONST(ptr, type, m) \
((const type *)(const void *)((const char *)(const void *) \
(1 ? (ptr) : &((type *)NULL)->m) - MY_offsetof(type, m)))
/*
#define Z7_container_of_NON_CONST_FROM_CONST(ptr, type, m) \
((type *)(void *)(const void *)((const char *)(const void *) \
(1 ? (ptr) : &((type *)NULL)->m) - MY_offsetof(type, m)))
*/
#endif
#define Z7_CONTAINER_FROM_VTBL_SIMPLE(ptr, type, m) ((type *)(void *)(ptr))
// #define Z7_CONTAINER_FROM_VTBL(ptr, type, m) Z7_CONTAINER_FROM_VTBL_SIMPLE(ptr, type, m)
#define Z7_CONTAINER_FROM_VTBL(ptr, type, m) Z7_container_of(ptr, type, m)
// #define Z7_CONTAINER_FROM_VTBL(ptr, type, m) Z7_container_of_NON_CONST_FROM_CONST(ptr, type, m)
#define Z7_CONTAINER_FROM_VTBL_CONST(ptr, type, m) Z7_container_of_CONST(ptr, type, m)
#define Z7_CONTAINER_FROM_VTBL_CLS(ptr, type, m) Z7_CONTAINER_FROM_VTBL_SIMPLE(ptr, type, m)
/*
#define Z7_CONTAINER_FROM_VTBL_CLS(ptr, type, m) Z7_CONTAINER_FROM_VTBL(ptr, type, m)
*/
#if defined (__clang__) || defined(__GNUC__)
#define Z7_DIAGNOSCTIC_IGNORE_BEGIN_CAST_QUAL \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wcast-qual\"")
#define Z7_DIAGNOSCTIC_IGNORE_END_CAST_QUAL \
_Pragma("GCC diagnostic pop")
#else
#define Z7_DIAGNOSCTIC_IGNORE_BEGIN_CAST_QUAL
#define Z7_DIAGNOSCTIC_IGNORE_END_CAST_QUAL
#endif
#define Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR(ptr, type, m, p) \
Z7_DIAGNOSCTIC_IGNORE_BEGIN_CAST_QUAL \
type *p = Z7_CONTAINER_FROM_VTBL(ptr, type, m); \
Z7_DIAGNOSCTIC_IGNORE_END_CAST_QUAL
#define Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(type) \
Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR(pp, type, vt, p)
// #define ZIP7_DECLARE_HANDLE(name) typedef void *name;
#define Z7_DECLARE_HANDLE(name) struct name##_dummy{int unused;}; typedef struct name##_dummy *name;
#define Z7_memset_0_ARRAY(a) memset((a), 0, sizeof(a))
#ifndef Z7_ARRAY_SIZE
#define Z7_ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
#endif
#ifdef _WIN32
#define CHAR_PATH_SEPARATOR '\\'
#define WCHAR_PATH_SEPARATOR L'\\'
#define STRING_PATH_SEPARATOR "\\"
#define WSTRING_PATH_SEPARATOR L"\\"
#else
#define CHAR_PATH_SEPARATOR '/'
#define WCHAR_PATH_SEPARATOR L'/'
#define STRING_PATH_SEPARATOR "/"
#define WSTRING_PATH_SEPARATOR L"/"
#endif
#define k_PropVar_TimePrec_0 0
#define k_PropVar_TimePrec_Unix 1
#define k_PropVar_TimePrec_DOS 2
#define k_PropVar_TimePrec_HighPrec 3
#define k_PropVar_TimePrec_Base 16
#define k_PropVar_TimePrec_100ns (k_PropVar_TimePrec_Base + 7)
#define k_PropVar_TimePrec_1ns (k_PropVar_TimePrec_Base + 9)
EXTERN_C_END
#endif
/*
#ifndef Z7_ST
#ifdef _7ZIP_ST
#define Z7_ST
#endif
#endif
*/
+101
View File
@@ -0,0 +1,101 @@
/* 7zWindows.h -- StdAfx
2023-04-02 : Igor Pavlov : Public domain */
#ifndef ZIP7_INC_7Z_WINDOWS_H
#define ZIP7_INC_7Z_WINDOWS_H
#ifdef _WIN32
#if defined(__clang__)
# pragma clang diagnostic push
#endif
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable : 4668) // '_WIN32_WINNT' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif'
#if _MSC_VER == 1900
// for old kit10 versions
// #pragma warning(disable : 4255) // winuser.h(13979): warning C4255: 'GetThreadDpiAwarenessContext':
#endif
// win10 Windows Kit:
#endif // _MSC_VER
#if defined(_MSC_VER) && _MSC_VER <= 1200 && !defined(_WIN64)
// for msvc6 without sdk2003
#define RPC_NO_WINDOWS_H
#endif
#if defined(__MINGW32__) || defined(__MINGW64__)
// #if defined(__GNUC__) && !defined(__clang__)
#include <windows.h>
#else
#include <Windows.h>
#endif
// #include <basetsd.h>
// #include <wtypes.h>
// but if precompiled with clang-cl then we need
// #include <windows.h>
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
#if defined(__clang__)
# pragma clang diagnostic pop
#endif
#if defined(_MSC_VER) && _MSC_VER <= 1200 && !defined(_WIN64)
#ifndef _W64
typedef long LONG_PTR, *PLONG_PTR;
typedef unsigned long ULONG_PTR, *PULONG_PTR;
typedef ULONG_PTR DWORD_PTR, *PDWORD_PTR;
#define Z7_OLD_WIN_SDK
#endif // _W64
#endif // _MSC_VER == 1200
#ifdef Z7_OLD_WIN_SDK
#ifndef INVALID_FILE_ATTRIBUTES
#define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
#endif
#ifndef INVALID_SET_FILE_POINTER
#define INVALID_SET_FILE_POINTER ((DWORD)-1)
#endif
#ifndef FILE_SPECIAL_ACCESS
#define FILE_SPECIAL_ACCESS (FILE_ANY_ACCESS)
#endif
// ShlObj.h:
// #define BIF_NEWDIALOGSTYLE 0x0040
#pragma warning(disable : 4201)
// #pragma warning(disable : 4115)
#undef VARIANT_TRUE
#define VARIANT_TRUE ((VARIANT_BOOL)-1)
#endif
#endif // Z7_OLD_WIN_SDK
#ifdef UNDER_CE
#undef VARIANT_TRUE
#define VARIANT_TRUE ((VARIANT_BOOL)-1)
#endif
#if defined(_MSC_VER)
#if _MSC_VER >= 1400 && _MSC_VER <= 1600
// BaseTsd.h(148) : 'HandleToULong' : unreferenced inline function has been removed
// string.h
// #pragma warning(disable : 4514)
#endif
#endif
/* #include "7zTypes.h" */
#endif
+290
View File
@@ -0,0 +1,290 @@
/* Bcj2.c -- BCJ2 Decoder (Converter for x86 code)
2023-03-01 : Igor Pavlov : Public domain */
#include "Precomp.h"
#include "Bcj2.h"
#include "CpuArch.h"
#define kTopValue ((UInt32)1 << 24)
#define kNumBitModelTotalBits 11
#define kBitModelTotal (1 << kNumBitModelTotalBits)
#define kNumMoveBits 5
// UInt32 bcj2_stats[256 + 2][2];
void Bcj2Dec_Init(CBcj2Dec *p)
{
unsigned i;
p->state = BCJ2_STREAM_RC; // BCJ2_DEC_STATE_OK;
p->ip = 0;
p->temp = 0;
p->range = 0;
p->code = 0;
for (i = 0; i < sizeof(p->probs) / sizeof(p->probs[0]); i++)
p->probs[i] = kBitModelTotal >> 1;
}
SRes Bcj2Dec_Decode(CBcj2Dec *p)
{
UInt32 v = p->temp;
// const Byte *src;
if (p->range <= 5)
{
UInt32 code = p->code;
p->state = BCJ2_DEC_STATE_ERROR; /* for case if we return SZ_ERROR_DATA; */
for (; p->range != 5; p->range++)
{
if (p->range == 1 && code != 0)
return SZ_ERROR_DATA;
if (p->bufs[BCJ2_STREAM_RC] == p->lims[BCJ2_STREAM_RC])
{
p->state = BCJ2_STREAM_RC;
return SZ_OK;
}
code = (code << 8) | *(p->bufs[BCJ2_STREAM_RC])++;
p->code = code;
}
if (code == 0xffffffff)
return SZ_ERROR_DATA;
p->range = 0xffffffff;
}
// else
{
unsigned state = p->state;
// we check BCJ2_IS_32BIT_STREAM() here instead of check in the main loop
if (BCJ2_IS_32BIT_STREAM(state))
{
const Byte *cur = p->bufs[state];
if (cur == p->lims[state])
return SZ_OK;
p->bufs[state] = cur + 4;
{
const UInt32 ip = p->ip + 4;
v = GetBe32a(cur) - ip;
p->ip = ip;
}
state = BCJ2_DEC_STATE_ORIG_0;
}
if ((unsigned)(state - BCJ2_DEC_STATE_ORIG_0) < 4)
{
Byte *dest = p->dest;
for (;;)
{
if (dest == p->destLim)
{
p->state = state;
p->temp = v;
return SZ_OK;
}
*dest++ = (Byte)v;
p->dest = dest;
if (++state == BCJ2_DEC_STATE_ORIG_3 + 1)
break;
v >>= 8;
}
}
}
// src = p->bufs[BCJ2_STREAM_MAIN];
for (;;)
{
/*
if (BCJ2_IS_32BIT_STREAM(p->state))
p->state = BCJ2_DEC_STATE_OK;
else
*/
{
if (p->range < kTopValue)
{
if (p->bufs[BCJ2_STREAM_RC] == p->lims[BCJ2_STREAM_RC])
{
p->state = BCJ2_STREAM_RC;
p->temp = v;
return SZ_OK;
}
p->range <<= 8;
p->code = (p->code << 8) | *(p->bufs[BCJ2_STREAM_RC])++;
}
{
const Byte *src = p->bufs[BCJ2_STREAM_MAIN];
const Byte *srcLim;
Byte *dest = p->dest;
{
const SizeT rem = (SizeT)(p->lims[BCJ2_STREAM_MAIN] - src);
SizeT num = (SizeT)(p->destLim - dest);
if (num >= rem)
num = rem;
#define NUM_ITERS 4
#if (NUM_ITERS & (NUM_ITERS - 1)) == 0
num &= ~((SizeT)NUM_ITERS - 1); // if (NUM_ITERS == (1 << x))
#else
num -= num % NUM_ITERS; // if (NUM_ITERS != (1 << x))
#endif
srcLim = src + num;
}
#define NUM_SHIFT_BITS 24
#define ONE_ITER(indx) { \
const unsigned b = src[indx]; \
*dest++ = (Byte)b; \
v = (v << NUM_SHIFT_BITS) | b; \
if (((b + (0x100 - 0xe8)) & 0xfe) == 0) break; \
if (((v - (((UInt32)0x0f << (NUM_SHIFT_BITS)) + 0x80)) & \
((((UInt32)1 << (4 + NUM_SHIFT_BITS)) - 0x1) << 4)) == 0) break; \
/* ++dest */; /* v = b; */ }
if (src != srcLim)
for (;;)
{
/* The dependency chain of 2-cycle for (v) calculation is not big problem here.
But we can remove dependency chain with v = b in the end of loop. */
ONE_ITER(0)
#if (NUM_ITERS > 1)
ONE_ITER(1)
#if (NUM_ITERS > 2)
ONE_ITER(2)
#if (NUM_ITERS > 3)
ONE_ITER(3)
#if (NUM_ITERS > 4)
ONE_ITER(4)
#if (NUM_ITERS > 5)
ONE_ITER(5)
#if (NUM_ITERS > 6)
ONE_ITER(6)
#if (NUM_ITERS > 7)
ONE_ITER(7)
#endif
#endif
#endif
#endif
#endif
#endif
#endif
src += NUM_ITERS;
if (src == srcLim)
break;
}
if (src == srcLim)
#if (NUM_ITERS > 1)
for (;;)
#endif
{
#if (NUM_ITERS > 1)
if (src == p->lims[BCJ2_STREAM_MAIN] || dest == p->destLim)
#endif
{
const SizeT num = (SizeT)(src - p->bufs[BCJ2_STREAM_MAIN]);
p->bufs[BCJ2_STREAM_MAIN] = src;
p->dest = dest;
p->ip += (UInt32)num;
/* state BCJ2_STREAM_MAIN has more priority than BCJ2_STATE_ORIG */
p->state =
src == p->lims[BCJ2_STREAM_MAIN] ?
(unsigned)BCJ2_STREAM_MAIN :
(unsigned)BCJ2_DEC_STATE_ORIG;
p->temp = v;
return SZ_OK;
}
#if (NUM_ITERS > 1)
ONE_ITER(0)
src++;
#endif
}
{
const SizeT num = (SizeT)(dest - p->dest);
p->dest = dest; // p->dest += num;
p->bufs[BCJ2_STREAM_MAIN] += num; // = src;
p->ip += (UInt32)num;
}
{
UInt32 bound, ttt;
CBcj2Prob *prob; // unsigned index;
/*
prob = p->probs + (unsigned)((Byte)v == 0xe8 ?
2 + (Byte)(v >> 8) :
((v >> 5) & 1)); // ((Byte)v < 0xe8 ? 0 : 1));
*/
{
const unsigned c = ((v + 0x17) >> 6) & 1;
prob = p->probs + (unsigned)
(((0 - c) & (Byte)(v >> NUM_SHIFT_BITS)) + c + ((v >> 5) & 1));
// (Byte)
// 8x->0 : e9->1 : xxe8->xx+2
// 8x->0x100 : e9->0x101 : xxe8->xx
// (((0x100 - (e & ~v)) & (0x100 | (v >> 8))) + (e & v));
// (((0x101 + (~e | v)) & (0x100 | (v >> 8))) + (e & v));
}
ttt = *prob;
bound = (p->range >> kNumBitModelTotalBits) * ttt;
if (p->code < bound)
{
// bcj2_stats[prob - p->probs][0]++;
p->range = bound;
*prob = (CBcj2Prob)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits));
continue;
}
{
// bcj2_stats[prob - p->probs][1]++;
p->range -= bound;
p->code -= bound;
*prob = (CBcj2Prob)(ttt - (ttt >> kNumMoveBits));
}
}
}
}
{
/* (v == 0xe8 ? 0 : 1) uses setcc instruction with additional zero register usage in x64 MSVC. */
// const unsigned cj = ((Byte)v == 0xe8) ? BCJ2_STREAM_CALL : BCJ2_STREAM_JUMP;
const unsigned cj = (((v + 0x57) >> 6) & 1) + BCJ2_STREAM_CALL;
const Byte *cur = p->bufs[cj];
Byte *dest;
SizeT rem;
if (cur == p->lims[cj])
{
p->state = cj;
break;
}
v = GetBe32a(cur);
p->bufs[cj] = cur + 4;
{
const UInt32 ip = p->ip + 4;
v -= ip;
p->ip = ip;
}
dest = p->dest;
rem = (SizeT)(p->destLim - dest);
if (rem < 4)
{
if ((unsigned)rem > 0) { dest[0] = (Byte)v; v >>= 8;
if ((unsigned)rem > 1) { dest[1] = (Byte)v; v >>= 8;
if ((unsigned)rem > 2) { dest[2] = (Byte)v; v >>= 8; }}}
p->temp = v;
p->dest = dest + rem;
p->state = BCJ2_DEC_STATE_ORIG_0 + (unsigned)rem;
break;
}
SetUi32(dest, v)
v >>= 24;
p->dest = dest + 4;
}
}
if (p->range < kTopValue && p->bufs[BCJ2_STREAM_RC] != p->lims[BCJ2_STREAM_RC])
{
p->range <<= 8;
p->code = (p->code << 8) | *(p->bufs[BCJ2_STREAM_RC])++;
}
return SZ_OK;
}
#undef NUM_ITERS
#undef ONE_ITER
#undef NUM_SHIFT_BITS
#undef kTopValue
#undef kNumBitModelTotalBits
#undef kBitModelTotal
#undef kNumMoveBits
+332
View File
@@ -0,0 +1,332 @@
/* Bcj2.h -- BCJ2 converter for x86 code (Branch CALL/JUMP variant2)
2023-03-02 : Igor Pavlov : Public domain */
#ifndef ZIP7_INC_BCJ2_H
#define ZIP7_INC_BCJ2_H
#include "7zTypes.h"
EXTERN_C_BEGIN
#define BCJ2_NUM_STREAMS 4
enum
{
BCJ2_STREAM_MAIN,
BCJ2_STREAM_CALL,
BCJ2_STREAM_JUMP,
BCJ2_STREAM_RC
};
enum
{
BCJ2_DEC_STATE_ORIG_0 = BCJ2_NUM_STREAMS,
BCJ2_DEC_STATE_ORIG_1,
BCJ2_DEC_STATE_ORIG_2,
BCJ2_DEC_STATE_ORIG_3,
BCJ2_DEC_STATE_ORIG,
BCJ2_DEC_STATE_ERROR /* after detected data error */
};
enum
{
BCJ2_ENC_STATE_ORIG = BCJ2_NUM_STREAMS,
BCJ2_ENC_STATE_FINISHED /* it's state after fully encoded stream */
};
/* #define BCJ2_IS_32BIT_STREAM(s) ((s) == BCJ2_STREAM_CALL || (s) == BCJ2_STREAM_JUMP) */
#define BCJ2_IS_32BIT_STREAM(s) ((unsigned)((unsigned)(s) - (unsigned)BCJ2_STREAM_CALL) < 2)
/*
CBcj2Dec / CBcj2Enc
bufs sizes:
BUF_SIZE(n) = lims[n] - bufs[n]
bufs sizes for BCJ2_STREAM_CALL and BCJ2_STREAM_JUMP must be multiply of 4:
(BUF_SIZE(BCJ2_STREAM_CALL) & 3) == 0
(BUF_SIZE(BCJ2_STREAM_JUMP) & 3) == 0
*/
// typedef UInt32 CBcj2Prob;
typedef UInt16 CBcj2Prob;
/*
BCJ2 encoder / decoder internal requirements:
- If last bytes of stream contain marker (e8/e8/0f8x), then
there is also encoded symbol (0 : no conversion) in RC stream.
- One case of overlapped instructions is supported,
if last byte of converted instruction is (0f) and next byte is (8x):
marker [xx xx xx 0f] 8x
then the pair (0f 8x) is treated as marker.
*/
/* ---------- BCJ2 Decoder ---------- */
/*
CBcj2Dec:
(dest) is allowed to overlap with bufs[BCJ2_STREAM_MAIN], with the following conditions:
bufs[BCJ2_STREAM_MAIN] >= dest &&
bufs[BCJ2_STREAM_MAIN] - dest >=
BUF_SIZE(BCJ2_STREAM_CALL) +
BUF_SIZE(BCJ2_STREAM_JUMP)
reserve = bufs[BCJ2_STREAM_MAIN] - dest -
( BUF_SIZE(BCJ2_STREAM_CALL) +
BUF_SIZE(BCJ2_STREAM_JUMP) )
and additional conditions:
if (it's first call of Bcj2Dec_Decode() after Bcj2Dec_Init())
{
(reserve != 1) : if (ver < v23.00)
}
else // if there are more than one calls of Bcj2Dec_Decode() after Bcj2Dec_Init())
{
(reserve >= 6) : if (ver < v23.00)
(reserve >= 4) : if (ver >= v23.00)
We need that (reserve) because after first call of Bcj2Dec_Decode(),
CBcj2Dec::temp can contain up to 4 bytes for writing to (dest).
}
(reserve == 0) is allowed, if we decode full stream via single call of Bcj2Dec_Decode().
(reserve == 0) also is allowed in case of multi-call, if we use fixed buffers,
and (reserve) is calculated from full (final) sizes of all streams before first call.
*/
typedef struct
{
const Byte *bufs[BCJ2_NUM_STREAMS];
const Byte *lims[BCJ2_NUM_STREAMS];
Byte *dest;
const Byte *destLim;
unsigned state; /* BCJ2_STREAM_MAIN has more priority than BCJ2_STATE_ORIG */
UInt32 ip; /* property of starting base for decoding */
UInt32 temp; /* Byte temp[4]; */
UInt32 range;
UInt32 code;
CBcj2Prob probs[2 + 256];
} CBcj2Dec;
/* Note:
Bcj2Dec_Init() sets (CBcj2Dec::ip = 0)
if (ip != 0) property is required, the caller must set CBcj2Dec::ip after Bcj2Dec_Init()
*/
void Bcj2Dec_Init(CBcj2Dec *p);
/* Bcj2Dec_Decode():
returns:
SZ_OK
SZ_ERROR_DATA : if data in 5 starting bytes of BCJ2_STREAM_RC stream are not correct
*/
SRes Bcj2Dec_Decode(CBcj2Dec *p);
/* To check that decoding was finished you can compare
sizes of processed streams with sizes known from another sources.
You must do at least one mandatory check from the two following options:
- the check for size of processed output (ORIG) stream.
- the check for size of processed input (MAIN) stream.
additional optional checks:
- the checks for processed sizes of all input streams (MAIN, CALL, JUMP, RC)
- the checks Bcj2Dec_IsMaybeFinished*()
also before actual decoding you can check that the
following condition is met for stream sizes:
( size(ORIG) == size(MAIN) + size(CALL) + size(JUMP) )
*/
/* (state == BCJ2_STREAM_MAIN) means that decoder is ready for
additional input data in BCJ2_STREAM_MAIN stream.
Note that (state == BCJ2_STREAM_MAIN) is allowed for non-finished decoding.
*/
#define Bcj2Dec_IsMaybeFinished_state_MAIN(_p_) ((_p_)->state == BCJ2_STREAM_MAIN)
/* if the stream decoding was finished correctly, then range decoder
part of CBcj2Dec also was finished, and then (CBcj2Dec::code == 0).
Note that (CBcj2Dec::code == 0) is allowed for non-finished decoding.
*/
#define Bcj2Dec_IsMaybeFinished_code(_p_) ((_p_)->code == 0)
/* use Bcj2Dec_IsMaybeFinished() only as additional check
after at least one mandatory check from the two following options:
- the check for size of processed output (ORIG) stream.
- the check for size of processed input (MAIN) stream.
*/
#define Bcj2Dec_IsMaybeFinished(_p_) ( \
Bcj2Dec_IsMaybeFinished_state_MAIN(_p_) && \
Bcj2Dec_IsMaybeFinished_code(_p_))
/* ---------- BCJ2 Encoder ---------- */
typedef enum
{
BCJ2_ENC_FINISH_MODE_CONTINUE,
BCJ2_ENC_FINISH_MODE_END_BLOCK,
BCJ2_ENC_FINISH_MODE_END_STREAM
} EBcj2Enc_FinishMode;
/*
BCJ2_ENC_FINISH_MODE_CONTINUE:
process non finished encoding.
It notifies the encoder that additional further calls
can provide more input data (src) than provided by current call.
In that case the CBcj2Enc encoder still can move (src) pointer
up to (srcLim), but CBcj2Enc encoder can store some of the last
processed bytes (up to 4 bytes) from src to internal CBcj2Enc::temp[] buffer.
at return:
(CBcj2Enc::src will point to position that includes
processed data and data copied to (temp[]) buffer)
That data from (temp[]) buffer will be used in further calls.
BCJ2_ENC_FINISH_MODE_END_BLOCK:
finish encoding of current block (ended at srcLim) without RC flushing.
at return: if (CBcj2Enc::state == BCJ2_ENC_STATE_ORIG) &&
CBcj2Enc::src == CBcj2Enc::srcLim)
: it shows that block encoding was finished. And the encoder is
ready for new (src) data or for stream finish operation.
finished block means
{
CBcj2Enc has completed block encoding up to (srcLim).
(1 + 4 bytes) or (2 + 4 bytes) CALL/JUMP cortages will
not cross block boundary at (srcLim).
temporary CBcj2Enc buffer for (ORIG) src data is empty.
3 output uncompressed streams (MAIN, CALL, JUMP) were flushed.
RC stream was not flushed. And RC stream will cross block boundary.
}
Note: some possible implementation of BCJ2 encoder could
write branch marker (e8/e8/0f8x) in one call of Bcj2Enc_Encode(),
and it could calculate symbol for RC in another call of Bcj2Enc_Encode().
BCJ2 encoder uses ip/fileIp/fileSize/relatLimit values to calculate RC symbol.
And these CBcj2Enc variables can have different values in different Bcj2Enc_Encode() calls.
So caller must finish each block with BCJ2_ENC_FINISH_MODE_END_BLOCK
to ensure that RC symbol is calculated and written in proper block.
BCJ2_ENC_FINISH_MODE_END_STREAM
finish encoding of stream (ended at srcLim) fully including RC flushing.
at return: if (CBcj2Enc::state == BCJ2_ENC_STATE_FINISHED)
: it shows that stream encoding was finished fully,
and all output streams were flushed fully.
also Bcj2Enc_IsFinished() can be called.
*/
/*
32-bit relative offset in JUMP/CALL commands is
- (mod 4 GiB) for 32-bit x86 code
- signed Int32 for 64-bit x86-64 code
BCJ2 encoder also does internal relative to absolute address conversions.
And there are 2 possible ways to do it:
before v23: we used 32-bit variables and (mod 4 GiB) conversion
since v23: we use 64-bit variables and (signed Int32 offset) conversion.
The absolute address condition for conversion in v23:
((UInt64)((Int64)ip64 - (Int64)fileIp64 + 5 + (Int32)offset) < (UInt64)fileSize64)
note that if (fileSize64 > 2 GiB). there is difference between
old (mod 4 GiB) way (v22) and new (signed Int32 offset) way (v23).
And new (v23) way is more suitable to encode 64-bit x86-64 code for (fileSize64 > 2 GiB) cases.
*/
/*
// for old (v22) way for conversion:
typedef UInt32 CBcj2Enc_ip_unsigned;
typedef Int32 CBcj2Enc_ip_signed;
#define BCJ2_ENC_FileSize_MAX ((UInt32)1 << 31)
*/
typedef UInt64 CBcj2Enc_ip_unsigned;
typedef Int64 CBcj2Enc_ip_signed;
/* maximum size of file that can be used for conversion condition */
#define BCJ2_ENC_FileSize_MAX ((CBcj2Enc_ip_unsigned)0 - 2)
/* default value of fileSize64_minus1 variable that means
that absolute address limitation will not be used */
#define BCJ2_ENC_FileSizeField_UNLIMITED ((CBcj2Enc_ip_unsigned)0 - 1)
/* calculate value that later can be set to CBcj2Enc::fileSize64_minus1 */
#define BCJ2_ENC_GET_FileSizeField_VAL_FROM_FileSize(fileSize) \
((CBcj2Enc_ip_unsigned)(fileSize) - 1)
/* set CBcj2Enc::fileSize64_minus1 variable from size of file */
#define Bcj2Enc_SET_FileSize(p, fileSize) \
(p)->fileSize64_minus1 = BCJ2_ENC_GET_FileSizeField_VAL_FROM_FileSize(fileSize);
typedef struct
{
Byte *bufs[BCJ2_NUM_STREAMS];
const Byte *lims[BCJ2_NUM_STREAMS];
const Byte *src;
const Byte *srcLim;
unsigned state;
EBcj2Enc_FinishMode finishMode;
Byte context;
Byte flushRem;
Byte isFlushState;
Byte cache;
UInt32 range;
UInt64 low;
UInt64 cacheSize;
// UInt32 context; // for marker version, it can include marker flag.
/* (ip64) and (fileIp64) correspond to virtual source stream position
that doesn't include data in temp[] */
CBcj2Enc_ip_unsigned ip64; /* current (ip) position */
CBcj2Enc_ip_unsigned fileIp64; /* start (ip) position of current file */
CBcj2Enc_ip_unsigned fileSize64_minus1; /* size of current file (for conversion limitation) */
UInt32 relatLimit; /* (relatLimit <= ((UInt32)1 << 31)) : 0 means disable_conversion */
// UInt32 relatExcludeBits;
UInt32 tempTarget;
unsigned tempPos; /* the number of bytes that were copied to temp[] buffer
(tempPos <= 4) outside of Bcj2Enc_Encode() */
// Byte temp[4]; // for marker version
Byte temp[8];
CBcj2Prob probs[2 + 256];
} CBcj2Enc;
void Bcj2Enc_Init(CBcj2Enc *p);
/*
Bcj2Enc_Encode(): at exit:
p->State < BCJ2_NUM_STREAMS : we need more buffer space for output stream
(bufs[p->State] == lims[p->State])
p->State == BCJ2_ENC_STATE_ORIG : we need more data in input src stream
(src == srcLim)
p->State == BCJ2_ENC_STATE_FINISHED : after fully encoded stream
*/
void Bcj2Enc_Encode(CBcj2Enc *p);
/* Bcj2Enc encoder can look ahead for up 4 bytes of source stream.
CBcj2Enc::tempPos : is the number of bytes that were copied from input stream to temp[] buffer.
(CBcj2Enc::src) after Bcj2Enc_Encode() is starting position after
fully processed data and after data copied to temp buffer.
So if the caller needs to get real number of fully processed input
bytes (without look ahead data in temp buffer),
the caller must subtruct (CBcj2Enc::tempPos) value from processed size
value that is calculated based on current (CBcj2Enc::src):
cur_processed_pos = Calc_Big_Processed_Pos(enc.src)) -
Bcj2Enc_Get_AvailInputSize_in_Temp(&enc);
*/
/* get the size of input data that was stored in temp[] buffer: */
#define Bcj2Enc_Get_AvailInputSize_in_Temp(p) ((p)->tempPos)
#define Bcj2Enc_IsFinished(p) ((p)->flushRem == 0)
/* Note : the decoder supports overlapping of marker (0f 80).
But we can eliminate such overlapping cases by setting
the limit for relative offset conversion as
CBcj2Enc::relatLimit <= (0x0f << 24) == (240 MiB)
*/
/* default value for CBcj2Enc::relatLimit */
#define BCJ2_ENC_RELAT_LIMIT_DEFAULT ((UInt32)0x0f << 24)
#define BCJ2_ENC_RELAT_LIMIT_MAX ((UInt32)1 << 31)
// #define BCJ2_RELAT_EXCLUDE_NUM_BITS 5
EXTERN_C_END
#endif
+420
View File
@@ -0,0 +1,420 @@
/* Bra.c -- Branch converters for RISC code
2023-04-02 : Igor Pavlov : Public domain */
#include "Precomp.h"
#include "Bra.h"
#include "CpuArch.h"
#include "RotateDefs.h"
#if defined(MY_CPU_SIZEOF_POINTER) \
&& ( MY_CPU_SIZEOF_POINTER == 4 \
|| MY_CPU_SIZEOF_POINTER == 8)
#define BR_CONV_USE_OPT_PC_PTR
#endif
#ifdef BR_CONV_USE_OPT_PC_PTR
#define BR_PC_INIT pc -= (UInt32)(SizeT)p;
#define BR_PC_GET (pc + (UInt32)(SizeT)p)
#else
#define BR_PC_INIT pc += (UInt32)size;
#define BR_PC_GET (pc - (UInt32)(SizeT)(lim - p))
// #define BR_PC_INIT
// #define BR_PC_GET (pc + (UInt32)(SizeT)(p - data))
#endif
#define BR_CONVERT_VAL(v, c) if (encoding) v += c; else v -= c;
// #define BR_CONVERT_VAL(v, c) if (!encoding) c = (UInt32)0 - c; v += c;
#define Z7_BRANCH_CONV(name) z7_BranchConv_ ## name
#define Z7_BRANCH_FUNC_MAIN(name) \
static \
Z7_FORCE_INLINE \
Z7_ATTRIB_NO_VECTOR \
Byte *Z7_BRANCH_CONV(name)(Byte *p, SizeT size, UInt32 pc, int encoding)
#define Z7_BRANCH_FUNC_IMP(name, m, encoding) \
Z7_NO_INLINE \
Z7_ATTRIB_NO_VECTOR \
Byte *m(name)(Byte *data, SizeT size, UInt32 pc) \
{ return Z7_BRANCH_CONV(name)(data, size, pc, encoding); } \
#ifdef Z7_EXTRACT_ONLY
#define Z7_BRANCH_FUNCS_IMP(name) \
Z7_BRANCH_FUNC_IMP(name, Z7_BRANCH_CONV_DEC, 0)
#else
#define Z7_BRANCH_FUNCS_IMP(name) \
Z7_BRANCH_FUNC_IMP(name, Z7_BRANCH_CONV_DEC, 0) \
Z7_BRANCH_FUNC_IMP(name, Z7_BRANCH_CONV_ENC, 1)
#endif
#if defined(__clang__)
#define BR_EXTERNAL_FOR
#define BR_NEXT_ITERATION continue;
#else
#define BR_EXTERNAL_FOR for (;;)
#define BR_NEXT_ITERATION break;
#endif
#if defined(__clang__) && (__clang_major__ >= 8) \
|| defined(__GNUC__) && (__GNUC__ >= 1000) \
// GCC is not good for __builtin_expect() here
/* || defined(_MSC_VER) && (_MSC_VER >= 1920) */
// #define Z7_unlikely [[unlikely]]
// #define Z7_LIKELY(x) (__builtin_expect((x), 1))
#define Z7_UNLIKELY(x) (__builtin_expect((x), 0))
// #define Z7_likely [[likely]]
#else
// #define Z7_LIKELY(x) (x)
#define Z7_UNLIKELY(x) (x)
// #define Z7_likely
#endif
Z7_BRANCH_FUNC_MAIN(ARM64)
{
// Byte *p = data;
const Byte *lim;
const UInt32 flag = (UInt32)1 << (24 - 4);
const UInt32 mask = ((UInt32)1 << 24) - (flag << 1);
size &= ~(SizeT)3;
// if (size == 0) return p;
lim = p + size;
BR_PC_INIT
pc -= 4; // because (p) will point to next instruction
BR_EXTERNAL_FOR
{
// Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE
for (;;)
{
UInt32 v;
if Z7_UNLIKELY(p == lim)
return p;
v = GetUi32a(p);
p += 4;
if Z7_UNLIKELY(((v - 0x94000000) & 0xfc000000) == 0)
{
UInt32 c = BR_PC_GET >> 2;
BR_CONVERT_VAL(v, c)
v &= 0x03ffffff;
v |= 0x94000000;
SetUi32a(p - 4, v)
BR_NEXT_ITERATION
}
// v = rotlFixed(v, 8); v += (flag << 8) - 0x90; if Z7_UNLIKELY((v & ((mask << 8) + 0x9f)) == 0)
v -= 0x90000000; if Z7_UNLIKELY((v & 0x9f000000) == 0)
{
UInt32 z, c;
// v = rotrFixed(v, 8);
v += flag; if Z7_UNLIKELY(v & mask) continue;
z = (v & 0xffffffe0) | (v >> 26);
c = (BR_PC_GET >> (12 - 3)) & ~(UInt32)7;
BR_CONVERT_VAL(z, c)
v &= 0x1f;
v |= 0x90000000;
v |= z << 26;
v |= 0x00ffffe0 & ((z & (((flag << 1) - 1))) - flag);
SetUi32a(p - 4, v)
}
}
}
}
Z7_BRANCH_FUNCS_IMP(ARM64)
Z7_BRANCH_FUNC_MAIN(ARM)
{
// Byte *p = data;
const Byte *lim;
size &= ~(SizeT)3;
lim = p + size;
BR_PC_INIT
/* in ARM: branch offset is relative to the +2 instructions from current instruction.
(p) will point to next instruction */
pc += 8 - 4;
for (;;)
{
for (;;)
{
if Z7_UNLIKELY(p >= lim) { return p; } p += 4; if Z7_UNLIKELY(p[-1] == 0xeb) break;
if Z7_UNLIKELY(p >= lim) { return p; } p += 4; if Z7_UNLIKELY(p[-1] == 0xeb) break;
}
{
UInt32 v = GetUi32a(p - 4);
UInt32 c = BR_PC_GET >> 2;
BR_CONVERT_VAL(v, c)
v &= 0x00ffffff;
v |= 0xeb000000;
SetUi32a(p - 4, v)
}
}
}
Z7_BRANCH_FUNCS_IMP(ARM)
Z7_BRANCH_FUNC_MAIN(PPC)
{
// Byte *p = data;
const Byte *lim;
size &= ~(SizeT)3;
lim = p + size;
BR_PC_INIT
pc -= 4; // because (p) will point to next instruction
for (;;)
{
UInt32 v;
for (;;)
{
if Z7_UNLIKELY(p == lim)
return p;
// v = GetBe32a(p);
v = *(UInt32 *)(void *)p;
p += 4;
// if ((v & 0xfc000003) == 0x48000001) break;
// if ((p[-4] & 0xFC) == 0x48 && (p[-1] & 3) == 1) break;
if Z7_UNLIKELY(
((v - Z7_CONV_BE_TO_NATIVE_CONST32(0x48000001))
& Z7_CONV_BE_TO_NATIVE_CONST32(0xfc000003)) == 0) break;
}
{
v = Z7_CONV_NATIVE_TO_BE_32(v);
{
UInt32 c = BR_PC_GET;
BR_CONVERT_VAL(v, c)
}
v &= 0x03ffffff;
v |= 0x48000000;
SetBe32a(p - 4, v)
}
}
}
Z7_BRANCH_FUNCS_IMP(PPC)
#ifdef Z7_CPU_FAST_ROTATE_SUPPORTED
#define BR_SPARC_USE_ROTATE
#endif
Z7_BRANCH_FUNC_MAIN(SPARC)
{
// Byte *p = data;
const Byte *lim;
const UInt32 flag = (UInt32)1 << 22;
size &= ~(SizeT)3;
lim = p + size;
BR_PC_INIT
pc -= 4; // because (p) will point to next instruction
for (;;)
{
UInt32 v;
for (;;)
{
if Z7_UNLIKELY(p == lim)
return p;
/* // the code without GetBe32a():
{ const UInt32 v = GetUi16a(p) & 0xc0ff; p += 4; if (v == 0x40 || v == 0xc07f) break; }
*/
v = GetBe32a(p);
p += 4;
#ifdef BR_SPARC_USE_ROTATE
v = rotlFixed(v, 2);
v += (flag << 2) - 1;
if Z7_UNLIKELY((v & (3 - (flag << 3))) == 0)
#else
v += (UInt32)5 << 29;
v ^= (UInt32)7 << 29;
v += flag;
if Z7_UNLIKELY((v & (0 - (flag << 1))) == 0)
#endif
break;
}
{
// UInt32 v = GetBe32a(p - 4);
#ifndef BR_SPARC_USE_ROTATE
v <<= 2;
#endif
{
UInt32 c = BR_PC_GET;
BR_CONVERT_VAL(v, c)
}
v &= (flag << 3) - 1;
#ifdef BR_SPARC_USE_ROTATE
v -= (flag << 2) - 1;
v = rotrFixed(v, 2);
#else
v -= (flag << 2);
v >>= 2;
v |= (UInt32)1 << 30;
#endif
SetBe32a(p - 4, v)
}
}
}
Z7_BRANCH_FUNCS_IMP(SPARC)
Z7_BRANCH_FUNC_MAIN(ARMT)
{
// Byte *p = data;
Byte *lim;
size &= ~(SizeT)1;
// if (size == 0) return p;
if (size <= 2) return p;
size -= 2;
lim = p + size;
BR_PC_INIT
/* in ARM: branch offset is relative to the +2 instructions from current instruction.
(p) will point to the +2 instructions from current instruction */
// pc += 4 - 4;
// if (encoding) pc -= 0xf800 << 1; else pc += 0xf800 << 1;
// #define ARMT_TAIL_PROC { goto armt_tail; }
#define ARMT_TAIL_PROC { return p; }
do
{
/* in MSVC 32-bit x86 compilers:
UInt32 version : it loads value from memory with movzx
Byte version : it loads value to 8-bit register (AL/CL)
movzx version is slightly faster in some cpus
*/
unsigned b1;
// Byte / unsigned
b1 = p[1];
// optimized version to reduce one (p >= lim) check:
// unsigned a1 = p[1]; b1 = p[3]; p += 2; if Z7_LIKELY((b1 & (a1 ^ 8)) < 0xf8)
for (;;)
{
unsigned b3; // Byte / UInt32
/* (Byte)(b3) normalization can use low byte computations in MSVC.
It gives smaller code, and no loss of speed in some compilers/cpus.
But new MSVC 32-bit x86 compilers use more slow load
from memory to low byte register in that case.
So we try to use full 32-bit computations for faster code.
*/
// if (p >= lim) { ARMT_TAIL_PROC } b3 = b1 + 8; b1 = p[3]; p += 2; if ((b3 & b1) >= 0xf8) break;
if Z7_UNLIKELY(p >= lim) { ARMT_TAIL_PROC } b3 = p[3]; p += 2; if Z7_UNLIKELY((b3 & (b1 ^ 8)) >= 0xf8) break;
if Z7_UNLIKELY(p >= lim) { ARMT_TAIL_PROC } b1 = p[3]; p += 2; if Z7_UNLIKELY((b1 & (b3 ^ 8)) >= 0xf8) break;
}
{
/* we can adjust pc for (0xf800) to rid of (& 0x7FF) operation.
But gcc/clang for arm64 can use bfi instruction for full code here */
UInt32 v =
((UInt32)GetUi16a(p - 2) << 11) |
((UInt32)GetUi16a(p) & 0x7FF);
/*
UInt32 v =
((UInt32)p[1 - 2] << 19)
+ (((UInt32)p[1] & 0x7) << 8)
+ (((UInt32)p[-2] << 11))
+ (p[0]);
*/
p += 2;
{
UInt32 c = BR_PC_GET >> 1;
BR_CONVERT_VAL(v, c)
}
SetUi16a(p - 4, (UInt16)(((v >> 11) & 0x7ff) | 0xf000))
SetUi16a(p - 2, (UInt16)(v | 0xf800))
/*
p[-4] = (Byte)(v >> 11);
p[-3] = (Byte)(0xf0 | ((v >> 19) & 0x7));
p[-2] = (Byte)v;
p[-1] = (Byte)(0xf8 | (v >> 8));
*/
}
}
while (p < lim);
return p;
// armt_tail:
// if ((Byte)((lim[1] & 0xf8)) != 0xf0) { lim += 2; } return lim;
// return (Byte *)(lim + ((Byte)((lim[1] ^ 0xf0) & 0xf8) == 0 ? 0 : 2));
// return (Byte *)(lim + (((lim[1] ^ ~0xfu) & ~7u) == 0 ? 0 : 2));
// return (Byte *)(lim + 2 - (((((unsigned)lim[1] ^ 8) + 8) >> 7) & 2));
}
Z7_BRANCH_FUNCS_IMP(ARMT)
// #define BR_IA64_NO_INLINE
Z7_BRANCH_FUNC_MAIN(IA64)
{
// Byte *p = data;
const Byte *lim;
size &= ~(SizeT)15;
lim = p + size;
pc -= 1 << 4;
pc >>= 4 - 1;
// pc -= 1 << 1;
for (;;)
{
unsigned m;
for (;;)
{
if Z7_UNLIKELY(p == lim)
return p;
m = (unsigned)((UInt32)0x334b0000 >> (*p & 0x1e));
p += 16;
pc += 1 << 1;
if (m &= 3)
break;
}
{
p += (ptrdiff_t)m * 5 - 20; // negative value is expected here.
do
{
const UInt32 t =
#if defined(MY_CPU_X86_OR_AMD64)
// we use 32-bit load here to reduce code size on x86:
GetUi32(p);
#else
GetUi16(p);
#endif
UInt32 z = GetUi32(p + 1) >> m;
p += 5;
if (((t >> m) & (0x70 << 1)) == 0
&& ((z - (0x5000000 << 1)) & (0xf000000 << 1)) == 0)
{
UInt32 v = (UInt32)((0x8fffff << 1) | 1) & z;
z ^= v;
#ifdef BR_IA64_NO_INLINE
v |= (v & ((UInt32)1 << (23 + 1))) >> 3;
{
UInt32 c = pc;
BR_CONVERT_VAL(v, c)
}
v &= (0x1fffff << 1) | 1;
#else
{
if (encoding)
{
// pc &= ~(0xc00000 << 1); // we just need to clear at least 2 bits
pc &= (0x1fffff << 1) | 1;
v += pc;
}
else
{
// pc |= 0xc00000 << 1; // we need to set at least 2 bits
pc |= ~(UInt32)((0x1fffff << 1) | 1);
v -= pc;
}
}
v &= ~(UInt32)(0x600000 << 1);
#endif
v += (0x700000 << 1);
v &= (0x8fffff << 1) | 1;
z |= v;
z <<= m;
SetUi32(p + 1 - 5, z)
}
m++;
}
while (m &= 3); // while (m < 4);
}
}
}
Z7_BRANCH_FUNCS_IMP(IA64)
+99
View File
@@ -0,0 +1,99 @@
/* Bra.h -- Branch converters for executables
2023-04-02 : Igor Pavlov : Public domain */
#ifndef ZIP7_INC_BRA_H
#define ZIP7_INC_BRA_H
#include "7zTypes.h"
EXTERN_C_BEGIN
#define Z7_BRANCH_CONV_DEC(name) z7_BranchConv_ ## name ## _Dec
#define Z7_BRANCH_CONV_ENC(name) z7_BranchConv_ ## name ## _Enc
#define Z7_BRANCH_CONV_ST_DEC(name) z7_BranchConvSt_ ## name ## _Dec
#define Z7_BRANCH_CONV_ST_ENC(name) z7_BranchConvSt_ ## name ## _Enc
#define Z7_BRANCH_CONV_DECL(name) Byte * name(Byte *data, SizeT size, UInt32 pc)
#define Z7_BRANCH_CONV_ST_DECL(name) Byte * name(Byte *data, SizeT size, UInt32 pc, UInt32 *state)
typedef Z7_BRANCH_CONV_DECL( (*z7_Func_BranchConv));
typedef Z7_BRANCH_CONV_ST_DECL((*z7_Func_BranchConvSt));
#define Z7_BRANCH_CONV_ST_X86_STATE_INIT_VAL 0
Z7_BRANCH_CONV_ST_DECL(Z7_BRANCH_CONV_ST_DEC(X86));
Z7_BRANCH_CONV_ST_DECL(Z7_BRANCH_CONV_ST_ENC(X86));
#define Z7_BRANCH_FUNCS_DECL(name) \
Z7_BRANCH_CONV_DECL(Z7_BRANCH_CONV_DEC(name)); \
Z7_BRANCH_CONV_DECL(Z7_BRANCH_CONV_ENC(name));
Z7_BRANCH_FUNCS_DECL(ARM64)
Z7_BRANCH_FUNCS_DECL(ARM)
Z7_BRANCH_FUNCS_DECL(ARMT)
Z7_BRANCH_FUNCS_DECL(PPC)
Z7_BRANCH_FUNCS_DECL(SPARC)
Z7_BRANCH_FUNCS_DECL(IA64)
/*
These functions convert data that contain CPU instructions.
Each such function converts relative addresses to absolute addresses in some
branch instructions: CALL (in all converters) and JUMP (X86 converter only).
Such conversion allows to increase compression ratio, if we compress that data.
There are 2 types of converters:
Byte * Conv_RISC (Byte *data, SizeT size, UInt32 pc);
Byte * ConvSt_X86(Byte *data, SizeT size, UInt32 pc, UInt32 *state);
Each Converter supports 2 versions: one for encoding
and one for decoding (_Enc/_Dec postfixes in function name).
In params:
data : data buffer
size : size of data
pc : current virtual Program Counter (Instruction Pinter) value
In/Out param:
state : pointer to state variable (for X86 converter only)
Return:
The pointer to position in (data) buffer after last byte that was processed.
If the caller calls converter again, it must call it starting with that position.
But the caller is allowed to move data in buffer. so pointer to
current processed position also will be changed for next call.
Also the caller must increase internal (pc) value for next call.
Each converter has some characteristics: Endian, Alignment, LookAhead.
Type Endian Alignment LookAhead
X86 little 1 4
ARMT little 2 2
ARM little 4 0
ARM64 little 4 0
PPC big 4 0
SPARC big 4 0
IA64 little 16 0
(data) must be aligned for (Alignment).
processed size can be calculated as:
SizeT processed = Conv(data, size, pc) - data;
if (processed == 0)
it means that converter needs more data for processing.
If (size < Alignment + LookAhead)
then (processed == 0) is allowed.
Example code for conversion in loop:
UInt32 pc = 0;
size = 0;
for (;;)
{
size += Load_more_input_data(data + size);
SizeT processed = Conv(data, size, pc) - data;
if (processed == 0 && no_more_input_data_after_size)
break; // we stop convert loop
data += processed;
size -= processed;
pc += processed;
}
*/
EXTERN_C_END
#endif

Some files were not shown because too many files have changed in this diff Show More