mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-08-06 13:24:14 +02:00
Merge branch 'master' into qt6_cmake_joshua
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()};
|
||||
|
||||
|
||||
@@ -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,"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -112,6 +112,7 @@ ExportDialog::ExportDialog(
|
||||
*/
|
||||
ExportDialog::~ExportDialog()
|
||||
{
|
||||
qDeleteAll(diagram_lines_);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include "../qetgraphicsitem/ViewItem/qetgraphicstableitem.h"
|
||||
#include "../utils/qetutils.h"
|
||||
#include "ui/addtabledialog.h"
|
||||
|
||||
#include "../qetproject.h"
|
||||
#include <QDialog>
|
||||
|
||||
QetGraphicsTableFactory::QetGraphicsTableFactory()
|
||||
|
||||
@@ -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());
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
Reference in New Issue
Block a user