mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-15 10:04:13 +02:00
Merge pull request #1483 from ispyisail/fix/qetz-1472-followups
Fix saving to a read-only folder, and the #1472 review follow-ups
This commit is contained in:
@@ -69,6 +69,10 @@ set reg_file=qet_install_file_associations.reg
|
||||
echo [HKEY_CURRENT_USER\Software\Classes\qet_diagram_file\shell\open\command]
|
||||
echo @="\"%final_qet_exe%\" \"%%1\""
|
||||
|
||||
rem *.qetz (zipped project): the same file type as *.qet
|
||||
echo [HKEY_CURRENT_USER\Software\Classes\.qetz]
|
||||
echo @="qet_diagram_file"
|
||||
|
||||
rem association de fichier *.elmt
|
||||
echo [HKEY_CURRENT_USER\Software\Classes\.elmt]
|
||||
echo @="qet_element_file"
|
||||
|
||||
+922
-943
File diff suppressed because it is too large
Load Diff
+931
-937
File diff suppressed because it is too large
Load Diff
+922
-943
File diff suppressed because it is too large
Load Diff
+922
-943
File diff suppressed because it is too large
Load Diff
+947
-878
File diff suppressed because it is too large
Load Diff
+922
-943
File diff suppressed because it is too large
Load Diff
+947
-878
File diff suppressed because it is too large
Load Diff
+929
-935
File diff suppressed because it is too large
Load Diff
+920
-949
File diff suppressed because it is too large
Load Diff
+911
-880
File diff suppressed because it is too large
Load Diff
+920
-949
File diff suppressed because it is too large
Load Diff
+931
-937
File diff suppressed because it is too large
Load Diff
+926
-947
File diff suppressed because it is too large
Load Diff
+922
-943
File diff suppressed because it is too large
Load Diff
+922
-943
File diff suppressed because it is too large
Load Diff
+921
-946
File diff suppressed because it is too large
Load Diff
+922
-943
File diff suppressed because it is too large
Load Diff
+922
-943
File diff suppressed because it is too large
Load Diff
+931
-937
File diff suppressed because it is too large
Load Diff
+920
-949
File diff suppressed because it is too large
Load Diff
+947
-878
File diff suppressed because it is too large
Load Diff
+920
-949
File diff suppressed because it is too large
Load Diff
+959
-890
File diff suppressed because it is too large
Load Diff
+920
-949
File diff suppressed because it is too large
Load Diff
+920
-949
File diff suppressed because it is too large
Load Diff
+929
-935
File diff suppressed because it is too large
Load Diff
+920
-949
File diff suppressed because it is too large
Load Diff
+920
-949
File diff suppressed because it is too large
Load Diff
+920
-949
File diff suppressed because it is too large
Load Diff
+922
-943
File diff suppressed because it is too large
Load Diff
+922
-943
File diff suppressed because it is too large
Load Diff
+922
-943
File diff suppressed because it is too large
Load Diff
+931
-937
File diff suppressed because it is too large
Load Diff
@@ -34,10 +34,62 @@
|
||||
<string>qet</string>
|
||||
<string>qetz</string>
|
||||
</array>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>org.qelectrotech.project</string>
|
||||
<string>org.qelectrotech.project-zip</string>
|
||||
</array>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>org.qelectrotech.project</string>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>QElectroTech project</string>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.xml</string>
|
||||
</array>
|
||||
<key>UTTypeIconFile</key>
|
||||
<string>qet</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>qet</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<string>application/x-qet-project</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>org.qelectrotech.project-zip</string>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>QElectroTech zipped project</string>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.zip-archive</string>
|
||||
</array>
|
||||
<key>UTTypeIconFile</key>
|
||||
<string>qet</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>qetz</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<string>application/x-qet-project+zip</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>qelectrotech</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
|
||||
@@ -6,7 +6,7 @@ Exec=qelectrotech %F
|
||||
Icon=qelectrotech
|
||||
Terminal=false
|
||||
Type=Application
|
||||
MimeType=application/x-qet-project;application/x-qelectrotech-project+zip;application/x-qet-element;application/x-qet-titleblock;
|
||||
MimeType=application/x-qet-project;application/x-qet-project+zip;application/x-qet-element;application/x-qet-titleblock;
|
||||
Categories=Graphics;
|
||||
Keywords=Graphics;Science;Electricity;Engineering;
|
||||
Comment=Edit electrical diagrams.
|
||||
|
||||
@@ -37,6 +37,12 @@
|
||||
<summary xml:lang="tr">Elektrik şeması düzenleyici</summary>
|
||||
<summary xml:lang="uk">Редактор електричних схем</summary>
|
||||
<summary xml:lang="zh">电气图编辑器</summary>
|
||||
<provides>
|
||||
<mediatype>application/x-qet-project</mediatype>
|
||||
<mediatype>application/x-qet-project+zip</mediatype>
|
||||
<mediatype>application/x-qet-element</mediatype>
|
||||
<mediatype>application/x-qet-titleblock</mediatype>
|
||||
</provides>
|
||||
<content_rating type="oars-1.1"/>
|
||||
<releases>
|
||||
<release version="0.100.1-dev" date="2026"/>
|
||||
|
||||
@@ -34,11 +34,13 @@
|
||||
<comment xml:lang="zh">QElectroTech 项目文件</comment>
|
||||
<glob pattern="*.qet" />
|
||||
</mime-type>
|
||||
<mime-type type="application/x-qelectrotech-project+zip">
|
||||
<mime-type type="application/x-qet-project+zip">
|
||||
<alias type="application/x-qelectrotech-project+zip"/>
|
||||
<sub-class-of type="application/zip"/>
|
||||
<comment>QElectroTech zipped project file</comment>
|
||||
<glob pattern="*.qetz" />
|
||||
<magic priority="60">
|
||||
<match type="string" offset="30" value="mimetypeapplication/x-qet-project+zip"/>
|
||||
<match type="string" offset="30" value="mimetypeapplication/x-qelectrotech-project+zip"/>
|
||||
</magic>
|
||||
</mime-type>
|
||||
|
||||
+52
-9
@@ -56,6 +56,7 @@ workspace policy applies exactly as it does over stdio.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import atexit
|
||||
import datetime
|
||||
import json
|
||||
import re
|
||||
@@ -110,7 +111,21 @@ def _root(path: str) -> ET.Element:
|
||||
# folio. The tools that read a project's XML read the .qet QElectroTech
|
||||
# saves from it, so they read what QElectroTech reads, whatever the zipped
|
||||
# format holds; one copy per version of the file (path, size, time).
|
||||
# Only the newest few are kept, and the server removes them when it exits.
|
||||
_QETZ_COPIES: dict = {}
|
||||
_QETZ_COPIES_MAX = 8
|
||||
|
||||
|
||||
def _forget_qetz_copy(key) -> None:
|
||||
out = _QETZ_COPIES.pop(key, None)
|
||||
if out is not None:
|
||||
shutil.rmtree(out.parent, ignore_errors=True)
|
||||
|
||||
|
||||
@atexit.register
|
||||
def _forget_qetz_copies() -> None:
|
||||
for key in list(_QETZ_COPIES):
|
||||
_forget_qetz_copy(key)
|
||||
|
||||
|
||||
def _is_qetz(path) -> bool:
|
||||
@@ -129,6 +144,7 @@ def _as_qet(path) -> Path:
|
||||
key = (str(p.resolve()), st.st_size, st.st_mtime_ns)
|
||||
cached = _QETZ_COPIES.get(key)
|
||||
if cached is not None and cached.is_file():
|
||||
_QETZ_COPIES[key] = _QETZ_COPIES.pop(key) # newest last
|
||||
return cached
|
||||
binary = resolve_binary()
|
||||
if not binary:
|
||||
@@ -138,12 +154,21 @@ def _as_qet(path) -> Path:
|
||||
out = folder / (p.stem + ".qet")
|
||||
# Saving as .qet builds every folio: allow a big project its time
|
||||
result = _run_qet(str(binary), ["--resave", str(p), str(out)], timeout=900)
|
||||
if not out.is_file() or out.stat().st_size == 0:
|
||||
# A run that timed out or failed may have left part of a .qet behind
|
||||
if not result.get("ok") or not out.is_file() or out.stat().st_size == 0:
|
||||
shutil.rmtree(folder, ignore_errors=True)
|
||||
detail = (result.get("stderr") or result.get("stdout") or "").strip()[-400:]
|
||||
if result.get("timed_out"):
|
||||
detail = f"timed out after {result.get('timeout_s')} s"
|
||||
else:
|
||||
detail = (result.get("stderr") or result.get("stdout") or "").strip()[-400:]
|
||||
raise ValueError(f"QElectroTech could not read {p.name}"
|
||||
+ (f": {detail}" if detail else ""))
|
||||
# An older version of the same file is not read again
|
||||
for old in [k for k in _QETZ_COPIES if k[0] == key[0]]:
|
||||
_forget_qetz_copy(old)
|
||||
_QETZ_COPIES[key] = out
|
||||
while len(_QETZ_COPIES) > _QETZ_COPIES_MAX:
|
||||
_forget_qetz_copy(next(iter(_QETZ_COPIES)))
|
||||
return out
|
||||
|
||||
|
||||
@@ -840,12 +865,17 @@ def _num(v) -> float:
|
||||
|
||||
|
||||
def tool_scan(directory: str, tag: str = "conductor",
|
||||
attribute: str = "cable", recursive: bool = True) -> dict:
|
||||
attribute: str = "cable", recursive: bool = True,
|
||||
max_qetz: int = 20) -> dict:
|
||||
"""Sweep every .qet and .qetz in a directory, counting how many <tag> carry a
|
||||
non-empty `attribute`.
|
||||
|
||||
This is the corpus question: "3190 conductors, 0 cable values across
|
||||
25 projects" is exactly one call to this tool.
|
||||
|
||||
Each .qetz is read by QElectroTech saving it as a .qet, which can take
|
||||
minutes for a big project, so only the first `max_qetz` are read; the
|
||||
others are listed as skipped.
|
||||
"""
|
||||
d = Path(directory).expanduser()
|
||||
if not d.is_dir():
|
||||
@@ -853,8 +883,14 @@ def tool_scan(directory: str, tag: str = "conductor",
|
||||
files = sorted([f for pattern in ("*.qet", "*.qetz")
|
||||
for f in (d.rglob(pattern) if recursive else d.glob(pattern))])
|
||||
total = non_empty = 0
|
||||
per_file, values, unreadable = [], {}, []
|
||||
per_file, values, unreadable, skipped = [], {}, [], []
|
||||
qetz_read = 0
|
||||
for f in files:
|
||||
if _is_qetz(f):
|
||||
if qetz_read >= max_qetz:
|
||||
skipped.append(f.name)
|
||||
continue
|
||||
qetz_read += 1
|
||||
try:
|
||||
root = ET.parse(_as_qet(f)).getroot()
|
||||
except (ET.ParseError, ValueError) as exc:
|
||||
@@ -872,6 +908,9 @@ def tool_scan(directory: str, tag: str = "conductor",
|
||||
per_file.append({"file": f.name, tag: n, "non_empty": ne})
|
||||
return {
|
||||
"directory": str(d), "files": len(files), "unreadable": unreadable,
|
||||
**({"skipped_qetz": skipped,
|
||||
"note": f"only the first {max_qetz} .qetz were read (max_qetz)"}
|
||||
if skipped else {}),
|
||||
"tag": tag, "attribute": attribute,
|
||||
"total": total, "non_empty": non_empty,
|
||||
"distinct_values": sorted(values.items(), key=lambda kv: -kv[1])[:25],
|
||||
@@ -4902,10 +4941,12 @@ TOOLS = [
|
||||
},
|
||||
{
|
||||
"name": "qet_scan",
|
||||
"description": "Sweep every .qet in a directory and count how many nodes of "
|
||||
"a given tag carry a non-empty attribute, with the distinct "
|
||||
"values found. For corpus questions such as how many "
|
||||
"conductors in the shipped examples have a cable value.",
|
||||
"description": "Sweep every .qet and .qetz in a directory and count how many "
|
||||
"nodes of a given tag carry a non-empty attribute, with the "
|
||||
"distinct values found. For corpus questions such as how many "
|
||||
"conductors in the shipped examples have a cable value. Each "
|
||||
".qetz is read through QElectroTech, which can take minutes "
|
||||
"for a big project: only the first max_qetz are read.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -4913,12 +4954,14 @@ TOOLS = [
|
||||
"tag": {"type": "string", "default": "conductor"},
|
||||
"attribute": {"type": "string", "default": "cable"},
|
||||
"recursive": {"type": "boolean", "default": True},
|
||||
"max_qetz": {"type": "integer", "default": 20},
|
||||
},
|
||||
"required": ["directory"],
|
||||
},
|
||||
"handler": lambda a: tool_scan(a["directory"], a.get("tag", "conductor"),
|
||||
a.get("attribute", "cable"),
|
||||
a.get("recursive", True)),
|
||||
a.get("recursive", True),
|
||||
a.get("max_qetz", 20)),
|
||||
},
|
||||
{
|
||||
"name": "qet_element_info",
|
||||
|
||||
@@ -6489,6 +6489,66 @@ class ZippedProjectWithoutBinary(unittest.TestCase):
|
||||
m.tool_project_info(str(z))
|
||||
self.assertIn("QET_BINARY", str(caught.exception))
|
||||
|
||||
QET = '<project><diagram><conductors><conductor cable="W1"/></conductors></diagram></project>'
|
||||
|
||||
def fake_resave(self, ok=True, timed_out=False):
|
||||
"""A _run_qet that writes a small .qet where --resave was asked to"""
|
||||
def run(binary, args, timeout=180, **kw):
|
||||
Path(args[2]).write_text(self.QET)
|
||||
if timed_out:
|
||||
return {"ok": False, "timed_out": True, "timeout_s": timeout}
|
||||
return {"ok": ok, "exit_code": 0 if ok else 1, "stderr": "", "stdout": ""}
|
||||
return run
|
||||
|
||||
def qetz_files(self, tmp, n):
|
||||
files = []
|
||||
for i in range(n):
|
||||
z = Path(tmp) / f"p{i}.qetz"
|
||||
z.write_bytes(b"PK")
|
||||
files.append(z)
|
||||
return files
|
||||
|
||||
def test_a_failed_or_timed_out_conversion_is_not_kept(self):
|
||||
"""A .qet left behind by a run that did not finish is not read"""
|
||||
for kind in ({"ok": False}, {"timed_out": True}):
|
||||
with self.subTest(**kind), tempfile.TemporaryDirectory() as tmp:
|
||||
m._forget_qetz_copies()
|
||||
z, = self.qetz_files(tmp, 1)
|
||||
with mock.patch.object(m, "resolve_binary", return_value="qet"), \
|
||||
mock.patch.object(m, "_run_qet", side_effect=self.fake_resave(**kind)):
|
||||
with self.assertRaises(ValueError):
|
||||
m._as_qet(z)
|
||||
self.assertEqual(m._QETZ_COPIES, {})
|
||||
|
||||
def test_only_the_newest_conversions_are_kept_on_disk(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
m._forget_qetz_copies()
|
||||
files = self.qetz_files(tmp, m._QETZ_COPIES_MAX + 2)
|
||||
with mock.patch.object(m, "resolve_binary", return_value="qet"), \
|
||||
mock.patch.object(m, "_run_qet", side_effect=self.fake_resave()):
|
||||
copies = [m._as_qet(z) for z in files]
|
||||
self.assertEqual(len(m._QETZ_COPIES), m._QETZ_COPIES_MAX)
|
||||
self.assertFalse(copies[0].parent.exists())
|
||||
self.assertTrue(copies[-1].is_file())
|
||||
# A new version of a file replaces its old copy
|
||||
os.utime(files[-1], ns=(1, 1))
|
||||
newer = m._as_qet(files[-1])
|
||||
self.assertFalse(copies[-1].parent.exists())
|
||||
m._forget_qetz_copies()
|
||||
self.assertFalse(newer.parent.exists())
|
||||
|
||||
def test_scan_reads_at_most_max_qetz(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
m._forget_qetz_copies()
|
||||
self.qetz_files(tmp, 3)
|
||||
with mock.patch.object(m, "resolve_binary", return_value="qet"), \
|
||||
mock.patch.object(m, "_run_qet", side_effect=self.fake_resave()) as run:
|
||||
scan = m.tool_scan(tmp, max_qetz=2)
|
||||
m._forget_qetz_copies()
|
||||
self.assertEqual(run.call_count, 2)
|
||||
self.assertEqual((scan["total"], scan["non_empty"]), (2, 2))
|
||||
self.assertEqual(scan["skipped_qetz"], ["p2.qetz"])
|
||||
|
||||
|
||||
@needs_examples
|
||||
class ZippedProjects(unittest.TestCase):
|
||||
@@ -6561,8 +6621,9 @@ class ZippedProjects(unittest.TestCase):
|
||||
self.assertTrue(r.get("ok"), r)
|
||||
self.assertEqual(out.read_bytes()[:2], b"PK")
|
||||
# The rotation is what the diff of the two .qetz finds
|
||||
diff = json.dumps(m.tool_diff(str(self.qetz), str(out)))
|
||||
self.assertIn(m._uuid_key(first["uuid"]) if hasattr(m, "_uuid_key") else first["uuid"], diff.lower())
|
||||
rotated = m.tool_diff(str(self.qetz), str(out))["elements"]["rotated"]
|
||||
self.assertEqual([r["uuid"] for r in rotated], [first["uuid"]])
|
||||
self.assertNotEqual(*rotated[0]["orientation"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -69,6 +69,10 @@ set reg_file=qet_install_file_associations.reg
|
||||
echo [HKEY_CURRENT_USER\Software\Classes\qet_diagram_file\shell\open\command]
|
||||
echo @="\"%final_qet_exe%\" \"%%1\""
|
||||
|
||||
rem *.qetz (zipped project): the same file type as *.qet
|
||||
echo [HKEY_CURRENT_USER\Software\Classes\.qetz]
|
||||
echo @="qet_diagram_file"
|
||||
|
||||
rem association de fichier *.elmt
|
||||
echo [HKEY_CURRENT_USER\Software\Classes\.elmt]
|
||||
echo @="qet_element_file"
|
||||
|
||||
@@ -1034,6 +1034,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")));
|
||||
bool found = false;
|
||||
for (auto terminal_elmt : std::as_const(free_terminals))
|
||||
{
|
||||
if (terminal_elmt->uuid() == uuid_)
|
||||
@@ -1042,9 +1043,15 @@ bool TerminalStrip::fromXml(QDomElement &xml_element)
|
||||
//Remove the actual terminal element from the vector, they dicrease the size
|
||||
//of the vector and so each iteration have less terminal element to check
|
||||
free_terminals.removeOne(terminal_elmt);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
qWarning() << "TerminalStrip::fromXml: strip" << name()
|
||||
<< "lists terminal" << uuid_.toString()
|
||||
<< "but no free terminal symbol has this uuid; it is left out";
|
||||
}
|
||||
}
|
||||
|
||||
if (!real_t_vector.isEmpty()) {
|
||||
|
||||
@@ -46,7 +46,7 @@ The code is `qetcontainer.cpp` (the zip and its files), `qetcontainerdb.cpp`
|
||||
|
||||
| Entry | Content |
|
||||
|---|---|
|
||||
| `mimetype` | `application/x-qelectrotech-project+zip`, the first entry, stored uncompressed |
|
||||
| `mimetype` | `application/x-qet-project+zip`, the first entry, stored uncompressed (files written before October 2026 say `application/x-qelectrotech-project+zip`; both are read) |
|
||||
| `manifest.xml` | `<qetz format="4" min-reader="4" writer="QElectroTech …"/>` |
|
||||
| `project.xml` | the project document, each part replaced by `<qetz-part file="…"/>` |
|
||||
| `project.sqlite` | the engineering data (section 4) |
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
#include <QRegularExpression>
|
||||
#include <QUuid>
|
||||
|
||||
const char QetContainer::MimeType[] = "application/x-qelectrotech-project+zip";
|
||||
const char QetContainer::MimeType[] = "application/x-qet-project+zip";
|
||||
const char QetContainer::OldMimeType[] = "application/x-qelectrotech-project+zip";
|
||||
|
||||
namespace {
|
||||
const QString PartTag = QStringLiteral("qetz-part");
|
||||
@@ -426,7 +427,8 @@ bool QetContainer::join(const QList<QetZip::Entry> &entries, QDomDocument *proje
|
||||
for (const QetZip::Entry &entry : entries)
|
||||
files.insert(entry.name, entry.data);
|
||||
|
||||
if (files.value(QStringLiteral("mimetype")) != QByteArray(MimeType)) {
|
||||
const QByteArray mimetype = files.value(QStringLiteral("mimetype"));
|
||||
if (mimetype != QByteArray(MimeType) && mimetype != QByteArray(OldMimeType)) {
|
||||
setError(error, QStringLiteral("not a QElectroTech project (.qetz)"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
namespace QetContainer
|
||||
{
|
||||
extern const char MimeType[];
|
||||
/// The MIME type .qetz files had before it was renamed; still read
|
||||
extern const char OldMimeType[];
|
||||
/// Picture bytes by file name, see join()
|
||||
using Pictures = QHash<QString, QByteArray>;
|
||||
/// The format written; files of a later format are refused by join()
|
||||
|
||||
@@ -1828,7 +1828,7 @@ bool QETDiagramEditor::openProject()
|
||||
this,
|
||||
tr("Open a file"),
|
||||
open_dialog_dir.absolutePath(),
|
||||
tr("QElectroTech project(*.qet *.qetz);;XML Files (*.xml);;All files (*)")
|
||||
QETProject::openFileFilter() + ";;" + tr("XML Files (*.xml);;All files (*)")
|
||||
);
|
||||
if (filepath.isEmpty()) return(false);
|
||||
|
||||
|
||||
+22
-3
@@ -2654,6 +2654,15 @@ bool QETProject::isContainerPath(const QString &path)
|
||||
return path.endsWith(QLatin1String(".qetz"), Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETProject::openFileFilter
|
||||
@return the file-dialog filter for a project to open: .qet and .qetz
|
||||
*/
|
||||
QString QETProject::openFileFilter()
|
||||
{
|
||||
return tr("QElectroTech project (*.qet *.qetz)");
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETProject::writeContainer
|
||||
Write the project document @p project to @p path as a .qetz: the same
|
||||
@@ -2721,10 +2730,20 @@ QETResult QETProject::write()
|
||||
return(QString("unable to save project to file: no filepath was specified"));
|
||||
|
||||
//A project read from a .qet is saved as a .qetz beside it; the
|
||||
//.qet stays as it was (stage D1)
|
||||
//.qet stays as it was (stage D1). If the .qetz cannot be written
|
||||
//(a read-only folder), the project stays the .qet it was.
|
||||
const QString opened_qet = m_opened_from_qet ? m_file_path : QString();
|
||||
if (m_opened_from_qet) {
|
||||
setFilePath(besideContainerPath());
|
||||
}
|
||||
const auto failed = [this, &opened_qet](const QString &error) {
|
||||
const QString message = tr("Cannot save %1: %2").arg(m_file_path, error);
|
||||
if (!opened_qet.isEmpty()) {
|
||||
setFilePath(opened_qet);
|
||||
m_opened_from_qet = true;
|
||||
}
|
||||
return(QETResult(message));
|
||||
};
|
||||
|
||||
// If the project was opened read-only, only refuse when the target
|
||||
// really can't be written: an existing file that is not writable, or a
|
||||
@@ -2746,7 +2765,7 @@ QETResult QETProject::write()
|
||||
//A container is written from the whole document: every folio
|
||||
//built (stage D4 writes it from the database)
|
||||
if (!writeContainerFile(m_file_path, &error_message))
|
||||
return(error_message);
|
||||
return(failed(error_message));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2756,7 +2775,7 @@ QETResult QETProject::write()
|
||||
QDomDocument xml_project(toXml(&kept_folios));
|
||||
if (!QET::writeXmlText(spliceKeptFolios(xml_project.toString(4), kept_folios),
|
||||
m_file_path, &error_message))
|
||||
return(error_message);
|
||||
return(failed(error_message));
|
||||
}
|
||||
|
||||
// The project has just been written to a writable file (e.g. saved to
|
||||
|
||||
@@ -455,6 +455,7 @@ class QETProject : public QObject
|
||||
ProjectState openFile(QFile *file);
|
||||
public:
|
||||
static bool isContainerPath(const QString &path);
|
||||
static QString openFileFilter();
|
||||
/// Saving writes a .qetz: one read from a .qet is saved as
|
||||
/// a .qetz beside it, the .qet left as it is
|
||||
/// (stage D1)
|
||||
|
||||
@@ -1166,7 +1166,7 @@ void ProjectAutoNumConfigPage::importFromProject()
|
||||
this,
|
||||
tr("Import numbering from a project"),
|
||||
m_project->currentDir(),
|
||||
tr("Project QElectroTech (*.qet *.qetz)"));
|
||||
QETProject::openFileFilter());
|
||||
if (path.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class tst_qetzip : public QObject
|
||||
static QList<QetZip::Entry> sample()
|
||||
{
|
||||
QList<QetZip::Entry> entries;
|
||||
entries.append({QStringLiteral("mimetype"), QByteArray("application/x-qelectrotech-project+zip"), false});
|
||||
entries.append({QStringLiteral("mimetype"), QByteArray("application/x-qet-project+zip"), false});
|
||||
QByteArray xml("<project>");
|
||||
for (int i = 0 ; i < 2000 ; ++i) xml += "<element name=\"K1\"/>";
|
||||
xml += "</project>";
|
||||
@@ -65,7 +65,7 @@ private slots:
|
||||
QCOMPARE(out.at(i).compress, in.at(i).compress);
|
||||
}
|
||||
// mimetype first and stored: its bytes appear as is after the header
|
||||
QCOMPARE(zip.indexOf("application/x-qelectrotech-project+zip"), 30 + int(qstrlen("mimetype")));
|
||||
QCOMPARE(zip.indexOf("application/x-qet-project+zip"), 30 + int(qstrlen("mimetype")));
|
||||
}
|
||||
|
||||
void sameBytesTwice()
|
||||
|
||||
Reference in New Issue
Block a user