Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 77e2a9bfc5 | |||
| a78e95be2c | |||
| 2651114866 | |||
| 4b4b9a2ad0 | |||
| 9dc03005da | |||
| fd34a10897 | |||
| b8dd03923a | |||
| 12a695a503 | |||
| cce75f4306 | |||
| 1f70ddb666 | |||
| 9f5406bd8c | |||
| 6f4171c926 | |||
| e335aa2e55 | |||
| ee0593ce7d | |||
| 78ac5222c0 | |||
| 1cd8fadbc4 | |||
| e52b89cb84 | |||
| a44e816d59 | |||
| 739061b0f0 | |||
| cbaf40000c | |||
| fac09253a0 | |||
| a475383915 | |||
| d7ac633043 | |||
| 124a889be3 | |||
| 7f6753696b | |||
| ec5246f6db | |||
| 446479e8d2 | |||
| 8261524208 | |||
| c52cd3b2fc | |||
| 448a82fae6 | |||
| eab557d9af | |||
| 3dd977a040 | |||
| d996426e8a | |||
| 05131d1238 | |||
| d86f2d5461 |
@@ -102,6 +102,13 @@ else()
|
||||
message(STATUS "Qt Qml module not available: JavaScript scripting (--run) disabled")
|
||||
endif()
|
||||
|
||||
# The zip container of a .qetz project is read and written with the bundled
|
||||
# miniz (sources/container/miniz): in memory only, and without timestamps so
|
||||
# that saving the same project twice gives the same bytes. The names must not
|
||||
# clash with the zlib Qt links. Every target including miniz.h needs the same
|
||||
# three, hence directory-wide.
|
||||
add_compile_definitions(MINIZ_NO_TIME MINIZ_NO_STDIO MINIZ_NO_ZLIB_COMPATIBLE_NAMES)
|
||||
|
||||
# The crash handler writes a backtrace into the dump. Detecting this with
|
||||
# __has_include(<execinfo.h>) is not enough: the header is present on FreeBSD
|
||||
# too, but backtrace() lives in a separate libexecinfo there, so the compile
|
||||
@@ -146,6 +153,25 @@ endif()
|
||||
# als laatse
|
||||
include(cmake/define_definitions.cmake)
|
||||
|
||||
# The application's code, compiled once, as an object library: the
|
||||
# executable is main.cpp linked with it, and tests that need the real
|
||||
# classes (a folio, its view, the editor window) link it too.
|
||||
set(QET_MAIN_FILE ${QET_DIR}/sources/main.cpp)
|
||||
list(REMOVE_ITEM QET_SRC_FILES ${QET_MAIN_FILE})
|
||||
add_library(
|
||||
${PROJECT_NAME}_objects
|
||||
OBJECT
|
||||
${QET_RES_FILES}
|
||||
${QET_SRC_FILES}
|
||||
${QET_DIR}/qelectrotech.qrc
|
||||
${QET_DIR}/ico/icon-themes.qrc
|
||||
)
|
||||
# The definitions above are set for this whole directory; a test target
|
||||
# lives in tests/ and gets them from here, through the library.
|
||||
get_directory_property(QET_DIRECTORY_DEFINITIONS COMPILE_DEFINITIONS)
|
||||
target_compile_definitions(${PROJECT_NAME}_objects
|
||||
INTERFACE ${QET_DIRECTORY_DEFINITIONS})
|
||||
|
||||
# On Windows, WIN32 sets /SUBSYSTEM:WINDOWS to suppress the console window.
|
||||
# Qt automatically links qtmain.lib which provides the WinMain entry point,
|
||||
# so no source code change is needed.
|
||||
@@ -153,11 +179,8 @@ if(WIN32)
|
||||
add_executable(
|
||||
${PROJECT_NAME}
|
||||
WIN32
|
||||
${QET_RES_FILES}
|
||||
${QET_SRC_FILES}
|
||||
${QET_MAIN_FILE}
|
||||
${QM_FILES}
|
||||
${QET_DIR}/qelectrotech.qrc
|
||||
${QET_DIR}/ico/icon-themes.qrc
|
||||
)
|
||||
# Our own manifest (it declares longPathAware). MSVC merges a .manifest
|
||||
# listed as a source; MinGW needs it embedded through a resource file,
|
||||
@@ -174,13 +197,11 @@ if(WIN32)
|
||||
else()
|
||||
add_executable(
|
||||
${PROJECT_NAME}
|
||||
${QET_RES_FILES}
|
||||
${QET_SRC_FILES}
|
||||
${QET_MAIN_FILE}
|
||||
${QM_FILES}
|
||||
${QET_DIR}/qelectrotech.qrc
|
||||
${QET_DIR}/ico/icon-themes.qrc
|
||||
)
|
||||
endif()
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_NAME}_objects)
|
||||
|
||||
if(QET_SPACEMOUSE_BACKEND_SPNAV_ENABLED)
|
||||
list(APPEND QET_SPACEMOUSE_LIBRARIES PkgConfig::SPNAV)
|
||||
@@ -249,7 +270,7 @@ elseif(Qt6_VERSION VERSION_LESS "6.7")
|
||||
|
||||
# Automatically creates the update_translations umbrella target.
|
||||
qt_add_lupdate(
|
||||
${PROJECT_NAME}
|
||||
${PROJECT_NAME}_objects
|
||||
TS_FILES ${TS_FILES}
|
||||
)
|
||||
|
||||
@@ -262,7 +283,7 @@ else()
|
||||
)
|
||||
|
||||
qt_add_lupdate(
|
||||
SOURCE_TARGETS ${PROJECT_NAME}
|
||||
SOURCE_TARGETS ${PROJECT_NAME}_objects
|
||||
TS_FILES ${TS_FILES}
|
||||
LUPDATE_TARGET update_translations
|
||||
NO_GLOBAL_TARGET
|
||||
@@ -288,7 +309,7 @@ if(QET_ENABLE_PCH)
|
||||
"QET_ENABLE_PCH needs CMake 3.16 or newer (found ${CMAKE_VERSION}); "
|
||||
"building without precompiled headers.")
|
||||
else()
|
||||
target_precompile_headers(${PROJECT_NAME} PRIVATE
|
||||
target_precompile_headers(${PROJECT_NAME}_objects PRIVATE
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<QtCore/QtCore$<ANGLE-R>>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<QtGui/QtGui$<ANGLE-R>>"
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:<QtWidgets/QtWidgets$<ANGLE-R>>"
|
||||
@@ -299,9 +320,8 @@ if(QET_ENABLE_PCH)
|
||||
endif()
|
||||
|
||||
target_link_libraries(
|
||||
${PROJECT_NAME}
|
||||
${PROJECT_NAME}_objects
|
||||
PUBLIC
|
||||
PRIVATE
|
||||
pugixml::pugixml
|
||||
SingleApplication::SingleApplication
|
||||
SQLite3::SQLite3
|
||||
@@ -311,8 +331,8 @@ target_link_libraries(
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
${PROJECT_NAME}
|
||||
PRIVATE
|
||||
${PROJECT_NAME}_objects
|
||||
PUBLIC
|
||||
${QET_DIR}/sources/titleblock
|
||||
${QET_DIR}/sources/ui
|
||||
${QET_DIR}/sources/qetgraphicsitem
|
||||
@@ -345,12 +365,13 @@ target_include_directories(
|
||||
${QET_DIR}/sources/print
|
||||
${QET_DIR}/sources/svg
|
||||
${QET_DIR}/sources/materiallist
|
||||
${QET_DIR}/sources/cablelist
|
||||
)
|
||||
|
||||
if(NOT BUILD_WITH_KF)
|
||||
target_include_directories(
|
||||
${PROJECT_NAME}
|
||||
PRIVATE
|
||||
${PROJECT_NAME}_objects
|
||||
PUBLIC
|
||||
${QET_DIR}/sources/ui/nokde
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
# QElectroTech — Release notes: 0.100 → current master (0.200.1-dev)
|
||||
|
||||
*Covers the period 26 January 2026 – 9 October 2026: 2,253 commits and 669 merged pull requests. Numbers in brackets are GitHub PRs (#nnn) or bug tracker entries (bugtracker #nnn).*
|
||||
|
||||
---
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Qt 6 is now the platform.** The Qt 6 migration is complete, Qt 5 support has been removed, and Windows, macOS, Flatpak and Snap packages are built on Qt 6 / KF6.
|
||||
- **Rewritten auto-numbering.** Renumbering of existing elements, new numbering parts (alphabetic, cyclic/modulo, zero-padded formats), undo/redo for counters, global rules in the settings, and importing rules from another project.
|
||||
- **Many more drawing and editing tools.** Editable resize/rotate/skew handles for shapes and pictures, an Arc tool and a fillet tool, Group/Ungroup, Align and Snap to grid, mirroring symbols, rotating groups, text-box width handles, and dropping pictures onto a sheet.
|
||||
- **Productivity UI.** Command search, Ctrl+G jump to element or cell, an element picker and shortcut bar at the cursor, a context command bar, mouse gestures, repeat-last-command, configurable shortcuts, toolbars and gestures (Customise window), and paste under the cursor.
|
||||
- **Wiring, BOM and PLC.** A from-to wiring list export, a PLC Manager, slave contact groups and a full contact comb, terminal potential grouping, a material list lookup, smart-device BOM metadata, a generic device wizard, and drag and drop onto terminal strips.
|
||||
- **Import and export.** EPLAN Data Portal (.edz) import, PDF page import, and much better DXF export (layers, symbols as blocks with attributes). PDF export gains clickable cross-reference links and can be made reproducible.
|
||||
- **Command line, scripting and AI assistants.** Headless export and verification from the command line, a JavaScript scripting API (off by default), script buttons and a script manager, a macro recorder, and an MCP server (`qet-mcp`) with a "live mode" so an AI assistant can work on an open project.
|
||||
- **Dark themes and icons.** All toolbar and menu icons are now SVG, there are "qet" and "qet-dark" icon themes, an optional dark folio canvas, and many dark-theme fixes.
|
||||
- **English is the source language.** The interface text is now written in English (previously French), "folio" is renamed "sheet" in the English UI, and Korean has been added as a new language.
|
||||
- **Stability.** A large number of crash, use-after-free and memory-leak fixes, rotating crash-recovery snapshots, a hardened crash reporter, diagnostic logging, and reproducible project saving.
|
||||
|
||||
---
|
||||
|
||||
## New features
|
||||
|
||||
### Auto-numbering
|
||||
- Renumber elements that are already placed [#704]; a broad overhaul of the formulas for elements, conductors and sheets [#1282]
|
||||
- Alphabetic numbering (a, b … z, aa, ab …) [#594]; wrap-and-carry (cyclic/modulo) numbering [#593]
|
||||
- A display format (zero mask) you can set for each numbering part [#634]
|
||||
- Quick reset buttons [#626]; edit the increment and preview the next number in the dock [#697, bugtracker #331]
|
||||
- Undo/redo now covers auto-numbering counter changes [#645]
|
||||
- Global auto-numbering rules in the application settings, applied to new projects [#924]
|
||||
- Reuse the numbering rules from another project [#851]
|
||||
|
||||
### Drawing, shapes, pictures and text
|
||||
- Editable resize, rotate and skew handles for shapes and pictures, plus picture transparency and faster image handling [#809, #810, #817, #1024]
|
||||
- Arc tool with a handle to pull a half arc in or out [#1202, #1211]; fillet tool to round the corner between two lines [#1212]
|
||||
- Group and Ungroup items on a sheet [#1074]; click a member of a selected group to select it alone [#1110]; rotate a group as one piece [#660, #1111]
|
||||
- Align left/centre/right/top/middle/bottom, with icons [#1087, #1153]; Snap to grid for selected symbols, pictures, texts and shapes [#1073, #1152, #1154]
|
||||
- Mirror symbols horizontally and vertically on a sheet [#1354]; an option to keep the texts of rotated symbols horizontal [#1355]
|
||||
- Free texts can keep a width they wrap to, set in their properties or by dragging a corner handle [#1320, #1321, #1341, #1342]
|
||||
- Drag-to-resize the width of dynamic element texts [#591, #977]; a centre rotation point for dynamic text fields [#694]
|
||||
- Labels on pictures [#1068]; drop picture files onto a sheet to add them [#1416]; the crop, colours and original of a picture undo together [#1310]
|
||||
- Import a PDF page as an image [#772, #811]
|
||||
- Remember the last-used shape and text style for the session [#656]
|
||||
- Custom guides [#509]; a finer snap grid for dragged texts [#1040]
|
||||
- View > Show to hide texts, shapes and pictures [#1268, bugtracker #301]
|
||||
- Sheet background colour picker with adaptive border and title block, remembered between runs [#968, #1011]; a custom application colour picker [#979]
|
||||
- A permanent ID for shapes, texts, pictures and symbol parts [#1065]; lasting UUIDs for projects, sheets, symbols, wires and terminals, including those in older files [#883, #884, #1105, #1107, #1118, #1122]
|
||||
|
||||
### Element (symbol) editor
|
||||
- Live cursor-coordinate readout [#589, #761]
|
||||
- Export a symbol to SVG [#637]
|
||||
- Optional alignment for static texts [#569]; `anchor="alignment"` for static texts in element files [#1404]
|
||||
- Show terminal names [#536]; check terminal names when saving [#1159]
|
||||
- Scale element, keeping terminals on the grid [#1378]
|
||||
- An adjustable background frame [#1164]
|
||||
- Mirror and flip the selection in place instead of across the origin [#813]
|
||||
- The Information tab is available for Slave and Terminal types, and auto_num_locked, potential_isolating and exclude_from_bom can be edited [#721, #765, #767, #1399]
|
||||
- New element type: line definition [#464]
|
||||
|
||||
### Sheets and navigation
|
||||
- Duplicate sheets with all their metadata [#473]; undo/redo for adding, deleting and reordering sheets [#590]; insert a sheet above or below [#658]
|
||||
- Paper sizes A0–A5 [#1362]
|
||||
- Keep the row and column headers visible, and optionally show the cell limits across the drawing [#1038, #1048]
|
||||
- Ctrl+G jumps to an element or to a sheet cell such as B13 [#586, #1036]
|
||||
- Centre on cursor [#1190]; the search view scrolls to the selected hit [#706]
|
||||
- Auto-select the active sheet in the elements panel tree [#443]; a newly added sheet is selected in the project tree [#805]
|
||||
- The main window title shows unsaved changes (macOS modified dot) [#624]
|
||||
- 3D mouse (SpaceMouse/SpacePilot) support on Linux, Windows and macOS, with speed, direction and twist-to-zoom settings [#635, #1022, #1023, #1025, #1026, #1028, #1032]
|
||||
|
||||
### Productivity and customisation
|
||||
- Command search: type part of a command's name and press Enter [#1049]
|
||||
- Element picker at the cursor (Insert) [#1052]; shortcut bar at the cursor (S), customisable in place and with pinned elements [#1053, #1054, #1060]
|
||||
- A command bar beside the selection [#1055]; repeat the last drawing or placing command with Enter [#1056]; right-drag mouse gestures [#1057, #1364]
|
||||
- Place elements by double-clicking in the collection, and repeat with A [#1042]; placement and sheet references in the right-click menu [#1041, #1050]
|
||||
- Configurable shortcuts with a dedicated preferences page (grouped, searchable, icons, menu locations) [#587, #760, #821, #1325, #1326]
|
||||
- Toolbars settings page (icon size, text with icons, lock) and a combined Customise window [#1327, #1365]
|
||||
- Paste under the cursor; Ctrl+Shift+V pastes at the origin [#878, #917, #1208]; Ctrl+D duplicates with a configurable offset [#993]
|
||||
- Tab cycles through the selection; select all conductors or text fields [#874]
|
||||
- Keyboard accessibility: F10 opens the menu bar, the drawing tools are in a menu, and the context-menu key opens the sheet menu [#875, #876, #877]
|
||||
- Save and load the configuration, in full or in part, to a file [#1166, #1410]
|
||||
- Dialog sizes are remembered [#1165]; QColorDialog custom colours are kept across restarts [#918, #1370]
|
||||
- Edit several wires at once in the Selection properties panel [#502, #1205]
|
||||
- One-click conductor colour on the toolbar; F2 remembers the colour for the session; double-click to choose a wire colour [#888, #929, #1172]
|
||||
- Paste element information from one symbol onto others [#1376]
|
||||
- Element information suggests values already used in the project [#1021, bugtracker #217]
|
||||
- A position-lock checkbox for elements [#803]
|
||||
- The Edit menu is split into submenus [#1346]
|
||||
- Settings for the folder prefixes of the user collection [#1168]
|
||||
- A local tracker for the time spent on each project [#583]
|
||||
|
||||
### Conductors and wiring
|
||||
- Auto-break conductors [#639, #700]
|
||||
- Optional hops where wires cross [#1200]
|
||||
- An optional limit on the number of wires per terminal, wiring terminals in a chain rather than a star [#1272, #1273]
|
||||
- Wire routing around symbols (scripting/MCP) [#1245, #1256, #1258]
|
||||
- Linked cross-references inherit the conductor line style [#967]
|
||||
|
||||
### Wiring list, BOM and reports
|
||||
- A from-to wiring list built on new terminal/conductor tables in the project database, with a dialog, export, Cable column, terminal index/UUID and numeric wire order [#447, #462, #628, #629, #630, #835, #1197, #1248]
|
||||
- Exclude specific elements from the BOM [#472]; options to leave junctions and contact blocks out of the parts list [#1318]
|
||||
- Smart device metadata in the BOM export [#830]
|
||||
- A material list (CSV) lookup for the element article fields [#1088, #1119]
|
||||
- Custom SQL reports with read-only enforcement, preview and import/export [#896, #983]
|
||||
|
||||
### Master/slave, PLC and terminals
|
||||
- PLC Manager [#562, #588, #623, #669, #703, #774, #1001]; PLC terminal names and cross-references [#766, #796]; "Hide linked elements" and "Hide full masters" filters [#858, #881]
|
||||
- Slave contact groups (label transfer, terminal assignment) [#545]; a full contact comb that shows every slave defined by the master [#1010]
|
||||
- Contact usage counting (NO/NC/SW breakdown, used vs. declared capacity) and an advisory slave limit [#444, #825, #826, #827, #828, #831]
|
||||
- Terminal names in cross-references, with an option to hide them; terminal names shown in export [#673]
|
||||
- Configurable distance between the label and the slave cross-reference [#771]; an option for stacking cross-references at the sheet bottom [#1311]
|
||||
- Automatic terminal numbering tool, with letter numbering and strip selection [#449, #741]
|
||||
- Terminal potential grouping [#1396]; a potential isolation option for terminals [#471]
|
||||
- Drag and drop free terminals onto a terminal strip [#1199]; natural sort for terminal ordering
|
||||
- Width/height/depth properties for elements [#723]; cabinet thumbnails generated from the manufacturer and reference [#1170]
|
||||
- Generic device wizard: a box symbol with terminals on any side, for devices too rare to draw a symbol for [#1418]
|
||||
|
||||
### Templates (macros)
|
||||
- A User Templates collection with its own tab [#451, #455]
|
||||
|
||||
### Import and export
|
||||
- **EPLAN Data Portal (.edz)** parts import into element collections [#513, #555]
|
||||
- **DXF:** named layers, deterministic entity order, symbols as blocks with attributes and hidden part data, slave/master cross-references, and wrapped text [#740, #750, #1075, #1076, #1079, #1324, #1350, #1351, #1352, #1353]
|
||||
- **PDF:** clickable cross-reference links [#490], component info as invisible annotations [#739], and repeatable output when SOURCE_DATE_EPOCH is set [#1257, #1286, #1317]
|
||||
- Folio properties automatically add a title block's custom variables [#495]
|
||||
|
||||
### Command line, scripting and AI assistants
|
||||
- **Headless CLI:** export to PDF, PNG, SVG, DXF, cable list and wire list; info, BOM, nets, links, check-elements, resave; `--set-titleblock`; `--show-terminals` [#483, #485, #489, #493, #731, #1078]
|
||||
- **JavaScript scripting API:** read, export, edit, draw, cross-reference and query a project, with UUID lookups. It is **off by default** and asks before enabling [#891, #970, #980, #984, #1098, #1106, #1114, #1125, #1219, #1264]
|
||||
- Script buttons, a script manager and a macro recorder [#1220, #1221, #1227]
|
||||
- **`misc/qet-mcp`:** a Model Context Protocol server over QET projects, installed with QElectroTech (and with Python offered by the Windows installer). Help → Connect an AI assistant, and live mode (ask-first, an Assistant panel, and project create/open/print/save), plus a shared house style [#969, #1128, #1136, #1137, #1138, #1222–#1225, #1228, #1265, #1266, #1312–#1316]
|
||||
|
||||
### Diagnostics and robustness features
|
||||
- Diagnostic logging rework with rotation, a ring buffer, crash-time flush and an export UI [#646, #647]; an event-loop watchdog [#665]
|
||||
- Three rotating crash-recovery snapshots [#1167]; a hardened crash reporter that keeps every dump [#905, #1181]
|
||||
- A backup dialog when opening an existing project [#534]
|
||||
- Project load timing in the log [#560]
|
||||
- Reload element drawings to refresh placed elements [#889]
|
||||
|
||||
---
|
||||
|
||||
## Bug fixes (selection)
|
||||
|
||||
### Crashes and memory safety
|
||||
- Use-after-free and teardown crashes: backup threads [#512], restore of backups [#711, #1031], project/diagram teardown [#789], IPC file forwarding [#868], the terminal strip dock [#1162], closing without saving [#1231], and the template editor [#1407]
|
||||
- Crashes when opening a project without a sheet [#797], with an unlinked contact [#1234], or with invalid corner layout [#670]
|
||||
- Element editor zoom crash [#799]; huge symbol sizes [#1232]; NaN and infinite coordinates rejected on load [#792, #996, #1161, #1194]
|
||||
- Dynamic text colour [#693]; cancelling collection load [#743, #995]; templates tree on Qt 6 [#795]; online-installer Qt SQLite handle [#1046]
|
||||
- Memory leaks found by AddressSanitizer and Valgrind [#515, #518, #519, #1360]; uninitialised members [#1358, #1359, #1387, #1388]
|
||||
- Thread-safety fixes for data paths and the elements panel [#514, #1163]
|
||||
|
||||
### Saving, loading and file format
|
||||
- Saving is now reproducible: deterministic element, conductor, title block and XML ordering, and a second save changes nothing [#779, #814, #844, #846, #1030, #1109]
|
||||
- QDom serialization hardened against invalid data (CVE-2026-15037) [#568]
|
||||
- Font descriptions are written in a version-stable format, and salvaged fonts are reported [#582, #592]
|
||||
- The read-only state is cleared after Save As [#497]; Save As on Snap adds the .qet extension [#716]
|
||||
- Older parts lists keep their rows [#1077]; legacy sequential attributes are read correctly [#1385, #1386]
|
||||
- Wires no longer move to another symbol after saving an older project [#1417]; replacing a symbol keeps its wires [#1116]
|
||||
- Text position stays stable after a font-size change [#501]; wire text rotation is preserved [#707]
|
||||
|
||||
### Editing and the diagram editor
|
||||
- Several copy/paste fixes: broken conductors, alignment, PLC data, master/slave links and underscore labels [#746, #770, #899, #909, #910, #911, #982, #997, #1188]
|
||||
- Undo stack fixes [#925, #1039, #1192]; group rotation [#921]; the first click in the element editor no longer moves elements [#520]
|
||||
- Junction dots on wide conductors and on shared lines [#862, #1299]; straight wires drawn diagonally [#1127]; unnecessary bridge routing [#890]
|
||||
- Duplicating sheets renews UUIDs and keeps text positions [#477, #544, #856, #895]
|
||||
- Multi-line symbol text alignment [#1155, #1160, #1176, #1302, #1357]
|
||||
- Cross-reference overlap and positioning fixes [#712, #1263, #1287]
|
||||
|
||||
### Title blocks, reports and BOM
|
||||
- Title block variables: empty page-level overrides, bare %name variables, unset variables, names with a slash, and file names cut at the first dot [#572, #717, #719, #725, #989, #1121, #1140]
|
||||
- The summary table is ordered by sheet [#864]; custom summary SQL is kept [#887]; queries return all their rows [#1066] and are capped [#985]
|
||||
- Terminal and contact blocks appear in the parts list [#849]; the wire-name export no longer double-counts [#724]
|
||||
- Sheet dates are correct in the project database [#1134]; the database is rebuilt only once per load [#840]
|
||||
|
||||
### UI, theming and platform
|
||||
- Dark theme: element icons [#620, #695, #744, #1092], slave cross-references [#718], the search field, and imported image colours [#1349]
|
||||
- Locale and translation loading: regional locales, the lang/ path, double-click on Windows, and an English fallback [#496, #498, #559]
|
||||
- Windows: Unicode and long paths in collections [#524, #1243, #1244], file association without admin rights [#1149], and MS Shell Dlg fonts in PDF [#1012, #1029]
|
||||
- macOS: bundle identifier and file dialogs [#829], Fusion dark palette [#894], properties dialog placement [#715], file-open events, arm64 bundle on Sequoia, and the black screen after PDF export
|
||||
- Snap: libxcb-cursor crash on Ubuntu 24.04 [#517]
|
||||
- Recent files menu updates during the session [#863]; cancelling the potential selector [#584]; dock layout restored on Qt 6 [#769]
|
||||
|
||||
### Performance
|
||||
- Fixed the Qt 6 load-time regression in XmlElementCollection [#570]; removed the pugixml pass from ElementFactory [#567]
|
||||
- Faster saving of projects with pictures [#1067]; the element style regex is compiled once [#841]; picture cache fixes [#842]
|
||||
|
||||
---
|
||||
|
||||
## Translations
|
||||
- **New language: Korean** [#419–#431]
|
||||
- **English is now the source language** of the interface [#1390]; "folio" is renamed "sheet" in the English UI [#1216]; hundreds of untranslated or broken English strings are fixed [#1124, #1261, #1389]
|
||||
- Updated: German, Polish, French (with real plural forms), Brazilian Portuguese, Spanish (complete), Russian, Chinese (zh-CN), Czech, Turkish, Hungarian, Dutch (Belgium) and Catalan
|
||||
- Translation suggestions for 29 phrase books [#1006]; singular/plural fixes [#938, #971]
|
||||
|
||||
---
|
||||
|
||||
## Build, packaging and infrastructure
|
||||
- **Qt 6 migration completed and Qt 5 removed** [#540, #541, #546, #547, #698, #705, #709, #880]; C++20 [#1123]
|
||||
- qmake `.pro`/`.pri` files removed; CMake only [#1374]
|
||||
- Optional precompiled headers (`QET_ENABLE_PCH`) [#680]; builds without KF5/KF6 [#533, #557, #1086]; Qt < 6.5 [#1085]; QtPdf optional
|
||||
- SQLite kept for the project database; the export can be enabled via a CMake option
|
||||
- FetchContent dependencies pinned [#958]; PugiXML and GoogleTest are no longer installed [#838]; SingleApplication updated to v3.5.6
|
||||
- **CI:** Linux build and unit tests [#872]; Windows build and tests [#1361, #1425]; Windows MSI with WiX and signing (SignPath); monthly nightly builds
|
||||
- **Packaging:** Flatpak and Snap moved to Qt 6/KF6 (Flatpak runtime 6.11, Snap core24); the macOS arm64 bundle uses CMake; the NSIS installer is on 3.x with fonts and more installer languages; `publiccode.yml` added
|
||||
- New example project: Shelly single-phase and three-phase designs
|
||||
|
||||
---
|
||||
|
||||
## Contributors
|
||||
|
||||
Thanks to everyone who contributed to this release, including: ispyisail, Laurent Trinques (scorpio810), Kellermorph, Andre Rummler, Beat Hangartner, Shane Ringrose, Jeff Patterson, jp2images, plc-user, Dieter Mayer, Levi Jetzer (IBSYSLevi), Magnus Hellströmer (elevatormind), ChuckNr11, Gerhard Schwanzer, saschbe, enesgursoy6110, philippeagray, Kyle-Code-CA and jkh (정광호), zi-mozhuang, jindongjie, neitri, Alfmat01, cezlom, Vinícius Santos, zakb120, pafri, geri1701, achim and joshua, and to the translators Gábor (hu), Ronny (nl-BE), Pawel (pl) and Antoni (ca).
|
||||
@@ -165,6 +165,14 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/import/edz/lzma/LzmaDec.c
|
||||
${QET_DIR}/sources/import/edz/lzma/Ppmd7.c
|
||||
${QET_DIR}/sources/import/edz/lzma/Ppmd7Dec.c
|
||||
${QET_DIR}/sources/container/miniz/miniz.c
|
||||
${QET_DIR}/sources/container/miniz/miniz.h
|
||||
${QET_DIR}/sources/container/qetcontainer.cpp
|
||||
${QET_DIR}/sources/container/qetcontainerdb.cpp
|
||||
${QET_DIR}/sources/container/qetcontainerdb.h
|
||||
${QET_DIR}/sources/container/qetcontainer.h
|
||||
${QET_DIR}/sources/container/qetzip.cpp
|
||||
${QET_DIR}/sources/container/qetzip.h
|
||||
${QET_DIR}/sources/borderproperties.cpp
|
||||
${QET_DIR}/sources/borderproperties.h
|
||||
${QET_DIR}/sources/bordertitleblock.cpp
|
||||
@@ -387,6 +395,8 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/diagramevent/diagrameventfillet.h
|
||||
${QET_DIR}/sources/diagramevent/diagrameventaddpath.cpp
|
||||
${QET_DIR}/sources/diagramevent/diagrameventaddpath.h
|
||||
${QET_DIR}/sources/diagramevent/diagrameventaddcable.cpp
|
||||
${QET_DIR}/sources/diagramevent/diagrameventaddcable.h
|
||||
${QET_DIR}/sources/diagramevent/diagrameventaddtext.cpp
|
||||
${QET_DIR}/sources/diagramevent/diagrameventaddtext.h
|
||||
${QET_DIR}/sources/diagramevent/diagrameventinterface.cpp
|
||||
@@ -581,6 +591,7 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/qetgraphicsitem/crossrefitem.h
|
||||
${QET_DIR}/sources/qetgraphicsitem/diagramimageitem.cpp
|
||||
${QET_DIR}/sources/qetgraphicsitem/diagramimageitem.h
|
||||
${QET_DIR}/sources/qetgraphicsitem/cropgeometry.h
|
||||
${QET_DIR}/sources/qetgraphicsitem/diagramtextitem.cpp
|
||||
${QET_DIR}/sources/qetgraphicsitem/diagramtextitem.h
|
||||
${QET_DIR}/sources/qetgraphicsitem/dynamicelementtextitem.cpp
|
||||
@@ -811,6 +822,8 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/ui/elementinfowidget.h
|
||||
${QET_DIR}/sources/ui/terminalnumberingdialog.cpp
|
||||
${QET_DIR}/sources/ui/terminalnumberingdialog.h
|
||||
${QET_DIR}/sources/ui/cablenumberingdialog.cpp
|
||||
${QET_DIR}/sources/ui/cablenumberingdialog.h
|
||||
${QET_DIR}/sources/ui/elementpropertieswidget.cpp
|
||||
${QET_DIR}/sources/ui/elementpropertieswidget.h
|
||||
${QET_DIR}/sources/ui/formulaassistantdialog.cpp
|
||||
@@ -936,6 +949,44 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/materiallist/materialselectiondialog.cpp
|
||||
${QET_DIR}/sources/materiallist/materialselectiondialog.h
|
||||
|
||||
${QET_DIR}/sources/cablelist/cabletypelist.cpp
|
||||
${QET_DIR}/sources/cablelist/cabletypelist.h
|
||||
${QET_DIR}/sources/cablelist/cableexportdialog.cpp
|
||||
${QET_DIR}/sources/cablelist/cableexportdialog.h
|
||||
${QET_DIR}/sources/cablelist/cablelistmodel.cpp
|
||||
${QET_DIR}/sources/cablelist/cablelistmodel.h
|
||||
${QET_DIR}/sources/cablelist/cablelistmodelpropertieswidget.cpp
|
||||
${QET_DIR}/sources/cablelist/cablelistmodelpropertieswidget.h
|
||||
${QET_DIR}/sources/cablelist/cablelistrows.cpp
|
||||
${QET_DIR}/sources/cablelist/cablelistrows.h
|
||||
${QET_DIR}/sources/cablelist/cablequerywidget.cpp
|
||||
${QET_DIR}/sources/cablelist/cablequerywidget.h
|
||||
|
||||
${QET_DIR}/sources/cable/addcablecommand.cpp
|
||||
${QET_DIR}/sources/cable/addcablecommand.h
|
||||
${QET_DIR}/sources/cable/cable.cpp
|
||||
${QET_DIR}/sources/cable/cable.h
|
||||
${QET_DIR}/sources/cable/cablecopy.cpp
|
||||
${QET_DIR}/sources/cable/cablecopy.h
|
||||
${QET_DIR}/sources/cable/cablecreatedialog.cpp
|
||||
${QET_DIR}/sources/cable/cablecreatedialog.h
|
||||
${QET_DIR}/sources/cable/cablemanager.cpp
|
||||
${QET_DIR}/sources/cable/cablemanager.h
|
||||
${QET_DIR}/sources/cable/cablepart.cpp
|
||||
${QET_DIR}/sources/cable/cablepart.h
|
||||
${QET_DIR}/sources/cable/cablepropertiesdialog.cpp
|
||||
${QET_DIR}/sources/cable/cablepropertiesdialog.h
|
||||
${QET_DIR}/sources/cable/cablepropertieseditorwidget.cpp
|
||||
${QET_DIR}/sources/cable/cablepropertieseditorwidget.h
|
||||
${QET_DIR}/sources/cable/cablepropertieswidget.cpp
|
||||
${QET_DIR}/sources/cable/cablepropertieswidget.h
|
||||
${QET_DIR}/sources/cable/cablerenumber.cpp
|
||||
${QET_DIR}/sources/cable/cablerenumber.h
|
||||
${QET_DIR}/sources/cable/cabletextformatdialog.cpp
|
||||
${QET_DIR}/sources/cable/cabletextformatdialog.h
|
||||
${QET_DIR}/sources/cable/editcablecommand.cpp
|
||||
${QET_DIR}/sources/cable/editcablecommand.h
|
||||
|
||||
${QET_DIR}/sources/xml/terminalstripitemxml.cpp
|
||||
${QET_DIR}/sources/xml/terminalstripitemxml.h
|
||||
${QET_DIR}/sources/xml/terminalstriplayoutpatternxml.cpp
|
||||
|
||||
@@ -257,6 +257,21 @@
|
||||
<file alias="themes/qet/24x24/bring_forward.svg">themes/generated/traced-24/bring_forward.svg</file>
|
||||
<file alias="themes/qet/24x24@2/bring_forward.svg">themes/generated/traced-large-24/bring_forward.svg</file>
|
||||
<file alias="themes/qet/scalable/bring_forward.svg">qet/traced/large22/bring_forward.svg</file>
|
||||
<file alias="themes/qet/16x16/cable.svg">qet/traced/16/cable.svg</file>
|
||||
<file alias="themes/qet/16x16@2/cable.svg">qet/traced/large16/cable.svg</file>
|
||||
<file alias="themes/qet/22x22/cable.svg">qet/traced/22/cable.svg</file>
|
||||
<file alias="themes/qet/22x22@2/cable.svg">qet/traced/large22/cable.svg</file>
|
||||
<file alias="themes/qet/32x32/cable.svg">qet/traced/large16/cable.svg</file>
|
||||
<file alias="themes/qet/32x32@2/cable.svg">qet/traced/large16/cable.svg</file>
|
||||
<file alias="themes/qet/48x48/cable.svg">themes/generated/traced-large-24/cable.svg</file>
|
||||
<file alias="themes/qet/48x48@2/cable.svg">themes/generated/traced-large-24/cable.svg</file>
|
||||
<file alias="themes/qet/64x64/cable.svg">qet/traced/large16/cable.svg</file>
|
||||
<file alias="themes/qet/64x64@2/cable.svg">qet/traced/large16/cable.svg</file>
|
||||
<file alias="themes/qet/128x128/cable.svg">qet/traced/large16/cable.svg</file>
|
||||
<file alias="themes/qet/128x128@2/cable.svg">qet/traced/large16/cable.svg</file>
|
||||
<file alias="themes/qet/24x24/cable.svg">themes/generated/traced-24/cable.svg</file>
|
||||
<file alias="themes/qet/24x24@2/cable.svg">themes/generated/traced-large-24/cable.svg</file>
|
||||
<file alias="themes/qet/scalable/cable.svg">qet/traced/large22/cable.svg</file>
|
||||
<file alias="themes/qet/16x16/circle.svg">qet/traced/16/circle.svg</file>
|
||||
<file alias="themes/qet/16x16@2/circle.svg">qet/traced/large16/circle.svg</file>
|
||||
<file alias="themes/qet/22x22/circle.svg">qet/traced/22/circle.svg</file>
|
||||
@@ -2699,6 +2714,12 @@
|
||||
<file>themes/qet-dark/traced/large22/bring_forward.svg</file>
|
||||
<file>themes/qet-dark/traced-large-24/bring_forward.svg</file>
|
||||
<file>themes/qet-dark/traced-24/bring_forward.svg</file>
|
||||
<file>themes/qet-dark/traced/16/cable.svg</file>
|
||||
<file>themes/qet-dark/traced/large16/cable.svg</file>
|
||||
<file>themes/qet-dark/traced/22/cable.svg</file>
|
||||
<file>themes/qet-dark/traced/large22/cable.svg</file>
|
||||
<file>themes/qet-dark/traced-large-24/cable.svg</file>
|
||||
<file>themes/qet-dark/traced-24/cable.svg</file>
|
||||
<file>themes/qet-dark/traced/16/circle.svg</file>
|
||||
<file>themes/qet-dark/traced/large16/circle.svg</file>
|
||||
<file>themes/qet-dark/traced/22/circle.svg</file>
|
||||
@@ -3516,6 +3537,21 @@
|
||||
<file alias="themes/qet-dark/24x24/bring_forward.svg">themes/qet-dark/traced-24/bring_forward.svg</file>
|
||||
<file alias="themes/qet-dark/24x24@2/bring_forward.svg">themes/qet-dark/traced-large-24/bring_forward.svg</file>
|
||||
<file alias="themes/qet-dark/scalable/bring_forward.svg">themes/qet-dark/traced/large22/bring_forward.svg</file>
|
||||
<file alias="themes/qet-dark/16x16/cable.svg">themes/qet-dark/traced/16/cable.svg</file>
|
||||
<file alias="themes/qet-dark/16x16@2/cable.svg">themes/qet-dark/traced/large16/cable.svg</file>
|
||||
<file alias="themes/qet-dark/22x22/cable.svg">themes/qet-dark/traced/22/cable.svg</file>
|
||||
<file alias="themes/qet-dark/22x22@2/cable.svg">themes/qet-dark/traced/large22/cable.svg</file>
|
||||
<file alias="themes/qet-dark/32x32/cable.svg">themes/qet-dark/traced/large16/cable.svg</file>
|
||||
<file alias="themes/qet-dark/32x32@2/cable.svg">themes/qet-dark/traced/large16/cable.svg</file>
|
||||
<file alias="themes/qet-dark/48x48/cable.svg">themes/qet-dark/traced-large-24/cable.svg</file>
|
||||
<file alias="themes/qet-dark/48x48@2/cable.svg">themes/qet-dark/traced-large-24/cable.svg</file>
|
||||
<file alias="themes/qet-dark/64x64/cable.svg">themes/qet-dark/traced/large16/cable.svg</file>
|
||||
<file alias="themes/qet-dark/64x64@2/cable.svg">themes/qet-dark/traced/large16/cable.svg</file>
|
||||
<file alias="themes/qet-dark/128x128/cable.svg">themes/qet-dark/traced/large16/cable.svg</file>
|
||||
<file alias="themes/qet-dark/128x128@2/cable.svg">themes/qet-dark/traced/large16/cable.svg</file>
|
||||
<file alias="themes/qet-dark/24x24/cable.svg">themes/qet-dark/traced-24/cable.svg</file>
|
||||
<file alias="themes/qet-dark/24x24@2/cable.svg">themes/qet-dark/traced-large-24/cable.svg</file>
|
||||
<file alias="themes/qet-dark/scalable/cable.svg">themes/qet-dark/traced/large22/cable.svg</file>
|
||||
<file alias="themes/qet-dark/16x16/circle.svg">themes/qet-dark/traced/16/circle.svg</file>
|
||||
<file alias="themes/qet-dark/16x16@2/circle.svg">themes/qet-dark/traced/large16/circle.svg</file>
|
||||
<file alias="themes/qet-dark/22x22/circle.svg">themes/qet-dark/traced/22/circle.svg</file>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<path fill="none" stroke="currentColor" stroke-width="2" d="M2 5h12"/>
|
||||
<path fill="none" stroke="currentColor" stroke-width="1" d="M11 1v14"/>
|
||||
<path fill="none" stroke="currentColor" stroke-width="1.5" d="M9 9L13 1"/>
|
||||
<path fill="currentColor" d="M1 9h5v1H1zM1 11h3v1H1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 350 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<path fill="none" stroke="currentColor" stroke-width="3" d="M3 7h16"/>
|
||||
<path fill="none" stroke="currentColor" stroke-width="1.5" d="M15 1v20"/>
|
||||
<path fill="none" stroke="currentColor" stroke-width="2" d="M12.5 12.5L17.5 2.5"/>
|
||||
<path fill="currentColor" d="M2 13h7v1.5H2zM2 16.5h4v1.5H2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 367 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" d="M2 5h12"/>
|
||||
<path fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" d="M11 1v14"/>
|
||||
<path fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" d="M9 9L13 1"/>
|
||||
<path fill="currentColor" d="M1 9h5v1H1zM1 11h3v1H1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 419 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<path fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" d="M3 7h16"/>
|
||||
<path fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" d="M15 1v20"/>
|
||||
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" d="M12.5 12.5L17.5 2.5"/>
|
||||
<path fill="currentColor" d="M2 13h7v1.5H2zM2 16.5h4v1.5H2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 436 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<g transform="translate(1,1)">
|
||||
<path fill="none" stroke="currentColor" stroke-width="3" d="M3 7h16"/>
|
||||
<path fill="none" stroke="currentColor" stroke-width="1.5" d="M15 1v20"/>
|
||||
<path fill="none" stroke="currentColor" stroke-width="2" d="M12.5 12.5L17.5 2.5"/>
|
||||
<path fill="currentColor" d="M2 13h7v1.5H2zM2 16.5h4v1.5H2z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 407 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<g transform="translate(1,1)">
|
||||
<path fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" d="M3 7h16"/>
|
||||
<path fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" d="M15 1v20"/>
|
||||
<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" d="M12.5 12.5L17.5 2.5"/>
|
||||
<path fill="currentColor" d="M2 13h7v1.5H2zM2 16.5h4v1.5H2z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 476 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<g transform="translate(1,1)">
|
||||
<path fill="none" stroke="#dcdcdc" stroke-width="3" d="M3 7h16"/>
|
||||
<path fill="none" stroke="#dcdcdc" stroke-width="1.5" d="M15 1v20"/>
|
||||
<path fill="none" stroke="#dcdcdc" stroke-width="2" d="M12.5 12.5L17.5 2.5"/>
|
||||
<path fill="#dcdcdc" d="M2 13h7v1.5H2zM2 16.5h4v1.5H2z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 387 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<g transform="translate(1,1)">
|
||||
<path fill="none" stroke="#dcdcdc" stroke-width="3" stroke-linecap="round" d="M3 7h16"/>
|
||||
<path fill="none" stroke="#dcdcdc" stroke-width="1.5" stroke-linecap="round" d="M15 1v20"/>
|
||||
<path fill="none" stroke="#dcdcdc" stroke-width="2" stroke-linecap="round" d="M12.5 12.5L17.5 2.5"/>
|
||||
<path fill="#dcdcdc" d="M2 13h7v1.5H2zM2 16.5h4v1.5H2z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 456 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<path fill="none" stroke="#dcdcdc" stroke-width="2" d="M2 5h12"/>
|
||||
<path fill="none" stroke="#dcdcdc" stroke-width="1" d="M11 1v14"/>
|
||||
<path fill="none" stroke="#dcdcdc" stroke-width="1.5" d="M9 9L13 1"/>
|
||||
<path fill="#dcdcdc" d="M1 9h5v1H1zM1 11h3v1H1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 330 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<path fill="none" stroke="#dcdcdc" stroke-width="3" d="M3 7h16"/>
|
||||
<path fill="none" stroke="#dcdcdc" stroke-width="1.5" d="M15 1v20"/>
|
||||
<path fill="none" stroke="#dcdcdc" stroke-width="2" d="M12.5 12.5L17.5 2.5"/>
|
||||
<path fill="#dcdcdc" d="M2 13h7v1.5H2zM2 16.5h4v1.5H2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 347 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<path fill="none" stroke="#dcdcdc" stroke-width="2" stroke-linecap="round" d="M2 5h12"/>
|
||||
<path fill="none" stroke="#dcdcdc" stroke-width="1" stroke-linecap="round" d="M11 1v14"/>
|
||||
<path fill="none" stroke="#dcdcdc" stroke-width="1.5" stroke-linecap="round" d="M9 9L13 1"/>
|
||||
<path fill="#dcdcdc" d="M1 9h5v1H1zM1 11h3v1H1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 399 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<path fill="none" stroke="#dcdcdc" stroke-width="3" stroke-linecap="round" d="M3 7h16"/>
|
||||
<path fill="none" stroke="#dcdcdc" stroke-width="1.5" stroke-linecap="round" d="M15 1v20"/>
|
||||
<path fill="none" stroke="#dcdcdc" stroke-width="2" stroke-linecap="round" d="M12.5 12.5L17.5 2.5"/>
|
||||
<path fill="#dcdcdc" d="M2 13h7v1.5H2zM2 16.5h4v1.5H2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 416 B |
@@ -416,6 +416,7 @@ void TerminalStripDrawer::paint(QPainter *painter)
|
||||
if (m_strip && m_pattern)
|
||||
{
|
||||
m_united_xref_text_rect = QRectF();
|
||||
m_xref_rects.clear();
|
||||
//To draw text, QPainter need a Qrect. Instead of create an instance
|
||||
//for each text, we re-use the same instance of QRect.
|
||||
QRect text_rect;
|
||||
@@ -612,6 +613,9 @@ void TerminalStripDrawer::paint(QPainter *painter)
|
||||
auto xref_string = shared_real_terminal->xref();
|
||||
|
||||
const auto mapped_xref_text_rect = transform.mapRect(painter->boundingRect(xref_rect, xref_string, xref_text_option));
|
||||
if (!xref_string.isEmpty()) {
|
||||
m_xref_rects.append({mapped_xref_text_rect, physical_index, i});
|
||||
}
|
||||
if (m_united_xref_text_rect.isNull()) {
|
||||
m_united_xref_text_rect = mapped_xref_text_rect;
|
||||
} else {
|
||||
@@ -792,6 +796,16 @@ hoverTerminal TerminalStripDrawer::hoveredXref() const
|
||||
return m_hovered_xref;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief TerminalStripDrawer::xrefRects
|
||||
* @return the rectangle of every xref text drawn by the last call
|
||||
* of paint(), in the coordinates of the painter.
|
||||
*/
|
||||
QVector<XrefRect> TerminalStripDrawer::xrefRects() const
|
||||
{
|
||||
return m_xref_rects;
|
||||
}
|
||||
|
||||
qreal TerminalStripDrawer::height() const
|
||||
{
|
||||
if (m_pattern)
|
||||
|
||||
@@ -40,6 +40,18 @@ namespace TerminalStripDrawer
|
||||
int real{-1};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The XrefRect struct
|
||||
* Where the xref text of a real terminal was drawn, in the coordinates
|
||||
* of the item, with the physical and real index of that terminal.
|
||||
* Filled by paint(): used to make the xref clickable in an exported PDF.
|
||||
*/
|
||||
struct XrefRect{
|
||||
QRectF rect;
|
||||
int physical{-1};
|
||||
int real{-1};
|
||||
};
|
||||
|
||||
class AbstractBridgeInterface
|
||||
{
|
||||
public:
|
||||
@@ -105,6 +117,7 @@ namespace TerminalStripDrawer
|
||||
bool mouseHoverXref() const;
|
||||
bool needUpdate();
|
||||
hoverTerminal hoveredXref() const;
|
||||
QVector<XrefRect> xrefRects() const;
|
||||
|
||||
private:
|
||||
qreal height() const;
|
||||
@@ -118,6 +131,7 @@ namespace TerminalStripDrawer
|
||||
QRectF m_united_xref_text_rect;
|
||||
bool m_last_mouse_pos_in_xrefs_rect{false};
|
||||
hoverTerminal m_hovered_xref;
|
||||
QVector<XrefRect> m_xref_rects;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -96,6 +96,45 @@ QString TerminalStripItem::name() const {
|
||||
return tr("plan of terminals");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief TerminalStripItem::pdfRefs
|
||||
* @return the xref texts of the plan which lead to an element,
|
||||
* as the folio and rectangles needed to make them clickable in a PDF.
|
||||
* The rectangles are those of the last paint, so call this after
|
||||
* the item was rendered.
|
||||
*/
|
||||
QList<TerminalStripItem::PdfRef> TerminalStripItem::pdfRefs() const
|
||||
{
|
||||
QList<PdfRef> refs;
|
||||
if (m_strip.isNull()) {
|
||||
return refs;
|
||||
}
|
||||
|
||||
const auto xref_rects = m_drawer.xrefRects();
|
||||
for (const auto &xref_rect : xref_rects)
|
||||
{
|
||||
const auto physical_terminal = m_strip->physicalTerminal(xref_rect.physical);
|
||||
if (physical_terminal.isNull()) {
|
||||
continue;
|
||||
}
|
||||
const auto real_terminal = physical_terminal->realTerminal(xref_rect.real);
|
||||
if (real_terminal.isNull() || !real_terminal->isElement()) {
|
||||
continue;
|
||||
}
|
||||
const QPointer<Element> element = real_terminal->element();
|
||||
if (element.isNull() || !element->diagram()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PdfRef ref;
|
||||
ref.diagram = element->diagram();
|
||||
ref.here = mapRectToScene(xref_rect.rect);
|
||||
ref.there = element->mapRectToScene(element->boundingRect());
|
||||
refs.append(ref);
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
void TerminalStripItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
|
||||
{
|
||||
QetGraphicsItem::hoverMoveEvent(event);
|
||||
|
||||
@@ -19,11 +19,14 @@
|
||||
#define TERMINALSTRIPITEM_H
|
||||
|
||||
#include <QGraphicsObject>
|
||||
#include <QList>
|
||||
#include <QRectF>
|
||||
#include <QUuid>
|
||||
|
||||
#include "terminalstripdrawer.h"
|
||||
#include "../../qetgraphicsitem/qetgraphicsitem.h"
|
||||
|
||||
class Diagram;
|
||||
class TerminalStrip;
|
||||
|
||||
class TerminalStripItem : public QetGraphicsItem
|
||||
@@ -52,6 +55,22 @@ class TerminalStripItem : public QetGraphicsItem
|
||||
void refreshPending();
|
||||
void setLayout(QSharedPointer<TerminalStripLayoutPattern> layout);
|
||||
|
||||
/**
|
||||
* @brief The PdfRef struct
|
||||
* One xref of the plan which leads to an element: used to make
|
||||
* it a clickable link in an exported PDF.
|
||||
*/
|
||||
struct PdfRef
|
||||
{
|
||||
///The folio of the element the xref points at
|
||||
Diagram *diagram = nullptr;
|
||||
///Where the text of the xref stands on the folio of this item
|
||||
QRectF here;
|
||||
///Where the element stands on its folio
|
||||
QRectF there;
|
||||
};
|
||||
QList<PdfRef> pdfRefs() const;
|
||||
|
||||
private:
|
||||
void setDefaultLayout();
|
||||
|
||||
|
||||
@@ -1011,6 +1011,15 @@ bool TerminalStrip::fromXml(QDomElement &xml_element)
|
||||
auto xml_layout = xml_element.firstChildElement(QStringLiteral("layout"));
|
||||
if (!xml_layout.isNull())
|
||||
{
|
||||
//The folios holding its terminals are built (QET_LAZY_FOLIOS):
|
||||
//a strip is read with the project, before the database could
|
||||
//tell where any symbol is
|
||||
QSet<QUuid> terminals;
|
||||
const QDomNodeList xml_reals = xml_layout.elementsByTagName(RealTerminal::xmlTagName());
|
||||
for (int i = 0 ; i < xml_reals.size() ; ++i)
|
||||
terminals.insert(QUuid(xml_reals.at(i).toElement().attribute(QStringLiteral("element_uuid"))));
|
||||
m_project->buildFoliosHoldingSymbols(terminals);
|
||||
|
||||
//Get all free elements terminal of the project
|
||||
const ElementProvider ep(m_project);
|
||||
auto free_terminals = ep.freeTerminal();
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#include "../terminalstrip.h"
|
||||
#include "../UndoCommand/addterminaltostripcommand.h"
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
/**
|
||||
* @brief FreeTerminalEditor::FreeTerminalEditor
|
||||
* @param project
|
||||
@@ -39,8 +41,23 @@ FreeTerminalEditor::FreeTerminalEditor(QETProject *project, QWidget *parent) :
|
||||
ui->m_table_view->setItemDelegate(new FreeTerminalModelDelegate(ui->m_table_view));
|
||||
|
||||
m_model = new FreeTerminalModel(m_project, this);
|
||||
ui->m_table_view->setModel(m_model);
|
||||
ui->m_table_view->setCurrentIndex(m_model->index(0,0));
|
||||
|
||||
//The view works on a filtered proxy of the model. The filter is a plain,
|
||||
//case insensitive "contains" on the label, so typing Z1 shows Z1:1, Z1:2...
|
||||
//Not dynamic : an edited label must not vanish from the table while it is typed.
|
||||
m_proxy_model = new QSortFilterProxyModel(this);
|
||||
m_proxy_model->setSourceModel(m_model);
|
||||
m_proxy_model->setFilterKeyColumn(FreeTerminalModel::Label);
|
||||
m_proxy_model->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
m_proxy_model->setDynamicSortFilter(false);
|
||||
ui->m_table_view->setModel(m_proxy_model);
|
||||
ui->m_table_view->setCurrentIndex(m_proxy_model->index(0,0));
|
||||
|
||||
connect(ui->m_filter_le, &QLineEdit::textChanged, this, [this](const QString &text)
|
||||
{
|
||||
m_proxy_model->setFilterFixedString(text);
|
||||
selectionChanged();
|
||||
});
|
||||
//Selected rows can be dragged onto a strip of the terminal strip tree
|
||||
ui->m_table_view->setDragEnabled(true);
|
||||
ui->m_table_view->setDragDropMode(QAbstractItemView::DragOnly);
|
||||
@@ -55,8 +72,9 @@ FreeTerminalEditor::FreeTerminalEditor(QETProject *project, QWidget *parent) :
|
||||
connect(ui->m_table_view->selectionModel(), &QItemSelectionModel::selectionChanged,
|
||||
this, &FreeTerminalEditor::selectionChanged);
|
||||
|
||||
connect(ui->m_table_view, &QAbstractItemView::doubleClicked, this, [this](const QModelIndex &index)
|
||||
connect(ui->m_table_view, &QAbstractItemView::doubleClicked, this, [this](const QModelIndex &proxy_index)
|
||||
{
|
||||
const auto index = m_proxy_model->mapToSource(proxy_index);
|
||||
if (m_model->columnTypeForIndex(index) == FreeTerminalModel::XRef)
|
||||
{
|
||||
auto mrtd = m_model->dataAtRow(index.row());
|
||||
@@ -176,7 +194,7 @@ void FreeTerminalEditor::on_m_type_cb_activated(int index)
|
||||
|
||||
for (auto model_index : index_list)
|
||||
{
|
||||
auto type_index = m_model->index(model_index.row(), FreeTerminalModel::Type, model_index.parent());
|
||||
auto type_index = m_model->index(m_proxy_model->mapToSource(model_index).row(), FreeTerminalModel::Type);
|
||||
if (type_index.isValid())
|
||||
{
|
||||
ElementData::TerminalType override_type;
|
||||
@@ -219,7 +237,7 @@ void FreeTerminalEditor::on_m_function_cb_activated(int index)
|
||||
|
||||
for (auto model_index : index_list)
|
||||
{
|
||||
auto function_index = m_model->index(model_index.row(), FreeTerminalModel::Function, model_index.parent());
|
||||
auto function_index = m_model->index(m_proxy_model->mapToSource(model_index).row(), FreeTerminalModel::Function);
|
||||
if (function_index.isValid())
|
||||
{
|
||||
ElementData::TerminalFunction override_function;
|
||||
@@ -248,7 +266,7 @@ void FreeTerminalEditor::on_m_led_cb_activated(int index)
|
||||
|
||||
for (auto model_index : index_list)
|
||||
{
|
||||
auto led_index = m_model->index(model_index.row(), FreeTerminalModel::Led, model_index.parent());
|
||||
auto led_index = m_model->index(m_proxy_model->mapToSource(model_index).row(), FreeTerminalModel::Led);
|
||||
|
||||
if (led_index.isValid()) {
|
||||
m_model->setData(led_index,
|
||||
@@ -262,7 +280,10 @@ void FreeTerminalEditor::on_m_led_cb_activated(int index)
|
||||
void FreeTerminalEditor::on_m_move_pb_clicked()
|
||||
{
|
||||
//Get the selected real terminal
|
||||
const auto index_list = ui->m_table_view->selectionModel()->selectedIndexes();
|
||||
QModelIndexList index_list;
|
||||
for (const auto &proxy_index : ui->m_table_view->selectionModel()->selectedIndexes()) {
|
||||
index_list.append(m_proxy_model->mapToSource(proxy_index));
|
||||
}
|
||||
const auto real_t_vector = m_model->realTerminalForIndex(index_list);
|
||||
if (real_t_vector.isEmpty()) {
|
||||
return;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
class RealTerminal;
|
||||
class FreeTerminalModel;
|
||||
class QTableView;
|
||||
class QSortFilterProxyModel;
|
||||
|
||||
namespace Ui {
|
||||
class FreeTerminalEditor;
|
||||
@@ -57,5 +58,6 @@ class FreeTerminalEditor : public QWidget
|
||||
Ui::FreeTerminalEditor *ui;
|
||||
QPointer <QETProject> m_project;
|
||||
FreeTerminalModel *m_model {nullptr};
|
||||
QSortFilterProxyModel *m_proxy_model {nullptr};
|
||||
};
|
||||
#endif // FREETERMINALEDITOR_H
|
||||
|
||||
@@ -14,14 +14,24 @@
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="1">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLineEdit" name="m_filter_le">
|
||||
<property name="placeholderText">
|
||||
<string>Filter by label (e.g. Z1)</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="m_move_label">
|
||||
<property name="text">
|
||||
<string>Move to :</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<item row="1" column="2">
|
||||
<widget class="QComboBox" name="m_move_in_cb">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
@@ -34,7 +44,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="2">
|
||||
<item row="6" column="2">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
@@ -47,35 +57,35 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="0" rowspan="6">
|
||||
<item row="1" column="0" rowspan="6">
|
||||
<widget class="QTableView" name="m_table_view">
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Type :</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="4" column="1">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Function :</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<item row="5" column="1">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>LED :</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="m_move_pb">
|
||||
<property name="toolTip">
|
||||
<string>Apply displacement</string>
|
||||
@@ -88,14 +98,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="3">
|
||||
<item row="2" column="1" colspan="3">
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2" colspan="2">
|
||||
<item row="3" column="2" colspan="2">
|
||||
<widget class="QComboBox" name="m_type_cb">
|
||||
<item>
|
||||
<property name="text">
|
||||
@@ -149,7 +159,7 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2" colspan="2">
|
||||
<item row="4" column="2" colspan="2">
|
||||
<widget class="QComboBox" name="m_function_cb">
|
||||
<item>
|
||||
<property name="text">
|
||||
@@ -168,7 +178,7 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2" colspan="2">
|
||||
<item row="5" column="2" colspan="2">
|
||||
<widget class="QComboBox" name="m_led_cb">
|
||||
<item>
|
||||
<property name="text">
|
||||
|
||||
@@ -649,6 +649,74 @@ namespace autonum
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief resetFolioCounters
|
||||
@param nc the rule to read
|
||||
@return the same rule, with the parts whose counter belongs to a
|
||||
single folio put back to the beginning that folio is numbered
|
||||
from -- the initial value the rule carries for it -- while the
|
||||
parts counting over the whole project are left alone
|
||||
*/
|
||||
NumerotationContext resetFolioCounters(const NumerotationContext &nc)
|
||||
{
|
||||
NumerotationContext out = nc;
|
||||
for (int i = 0; i < out.size(); ++i)
|
||||
{
|
||||
const QStringList parts = out.itemAt(i);
|
||||
if (parts.isEmpty()) continue;
|
||||
|
||||
const QString &type = parts.at(0);
|
||||
if (type == QLatin1String("unitfolio")
|
||||
|| type == QLatin1String("tenfolio")
|
||||
|| type == QLatin1String("hundredfolio"))
|
||||
{
|
||||
//The beginning a folio is numbered from is the one
|
||||
//written into the rule, never a hard 1: a counter
|
||||
//starting at 5 starts every folio at 5.
|
||||
const bool has_start = parts.size() > 3
|
||||
&& !parts.at(3).isEmpty();
|
||||
out.replaceValue(i, has_start ? parts.at(3)
|
||||
: QStringLiteral("1"));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief resetContextCounters
|
||||
@param nc the rule to read
|
||||
@return the same rule, with every part which counts set back to
|
||||
the value it started from
|
||||
*/
|
||||
NumerotationContext resetContextCounters(const NumerotationContext &nc)
|
||||
{
|
||||
NumerotationContext out = resetFolioCounters(nc);
|
||||
for (int i = 0; i < out.size(); ++i)
|
||||
{
|
||||
const QStringList parts = out.itemAt(i);
|
||||
if (parts.isEmpty()) continue;
|
||||
|
||||
const QString &type = parts.at(0);
|
||||
if (type == QLatin1String("unit")
|
||||
|| type == QLatin1String("ten")
|
||||
|| type == QLatin1String("hundred"))
|
||||
{
|
||||
out.replaceValue(i, QStringLiteral("1"));
|
||||
}
|
||||
else if (type == QLatin1String("wrap"))
|
||||
{
|
||||
//A cyclic part walks over [0, modulus), so it goes
|
||||
//back to 0 rather than to 1
|
||||
out.replaceValue(i, QStringLiteral("0"));
|
||||
}
|
||||
else if (type == QLatin1String("alpha"))
|
||||
{
|
||||
out.replaceValue(i, QStringLiteral("a"));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief numerotationContextToFormula
|
||||
@param nc
|
||||
|
||||
@@ -117,6 +117,31 @@ namespace autonum
|
||||
void setFolioSequentialToHash(QStringList &list, QHash<QString, QStringList> &hash, const QString& autoNumName);
|
||||
void setSequential(const QString& label, autonum::sequentialNumbers &seqStruct, NumerotationContext &context, Diagram *diagram, const QString& hashKey);
|
||||
QString numerotationContextToFormula(const NumerotationContext &nc);
|
||||
/**
|
||||
Put back every part of a numbering rule whose counter belongs to
|
||||
one single folio -- "Chiffre 1 - Folio" and its two longer forms
|
||||
-- to the beginning that folio is numbered from, which is the
|
||||
initial value written into the rule itself. The parts which count
|
||||
over the whole project are left exactly as they are: a folio
|
||||
starts again, the project does not.
|
||||
@param nc the rule to read
|
||||
@return the same rule, with its folio-bound counters restarted
|
||||
*/
|
||||
NumerotationContext resetFolioCounters(const NumerotationContext &nc);
|
||||
/**
|
||||
Set every part of a numbering rule which counts back to its very
|
||||
first value, so that numbering starts at the beginning again --
|
||||
which is what numbering a whole project over has to do, whatever
|
||||
the last number handed out happened to be. A part which counts
|
||||
from an initial value of its own goes back to that value, a plain
|
||||
number goes back to 1, a cyclic one to 0 and an alphabetic one to
|
||||
"a". The parts which do not count -- a fixed text, the number of
|
||||
the folio, the plant -- are left exactly as they are: they say
|
||||
what a number is made of, they are not the part which goes on.
|
||||
@param nc the rule to read
|
||||
@return the same rule, with its counters at their beginning
|
||||
*/
|
||||
NumerotationContext resetContextCounters(const NumerotationContext &nc);
|
||||
QString elementPrefixForLocation(const ElementsLocation &location);
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,9 @@ QList<Diagram *> AutoNumSchemeCommand::usersOf(const QETProject *project, Kind k
|
||||
if (!project || title.isEmpty()) {
|
||||
return users;
|
||||
}
|
||||
for (Diagram *diagram : project->diagrams()) {
|
||||
//The schemes a folio uses are its own data, read on opening:
|
||||
//folios() builds nothing
|
||||
for (Diagram *diagram : project->folios()) {
|
||||
if (kind == Kind::Conductor) {
|
||||
if (diagram->conductorsAutonumName() == title) {
|
||||
users << diagram;
|
||||
@@ -234,7 +236,8 @@ void AutoNumSchemeCommand::rename(const QString &from, const QString &to)
|
||||
if (m_project->conductorCurrentAutoNum() == from) {
|
||||
m_project->setCurrentConductorAutoNum(to);
|
||||
}
|
||||
for (Diagram *d : m_project->diagrams()) {
|
||||
//Folios' own data, read on opening: folios() builds nothing
|
||||
for (Diagram *d : m_project->folios()) {
|
||||
//The highest folio number given, kept by scheme title
|
||||
for (auto *hash : {&d->m_cnd_unitfolio_max, &d->m_cnd_tenfolio_max, &d->m_cnd_hundredfolio_max}) {
|
||||
if (hash->contains(from)) {
|
||||
|
||||
@@ -369,10 +369,19 @@ ElementAutoNumSchemeCommand::NumberSupport ElementAutoNumSchemeCommand::numberSu
|
||||
std::optional<int> ElementAutoNumSchemeCommand::numberOf(const NumberSupport &support,
|
||||
const Element *element)
|
||||
{
|
||||
if (!support.supported || !element) {
|
||||
if (!element) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return numberOf(support, element->sequenceStruct());
|
||||
}
|
||||
|
||||
/// @return the number the sequential numbers @p seq give, if the scheme knows it
|
||||
std::optional<int> ElementAutoNumSchemeCommand::numberOf(const NumberSupport &support,
|
||||
const autonum::sequentialNumbers &seq)
|
||||
{
|
||||
if (!support.supported) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const autonum::sequentialNumbers seq = element->sequenceStruct();
|
||||
const QStringList &list = support.type == QLatin1String("unit") ? seq.unit
|
||||
: support.type == QLatin1String("ten") ? seq.ten
|
||||
: seq.hundred;
|
||||
@@ -385,14 +394,18 @@ std::optional<int> ElementAutoNumSchemeCommand::numberOf(const NumberSupport &su
|
||||
}
|
||||
|
||||
namespace {
|
||||
/// The elements of @p project which follow the scheme @p title and are not slaves or reports
|
||||
QVector<Element *> numberedFollowers(const QETProject *project, const QString &title)
|
||||
/// The symbols of @p project which follow the scheme @p title and are not
|
||||
/// slaves or reports, built or not (QETProject::symbolNumbering())
|
||||
QList<QETProject::SymbolNumbering> numberedFollowers(const QETProject *project, const QString &title)
|
||||
{
|
||||
QVector<Element *> list;
|
||||
const auto followers = project->elementsUsingElementAutoNum(title);
|
||||
for (Element *el : followers) {
|
||||
if (el->linkType() != Element::Slave && !(el->linkType() & Element::AllReport)) {
|
||||
list << el;
|
||||
QList<QETProject::SymbolNumbering> list;
|
||||
const QUuid id = project->elementAutoNumId(title);
|
||||
if (id.isNull()) {
|
||||
return list;
|
||||
}
|
||||
for (const auto &symbol : project->symbolNumbering()) {
|
||||
if (symbol.numbered && !symbol.formula.isEmpty() && symbol.scheme == id) {
|
||||
list << symbol;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
@@ -411,13 +424,10 @@ QSet<QString> ElementAutoNumSchemeCommand::labelsHeldBesides(const QETProject *p
|
||||
if (!project) {
|
||||
return labels;
|
||||
}
|
||||
for (Diagram *d : project->diagrams()) {
|
||||
for (QGraphicsItem *item : d->items()) {
|
||||
if (auto *el = qgraphicsitem_cast<Element *>(item)) {
|
||||
if (el != element) {
|
||||
labels << el->elementInformations().value(QETInformation::ELMT_LABEL).toString();
|
||||
}
|
||||
}
|
||||
//Folios not built yet answer from what building them would give
|
||||
for (const auto &symbol : project->symbolNumbering()) {
|
||||
if (symbol.element != element) {
|
||||
labels << symbol.label;
|
||||
}
|
||||
}
|
||||
labels.remove(QString());
|
||||
@@ -453,8 +463,8 @@ std::optional<ElementAutoNumSchemeCommand::CounterConflict> ElementAutoNumScheme
|
||||
|
||||
CounterConflict conflict;
|
||||
conflict.counter = counter;
|
||||
for (const Element *el : numberedFollowers(project, title)) {
|
||||
if (const auto number = numberOf(support, el)) {
|
||||
for (const auto &symbol : numberedFollowers(project, title)) {
|
||||
if (const auto number = numberOf(support, symbol.sequence)) {
|
||||
if (*number >= counter) {
|
||||
++conflict.count;
|
||||
conflict.highest = std::max(conflict.highest, *number);
|
||||
@@ -486,8 +496,8 @@ QList<ElementAutoNumSchemeCommand::GapRange> ElementAutoNumSchemeCommand::gapRan
|
||||
return ranges;
|
||||
}
|
||||
QSet<int> used;
|
||||
for (const Element *el : numberedFollowers(project, title)) {
|
||||
if (const auto number = numberOf(support, el)) {
|
||||
for (const auto &symbol : numberedFollowers(project, title)) {
|
||||
if (const auto number = numberOf(support, symbol.sequence)) {
|
||||
used << *number;
|
||||
}
|
||||
}
|
||||
@@ -559,22 +569,19 @@ QList<int> ElementAutoNumSchemeCommand::freeNumbers(const QETProject *project,
|
||||
|
||||
QSet<int> used;
|
||||
int highest = context.itemAt(support.partIndex).value(1).toInt();
|
||||
for (const Element *el : numberedFollowers(project, title)) {
|
||||
if (el == element) continue;
|
||||
if (const auto number = numberOf(support, el)) {
|
||||
for (const auto &symbol : numberedFollowers(project, title)) {
|
||||
if (symbol.element == element) continue;
|
||||
if (const auto number = numberOf(support, symbol.sequence)) {
|
||||
used << *number;
|
||||
highest = std::max(highest, *number);
|
||||
}
|
||||
}
|
||||
|
||||
QSet<QString> labels;
|
||||
for (Diagram *d : project->diagrams()) {
|
||||
for (QGraphicsItem *item : d->items()) {
|
||||
if (auto *el = qgraphicsitem_cast<Element *>(item)) {
|
||||
if (el != element) {
|
||||
labels << el->elementInformations().value(QETInformation::ELMT_LABEL).toString();
|
||||
}
|
||||
}
|
||||
//Folios not built yet answer from what building them would give
|
||||
for (const auto &symbol : project->symbolNumbering()) {
|
||||
if (symbol.element != element) {
|
||||
labels << symbol.label;
|
||||
}
|
||||
}
|
||||
labels.remove(QString());
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#ifndef ELEMENTAUTONUMSCHEMECOMMAND_H
|
||||
#define ELEMENTAUTONUMSCHEMECOMMAND_H
|
||||
|
||||
#include "assignvariables.h"
|
||||
#include "numerotationcontext.h"
|
||||
#include "renumberelementscommand.h"
|
||||
|
||||
@@ -201,6 +202,8 @@ class ElementAutoNumSchemeCommand : public QUndoCommand
|
||||
static QSet<QString> labelsHeldBesides(const QETProject *project, const Element *element);
|
||||
static NumberSupport numberSupport(const NumerotationContext &context);
|
||||
static std::optional<int> numberOf(const NumberSupport &support, const Element *element);
|
||||
static std::optional<int> numberOf(const NumberSupport &support,
|
||||
const autonum::sequentialNumbers &seq);
|
||||
static QList<GapRange> gapRanges(const QETProject *project, const QString &title);
|
||||
static QList<int> freeNumbers(const QETProject *project, const QString &title,
|
||||
const Element *element, int limit = 500);
|
||||
|
||||
@@ -175,7 +175,8 @@ void AutoNumberingManagementW::on_m_apply_folios_rb_clicked()
|
||||
if (ui->m_from_folios_cb->count()<=0) {
|
||||
ui->m_from_folios_cb->clear();
|
||||
ui->m_from_folios_cb->addItem("");
|
||||
foreach (Diagram *diagram, project_->diagrams()){
|
||||
//Titles and positions only: folios() builds nothing
|
||||
foreach (Diagram *diagram, project_->folios()){
|
||||
if (diagram->title() != "")
|
||||
ui->m_from_folios_cb->addItem(
|
||||
diagram->title(),
|
||||
@@ -206,16 +207,17 @@ void AutoNumberingManagementW::on_m_from_folios_cb_currentIndexChanged(
|
||||
if (index > 0) {
|
||||
ui->m_to_folios_cb->setEnabled(true);
|
||||
ui->m_to_folios_cb->addItem("");
|
||||
for (int i=index;i<project_->diagrams().size();i++) {
|
||||
if (project_->diagrams().at(i)->title() != "") {
|
||||
const QList<Diagram *> folios = project_->folios();
|
||||
for (int i=index;i<folios.size();i++) {
|
||||
if (folios.at(i)->title() != "") {
|
||||
ui->m_to_folios_cb->addItem(
|
||||
project_->diagrams().at(i)->title(),
|
||||
project_->diagrams().at(i)->folioIndex());
|
||||
folios.at(i)->title(),
|
||||
folios.at(i)->folioIndex());
|
||||
}
|
||||
else ui->m_to_folios_cb->addItem(
|
||||
QString::number(
|
||||
project_->diagrams().at(i)->folioIndex()),
|
||||
project_->diagrams().at(i)->folioIndex());
|
||||
folios.at(i)->folioIndex()),
|
||||
folios.at(i)->folioIndex());
|
||||
}
|
||||
applyEnable(true);
|
||||
ui->m_selected_folios_le->clear();
|
||||
|
||||
@@ -136,7 +136,8 @@ void FolioAutonumberingW::on_m_autonumber_tabs_rb_clicked()
|
||||
if (ui->m_from_cb->count()<=0){
|
||||
ui->m_from_cb->clear();
|
||||
ui->m_from_cb->addItem("");
|
||||
foreach (Diagram *diagram, project_->diagrams()){
|
||||
//Titles only: folios() builds nothing
|
||||
foreach (Diagram *diagram, project_->folios()){
|
||||
ui->m_from_cb->addItem(diagram->title());
|
||||
}
|
||||
}
|
||||
@@ -164,9 +165,9 @@ void FolioAutonumberingW::on_m_from_cb_currentIndexChanged(int)
|
||||
ui->m_to_cb->clear();
|
||||
if (index > 0){
|
||||
ui->m_to_cb->setEnabled(true);
|
||||
for (int i=index;i<project_->diagrams().size();i++)
|
||||
ui->m_to_cb->addItem(project_
|
||||
->diagrams().at(i)->title());
|
||||
const QList<Diagram *> folios = project_->folios();
|
||||
for (int i=index;i<folios.size();i++)
|
||||
ui->m_to_cb->addItem(folios.at(i)->title());
|
||||
applyEnable(true);
|
||||
}
|
||||
else{
|
||||
|
||||
@@ -105,6 +105,16 @@ NumPartEditorW::NumPartEditorW (NumerotationContext &context,
|
||||
if (strl.at(0)=="wrap" && strl.size() > 4)
|
||||
ui -> modulus_spinBox -> setValue(strl.at(4).toInt());
|
||||
ui -> format_le -> setText(NumerotationContext::formatOf(strl));
|
||||
//A counter bound to the folio keeps the start it was
|
||||
//configured with: value_field shows the live counter, and
|
||||
//writing that back as the start would make every folio
|
||||
//begin at the last number the previous numbering gave out
|
||||
if ((strl.at(0)==QLatin1String("unitfolio")
|
||||
|| strl.at(0)==QLatin1String("tenfolio")
|
||||
|| strl.at(0)==QLatin1String("hundredfolio"))
|
||||
&& strl.size() > 3 && !strl.at(3).isEmpty()) {
|
||||
m_initial_value = strl.at(3).toInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,8 +141,12 @@ void NumPartEditorW::setVisibleItems()
|
||||
<< tr("Alphabetical")
|
||||
<< tr("Text");
|
||||
}
|
||||
else if (m_edited_type == 1)
|
||||
else if (m_edited_type == 1 || m_edited_type == 3)
|
||||
{
|
||||
//Type 3 is a cable: it is drawn along a folio just like a
|
||||
//conductor is, so it takes the same kinds of variable and
|
||||
//none of the ones an element has and a cable has not (its
|
||||
//line, its column, its prefix).
|
||||
items << tr("number format 1")
|
||||
<< tr("number format 1 - Sheet")
|
||||
<< tr("number format 01")
|
||||
@@ -232,7 +246,8 @@ NumerotationContext NumPartEditorW::toNumContext()
|
||||
nc.addValue(type_str,
|
||||
ui -> value_field -> displayText(),
|
||||
ui -> increase_spinBox -> value(),
|
||||
ui->value_field->displayText().toInt(),
|
||||
m_initial_value >= 0 ? m_initial_value
|
||||
: ui->value_field->displayText().toInt(),
|
||||
0,
|
||||
number_format);
|
||||
else if (type_str == "wrap")
|
||||
@@ -315,6 +330,10 @@ void NumPartEditorW::on_type_cb_activated(int) {
|
||||
*/
|
||||
void NumPartEditorW::on_value_field_textEdited()
|
||||
{
|
||||
//What the user types in is a deliberate start for a counter
|
||||
//bound to the folio; merely showing the live counter (as
|
||||
//setContext() does) never changes the start.
|
||||
m_initial_value = ui -> value_field -> text().toInt();
|
||||
emit changed();
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,14 @@ class NumPartEditorW : public QWidget
|
||||
QValidator *intValidator;
|
||||
QValidator *alphaValidator;
|
||||
int m_edited_type = -1; ///<0 == element : 1 == conductor : 2 == folio
|
||||
/**
|
||||
The value a counter bound to the folio counts each folio
|
||||
from (-1 while the row carries none): value_field shows
|
||||
the live counter, which numbering a project over leaves
|
||||
at the last number given out, so the start has to be
|
||||
kept apart from what is on show.
|
||||
*/
|
||||
int m_initial_value = -1;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -37,22 +37,41 @@ QList<ElementAutoNumSchemeCommand::Scheme> PasteNumberingImport::copiedBy(const
|
||||
elements follow and the project does not have.
|
||||
@param parent : parent of the question
|
||||
@param copied : the numberings the copy carries
|
||||
@param extra : commands the copy brings beside the paste itself
|
||||
(the cable lines of a fragment, for example); they are pushed inside
|
||||
the same macro, so one Ctrl+Z undoes all of it at once.
|
||||
*/
|
||||
void PasteNumberingImport::push(QWidget *parent,
|
||||
Diagram *diagram,
|
||||
const DiagramContent &content,
|
||||
const QList<ElementAutoNumSchemeCommand::Scheme> &copied)
|
||||
const QList<ElementAutoNumSchemeCommand::Scheme> &copied,
|
||||
const QList<QUndoCommand *> &extra)
|
||||
{
|
||||
QETProject *project = diagram->project();
|
||||
auto *paste = new PasteDiagramCommand(diagram, content);
|
||||
|
||||
//The paste and the commands beside it are one step: only the
|
||||
//macros really need more than the single push.
|
||||
const auto push_all = [&]() {
|
||||
if (extra.isEmpty()) {
|
||||
diagram->undoStack().push(paste);
|
||||
return;
|
||||
}
|
||||
diagram->undoStack().beginMacro(paste->text());
|
||||
diagram->undoStack().push(paste);
|
||||
for (QUndoCommand *cmd : extra) {
|
||||
diagram->undoStack().push(cmd);
|
||||
}
|
||||
diagram->undoStack().endMacro();
|
||||
};
|
||||
|
||||
QList<ElementAutoNumSchemeCommand::Scheme> missing;
|
||||
if (project && !copied.isEmpty()
|
||||
&& QSettings().value("diagramcommands/autonumber-pasted-elements", true).toBool()) {
|
||||
missing = ElementAutoNumSchemeCommand::missingForPaste(project, copied, content.m_elements);
|
||||
}
|
||||
if (missing.isEmpty()) {
|
||||
diagram->undoStack().push(paste);
|
||||
push_all();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -71,7 +90,7 @@ void PasteNumberingImport::push(QWidget *parent,
|
||||
QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::Yes);
|
||||
if (answer != QMessageBox::Yes) {
|
||||
diagram->undoStack().push(paste);
|
||||
push_all();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -91,5 +110,8 @@ void PasteNumberingImport::push(QWidget *parent,
|
||||
}
|
||||
}
|
||||
diagram->undoStack().push(paste);
|
||||
for (QUndoCommand *cmd : extra) {
|
||||
diagram->undoStack().push(cmd);
|
||||
}
|
||||
diagram->undoStack().endMacro();
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QList>
|
||||
#include <QUndoCommand>
|
||||
|
||||
class Diagram;
|
||||
class QWidget;
|
||||
@@ -40,6 +41,11 @@ class QWidget;
|
||||
without a numbering.
|
||||
|
||||
The import and the paste are one undo step.
|
||||
|
||||
A paste which brings more than itself (the cable lines of the copy,
|
||||
for instance, which are no part of a DiagramContent) hands those
|
||||
commands over through @p extra: they are pushed inside the very
|
||||
same macro, so one Ctrl+Z still takes the whole of it back.
|
||||
*/
|
||||
class PasteNumberingImport
|
||||
{
|
||||
@@ -49,7 +55,8 @@ class PasteNumberingImport
|
||||
static void push(QWidget *parent,
|
||||
Diagram *diagram,
|
||||
const DiagramContent &content,
|
||||
const QList<ElementAutoNumSchemeCommand::Scheme> &copied);
|
||||
const QList<ElementAutoNumSchemeCommand::Scheme> &copied,
|
||||
const QList<QUndoCommand *> &extra = QList<QUndoCommand *>());
|
||||
|
||||
static QList<ElementAutoNumSchemeCommand::Scheme> copiedBy(const QDomDocument &clipboard);
|
||||
};
|
||||
|
||||
@@ -138,6 +138,9 @@ void SelectAutonumW::setContext(const NumerotationContext &context)
|
||||
|
||||
updatePartButtons();
|
||||
applyEnable(false);
|
||||
//Everything the rule held before is read now, so nothing of it
|
||||
//is left to write: what is on show is what there is.
|
||||
m_dirty = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,7 +150,11 @@ void SelectAutonumW::setContext(const NumerotationContext &context)
|
||||
*/
|
||||
void SelectAutonumW::insertPartRow(NumPartEditorW *part)
|
||||
{
|
||||
connect(part, &NumPartEditorW::changed, this, [this]() { applyEnable(); });
|
||||
connect(part, &NumPartEditorW::changed, this, [this]() {
|
||||
m_dirty = true;
|
||||
applyEnable();
|
||||
emit contextEdited();
|
||||
});
|
||||
|
||||
PartRow r;
|
||||
r.part = part;
|
||||
@@ -263,7 +270,10 @@ NumerotationContext SelectAutonumW::toNumContext() const
|
||||
void SelectAutonumW::on_add_button_clicked()
|
||||
{
|
||||
insertPartRow(new NumPartEditorW(m_edited_type, this));
|
||||
m_dirty = true;
|
||||
ui -> remove_button -> setEnabled(true);
|
||||
applyEnable();
|
||||
emit contextEdited();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,9 +282,16 @@ void SelectAutonumW::on_add_button_clicked()
|
||||
*/
|
||||
void SelectAutonumW::on_remove_button_clicked()
|
||||
{
|
||||
if (!m_rows.isEmpty()) {
|
||||
//remove if @num_part_list contains more than one item
|
||||
if (num_part_list_.size() > 1) {
|
||||
m_dirty = true;
|
||||
removePartRow(m_rows.last().row);
|
||||
if (num_part_list_.size() == 1) {
|
||||
ui -> remove_button -> setDisabled(true);
|
||||
}
|
||||
}
|
||||
applyEnable();
|
||||
emit contextEdited();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,6 +313,78 @@ QComboBox *SelectAutonumW::contextComboBox() const
|
||||
return ui->m_comboBox;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief SelectAutonumW::setSingleRuleMode
|
||||
@param single true to show this editor as the one and only rule of
|
||||
its kind.
|
||||
|
||||
The row which lists the available numberings -- its label, its combo
|
||||
box and the button which deletes one -- is hidden, so there is
|
||||
nothing to name a rule with and nothing to choose between: the rule
|
||||
being edited is all there is. The combo box itself stays where it is
|
||||
and keeps holding the rule's name, so everything which reads or
|
||||
writes the rule through contextComboBox() carries on untouched; he
|
||||
just cannot see it.
|
||||
*/
|
||||
void SelectAutonumW::setSingleRuleMode(bool single)
|
||||
{
|
||||
m_single_rule = single;
|
||||
ui->label->setVisible(!single);
|
||||
ui->m_comboBox->setVisible(!single);
|
||||
ui->m_remove_pb->setVisible(!single);
|
||||
|
||||
//A single rule has no list to be deleted from, so it needs a
|
||||
//button of its own to get out of the numbering again. It is
|
||||
//only shown when there is really a rule to take away -- see
|
||||
//setRuleRemovable.
|
||||
if (single && !m_rule_remove_pb) {
|
||||
m_rule_remove_pb = new QPushButton(tr("Delete the rule"), this);
|
||||
m_rule_remove_pb->setToolTip(tr(
|
||||
"Delete the cable numbering rule: cables are "
|
||||
"numbered again as before, and the question of "
|
||||
"defining one comes back on the next drawing."));
|
||||
m_rule_remove_pb->setObjectName(QStringLiteral("m_rule_remove_pb"));
|
||||
if (ui->m_definition_groupe && ui->m_definition_groupe->layout()) {
|
||||
ui->m_definition_groupe->layout()->addWidget(m_rule_remove_pb);
|
||||
}
|
||||
connect(m_rule_remove_pb, &QPushButton::clicked,
|
||||
this, &SelectAutonumW::removeClicked);
|
||||
}
|
||||
if (m_rule_remove_pb) {
|
||||
m_rule_remove_pb->setVisible(m_single_rule && m_rule_removable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief SelectAutonumW::setRuleRemovable
|
||||
Whether the button which takes the whole rule away is worth showing:
|
||||
it has nothing to do while this editor holds no rule at all, and it
|
||||
would only be a button which does nothing.
|
||||
@param can_remove true when a rule is there to be taken away
|
||||
*/
|
||||
void SelectAutonumW::setRuleRemovable(bool can_remove)
|
||||
{
|
||||
m_rule_removable = can_remove;
|
||||
if (m_rule_remove_pb) {
|
||||
m_rule_remove_pb->setVisible(m_single_rule && can_remove);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief SelectAutonumW::isValid
|
||||
@return true when there is at least one variable in the editor and
|
||||
every one of them holds what it needs. An editor which has only just
|
||||
been opened holds an empty rule, and an empty rule is not one.
|
||||
*/
|
||||
bool SelectAutonumW::isValid()
|
||||
{
|
||||
if (num_part_list_.isEmpty()) return false;
|
||||
for (NumPartEditorW *npe : num_part_list_) {
|
||||
if (!npe || !npe->isValid()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief SelectAutonumW::on_buttonBox_clicked
|
||||
Action on button clicked
|
||||
@@ -364,6 +453,32 @@ void SelectAutonumW::on_buttonBox_clicked(QAbstractButton *button)
|
||||
));
|
||||
break;
|
||||
}
|
||||
else if (m_edited_type == 3)
|
||||
{
|
||||
QMessageBox::information (
|
||||
this,
|
||||
tr("Cable Auto Numbering",
|
||||
"title window"),
|
||||
tr("This is where you can define how new cables will be numbered.\n"
|
||||
"-A numbering is composed of a minimum variable.\n"
|
||||
"-You can add or delete a dialing variable through the - and + buttons.\n"
|
||||
"-A numbering variable includes: a type, a value and an increment.\n"
|
||||
|
||||
"\n-the \"Digit 1\", \"Digit 01\" and \"Digit 001\" types represent a numeric type defined in the \"Value\" field, "
|
||||
"which is incremented to each new cable by the value of the \"Increment\" field.\n"
|
||||
"-\"Digit 01\" and \"Digit 001\", are respectively represented on the diagram by two and three digits minimum.\n"
|
||||
"If the digit defined in the Value field has fewer digits than the chosen type, it will be preceded by one or two 0s in "
|
||||
"order to respect its type.\n"
|
||||
|
||||
"\n-Type \"Text\", represents a fixed text.\nThe \"Increment\" field is not used.\n"
|
||||
|
||||
"\n-The \"Sheet no.\" type represents the number of the current sheet.\nThe other fields are not used.\n"
|
||||
|
||||
"\n-The \"Sheet\" type represents the name of the current sheet.\nThe other fields are not used.",
|
||||
"help dialog about the cable autonumerotation"
|
||||
));
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::information (
|
||||
@@ -397,6 +512,9 @@ void SelectAutonumW::on_buttonBox_clicked(QAbstractButton *button)
|
||||
case QDialogButtonBox::ApplyRole:
|
||||
applyEnable(false);
|
||||
emit applyPressed();
|
||||
//Whatever was edited is written now, so there is
|
||||
//nothing of it left for a later save to write again.
|
||||
m_dirty = false;
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ class NumPartEditorW;
|
||||
class QAbstractButton;
|
||||
class FormulaAutonumberingW;
|
||||
class QComboBox;
|
||||
class QPushButton;
|
||||
class QToolButton;
|
||||
class QHBoxLayout;
|
||||
|
||||
@@ -54,11 +55,55 @@ class SelectAutonumW : public QWidget
|
||||
void contextToFormula ();
|
||||
QString formula();
|
||||
QComboBox *contextComboBox() const;
|
||||
/**
|
||||
Show this editor as the only numbering rule there is: the
|
||||
row which lets several named numberings be kept and chosen
|
||||
between is hidden, so the rule being edited is the one and
|
||||
only. Everything else -- the definition itself, the buttons
|
||||
around it -- stays exactly as it is for the other kinds.
|
||||
*/
|
||||
void setSingleRuleMode(bool single);
|
||||
bool isSingleRuleMode() const { return m_single_rule; }
|
||||
/**
|
||||
True while everything the editor holds is filled in well
|
||||
enough to be a rule -- no variable left with nothing in it.
|
||||
The Apply button says the same thing; this is so that a page
|
||||
which saves on its own way knows it too, rather than writing
|
||||
a rule nobody has finished filling in.
|
||||
*/
|
||||
bool isValid();
|
||||
/**
|
||||
True while the rule on show has been touched since it was
|
||||
read or since it was last applied: a page which saves on
|
||||
its own way (the cable rule does) writes it only when there
|
||||
is something to write, so that confirming a window with
|
||||
nothing changed in it leaves the project alone.
|
||||
*/
|
||||
bool isModified() const { return m_dirty; }
|
||||
/**
|
||||
Show the button which takes the rule itself away, which is
|
||||
the only way to stop numbering cables automatically again:
|
||||
the row the other numberings delete from is hidden here, so
|
||||
a single rule would have no way out at all. The page behind
|
||||
the editor says whether there is a rule to take away at the
|
||||
moment -- without one the button has nothing to do and is
|
||||
kept out of sight.
|
||||
@param can_remove true when a rule is really there
|
||||
*/
|
||||
void setRuleRemovable(bool can_remove);
|
||||
void setExplicitNaming();
|
||||
|
||||
signals:
|
||||
void applyPressed();
|
||||
void removeClicked();
|
||||
/**
|
||||
Sent whenever what the rule holds is edited -- a
|
||||
field of a part changed, a part added, a part taken
|
||||
away -- so that a window holding this editor can let
|
||||
its own buttons say whether there is something to
|
||||
write now.
|
||||
*/
|
||||
void contextEdited();
|
||||
/// Only with setExplicitNaming()
|
||||
void newClicked();
|
||||
void renameClicked();
|
||||
@@ -96,7 +141,19 @@ class SelectAutonumW : public QWidget
|
||||
NumerotationContext m_context;
|
||||
FormulaAutonumberingW *m_feaw;
|
||||
FormulaAutonumberingW *m_fcaw;
|
||||
int m_edited_type = -1; ///<0 == element : 1 == conductor : 2 == folio
|
||||
int m_edited_type = -1; ///<0 == element : 1 == conductor : 2 == folio : 3 == cable
|
||||
///< when true, the row of available numberings is hidden and
|
||||
///< this editor holds the one rule there is (see setSingleRuleMode)
|
||||
bool m_single_rule = false;
|
||||
///< true from the moment the rule on show is touched until
|
||||
///< it is read again or applied (see isModified)
|
||||
bool m_dirty = false;
|
||||
///< the button which takes the whole rule away, built for
|
||||
///< the one kind of numbering which has no list to delete
|
||||
///< it from (see setRuleRemovable)
|
||||
QPushButton *m_rule_remove_pb = nullptr;
|
||||
///< whether such a rule is there to be taken away
|
||||
bool m_rule_removable = false;
|
||||
};
|
||||
|
||||
#endif // SELECTAUTONUMW_H
|
||||
|
||||
@@ -29,7 +29,9 @@ QString csvField(QString value)
|
||||
return QLatin1Char('"') + value + QLatin1Char('"');
|
||||
}
|
||||
|
||||
QByteArray csvRecord(const QStringList &values)
|
||||
}
|
||||
|
||||
QByteArray BomExport::csvRecord(const QStringList &values)
|
||||
{
|
||||
QStringList escaped;
|
||||
for (const auto &value : values) {
|
||||
@@ -37,7 +39,6 @@ QByteArray csvRecord(const QStringList &values)
|
||||
}
|
||||
return (escaped.join(QLatin1Char(';')) + QLatin1Char('\n')).toUtf8();
|
||||
}
|
||||
}
|
||||
|
||||
QStringList BomExport::defaultColumns()
|
||||
{
|
||||
|
||||
@@ -30,6 +30,11 @@ namespace BomExport
|
||||
QString defaultQuery(bool include_slaves = true,
|
||||
bool include_junctions = true);
|
||||
QString junctionFilter();
|
||||
/// One CSV record: every field quoted, ';' between them, '\n'
|
||||
/// after -- the format every CSV export of this program writes.
|
||||
/// Public so a list which is not read from the project database
|
||||
/// (the cable list) writes the same bytes.
|
||||
QByteArray csvRecord(const QStringList &values);
|
||||
QByteArray toCsv(QSqlQuery &query, const QStringList &headers,
|
||||
bool include_headers = true, int *row_count = nullptr);
|
||||
bool writeCsv(const QString &file_path, const QByteArray &csv,
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
#include "titleblocktemplaterenderer.h"
|
||||
|
||||
|
||||
#include "diagram.h"
|
||||
#include "qetproject.h"
|
||||
#include "dataBase/projectdatabase.h"
|
||||
|
||||
#include <QLocale>
|
||||
#include <QPainter>
|
||||
#include <QRegularExpression>
|
||||
@@ -282,28 +286,98 @@ void BorderTitleBlock::borderFromXml(const QDomElement &xml_elmt) {
|
||||
@return the properties of the titleblock
|
||||
\~French les proprietes du cartouches
|
||||
*/
|
||||
TitleBlockProperties BorderTitleBlock::exportTitleBlock()
|
||||
TitleBlockProperties BorderTitleBlock::exportTitleBlock() const
|
||||
{
|
||||
return current();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BorderTitleBlock::localTitleBlock
|
||||
@return the properties of the titleblock this object holds itself,
|
||||
whether or not its folio is placed in a project
|
||||
*/
|
||||
TitleBlockProperties BorderTitleBlock::localTitleBlock() const
|
||||
{
|
||||
TitleBlockProperties ip;
|
||||
|
||||
ip.author = author();
|
||||
ip.date = date();
|
||||
ip.title = title();
|
||||
ip.filename = fileName();
|
||||
ip.plant = plant();
|
||||
ip.locmach = locmach();
|
||||
ip.indexrev = indexrev();
|
||||
ip.version = version();
|
||||
ip.folio = folio();
|
||||
ip.author = btb_author_;
|
||||
ip.date = btb_date_;
|
||||
ip.title = btb_title_;
|
||||
ip.filename = btb_filename_;
|
||||
ip.plant = btb_plant_;
|
||||
ip.locmach = btb_locmach_;
|
||||
ip.indexrev = btb_indexrev_;
|
||||
ip.version = btb_version_;
|
||||
ip.folio = btb_folio_;
|
||||
ip.template_name = titleBlockTemplateName();
|
||||
ip.display_at = m_edge;
|
||||
ip.auto_page_num = autoPageNum();
|
||||
ip.auto_page_num = btb_auto_page_num_;
|
||||
ip.context = additional_fields_;
|
||||
ip.collection = QET::QetCollection::Embedded;
|
||||
|
||||
return(ip);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BorderTitleBlock::current
|
||||
@return the properties of the titleblock: for a folio placed in a
|
||||
project the project's store holds them (DB-ACCESSORS-PLAN.md stage
|
||||
4.4), for any other this object's own copy.
|
||||
*/
|
||||
TitleBlockProperties BorderTitleBlock::current() const
|
||||
{
|
||||
if (const TitleBlockProperties *s = stored())
|
||||
return *s;
|
||||
return localTitleBlock();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BorderTitleBlock::stored
|
||||
@return the properties the project's store holds for this titleblock's
|
||||
folio, or nullptr if it is not placed. Read at once: the getters use it
|
||||
field by field, as label formulas ask for the folio and title of every
|
||||
symbol's folio, and copying the whole properties each time costs.
|
||||
*/
|
||||
const TitleBlockProperties *BorderTitleBlock::stored() const
|
||||
{
|
||||
if (projectDataBase *store = placedStore())
|
||||
return store->storedFolioTitleBlock(m_owner_folio->uuid());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BorderTitleBlock::setOwnerFolio
|
||||
@param folio : the folio this border and title block belongs to
|
||||
*/
|
||||
void BorderTitleBlock::setOwnerFolio(Diagram *folio)
|
||||
{
|
||||
m_owner_folio = folio;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BorderTitleBlock::placedStore
|
||||
@return the project database holding this titleblock's properties, or
|
||||
nullptr if its folio is not placed in a project
|
||||
*/
|
||||
projectDataBase *BorderTitleBlock::placedStore() const
|
||||
{
|
||||
QETProject *project = m_owner_folio ? m_owner_folio->project() : nullptr;
|
||||
projectDataBase *store = project ? project->dataBase() : nullptr;
|
||||
return store && store->placedFolio(m_owner_folio->uuid()) == m_owner_folio
|
||||
? store : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BorderTitleBlock::storeTitleBlock
|
||||
Write this titleblock's properties to its project's store, once its
|
||||
folio is placed: every change of them comes through here.
|
||||
*/
|
||||
void BorderTitleBlock::storeTitleBlock()
|
||||
{
|
||||
if (projectDataBase *store = placedStore())
|
||||
store->storeFolioTitleBlock(m_owner_folio->uuid(), localTitleBlock());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BorderTitleBlock::importTitleBlock
|
||||
@param ip the new properties of titleblock
|
||||
@@ -333,6 +407,7 @@ void BorderTitleBlock::importTitleBlock(const TitleBlockProperties &ip)
|
||||
// through setFolioData(),
|
||||
// which in turn calls updateDiagramContextForTitleBlock().
|
||||
emit(needTitleBlockTemplate(ip.template_name));
|
||||
storeTitleBlock();
|
||||
emit informationChanged();
|
||||
}
|
||||
|
||||
@@ -391,6 +466,7 @@ void BorderTitleBlock::setTitleBlockTemplate(
|
||||
const TitleBlockTemplate *titleblock_template) {
|
||||
m_titleblock_template_renderer -> setTitleBlockTemplate(
|
||||
titleblock_template);
|
||||
storeTitleBlock();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -889,9 +965,21 @@ DiagramPosition BorderTitleBlock::convertPosition(const QPointF &pos)
|
||||
void BorderTitleBlock::setFolio(const QString &folio)
|
||||
{
|
||||
btb_folio_ = folio;
|
||||
storeTitleBlock();
|
||||
emit (titleBlockFolioChanged(folio));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BorderTitleBlock::setAutoPageNum
|
||||
@param title : the title of the folio numbering this folio follows,
|
||||
nothing else changes
|
||||
*/
|
||||
void BorderTitleBlock::setAutoPageNum(const QString &title)
|
||||
{
|
||||
btb_auto_page_num_ = title;
|
||||
storeTitleBlock();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BorderTitleBlock::updateDiagramContextForTitleBlock
|
||||
Update the informations given to the titleblock template
|
||||
@@ -915,9 +1003,10 @@ void BorderTitleBlock::updateDiagramContextForTitleBlock(
|
||||
// So an empty page-level value is skipped only when a real project-level
|
||||
// one is already there to show through; otherwise it still goes in
|
||||
// empty, which is what makes the placeholder resolve to nothing.
|
||||
const TitleBlockProperties tb = current();
|
||||
DiagramContext context = initial_context;
|
||||
foreach (QString key, additional_fields_.keys()) {
|
||||
const QVariant value = additional_fields_[key];
|
||||
foreach (QString key, tb.context.keys()) {
|
||||
const QVariant value = tb.context[key];
|
||||
if (!value.toString().isEmpty() || !context.contains(key))
|
||||
context.addValue(key, value);
|
||||
}
|
||||
@@ -925,20 +1014,20 @@ void BorderTitleBlock::updateDiagramContextForTitleBlock(
|
||||
// ... overridden by the historical and/or dynamically generated fields
|
||||
QLocale var;
|
||||
var.dateFormat(QLocale::ShortFormat);
|
||||
context.addValue("author", btb_author_);
|
||||
context.addValue("author", tb.author);
|
||||
context.addValue(
|
||||
"date",
|
||||
QLocale::system().toString(btb_date_, QLocale::ShortFormat));
|
||||
context.addValue("title", btb_title_);
|
||||
context.addValue("filename", btb_filename_);
|
||||
context.addValue("plant", btb_plant_);
|
||||
context.addValue("locmach", btb_locmach_);
|
||||
context.addValue("indexrev", btb_indexrev_);
|
||||
context.addValue("version", btb_version_);
|
||||
QLocale::system().toString(tb.date, QLocale::ShortFormat));
|
||||
context.addValue("title", tb.title);
|
||||
context.addValue("filename", tb.filename);
|
||||
context.addValue("plant", tb.plant);
|
||||
context.addValue("locmach", tb.locmach);
|
||||
context.addValue("indexrev", tb.indexrev);
|
||||
context.addValue("version", tb.version);
|
||||
context.addValue("folio", btb_final_folio_);
|
||||
context.addValue("folio-id", folio_index_);
|
||||
context.addValue("folio-total", folio_total_);
|
||||
context.addValue("auto_page_num", btb_auto_page_num_);
|
||||
context.addValue("auto_page_num", tb.auto_page_num);
|
||||
context.addValue("previous-folio-num", m_previous_folio_num);
|
||||
context.addValue("next-folio-num", m_next_folio_num);
|
||||
|
||||
@@ -1041,11 +1130,12 @@ void BorderTitleBlock::setFolioData(
|
||||
|
||||
// regenerate the content of the folio field
|
||||
// regenere le contenu du champ folio
|
||||
btb_final_folio_ = btb_folio_;
|
||||
btb_final_folio_ = folio();
|
||||
|
||||
if (btb_final_folio_.contains("%autonum")){
|
||||
btb_final_folio_.replace("%autonum", autonum);
|
||||
btb_folio_ = btb_final_folio_;
|
||||
storeTitleBlock();
|
||||
}
|
||||
btb_final_folio_.replace("%id", QString::number(folio_index_));
|
||||
btb_final_folio_.replace("%total", QString::number(folio_total_));
|
||||
|
||||
@@ -29,6 +29,8 @@ class QPainter;
|
||||
class DiagramPosition;
|
||||
class TitleBlockTemplate;
|
||||
class TitleBlockTemplateRenderer;
|
||||
class Diagram;
|
||||
class projectDataBase;
|
||||
/**
|
||||
@brief The BorderTitleBlock class
|
||||
This class represents the border and the titleblock which frame a
|
||||
@@ -107,34 +109,36 @@ class BorderTitleBlock : public QObject
|
||||
QRectF outsideBorderRect() const;
|
||||
QRectF insideBorderRect() const;
|
||||
|
||||
// methods to get title block basic data
|
||||
// methods to get title block basic data. A folio placed in a
|
||||
// project reads them from the project's store
|
||||
// (DB-ACCESSORS-PLAN.md stage 4.4), any other from its own copy.
|
||||
/// @return the value of the title block "Author" field
|
||||
QString author() const { return(btb_author_); }
|
||||
QString author() const { const TitleBlockProperties *s = stored(); return(s ? s->author : btb_author_); }
|
||||
/// @return the value of the title block "Date" field
|
||||
QDate date() const { return(btb_date_); }
|
||||
QDate date() const { const TitleBlockProperties *s = stored(); return(s ? s->date : btb_date_); }
|
||||
/// @return the value of the title block "Title" field
|
||||
QString title() const { return(btb_title_); }
|
||||
QString title() const { const TitleBlockProperties *s = stored(); return(s ? s->title : btb_title_); }
|
||||
/// @return the value of the title block "Folio" field
|
||||
QString folio() const { return(btb_folio_); }
|
||||
QString folio() const { const TitleBlockProperties *s = stored(); return(s ? s->folio : btb_folio_); }
|
||||
/// @return the value of the title block "Folio" field as displayed
|
||||
QString finalfolio() const { return(btb_final_folio_); }
|
||||
/// @return the value of the title block "Plant" field
|
||||
QString plant() const { return(btb_plant_); }
|
||||
QString plant() const { const TitleBlockProperties *s = stored(); return(s ? s->plant : btb_plant_); }
|
||||
/// @return the value of the title block "Locmach" field
|
||||
QString locmach() const { return(btb_locmach_); }
|
||||
QString locmach() const { const TitleBlockProperties *s = stored(); return(s ? s->locmach : btb_locmach_); }
|
||||
/// @return the value of the revision index block "Folio" field
|
||||
QString indexrev() const { return(btb_indexrev_); }
|
||||
QString indexrev() const { const TitleBlockProperties *s = stored(); return(s ? s->indexrev : btb_indexrev_); }
|
||||
/// @return the value of the title block "File" field
|
||||
QString fileName() const { return(btb_filename_); }
|
||||
QString fileName() const { const TitleBlockProperties *s = stored(); return(s ? s->filename : btb_filename_); }
|
||||
/// @return the value of the title block Additional Fields
|
||||
QString version() const { return(btb_version_); }
|
||||
QString version() const { const TitleBlockProperties *s = stored(); return(s ? s->version : btb_version_); }
|
||||
/// @return the value of the title block Additional Fields
|
||||
DiagramContext additionalFields() const {
|
||||
return (additional_fields_); }
|
||||
const TitleBlockProperties *s = stored(); return(s ? s->context : additional_fields_); }
|
||||
/// @return the value of the title block
|
||||
QString autoPageNum() const { return(btb_auto_page_num_); }
|
||||
QString autoPageNum() const { const TitleBlockProperties *s = stored(); return(s ? s->auto_page_num : btb_auto_page_num_); }
|
||||
/// The title of the folio numbering this folio follows, nothing else changes
|
||||
void setAutoPageNum(const QString &title) { btb_auto_page_num_ = title; }
|
||||
void setAutoPageNum(const QString &title);
|
||||
/// @return the value of the total number of folios
|
||||
int folioTotal() const { return(folio_total_);}
|
||||
|
||||
@@ -175,7 +179,9 @@ class BorderTitleBlock : public QObject
|
||||
void borderToXml(QDomElement &);
|
||||
void borderFromXml(const QDomElement &);
|
||||
|
||||
TitleBlockProperties exportTitleBlock();
|
||||
TitleBlockProperties exportTitleBlock() const;
|
||||
TitleBlockProperties localTitleBlock() const;
|
||||
void setOwnerFolio(Diagram *folio);
|
||||
void importTitleBlock(const TitleBlockProperties &);
|
||||
BorderProperties exportBorder();
|
||||
void importBorder(const BorderProperties &);
|
||||
@@ -289,5 +295,10 @@ class BorderTitleBlock : public QObject
|
||||
bool display_rows_ = false;
|
||||
bool display_border_ = false;
|
||||
TitleBlockTemplateRenderer *m_titleblock_template_renderer;
|
||||
Diagram *m_owner_folio = nullptr;
|
||||
TitleBlockProperties current() const;
|
||||
const TitleBlockProperties *stored() const;
|
||||
projectDataBase *placedStore() const;
|
||||
void storeTitleBlock();
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
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 "addcablecommand.h"
|
||||
|
||||
#include "cablemanager.h"
|
||||
#include "cablepart.h"
|
||||
#include "../autoNum/numerotationcontextcommands.h"
|
||||
#include "../diagram.h"
|
||||
#include "../qetproject.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
@brief AddCableCommand::AddCableCommand
|
||||
@param cable the cable the line was just given to
|
||||
@param part the line which was just drawn
|
||||
@param diagram the folio it was drawn on
|
||||
@param own_cable true when the cable was created by this very action
|
||||
@param taken the cores this line took away from other cables, so
|
||||
undo can give them back
|
||||
*/
|
||||
AddCableCommand::AddCableCommand(Cable *cable,
|
||||
CablePart *part,
|
||||
Diagram *diagram,
|
||||
bool own_cable,
|
||||
const QList<CableCoreTaken> &taken) :
|
||||
m_cable(cable),
|
||||
m_part(part),
|
||||
m_diagram(diagram),
|
||||
m_taken(taken),
|
||||
m_own_cable(own_cable)
|
||||
{
|
||||
if (!m_cable || !m_part) return;
|
||||
|
||||
m_part_data.uuid = m_part->partUuid();
|
||||
m_part_data.diagram = m_diagram ? m_diagram->uuid() : QUuid();
|
||||
m_part_data.p1 = m_part->firstPoint();
|
||||
m_part_data.p2 = m_part->secondPoint();
|
||||
|
||||
for (const CableCore &core : m_cable->usedCores()) {
|
||||
if (core.part == m_part_data.uuid) {
|
||||
m_cores.append(core);
|
||||
}
|
||||
}
|
||||
|
||||
//A cable this action brought into being takes the next number
|
||||
//the project's numbering rule hands out. The number itself was
|
||||
//worked out before this command existed; what has to happen here
|
||||
//is moving the rule on, and moving it is undoable just like the
|
||||
//cable is -- so undoing gives the number back and the cable
|
||||
//drawn after that takes it again instead of skipping over it.
|
||||
//
|
||||
//Only the rule as it stands now is taken down. Where the rule
|
||||
//stands after this cable is worked out when the cable really
|
||||
//comes into being, because several cables pasted together are
|
||||
//all made before any of them is added, and each of them has to
|
||||
//move the rule on from wherever the one before it left it.
|
||||
if (m_own_cable && m_diagram && project() && project()->hasCableAutoNum())
|
||||
{
|
||||
const QString key = project()->cableCurrentAutoNum();
|
||||
const NumerotationContext current = project()->cableAutoNum(key);
|
||||
if (!current.isEmpty())
|
||||
{
|
||||
m_num_key = key;
|
||||
m_num_before = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief AddCableCommand::~AddCableCommand
|
||||
Whatever the stack leaves behind has to be cleaned up here: a cable
|
||||
which is not held by the project any more belongs to this command,
|
||||
and so does a line which never made it onto a folio.
|
||||
|
||||
The cable is a QPointer, so if another command took it away first it
|
||||
is already null here and there is nothing left to do -- which is
|
||||
exactly the point, since following a raw pointer here meant freeing
|
||||
the same cable a second time.
|
||||
*/
|
||||
AddCableCommand::~AddCableCommand()
|
||||
{
|
||||
if (m_cable && m_own_cable) {
|
||||
QETProject *proj = project();
|
||||
if (!proj || !proj->cables().contains(m_cable.data())) {
|
||||
delete m_cable.data();
|
||||
}
|
||||
}
|
||||
if (m_part && !m_part->scene()) {
|
||||
delete m_part;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief AddCableCommand::project
|
||||
@return the project this command works in, or nullptr
|
||||
*/
|
||||
QETProject *AddCableCommand::project() const
|
||||
{
|
||||
return m_diagram ? m_diagram->project() : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief AddCableCommand::redo
|
||||
Put the line on the folio, together with everything the cable holds
|
||||
for it.
|
||||
*/
|
||||
void AddCableCommand::redo()
|
||||
{
|
||||
QETProject *proj = project();
|
||||
if (!proj || !m_cable || !m_part || !m_diagram) return;
|
||||
|
||||
if (m_own_cable) {
|
||||
proj->addCable(m_cable);
|
||||
} else {
|
||||
if (!m_cable->hasPart(m_part_data.uuid)) {
|
||||
m_cable->addPart(m_part_data);
|
||||
}
|
||||
for (const CableCore &core : m_cores) {
|
||||
m_cable->setCore(core);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_part->scene() != m_diagram) {
|
||||
m_diagram->addItem(m_part);
|
||||
}
|
||||
|
||||
//Take the cores away from the cables which held them before this
|
||||
//line claimed those conductors. On the very first run they are
|
||||
//already gone, so nothing happens then; redoing after an undo
|
||||
//has to take them again.
|
||||
for (const CableCoreTaken &taken : std::as_const(m_taken))
|
||||
{
|
||||
if (taken.cable && taken.cable != m_cable) {
|
||||
taken.cable->removeCore(taken.core.core);
|
||||
}
|
||||
}
|
||||
//The cable field of every conductor is worked out again either
|
||||
//way: redoing after an undo puts the cable back into the project
|
||||
//without it having said anything, and the wires it feeds would go
|
||||
//on showing a blank cable field although the cable which
|
||||
//generates that field is right there again.
|
||||
//
|
||||
//And the number this cable was given is spent now: the rule
|
||||
//moves on to the one the next cable will be handed. Worked out
|
||||
//here rather than when the command was made, because a run of
|
||||
//cables pasted together is made from end to end before any of
|
||||
//it is added -- each of them has to move the rule on from where
|
||||
//the one before it left it.
|
||||
if (!m_num_key.isEmpty()) {
|
||||
NumerotationContextCommands ncc(proj->cableAutoNum(m_num_key),
|
||||
m_diagram.data());
|
||||
proj->addCableAutoNum(m_num_key, ncc.next());
|
||||
proj->setCurrentCableAutoNum(m_num_key);
|
||||
}
|
||||
CableManager::refreshLabels(proj);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief AddCableCommand::undo
|
||||
Take the line back off the folio. An existing cable loses the
|
||||
section and the cores it was carrying on it -- and with them the
|
||||
cable field of the conductors, which CableManager works out again as
|
||||
soon as the cable says it changed.
|
||||
|
||||
The cores this line had taken away from other cables go back to
|
||||
those cables afterwards: without them the conductors they belong to
|
||||
would be left with a blank cable field although the cable which
|
||||
generates that field is still there.
|
||||
*/
|
||||
void AddCableCommand::undo()
|
||||
{
|
||||
QETProject *proj = project();
|
||||
if (!proj || !m_cable || !m_part || !m_diagram) return;
|
||||
|
||||
if (m_part->scene()) {
|
||||
m_diagram->removeItem(m_part);
|
||||
}
|
||||
|
||||
if (m_own_cable) {
|
||||
//The cable keeps everything it knows: it is only taken out of
|
||||
//the project, so redo finds it exactly as it was.
|
||||
proj->removeCable(m_cable);
|
||||
} else {
|
||||
m_cable->removePart(m_part_data.uuid);
|
||||
}
|
||||
|
||||
for (const CableCoreTaken &taken : std::as_const(m_taken))
|
||||
{
|
||||
if (taken.cable && taken.cable != m_cable) {
|
||||
taken.cable->setCore(taken.core);
|
||||
}
|
||||
}
|
||||
//The number this cable was given goes back into the rule, so
|
||||
//the cable drawn after an undo takes that number rather than
|
||||
//the one after it: nothing of it is left spent.
|
||||
if (!m_num_key.isEmpty()) {
|
||||
proj->addCableAutoNum(m_num_key, m_num_before);
|
||||
proj->setCurrentCableAutoNum(m_num_key);
|
||||
}
|
||||
|
||||
//Same as in redo(): worked out again whatever happened above,
|
||||
//so a cable section or a whole cable taken away never leaves
|
||||
//the wires it used to feed with a cable field it still writes.
|
||||
CableManager::refreshLabels(proj);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
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 ADDCABLECOMMAND_H
|
||||
#define ADDCABLECOMMAND_H
|
||||
|
||||
#include "cable.h"
|
||||
|
||||
#include "../autoNum/numerotationcontext.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QPointer>
|
||||
#include <QUndoCommand>
|
||||
|
||||
class CablePart;
|
||||
class Diagram;
|
||||
class QETProject;
|
||||
|
||||
/**
|
||||
@brief Puts a drawn cable line on the folio, or takes it back off.
|
||||
|
||||
Both halves are one single step, because a cable line without its
|
||||
cable is of no use to anybody: undoing has to take the line away and
|
||||
leave nothing behind, and redoing has to put both back exactly as
|
||||
they were.
|
||||
*/
|
||||
class AddCableCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
AddCableCommand(Cable *cable,
|
||||
CablePart *part,
|
||||
Diagram *diagram,
|
||||
bool own_cable,
|
||||
const QList<CableCoreTaken> &taken = QList<CableCoreTaken>());
|
||||
~AddCableCommand() override;
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
QETProject *project() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
The cable the line belongs to.
|
||||
|
||||
A pointer, not a raw one: RemoveCableCommand deletes the
|
||||
cable when it goes, and both of them are torn down together
|
||||
when the stack takes a new command. Whoever of the two runs
|
||||
first frees the cable, so the other one must not follow it
|
||||
into the freed memory -- QPointer goes null instead, and
|
||||
this command then simply has nothing left to clean up.
|
||||
*/
|
||||
QPointer<Cable> m_cable;
|
||||
///The line itself, owned by the folio once it is drawn
|
||||
QPointer<CablePart> m_part;
|
||||
QPointer<Diagram> m_diagram;
|
||||
///Which section of the cable this line is
|
||||
CablePartData m_part_data;
|
||||
///The cores the line was giving to the cable, put back by redo
|
||||
QList<CableCore> m_cores;
|
||||
///The cores this line took away from other cables: redo takes
|
||||
///them again, undo gives them back
|
||||
QList<CableCoreTaken> m_taken;
|
||||
///True when this command brought the cable into existence, so it
|
||||
///is the one which has to take it away again
|
||||
bool m_own_cable = false;
|
||||
/**
|
||||
When this cable took a number out of the project's numbering
|
||||
rule, the rule as it was before it did and as it was after:
|
||||
an empty key means the project numbers its cables by no rule
|
||||
and nothing here applies. Handing the number out is part of
|
||||
bringing the cable into being, so undo has to hand it back --
|
||||
otherwise undoing a cable and drawing it once more would
|
||||
leave a gap in the numbering for good.
|
||||
*/
|
||||
QString m_num_key;
|
||||
NumerotationContext m_num_before;
|
||||
};
|
||||
|
||||
#endif // ADDCABLECOMMAND_H
|
||||
@@ -0,0 +1,921 @@
|
||||
/*
|
||||
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 "cable.h"
|
||||
|
||||
#include <QDomDocument>
|
||||
#include <QDomElement>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
/**
|
||||
@brief The name an alignment is written under in the file, an
|
||||
empty string when the alignment is the plain default (the text
|
||||
ends where it always did, before the line).
|
||||
@param alignment Qt::AlignLeft, Qt::AlignHCenter or Qt::AlignRight
|
||||
@return left, center or right
|
||||
*/
|
||||
QString alignmentName(int alignment)
|
||||
{
|
||||
switch (alignment)
|
||||
{
|
||||
case int(Qt::AlignLeft): return QStringLiteral("left");
|
||||
case int(Qt::AlignHCenter): return QStringLiteral("center");
|
||||
case int(Qt::AlignRight): return QStringLiteral("right");
|
||||
default: return QString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief The alignment a name written in the file stands for.
|
||||
@param name left, center or right
|
||||
@return the Qt flags, 0 when the name says nothing
|
||||
*/
|
||||
int alignmentFromName(const QString &name)
|
||||
{
|
||||
if (name == QLatin1String("left")) return int(Qt::AlignLeft);
|
||||
if (name == QLatin1String("center")) return int(Qt::AlignHCenter);
|
||||
if (name == QLatin1String("right")) return int(Qt::AlignRight);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::Cable
|
||||
@param parent
|
||||
*/
|
||||
Cable::Cable(QObject *parent) :
|
||||
QObject(parent),
|
||||
m_uuid(QUuid::createUuid())
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::uuid
|
||||
@return the identity of the cable, never empty once constructed
|
||||
*/
|
||||
QUuid Cable::uuid() const
|
||||
{
|
||||
return m_uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setUuid
|
||||
Only used while reading a project: a cable keeps the identity it was
|
||||
saved with, so the conductors it feeds stay bound to it.
|
||||
@param uuid
|
||||
*/
|
||||
void Cable::setUuid(const QUuid &uuid)
|
||||
{
|
||||
if (uuid.isNull() || uuid == m_uuid) {
|
||||
return;
|
||||
}
|
||||
m_uuid = uuid;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::designation
|
||||
@return the cable number, e.g. 15W1, which heads the label drawn next
|
||||
to the trunk line and opens every core label
|
||||
*/
|
||||
QString Cable::designation() const
|
||||
{
|
||||
return m_designation;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setDesignation
|
||||
@param designation
|
||||
*/
|
||||
void Cable::setDesignation(const QString &designation)
|
||||
{
|
||||
if (m_designation == designation) return;
|
||||
m_designation = designation;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::designationByHand
|
||||
@return true when the name of this cable was typed in rather than
|
||||
worked out of the numbering rule
|
||||
*/
|
||||
bool Cable::designationByHand() const
|
||||
{
|
||||
return m_designation_by_hand;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setDesignationByHand
|
||||
Says where the name of this cable comes from. Nothing of it is
|
||||
drawn, so it changes no label and asks for no redraw.
|
||||
@param by_hand
|
||||
*/
|
||||
void Cable::setDesignationByHand(bool by_hand)
|
||||
{
|
||||
m_designation_by_hand = by_hand;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::bmk
|
||||
@return the BMK of the cable
|
||||
*/
|
||||
QString Cable::bmk() const
|
||||
{
|
||||
return m_bmk;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setBmk
|
||||
@param bmk
|
||||
*/
|
||||
void Cable::setBmk(const QString &bmk)
|
||||
{
|
||||
if (m_bmk == bmk) return;
|
||||
m_bmk = bmk;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::installation
|
||||
@return the installation (Anlage) the cable belongs to
|
||||
*/
|
||||
QString Cable::installation() const
|
||||
{
|
||||
return m_installation;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setInstallation
|
||||
@param installation
|
||||
*/
|
||||
void Cable::setInstallation(const QString &installation)
|
||||
{
|
||||
if (m_installation == installation) return;
|
||||
m_installation = installation;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::location
|
||||
@return the location (Ort) the cable runs through
|
||||
*/
|
||||
QString Cable::location() const
|
||||
{
|
||||
return m_location;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setLocation
|
||||
@param location
|
||||
*/
|
||||
void Cable::setLocation(const QString &location)
|
||||
{
|
||||
if (m_location == location) return;
|
||||
m_location = location;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::length
|
||||
@return the length of the cable as it is written down: a free text,
|
||||
because it may come from the drawing or be typed in by hand
|
||||
*/
|
||||
QString Cable::length() const
|
||||
{
|
||||
return m_length;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setLength
|
||||
@param length
|
||||
*/
|
||||
void Cable::setLength(const QString &length)
|
||||
{
|
||||
if (m_length == length) return;
|
||||
m_length = length;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::properties
|
||||
@return every field the user fills in, together with what of it he
|
||||
want to see drawn next to the trunk line
|
||||
*/
|
||||
CableProperties Cable::properties() const
|
||||
{
|
||||
CableProperties properties;
|
||||
properties.designation = m_designation;
|
||||
properties.designation_by_hand = m_designation_by_hand;
|
||||
properties.type = m_type;
|
||||
properties.installation = m_installation;
|
||||
properties.location = m_location;
|
||||
properties.length = m_length;
|
||||
properties.show_designation = m_show_designation;
|
||||
properties.show_type = m_show_type;
|
||||
properties.show_installation = m_show_installation;
|
||||
properties.show_location = m_show_location;
|
||||
properties.show_length = m_show_length;
|
||||
properties.texts = m_texts;
|
||||
properties.text_offset = m_text_offset;
|
||||
properties.core_offset = m_core_offset;
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setProperties
|
||||
Write a whole set of fields at once -- one undo step covers it, and
|
||||
the label next to the trunk line and the conductors it feeds are
|
||||
worked out again once, not ten times.
|
||||
@param properties
|
||||
*/
|
||||
void Cable::setProperties(const CableProperties &properties)
|
||||
{
|
||||
if (properties == this->properties()) return;
|
||||
|
||||
m_designation = properties.designation;
|
||||
m_designation_by_hand = properties.designation_by_hand;
|
||||
m_type = properties.type;
|
||||
m_installation = properties.installation;
|
||||
m_location = properties.location;
|
||||
m_length = properties.length;
|
||||
m_show_designation = properties.show_designation;
|
||||
m_show_type = properties.show_type;
|
||||
m_show_installation = properties.show_installation;
|
||||
m_show_location = properties.show_location;
|
||||
m_show_length = properties.show_length;
|
||||
m_texts = properties.texts;
|
||||
m_text_offset = properties.text_offset;
|
||||
m_core_offset = properties.core_offset;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::type
|
||||
@return the type of the cable, e.g. H07RN-F 4G1,5. The type is copied
|
||||
from the cable type file when the cable is created, so a project
|
||||
opens even when that file is gone or has changed since.
|
||||
*/
|
||||
QString Cable::type() const
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setType
|
||||
@param type
|
||||
*/
|
||||
void Cable::setType(const QString &type)
|
||||
{
|
||||
if (m_type == type) return;
|
||||
m_type = type;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::coreCount
|
||||
@return how many cores the type of this cable has. This is only the
|
||||
size of the type, not the number of cores actually wired: see
|
||||
usedCoreCount().
|
||||
*/
|
||||
int Cable::coreCount() const
|
||||
{
|
||||
return m_core_count;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setCoreCount
|
||||
@param count
|
||||
*/
|
||||
void Cable::setCoreCount(int count)
|
||||
{
|
||||
count = qMax(0, count);
|
||||
if (m_core_count == count) return;
|
||||
m_core_count = count;
|
||||
|
||||
//A type with fewer cores takes the cores it no longer has away
|
||||
//from the conductors which showed them: the label of a core the
|
||||
//cable doesn't own would be a lie.
|
||||
for (int i = m_cores.size() - 1; i >= 0; --i) {
|
||||
if (m_cores.at(i).core >= m_core_count) {
|
||||
m_cores.removeAt(i);
|
||||
}
|
||||
}
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::coreColors
|
||||
@return the colour of each core, in the order of the cable type file
|
||||
*/
|
||||
QStringList Cable::coreColors() const
|
||||
{
|
||||
return m_core_colors;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setCoreColors
|
||||
@param colors
|
||||
*/
|
||||
void Cable::setCoreColors(const QStringList &colors)
|
||||
{
|
||||
if (m_core_colors == colors) return;
|
||||
m_core_colors = colors;
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::typeState
|
||||
@return everything the type of this cable decides, taken in one
|
||||
block: changing the type works on that block, and undo puts the very
|
||||
same block back -- name, size, colours and the cores which go with
|
||||
them, so no mark can be left standing on a core the cable lost.
|
||||
*/
|
||||
CableTypeState Cable::typeState() const
|
||||
{
|
||||
CableTypeState state;
|
||||
state.type = m_type;
|
||||
state.core_count = m_core_count;
|
||||
state.core_colors = m_core_colors;
|
||||
state.cores = m_cores;
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::applyTypeState
|
||||
Put back a state taken with typeState().
|
||||
|
||||
The count comes before the cores: a cable which has fewer cores than
|
||||
it had cannot hold the others, and setCores() drops what stands
|
||||
beyond the count -- which is exactly what a type with fewer cores
|
||||
means, and exactly what undo has to take back.
|
||||
@param state
|
||||
*/
|
||||
void Cable::applyTypeState(const CableTypeState &state)
|
||||
{
|
||||
setType(state.type);
|
||||
setCoreColors(state.core_colors);
|
||||
setCoreCount(state.core_count);
|
||||
setCores(state.cores);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::usedCores
|
||||
@return every core wired to a conductor, ordered by core number
|
||||
*/
|
||||
QList<CableCore> Cable::usedCores() const
|
||||
{
|
||||
QList<CableCore> cores = m_cores;
|
||||
std::sort(cores.begin(), cores.end(),
|
||||
[](const CableCore &a, const CableCore &b) {return a.core < b.core;});
|
||||
return cores;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::hasCore
|
||||
@param core
|
||||
@return true when that core is wired to a conductor
|
||||
*/
|
||||
bool Cable::hasCore(int core) const
|
||||
{
|
||||
for (const CableCore &c : m_cores) {
|
||||
if (c.core == core) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::core
|
||||
@param core
|
||||
@return the assignment of that core, an empty one when it is free
|
||||
*/
|
||||
CableCore Cable::core(int core) const
|
||||
{
|
||||
for (const CableCore &c : m_cores) {
|
||||
if (c.core == core) return c;
|
||||
}
|
||||
return CableCore();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setCore
|
||||
Wire a core to a conductor, or move the slash of an already wired
|
||||
core. A core number may only appear once, so the previous holder of
|
||||
that number, if any, is replaced.
|
||||
@param core
|
||||
*/
|
||||
void Cable::setCore(const CableCore &core)
|
||||
{
|
||||
if (core.core < 0 || core.core >= m_core_count) return;
|
||||
|
||||
for (int i = 0; i < m_cores.size(); ++i) {
|
||||
if (m_cores.at(i).core == core.core) {
|
||||
if (m_cores.at(i).conductor == core.conductor
|
||||
&& m_cores.at(i).part == core.part
|
||||
&& m_cores.at(i).position == core.position) {
|
||||
return;
|
||||
}
|
||||
m_cores[i] = core;
|
||||
emit changed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_cores.append(core);
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setCores
|
||||
Write a whole set of assignments in place of the current one, the way
|
||||
undoing a dragged slash has to: every core which was moved, swapped
|
||||
or dropped comes back at once.
|
||||
@param cores
|
||||
*/
|
||||
void Cable::setCores(const QList<CableCore> &cores)
|
||||
{
|
||||
m_cores = cores;
|
||||
for (int i = m_cores.size() - 1; i >= 0; --i) {
|
||||
if (m_cores.at(i).core < 0 || m_cores.at(i).core >= m_core_count) {
|
||||
m_cores.removeAt(i);
|
||||
}
|
||||
}
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::removeCore
|
||||
Wire a core back to nothing: its conductor stops showing the label
|
||||
this cable gave it.
|
||||
@param core
|
||||
*/
|
||||
void Cable::removeCore(int core)
|
||||
{
|
||||
for (int i = 0; i < m_cores.size(); ++i) {
|
||||
if (m_cores.at(i).core == core) {
|
||||
m_cores.removeAt(i);
|
||||
emit changed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::usedCoreCount
|
||||
@return how many cores of this cable are wired to a conductor
|
||||
*/
|
||||
int Cable::usedCoreCount() const
|
||||
{
|
||||
return m_cores.size();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::freeCoreCount
|
||||
@return how many cores the type offers and no conductor shows yet
|
||||
*/
|
||||
int Cable::freeCoreCount() const
|
||||
{
|
||||
return qMax(0, m_core_count - m_cores.size());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::firstFreeCore
|
||||
@return the lowest core number which is free, -1 when every core of
|
||||
the type is already wired
|
||||
*/
|
||||
int Cable::firstFreeCore() const
|
||||
{
|
||||
for (int i = 0; i < m_core_count; ++i) {
|
||||
if (!hasCore(i)) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::colorOfCore
|
||||
@param core
|
||||
@return the colour this core has in the cable type file, an empty
|
||||
string when the type doesn't describe that many cores
|
||||
*/
|
||||
QString Cable::colorOfCore(int core) const
|
||||
{
|
||||
return m_core_colors.value(core);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::labelOfCore
|
||||
The text written into the cable field of the conductor this core is
|
||||
wired to. It is never typed by hand: it is worked out here, from the
|
||||
cable and the core, so renaming the cable or changing its type
|
||||
updates every conductor at once.
|
||||
@param core
|
||||
@return e.g. "15W1:br"
|
||||
*/
|
||||
QString Cable::labelOfCore(int core) const
|
||||
{
|
||||
if (m_designation.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
const QString color = colorOfCore(core);
|
||||
if (color.isEmpty()) {
|
||||
return m_designation;
|
||||
}
|
||||
return m_designation + QLatin1Char(':') + color;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::parts
|
||||
@return every drawn section of this cable, whatever folio it sits on
|
||||
*/
|
||||
QList<CablePartData> Cable::parts() const
|
||||
{
|
||||
return m_parts;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::hasPart
|
||||
@param part
|
||||
@return true when the cable has a section with that identity
|
||||
*/
|
||||
bool Cable::hasPart(const QUuid &part) const
|
||||
{
|
||||
if (part.isNull()) return false;
|
||||
for (const CablePartData &p : m_parts) {
|
||||
if (p.uuid == part) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::part
|
||||
@param part
|
||||
@return the section with that identity, an invalid one when there is none
|
||||
*/
|
||||
CablePartData Cable::part(const QUuid &part) const
|
||||
{
|
||||
for (const CablePartData &p : m_parts) {
|
||||
if (p.uuid == part) return p;
|
||||
}
|
||||
return CablePartData();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::addPart
|
||||
@param part
|
||||
*/
|
||||
void Cable::addPart(const CablePartData &part)
|
||||
{
|
||||
if (part.uuid.isNull() || hasPart(part.uuid)) return;
|
||||
m_parts.append(part);
|
||||
emit changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::removePart
|
||||
Also drops the cores drawn on that section, since their slash and
|
||||
their colour label live with it.
|
||||
@param part
|
||||
*/
|
||||
void Cable::removePart(const QUuid &part)
|
||||
{
|
||||
bool touched = false;
|
||||
for (int i = m_parts.size() - 1; i >= 0; --i) {
|
||||
if (m_parts.at(i).uuid == part) {
|
||||
m_parts.removeAt(i);
|
||||
touched = true;
|
||||
}
|
||||
}
|
||||
for (int i = m_cores.size() - 1; i >= 0; --i) {
|
||||
if (m_cores.at(i).part == part) {
|
||||
m_cores.removeAt(i);
|
||||
touched = true;
|
||||
}
|
||||
}
|
||||
if (touched) {
|
||||
emit changed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setPartGeometry
|
||||
Moving or lengthening the trunk line is a purely optical change: no
|
||||
core is added, none is removed, nothing is reassigned. Only the line
|
||||
itself moves -- which is why this never emits changed(): the labels
|
||||
the cable writes into the conductors do not depend on it.
|
||||
@param part
|
||||
@param p1
|
||||
@param p2
|
||||
*/
|
||||
void Cable::setPartGeometry(const QUuid &part, const QPointF &p1, const QPointF &p2)
|
||||
{
|
||||
for (int i = 0; i < m_parts.size(); ++i) {
|
||||
if (m_parts.at(i).uuid != part) continue;
|
||||
if (m_parts.at(i).p1 == p1 && m_parts.at(i).p2 == p2) return;
|
||||
m_parts[i].p1 = p1;
|
||||
m_parts[i].p2 = p2;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::setPartDiagram
|
||||
@param part
|
||||
@param diagram
|
||||
*/
|
||||
void Cable::setPartDiagram(const QUuid &part, const QUuid &diagram)
|
||||
{
|
||||
for (int i = 0; i < m_parts.size(); ++i) {
|
||||
if (m_parts.at(i).uuid != part) continue;
|
||||
if (m_parts.at(i).diagram == diagram) return;
|
||||
m_parts[i].diagram = diagram;
|
||||
emit changed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::movePartCores
|
||||
Carry the colour labels of one section along when that section is
|
||||
moved as a whole: when a copy is put down, when it is stamped a grid
|
||||
way beside the original, and when the line travels with the rest of
|
||||
a selection rather than on its own.
|
||||
|
||||
Quiet on purpose. Where a slash stands says nothing about the text
|
||||
a conductor shows, so there is nothing to work out again -- and this
|
||||
runs while items travel under the mouse, where one full pass over
|
||||
every wire of the project would cost more than the movement itself.
|
||||
@param part the section which moved
|
||||
@param delta how far it came, in scene coordinates
|
||||
*/
|
||||
void Cable::movePartCores(const QUuid &part, const QPointF &delta)
|
||||
{
|
||||
if (part.isNull() || delta.isNull()) return;
|
||||
|
||||
for (CableCore &core : m_cores)
|
||||
{
|
||||
if (core.part == part) {
|
||||
core.position += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::toXml
|
||||
@param document
|
||||
@return the <cable> element holding every part and every wired core
|
||||
*/
|
||||
QDomElement Cable::toXml(QDomDocument &document) const
|
||||
{
|
||||
QDomElement root = document.createElement(QLatin1String("cable"));
|
||||
root.setAttribute(QLatin1String("uuid"), m_uuid.toString());
|
||||
if (!m_designation.isEmpty()) {
|
||||
root.setAttribute(QLatin1String("designation"), m_designation);
|
||||
}
|
||||
//Only a name which was typed in says so: the ones the numbering
|
||||
//rule handed out are the ordinary case and need no marking
|
||||
if (m_designation_by_hand) {
|
||||
root.setAttribute(QLatin1String("designation_by_hand"), QLatin1String("1"));
|
||||
}
|
||||
if (!m_bmk.isEmpty()) {
|
||||
root.setAttribute(QLatin1String("bmk"), m_bmk);
|
||||
}
|
||||
if (!m_installation.isEmpty()) {
|
||||
root.setAttribute(QLatin1String("installation"), m_installation);
|
||||
}
|
||||
if (!m_location.isEmpty()) {
|
||||
root.setAttribute(QLatin1String("location"), m_location);
|
||||
}
|
||||
if (!m_length.isEmpty()) {
|
||||
root.setAttribute(QLatin1String("length"), m_length);
|
||||
}
|
||||
//Only what was switched off is written: a cable written before
|
||||
//these existed shows everything, like the ones written from now
|
||||
if (!m_show_designation) {
|
||||
root.setAttribute(QLatin1String("show_designation"), QLatin1String("0"));
|
||||
}
|
||||
if (!m_show_type) {
|
||||
root.setAttribute(QLatin1String("show_type"), QLatin1String("0"));
|
||||
}
|
||||
if (!m_show_installation) {
|
||||
root.setAttribute(QLatin1String("show_installation"), QLatin1String("0"));
|
||||
}
|
||||
if (!m_show_location) {
|
||||
root.setAttribute(QLatin1String("show_location"), QLatin1String("0"));
|
||||
}
|
||||
if (!m_show_length) {
|
||||
root.setAttribute(QLatin1String("show_length"), QLatin1String("0"));
|
||||
}
|
||||
if (!m_type.isEmpty()) {
|
||||
root.setAttribute(QLatin1String("type"), m_type);
|
||||
}
|
||||
if (m_core_count > 0) {
|
||||
root.setAttribute(QLatin1String("cores"), QString::number(m_core_count));
|
||||
}
|
||||
|
||||
if (!m_core_colors.isEmpty())
|
||||
{
|
||||
QDomElement colors = document.createElement(QLatin1String("colors"));
|
||||
for (const QString &color : m_core_colors) {
|
||||
QDomElement e = document.createElement(QLatin1String("color"));
|
||||
e.appendChild(document.createTextNode(color));
|
||||
colors.appendChild(e);
|
||||
}
|
||||
root.appendChild(colors);
|
||||
}
|
||||
|
||||
//Only the texts this cable does not write the way the preferences
|
||||
//say are stored, so a cable stays silent as long as it merely
|
||||
//follows the default -- and a later change of that default still
|
||||
//reaches it. The nudges are kept in the same element: they say
|
||||
//where those texts stand rather than what the cable is.
|
||||
if (!m_texts.isEmpty() || !m_text_offset.isNull() || !m_core_offset.isNull())
|
||||
{
|
||||
QDomElement texts = document.createElement(QLatin1String("texts"));
|
||||
bool stored = false;
|
||||
for (auto it = m_texts.cbegin(); it != m_texts.cend(); ++it)
|
||||
{
|
||||
const CableTextFormat &format = it.value();
|
||||
if (format.font.isEmpty() && format.alignment == 0) {
|
||||
continue;
|
||||
}
|
||||
QDomElement e = document.createElement(QLatin1String("text"));
|
||||
e.setAttribute(QLatin1String("key"), it.key());
|
||||
if (!format.font.isEmpty()) {
|
||||
e.setAttribute(QLatin1String("font"), format.font);
|
||||
}
|
||||
const QString align = alignmentName(format.alignment);
|
||||
if (!align.isEmpty()) {
|
||||
e.setAttribute(QLatin1String("align"), align);
|
||||
}
|
||||
texts.appendChild(e);
|
||||
stored = true;
|
||||
}
|
||||
if (!m_text_offset.isNull())
|
||||
{
|
||||
texts.setAttribute(QLatin1String("text_dx"),
|
||||
QString::number(m_text_offset.x(), 'f', 2));
|
||||
texts.setAttribute(QLatin1String("text_dy"),
|
||||
QString::number(m_text_offset.y(), 'f', 2));
|
||||
stored = true;
|
||||
}
|
||||
if (!m_core_offset.isNull())
|
||||
{
|
||||
texts.setAttribute(QLatin1String("core_dx"),
|
||||
QString::number(m_core_offset.x(), 'f', 2));
|
||||
texts.setAttribute(QLatin1String("core_dy"),
|
||||
QString::number(m_core_offset.y(), 'f', 2));
|
||||
stored = true;
|
||||
}
|
||||
if (stored) {
|
||||
root.appendChild(texts);
|
||||
}
|
||||
}
|
||||
|
||||
for (const CablePartData &part : m_parts)
|
||||
{
|
||||
QDomElement e = document.createElement(QLatin1String("part"));
|
||||
e.setAttribute(QLatin1String("uuid"), part.uuid.toString());
|
||||
e.setAttribute(QLatin1String("diagram"), part.diagram.toString());
|
||||
e.setAttribute(QLatin1String("x1"), QString::number(part.p1.x(), 'f', 2));
|
||||
e.setAttribute(QLatin1String("y1"), QString::number(part.p1.y(), 'f', 2));
|
||||
e.setAttribute(QLatin1String("x2"), QString::number(part.p2.x(), 'f', 2));
|
||||
e.setAttribute(QLatin1String("y2"), QString::number(part.p2.y(), 'f', 2));
|
||||
root.appendChild(e);
|
||||
}
|
||||
|
||||
for (const CableCore &core : usedCores())
|
||||
{
|
||||
QDomElement e = document.createElement(QLatin1String("core"));
|
||||
e.setAttribute(QLatin1String("index"), QString::number(core.core));
|
||||
if (!core.part.isNull()) {
|
||||
e.setAttribute(QLatin1String("part"), core.part.toString());
|
||||
}
|
||||
if (!core.conductor.isNull()) {
|
||||
e.setAttribute(QLatin1String("conductor"), core.conductor.toString());
|
||||
}
|
||||
e.setAttribute(QLatin1String("x"), QString::number(core.position.x(), 'f', 2));
|
||||
e.setAttribute(QLatin1String("y"), QString::number(core.position.y(), 'f', 2));
|
||||
root.appendChild(e);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Cable::fromXml
|
||||
@param element a <cable> element
|
||||
*/
|
||||
void Cable::fromXml(const QDomElement &element)
|
||||
{
|
||||
m_uuid = QUuid(element.attribute(QLatin1String("uuid")));
|
||||
if (m_uuid.isNull()) {
|
||||
m_uuid = QUuid::createUuid();
|
||||
}
|
||||
m_designation = element.attribute(QLatin1String("designation"));
|
||||
//A file written before this existed holds names the program
|
||||
//gave, which is what not marking them means
|
||||
m_designation_by_hand = element.attribute(
|
||||
QLatin1String("designation_by_hand")) == QLatin1String("1");
|
||||
m_bmk = element.attribute(QLatin1String("bmk"));
|
||||
m_installation = element.attribute(QLatin1String("installation"));
|
||||
m_location = element.attribute(QLatin1String("location"));
|
||||
m_length = element.attribute(QLatin1String("length"));
|
||||
m_show_designation = element.attribute(
|
||||
QLatin1String("show_designation"), QLatin1String("1")).toInt() != 0;
|
||||
m_show_type = element.attribute(
|
||||
QLatin1String("show_type"), QLatin1String("1")).toInt() != 0;
|
||||
m_show_installation = element.attribute(
|
||||
QLatin1String("show_installation"), QLatin1String("1")).toInt() != 0;
|
||||
m_show_location = element.attribute(
|
||||
QLatin1String("show_location"), QLatin1String("1")).toInt() != 0;
|
||||
m_show_length = element.attribute(
|
||||
QLatin1String("show_length"), QLatin1String("1")).toInt() != 0;
|
||||
m_type = element.attribute(QLatin1String("type"));
|
||||
m_core_count = qMax(0, element.attribute(QLatin1String("cores"), QLatin1String("0")).toInt());
|
||||
|
||||
m_core_colors.clear();
|
||||
const QDomElement colors = element.firstChildElement(QLatin1String("colors"));
|
||||
for (QDomElement e = colors.firstChildElement(QLatin1String("color"));
|
||||
!e.isNull();
|
||||
e = e.nextSiblingElement(QLatin1String("color"))) {
|
||||
m_core_colors.append(e.text());
|
||||
}
|
||||
|
||||
//The texts this cable writes its own way; everything not in here
|
||||
//keeps following what the preferences say.
|
||||
m_texts.clear();
|
||||
m_text_offset = QPointF();
|
||||
m_core_offset = QPointF();
|
||||
const QDomElement texts = element.firstChildElement(QLatin1String("texts"));
|
||||
for (QDomElement e = texts.firstChildElement(QLatin1String("text"));
|
||||
!e.isNull();
|
||||
e = e.nextSiblingElement(QLatin1String("text")))
|
||||
{
|
||||
const QString key = e.attribute(QLatin1String("key"));
|
||||
if (key.isEmpty()) continue;
|
||||
CableTextFormat format;
|
||||
format.font = e.attribute(QLatin1String("font"));
|
||||
format.alignment = alignmentFromName(e.attribute(QLatin1String("align")));
|
||||
if (format.font.isEmpty() && format.alignment == 0) continue;
|
||||
m_texts.insert(key, format);
|
||||
}
|
||||
if (!texts.isNull())
|
||||
{
|
||||
m_text_offset.setX(texts.attribute(QLatin1String("text_dx")).toDouble());
|
||||
m_text_offset.setY(texts.attribute(QLatin1String("text_dy")).toDouble());
|
||||
m_core_offset.setX(texts.attribute(QLatin1String("core_dx")).toDouble());
|
||||
m_core_offset.setY(texts.attribute(QLatin1String("core_dy")).toDouble());
|
||||
}
|
||||
|
||||
m_parts.clear();
|
||||
for (QDomElement e = element.firstChildElement(QLatin1String("part"));
|
||||
!e.isNull();
|
||||
e = e.nextSiblingElement(QLatin1String("part")))
|
||||
{
|
||||
CablePartData part;
|
||||
part.uuid = QUuid(e.attribute(QLatin1String("uuid")));
|
||||
if (part.uuid.isNull()) {
|
||||
continue;
|
||||
}
|
||||
part.diagram = QUuid(e.attribute(QLatin1String("diagram")));
|
||||
part.p1 = QPointF(e.attribute(QLatin1String("x1")).toDouble(),
|
||||
e.attribute(QLatin1String("y1")).toDouble());
|
||||
part.p2 = QPointF(e.attribute(QLatin1String("x2")).toDouble(),
|
||||
e.attribute(QLatin1String("y2")).toDouble());
|
||||
m_parts.append(part);
|
||||
}
|
||||
|
||||
m_cores.clear();
|
||||
for (QDomElement e = element.firstChildElement(QLatin1String("core"));
|
||||
!e.isNull();
|
||||
e = e.nextSiblingElement(QLatin1String("core")))
|
||||
{
|
||||
CableCore core;
|
||||
core.core = e.attribute(QLatin1String("index"), QLatin1String("-1")).toInt();
|
||||
if (core.core < 0) {
|
||||
continue;
|
||||
}
|
||||
core.part = QUuid(e.attribute(QLatin1String("part")));
|
||||
core.conductor = QUuid(e.attribute(QLatin1String("conductor")));
|
||||
core.position = QPointF(e.attribute(QLatin1String("x")).toDouble(),
|
||||
e.attribute(QLatin1String("y")).toDouble());
|
||||
if (hasCore(core.core)) {
|
||||
continue;
|
||||
}
|
||||
m_cores.append(core);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
/*
|
||||
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 CABLE_H
|
||||
#define CABLE_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QPointF>
|
||||
#include <QStringList>
|
||||
#include <QUuid>
|
||||
|
||||
class QDomDocument;
|
||||
class QDomElement;
|
||||
|
||||
/**
|
||||
@brief One core of a cable which is wired to a conductor of the drawing.
|
||||
|
||||
A cable owns its assignment: this is the only place in QElectroTech
|
||||
which knows that core number 3 of cable 15W1 lands on such a
|
||||
conductor. The conductor itself only carries the text the assignment
|
||||
generates (15W1:br), written back by CableManager::refreshLabels(),
|
||||
so the text can never drift away from the cable it comes from.
|
||||
*/
|
||||
struct CableCore
|
||||
{
|
||||
/// Core number inside the cable, 0 based, from 0 to coreCount()-1
|
||||
int core = -1;
|
||||
/// The part carrying the slash drawn for this core
|
||||
QUuid part;
|
||||
/// The conductor this core lands on, null when the core is free
|
||||
QUuid conductor;
|
||||
/// Where the slash sits, in the coordinates of its own part
|
||||
QPointF position;
|
||||
|
||||
///Two assignments are the same when they say the same thing, so
|
||||
///undo can tell a gesture which changed something from one which
|
||||
///merely put the slash back where it already was
|
||||
friend bool operator==(const CableCore &a, const CableCore &b)
|
||||
{
|
||||
return a.core == b.core && a.part == b.part
|
||||
&& a.conductor == b.conductor && a.position == b.position;
|
||||
}
|
||||
friend bool operator!=(const CableCore &a, const CableCore &b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@brief One drawn section of a cable: the trunk line of one folio.
|
||||
|
||||
The geometry is kept as plain data rather than as a pointer to the
|
||||
graphics item, because the folio owns the item and deletes it when the
|
||||
folio is deleted, while the cable outlives it (it may have another
|
||||
part on another folio).
|
||||
*/
|
||||
struct CablePartData
|
||||
{
|
||||
QUuid uuid;
|
||||
QUuid diagram;
|
||||
QPointF p1;
|
||||
QPointF p2;
|
||||
|
||||
bool isHorizontal() const {return qFuzzyCompare(p1.y(), p2.y());}
|
||||
};
|
||||
|
||||
/**
|
||||
@brief How one of the texts of a cable is written.
|
||||
|
||||
Both fields may say nothing at all, and that is the normal case:
|
||||
the cable then writes that text the way the preferences offer it
|
||||
(QETApp::cableTextsFont() and QETApp::cableCoreFont()), so picking
|
||||
another default in the preferences reaches every cable which has
|
||||
not been given a font of its own.
|
||||
|
||||
The texts of a cable are kept in one hash rather than one pair of
|
||||
members each, so that a new text can be added without dragging a
|
||||
new field through the whole code. They are named the way the file
|
||||
writes them:
|
||||
"designation", "type", "installation", "location", "length" for the
|
||||
five texts written next to the trunk line, and "cores" for the
|
||||
colour labels of the cores -- which have no alignment, being turned
|
||||
by a quarter along their own line.
|
||||
*/
|
||||
struct CableTextFormat
|
||||
{
|
||||
///The font, in the stable description QETUtils::fontToString()
|
||||
///writes; empty when this cable follows the preferences
|
||||
QString font;
|
||||
///Qt::AlignLeft, Qt::AlignHCenter or Qt::AlignRight; 0 when the
|
||||
///text goes on ending where it always did, before the line
|
||||
int alignment = 0;
|
||||
|
||||
bool operator==(const CableTextFormat &other) const
|
||||
{
|
||||
return font == other.font && alignment == other.alignment;
|
||||
}
|
||||
bool operator!=(const CableTextFormat &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Everything about a cable the user may type in or tick, and
|
||||
whether what he typed is written on the sheet at all.
|
||||
|
||||
It is kept as a whole so that one undo step can put a whole edit
|
||||
back, and so the selection panel and the dialog always offer exactly
|
||||
the same fields in the same order.
|
||||
*/
|
||||
struct CableProperties
|
||||
{
|
||||
///Name of the cable, e.g. 15W1 -- the head of the label
|
||||
QString designation;
|
||||
/**
|
||||
Where that name comes from: false while the program worked
|
||||
it out of the numbering rule, true from the moment it was
|
||||
typed in. Nothing shows it and no field edits it -- it
|
||||
travels with the fields so that undo puts it back with them,
|
||||
and so that numbering the whole project over again knows
|
||||
which names were never the rule's to begin with and may be
|
||||
left alone.
|
||||
*/
|
||||
bool designation_by_hand = false;
|
||||
///Kind of cable, e.g. H07RN-F 4G1,5
|
||||
QString type;
|
||||
///Installation the cable belongs to
|
||||
QString installation;
|
||||
///Place the cable runs through
|
||||
QString location;
|
||||
///Length, typed in by hand
|
||||
QString length;
|
||||
|
||||
///What of that is written next to the trunk line of the drawing
|
||||
bool show_designation = true;
|
||||
bool show_type = true;
|
||||
bool show_installation = true;
|
||||
bool show_location = true;
|
||||
bool show_length = true;
|
||||
|
||||
///How each text is written -- font and alignment, keyed by the
|
||||
///names listed on CableTextFormat. A text which is not in here
|
||||
///follows what the preferences say, which is where a cable
|
||||
///freshly drawn stands for all of them.
|
||||
QHash<QString, CableTextFormat> texts;
|
||||
|
||||
///How far the label at the left of the trunk line is nudged from
|
||||
///where it has always stood. It is a nudge on the drawing only,
|
||||
///and it moves the whole label as one thing: installation, place
|
||||
///and designation over the line, the type and the length under
|
||||
///it -- while the line, the slashes and the wiring keep standing
|
||||
///exactly where they were.
|
||||
QPointF text_offset;
|
||||
///How far the row of colours is nudged from the slashes it names.
|
||||
///Again on the drawing only: the slashes, the entries they mark
|
||||
///and every wire behind them stay where they are, and so does the
|
||||
///row itself when the trunk line is moved.
|
||||
QPointF core_offset;
|
||||
|
||||
bool operator==(const CableProperties &other) const
|
||||
{
|
||||
return designation == other.designation
|
||||
&& designation_by_hand == other.designation_by_hand
|
||||
&& type == other.type
|
||||
&& installation == other.installation
|
||||
&& location == other.location
|
||||
&& length == other.length
|
||||
&& show_designation == other.show_designation
|
||||
&& show_type == other.show_type
|
||||
&& show_installation == other.show_installation
|
||||
&& show_location == other.show_location
|
||||
&& show_length == other.show_length
|
||||
&& texts == other.texts
|
||||
&& text_offset == other.text_offset
|
||||
&& core_offset == other.core_offset;
|
||||
}
|
||||
bool operator!=(const CableProperties &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Everything about a cable which its type decides, taken as one
|
||||
block so that changing the type is one single step.
|
||||
|
||||
The cores go with the type rather than with the text fields: taking a
|
||||
type with fewer cores takes those cores off the drawing, and undoing
|
||||
has to bring their marks back along with the name and the colours.
|
||||
*/
|
||||
struct CableTypeState
|
||||
{
|
||||
///Kind of cable, e.g. H07RN-F 4G1,5
|
||||
QString type;
|
||||
///How many cores that type has
|
||||
int core_count = 0;
|
||||
///The colour of each of them, in core order
|
||||
QStringList core_colors;
|
||||
///The cores the cable really holds at this moment
|
||||
QList<CableCore> cores;
|
||||
|
||||
bool operator==(const CableTypeState &other) const
|
||||
{
|
||||
return type == other.type
|
||||
&& core_count == other.core_count
|
||||
&& core_colors == other.core_colors
|
||||
&& cores == other.cores;
|
||||
}
|
||||
bool operator!=(const CableTypeState &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@brief The Cable class is the project wide object a drawn cable line
|
||||
stands for.
|
||||
|
||||
It carries what the user fills in -- the type, the number, BMK,
|
||||
installation, location -- together with what QElectroTech works out:
|
||||
how many cores the type has, which colour each core has, which cores
|
||||
are wired to which conductor, and which folios the cable runs on.
|
||||
|
||||
Properties belong to the cable, never to one of its parts: editing
|
||||
the type or the number anywhere changes it for every part.
|
||||
*/
|
||||
class Cable : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Cable(QObject *parent = nullptr);
|
||||
|
||||
//Identity
|
||||
QUuid uuid() const;
|
||||
void setUuid(const QUuid &uuid);
|
||||
|
||||
//Properties the user fills in
|
||||
QString designation() const;
|
||||
void setDesignation(const QString &designation);
|
||||
/**
|
||||
True when the name of this cable was typed in rather
|
||||
than worked out of the numbering rule: numbering the
|
||||
whole project over asks before it takes such a name.
|
||||
*/
|
||||
bool designationByHand() const;
|
||||
void setDesignationByHand(bool by_hand);
|
||||
QString bmk() const;
|
||||
void setBmk(const QString &bmk);
|
||||
QString installation() const;
|
||||
void setInstallation(const QString &installation);
|
||||
QString location() const;
|
||||
void setLocation(const QString &location);
|
||||
QString length() const;
|
||||
void setLength(const QString &length);
|
||||
|
||||
///Every field of the cable, and what of it is drawn on the sheet
|
||||
CableProperties properties() const;
|
||||
void setProperties(const CableProperties &properties);
|
||||
|
||||
//Type, copied from the cable type file when the cable is created
|
||||
QString type() const;
|
||||
void setType(const QString &type);
|
||||
int coreCount() const;
|
||||
void setCoreCount(int count);
|
||||
QStringList coreColors() const;
|
||||
void setCoreColors(const QStringList &colors);
|
||||
///Everything the type of this cable decides, taken as one block
|
||||
CableTypeState typeState() const;
|
||||
///Put back a state taken with typeState(), in one single pass
|
||||
void applyTypeState(const CableTypeState &state);
|
||||
|
||||
//Cores
|
||||
QList<CableCore> usedCores() const;
|
||||
bool hasCore(int core) const;
|
||||
CableCore core(int core) const;
|
||||
void setCore(const CableCore &core);
|
||||
///Write a whole set of assignments in place of another
|
||||
void setCores(const QList<CableCore> &cores);
|
||||
void removeCore(int core);
|
||||
int usedCoreCount() const;
|
||||
int freeCoreCount() const;
|
||||
///The lowest core number which is free, -1 when none is
|
||||
int firstFreeCore() const;
|
||||
///The colour of a core, as written in the cable type file
|
||||
QString colorOfCore(int core) const;
|
||||
///The text a conductor of this core shows, e.g. "15W1:br"
|
||||
QString labelOfCore(int core) const;
|
||||
|
||||
//Parts
|
||||
QList<CablePartData> parts() const;
|
||||
bool hasPart(const QUuid &part) const;
|
||||
CablePartData part(const QUuid &part) const;
|
||||
void addPart(const CablePartData &part);
|
||||
void removePart(const QUuid &part);
|
||||
void setPartGeometry(const QUuid &part, const QPointF &p1, const QPointF &p2);
|
||||
void setPartDiagram(const QUuid &part, const QUuid &diagram);
|
||||
///Carry the colour labels of one section along when that
|
||||
///section is moved as a whole (paste, duplicate, a selection
|
||||
///being moved with everything else on it)
|
||||
void movePartCores(const QUuid &part, const QPointF &delta);
|
||||
|
||||
//Serialisation
|
||||
QDomElement toXml(QDomDocument &document) const;
|
||||
void fromXml(const QDomElement &element);
|
||||
|
||||
signals:
|
||||
void changed();
|
||||
|
||||
private:
|
||||
QUuid m_uuid;
|
||||
QString m_designation;
|
||||
///< where that name comes from (see designationByHand)
|
||||
bool m_designation_by_hand = false;
|
||||
QString m_bmk;
|
||||
QString m_installation;
|
||||
QString m_location;
|
||||
QString m_length;
|
||||
QString m_type;
|
||||
int m_core_count = 0;
|
||||
QStringList m_core_colors;
|
||||
QList<CableCore> m_cores;
|
||||
QList<CablePartData> m_parts;
|
||||
///What the label next to the trunk line is allowed to show
|
||||
bool m_show_designation = true;
|
||||
bool m_show_type = true;
|
||||
bool m_show_installation = true;
|
||||
bool m_show_location = true;
|
||||
bool m_show_length = true;
|
||||
///The texts this cable writes its own way, by the names
|
||||
///listed on CableTextFormat; everything not in here follows
|
||||
///what the preferences say
|
||||
QHash<QString, CableTextFormat> m_texts;
|
||||
///How far the label at the left of the line and the row of
|
||||
///colours are nudged on the drawing: distances nothing else
|
||||
///in the project ever looks at
|
||||
QPointF m_text_offset;
|
||||
QPointF m_core_offset;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief One core which was taken away from another cable.
|
||||
|
||||
A conductor belongs to one cable only, so drawing a second cable
|
||||
across wires a first one already holds takes those cores away from
|
||||
it. That has to be remembered where it is decided, so that undoing
|
||||
the second cable gives the first one its cores back instead of
|
||||
leaving those conductors with a blank cable field forever.
|
||||
*/
|
||||
struct CableCoreTaken
|
||||
{
|
||||
///The cable the core was taken from; null when it is gone
|
||||
QPointer<Cable> cable;
|
||||
///The core exactly as the other cable held it
|
||||
CableCore core;
|
||||
};
|
||||
|
||||
#endif // CABLE_H
|
||||
@@ -0,0 +1,454 @@
|
||||
/*
|
||||
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 "cablecopy.h"
|
||||
|
||||
#include "cablemanager.h"
|
||||
#include "cablepart.h"
|
||||
|
||||
#include "../autoNum/assignvariables.h"
|
||||
#include "../autoNum/numerotationcontext.h"
|
||||
#include "../diagram.h"
|
||||
#include "../qetgraphicsitem/conductor.h"
|
||||
#include "../qetproject.h"
|
||||
|
||||
#include <QDomDocument>
|
||||
#include <QSet>
|
||||
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
@brief CableCopy::write
|
||||
Puts the cable lines selected on @a diagram into @a document, so a
|
||||
copy carries them the way it carries the elements and the texts of
|
||||
the same selection.
|
||||
|
||||
Only the line which was picked is written, together with the whole
|
||||
cable it belongs to: the copy needs everything the cable knows, but
|
||||
only the one section it is standing on.
|
||||
@param diagram the folio being copied
|
||||
@param document the fragment the clipboard is about to hold
|
||||
*/
|
||||
void CableCopy::write(Diagram *diagram, QDomDocument &document)
|
||||
{
|
||||
if (!diagram) return;
|
||||
QDomElement root = document.documentElement();
|
||||
if (root.isNull()) return;
|
||||
|
||||
QList<CablePart *> parts;
|
||||
for (QGraphicsItem *item : diagram->selectedItems()) {
|
||||
if (auto *part = qgraphicsitem_cast<CablePart *>(item)) {
|
||||
parts << part;
|
||||
}
|
||||
}
|
||||
if (parts.isEmpty()) return;
|
||||
|
||||
QDomElement block = document.createElement(QStringLiteral("cables"));
|
||||
bool anything = false;
|
||||
for (CablePart *part : std::as_const(parts))
|
||||
{
|
||||
Cable *cable = part->cable();
|
||||
if (!cable) continue;
|
||||
|
||||
QDomElement element = cable->toXml(document);
|
||||
const QPointF p1 = part->firstPoint();
|
||||
const QPointF p2 = part->secondPoint();
|
||||
element.setAttribute(QStringLiteral("line"), part->partUuid().toString());
|
||||
element.setAttribute(QStringLiteral("x1"), QString::number(p1.x(), 'f', 2));
|
||||
element.setAttribute(QStringLiteral("y1"), QString::number(p1.y(), 'f', 2));
|
||||
element.setAttribute(QStringLiteral("x2"), QString::number(p2.x(), 'f', 2));
|
||||
element.setAttribute(QStringLiteral("y2"), QString::number(p2.y(), 'f', 2));
|
||||
block.appendChild(element);
|
||||
anything = true;
|
||||
}
|
||||
if (anything) root.appendChild(block);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableCopy::read
|
||||
Builds a cable of its own for every line @a root carries, each of
|
||||
them standing exactly where the fragment says it stood.
|
||||
|
||||
The cable keeps everything the source cable knows -- type,
|
||||
installation, place, length, how many cores, which colours -- and it
|
||||
also keeps where the colour labels of the copied section stand, so a
|
||||
copy arrives as a whole: onto a folio which holds no wire at all,
|
||||
into an empty part of a sheet, wherever. What it does not keep is
|
||||
any wire: a copy may not take a wire away from the cable it was
|
||||
copied from, not even for as long as it is being built, so every
|
||||
core is let go while the cable still answers to the source's
|
||||
identity -- no conductor of the source is ever repointed, not even
|
||||
for the length of one signal.
|
||||
|
||||
Which wire each carried core belongs to is worked out later, in
|
||||
CableCopy::wire(), because that depends on where the line has
|
||||
finally been put -- and a copy put where there is no wire simply
|
||||
keeps its labels, to be wired when the line is pulled into place.
|
||||
@param diagram the folio which is about to hold those lines
|
||||
@param root the <diagram> element of a copy fragment
|
||||
@return the lines built, not yet wired
|
||||
*/
|
||||
QList<CablePart *> CableCopy::read(Diagram *diagram, const QDomElement &root)
|
||||
{
|
||||
QList<CablePart *> built;
|
||||
if (!diagram || !diagram->project()) return built;
|
||||
|
||||
const QDomElement block = root.firstChildElement(QStringLiteral("cables"));
|
||||
if (block.isNull()) return built;
|
||||
|
||||
QETProject *project = diagram->project();
|
||||
//One rule for the whole run. Every cable of a paste takes a
|
||||
//number from it and steps it on, so several cables pasted at
|
||||
//once do not all end up under the same number -- although the
|
||||
//project's own rule does not move until those cables really
|
||||
//exist, which is where the commands which add them take over.
|
||||
//A counter bound to the folio starts the folio the copies are
|
||||
//pasted onto at its own beginning, once for the whole run.
|
||||
NumerotationContext rule = autonum::resetFolioCounters(
|
||||
project->cableAutoNum(project->cableCurrentAutoNum()));
|
||||
for (QDomElement element = block.firstChildElement(QStringLiteral("cable"));
|
||||
!element.isNull();
|
||||
element = element.nextSiblingElement(QStringLiteral("cable")))
|
||||
{
|
||||
if (element.attribute(QStringLiteral("x1")).isEmpty()) continue;
|
||||
|
||||
Cable *cable = project->newCable();
|
||||
cable->fromXml(element);
|
||||
|
||||
//The section of the copy, standing where the section it was
|
||||
//copied from stood: the caller moves it to its final place,
|
||||
//and the colour labels come with it (CablePart::itemChange)
|
||||
CablePartData own;
|
||||
own.uuid = QUuid::createUuid();
|
||||
own.diagram = diagram->uuid();
|
||||
own.p1 = QPointF(element.attribute(QStringLiteral("x1")).toDouble(),
|
||||
element.attribute(QStringLiteral("y1")).toDouble());
|
||||
own.p2 = QPointF(element.attribute(QStringLiteral("x2")).toDouble(),
|
||||
element.attribute(QStringLiteral("y2")).toDouble());
|
||||
|
||||
//The colour labels of that one section, carried over whole
|
||||
//and with every wire taken away
|
||||
const QUuid source_part(element.attribute(QStringLiteral("line")));
|
||||
QList<CableCore> carried;
|
||||
for (const CableCore &core : cable->usedCores())
|
||||
{
|
||||
if (core.part != source_part) continue;
|
||||
|
||||
CableCore copy = core;
|
||||
copy.conductor = QUuid();
|
||||
copy.part = own.uuid;
|
||||
carried.append(copy);
|
||||
}
|
||||
//Goes first, while the copy still answers to the source's
|
||||
//identity: that way nothing of the source is left pointing
|
||||
//at a cable which is about to become somebody else
|
||||
cable->setCores(carried);
|
||||
|
||||
//The copy is a cable of its own: nothing of the source's
|
||||
//identity, nothing of its sections and nothing of the wires
|
||||
//it wires may be taken over, only what the user filled in
|
||||
for (const CablePartData &other : cable->parts()) {
|
||||
cable->removePart(other.uuid);
|
||||
}
|
||||
cable->setUuid(QUuid::createUuid());
|
||||
//The copy is numbered by the rule like every other cable.
|
||||
//A project which has no rule gets no number invented for it,
|
||||
//and then what the copy was copied from keeps standing there
|
||||
//rather than being wiped out by an empty one.
|
||||
const QString designation =
|
||||
CableManager::nextDesignation(project, diagram, rule);
|
||||
if (!designation.isEmpty()) {
|
||||
cable->setDesignation(designation);
|
||||
//The copy is numbered by the rule, so whatever the
|
||||
//source was called by hand is not carried over
|
||||
cable->setDesignationByHand(false);
|
||||
}
|
||||
cable->addPart(own);
|
||||
|
||||
auto *item = new CablePart(own);
|
||||
item->setCable(cable);
|
||||
diagram->addItem(item);
|
||||
built << item;
|
||||
}
|
||||
return built;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableCopy::wire
|
||||
Give the cores of every line in @a parts the wire each of them
|
||||
stands on now that the line is where it is going to stay.
|
||||
|
||||
A copy arrives whole: it carries the colour labels it was copied
|
||||
with, and this is the moment they are matched against the wires of
|
||||
the new place. Matching never moves a label: each one goes on
|
||||
standing exactly where it was carried, and the wire it stands on --
|
||||
within reach, as everywhere else -- is the one which takes the cable
|
||||
field. A label standing on no wire at all simply goes on
|
||||
standing there -- that is what makes a copy useful onto a folio
|
||||
which holds no wire yet: the line is there, with its colours, ready
|
||||
to be pulled into place, and pulling it there wires it
|
||||
(CablePart::settleCores).
|
||||
|
||||
A piece of wire belongs to one cable only, and a copy never takes
|
||||
one away: the wires of the folio it lands on which another cable
|
||||
already describes are left to that cable, nothing is asked about
|
||||
them and nothing is written for them. The wires which came along
|
||||
with the copy itself are different -- they are new wires on this
|
||||
folio, carrying the cable field they had back on the folio they
|
||||
come from, where they used to be one of that cable's own wires. If
|
||||
that were still believed here, every one of them would look like a
|
||||
wire another cable holds and the copy would be refused all of them,
|
||||
arriving without a single entry although it is standing on wires of
|
||||
its own. So they are let go of first, exactly like read() lets go
|
||||
of the wires the cores used to name.
|
||||
@param diagram the folio the lines are on
|
||||
@param parts the lines built by CableCopy::read()
|
||||
@param fresh the wires which came with this copy
|
||||
@return one entry per line which is going to stay, with what it
|
||||
had to give back to other cables, ready to be put on the undo stack
|
||||
*/
|
||||
QList<CableCopy::Wired> CableCopy::wire(Diagram *diagram,
|
||||
const QList<CablePart *> &parts,
|
||||
const QList<Conductor *> &fresh)
|
||||
{
|
||||
QList<Wired> wired;
|
||||
if (!diagram || !diagram->project()) {
|
||||
for (CablePart *part : parts) discard(diagram, part);
|
||||
return wired;
|
||||
}
|
||||
QETProject *project = diagram->project();
|
||||
|
||||
//A wire which came with this copy is a new wire here: what it
|
||||
//says about a cable describes the folio it was copied from.
|
||||
//Done by identity rather than by position, because the wire it
|
||||
//was copied from may well stand in this very place -- and that
|
||||
//one goes on belonging to its own cable.
|
||||
for (Conductor *conductor : fresh)
|
||||
{
|
||||
if (!conductor) continue;
|
||||
if (conductor->properties().m_cable_uuid.isNull()) continue;
|
||||
|
||||
|
||||
conductor->setCableReference(QString(), QUuid(), -1);
|
||||
}
|
||||
|
||||
for (CablePart *part : parts)
|
||||
{
|
||||
Cable *cable = part ? part->cable() : nullptr;
|
||||
if (!cable) {
|
||||
discard(diagram, part);
|
||||
continue;
|
||||
}
|
||||
|
||||
//One wire a colour label is dropped on. Two labels may
|
||||
//not be given the same wire, so each one is handed out
|
||||
//once.
|
||||
struct Offered
|
||||
{
|
||||
int core;
|
||||
Conductor *conductor;
|
||||
};
|
||||
QList<Offered> offered;
|
||||
QSet<QUuid> wires;
|
||||
|
||||
const QLineF line(part->firstPoint(), part->secondPoint());
|
||||
const QList<CableCrossing> crossings = CableManager::crossedConductors(diagram, line);
|
||||
for (const CableCore &core : cable->usedCores())
|
||||
{
|
||||
if (core.part != part->partUuid()) continue;
|
||||
if (!core.conductor.isNull()) continue;
|
||||
|
||||
//The wire this label stands on, worked out the way it
|
||||
//is worked out everywhere else (CablePart::wireFor):
|
||||
//the nearest place this line runs across a wire, close
|
||||
//enough for the slash and the wire to read as one
|
||||
//thing. The label itself is not moved to reach it --
|
||||
//a copy arrives with its colours where they were
|
||||
//carried, and no gesture moves one of them, so here
|
||||
//too the entry follows the label rather than the
|
||||
//label being sent off to find a wire. When that wire
|
||||
//has already been handed to another label of the same
|
||||
//copy, the next nearest one this label stands on wins,
|
||||
//rather than leaving it with no wire although one is
|
||||
//under it.
|
||||
QList<CableCrossing> open = crossings;
|
||||
Conductor *conductor = nullptr;
|
||||
while (true)
|
||||
{
|
||||
Conductor *nearest = part->wireFor(open, core, core.position);
|
||||
if (!nearest) break;
|
||||
if (!wires.contains(nearest->uuid())) {
|
||||
conductor = nearest;
|
||||
break;
|
||||
}
|
||||
for (int i = open.size() - 1; i >= 0; --i) {
|
||||
if (open.at(i).conductor == nearest) open.removeAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (!conductor) continue;
|
||||
|
||||
//A wire another cable already describes stays with that
|
||||
//cable. Taking it away would empty a cable behind the
|
||||
//user's back, and asking about it on every paste turned
|
||||
//putting a copy into an existing drawing into a question
|
||||
//to answer first -- so a copy simply takes the wires
|
||||
//nobody holds, and its colour labels standing on the
|
||||
//others name nothing at all. An entry follows its label,
|
||||
//and here it comes to a stop. He can give such a label
|
||||
//one of those wires himself afterwards, by dragging it
|
||||
//there, which is where the question is still asked.
|
||||
const QUuid holder_uuid = conductor->properties().m_cable_uuid;
|
||||
if (!holder_uuid.isNull() && holder_uuid != cable->uuid())
|
||||
{
|
||||
const Cable *previous = CableManager::cableByUuid(project, holder_uuid);
|
||||
if (previous
|
||||
&& previous->hasCore(conductor->properties().m_cable_slot))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
wires.insert(conductor->uuid());
|
||||
Offered item;
|
||||
item.core = core.core;
|
||||
item.conductor = conductor;
|
||||
offered.append(item);
|
||||
}
|
||||
|
||||
QList<Bound> bound;
|
||||
for (const Offered &item : std::as_const(offered))
|
||||
{
|
||||
//A wire is one piece of wire: whichever cable held it
|
||||
//before has to let it go.
|
||||
CableManager::claimConductor(project, item.conductor, cable);
|
||||
|
||||
//A cable binds a conductor by its identity, so a
|
||||
//conductor read back from a file written before it had a
|
||||
//persistent one is given one to keep from now on.
|
||||
item.conductor->setUuid(item.conductor->uuid());
|
||||
|
||||
CableCore core = cable->core(item.core);
|
||||
core.conductor = item.conductor->uuid();
|
||||
cable->setCore(core);
|
||||
|
||||
Bound bind;
|
||||
bind.core = item.core;
|
||||
bind.conductor = item.conductor;
|
||||
bound.append(bind);
|
||||
}
|
||||
|
||||
Wired wired_line;
|
||||
wired_line.part = part;
|
||||
wired_line.bound = bound;
|
||||
wired.append(wired_line);
|
||||
}
|
||||
return wired;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableCopy::repoint
|
||||
Work out again which wire each core of @a wired is on, using the
|
||||
identity the wire has right now.
|
||||
|
||||
A copy is put on the undo stack just after it was wired, and the
|
||||
first run of that stack renews the identity of every conductor it
|
||||
pasted (PasteDiagramCommand::redo): a copy must not keep the
|
||||
identity of the conductor it was copied from. The cores of the copy
|
||||
would then go on naming an identity which no longer exists, and the
|
||||
next working out of the labels (CableManager::refreshLabels) would
|
||||
find nothing there and take the cable field away from those wires.
|
||||
|
||||
So the cores are pointed at the very same wires again, with their
|
||||
new identity, and the cable fields are written out from the cables
|
||||
one more time afterwards: what the copy was given as it was put
|
||||
down still names the identity its wires had a moment earlier, and
|
||||
on a folio where those wires have their own new identity that
|
||||
leaves them without an entry although the copy is standing on them.
|
||||
Nothing moves and nothing is asked: an untouched copy is not
|
||||
changed by any of this at all.
|
||||
@param wired the lines CableCopy::wire() just gave wires to
|
||||
*/
|
||||
void CableCopy::repoint(const QList<Wired> &wired)
|
||||
{
|
||||
for (const Wired &line : wired)
|
||||
{
|
||||
Cable *cable = line.part ? line.part->cable() : nullptr;
|
||||
if (!cable) continue;
|
||||
|
||||
for (const Bound &bind : line.bound)
|
||||
{
|
||||
if (!bind.conductor) continue;
|
||||
|
||||
const QUuid now = bind.conductor->uuid();
|
||||
const CableCore core = cable->core(bind.core);
|
||||
if (core.core != bind.core || core.conductor == now) continue;
|
||||
|
||||
|
||||
CableCore updated = core;
|
||||
updated.conductor = now;
|
||||
cable->setCore(updated);
|
||||
}
|
||||
}
|
||||
|
||||
//With every core naming the identity its wire really has now,
|
||||
//the cable fields are written out from the cables once more:
|
||||
//this is what puts the entries a copy was given as it was put
|
||||
//down onto the wires it has actually landed on.
|
||||
Diagram *folio = nullptr;
|
||||
for (const Wired &line : wired) {
|
||||
if (line.part && line.part->diagram()) {
|
||||
folio = line.part->diagram();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (folio && folio->project()) {
|
||||
CableManager::refreshLabels(folio->project());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableCopy::discard
|
||||
Take a line away again whole -- off the folio, out of the project,
|
||||
and deleted -- without putting anything on the undo stack, because
|
||||
nothing really happened: the user said no to the question above.
|
||||
|
||||
Whatever the cable said while it was being built must not be left
|
||||
behind either: the project has to go on claiming exactly what it
|
||||
claimed before, or it would ask to be saved although nothing was
|
||||
changed at all.
|
||||
@param diagram the folio it was built on
|
||||
@param part the line to take away, may be null
|
||||
*/
|
||||
void CableCopy::discard(Diagram *diagram, CablePart *part)
|
||||
{
|
||||
if (!part) return;
|
||||
Cable *cable = part->cable();
|
||||
QETProject *project = diagram ? diagram->project() : nullptr;
|
||||
|
||||
if (part->scene()) part->scene()->removeItem(part);
|
||||
|
||||
if (cable && project && project->cables().contains(cable))
|
||||
{
|
||||
const bool options_modified = project->projectOptionsWereModified();
|
||||
project->removeCable(cable);
|
||||
project->setModified(options_modified);
|
||||
}
|
||||
|
||||
delete part;
|
||||
delete cable;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
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 CABLECOPY_H
|
||||
#define CABLECOPY_H
|
||||
|
||||
#include "cable.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QPointer>
|
||||
|
||||
class CablePart;
|
||||
class Conductor;
|
||||
class Diagram;
|
||||
class QDomDocument;
|
||||
class QDomElement;
|
||||
class QUndoCommand;
|
||||
|
||||
/**
|
||||
@brief Carrying a drawn cable line over into a copy -- through the
|
||||
clipboard (Ctrl+C then Ctrl+V) or as a duplicate (Ctrl+D).
|
||||
|
||||
What travels is the line and everything which made the cable what it
|
||||
was: its type, its installation, the place it runs through, its
|
||||
length, how many cores it has, which colours those have -- and where
|
||||
the colour labels of the copied section stand, so a copy arrives as
|
||||
a whole even onto a folio which holds no wire at all. What does not
|
||||
travel is which wire each core is on: a copy is a cable of its own
|
||||
with a number of its own, and it takes the wire each colour label
|
||||
happens to stand on at the place it is put -- those of the folio it
|
||||
lands on which no other cable describes. A wire another cable
|
||||
already holds is left to that cable, and a label put where there is
|
||||
no wire goes on standing there, to be wired when the line is pulled
|
||||
into place.
|
||||
|
||||
A cable line is not one of the folio's items (the cable owns it),
|
||||
so Diagram::toXml() never writes it: the block travels beside the
|
||||
folio's own XML rather than inside it, and Diagram::fromXml() reads
|
||||
it back only when its caller asks to be handed the lines it built.
|
||||
*/
|
||||
namespace CableCopy
|
||||
{
|
||||
///Put the cable lines selected on @a diagram into @a document
|
||||
void write(Diagram *diagram, QDomDocument &document);
|
||||
|
||||
///Build new cables of their own for the lines @a root holds,
|
||||
///at the coordinates the fragment carries, with the colour
|
||||
///labels of the copied section carried over as they were.
|
||||
///Nothing is asked and no wire is taken yet: that needs the
|
||||
///place the lines have really been put at, which only the
|
||||
///caller knows
|
||||
QList<CablePart *> read(Diagram *diagram, const QDomElement &root);
|
||||
|
||||
///One core of a line, and the wire it was given
|
||||
struct Bound
|
||||
{
|
||||
int core = -1;
|
||||
Conductor *conductor = nullptr;
|
||||
};
|
||||
|
||||
///One line which was built, with what it had to take away
|
||||
struct Wired
|
||||
{
|
||||
QPointer<CablePart> part;
|
||||
QList<CableCoreTaken> taken;
|
||||
///The wires the colour labels of this line were given,
|
||||
///so they can be pointed at them again afterwards
|
||||
QList<Bound> bound;
|
||||
};
|
||||
|
||||
///Give each colour label of a line the wire it stands on now
|
||||
///that the line is where it will stay.
|
||||
///
|
||||
///@a fresh lists the wires which came along with the copy
|
||||
///itself. They are new wires on this folio: the cable field
|
||||
///they carry describes the folio they were copied from, where
|
||||
///they used to be one of that cable's own wires -- and carried
|
||||
///over, that reference would make every one of them look like
|
||||
///a wire some other cable holds, so the copy would be refused
|
||||
///all of them and arrive without a single entry although it is
|
||||
///standing on wires of its own. They are let go of first, the
|
||||
///same way read() lets go of the wires the cores used to name.
|
||||
///
|
||||
///A wire the folio already held, which another cable
|
||||
///describes, is never taken away from that cable: nothing is
|
||||
///asked and nothing is written for it, and the colour label
|
||||
///standing on it simply names nothing until the user drags it
|
||||
///onto a free wire himself, which is where the question is
|
||||
///still asked. A label standing on no wire at all goes on
|
||||
///standing where it is, to be wired when the line is pulled
|
||||
///into place.
|
||||
QList<Wired> wire(Diagram *diagram,
|
||||
const QList<CablePart *> &parts,
|
||||
const QList<Conductor *> &fresh);
|
||||
|
||||
///Point the cores @a wired lists at the wires they were given,
|
||||
///again: putting the copy on the undo stack may have renewed
|
||||
///the identity of a pasted conductor (PasteDiagramCommand::redo),
|
||||
///and a cable binds a wire by its identity. Nothing is asked and
|
||||
///nothing moves here. The cable fields are written out once more
|
||||
///afterwards, from the cables themselves, so the entries a copy
|
||||
///was given as it was put down are on the wires it really
|
||||
///landed on rather than on the identity they had one moment
|
||||
///earlier.
|
||||
void repoint(const QList<Wired> &wired);
|
||||
|
||||
///Take a built line back off the folio without leaving a trace
|
||||
///in the undo history, for a copy which was refused
|
||||
void discard(Diagram *diagram, CablePart *part);
|
||||
}
|
||||
|
||||
#endif // CABLECOPY_H
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
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 CABLECREATEDIALOG_H
|
||||
#define CABLECREATEDIALOG_H
|
||||
|
||||
#include "../cablelist/cabletypelist.h"
|
||||
#include "cable.h"
|
||||
#include "cablemanager.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QList>
|
||||
#include <QPointer>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
class Diagram;
|
||||
class QCheckBox;
|
||||
class QDialogButtonBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QStandardItem;
|
||||
class QStandardItemModel;
|
||||
class QTableView;
|
||||
class QTabWidget;
|
||||
class QWidget;
|
||||
class QPushButton;
|
||||
|
||||
/**
|
||||
@brief Filter keeping the rows the user is looking for.
|
||||
|
||||
Every word he types has to be found somewhere in the row, whatever
|
||||
the column it sits in, the same way the material file is searched.
|
||||
On top of that it can hide the rows which are of no use to the cable
|
||||
being drawn -- the checkbox each of the two tabs carries.
|
||||
*/
|
||||
class CableFilterProxy : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static constexpr int UnsuitableRole = Qt::UserRole + 1;
|
||||
|
||||
explicit CableFilterProxy(QObject *parent = nullptr);
|
||||
|
||||
void setTokens(const QStringList &tokens);
|
||||
void setHideUnsuitable(bool hide);
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||
|
||||
private:
|
||||
QStringList m_tokens;
|
||||
bool m_hide_unsuitable = true;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief The small window filling in one line of the cable type file.
|
||||
|
||||
It is the cable counterpart of the window the material list opens to
|
||||
add one of its own articles: one field per column of the file, in
|
||||
file order, and a column the program doesn't know still gets its
|
||||
field since it belongs to the user rather than to QElectroTech. The
|
||||
two columns a cable type is read by get the shape they need -- a
|
||||
number which counts, and colours written the way the file separates
|
||||
them.
|
||||
*/
|
||||
class CableTypeEntryDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
@param columns the columns of the cable type file, in file
|
||||
order
|
||||
@param parent
|
||||
*/
|
||||
explicit CableTypeEntryDialog(const QStringList &columns, QWidget *parent = nullptr);
|
||||
|
||||
///The line the form describes, empty fields left out
|
||||
CableTypeRecord record() const;
|
||||
/**
|
||||
Fill the form with a line which is already in the file,
|
||||
for the window where that line is edited rather than
|
||||
added: the window then says so, and every field carries
|
||||
what the file holds about that column.
|
||||
@param values the line as it stands in the file
|
||||
*/
|
||||
void setValues(const CableTypeRecord &values);
|
||||
|
||||
void accept() override;
|
||||
|
||||
private:
|
||||
QStringList m_columns;
|
||||
///One field per column, in file order, for the tab key
|
||||
QList<QWidget *> m_fields;
|
||||
///What the window says it is about, changed when editing
|
||||
QLabel *m_intro = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief The dialog which opens as soon as a cable line has been drawn.
|
||||
|
||||
It says how many cores were recognised, then lets the user either
|
||||
pick a type from the cable type file -- a brand new cable -- or pick
|
||||
one of the cables the project already holds and give it more cores.
|
||||
Both tabs carry a search field and the checkbox hiding what cannot be
|
||||
used, exactly like the material selection does.
|
||||
|
||||
The very same window opens to change the type of a cable which
|
||||
already exists: it then shows the type tab alone, filled in with what
|
||||
that cable holds, and takes the chosen type onto the cable -- cores
|
||||
reworked, marks redrawn -- as one single undo step. It carries the
|
||||
button filling in a new line of the cable type file, the way the
|
||||
material window carries its own.
|
||||
*/
|
||||
class CableCreateDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CableCreateDialog(Diagram *diagram,
|
||||
const QList<CableCrossing> &crossings,
|
||||
const CablePartData &part,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
The same window, opened to change the type of a cable which
|
||||
already exists rather than to settle a line just drawn.
|
||||
@param diagram the folio whose undo stack takes the change
|
||||
@param cable the cable whose type is being changed
|
||||
@param parent
|
||||
*/
|
||||
CableCreateDialog(Diagram *diagram,
|
||||
Cable *cable,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
///The cable the user settled on, null when he cancelled
|
||||
Cable *cable() const {return m_cable;}
|
||||
///The cores this cable took away from cables the project
|
||||
///already held, so that undo gives them back
|
||||
QList<CableCoreTaken> takenCores() const {return m_taken;}
|
||||
|
||||
void accept() override;
|
||||
|
||||
private:
|
||||
void build();
|
||||
void buildNewTab();
|
||||
void buildExistingTab();
|
||||
void buildPropertyRow();
|
||||
void loadTypeFile();
|
||||
void refreshExisting();
|
||||
void selectionChanged();
|
||||
///Fill the name field in and remember what this window put there
|
||||
void fillDesignation(const QString &text, bool by_hand);
|
||||
void assignCrossings(const QList<int> &core_slots);
|
||||
QList<CableCoreTaken> coresTakenFromOthers(const QUuid &target) const;
|
||||
QList<int> nextFreeSlots(int count) const;
|
||||
QModelIndex currentSourceIndex() const;
|
||||
bool currentSelectionIsUnsuitable() const;
|
||||
QString unsuitableNote() const;
|
||||
QStandardItem *cell(const QString &value, bool unsuitable) const;
|
||||
///True while this window changes the type of an existing cable
|
||||
bool changeMode() const {return m_change != nullptr;}
|
||||
///How many cores whatever he picks has to hold at least
|
||||
int neededCores() const;
|
||||
///Where the cable type file stands
|
||||
QString typeFilePath() const;
|
||||
///The button filling in a new line of the cable type file
|
||||
void addTypeRecord();
|
||||
///The button rewriting the line of the cable type file he picked
|
||||
void editTypeRecord();
|
||||
///Take the chosen type onto the cable being changed
|
||||
void acceptTypeChange();
|
||||
|
||||
//ATTRIBUTES
|
||||
private:
|
||||
QPointer<Diagram> m_diagram;
|
||||
QList<CableCrossing> m_crossings;
|
||||
CablePartData m_part;
|
||||
Cable *m_cable = nullptr;
|
||||
///The cable whose type is changed, null while a line is drawn
|
||||
QPointer<Cable> m_change;
|
||||
///The cores other cables lost to this one, given back by undo
|
||||
QList<CableCoreTaken> m_taken;
|
||||
|
||||
QLabel *m_header = nullptr;
|
||||
QTabWidget *m_tabs = nullptr;
|
||||
QDialogButtonBox *m_buttons = nullptr;
|
||||
|
||||
QLineEdit *m_new_search = nullptr;
|
||||
QCheckBox *m_new_hide = nullptr;
|
||||
QTableView *m_new_table = nullptr;
|
||||
QStandardItemModel *m_new_model = nullptr;
|
||||
CableFilterProxy *m_new_proxy = nullptr;
|
||||
QLabel *m_new_note = nullptr;
|
||||
///The button rewriting the chosen line of the file, which
|
||||
///can only work on a line which was picked
|
||||
QPushButton *m_edit_button = nullptr;
|
||||
CableTypeListData m_types;
|
||||
|
||||
QLineEdit *m_existing_search = nullptr;
|
||||
QCheckBox *m_existing_hide = nullptr;
|
||||
QTableView *m_existing_table = nullptr;
|
||||
QStandardItemModel *m_existing_model = nullptr;
|
||||
CableFilterProxy *m_existing_proxy = nullptr;
|
||||
QLabel *m_existing_note = nullptr;
|
||||
QList<Cable *> m_existing;
|
||||
|
||||
QWidget *m_property_row = nullptr;
|
||||
QLineEdit *m_designation = nullptr;
|
||||
/**
|
||||
The name exactly as this window filled it in, and where
|
||||
that name came from: whatever he types over it from then
|
||||
on is a name of his own (see accept())
|
||||
*/
|
||||
QString m_designation_prefill;
|
||||
bool m_designation_prefill_by_hand = false;
|
||||
QLineEdit *m_installation = nullptr;
|
||||
QLineEdit *m_location = nullptr;
|
||||
};
|
||||
|
||||
#endif // CABLECREATEDIALOG_H
|
||||
@@ -0,0 +1,639 @@
|
||||
/*
|
||||
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 "cablemanager.h"
|
||||
|
||||
#include "cable.h"
|
||||
#include "../autoNum/assignvariables.h"
|
||||
#include "../autoNum/numerotationcontextcommands.h"
|
||||
#include "../diagram.h"
|
||||
#include "../qetgraphicsitem/conductor.h"
|
||||
#include "../qetgraphicsitem/element.h"
|
||||
#include "../qetgraphicsitem/terminal.h"
|
||||
#include "../qetproject.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QPainterPath>
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
@brief Where two segments cross, if they do at all.
|
||||
|
||||
Done by hand rather than with QLineF::intersects() because the
|
||||
collinear case has to be a miss: a cable drawn exactly on top of a
|
||||
conductor follows it, it does not cross it, and counting it would
|
||||
claim a core the cable never reaches.
|
||||
@param a1 start of the first segment
|
||||
@param a2 end of the first segment
|
||||
@param b1 start of the second segment
|
||||
@param b2 end of the second segment
|
||||
@param result receives the crossing point when there is one
|
||||
@return true when the two segments cross within their own length
|
||||
*/
|
||||
bool segmentIntersection(const QPointF &a1, const QPointF &a2,
|
||||
const QPointF &b1, const QPointF &b2,
|
||||
QPointF *result)
|
||||
{
|
||||
const QPointF r = a2 - a1;
|
||||
const QPointF s = b2 - b1;
|
||||
const QPointF u = b1 - a1;
|
||||
|
||||
const qreal denominator = r.x() * s.y() - r.y() * s.x();
|
||||
if (qFuzzyIsNull(denominator)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const qreal t = (u.x() * s.y() - u.y() * s.x()) / denominator;
|
||||
const qreal v = (u.x() * r.y() - u.y() * r.x()) / denominator;
|
||||
if (t < 0.0 || t > 1.0 || v < 0.0 || v > 1.0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
*result = a1 + t * r;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief How far a point stands from a segment, in scene units
|
||||
*/
|
||||
qreal distanceToSegment(const QPointF &point, const QPointF &a, const QPointF &b)
|
||||
{
|
||||
const QPointF ab = b - a;
|
||||
const qreal length_squared = ab.x() * ab.x() + ab.y() * ab.y();
|
||||
if (qFuzzyIsNull(length_squared)) {
|
||||
return QLineF(point, a).length();
|
||||
}
|
||||
|
||||
const QPointF ap = point - a;
|
||||
qreal t = (ap.x() * ab.x() + ap.y() * ab.y()) / length_squared;
|
||||
t = qBound(0.0, t, 1.0);
|
||||
return QLineF(point, a + t * ab).length();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Whether some cable of the project already stands under this
|
||||
number.
|
||||
|
||||
A number which is taken must not be handed out twice. That happens
|
||||
while a whole run of cables is numbered one after the other -- none
|
||||
of them has been added to the project by anything but its own
|
||||
creation yet -- and it happens when a rule has simply been given a
|
||||
value which some cable carries already.
|
||||
@param project
|
||||
@param designation the number to look for
|
||||
@return true when a cable of that project stands under it
|
||||
*/
|
||||
bool designationIsTaken(QETProject *project, const QString &designation)
|
||||
{
|
||||
if (!project || designation.isEmpty()) return false;
|
||||
|
||||
for (Cable *cable : project->cables())
|
||||
{
|
||||
if (cable && cable->designation() == designation) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
@brief CableManager::crossedConductors
|
||||
Tell which conductors a line about to become a cable runs across, in
|
||||
the order they are met along the line. That order is what gives each
|
||||
core its place, and therefore its colour, so it has to be the order
|
||||
of the drawing and not the order the conductors happen to be stored
|
||||
in.
|
||||
@param diagram the folio the line is drawn on
|
||||
@param line the line, in scene coordinates
|
||||
@return the crossings, ordered from the start of the line
|
||||
*/
|
||||
QList<CableCrossing> CableManager::crossedConductors(Diagram *diagram, const QLineF &line)
|
||||
{
|
||||
QList<CableCrossing> crossings;
|
||||
if (!diagram) {
|
||||
return crossings;
|
||||
}
|
||||
|
||||
const QPointF start = line.p1();
|
||||
if (qFuzzyCompare(start, line.p2())) {
|
||||
return crossings;
|
||||
}
|
||||
|
||||
const QPointF direction = line.p2() - start;
|
||||
for (Conductor *conductor : diagram->conductors())
|
||||
{
|
||||
QPointF where;
|
||||
if (!crossingOf(diagram, line, conductor, &where)) continue;
|
||||
|
||||
CableCrossing crossing;
|
||||
crossing.conductor = conductor;
|
||||
crossing.position = where;
|
||||
crossings.append(crossing);
|
||||
}
|
||||
|
||||
std::sort(crossings.begin(), crossings.end(),
|
||||
[&start, &direction](const CableCrossing &a_, const CableCrossing &b_) {
|
||||
const QPointF da = a_.position - start;
|
||||
const QPointF db = b_.position - start;
|
||||
const qreal ta = da.x() * direction.x() + da.y() * direction.y();
|
||||
const qreal tb = db.x() * direction.x() + db.y() * direction.y();
|
||||
return ta < tb;
|
||||
});
|
||||
|
||||
return crossings;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableManager::crossingOf
|
||||
Where one line runs across one conductor, if it does at all.
|
||||
|
||||
Done by hand rather than with QLineF::intersects() because the
|
||||
collinear case has to be a miss: a cable drawn exactly on top of a
|
||||
conductor follows it, it does not cross it, and counting it would
|
||||
claim a core the cable never reaches.
|
||||
@param diagram the folio the line is drawn on
|
||||
@param line the line, in scene coordinates
|
||||
@param conductor the wire being asked about
|
||||
@param result receives the crossing point, in scene coordinates
|
||||
@return true when the line really runs across that wire
|
||||
*/
|
||||
bool CableManager::crossingOf(Diagram *diagram,
|
||||
const QLineF &line,
|
||||
Conductor *conductor,
|
||||
QPointF *result)
|
||||
{
|
||||
if (!conductor || !diagram) return false;
|
||||
|
||||
const QPointF start = line.p1();
|
||||
const QPointF end = line.p2();
|
||||
if (qFuzzyCompare(start, end)) return false;
|
||||
|
||||
//Work in the conductor's own coordinates: its path is stored
|
||||
//there, and a conductor is never where it was when it was
|
||||
//created once the whole drawing has been moved about.
|
||||
const QPointF a = conductor->mapFromScene(start);
|
||||
const QPointF b = conductor->mapFromScene(end);
|
||||
|
||||
const QPainterPath path = conductor->path();
|
||||
for (int i = 0; i + 1 < path.elementCount(); ++i)
|
||||
{
|
||||
const QPainterPath::Element &e1 = path.elementAt(i);
|
||||
const QPainterPath::Element &e2 = path.elementAt(i + 1);
|
||||
//A MoveTo ends the segment before it: the jump to the new
|
||||
//subpath is not drawn, so nothing can cross it.
|
||||
if (e2.type == QPainterPath::MoveToElement) {
|
||||
continue;
|
||||
}
|
||||
QPointF where;
|
||||
if (segmentIntersection(a, b,
|
||||
QPointF(e1.x, e1.y),
|
||||
QPointF(e2.x, e2.y),
|
||||
&where))
|
||||
{
|
||||
if (result) {
|
||||
*result = conductor->mapToScene(where);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableManager::distanceTo
|
||||
How far a point of the sheet stands from a wire, in scene units.
|
||||
@param conductor
|
||||
@param scene_pos
|
||||
@return that distance, which is where the wire passes when the point
|
||||
is beside it rather than in front of one of its ends
|
||||
*/
|
||||
qreal CableManager::distanceTo(Conductor *conductor, const QPointF &scene_pos)
|
||||
{
|
||||
if (!conductor) {
|
||||
return std::numeric_limits<qreal>::max();
|
||||
}
|
||||
|
||||
qreal best = std::numeric_limits<qreal>::max();
|
||||
const QPainterPath path = conductor->path();
|
||||
for (int i = 0; i + 1 < path.elementCount(); ++i)
|
||||
{
|
||||
const QPainterPath::Element &e1 = path.elementAt(i);
|
||||
const QPainterPath::Element &e2 = path.elementAt(i + 1);
|
||||
if (e2.type == QPainterPath::MoveToElement) continue;
|
||||
|
||||
const qreal d = distanceToSegment(
|
||||
scene_pos,
|
||||
conductor->mapToScene(QPointF(e1.x, e1.y)),
|
||||
conductor->mapToScene(QPointF(e2.x, e2.y)));
|
||||
if (d < best) {
|
||||
best = d;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
@brief The end of @a conductor which does not touch @a element.
|
||||
|
||||
Which end that is tells where the wire goes on its own sheet: a wire
|
||||
ending on a report arrow points away from it, and that is the end
|
||||
which says something about where it comes from or where it runs to.
|
||||
@param conductor the wire to look at
|
||||
@param element the report arrow it ends on (may be nullptr)
|
||||
@return the scene position of the other end, (0, 0) when there is none
|
||||
*/
|
||||
QPointF farEndPosition(const Conductor *conductor, Element *element)
|
||||
{
|
||||
const Terminal *far = nullptr;
|
||||
if (conductor->terminal1
|
||||
&& conductor->terminal1->parentElement() != element) {
|
||||
far = conductor->terminal1;
|
||||
} else {
|
||||
far = conductor->terminal2 ? conductor->terminal2 : conductor->terminal1;
|
||||
}
|
||||
return far ? far->scenePos() : QPointF();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Whether wire @a a runs before wire @a b on its own sheet.
|
||||
|
||||
Both are looked at from @a element: what tells them apart is where
|
||||
their other end goes. Putting the wires which come in to a report
|
||||
and the wires which go out of its partner in this order is what
|
||||
pairs a continuation up with the core it belongs to when several
|
||||
cores run through one and the same report.
|
||||
@param a @param b the two wires to compare
|
||||
@param element the report arrow both end on (may be nullptr)
|
||||
@return true when @a a comes first
|
||||
*/
|
||||
bool farBefore(const Conductor *a, const Conductor *b, Element *element)
|
||||
{
|
||||
const QPointF pa = farEndPosition(a, element);
|
||||
const QPointF pb = farEndPosition(b, element);
|
||||
if (pa.y() != pb.y()) return pa.y() < pb.y();
|
||||
if (pa.x() != pb.x()) return pa.x() < pb.x();
|
||||
return a->uuid() < b->uuid();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
@brief CableManager::refreshLabels
|
||||
Rewrite the cable field of every conductor of the project from the
|
||||
cables themselves.
|
||||
|
||||
The rule is one-directional and therefore cannot drift: a conductor
|
||||
which some cable claims gets the label that cable generates, a
|
||||
conductor which no cable claims any more -- because the cable was
|
||||
delete, the core was dragged away or the type lost that core -- has
|
||||
the generated label taken away. A conductor which never belonged to
|
||||
a cable keeps whatever it had, since only one with a reference is
|
||||
touched.
|
||||
|
||||
A core which ends on a folio report goes on over there: the wire
|
||||
coming out of the linked arrow shows the same piece of copper, so
|
||||
it belongs to the same core and carries the same cable field. What
|
||||
the links say is worked out from scratch on every pass, and a wire
|
||||
which some cable already claims is never taken away from it, so the
|
||||
reference appears wherever a report carries a core and disappears
|
||||
again by itself as soon as the cable, the core or the link itself
|
||||
is gone. Chains of reports are followed as far as they go, so a
|
||||
cable may run over any number of sheets.
|
||||
@param project
|
||||
*/
|
||||
void CableManager::refreshLabels(QETProject *project)
|
||||
{
|
||||
if (!project) return;
|
||||
|
||||
struct Binding {
|
||||
QUuid cable;
|
||||
int slot = -1;
|
||||
QString label;
|
||||
};
|
||||
|
||||
//Without cables, the folios not built hold nothing this pass
|
||||
//changes but stale references (QETProject::cableLabelFolios())
|
||||
const QList<Diagram *> folios = project->cableLabelFolios();
|
||||
|
||||
QHash<QUuid, Binding> bindings;
|
||||
for (Cable *cable : project->cables())
|
||||
{
|
||||
if (!cable) continue;
|
||||
for (const CableCore &core : cable->usedCores())
|
||||
{
|
||||
if (core.conductor.isNull()) continue;
|
||||
Binding binding;
|
||||
binding.cable = cable->uuid();
|
||||
binding.slot = core.core;
|
||||
binding.label = cable->labelOfCore(core.core);
|
||||
bindings.insert(core.conductor, binding);
|
||||
}
|
||||
}
|
||||
|
||||
//A core which ends on a folio report continues on the wire which
|
||||
//leaves the linked arrow on the other sheet. That wire shows the
|
||||
//same piece of copper, so it gets the same reference as the core
|
||||
//it continues. The walk repeats while it finds new wires, so a
|
||||
//chain of reports is followed across any number of sheets, and
|
||||
//it only ever fills a wire no cable has claimed yet: two cores
|
||||
//can never fight over the same wire, and a reference worked out
|
||||
//here is taken away again by the same pass as soon as the cable,
|
||||
//the core or the link does.
|
||||
QHash<QUuid, Conductor *> conductors_by_uuid;
|
||||
QHash<Element *, QList<Conductor *>> endings_on_report;
|
||||
for (Diagram *diagram : folios)
|
||||
{
|
||||
if (!diagram) continue;
|
||||
for (Conductor *conductor : diagram->conductors())
|
||||
{
|
||||
if (conductor) conductors_by_uuid.insert(conductor->uuid(), conductor);
|
||||
}
|
||||
}
|
||||
|
||||
//Where a wire goes on its own sheet (farBefore, above): putting the
|
||||
//wires which come in and the wires which go out in the same order
|
||||
//is what pairs a continuation up with its core when several cores
|
||||
//run through one and the same report.
|
||||
|
||||
bool grew = true;
|
||||
while (grew)
|
||||
{
|
||||
grew = false;
|
||||
endings_on_report.clear();
|
||||
for (const QUuid &source_uuid : bindings.keys())
|
||||
{
|
||||
Conductor *source = conductors_by_uuid.value(source_uuid);
|
||||
if (!source) continue;
|
||||
|
||||
Terminal *ends[2] = {source->terminal1, source->terminal2};
|
||||
for (Terminal *end : ends)
|
||||
{
|
||||
if (!end) continue;
|
||||
Element *element = end->parentElement();
|
||||
//An arrow taken off the sheet carries nothing over
|
||||
//any more, whatever still links it
|
||||
if (!element || !element->scene()) continue;
|
||||
if (!(element->linkType() & Element::AllReport)) continue;
|
||||
|
||||
QList<Conductor *> &in = endings_on_report[element];
|
||||
if (!in.contains(source)) in.append(source);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = endings_on_report.constBegin();
|
||||
it != endings_on_report.constEnd(); ++it)
|
||||
{
|
||||
Element *element = it.key();
|
||||
QList<Conductor *> in = it.value();
|
||||
std::sort(in.begin(), in.end(),
|
||||
[&](const Conductor *a, const Conductor *b) {
|
||||
return farBefore(a, b, element);
|
||||
});
|
||||
|
||||
for (Element *partner : element->linkedElements())
|
||||
{
|
||||
if (!partner || partner == element || !partner->scene()) continue;
|
||||
|
||||
QList<Conductor *> out = partner->conductors();
|
||||
std::sort(out.begin(), out.end(),
|
||||
[&](const Conductor *a, const Conductor *b) {
|
||||
return farBefore(a, b, partner);
|
||||
});
|
||||
|
||||
//One core coming in may branch out into several wires
|
||||
//over there -- they all show the same piece of copper.
|
||||
//Several cores coming in take one wire each, in the
|
||||
//order they run on their own sheet.
|
||||
const int count = (in.size() == 1)
|
||||
? out.size()
|
||||
: qMin(in.size(), out.size());
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
Conductor *continued = out.at(i);
|
||||
if (!continued || bindings.contains(continued->uuid())) continue;
|
||||
const int from = qMin(i, in.size() - 1);
|
||||
bindings.insert(continued->uuid(),
|
||||
bindings.value(in.at(from)->uuid()));
|
||||
grew = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Diagram *diagram : folios)
|
||||
{
|
||||
if (!diagram) continue;
|
||||
for (Conductor *conductor : diagram->conductors())
|
||||
{
|
||||
if (!conductor) continue;
|
||||
|
||||
const auto it = bindings.constFind(conductor->uuid());
|
||||
if (it != bindings.constEnd())
|
||||
{
|
||||
const ConductorProperties &properties = conductor->properties();
|
||||
conductor->setCableReference(it->label, it->cable, it->slot);
|
||||
}
|
||||
else if (!conductor->properties().m_cable_uuid.isNull())
|
||||
{
|
||||
conductor->setCableReference(QString(), QUuid(), -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableManager::claimConductor
|
||||
Make a conductor a core of @a new_owner, taking it away from whichever
|
||||
cable held it before. A conductor is a single piece of wire: two
|
||||
cables cannot both claim it, and the one that loses it must not go
|
||||
on drawing a label for it.
|
||||
@param project
|
||||
@param conductor
|
||||
@param new_owner the cable which is about to use the conductor
|
||||
*/
|
||||
void CableManager::claimConductor(QETProject *project, Conductor *conductor, Cable *new_owner)
|
||||
{
|
||||
if (!project || !conductor) return;
|
||||
|
||||
const QUuid owner_uuid = conductor->properties().m_cable_uuid;
|
||||
if (owner_uuid.isNull() || (new_owner && owner_uuid == new_owner->uuid())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Cable *previous = cableByUuid(project, owner_uuid)) {
|
||||
previous->removeCore(conductor->properties().m_cable_slot);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableManager::cableByUuid
|
||||
@param project
|
||||
@param uuid
|
||||
@return the cable with that identity, or nullptr
|
||||
*/
|
||||
Cable *CableManager::cableByUuid(QETProject *project, const QUuid &uuid)
|
||||
{
|
||||
if (!project || uuid.isNull()) return nullptr;
|
||||
for (Cable *cable : project->cables()) {
|
||||
if (cable && cable->uuid() == uuid) return cable;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableManager::conductorByUuid
|
||||
@param project
|
||||
@param uuid
|
||||
@return the conductor with that identity, or nullptr. Needed because
|
||||
a cable remembers its cores by identity: only the drawing knows where
|
||||
they ended up.
|
||||
*/
|
||||
Conductor *CableManager::conductorByUuid(QETProject *project, const QUuid &uuid)
|
||||
{
|
||||
if (!project || uuid.isNull()) return nullptr;
|
||||
for (Diagram *diagram : project->diagrams()) {
|
||||
if (!diagram) continue;
|
||||
for (Conductor *conductor : diagram->conductors()) {
|
||||
if (conductor && conductor->uuid() == uuid) return conductor;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableManager::nextDesignation
|
||||
Work out the number of the next cable drawn on a folio.
|
||||
|
||||
When the project has a numbering rule for cables, that rule decides:
|
||||
whatever it holds at this moment is the number, read by the very
|
||||
same machinery the element numbering reads its own. When it has
|
||||
none, the plain letter W is handed out: a project which numbers its
|
||||
cables by no rule gets no numbers invented for it, and W -- the one
|
||||
part of the old numbers which was never a number -- is all such a
|
||||
cable is called until a rule is defined.
|
||||
|
||||
A rule whose counter belongs to one folio is read from the beginning
|
||||
of the folio the cable is drawn on, so that the number this cable
|
||||
gets is the first free one of its own folio rather than whatever the
|
||||
project's rule happens to hold after numbering other folios.
|
||||
|
||||
Nothing here moves the project's own rule on: that belongs to the
|
||||
moment the cable really comes into being.
|
||||
@param project
|
||||
@param diagram the folio the cable is being drawn on
|
||||
@return the number
|
||||
*/
|
||||
QString CableManager::nextDesignation(QETProject *project, Diagram *diagram)
|
||||
{
|
||||
NumerotationContext context;
|
||||
if (project) {
|
||||
context = project->cableAutoNum(project->cableCurrentAutoNum());
|
||||
}
|
||||
//The folio-bound parts are put back to the start of that folio;
|
||||
//the stepping of the overload below then walks past every
|
||||
//number of this folio which is already taken
|
||||
context = autonum::resetFolioCounters(context);
|
||||
return nextDesignation(project, diagram, context);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableManager::nextDesignation
|
||||
Work the number of a cable out of a rule which the caller keeps in
|
||||
@p context -- the project's own rule as it stands, or a copy of it
|
||||
which the caller is numbering a whole run of cables with.
|
||||
|
||||
Whatever the rule holds is the number, unless some cable already
|
||||
stands under it: then @p context is stepped on and the rule is read
|
||||
once more, so that a paste which brings in several cables at once
|
||||
numbers each of them differently although the project's own rule
|
||||
does not move until those cables really exist. A rule which cannot
|
||||
step -- one with nothing in it which counts -- hands out what it
|
||||
says and says no more, for what it says is all it knows.
|
||||
|
||||
A project which numbers its cables by no rule is not read at all
|
||||
and is handed the plain letter W instead: no numbers are invented
|
||||
where none have been defined.
|
||||
@param project
|
||||
@param diagram the folio the cable is being drawn on, which every
|
||||
folio-bound part of a rule is read from
|
||||
@param context the rule to read; stepped on when what it says is
|
||||
already taken
|
||||
@return the number
|
||||
*/
|
||||
QString CableManager::nextDesignation(QETProject *project,
|
||||
Diagram *diagram,
|
||||
NumerotationContext &context)
|
||||
{
|
||||
if (!project || !diagram) return QString();
|
||||
|
||||
const QString key = project->cableCurrentAutoNum();
|
||||
//Whether there was a rule to read at all, taken down before the
|
||||
//loop: stepping the rule on empties it, and that must not look
|
||||
//like a project which never had one.
|
||||
const bool ruled = !key.isEmpty() && !context.isEmpty();
|
||||
QString previous;
|
||||
|
||||
for (int attempt = 0; ruled && attempt < 1000 && !context.isEmpty(); ++attempt)
|
||||
{
|
||||
//The rule is read on a copy: reading it fills in what its
|
||||
//folio-bound parts are worth here and now, and that must
|
||||
//not creep into the rule itself.
|
||||
NumerotationContext evaluated = context;
|
||||
const QString formula = autonum::numerotationContextToFormula(evaluated);
|
||||
autonum::sequentialNumbers seq;
|
||||
autonum::setSequential(formula, seq, evaluated, diagram, key);
|
||||
const QString candidate =
|
||||
autonum::AssignVariables::formulaToLabel(formula, seq, diagram);
|
||||
|
||||
if (candidate.isEmpty()) break;
|
||||
if (!designationIsTaken(project, candidate)) return candidate;
|
||||
|
||||
//Taken already: step the rule on and read it once more.
|
||||
if (candidate == previous) return candidate;
|
||||
previous = candidate;
|
||||
|
||||
NumerotationContextCommands ncc(context, diagram);
|
||||
const NumerotationContext following = ncc.next();
|
||||
if (following.size() != context.size()) break;
|
||||
context = following;
|
||||
}
|
||||
|
||||
//No rule at all: the plain letter W, and nothing more. The old
|
||||
//numbers read folio + W + index, and W is the only part of them
|
||||
//which was never a number -- so it is what a project which has
|
||||
//defined no numbering rule calls its cables, until it defines
|
||||
//one.
|
||||
if (!ruled) {
|
||||
return QStringLiteral("W");
|
||||
}
|
||||
|
||||
//A rule which says nothing usable here: no number at all. The
|
||||
//number is what the rule says, and a rule which says nothing
|
||||
//leaves the field to the user rather than having it filled in
|
||||
//with something it never held.
|
||||
return QString();
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
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 CABLEMANAGER_H
|
||||
#define CABLEMANAGER_H
|
||||
|
||||
#include <QLineF>
|
||||
#include <QList>
|
||||
#include <QPointF>
|
||||
#include <QUuid>
|
||||
|
||||
class Cable;
|
||||
class Conductor;
|
||||
class Diagram;
|
||||
class NumerotationContext;
|
||||
class QETProject;
|
||||
|
||||
/**
|
||||
@brief One place where a drawn cable line crosses a conductor.
|
||||
*/
|
||||
struct CableCrossing
|
||||
{
|
||||
Conductor *conductor = nullptr;
|
||||
/// Scene coordinates of the crossing, where the slash is drawn
|
||||
QPointF position;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief The CableManager works out everything which follows from the
|
||||
cables of a project, so nothing has to be maintained by hand.
|
||||
|
||||
It detects which conductors a line about to be drawn crosses, and it
|
||||
turns the cables back into the cable field of every conductor they
|
||||
own -- both directions of one single rule: the cable is the only
|
||||
source of truth.
|
||||
*/
|
||||
class CableManager
|
||||
{
|
||||
public:
|
||||
///How far a colour label may stand from where a line crosses
|
||||
///a wire for that wire to count as the one the label stands
|
||||
///on: one scene unit, and that is already generous. The grid
|
||||
///is there to put a label exactly on the crossing, and it
|
||||
///does: a label drawn on a wire, slid along its own line or
|
||||
///carried there by a paste lands on that crossing to the
|
||||
///last decimal. The single unit only soaks up the rounding
|
||||
///which comes of projecting a label onto its line, and a
|
||||
///terminal which is a hair off the grid. Anything beyond it
|
||||
///is beside the wire rather than on it, so a label one
|
||||
///whole grid step away -- however the grid is set -- gives
|
||||
///up its entry. One number for every place a label is given
|
||||
///to a wire: drawing one, dragging one and wiring a copy
|
||||
///must not disagree about it.
|
||||
static constexpr qreal bind_tolerance = 1.0;
|
||||
|
||||
//Drawing
|
||||
static QList<CableCrossing> crossedConductors(Diagram *diagram, const QLineF &line);
|
||||
static bool crossingOf(Diagram *diagram,
|
||||
const QLineF &line,
|
||||
Conductor *conductor,
|
||||
QPointF *result);
|
||||
static qreal distanceTo(Conductor *conductor, const QPointF &scene_pos);
|
||||
|
||||
//Keeping the drawing in step with the cables
|
||||
static void refreshLabels(QETProject *project);
|
||||
static void claimConductor(QETProject *project, Conductor *conductor, Cable *new_owner);
|
||||
|
||||
//Lookup
|
||||
static Cable *cableByUuid(QETProject *project, const QUuid &uuid);
|
||||
static Conductor *conductorByUuid(QETProject *project, const QUuid &uuid);
|
||||
static QString nextDesignation(QETProject *project, Diagram *diagram);
|
||||
/**
|
||||
Work the number of a next cable out of a rule which is
|
||||
@em not the project's own state but the one @p context
|
||||
holds: the rule is read as it stands there and what it
|
||||
hands out is the number, unless some cable already
|
||||
stands under that number -- which is what happens while
|
||||
a whole run of cables is numbered one after the other
|
||||
before any of them has really been added. Then @p
|
||||
context is stepped on and the rule is read once more.
|
||||
|
||||
When the project numbers its cables by no rule at all,
|
||||
@p context is not read and the plain letter W is handed
|
||||
out instead: no numbers are invented where none have
|
||||
been defined.
|
||||
|
||||
A caller numbering a run of cables keeps the same @p
|
||||
context for all of them, so each of them gets a number
|
||||
of its own; a caller numbering one cable lets it go
|
||||
again afterwards. The project's own rule is never
|
||||
moved on here -- that belongs to the moment the cable
|
||||
comes into being.
|
||||
*/
|
||||
static QString nextDesignation(QETProject *project,
|
||||
Diagram *diagram,
|
||||
NumerotationContext &context);
|
||||
};
|
||||
|
||||
#endif // CABLEMANAGER_H
|
||||
@@ -0,0 +1,513 @@
|
||||
/*
|
||||
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 CABLEPART_H
|
||||
#define CABLEPART_H
|
||||
|
||||
#include "cable.h"
|
||||
#include "cablemanager.h"
|
||||
#include "../qetgraphicsitem/qetgraphicsitem.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QFont>
|
||||
#include <QPointer>
|
||||
#include <QSet>
|
||||
#include <QUuid>
|
||||
|
||||
class Cable;
|
||||
class CablePartData;
|
||||
class Conductor;
|
||||
class QUndoCommand;
|
||||
class QWidget;
|
||||
|
||||
/**
|
||||
@brief The CablePart class draws one section of a cable: the trunk
|
||||
line of one folio, the number and the type of the cable at its left,
|
||||
and a slash with the colour of a core wherever that core crosses a
|
||||
conductor.
|
||||
|
||||
All of it is a view of the Cable: the geometry lives in the cable
|
||||
itself, so lengthening the trunk line is purely optical. What a core
|
||||
wires is read off where its colour label stands: dropping a label
|
||||
gives that core the wire it lands on, and letting go of a moved line
|
||||
works the entries out the same way, from where the labels have come
|
||||
to rest. A label is never sent off to look for a wire and never
|
||||
moved to reach one -- it goes on standing where the user put it --
|
||||
and no gesture at all takes a core away from its cable.
|
||||
*/
|
||||
class CablePart : public QetGraphicsItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CablePart(const CablePartData &data, QGraphicsItem *parent = nullptr);
|
||||
explicit CablePart(QGraphicsItem *parent = nullptr);
|
||||
~CablePart() override;
|
||||
|
||||
enum {Type = UserType + 1012};
|
||||
int type() const override {return Type;}
|
||||
|
||||
void setCable(Cable *cable);
|
||||
Cable *cable() const;
|
||||
QUuid partUuid() const;
|
||||
///Bind the item to its cable once the project is loaded
|
||||
void refreshPending();
|
||||
|
||||
void setLine(const QPointF &p1, const QPointF &p2);
|
||||
QLineF line() const;
|
||||
QPointF firstPoint() const {return m_p1;}
|
||||
QPointF secondPoint() const {return m_p2;}
|
||||
|
||||
QRectF boundingRect() const override;
|
||||
QPainterPath shape() const override;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||
QString name() const override;
|
||||
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
///Shows the hand over a line which points at another folio
|
||||
void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override;
|
||||
///Opens the dialog editing the properties of the whole cable
|
||||
void editProperty() override;
|
||||
|
||||
/**
|
||||
Give this line the mouse and wait for the next click to put
|
||||
down a core of its cable which is not on the sheet yet: the
|
||||
core follows this line wherever the mouse comes near it and
|
||||
the click leaves it standing there, snapped to the grid like
|
||||
anything else drawn.
|
||||
|
||||
It is always this very section which takes it, never the
|
||||
first section of the cable: a cable running over two folios
|
||||
stands on two lines, and the one whose dialog was asked for
|
||||
is the one being worked on.
|
||||
@param core which core of the cable is being put down
|
||||
*/
|
||||
void startPlacingCore(int core);
|
||||
///Stop waiting for that click, putting nothing down at all
|
||||
void cancelPlacement();
|
||||
|
||||
/**
|
||||
The line which waits for the click putting one of its cores
|
||||
down at this very moment, anywhere in the application, or
|
||||
nullptr when no line does.
|
||||
|
||||
Only one line at a time may wait -- taking the core always
|
||||
belongs to the line whose dialog was asked for -- so Escape
|
||||
and the right button can simply ask here and always act on
|
||||
the right one, however many folios are open.
|
||||
*/
|
||||
static CablePart *placingPart();
|
||||
/**
|
||||
End that wait wherever it stands, without putting anything
|
||||
down anywhere: the core goes back to being a free one.
|
||||
|
||||
Used by the sheet itself, which gets the right button and
|
||||
Escape before any item does (see DiagramView).
|
||||
@return true when a line was waiting for its click
|
||||
*/
|
||||
static bool cancelPlacing();
|
||||
|
||||
/**
|
||||
Which core of this cable has its colour label standing at
|
||||
that very place on the sheet -- asked by the sheet itself
|
||||
when the right button comes down on a line, so that the
|
||||
menu which opens belongs to that one core rather than to
|
||||
the whole folio.
|
||||
@param scene_pos a point of the sheet, in scene coordinates
|
||||
@return the core standing there, -1 when none does
|
||||
*/
|
||||
int coreAt(const QPointF &scene_pos) const;
|
||||
/**
|
||||
Take one core off this line: it names no wire any more and
|
||||
goes back to being a free slot of its cable, listed among
|
||||
the ones which stand nowhere -- the very opposite of
|
||||
putting one down from that list.
|
||||
|
||||
Kept as one undo step, so a single undo puts the label
|
||||
back exactly where it stood.
|
||||
@param core which core standing on this line is taken off
|
||||
@return true when a core was taken off
|
||||
*/
|
||||
bool takeCoreOffLine(int core);
|
||||
|
||||
/**
|
||||
Which wire this line runs across at the very place a colour
|
||||
label stands -- so that the entry of a wire follows the label
|
||||
instead of the label being sent off to find a wire. Also asked
|
||||
by a copy being put down (CableCopy::wire), which must not
|
||||
move any label to reach a wire either.
|
||||
@param crossings every place this line runs across a wire
|
||||
@param core the core being worked out
|
||||
@param at where its label stands, in scene coordinates
|
||||
@return the wire to give the core, or nullptr
|
||||
*/
|
||||
Conductor *wireFor(const QList<CableCrossing> &crossings,
|
||||
const CableCore &core, const QPointF &at) const;
|
||||
|
||||
/**
|
||||
Move this line from outside: the sheet moving a whole
|
||||
selection of elements and lines at once asks every line
|
||||
of the selection to come along, one step after the other,
|
||||
exactly as if the finger had been on the line itself, so
|
||||
that one gesture moves everything which is marked instead
|
||||
of two halves of the selection going different ways.
|
||||
|
||||
The calls come in order -- begin once, continue as often
|
||||
as the gesture needs, settle once for the whole
|
||||
selection, then either done or abort -- and a line which
|
||||
never moves simply has no undo step to hand over.
|
||||
*/
|
||||
void beginLineGesture();
|
||||
///Take one more step of such a move, by that much
|
||||
void continueLineGesture(const QPointF &delta);
|
||||
/**
|
||||
Where this line comes to rest, worked out the same way as
|
||||
after a drag of its own: which wire every colour label now
|
||||
stands on, asked before anything changes hands. The step
|
||||
of this line is handed over rather than pushed, so that
|
||||
the movement as a whole ends up as one single undo step.
|
||||
@param steps receives the undo step of this line, if any
|
||||
@return false when he called the gesture off
|
||||
*/
|
||||
bool settleLineGesture(QList<QUndoCommand *> &steps);
|
||||
///Put this line back where the gesture found it
|
||||
void abortLineGesture();
|
||||
///The gesture is over for this line: forget where it began
|
||||
void doneLineGesture();
|
||||
|
||||
/**
|
||||
One cross reference of this section as the PDF export
|
||||
needs it: where the text of the reference stands on this
|
||||
folio, and on the folio it points at where the section it
|
||||
names stands -- so that exporting makes it a clickable
|
||||
link the way a cross reference of an element already is.
|
||||
|
||||
Both rectangles are in scene coordinates, each of its own
|
||||
folio.
|
||||
*/
|
||||
struct PdfRef
|
||||
{
|
||||
///The folio the reference points at
|
||||
Diagram *diagram = nullptr;
|
||||
///Where the text of the reference stands on this folio
|
||||
QRectF here;
|
||||
///Where the section it names stands on that folio
|
||||
QRectF there;
|
||||
};
|
||||
QList<PdfRef> pdfRefs() const;
|
||||
|
||||
protected:
|
||||
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
One line of the label at the left of the trunk line:
|
||||
what is written, which field of the cable it comes from
|
||||
and -- worked out from what that field asks for -- the
|
||||
font it is written with, the way it lines up and the
|
||||
rectangle it sits in.
|
||||
|
||||
Every line carries its own font, because every field
|
||||
may have been given one of its own: Anlage, Ort and
|
||||
Désignation stand in one block, and their lines may
|
||||
very well no longer all be the same size once he has
|
||||
set them apart.
|
||||
*/
|
||||
struct LabelLine
|
||||
{
|
||||
QString text;
|
||||
QString key;
|
||||
QFont font;
|
||||
Qt::Alignment align = Qt::AlignRight;
|
||||
qreal width = 0.0;
|
||||
qreal height = 0.0;
|
||||
QRectF rect;
|
||||
};
|
||||
|
||||
/**
|
||||
One line of the label which points at a folio this cable
|
||||
runs on besides this one: that folio, the section of the
|
||||
cable standing there, and the text this project asks for
|
||||
such a reference -- the format chosen for the type
|
||||
"Cable" under Cross-references, written out once per folio,
|
||||
in the order the folios turn.
|
||||
*/
|
||||
struct CrossRef
|
||||
{
|
||||
///The other folio, which always exists while listed
|
||||
Diagram *diagram = nullptr;
|
||||
///Which section of the cable stands on it
|
||||
QUuid part;
|
||||
///The text as it is written on the drawing
|
||||
QString text;
|
||||
};
|
||||
|
||||
QFont textFont() const;
|
||||
///The font the colours of the cores are written with
|
||||
QFont coreFont() const;
|
||||
///The font one named text of this cable is written with
|
||||
QFont fontOf(const QString &key) const;
|
||||
///How one named text of this cable lines up at the line
|
||||
Qt::Alignment alignOf(const QString &key) const;
|
||||
/**
|
||||
How far the whole label at the left of the line is
|
||||
nudged on the drawing: over the line, under it, left
|
||||
or right of the end of it. A nudge only, which moves
|
||||
the texts and never the line.
|
||||
@return the distance in scene units
|
||||
*/
|
||||
QPointF textOffset() const;
|
||||
/**
|
||||
How far the row of colours is nudged on the drawing:
|
||||
again a distance on the texts only, the slashes and
|
||||
the entries behind them staying where they stand.
|
||||
@return the distance in scene units
|
||||
*/
|
||||
QPointF coreOffset() const;
|
||||
/**
|
||||
Where a text of this width starts, at the left end of
|
||||
the trunk line: before the line when it is set to end
|
||||
there (the way it always did), on the line when it is
|
||||
centred, after it when it starts there.
|
||||
@param key the field the text comes from
|
||||
@param width how wide the text is, in scene units
|
||||
@return the x of the left of the text
|
||||
*/
|
||||
qreal textLeft(const QString &key, qreal width) const;
|
||||
bool trunkIsHorizontal() const;
|
||||
QList<LabelLine> labelLines() const;
|
||||
/**
|
||||
The references to the other folios this cable runs on,
|
||||
one line each, stacked under the length of the cable:
|
||||
at the very bottom of everything the line says.
|
||||
@return those lines, in the order crossRefs() lists them
|
||||
*/
|
||||
QList<LabelLine> refLines() const;
|
||||
/**
|
||||
Every other folio this cable runs on, one entry each, in
|
||||
the order the folios turn -- empty when the cable runs on
|
||||
this folio alone, which is the usual case.
|
||||
*/
|
||||
QList<CrossRef> crossRefs() const;
|
||||
/**
|
||||
Which of those references stands at that very place, so
|
||||
that both the hand of the mouse and a double click know
|
||||
which line they are about.
|
||||
@param scene_pos a point of the sheet, in scene coordinates
|
||||
@return the index of that reference, -1 when none stands there
|
||||
*/
|
||||
int crossRefAt(const QPointF &scene_pos) const;
|
||||
/**
|
||||
Send the mouse to the section of the cable which stands
|
||||
on the folio the reference at that place points at -- the
|
||||
very leap an element's cross reference takes, straight to
|
||||
the place to look at.
|
||||
@param scene_pos a point of the sheet, in scene coordinates
|
||||
@return true when the click stood on a reference
|
||||
*/
|
||||
bool goToCrossRef(const QPointF &scene_pos);
|
||||
/**
|
||||
Keep the reference lines up to date with the project: a
|
||||
folio which is added, removed or moved changes what they
|
||||
say, and so does the format chosen under Cross-references.
|
||||
*/
|
||||
void setUpXrefHooks();
|
||||
QString typeText() const;
|
||||
QString lengthText() const;
|
||||
QRectF labelRect() const;
|
||||
qreal underTop() const;
|
||||
QRectF typeRect() const;
|
||||
QRectF lengthRect() const;
|
||||
QRectF coreLabelRect(const QPointF &at, const QString &color) const;
|
||||
QPointF coreLabelPivot(const QPointF &at, const QString &color) const;
|
||||
QPointF coreDrawPosition(const CableCore &core) const;
|
||||
bool coreDrawn(const CableCore &core) const;
|
||||
QPointF dragPosition(const QPointF &scene_pos) const;
|
||||
/**
|
||||
Where the mouse would put down a core which waits to be
|
||||
put down: the mouse projected onto the line itself, at a
|
||||
grid step, and never past either end of the line -- the
|
||||
same coordinates a dragged label would be drawn at, only
|
||||
worked out from the mouse rather than from a gesture.
|
||||
@param scene_pos where the mouse stands, in scene coordinates
|
||||
@return where the slash would be drawn
|
||||
*/
|
||||
QPointF placePosition(const QPointF &scene_pos) const;
|
||||
/**
|
||||
Snap a distance measured along the line to a grid step, in
|
||||
the coordinates of the sheet -- so a horizontal line steps
|
||||
along x and a vertical one along y. Holding Ctrl lets go of
|
||||
the grid, as everywhere else on the sheet.
|
||||
@param along how far from the first end of the line
|
||||
@return that distance at a grid step
|
||||
*/
|
||||
qreal stepAlong(qreal along) const;
|
||||
void drawLabel(QPainter *painter, const QColor &ink) const;
|
||||
void drawCores(QPainter *painter, const QColor &ink) const;
|
||||
///Draw the slash of one core and the colour written beside it
|
||||
void drawCoreMark(QPainter *painter, const QColor &ink,
|
||||
const QPointF &at, const QString &color) const;
|
||||
void syncGeometry();
|
||||
bool labelAt(int core, const QPointF &scene_pos) const;
|
||||
///Answer to the question of taking a wire another cable holds
|
||||
enum class ClaimAnswer {
|
||||
///Take it: the other cable lets it go
|
||||
Take,
|
||||
///Leave it: nothing changes hands, the rest still happens
|
||||
Leave,
|
||||
///Cancel: this whole gesture is called off, as if undone
|
||||
Cancel
|
||||
};
|
||||
/**
|
||||
@param core which core is being let go
|
||||
@param dropped_at where its label comes to rest
|
||||
@param before every core the cable held before this one was
|
||||
added to it, so that the one step of undo takes the addition
|
||||
back as well; nullptr while a core which is already on the
|
||||
sheet is merely moved
|
||||
@return false when the user called the whole thing off
|
||||
*/
|
||||
bool rebindCore(int core, const QPointF &dropped_at,
|
||||
const QList<CableCore> *before = nullptr);
|
||||
/**
|
||||
Put a core which is not on the sheet yet onto this line: it
|
||||
stands where the mouse says, and if a wire runs across that
|
||||
very place it names that wire the same way a dragged label
|
||||
would. Called off means back to being a free core, with one
|
||||
step of undo to take it away again.
|
||||
@param core which core of the cable is being put down
|
||||
@param at where its label is to stand, in scene coordinates
|
||||
@return false when nothing was put down
|
||||
*/
|
||||
bool placeCore(int core, const QPointF &at);
|
||||
/**
|
||||
Send the mouse to this line, to the grid step the mark of
|
||||
the waiting core stands on, so that the very next click is
|
||||
the one which puts the core down -- wherever he asked for
|
||||
the core from, and whether he asks for this very one a
|
||||
second time or not. The mouse goes there only where the
|
||||
line is really in sight: a folio hidden behind another
|
||||
one, or off the edge of the view, is left alone rather
|
||||
than jumped to.
|
||||
*/
|
||||
void sendMouseToLine();
|
||||
void reconcileCores(QList<CableCore> &cores, const QPointF &delta) const;
|
||||
bool settleCores(QList<CableCore> &cores) const;
|
||||
ClaimAnswer mayClaim(Conductor *conductor) const;
|
||||
ClaimAnswer mayClaimSeveral(const QList<Conductor *> &conductors) const;
|
||||
ClaimAnswer askClaim(QWidget *parent,
|
||||
const QString &title,
|
||||
const QString &question) const;
|
||||
|
||||
private:
|
||||
///The one line which waits for the click putting a core down,
|
||||
///null once there is none: taking the core belongs to the line
|
||||
///whose dialog was asked for, never to the first line of the
|
||||
///cable, and the sheet has to be able to cancel that wait
|
||||
///without knowing which scene it stands in
|
||||
static QPointer<CablePart> s_placing;
|
||||
QPointer<Cable> m_cable;
|
||||
///Identity of the cable this part belongs to, kept until the
|
||||
///project has loaded it and setCable() can be called
|
||||
QUuid m_pending_cable;
|
||||
QUuid m_part_uuid;
|
||||
QPointF m_p1;
|
||||
QPointF m_p2;
|
||||
|
||||
///Core being dragged by its slash, -1 when none
|
||||
int m_dragged_core = -1;
|
||||
///Core waiting to be put down by the next click, -1 when none
|
||||
int m_pending_core = -1;
|
||||
///True while the mouse stands close enough to this line for
|
||||
///the waiting core to be put down where it shows
|
||||
bool m_pending_valid = false;
|
||||
///True between the click which put a core down and the second
|
||||
///click Qt counts as a double click, so that a click which was
|
||||
///there to place something does not open the dialog as well
|
||||
bool m_just_placed = false;
|
||||
///Where the dragged slash follows the mouse
|
||||
QPointF m_drag_scene;
|
||||
QPointF m_press_scene;
|
||||
QPointF m_move_p1;
|
||||
QPointF m_move_p2;
|
||||
///True while the trunk line is being dragged, so undo knows
|
||||
///there is a move to take back
|
||||
bool m_line_moved = false;
|
||||
///Which end of the trunk line is being pulled by one of its
|
||||
///blue points: 0 none, 1 the first end, 2 the second
|
||||
int m_stretch_end = 0;
|
||||
///Every core as it stands while the line is being dragged:
|
||||
///every label is carried along by however far the line has
|
||||
///come, so the line, its description and the colours of its
|
||||
///cores move as one thing and no label ever jumps, lands on
|
||||
///a neighbour's wire or disappears under his hand
|
||||
QList<CableCore> m_line_drag_cores;
|
||||
///Every core as it was when the line was picked up, so undo
|
||||
///can put the labels back with the line
|
||||
QList<CableCore> m_cores_before;
|
||||
///Another line he picked up at the same time: a drag takes
|
||||
///the whole selection with it, so each of them is carried
|
||||
///along and each of them goes back to where it stood if he
|
||||
///calls the gesture off
|
||||
struct CarriedLine
|
||||
{
|
||||
CablePart *part = nullptr;
|
||||
///Where that line ran when the gesture began
|
||||
QPointF p1;
|
||||
QPointF p2;
|
||||
///What its cable held when the gesture began
|
||||
QList<CableCore> cores;
|
||||
};
|
||||
QList<CarriedLine> m_carried;
|
||||
|
||||
///True when the sheet has been told that the rest of the
|
||||
///selection comes along with this line (see carrySelection),
|
||||
///so that it is told once per gesture rather than once per
|
||||
///step -- and never again once it had nothing to move
|
||||
bool m_mover_started = false;
|
||||
///How far of this gesture the sheet has already been told
|
||||
QPointF m_told_mover;
|
||||
|
||||
///The lines which keep the references to the other folios of
|
||||
///this cable up to date, taken down and taken again whenever
|
||||
///this section comes to stand in another folio
|
||||
QList<QMetaObject::Connection> m_xref_connections;
|
||||
|
||||
void carryCompanions(const QPointF &delta);
|
||||
/**
|
||||
Everything else he marked at the same time comes along
|
||||
too -- the elements, the texts and the images of the same
|
||||
selection -- by the very step this line has taken, so
|
||||
that one gesture moves all of it instead of the lines
|
||||
going one way and the rest another.
|
||||
|
||||
What this line measures is how far it has come all in
|
||||
altogether, from where the press found it, while the
|
||||
sheet takes a step at a time: so only the part it has not
|
||||
been told yet is handed over.
|
||||
@param total_delta how far this line has come since the press
|
||||
*/
|
||||
void carrySelection(const QPointF &total_delta);
|
||||
bool settleCompanions(QList<QUndoCommand *> &steps);
|
||||
void putCompanionsBack();
|
||||
void readyToSettle(QList<CableCore> &cores) const;
|
||||
};
|
||||
|
||||
#endif // CABLEPART_H
|
||||
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
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 "cablepropertiesdialog.h"
|
||||
|
||||
#include "cablecreatedialog.h"
|
||||
#include "cablepart.h"
|
||||
#include "cablepropertieswidget.h"
|
||||
#include "editcablecommand.h"
|
||||
#include "../diagram.h"
|
||||
#include "../qeticons.h"
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QMenu>
|
||||
#include <QUndoStack>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
/**
|
||||
@brief CablePropertiesDialog::CablePropertiesDialog
|
||||
@param cable the cable being edited
|
||||
@param diagram the folio the line was double-clicked on, whose undo
|
||||
stack takes the edit back
|
||||
@param part the very line which was double-clicked: a core put down
|
||||
from this dialog lands on that line, never on the first line of a
|
||||
cable which stands on two folios
|
||||
@param parent
|
||||
*/
|
||||
CablePropertiesDialog::CablePropertiesDialog(Cable *cable,
|
||||
Diagram *diagram,
|
||||
CablePart *part,
|
||||
QWidget *parent) :
|
||||
QDialog(parent),
|
||||
m_cable(cable),
|
||||
m_diagram(diagram),
|
||||
m_part(part)
|
||||
{
|
||||
setWindowTitle(tr("Cable"));
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
m_fields = new CablePropertiesWidget(this);
|
||||
layout->addWidget(m_fields);
|
||||
|
||||
//The button beside the type field: it opens the window which
|
||||
//opens when a cable line has just been drawn, in the mode where
|
||||
//it changes the type of this very cable.
|
||||
connect(m_fields, &CablePropertiesWidget::typeChangeRequested,
|
||||
this, &CablePropertiesDialog::changeTypeRequested);
|
||||
|
||||
m_cores = new QLabel(this);
|
||||
m_cores->setWordWrap(true);
|
||||
layout->addWidget(m_cores);
|
||||
|
||||
//The cores the type offers which are not on the sheet yet.
|
||||
//Double-clicking one brings it in: the line it was asked from
|
||||
//takes the mouse and the next click puts the core down there.
|
||||
m_free_label = new QLabel(tr("Unplaced cores:"), this);
|
||||
m_free_cores = new QListWidget(this);
|
||||
m_free_cores->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_free_cores->setAlternatingRowColors(true);
|
||||
m_free_cores->setToolTip(tr("Double-click a core to place it on the "
|
||||
"line: the line follows the mouse and a "
|
||||
"click leaves it where you aimed."));
|
||||
m_free_cores->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_free_cores, &QWidget::customContextMenuRequested,
|
||||
this, &CablePropertiesDialog::freeCoreMenu);
|
||||
connect(m_free_cores, &QListWidget::itemDoubleClicked,
|
||||
this, &CablePropertiesDialog::placeRequested);
|
||||
connect(m_free_cores, &QListWidget::itemActivated,
|
||||
this, &CablePropertiesDialog::placeRequested);
|
||||
layout->addWidget(m_free_label);
|
||||
layout->addWidget(m_free_cores);
|
||||
|
||||
auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &CablePropertiesDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &CablePropertiesDialog::reject);
|
||||
layout->addWidget(buttons);
|
||||
|
||||
if (m_cable) {
|
||||
m_fields->setProperties(m_cable->properties());
|
||||
}
|
||||
updateCoreCount();
|
||||
|
||||
fillFreeCores();
|
||||
|
||||
setMinimumWidth(400);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesDialog::updateCoreCount
|
||||
Say how many cores this cable has now and how many of them are wired
|
||||
to a conductor. It is said again after the type was changed, since
|
||||
the new type may hold more cores -- or fewer.
|
||||
*/
|
||||
void CablePropertiesDialog::updateCoreCount()
|
||||
{
|
||||
if (!m_cores) return;
|
||||
|
||||
if (!m_cable)
|
||||
{
|
||||
m_cores->setText(tr("No cable."));
|
||||
return;
|
||||
}
|
||||
|
||||
m_cores->setText(tr("%1 of %2 cores wired to a conductor",
|
||||
"how many cores of this cable are wired",
|
||||
m_cable->usedCoreCount())
|
||||
.arg(m_cable->usedCoreCount())
|
||||
.arg(m_cable->coreCount()));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesDialog::changeTypeRequested
|
||||
Open the window which opens when a cable line has just been drawn,
|
||||
in the mode where it changes the type of this very cable: he picks
|
||||
the new type there out of the cable type file, and the cable takes
|
||||
it -- cores reworked, marks redrawn -- as one single undo step.
|
||||
|
||||
Whatever he typed into the form is written first, so the change and
|
||||
the typing cannot overwrite each other; and the form is filled in
|
||||
again afterwards, since pressing OK right after would otherwise
|
||||
write the old type back.
|
||||
*/
|
||||
void CablePropertiesDialog::changeTypeRequested()
|
||||
{
|
||||
if (!m_cable) return;
|
||||
|
||||
apply();
|
||||
|
||||
CableCreateDialog dialog(m_diagram, m_cable, this);
|
||||
if (dialog.exec() != QDialog::Accepted) return;
|
||||
|
||||
m_fields->setProperties(m_cable->properties());
|
||||
updateCoreCount();
|
||||
fillFreeCores();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesDialog::fillFreeCores
|
||||
List every core the type of this cable offers and which stands
|
||||
nowhere on the sheet: those are the ones he can still put down. The
|
||||
list is of no use when there is none, so it takes no room then.
|
||||
*/
|
||||
void CablePropertiesDialog::fillFreeCores()
|
||||
{
|
||||
if (!m_free_cores) return;
|
||||
|
||||
m_free_cores->clear();
|
||||
for (int i = 0; m_cable && i < m_cable->coreCount(); ++i)
|
||||
{
|
||||
if (m_cable->hasCore(i)) continue;
|
||||
|
||||
const QString color = m_cable->colorOfCore(i);
|
||||
auto *item = new QListWidgetItem(
|
||||
color.isEmpty() ? tr("Core %1").arg(i + 1)
|
||||
: tr("Core %1 — %2").arg(i + 1).arg(color),
|
||||
m_free_cores);
|
||||
item->setData(Qt::UserRole, i);
|
||||
}
|
||||
|
||||
const bool any = m_part && m_free_cores->count() > 0;
|
||||
m_free_label->setVisible(any);
|
||||
m_free_cores->setVisible(any);
|
||||
if (!any) return;
|
||||
|
||||
//As high as the entries it really shows, six of them at most:
|
||||
//a handful of free cores must not turn the dialog into a wall.
|
||||
const int row = qMax(m_free_cores->sizeHintForRow(0), 18);
|
||||
m_free_cores->setFixedHeight(row * qMin(6, m_free_cores->count())
|
||||
+ 2 * m_free_cores->frameWidth() + 4);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesDialog::placeRequested
|
||||
He picked one of the cores which stand nowhere: whatever is typed in
|
||||
the form is written first, the dialog closes, and the line he asked
|
||||
it from takes the mouse -- so the core goes onto that very line, and
|
||||
the next click leaves it standing there.
|
||||
@param item the entry he double-clicked
|
||||
*/
|
||||
void CablePropertiesDialog::placeRequested(QListWidgetItem *item)
|
||||
{
|
||||
//A double click reports itself twice on some platforms (once as
|
||||
//activated, once as double-clicked): the first one takes the
|
||||
//mouse away, the second must not ask for it again.
|
||||
if (!item || !m_part || m_placing) return;
|
||||
|
||||
m_placing = true;
|
||||
const int core = item->data(Qt::UserRole).toInt();
|
||||
QPointer<CablePart> part = m_part;
|
||||
|
||||
apply();
|
||||
QDialog::accept();
|
||||
|
||||
part->startPlacingCore(core);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesDialog::freeCoreMenu
|
||||
The small menu a right click on a core which stands nowhere opens:
|
||||
one entry, doing what a double click does -- this dialog writes
|
||||
whatever he typed, closes itself, the line he asked it from takes
|
||||
the mouse, the mark shows on that line at once and the mouse is put
|
||||
down there, so that the very next click leaves the core standing on
|
||||
that line.
|
||||
|
||||
@param pos where he right-clicked, in list coordinates
|
||||
*/
|
||||
void CablePropertiesDialog::freeCoreMenu(const QPoint &pos)
|
||||
{
|
||||
QListWidgetItem *item = m_free_cores ? m_free_cores->itemAt(pos) : nullptr;
|
||||
if (!item || !m_part || m_placing) return;
|
||||
|
||||
m_free_cores->setCurrentItem(item);
|
||||
|
||||
QMenu menu(m_free_cores);
|
||||
QAction *place = menu.addAction(tr("Place this core on the line"));
|
||||
place->setIcon(QET::Icons::Cable);
|
||||
|
||||
if (menu.exec(m_free_cores->viewport()->mapToGlobal(pos)) == place) {
|
||||
//Gone before he is sent off: this dialog is closing, the
|
||||
//mouse goes to the line and nothing may be left standing
|
||||
//over the place it is sent to.
|
||||
menu.hide();
|
||||
placeRequested(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesDialog::accept
|
||||
Whatever was typed here is written into the cable as one undo step,
|
||||
and the cable works out the cable field of every conductor it feeds
|
||||
again on its own: rename the cable and every drawing which shows it
|
||||
follows.
|
||||
*/
|
||||
void CablePropertiesDialog::accept()
|
||||
{
|
||||
apply();
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesDialog::apply
|
||||
Everything the form holds goes into the cable as one single undo
|
||||
step -- and only when something really changed, so that closing the
|
||||
dialog by any means leaves no step of undo to walk back through.
|
||||
*/
|
||||
void CablePropertiesDialog::apply()
|
||||
{
|
||||
if (!m_cable) return;
|
||||
|
||||
const CableProperties before = m_cable->properties();
|
||||
const CableProperties after = m_fields->properties();
|
||||
if (after != before)
|
||||
{
|
||||
if (m_diagram) {
|
||||
m_diagram->undoStack().push(
|
||||
new ChangeCablePropertiesCommand(m_cable, before, after));
|
||||
} else {
|
||||
m_cable->setProperties(after);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
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 CABLEPROPERTIESDIALOG_H
|
||||
#define CABLEPROPERTIESDIALOG_H
|
||||
|
||||
#include "cable.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QPointer>
|
||||
|
||||
class CablePropertiesWidget;
|
||||
class CablePart;
|
||||
class Diagram;
|
||||
class QLabel;
|
||||
class QListWidget;
|
||||
class QListWidgetItem;
|
||||
|
||||
/**
|
||||
@brief The dialog which edits a cable once it is drawn.
|
||||
|
||||
Everything it holds belongs to the cable itself rather than to the
|
||||
line which was drawn: whatever is typed here applies to every section
|
||||
of that cable, on every folio, and the conductors it feeds get their
|
||||
cable field worked out again as soon as it says it changed.
|
||||
|
||||
Under the count of cores it lists the cores of the type which stand
|
||||
nowhere on the sheet yet: double-clicking one sends it to the very
|
||||
line this dialog was asked from, which takes the mouse until he
|
||||
clicks and leaves the core standing there.
|
||||
*/
|
||||
class CablePropertiesDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
@param cable the cable being edited
|
||||
@param diagram the folio whose undo stack takes the edit
|
||||
@param part the very line which was double-clicked, where a
|
||||
core chosen in this dialog is to be put down
|
||||
@param parent
|
||||
*/
|
||||
explicit CablePropertiesDialog(Cable *cable,
|
||||
Diagram *diagram = nullptr,
|
||||
CablePart *part = nullptr,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
void accept() override;
|
||||
|
||||
private:
|
||||
///Everything the form holds goes into the cable, as one step
|
||||
void apply();
|
||||
///What the count of cores under the form says right now
|
||||
void updateCoreCount();
|
||||
///He asked for the window choosing another type for this cable
|
||||
void changeTypeRequested();
|
||||
///Lists the cores of the type which stand nowhere yet
|
||||
void fillFreeCores();
|
||||
///He double-clicked one of them: put it down on the line
|
||||
void placeRequested(QListWidgetItem *item);
|
||||
/**
|
||||
The small menu a right click on a core which stands
|
||||
nowhere opens: one entry doing what a double click does
|
||||
-- the dialog closes, the line takes the mouse, the mark
|
||||
shows on it at once and the mouse is put down there.
|
||||
@param pos where he right-clicked, in list coordinates
|
||||
*/
|
||||
void freeCoreMenu(const QPoint &pos);
|
||||
|
||||
private:
|
||||
QPointer<Cable> m_cable;
|
||||
Diagram *m_diagram = nullptr;
|
||||
///The line whose dialog this is, and the one a core chosen
|
||||
///here is put down on
|
||||
QPointer<CablePart> m_part;
|
||||
CablePropertiesWidget *m_fields = nullptr;
|
||||
QLabel *m_cores = nullptr;
|
||||
QLabel *m_free_label = nullptr;
|
||||
QListWidget *m_free_cores = nullptr;
|
||||
///True from the moment he picks a core to put down
|
||||
bool m_placing = false;
|
||||
};
|
||||
|
||||
#endif // CABLEPROPERTIESDIALOG_H
|
||||
@@ -0,0 +1,389 @@
|
||||
/*
|
||||
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 "cablepropertieseditorwidget.h"
|
||||
|
||||
#include "cablecreatedialog.h"
|
||||
#include "cablepart.h"
|
||||
#include "cablepropertieswidget.h"
|
||||
#include "editcablecommand.h"
|
||||
#include "../qeticons.h"
|
||||
#include "../qetproject.h"
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QMenu>
|
||||
#include <QSizePolicy>
|
||||
#include <QUndoStack>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::CablePropertiesEditorWidget
|
||||
@param cable the cable being edited
|
||||
@param part the line of that cable this panel was opened by, which
|
||||
is the line a core picked here is put down on
|
||||
@param parent parent widget
|
||||
*/
|
||||
CablePropertiesEditorWidget::CablePropertiesEditorWidget(Cable *cable,
|
||||
CablePart *part,
|
||||
QWidget *parent) :
|
||||
PropertiesEditorWidget(parent),
|
||||
m_fields(new CablePropertiesWidget(this)),
|
||||
m_info(new QLabel(this)),
|
||||
m_free_label(new QLabel(this)),
|
||||
m_free_cores(new QListWidget(this))
|
||||
{
|
||||
m_info->setWordWrap(true);
|
||||
|
||||
//The cores which stand nowhere yet, listed under the count of
|
||||
//wired ones: double-clicking one puts it down on this very line
|
||||
//without any dialog having to be opened for it. The list takes
|
||||
//no keyboard focus, so clicking it never takes the drawing away
|
||||
//from the user -- startPlacingCore() hands the focus to the
|
||||
//sheet itself, which is where Escape has to be heard.
|
||||
m_free_label->setText(tr("Unplaced cores:"));
|
||||
m_free_label->setVisible(false);
|
||||
m_free_cores->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_free_cores->setAlternatingRowColors(true);
|
||||
m_free_cores->setFocusPolicy(Qt::NoFocus);
|
||||
m_free_cores->setToolTip(tr("Double-click a core to place it on the "
|
||||
"line: the line follows the mouse and a "
|
||||
"click leaves it where you aimed."));
|
||||
m_free_cores->setVisible(false);
|
||||
m_free_cores->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_free_cores, &QWidget::customContextMenuRequested,
|
||||
this, &CablePropertiesEditorWidget::freeCoreMenu);
|
||||
connect(m_free_cores, &QListWidget::itemDoubleClicked,
|
||||
this, &CablePropertiesEditorWidget::placeRequested);
|
||||
connect(m_free_cores, &QListWidget::itemActivated,
|
||||
this, &CablePropertiesEditorWidget::placeRequested);
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(4);
|
||||
layout->addWidget(m_fields);
|
||||
|
||||
//The button beside the type field: it opens the window which
|
||||
//opens when a cable line has just been drawn, in the mode where
|
||||
//it changes the type of this very cable.
|
||||
connect(m_fields, &CablePropertiesWidget::typeChangeRequested,
|
||||
this, &CablePropertiesEditorWidget::changeTypeRequested);
|
||||
|
||||
layout->addWidget(m_info);
|
||||
layout->addWidget(m_free_label);
|
||||
layout->addWidget(m_free_cores);
|
||||
layout->addStretch(1);
|
||||
|
||||
//Wide enough for the labels, the ticks, the fields, the buttons
|
||||
//setting how a text is written and the two distances nudging
|
||||
//the label and the row of colours -- they sit side by side.
|
||||
setMinimumWidth(250);
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
||||
|
||||
setDisabled(true);
|
||||
setCable(cable, part);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::setCable
|
||||
Set (or change) the cable whose properties are edited.
|
||||
@param cable
|
||||
@param part the line of that cable this panel was opened by: the one
|
||||
a core picked in the list is put down on, and null while no single
|
||||
line is selected -- then nothing can be put down and the list stays
|
||||
out of the way
|
||||
*/
|
||||
void CablePropertiesEditorWidget::setCable(Cable *cable, CablePart *part)
|
||||
{
|
||||
if (!cable) return;
|
||||
m_part = part;
|
||||
if (m_cable && m_cable != cable)
|
||||
disconnect(m_cable, nullptr, this, nullptr);
|
||||
|
||||
m_cable = cable;
|
||||
//Keep the panel in sync when the cable is edited elsewhere (the
|
||||
//dialog reached by double-clicking its line, for one): otherwise
|
||||
//the panel would write back what it held before that edit.
|
||||
connect(m_cable, &Cable::changed,
|
||||
this, &CablePropertiesEditorWidget::updateUi, Qt::UniqueConnection);
|
||||
setEnabled(true);
|
||||
updateUi();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::setLiveEdit
|
||||
In live-edit mode (how the panel uses every editor) an edit is
|
||||
applied as soon as a field is finished, rather than waiting for a
|
||||
call to apply(). On top of that the text itself is written onto the
|
||||
cable with every character, so what he types shows up on the drawing
|
||||
while he is still typing it -- the whole editing of one field stays
|
||||
one single undo step, which is worked out when the field is left.
|
||||
@param live_edit true to enable live edit
|
||||
@return always true
|
||||
*/
|
||||
bool CablePropertiesEditorWidget::setLiveEdit(bool live_edit)
|
||||
{
|
||||
if (m_live_edit == live_edit) return true;
|
||||
m_live_edit = live_edit;
|
||||
|
||||
if (m_live_edit) {
|
||||
m_live_connection = connect(m_fields, &CablePropertiesWidget::propertiesEdited,
|
||||
this, &CablePropertiesEditorWidget::apply);
|
||||
connect(m_fields, &CablePropertiesWidget::propertiesTyped,
|
||||
this, &CablePropertiesEditorWidget::applyLive);
|
||||
} else {
|
||||
disconnect(m_live_connection);
|
||||
disconnect(m_fields, &CablePropertiesWidget::propertiesTyped,
|
||||
this, &CablePropertiesEditorWidget::applyLive);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::applyLive
|
||||
Put what is being typed straight onto the cable, without pushing an
|
||||
undo step: the whole editing of one field is one step, which
|
||||
apply() works out from what the panel showed when the field is left.
|
||||
*/
|
||||
void CablePropertiesEditorWidget::applyLive()
|
||||
{
|
||||
//Ignore the signals sent while the form is being filled in: at
|
||||
//that moment it holds a half loaded state.
|
||||
if (m_updating) return;
|
||||
if (!m_cable) return;
|
||||
|
||||
m_live_cable = m_cable;
|
||||
m_cable->setProperties(m_fields->properties());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::changeTypeRequested
|
||||
Open the window which opens when a cable line has just been drawn,
|
||||
in the mode where it changes the type of this very cable: he picks
|
||||
the new type there out of the cable type file, and the cable takes
|
||||
it -- cores reworked, marks redrawn -- as one single undo step.
|
||||
|
||||
The form is filled in again afterwards rather than through the usual
|
||||
path: the type has just changed under it, and it may not go on
|
||||
offering the one which was left.
|
||||
*/
|
||||
void CablePropertiesEditorWidget::changeTypeRequested()
|
||||
{
|
||||
if (!m_cable) return;
|
||||
|
||||
CableCreateDialog dialog(m_part ? m_part->diagram() : nullptr,
|
||||
m_cable,
|
||||
this);
|
||||
if (dialog.exec() != QDialog::Accepted) return;
|
||||
|
||||
m_updating = true;
|
||||
m_fields->setProperties(m_cable->properties());
|
||||
m_shown = m_cable->properties();
|
||||
m_updating = false;
|
||||
updateInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::apply
|
||||
Put what was typed onto the cable as one undo step.
|
||||
*/
|
||||
void CablePropertiesEditorWidget::apply()
|
||||
{
|
||||
//Ignore the signals sent while the form is being filled in: at
|
||||
//that moment it holds a half loaded state.
|
||||
if (m_updating) return;
|
||||
if (!m_cable) return;
|
||||
|
||||
if (QUndoCommand *undo = associatedUndo())
|
||||
{
|
||||
auto *project = qobject_cast<QETProject *>(m_cable->parent());
|
||||
if (project && project->undoStack()) {
|
||||
project->undoStack()->push(undo);
|
||||
} else {
|
||||
m_cable->setProperties(m_fields->properties());
|
||||
delete undo;
|
||||
}
|
||||
}
|
||||
m_shown = m_fields->properties();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::associatedUndo
|
||||
@return the edit as one undo step, or nullptr when nothing changed
|
||||
*/
|
||||
QUndoCommand *CablePropertiesEditorWidget::associatedUndo() const
|
||||
{
|
||||
if (!m_cable) return nullptr;
|
||||
|
||||
const CableProperties after = m_fields->properties();
|
||||
if (after == m_shown) return nullptr;
|
||||
return new ChangeCablePropertiesCommand(m_cable, m_shown, after);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::reset
|
||||
Discard the edit in progress, putting back what the panel showed.
|
||||
*/
|
||||
void CablePropertiesEditorWidget::reset()
|
||||
{
|
||||
if (!m_cable) return;
|
||||
m_updating = true;
|
||||
m_fields->setProperties(m_shown);
|
||||
m_updating = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::updateInfo
|
||||
Say how many cores of this cable are wired to a conductor.
|
||||
*/
|
||||
void CablePropertiesEditorWidget::updateInfo()
|
||||
{
|
||||
if (!m_cable) return;
|
||||
|
||||
m_info->setText(tr("%1 of %2 cores wired to a conductor",
|
||||
"how many cores of this cable are wired",
|
||||
m_cable->usedCoreCount())
|
||||
.arg(m_cable->usedCoreCount())
|
||||
.arg(m_cable->coreCount()));
|
||||
|
||||
updateFreeCores();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::updateFreeCores
|
||||
List the cores the type of this cable offers which stand nowhere on
|
||||
the sheet yet: those are the ones he can still put down, and they
|
||||
come down straight from here, on the line this panel was opened by.
|
||||
|
||||
The list is of no use when there is none -- and it cannot work at
|
||||
all when no single line of the cable is the selected one, since a
|
||||
core then has no line to be put down on -- so it takes no room then.
|
||||
*/
|
||||
void CablePropertiesEditorWidget::updateFreeCores()
|
||||
{
|
||||
if (!m_free_cores) return;
|
||||
|
||||
const bool usable = m_cable && m_part && m_part->cable() == m_cable;
|
||||
|
||||
m_free_cores->clear();
|
||||
if (usable)
|
||||
{
|
||||
for (int i = 0; i < m_cable->coreCount(); ++i)
|
||||
{
|
||||
if (m_cable->hasCore(i)) continue;
|
||||
|
||||
const QString color = m_cable->colorOfCore(i);
|
||||
auto *item = new QListWidgetItem(
|
||||
color.isEmpty() ? tr("Core %1").arg(i + 1)
|
||||
: tr("Core %1 — %2").arg(i + 1).arg(color),
|
||||
m_free_cores);
|
||||
item->setData(Qt::UserRole, i);
|
||||
}
|
||||
}
|
||||
|
||||
const bool any = usable && m_free_cores->count() > 0;
|
||||
m_free_label->setVisible(any);
|
||||
m_free_cores->setVisible(any);
|
||||
if (!any) return;
|
||||
|
||||
//As high as the entries it really shows, four of them at most:
|
||||
//the dock is small, and a handful of free cores must not turn
|
||||
//it into a wall.
|
||||
const int row = qMax(m_free_cores->sizeHintForRow(0), 18);
|
||||
m_free_cores->setFixedHeight(row * qMin(4, m_free_cores->count())
|
||||
+ 2 * m_free_cores->frameWidth() + 4);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::placeRequested
|
||||
He double-clicked one of the cores which stand nowhere: this very
|
||||
line takes the mouse and waits for the click which puts the core
|
||||
down. There is no dialog to close here -- the panel stays where it
|
||||
is, and it reads the cable back as soon as the core lands.
|
||||
|
||||
@param item the entry he double-clicked
|
||||
*/
|
||||
void CablePropertiesEditorWidget::placeRequested(QListWidgetItem *item)
|
||||
{
|
||||
if (!item || !m_part) return;
|
||||
|
||||
m_part->startPlacingCore(item->data(Qt::UserRole).toInt());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::freeCoreMenu
|
||||
The small menu a right click on a core which stands nowhere opens:
|
||||
one entry, doing what a double click does -- the line takes the
|
||||
mouse, the mark shows on it at once and the mouse is put down there,
|
||||
so the very next click leaves the core standing on that line.
|
||||
|
||||
@param pos where he right-clicked, in list coordinates
|
||||
*/
|
||||
void CablePropertiesEditorWidget::freeCoreMenu(const QPoint &pos)
|
||||
{
|
||||
QListWidgetItem *item = m_free_cores ? m_free_cores->itemAt(pos) : nullptr;
|
||||
if (!item || !m_part) return;
|
||||
|
||||
m_free_cores->setCurrentItem(item);
|
||||
|
||||
QMenu menu(m_free_cores);
|
||||
QAction *place = menu.addAction(tr("Place this core on the line"));
|
||||
place->setIcon(QET::Icons::Cable);
|
||||
|
||||
if (menu.exec(m_free_cores->viewport()->mapToGlobal(pos)) == place) {
|
||||
//Gone before he is sent off: the mouse goes to the line
|
||||
//and nothing may be left standing over the place it is
|
||||
//sent to.
|
||||
menu.hide();
|
||||
placeRequested(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::updateUi
|
||||
Read the cable back into the panel.
|
||||
|
||||
Not while a field is being typed in: writing what was just typed
|
||||
back into that field would take the cursor away from the user with
|
||||
every key, and it would take back the state the whole editing of
|
||||
that field is measured against when the undo step is worked out.
|
||||
*/
|
||||
void CablePropertiesEditorWidget::updateUi()
|
||||
{
|
||||
if (!m_cable) return;
|
||||
|
||||
if (m_fields->editing() && m_live_cable == m_cable) {
|
||||
updateInfo();
|
||||
return;
|
||||
}
|
||||
|
||||
m_updating = true;
|
||||
m_fields->setProperties(m_cable->properties());
|
||||
m_shown = m_cable->properties();
|
||||
updateInfo();
|
||||
m_updating = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesEditorWidget::title
|
||||
@return the panel title
|
||||
*/
|
||||
QString CablePropertiesEditorWidget::title() const
|
||||
{
|
||||
return tr("Cable");
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
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 CABLEPROPERTIESEDITORWIDGET_H
|
||||
#define CABLEPROPERTIESEDITORWIDGET_H
|
||||
|
||||
#include "cable.h"
|
||||
|
||||
#include "../PropertiesEditor/propertieseditorwidget.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QMetaObject>
|
||||
#include <QPointer>
|
||||
|
||||
class CablePropertiesWidget;
|
||||
class CablePart;
|
||||
class QLabel;
|
||||
class QListWidget;
|
||||
class QListWidgetItem;
|
||||
|
||||
/**
|
||||
@brief The cable shown in the "Propriétés de la sélection" panel.
|
||||
|
||||
Selecting a line of a cable used to bring up nothing at all there:
|
||||
the panel only knew the item types it already had an editor for.
|
||||
Now it holds the very fields the cable is filled in with -- each of
|
||||
them with its own tick saying whether the drawing shows it -- and
|
||||
what stands there is written back live, one undo step per edit.
|
||||
*/
|
||||
class CablePropertiesEditorWidget : public PropertiesEditorWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CablePropertiesEditorWidget(Cable *cable = nullptr,
|
||||
CablePart *part = nullptr,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
void setCable(Cable *cable, CablePart *part = nullptr);
|
||||
|
||||
void apply() override;
|
||||
void reset() override;
|
||||
void updateUi() override;
|
||||
QUndoCommand *associatedUndo() const override;
|
||||
QString title() const override;
|
||||
bool setLiveEdit(bool live_edit) override;
|
||||
|
||||
private:
|
||||
///Write what is being typed onto the cable right away
|
||||
void applyLive();
|
||||
///He asked for the window choosing another type for this cable
|
||||
void changeTypeRequested();
|
||||
///Say how many cores of this cable are wired
|
||||
void updateInfo();
|
||||
/**
|
||||
List the cores which stand nowhere on the sheet yet, under
|
||||
the count of wired ones: those are the ones he can still
|
||||
put down, and double-clicking one puts it down straight
|
||||
from here, on the line this panel was opened for.
|
||||
*/
|
||||
void updateFreeCores();
|
||||
///He double-clicked one of those cores: give it this line
|
||||
void placeRequested(QListWidgetItem *item);
|
||||
/**
|
||||
He right-clicked one of them: a small menu with the one
|
||||
thing which can be done to a core which stands nowhere
|
||||
-- putting it down -- for whoever does not know that a
|
||||
double click does the same.
|
||||
@param pos where he clicked, in list coordinates
|
||||
*/
|
||||
void freeCoreMenu(const QPoint &pos);
|
||||
|
||||
private:
|
||||
CablePropertiesWidget *m_fields = nullptr;
|
||||
QLabel *m_info = nullptr;
|
||||
///The cores which stand nowhere yet, listed under the count
|
||||
QLabel *m_free_label = nullptr;
|
||||
QListWidget *m_free_cores = nullptr;
|
||||
QPointer<Cable> m_cable;
|
||||
/**
|
||||
The line this panel was opened by, which is the one a core
|
||||
picked here is put down on: taking a core always belongs to
|
||||
the line whose dialog was asked for, never to the first
|
||||
line of the cable. Null while no single line is selected,
|
||||
and then there is nothing to put a core down on and the
|
||||
list takes no room.
|
||||
*/
|
||||
QPointer<CablePart> m_part;
|
||||
///What the panel showed before the edit: the fields which
|
||||
///differ from it are the ones the user changed
|
||||
CableProperties m_shown;
|
||||
///The cable the text is being typed straight into, so that
|
||||
///reading the cable back only skips the very field which is
|
||||
///under the fingers of the user -- and never for another
|
||||
///cable which has just become the selected one
|
||||
QPointer<Cable> m_live_cable;
|
||||
QMetaObject::Connection m_live_connection;
|
||||
bool m_updating = false;
|
||||
};
|
||||
|
||||
#endif // CABLEPROPERTIESEDITORWIDGET_H
|
||||
@@ -0,0 +1,409 @@
|
||||
/*
|
||||
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 "cablepropertieswidget.h"
|
||||
|
||||
#include "cabletextformatdialog.h"
|
||||
#include "../qetapp.h"
|
||||
#include "../qeticons.h"
|
||||
#include "../utils/qetutils.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCheckBox>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QSignalBlocker>
|
||||
#include <QSpinBox>
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::CablePropertiesWidget
|
||||
One row per field: its name, the tick saying whether the drawing
|
||||
shows it, the field itself, then the button setting how that one
|
||||
text is written -- one last row for the colours of the cores, which
|
||||
are written all together along the line, and one row per distance
|
||||
he may nudge the whole label and that row of colours with.
|
||||
@param parent
|
||||
*/
|
||||
CablePropertiesWidget::CablePropertiesWidget(QWidget *parent) :
|
||||
QWidget(parent)
|
||||
{
|
||||
auto *grid = new QGridLayout(this);
|
||||
grid->setContentsMargins(0, 0, 0, 0);
|
||||
grid->setHorizontalSpacing(6);
|
||||
grid->setVerticalSpacing(3);
|
||||
|
||||
auto *plan = new QLabel(tr("Drawing",
|
||||
"column header: the drawing shows this field"), this);
|
||||
plan->setAlignment(Qt::AlignCenter);
|
||||
plan->setToolTip(tr("Tick to write this field on the drawing.",
|
||||
"tooltip of the tick before each field"));
|
||||
grid->addWidget(plan, 0, 1, Qt::AlignCenter);
|
||||
|
||||
int row = 1;
|
||||
addField(grid, row++, QStringLiteral("designation"),
|
||||
tr("Designation"), &m_designation, &m_show_designation);
|
||||
const int type_row = row;
|
||||
addField(grid, row++, QStringLiteral("type"),
|
||||
tr("Type"), &m_type, &m_show_type);
|
||||
|
||||
//A button beside the type field, opening the very window which
|
||||
//opens when a cable line has just been drawn: the type is
|
||||
//picked from the cable type file there -- with its search, its
|
||||
//filter and its own button for a type the file does not hold
|
||||
//yet -- rather than typed in as plain text.
|
||||
auto *pick_type = new QPushButton(this);
|
||||
pick_type->setFocusPolicy(Qt::NoFocus);
|
||||
pick_type->setIcon(QET::Icons::Cable);
|
||||
pick_type->setToolTip(tr("Choose the type of this cable from the list of "
|
||||
"cable types…"));
|
||||
connect(pick_type, &QPushButton::clicked,
|
||||
this, &CablePropertiesWidget::typeChangeRequested);
|
||||
grid->addWidget(pick_type, type_row, 4, Qt::AlignRight | Qt::AlignVCenter);
|
||||
|
||||
addField(grid, row++, QStringLiteral("installation"),
|
||||
tr("Plant (=)"), &m_installation, &m_show_installation);
|
||||
addField(grid, row++, QStringLiteral("location"),
|
||||
tr("Location (+)"), &m_location, &m_show_location);
|
||||
addField(grid, row++, QStringLiteral("length"),
|
||||
tr("Length"), &m_length, &m_show_length);
|
||||
|
||||
//The colours of the cores: one setting for every one of them at
|
||||
//once, since they are written in one row along the line and are
|
||||
//read as one row.
|
||||
auto *cores_label = new QLabel(tr("Core colors"), this);
|
||||
cores_label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
cores_label->setToolTip(tr("The font of the colors of all the cores "
|
||||
"of this cable, written along the line."));
|
||||
grid->addWidget(cores_label, row, 0, Qt::AlignRight | Qt::AlignVCenter);
|
||||
addFormatButton(grid, row, QStringLiteral("cores"));
|
||||
|
||||
//Where that whole label stands: one distance along the line and
|
||||
//one across it, in pixels, moving the label as one thing --
|
||||
//installation, place and designation over the line, type and
|
||||
//length under it -- and the line not at all.
|
||||
addOffsetRow(grid, row + 1, tr("Text position"),
|
||||
tr("Shifts all the cable text: plant, location "
|
||||
"and designation on one side, type and length "
|
||||
"on the other. The line, the slashes and the "
|
||||
"wiring stay in place. 10 px make one grid cell."),
|
||||
&m_text_offset_x, &m_text_offset_y);
|
||||
|
||||
//And where the row of colours stands from the slashes it names.
|
||||
addOffsetRow(grid, row + 2, tr("Core colors position"),
|
||||
tr("Shifts the core colors relative to their slashes. "
|
||||
"The slashes, the entries and the wiring stay in place. "
|
||||
"10 px make one grid cell."),
|
||||
&m_core_offset_x, &m_core_offset_y);
|
||||
|
||||
grid->setColumnStretch(2, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::addField
|
||||
Build one row of the form.
|
||||
@param grid the form being built
|
||||
@param row where to put it
|
||||
@param key the name this text goes by in the file
|
||||
@param name what the field holds
|
||||
@param edit receives the line edit
|
||||
@param tick receives the tick before it
|
||||
*/
|
||||
void CablePropertiesWidget::addField(QGridLayout *grid,
|
||||
int row,
|
||||
const QString &key,
|
||||
const QString &name,
|
||||
QLineEdit **edit,
|
||||
QCheckBox **tick)
|
||||
{
|
||||
auto *label = new QLabel(name, this);
|
||||
auto *check = new QCheckBox(this);
|
||||
check->setToolTip(tr("Tick to write this field on the drawing.",
|
||||
"tooltip of the tick before each field"));
|
||||
auto *field = new QLineEdit(this);
|
||||
field->setClearButtonEnabled(true);
|
||||
|
||||
connect(field, &QLineEdit::editingFinished,
|
||||
this, &CablePropertiesWidget::propertiesEdited);
|
||||
//Every character too: what is typed is written onto the cable
|
||||
//as it stands, so the drawing follows while he is still typing
|
||||
//rather than only once he leaves the field.
|
||||
connect(field, &QLineEdit::textChanged,
|
||||
this, &CablePropertiesWidget::propertiesTyped);
|
||||
connect(check, &QCheckBox::toggled,
|
||||
this, &CablePropertiesWidget::propertiesEdited);
|
||||
|
||||
grid->addWidget(label, row, 0, Qt::AlignRight | Qt::AlignVCenter);
|
||||
grid->addWidget(check, row, 1, Qt::AlignCenter);
|
||||
grid->addWidget(field, row, 2);
|
||||
addFormatButton(grid, row, key);
|
||||
|
||||
*edit = field;
|
||||
*tick = check;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::addFormatButton
|
||||
The small button standing at the right of a text: it says which
|
||||
size that text is written with, and opens the window where that
|
||||
size -- and the rest of how the text is written -- is set.
|
||||
@param grid the form being built
|
||||
@param row where to put it
|
||||
@param key the name of the text
|
||||
@return the button, so the caller may reach it again
|
||||
*/
|
||||
QPushButton *CablePropertiesWidget::addFormatButton(QGridLayout *grid,
|
||||
int row,
|
||||
const QString &key)
|
||||
{
|
||||
auto *button = new QPushButton(this);
|
||||
button->setFocusPolicy(Qt::NoFocus);
|
||||
connect(button, &QPushButton::clicked, this,
|
||||
[this, key]() {editFormat(key);});
|
||||
|
||||
grid->addWidget(button, row, 3, Qt::AlignRight | Qt::AlignVCenter);
|
||||
m_format_buttons.insert(key, button);
|
||||
refreshFormatButton(key);
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::addOffsetRow
|
||||
Build one of the two rows nudging what is drawn: a name at the left
|
||||
of the form, then two distances in pixels -- one measured along the
|
||||
line, one across it -- the way the cross references of a slave offer
|
||||
theirs.
|
||||
|
||||
Both boxes speak of the drawing and nothing else: what he types here
|
||||
moves texts, and the line, the slashes and the wires behind them keep
|
||||
standing exactly where they were.
|
||||
@param grid the form being built
|
||||
@param row where to put it
|
||||
@param name what that distance moves
|
||||
@param tip what it does and does not touch
|
||||
@param dx receives the spin box for the distance along the line
|
||||
@param dy receives the spin box for the distance across it
|
||||
*/
|
||||
void CablePropertiesWidget::addOffsetRow(QGridLayout *grid,
|
||||
int row,
|
||||
const QString &name,
|
||||
const QString &tip,
|
||||
QSpinBox **dx,
|
||||
QSpinBox **dy)
|
||||
{
|
||||
auto *label = new QLabel(name, this);
|
||||
label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
label->setToolTip(tip);
|
||||
|
||||
const auto spin = [this, tip]() {
|
||||
auto *box = new QSpinBox(this);
|
||||
box->setRange(-200, 200);
|
||||
box->setSingleStep(1);
|
||||
box->setSuffix(QStringLiteral(" px"));
|
||||
box->setAlignment(Qt::AlignRight);
|
||||
box->setToolTip(tip);
|
||||
connect(box, &QSpinBox::valueChanged,
|
||||
this, &CablePropertiesWidget::propertiesEdited);
|
||||
return box;
|
||||
};
|
||||
QSpinBox *x_box = spin();
|
||||
QSpinBox *y_box = spin();
|
||||
|
||||
auto *along = new QLabel(tr("X :"), this);
|
||||
along->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
auto *across = new QLabel(tr("Y :"), this);
|
||||
across->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
|
||||
auto *values = new QHBoxLayout;
|
||||
values->setContentsMargins(0, 0, 0, 0);
|
||||
values->setSpacing(4);
|
||||
values->addWidget(along);
|
||||
values->addWidget(x_box);
|
||||
values->addWidget(across);
|
||||
values->addWidget(y_box);
|
||||
values->addStretch(1);
|
||||
|
||||
grid->addWidget(label, row, 0, Qt::AlignRight | Qt::AlignVCenter);
|
||||
grid->addLayout(values, row, 2);
|
||||
|
||||
*dx = x_box;
|
||||
*dy = y_box;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::editFormat
|
||||
Open the window where one text of the cable is set, and -- when he
|
||||
came out of it having really changed something -- write the answer
|
||||
back into the form. One window is one change, so one step of undo.
|
||||
@param key the name of the text
|
||||
*/
|
||||
void CablePropertiesWidget::editFormat(const QString &key)
|
||||
{
|
||||
CableTextFormatDialog dialog(m_formats.value(key), coresKey(key), this);
|
||||
if (dialog.exec() != QDialog::Accepted) return;
|
||||
|
||||
const CableTextFormat answer = dialog.format();
|
||||
if (m_formats.value(key) == answer) return;
|
||||
|
||||
if (answer == CableTextFormat()) m_formats.remove(key);
|
||||
else m_formats.insert(key, answer);
|
||||
|
||||
refreshFormatButton(key);
|
||||
emit propertiesEdited();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::refreshFormatButton
|
||||
Let the button of one text say which size that text is written with,
|
||||
so that the size is read off the form rather than only in the
|
||||
drawing.
|
||||
@param key the name of the text
|
||||
*/
|
||||
void CablePropertiesWidget::refreshFormatButton(const QString &key)
|
||||
{
|
||||
QPushButton *button = m_format_buttons.value(key);
|
||||
if (!button) return;
|
||||
|
||||
const QFont font = effectiveFont(key);
|
||||
button->setText(tr("%1 pt").arg(QString::number(font.pointSizeF(), 'g', 3)));
|
||||
|
||||
const QString tip = coresKey(key)
|
||||
? tr("Font, size and style of the core colors.")
|
||||
: tr("Font, size, style and alignment of this text.");
|
||||
const CableTextFormat format = m_formats.value(key);
|
||||
const bool its_own = !format.font.isEmpty() || format.alignment != 0;
|
||||
button->setToolTip(tip + QLatin1Char(' ') + (its_own
|
||||
? tr("Set for this cable.")
|
||||
: tr("Value defined in the preferences.")));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::effectiveFont
|
||||
@param key the name of a text of the form
|
||||
@return the font that text really is written with on the drawing:
|
||||
the one this cable asks for, or -- when it asks for none -- the one
|
||||
the preferences offer
|
||||
*/
|
||||
QFont CablePropertiesWidget::effectiveFont(const QString &key) const
|
||||
{
|
||||
const CableTextFormat format = m_formats.value(key);
|
||||
if (!format.font.isEmpty())
|
||||
{
|
||||
QFont font;
|
||||
if (QETUtils::fontFromString(font, format.font)) {
|
||||
return font;
|
||||
}
|
||||
}
|
||||
return coresKey(key) ? QETApp::cableCoreFont() : QETApp::cableTextsFont();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::properties
|
||||
@return everything the form holds right now
|
||||
*/
|
||||
CableProperties CablePropertiesWidget::properties() const
|
||||
{
|
||||
CableProperties properties;
|
||||
properties.designation = m_designation->text().trimmed();
|
||||
//A name which is not the one read from the cable can only have
|
||||
//come from him typing it in: from that moment on the cable
|
||||
//carries a name of his own rather than one the numbering rule
|
||||
//handed out. Putting the very same name back by hand keeps the
|
||||
//cable as it stood, which is what the field says.
|
||||
properties.designation_by_hand = properties.designation == m_designation_text
|
||||
? m_designation_by_hand : true;
|
||||
properties.type = m_type->text().trimmed();
|
||||
properties.installation = m_installation->text().trimmed();
|
||||
properties.location = m_location->text().trimmed();
|
||||
properties.length = m_length->text().trimmed();
|
||||
properties.show_designation = m_show_designation->isChecked();
|
||||
properties.show_type = m_show_type->isChecked();
|
||||
properties.show_installation = m_show_installation->isChecked();
|
||||
properties.show_location = m_show_location->isChecked();
|
||||
properties.show_length = m_show_length->isChecked();
|
||||
properties.texts = m_formats;
|
||||
properties.text_offset = QPointF(m_text_offset_x->value(),
|
||||
m_text_offset_y->value());
|
||||
properties.core_offset = QPointF(m_core_offset_x->value(),
|
||||
m_core_offset_y->value());
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::setProperties
|
||||
Fill the form in. The ticks say what is drawn, so a field which is
|
||||
switched off still keeps the text standing in it -- hiding it on the
|
||||
drawing never throws away what the user typed.
|
||||
@param properties
|
||||
*/
|
||||
void CablePropertiesWidget::setProperties(const CableProperties &properties)
|
||||
{
|
||||
m_designation->setText(properties.designation);
|
||||
//What the field holds now is what the cable said: anything he
|
||||
//types from here on is a name of his own
|
||||
m_designation_text = properties.designation;
|
||||
m_designation_by_hand = properties.designation_by_hand;
|
||||
m_type->setText(properties.type);
|
||||
m_installation->setText(properties.installation);
|
||||
m_location->setText(properties.location);
|
||||
m_length->setText(properties.length);
|
||||
m_show_designation->setChecked(properties.show_designation);
|
||||
m_show_type->setChecked(properties.show_type);
|
||||
m_show_installation->setChecked(properties.show_installation);
|
||||
m_show_location->setChecked(properties.show_location);
|
||||
m_show_length->setChecked(properties.show_length);
|
||||
|
||||
//Filling the form in is not the user nudging anything: those
|
||||
//two rows are muted while they are set, so that loading a cable
|
||||
//can never look like an edit which then has to be undone.
|
||||
const QSignalBlocker block_text_x(m_text_offset_x);
|
||||
const QSignalBlocker block_text_y(m_text_offset_y);
|
||||
const QSignalBlocker block_core_x(m_core_offset_x);
|
||||
const QSignalBlocker block_core_y(m_core_offset_y);
|
||||
m_text_offset_x->setValue(qRound(properties.text_offset.x()));
|
||||
m_text_offset_y->setValue(qRound(properties.text_offset.y()));
|
||||
m_core_offset_x->setValue(qRound(properties.core_offset.x()));
|
||||
m_core_offset_y->setValue(qRound(properties.core_offset.y()));
|
||||
|
||||
//The texts this cable writes its own way. Only what really has
|
||||
//an answer of its own is kept, so that a stray empty entry can
|
||||
//never make the form claim a change it never made.
|
||||
m_formats.clear();
|
||||
for (auto it = properties.texts.cbegin(); it != properties.texts.cend(); ++it)
|
||||
{
|
||||
if (it.value() == CableTextFormat()) continue;
|
||||
m_formats.insert(it.key(), it.value());
|
||||
}
|
||||
for (auto it = m_format_buttons.cbegin(); it != m_format_buttons.cend(); ++it) {
|
||||
refreshFormatButton(it.key());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CablePropertiesWidget::editing
|
||||
@return true when the keyboard stands in one of the fields of this
|
||||
form. The panel uses it to keep its hands off what is being typed:
|
||||
writing the cable back into the form while he is typing would take
|
||||
the cursor away from him with every key.
|
||||
*/
|
||||
bool CablePropertiesWidget::editing() const
|
||||
{
|
||||
QWidget *focus = QApplication::focusWidget();
|
||||
return focus && (focus == this || isAncestorOf(focus));
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
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 CABLEPROPERTIESWIDGET_H
|
||||
#define CABLEPROPERTIESWIDGET_H
|
||||
|
||||
#include "cable.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QCheckBox;
|
||||
class QGridLayout;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
class QSpinBox;
|
||||
|
||||
/**
|
||||
@brief The fields a cable is filled in with, and the tick before each
|
||||
of them saying whether what stands there is written on the drawing.
|
||||
|
||||
It holds no cable of its own: it is a plain form, filled in with
|
||||
setProperties() and read back with properties(). That way the dialog
|
||||
editing one cable and the selection panel of QElectroTech offer
|
||||
exactly the same fields in the same order, and neither of them can
|
||||
drift away from the other.
|
||||
|
||||
Every field is followed by a checkbox: what the checkbox says is
|
||||
written next to the trunk line of the drawing, what it does not say
|
||||
stays in the cable alone and only shows up in lists.
|
||||
|
||||
Behind every field stands a button opening the window where that one
|
||||
text is written: its font, its size, its style and -- for the texts
|
||||
drawn next to the line -- the way it lines up there. The colours of
|
||||
the cores get a button of their own at the foot of the form, all of
|
||||
them at once, because they are written in one row along the line and
|
||||
are never set apart.
|
||||
|
||||
The type field carries one more button, on the right of that row. It
|
||||
does not say how the word is written: it asks for the window which
|
||||
opens when a cable line has just been drawn, where a type is picked
|
||||
out of the cable type file -- with its search, its filter and its own
|
||||
button for a type the file does not hold yet -- and taken onto the
|
||||
cable. Whoever holds this form answers typeChangeRequested().
|
||||
|
||||
At the foot of the form stand the two distances he may nudge: one for
|
||||
the whole label, one for the row of colours. Both are on the drawing
|
||||
only -- the line, the slashes and the wiring stay where they are.
|
||||
*/
|
||||
class CablePropertiesWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CablePropertiesWidget(QWidget *parent = nullptr);
|
||||
|
||||
///What the form holds right now
|
||||
CableProperties properties() const;
|
||||
///Fill the form in
|
||||
void setProperties(const CableProperties &properties);
|
||||
///True while one of the fields is being typed in
|
||||
bool editing() const;
|
||||
|
||||
signals:
|
||||
///Emitted when a field or a tick was really changed by hand
|
||||
void propertiesEdited();
|
||||
///Emitted with every character typed, so the drawing can
|
||||
///follow the text as it is written rather than only once the
|
||||
///field is left
|
||||
void propertiesTyped();
|
||||
///Emitted when he asks for the window choosing the type of
|
||||
///this cable out of the cable type file
|
||||
void typeChangeRequested();
|
||||
|
||||
private:
|
||||
void addField(QGridLayout *grid,
|
||||
int row,
|
||||
const QString &key,
|
||||
const QString &name,
|
||||
QLineEdit **edit,
|
||||
QCheckBox **tick);
|
||||
///The button opening the window which sets one text
|
||||
QPushButton *addFormatButton(QGridLayout *grid, int row, const QString &key);
|
||||
/**
|
||||
Build one row of the two spin boxes nudging what is
|
||||
drawn: one for the label of the cable, one for the row
|
||||
of its colours.
|
||||
@param grid the form being built
|
||||
@param row where to put it
|
||||
@param name what the row moves
|
||||
@param tip what that distance does and does not touch
|
||||
@param dx receives the spin box for the distance along
|
||||
the line
|
||||
@param dy receives the spin box for the distance across
|
||||
it
|
||||
*/
|
||||
void addOffsetRow(QGridLayout *grid,
|
||||
int row,
|
||||
const QString &name,
|
||||
const QString &tip,
|
||||
QSpinBox **dx,
|
||||
QSpinBox **dy);
|
||||
///Open the window setting one text, then say so if it changed
|
||||
void editFormat(const QString &key);
|
||||
///Let the button say which size that text is written with
|
||||
void refreshFormatButton(const QString &key);
|
||||
///The font this text is really written with right now
|
||||
QFont effectiveFont(const QString &key) const;
|
||||
///True when this key names the colours of the cores
|
||||
static bool coresKey(const QString &key) {return key == QLatin1String("cores");}
|
||||
|
||||
private:
|
||||
QLineEdit *m_designation = nullptr;
|
||||
/**
|
||||
The name exactly as it was read from the cable, and
|
||||
whether that name was typed in: anything else he puts
|
||||
into the field from then on is his own work, and the
|
||||
cable is marked as carrying a name he chose (see
|
||||
properties())
|
||||
*/
|
||||
QString m_designation_text;
|
||||
bool m_designation_by_hand = false;
|
||||
QLineEdit *m_type = nullptr;
|
||||
QLineEdit *m_installation = nullptr;
|
||||
QLineEdit *m_location = nullptr;
|
||||
QLineEdit *m_length = nullptr;
|
||||
QCheckBox *m_show_designation = nullptr;
|
||||
QCheckBox *m_show_type = nullptr;
|
||||
QCheckBox *m_show_installation = nullptr;
|
||||
QCheckBox *m_show_location = nullptr;
|
||||
QCheckBox *m_show_length = nullptr;
|
||||
///What each text of this form is written with; a text which
|
||||
///is not in here follows what the preferences say
|
||||
QHash<QString, CableTextFormat> m_formats;
|
||||
///The button opening the window of each text, by name
|
||||
QHash<QString, QPushButton *> m_format_buttons;
|
||||
///How far the label of the cable is nudged on the drawing
|
||||
QSpinBox *m_text_offset_x = nullptr;
|
||||
QSpinBox *m_text_offset_y = nullptr;
|
||||
///How far the row of colours is nudged on the drawing
|
||||
QSpinBox *m_core_offset_x = nullptr;
|
||||
QSpinBox *m_core_offset_y = nullptr;
|
||||
};
|
||||
|
||||
#endif // CABLEPROPERTIESWIDGET_H
|
||||
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
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 "cablerenumber.h"
|
||||
|
||||
#include "cable.h"
|
||||
#include "../autoNum/assignvariables.h"
|
||||
#include "../autoNum/numerotationcontextcommands.h"
|
||||
#include "../diagram.h"
|
||||
#include "../qetproject.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QSet>
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
@brief One cable of the project, and where it stands so that the
|
||||
cables can be laid out in an order everybody can see again.
|
||||
*/
|
||||
struct OrderedCable
|
||||
{
|
||||
Cable *cable = nullptr;
|
||||
///The lowest folio the cable is drawn on
|
||||
int folio = 0;
|
||||
///How far it reaches to the left and how far to the top on
|
||||
///that folio
|
||||
qreal left = 0.0;
|
||||
qreal top = 0.0;
|
||||
///The folio the rule is read on for this cable
|
||||
Diagram *diagram = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Read a rule once, the way the numbering of a cable reads it.
|
||||
|
||||
The rule is read on a copy, since reading it fills in what its
|
||||
folio-bound parts are worth on that one folio, and that must not
|
||||
creep into the rule itself.
|
||||
@param context the rule as it stands at this point of the numbering
|
||||
@param diagram the folio the number is worked out on
|
||||
@param key the name the rule is kept under in the project
|
||||
@return the number, empty when the rule says nothing here
|
||||
*/
|
||||
QString readRule(NumerotationContext context, Diagram *diagram, const QString &key)
|
||||
{
|
||||
const QString formula = autonum::numerotationContextToFormula(context);
|
||||
autonum::sequentialNumbers seq;
|
||||
autonum::setSequential(formula, seq, context, diagram, key);
|
||||
return autonum::AssignVariables::formulaToLabel(formula, seq, diagram);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
@brief CableRenumber::byHandCount
|
||||
@param project
|
||||
@return how many cables of that project carry a name typed in by hand
|
||||
*/
|
||||
int CableRenumber::byHandCount(QETProject *project)
|
||||
{
|
||||
if (!project) return 0;
|
||||
|
||||
int count = 0;
|
||||
for (Cable *cable : project->cables())
|
||||
{
|
||||
if (cable && cable->designationByHand()) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableRenumber::plan
|
||||
Work the number of every cable of the project out, without writing
|
||||
anywhere. The cables are put in the order they are numbered in --
|
||||
folio by folio, and on each folio along whichever axis leads -- and
|
||||
the rule is then read from its beginning once for each of them, so
|
||||
that the numbering runs on without a hole.
|
||||
@param project the project whose cables are numbered
|
||||
@param rule the numbering rule to read
|
||||
@param x_axis_first true when the leftmost cable of a folio comes first
|
||||
@param include_by_hand false to leave the names typed in by hand alone
|
||||
@return the numbers, or what stopped the numbering
|
||||
*/
|
||||
CableRenumberPlan CableRenumber::plan(QETProject *project,
|
||||
const NumerotationContext &rule,
|
||||
bool x_axis_first,
|
||||
bool include_by_hand)
|
||||
{
|
||||
CableRenumberPlan result;
|
||||
if (!project)
|
||||
{
|
||||
result.error = QCoreApplication::translate(
|
||||
"CableRenumber", "No project is open.");
|
||||
return result;
|
||||
}
|
||||
if (project->isReadOnly())
|
||||
{
|
||||
result.error = QCoreApplication::translate(
|
||||
"CableRenumber",
|
||||
"This project is read-only: its cables cannot be "
|
||||
"renumbered.");
|
||||
return result;
|
||||
}
|
||||
//The rule may be one the numbering window is drawing right now
|
||||
//and has not written into the project yet: the numbering writes
|
||||
//it together with the numbers themselves, so the project does
|
||||
//not have to hold it beforehand. What the counters are kept
|
||||
//under is the name the rule is stored with, which for cables is
|
||||
//fixed either way -- so numbering starts from a project which
|
||||
//has no rule saved yet just as well.
|
||||
QString key = project->cableCurrentAutoNum();
|
||||
if (key.isEmpty()) {
|
||||
key = QETProject::cableAutoNumRuleName();
|
||||
}
|
||||
if (rule.isEmpty())
|
||||
{
|
||||
result.error = QCoreApplication::translate(
|
||||
"CableRenumber",
|
||||
"No cable numbering rule is defined for this "
|
||||
"project. Define one in this window before "
|
||||
"renumbering its cables.");
|
||||
return result;
|
||||
}
|
||||
|
||||
//Where every cable stands, and which ones are left alone: their
|
||||
//names stay as they are, so the numbering must not hand the
|
||||
//same number out again
|
||||
QList<OrderedCable> ordered;
|
||||
QSet<QString> taken;
|
||||
for (Cable *cable : project->cables())
|
||||
{
|
||||
if (!cable) continue;
|
||||
if (!include_by_hand && cable->designationByHand())
|
||||
{
|
||||
if (!cable->designation().isEmpty()) {
|
||||
taken.insert(cable->designation());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
OrderedCable entry;
|
||||
entry.cable = cable;
|
||||
|
||||
for (const CablePartData &part : cable->parts())
|
||||
{
|
||||
Diagram *diagram = project->diagramByUuid(part.diagram);
|
||||
if (!diagram) continue;
|
||||
|
||||
const int index = project->folioIndex(diagram);
|
||||
const qreal left = qMin(part.p1.x(), part.p2.x());
|
||||
const qreal top = qMin(part.p1.y(), part.p2.y());
|
||||
|
||||
if (!entry.diagram || index < entry.folio)
|
||||
{
|
||||
entry.folio = index;
|
||||
entry.diagram = diagram;
|
||||
entry.left = left;
|
||||
entry.top = top;
|
||||
} else if (index == entry.folio) {
|
||||
entry.left = qMin(entry.left, left);
|
||||
entry.top = qMin(entry.top, top);
|
||||
}
|
||||
}
|
||||
//A cable which stands on no folio the project still knows
|
||||
//is numbered last rather than first by accident
|
||||
if (!entry.diagram) {
|
||||
entry.folio = std::numeric_limits<int>::max();
|
||||
}
|
||||
ordered.append(entry);
|
||||
}
|
||||
|
||||
//Folio by folio, and on each folio along the axis which leads:
|
||||
//the other axis is only asked when the leading one says two
|
||||
//cables are level, so that the numbering never depends on the
|
||||
//order the cables happen to be stored in
|
||||
std::sort(ordered.begin(), ordered.end(),
|
||||
[x_axis_first](const OrderedCable &a, const OrderedCable &b)
|
||||
{
|
||||
if (a.folio != b.folio) return a.folio < b.folio;
|
||||
|
||||
const qreal a_first = x_axis_first ? a.left : a.top;
|
||||
const qreal b_first = x_axis_first ? b.left : b.top;
|
||||
if (a_first != b_first) return a_first < b_first;
|
||||
|
||||
const qreal a_second = x_axis_first ? a.top : a.left;
|
||||
const qreal b_second = x_axis_first ? b.top : b.left;
|
||||
if (a_second != b_second) return a_second < b_second;
|
||||
|
||||
return a.cable->uuid().toString() < b.cable->uuid().toString();
|
||||
});
|
||||
|
||||
//Read the rule from the beginning, once for each cable: its
|
||||
//counters are put back first, since numbering a project over
|
||||
//starts at the beginning whatever the last number was
|
||||
NumerotationContext context = autonum::resetContextCounters(rule);
|
||||
int current_folio = -1;
|
||||
|
||||
for (const OrderedCable &entry : std::as_const(ordered))
|
||||
{
|
||||
if (!entry.diagram)
|
||||
{
|
||||
result.error = QCoreApplication::translate(
|
||||
"CableRenumber",
|
||||
"A cable is not on any sheet of the project: "
|
||||
"nothing was changed.");
|
||||
return result;
|
||||
}
|
||||
|
||||
//A counter bound to the folio counts from the beginning of
|
||||
//that folio: the first cable of a folio gets the first
|
||||
//number of it, and the folios after it start again rather
|
||||
//than carrying the number of the folio before on
|
||||
if (entry.folio != current_folio)
|
||||
{
|
||||
context = autonum::resetFolioCounters(context);
|
||||
current_folio = entry.folio;
|
||||
}
|
||||
|
||||
const QString candidate = readRule(context, entry.diagram, key);
|
||||
if (candidate.isEmpty())
|
||||
{
|
||||
result.error = QCoreApplication::translate(
|
||||
"CableRenumber",
|
||||
"The current rule gives no number here: "
|
||||
"nothing was changed.");
|
||||
return result;
|
||||
}
|
||||
//A rule which does not count hands out the same number
|
||||
//twice, and two cables of one project may not be called
|
||||
//the same thing
|
||||
if (result.designations.contains(candidate))
|
||||
{
|
||||
result.error = QCoreApplication::translate(
|
||||
"CableRenumber",
|
||||
"The current rule gives the same number (%1) twice: "
|
||||
"it must count to renumber a whole project. "
|
||||
"Nothing was changed.")
|
||||
.arg(candidate);
|
||||
return result;
|
||||
}
|
||||
//A name which stays because it was typed in keeps its
|
||||
//number, and the numbering may not hand that number out
|
||||
if (taken.contains(candidate))
|
||||
{
|
||||
result.error = QCoreApplication::translate(
|
||||
"CableRenumber",
|
||||
"Number %1 is already used by a cable whose name "
|
||||
"is left as it is: nothing was changed.")
|
||||
.arg(candidate);
|
||||
return result;
|
||||
}
|
||||
|
||||
taken.insert(candidate);
|
||||
result.cables.append(entry.cable);
|
||||
result.designations.append(candidate);
|
||||
|
||||
//Step the rule on for the next cable; a rule which cannot
|
||||
//step stays where it is and is caught by the check above
|
||||
NumerotationContextCommands ncc(context, entry.diagram);
|
||||
const NumerotationContext following = ncc.next();
|
||||
if (following.size() == context.size()) {
|
||||
context = following;
|
||||
}
|
||||
}
|
||||
|
||||
//Where the rule stands after the last cable has been numbered:
|
||||
//the caller writes that into the project, so numbering goes on
|
||||
//from there instead of from wherever the rule happened to stand
|
||||
//before this numbering ran
|
||||
result.after = context;
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
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 CABLERENUMBER_H
|
||||
#define CABLERENUMBER_H
|
||||
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include "../autoNum/numerotationcontext.h"
|
||||
|
||||
class Cable;
|
||||
class QETProject;
|
||||
|
||||
/**
|
||||
@brief What numbering a whole project would do, worked out before a
|
||||
single cable is written to.
|
||||
|
||||
Working it out first means the answer can be refused as a whole: a
|
||||
rule which hands out the same number twice, or a number some cable
|
||||
which is left alone already carries, stops the whole numbering
|
||||
rather than numbering half the project and then getting stuck.
|
||||
*/
|
||||
struct CableRenumberPlan
|
||||
{
|
||||
///The cables to number, in the order they are numbered in
|
||||
QList<Cable *> cables;
|
||||
///The number each of them gets, one for each cable
|
||||
QStringList designations;
|
||||
///The rule as it stands once the last cable has been numbered:
|
||||
///its counters are where the next number would be taken from
|
||||
NumerotationContext after;
|
||||
///What stopped the numbering, empty while it may be carried out
|
||||
QString error;
|
||||
|
||||
bool ok() const {return error.isEmpty();}
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Working out the numbers a whole project should carry.
|
||||
|
||||
Everything here is pure: nothing is written to the project and
|
||||
nothing is put on the undo stack, so a caller may look at the plan
|
||||
and decide against it without anything having happened.
|
||||
*/
|
||||
namespace CableRenumber
|
||||
{
|
||||
/**
|
||||
How many cables of this project carry a name which was typed in
|
||||
rather than handed out by the numbering rule. Those are the ones
|
||||
renumbering asks about before it takes their name away.
|
||||
@param project
|
||||
@return the number of such cables
|
||||
*/
|
||||
int byHandCount(QETProject *project);
|
||||
|
||||
/**
|
||||
Work the number of every cable of the project out of @p rule.
|
||||
|
||||
The cables are laid out first -- folio by folio, and on each
|
||||
folio either from the left to the right or from the top to the
|
||||
bottom, whichever axis leads -- and are then numbered one after
|
||||
the other from the beginning of the rule, so that a hole left by
|
||||
a cable which has been deleted closes behind it.
|
||||
|
||||
@param project the project whose cables are numbered
|
||||
@param rule the numbering rule to read; its counters are put
|
||||
back to 1 first, since numbering starts from the beginning
|
||||
@param x_axis_first true to number the cables of a folio from
|
||||
the left to the right, false to number them from the top to the
|
||||
bottom
|
||||
@param include_by_hand false to leave the cables whose name was
|
||||
typed in exactly as they are
|
||||
@return the numbers, or what stopped the numbering. The plan
|
||||
also carries the rule as it stands once every cable has been
|
||||
given its number, so that the caller may put that into the
|
||||
project: the counters have to stay where the last number was
|
||||
taken from, or the next cable drawn would skip over every
|
||||
number in between
|
||||
*/
|
||||
CableRenumberPlan plan(QETProject *project,
|
||||
const NumerotationContext &rule,
|
||||
bool x_axis_first,
|
||||
bool include_by_hand);
|
||||
}
|
||||
|
||||
#endif // CABLERENUMBER_H
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
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 "cabletextformatdialog.h"
|
||||
|
||||
#include "../qetapp.h"
|
||||
#include "../utils/qetutils.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QFontComboBox>
|
||||
#include <QFormLayout>
|
||||
#include <QFrame>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace {
|
||||
/**
|
||||
@brief The way a text lines up, as the dialog lists it: right
|
||||
first, which is where every text stands unless he says otherwise.
|
||||
@param index the place in the list of the dialog
|
||||
@return the flags the drawing understands
|
||||
*/
|
||||
Qt::Alignment alignmentFromIndex(int index)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 1: return Qt::AlignHCenter;
|
||||
case 2: return Qt::AlignLeft;
|
||||
default: return Qt::AlignRight;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief The place in the list of the dialog of an alignment.
|
||||
@param alignment Qt::AlignLeft, Qt::AlignHCenter or Qt::AlignRight
|
||||
@return the index, 0 (right) when nothing is named
|
||||
*/
|
||||
int alignmentIndex(int alignment)
|
||||
{
|
||||
switch (alignment)
|
||||
{
|
||||
case int(Qt::AlignHCenter): return 1;
|
||||
case int(Qt::AlignLeft): return 2;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Whether two fonts would be read the very same way on the sheet:
|
||||
the family he picks, the size he gives it, and the style he
|
||||
chose among the four offered.
|
||||
@param a the font he has chosen
|
||||
@param b the font the preferences would write it with
|
||||
@return true when nothing of them differs
|
||||
*/
|
||||
bool sameFont(const QFont &a, const QFont &b)
|
||||
{
|
||||
return a.family() == b.family()
|
||||
&& qFuzzyCompare(a.pointSizeF(), b.pointSizeF())
|
||||
&& a.bold() == b.bold()
|
||||
&& a.italic() == b.italic();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableTextFormatDialog::CableTextFormatDialog
|
||||
@param format what this cable writes that text with right now
|
||||
@param cores true for the colours of the cores
|
||||
@param parent
|
||||
*/
|
||||
CableTextFormatDialog::CableTextFormatDialog(const CableTextFormat &format,
|
||||
bool cores,
|
||||
QWidget *parent) :
|
||||
QDialog(parent),
|
||||
m_cores(cores)
|
||||
{
|
||||
setWindowTitle(cores ? tr("Core colors")
|
||||
: tr("Text format"));
|
||||
buildUi();
|
||||
|
||||
//The font this text is written with right now: the one this
|
||||
//cable asks for, or the one the preferences offer when it asks
|
||||
//for none -- which is what he sees on the sheet as he opens this.
|
||||
QFont font;
|
||||
bool restored = false;
|
||||
if (!format.font.isEmpty()) {
|
||||
restored = QETUtils::fontFromString(font, format.font);
|
||||
}
|
||||
if (!restored) {
|
||||
font = cores ? QETApp::cableCoreFont() : QETApp::cableTextsFont();
|
||||
}
|
||||
|
||||
//Where this text stands right now, in the same way: the answer
|
||||
//of the cable itself, or -- when it has none -- the one the
|
||||
//preferences offer, so that he opens this onto what the sheet
|
||||
//really shows rather than onto whatever the default used to be.
|
||||
m_initial_alignment = cores ? 0
|
||||
: (format.alignment ? format.alignment
|
||||
: int(QETApp::cableTextAlignment()));
|
||||
|
||||
m_family->setCurrentFont(font);
|
||||
m_size->setValue(font.pointSizeF());
|
||||
if (font.bold() && font.italic()) m_style->setCurrentIndex(3);
|
||||
else if (font.italic()) m_style->setCurrentIndex(2);
|
||||
else if (font.bold()) m_style->setCurrentIndex(1);
|
||||
else m_style->setCurrentIndex(0);
|
||||
if (m_alignment) {
|
||||
m_alignment->setCurrentIndex(alignmentIndex(m_initial_alignment));
|
||||
}
|
||||
|
||||
//Going back to the preferences means something only while this
|
||||
//cable has an answer of its own: when it already follows them,
|
||||
//there is nothing here to give back.
|
||||
m_reset->setEnabled(!format.font.isEmpty() || format.alignment != 0);
|
||||
updatePreview();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableTextFormatDialog::buildUi
|
||||
One row per thing he may set, then what it looks like -- because a
|
||||
size means nothing as a number alone.
|
||||
*/
|
||||
void CableTextFormatDialog::buildUi()
|
||||
{
|
||||
auto *form = new QFormLayout;
|
||||
form->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
|
||||
form->setHorizontalSpacing(12);
|
||||
|
||||
m_family = new QFontComboBox(this);
|
||||
m_family->setFontFilters(QFontComboBox::AllFonts);
|
||||
form->addRow(tr("Font:"), m_family);
|
||||
|
||||
m_size = new QDoubleSpinBox(this);
|
||||
m_size->setRange(1.0, 300.0);
|
||||
m_size->setDecimals(1);
|
||||
m_size->setSingleStep(0.5);
|
||||
m_size->setSuffix(tr(" pt"));
|
||||
form->addRow(tr("Size:"), m_size);
|
||||
|
||||
m_style = new QComboBox(this);
|
||||
m_style->addItem(tr("Normal"));
|
||||
m_style->addItem(tr("Bold"));
|
||||
m_style->addItem(tr("Italic"));
|
||||
m_style->addItem(tr("Bold italic"));
|
||||
form->addRow(tr("Style:"), m_style);
|
||||
|
||||
//The colours of the cores stand turned by a quarter along their
|
||||
//own line: there is nothing for them to line up with.
|
||||
if (!m_cores)
|
||||
{
|
||||
m_alignment = new QComboBox(this);
|
||||
m_alignment->addItem(tr("Right aligned"));
|
||||
m_alignment->addItem(tr("Centred"));
|
||||
m_alignment->addItem(tr("Left aligned"));
|
||||
form->addRow(tr("Alignment:"), m_alignment);
|
||||
}
|
||||
|
||||
m_preview = new QLabel(tr("15W1 · H07V-K 3G1.5"), this);
|
||||
m_preview->setFrameStyle(QFrame::Box | QFrame::Plain);
|
||||
m_preview->setMinimumHeight(30);
|
||||
m_preview->setIndent(6);
|
||||
form->addRow(tr("Preview:"), m_preview);
|
||||
|
||||
auto *buttons = new QDialogButtonBox(
|
||||
QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
m_reset = buttons->addButton(tr("Reset"), QDialogButtonBox::ResetRole);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
connect(m_reset, &QPushButton::clicked, this, [this]() {
|
||||
m_reset_chosen = true;
|
||||
accept();
|
||||
});
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->addLayout(form);
|
||||
layout->addWidget(buttons);
|
||||
setLayout(layout);
|
||||
|
||||
connect(m_family, &QFontComboBox::currentFontChanged,
|
||||
this, &CableTextFormatDialog::updatePreview);
|
||||
connect(m_size, &QDoubleSpinBox::valueChanged,
|
||||
this, &CableTextFormatDialog::updatePreview);
|
||||
connect(m_style, &QComboBox::currentIndexChanged,
|
||||
this, &CableTextFormatDialog::updatePreview);
|
||||
if (m_alignment) {
|
||||
connect(m_alignment, &QComboBox::currentIndexChanged,
|
||||
this, &CableTextFormatDialog::updatePreview);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableTextFormatDialog::chosenFont
|
||||
What the dialog offers right now: the family he picked, the size he
|
||||
gave it and the style he chose among the four offered -- rather than
|
||||
whatever that family happened to be called in the list.
|
||||
@return the font
|
||||
*/
|
||||
QFont CableTextFormatDialog::chosenFont() const
|
||||
{
|
||||
QFont font = m_family->currentFont();
|
||||
font.setPointSizeF(m_size->value());
|
||||
const bool italic = m_style->currentIndex() == 2 || m_style->currentIndex() == 3;
|
||||
const bool bold = m_style->currentIndex() == 1 || m_style->currentIndex() == 3;
|
||||
font.setBold(bold);
|
||||
font.setItalic(italic);
|
||||
return font;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableTextFormatDialog::updatePreview
|
||||
Write the line offered as an example the way it would be written
|
||||
after what he has just set -- the only place where a size becomes
|
||||
something one can judge.
|
||||
*/
|
||||
void CableTextFormatDialog::updatePreview()
|
||||
{
|
||||
m_preview->setFont(chosenFont());
|
||||
if (m_alignment) {
|
||||
m_preview->setAlignment(
|
||||
alignmentFromIndex(m_alignment->currentIndex()) | Qt::AlignVCenter);
|
||||
} else {
|
||||
m_preview->setAlignment(Qt::AlignCenter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableTextFormatDialog::format
|
||||
@return what the cable is to write that text with from now on.
|
||||
|
||||
An empty answer means "the way the preferences say", and the two
|
||||
halves of it are worked out apart from each other: he may set the
|
||||
way one text lines up without taking away its freedom to follow a
|
||||
new default font later, and the other way round. What already
|
||||
stands the way the preferences say is left out, so that a cable
|
||||
which said nothing keeps saying nothing -- and a later change of
|
||||
what those preferences are still reaches it.
|
||||
*/
|
||||
CableTextFormat CableTextFormatDialog::format() const
|
||||
{
|
||||
if (m_reset_chosen) return CableTextFormat();
|
||||
|
||||
const QFont font = chosenFont();
|
||||
const QFont default_font = m_cores ? QETApp::cableCoreFont()
|
||||
: QETApp::cableTextsFont();
|
||||
|
||||
CableTextFormat answer;
|
||||
if (!sameFont(font, default_font)) {
|
||||
answer.font = QETUtils::fontToString(font);
|
||||
}
|
||||
//The colours of the cores stand turned by a quarter: they have
|
||||
//no line to line up with, and saying so would only be noise in
|
||||
//the file.
|
||||
if (!m_cores)
|
||||
{
|
||||
const int alignment = int(alignmentFromIndex(m_alignment->currentIndex()));
|
||||
if (alignment != int(QETApp::cableTextAlignment())) {
|
||||
answer.alignment = alignment;
|
||||
}
|
||||
}
|
||||
return answer;
|
||||
}
|
||||
@@ -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/>.
|
||||
*/
|
||||
#ifndef CABLETEXTFORMATDIALOG_H
|
||||
#define CABLETEXTFORMATDIALOG_H
|
||||
|
||||
#include "cable.h"
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class QComboBox;
|
||||
class QFontComboBox;
|
||||
class QDoubleSpinBox;
|
||||
class QLabel;
|
||||
class QPushButton;
|
||||
|
||||
/**
|
||||
@brief The small window where one text of one cable is set: the font
|
||||
it is written with -- family, size and style -- and, for the texts
|
||||
written next to the trunk line, the way it lines up at the line.
|
||||
|
||||
The colours of the cores come here as well, all of them together
|
||||
because they are drawn together: they have no alignment, being
|
||||
turned by a quarter along their own line.
|
||||
|
||||
Saying nothing is the normal case: the cable then follows what the
|
||||
preferences offer on the "Textes" page, and OK keeps an answer of
|
||||
its own only when he really changed something -- so that picking
|
||||
another default later on still reaches this cable.
|
||||
*/
|
||||
class CableTextFormatDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
@param format what this cable writes that text with right
|
||||
now, an empty format when it merely follows the preferences
|
||||
@param cores true for the colours of the cores: they have
|
||||
no alignment and follow QETApp::cableCoreFont()
|
||||
@param parent
|
||||
*/
|
||||
CableTextFormatDialog(const CableTextFormat &format,
|
||||
bool cores,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
///What the cable is to write that text with from now on
|
||||
CableTextFormat format() const;
|
||||
|
||||
private:
|
||||
void buildUi();
|
||||
void updatePreview();
|
||||
QFont chosenFont() const;
|
||||
|
||||
bool m_cores = false;
|
||||
///True when he asked to go back to following the preferences
|
||||
bool m_reset_chosen = false;
|
||||
///The alignment this text has right now, 0 for the cores
|
||||
int m_initial_alignment = int(Qt::AlignRight);
|
||||
|
||||
QFontComboBox *m_family = nullptr;
|
||||
QDoubleSpinBox *m_size = nullptr;
|
||||
QComboBox *m_style = nullptr;
|
||||
QComboBox *m_alignment = nullptr;
|
||||
QLabel *m_preview = nullptr;
|
||||
QPushButton *m_reset = nullptr;
|
||||
};
|
||||
|
||||
#endif // CABLETEXTFORMATDIALOG_H
|
||||
@@ -0,0 +1,494 @@
|
||||
/*
|
||||
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 "editcablecommand.h"
|
||||
|
||||
#include "cablemanager.h"
|
||||
#include "cablepart.h"
|
||||
#include "../diagram.h"
|
||||
#include "../qetproject.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
@brief BatchCommand::BatchCommand
|
||||
@param first step to run first, kept by this command
|
||||
@param second step to run right after it, kept by this command
|
||||
*/
|
||||
BatchCommand::BatchCommand(QUndoCommand *first, QUndoCommand *second) :
|
||||
m_steps({first, second})
|
||||
{
|
||||
m_steps.removeAll(nullptr);
|
||||
setText(QCoreApplication::translate("BatchCommand", "Delete"));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BatchCommand::BatchCommand
|
||||
@param steps every step of one single gesture, in the order the
|
||||
user did them, kept by this command
|
||||
@param text what the gesture was, as the undo history shows it
|
||||
*/
|
||||
BatchCommand::BatchCommand(const QList<QUndoCommand *> &steps, const QString &text) :
|
||||
m_steps(steps)
|
||||
{
|
||||
m_steps.removeAll(nullptr);
|
||||
setText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BatchCommand::~BatchCommand
|
||||
The steps it holds go with it: they were never given to anything else.
|
||||
*/
|
||||
BatchCommand::~BatchCommand()
|
||||
{
|
||||
for (QUndoCommand *step : std::as_const(m_steps)) {
|
||||
delete step;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BatchCommand::redo
|
||||
Every step, in the order the user did them.
|
||||
*/
|
||||
void BatchCommand::redo()
|
||||
{
|
||||
for (QUndoCommand *step : std::as_const(m_steps)) {
|
||||
if (step) step->redo();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief BatchCommand::undo
|
||||
Every step taken back in the reverse order, so what one step changed
|
||||
is still there when the next one is undone.
|
||||
*/
|
||||
void BatchCommand::undo()
|
||||
{
|
||||
for (auto it = m_steps.crbegin(); it != m_steps.crend(); ++it) {
|
||||
if (*it) (*it)->undo();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MoveCablePartCommand::MoveCablePartCommand
|
||||
@param cable the cable the section belongs to
|
||||
@param part the line being dragged
|
||||
@param before_p1
|
||||
@param before_p2
|
||||
@param after_p1
|
||||
@param after_p2
|
||||
@param before_cores where every colour label stood before the drag
|
||||
@param after_cores where it stands after it
|
||||
*/
|
||||
MoveCablePartCommand::MoveCablePartCommand(Cable *cable,
|
||||
CablePart *part,
|
||||
const QPointF &before_p1,
|
||||
const QPointF &before_p2,
|
||||
const QPointF &after_p1,
|
||||
const QPointF &after_p2,
|
||||
const QList<CableCore> &before_cores,
|
||||
const QList<CableCore> &after_cores) :
|
||||
m_cable(cable),
|
||||
m_part(part),
|
||||
m_before_p1(before_p1),
|
||||
m_before_p2(before_p2),
|
||||
m_after_p1(after_p1),
|
||||
m_after_p2(after_p2),
|
||||
m_before_cores(before_cores),
|
||||
m_after_cores(after_cores)
|
||||
{
|
||||
setText(QCoreApplication::translate("MoveCablePartCommand", "Move a cable"));
|
||||
}
|
||||
|
||||
void MoveCablePartCommand::undo()
|
||||
{
|
||||
apply(m_before_p1, m_before_p2, m_before_cores);
|
||||
}
|
||||
|
||||
void MoveCablePartCommand::redo()
|
||||
{
|
||||
apply(m_after_p1, m_after_p2, m_after_cores);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MoveCablePartCommand::apply
|
||||
Put the line where it belongs and the colour labels with it. The
|
||||
positions are written last: they are stored in scene coordinates, so
|
||||
they only make sense once the line itself is back in place.
|
||||
@param p1
|
||||
@param p2
|
||||
@param cores
|
||||
*/
|
||||
void MoveCablePartCommand::apply(const QPointF &p1,
|
||||
const QPointF &p2,
|
||||
const QList<CableCore> &cores)
|
||||
{
|
||||
if (m_part) {
|
||||
m_part->setLine(p1, p2);
|
||||
}
|
||||
if (m_cable) {
|
||||
m_cable->setCores(cores);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ChangeCablePropertiesCommand::ChangeCablePropertiesCommand
|
||||
@param cable
|
||||
@param before every field as it was before the edit
|
||||
@param after every field as the user left it
|
||||
*/
|
||||
ChangeCablePropertiesCommand::ChangeCablePropertiesCommand(Cable *cable,
|
||||
const CableProperties &before,
|
||||
const CableProperties &after) :
|
||||
m_cable(cable),
|
||||
m_before(before),
|
||||
m_after(after)
|
||||
{
|
||||
setText(QCoreApplication::translate("ChangeCablePropertiesCommand",
|
||||
"Edit the properties of a cable"));
|
||||
}
|
||||
|
||||
void ChangeCablePropertiesCommand::undo()
|
||||
{
|
||||
if (m_cable) {
|
||||
m_cable->setProperties(m_before);
|
||||
}
|
||||
}
|
||||
|
||||
void ChangeCablePropertiesCommand::redo()
|
||||
{
|
||||
if (m_cable) {
|
||||
m_cable->setProperties(m_after);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief RenumberCablesCommand::RenumberCablesCommand
|
||||
@param cables the cables to number, in the order they are numbered in
|
||||
@param designations the number each of them gets
|
||||
@param project the project whose numbering rule moves along with
|
||||
the numbering
|
||||
@param num_key the name that rule is kept under in the project
|
||||
@param num_before where its counters stood before the numbering ran
|
||||
@param num_after where the numbering leaves them
|
||||
*/
|
||||
RenumberCablesCommand::RenumberCablesCommand(const QList<Cable *> &cables,
|
||||
const QStringList &designations,
|
||||
QETProject *project,
|
||||
const QString &num_key,
|
||||
const NumerotationContext &num_before,
|
||||
const NumerotationContext &num_after) :
|
||||
m_project(project),
|
||||
m_num_key(num_key),
|
||||
m_num_before(num_before),
|
||||
m_num_after(num_after)
|
||||
{
|
||||
for (int i = 0; i < cables.size() && i < designations.size(); ++i)
|
||||
{
|
||||
Cable *cable = cables.at(i);
|
||||
if (!cable) continue;
|
||||
|
||||
m_cables.append(cable);
|
||||
m_before.append(cable->designation());
|
||||
m_by_hand_before.append(cable->designationByHand());
|
||||
m_after.append(designations.at(i));
|
||||
//A number the rule has just handed out is the rule's, so
|
||||
//nothing here stays marked as having been typed in
|
||||
m_by_hand_after.append(false);
|
||||
}
|
||||
|
||||
setText(QCoreApplication::translate("RenumberCablesCommand",
|
||||
"Renumber %n cable(s)",
|
||||
"", m_cables.size()));
|
||||
}
|
||||
|
||||
void RenumberCablesCommand::undo()
|
||||
{
|
||||
apply(false);
|
||||
}
|
||||
|
||||
void RenumberCablesCommand::redo()
|
||||
{
|
||||
apply(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief RenumberCablesCommand::apply
|
||||
@param use_after true to write the numbers the rule handed out,
|
||||
false to put the ones back which were there before
|
||||
*/
|
||||
void RenumberCablesCommand::apply(bool use_after)
|
||||
{
|
||||
for (int i = 0; i < m_cables.size(); ++i)
|
||||
{
|
||||
Cable *cable = m_cables.at(i).data();
|
||||
if (!cable) continue;
|
||||
|
||||
//Where the name comes from is written first: a number the
|
||||
//rule has just handed out is the rule's whatever the cable
|
||||
//was called before, and undo has to say so again
|
||||
cable->setDesignationByHand(use_after ? m_by_hand_after.at(i)
|
||||
: m_by_hand_before.at(i));
|
||||
cable->setDesignation(use_after ? m_after.at(i) : m_before.at(i));
|
||||
}
|
||||
|
||||
//Where the numbering rule itself stands moves along with the
|
||||
//numbering: once it has run, its counters are where the last
|
||||
//number was taken from, so the next cable drawn goes on from
|
||||
//there rather than skipping over every number in between. Undo
|
||||
//puts them back like the names, since numbering a project over
|
||||
//is one single act.
|
||||
if (m_project && !m_num_key.isEmpty())
|
||||
{
|
||||
const NumerotationContext context = use_after ? m_num_after
|
||||
: m_num_before;
|
||||
//An empty rule is never written: losing the rule altogether
|
||||
//is not something numbering may do, not even on the way back
|
||||
if (!context.isEmpty())
|
||||
{
|
||||
m_project->addCableAutoNum(m_num_key, context);
|
||||
m_project->setCurrentCableAutoNum(m_num_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ChangeCableTypeCommand::ChangeCableTypeCommand
|
||||
@param cable
|
||||
@param before the type, the count, the colours and the cores as
|
||||
they were before the change
|
||||
@param after the very same four as the chosen type says
|
||||
*/
|
||||
ChangeCableTypeCommand::ChangeCableTypeCommand(Cable *cable,
|
||||
const CableTypeState &before,
|
||||
const CableTypeState &after) :
|
||||
m_cable(cable),
|
||||
m_before(before),
|
||||
m_after(after)
|
||||
{
|
||||
setText(QCoreApplication::translate("ChangeCableTypeCommand",
|
||||
"Change the cable type"));
|
||||
}
|
||||
|
||||
void ChangeCableTypeCommand::undo()
|
||||
{
|
||||
apply(m_before);
|
||||
}
|
||||
|
||||
void ChangeCableTypeCommand::redo()
|
||||
{
|
||||
apply(m_after);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ChangeCableTypeCommand::apply
|
||||
One block in one pass: name, colours, count, then the cores. The
|
||||
count has to stand before the cores, since a cable with fewer cores
|
||||
cannot hold the others -- and the other way round, undoing a type
|
||||
with fewer cores has to open the room before the cores come back
|
||||
into it.
|
||||
@param state the block to write
|
||||
*/
|
||||
void ChangeCableTypeCommand::apply(const CableTypeState &state)
|
||||
{
|
||||
if (m_cable) {
|
||||
m_cable->applyTypeState(state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ChangeCableCoresCommand::ChangeCableCoresCommand
|
||||
@param cable
|
||||
@param before every core the cable wired before the change
|
||||
@param after every core it wires after it
|
||||
@param text what undo says about this step, empty for a core which
|
||||
is merely moved
|
||||
*/
|
||||
ChangeCableCoresCommand::ChangeCableCoresCommand(Cable *cable,
|
||||
const QList<CableCore> &before,
|
||||
const QList<CableCore> &after,
|
||||
const QString &text) :
|
||||
m_cable(cable),
|
||||
m_before(before),
|
||||
m_after(after)
|
||||
{
|
||||
setText(text.isEmpty()
|
||||
? QCoreApplication::translate("ChangeCableCoresCommand",
|
||||
"Move a cable core")
|
||||
: text);
|
||||
}
|
||||
|
||||
void ChangeCableCoresCommand::undo()
|
||||
{
|
||||
apply(m_before);
|
||||
}
|
||||
|
||||
void ChangeCableCoresCommand::redo()
|
||||
{
|
||||
apply(m_after);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ChangeCableCoresCommand::apply
|
||||
Write one whole set of assignments in place of another. The cable
|
||||
says it changed, which is what makes the project work out the cable
|
||||
field of every conductor it feeds again, so the drawing follows.
|
||||
@param cores
|
||||
*/
|
||||
void ChangeCableCoresCommand::apply(const QList<CableCore> &cores)
|
||||
{
|
||||
if (m_cable) {
|
||||
m_cable->setCores(cores);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief RemoveCableCommand::RemoveCableCommand
|
||||
@param diagram the folios the lines are drawn on
|
||||
@param parts the lines the user asked to delete
|
||||
*/
|
||||
RemoveCableCommand::RemoveCableCommand(Diagram *diagram, const QList<CablePart *> &parts) :
|
||||
m_diagram(diagram)
|
||||
{
|
||||
setText(QCoreApplication::translate("RemoveCableCommand", "Delete a cable"));
|
||||
|
||||
for (CablePart *part : parts)
|
||||
{
|
||||
if (!part) continue;
|
||||
|
||||
Entry entry;
|
||||
entry.part = part;
|
||||
entry.cable = part->cable();
|
||||
entry.data.uuid = part->partUuid();
|
||||
entry.data.diagram = m_diagram ? m_diagram->uuid() : QUuid();
|
||||
entry.data.p1 = part->firstPoint();
|
||||
entry.data.p2 = part->secondPoint();
|
||||
m_entries.append(entry);
|
||||
|
||||
if (entry.cable && !m_cables.contains(entry.cable)) {
|
||||
m_cables.append(entry.cable);
|
||||
m_cores.insert(entry.cable.data(), entry.cable->usedCores());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief RemoveCableCommand::~RemoveCableCommand
|
||||
Whatever the stack leaves behind has to be cleaned up here: a cable
|
||||
which is not held by the project any more, and a line which never
|
||||
made it back onto a folio, belong to this command.
|
||||
*/
|
||||
RemoveCableCommand::~RemoveCableCommand()
|
||||
{
|
||||
QETProject *proj = project();
|
||||
for (const QPointer<Cable> &cable : std::as_const(m_cables))
|
||||
{
|
||||
if (cable && (!proj || !proj->cables().contains(cable))) {
|
||||
delete cable;
|
||||
}
|
||||
}
|
||||
for (const Entry &entry : std::as_const(m_entries))
|
||||
{
|
||||
if (entry.part && !entry.part->scene()) {
|
||||
delete entry.part;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief RemoveCableCommand::project
|
||||
@return the project this command works in, or nullptr
|
||||
*/
|
||||
QETProject *RemoveCableCommand::project() const
|
||||
{
|
||||
return m_diagram ? m_diagram->project() : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief RemoveCableCommand::redo
|
||||
Take the lines off their folios. Each cable loses the sections which
|
||||
are gone together with the cores drawn on them, and a cable which has
|
||||
no section left anywhere leaves the project: a cable nothing draws is
|
||||
no cable of this project.
|
||||
*/
|
||||
void RemoveCableCommand::redo()
|
||||
{
|
||||
QETProject *proj = project();
|
||||
|
||||
for (const Entry &entry : std::as_const(m_entries))
|
||||
{
|
||||
if (entry.cable) {
|
||||
entry.cable->removePart(entry.data.uuid);
|
||||
}
|
||||
if (entry.part && entry.part->scene() && m_diagram) {
|
||||
m_diagram->removeItem(entry.part);
|
||||
}
|
||||
}
|
||||
|
||||
if (proj)
|
||||
{
|
||||
for (const QPointer<Cable> &cable : std::as_const(m_cables))
|
||||
{
|
||||
if (cable && cable->parts().isEmpty() && proj->cables().contains(cable)) {
|
||||
proj->removeCable(cable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CableManager::refreshLabels(proj);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief RemoveCableCommand::undo
|
||||
Put the lines back on their folios, the sections back in their cable
|
||||
and the cores back on those sections: exactly the state the delete
|
||||
action took away.
|
||||
*/
|
||||
void RemoveCableCommand::undo()
|
||||
{
|
||||
QETProject *proj = project();
|
||||
|
||||
if (proj)
|
||||
{
|
||||
for (const QPointer<Cable> &cable : std::as_const(m_cables))
|
||||
{
|
||||
if (cable && !proj->cables().contains(cable)) {
|
||||
proj->addCable(cable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const Entry &entry : std::as_const(m_entries))
|
||||
{
|
||||
if (entry.cable)
|
||||
{
|
||||
if (!entry.cable->hasPart(entry.data.uuid)) {
|
||||
entry.cable->addPart(entry.data);
|
||||
}
|
||||
for (const CableCore &core : m_cores.value(entry.cable.data())) {
|
||||
entry.cable->setCore(core);
|
||||
}
|
||||
}
|
||||
if (entry.part && !entry.part->scene() && m_diagram) {
|
||||
m_diagram->addItem(entry.part);
|
||||
}
|
||||
}
|
||||
|
||||
CableManager::refreshLabels(proj);
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
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 EDITCABLECOMMAND_H
|
||||
#define EDITCABLECOMMAND_H
|
||||
|
||||
#include "cable.h"
|
||||
|
||||
#include "../autoNum/numerotationcontext.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QPointer>
|
||||
#include <QUndoCommand>
|
||||
|
||||
class CablePart;
|
||||
class Diagram;
|
||||
class QETProject;
|
||||
|
||||
/**
|
||||
@brief Puts a dragged cable line back where it was, or where the user
|
||||
just dragged it to.
|
||||
|
||||
The colour labels of its cores follow the line -- they are drawn on
|
||||
it -- and come back with it when the move is undone. Which core lands
|
||||
on which conductor never depends on where the trunk line stands, so
|
||||
the assignments are only carried along by this step, never changed.
|
||||
*/
|
||||
class MoveCablePartCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
MoveCablePartCommand(Cable *cable,
|
||||
CablePart *part,
|
||||
const QPointF &before_p1,
|
||||
const QPointF &before_p2,
|
||||
const QPointF &after_p1,
|
||||
const QPointF &after_p2,
|
||||
const QList<CableCore> &before_cores,
|
||||
const QList<CableCore> &after_cores);
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
void apply(const QPointF &p1,
|
||||
const QPointF &p2,
|
||||
const QList<CableCore> &cores);
|
||||
|
||||
private:
|
||||
QPointer<Cable> m_cable;
|
||||
QPointer<CablePart> m_part;
|
||||
QPointF m_before_p1;
|
||||
QPointF m_before_p2;
|
||||
QPointF m_after_p1;
|
||||
QPointF m_after_p2;
|
||||
QList<CableCore> m_before_cores;
|
||||
QList<CableCore> m_after_cores;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Rewrites which cores a cable wires, the way dragging the slash
|
||||
of a core does: the whole set of assignments is taken before and
|
||||
after the gesture, so undoing puts every one of them back -- the core
|
||||
which was moved, but also the one it swapped colours with.
|
||||
*/
|
||||
class ChangeCableCoresCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
/**
|
||||
@param cable the cable whose assignments are rewritten
|
||||
@param before every core it held before
|
||||
@param after every core it holds after
|
||||
@param text what undo says about this step: an empty string
|
||||
for the usual step of moving a core around, since putting a
|
||||
core down for the first time is not a move at all
|
||||
*/
|
||||
ChangeCableCoresCommand(Cable *cable,
|
||||
const QList<CableCore> &before,
|
||||
const QList<CableCore> &after,
|
||||
const QString &text = QString());
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
void apply(const QList<CableCore> &cores);
|
||||
|
||||
private:
|
||||
QPointer<Cable> m_cable;
|
||||
QList<CableCore> m_before;
|
||||
QList<CableCore> m_after;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Writes what the user typed into the fields of a cable -- and
|
||||
what he ticks off again -- as one single undo step.
|
||||
|
||||
All the fields belong to the cable rather than to one line of it, so
|
||||
one change puts every line of that cable, on every folio, right.
|
||||
*/
|
||||
class ChangeCablePropertiesCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
ChangeCablePropertiesCommand(Cable *cable,
|
||||
const CableProperties &before,
|
||||
const CableProperties &after);
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
QPointer<Cable> m_cable;
|
||||
CableProperties m_before;
|
||||
CableProperties m_after;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Gives every cable of a project the number the numbering rule
|
||||
says it should have, laid out in the order the cables are met along
|
||||
the sheets.
|
||||
|
||||
That is how a project is numbered again after a cable has been taken
|
||||
away: the hole its number leaves closes because the cables behind it
|
||||
move up one place, and a rule which has just been changed reaches
|
||||
every cable of the project at once.
|
||||
|
||||
One command covers them all -- numbering a whole project is one
|
||||
single act, so undo takes all of it back together. What the
|
||||
numbering rule itself holds is part of that act: once the cables
|
||||
are numbered, the counters of the rule stand where the last number
|
||||
was taken from, and undo puts them back where they stood.
|
||||
*/
|
||||
class RenumberCablesCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
/**
|
||||
@param cables the cables to number, in the order they are
|
||||
numbered in
|
||||
@param designations the number each of them gets, one for
|
||||
each cable
|
||||
@param project the project whose numbering rule moves along
|
||||
with the numbering
|
||||
@param num_key the name that rule is kept under in the
|
||||
project
|
||||
@param num_before where its counters stood before the
|
||||
numbering ran
|
||||
@param num_after where the numbering leaves them
|
||||
*/
|
||||
RenumberCablesCommand(const QList<Cable *> &cables,
|
||||
const QStringList &designations,
|
||||
QETProject *project,
|
||||
const QString &num_key,
|
||||
const NumerotationContext &num_before,
|
||||
const NumerotationContext &num_after);
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
void apply(bool use_after);
|
||||
|
||||
private:
|
||||
QList<QPointer<Cable>> m_cables;
|
||||
QStringList m_before;
|
||||
QStringList m_after;
|
||||
QList<bool> m_by_hand_before;
|
||||
QList<bool> m_by_hand_after;
|
||||
QETProject *m_project = nullptr;
|
||||
QString m_num_key;
|
||||
NumerotationContext m_num_before;
|
||||
NumerotationContext m_num_after;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Changes which type a cable is -- its name, how many cores it
|
||||
has and the colour of each of them -- together with the cores it
|
||||
may lose on the way, as one single step.
|
||||
|
||||
The cores belong to this step rather than to a step of their own:
|
||||
a type with fewer cores takes marks off the drawing, and undoing has
|
||||
to bring them back along with the name and the colours, never half
|
||||
of it.
|
||||
*/
|
||||
class ChangeCableTypeCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
/**
|
||||
@param cable the cable whose type is changed
|
||||
@param before the state the cable was in
|
||||
@param after the state it is put into
|
||||
*/
|
||||
ChangeCableTypeCommand(Cable *cable,
|
||||
const CableTypeState &before,
|
||||
const CableTypeState &after);
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
void apply(const CableTypeState &state);
|
||||
|
||||
private:
|
||||
QPointer<Cable> m_cable;
|
||||
CableTypeState m_before;
|
||||
CableTypeState m_after;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Holds the steps of one single gesture together, so a single
|
||||
undo takes all of them back -- an ordinary deletion with the cable
|
||||
deletions which go with it, or a drag which moved several cable
|
||||
lines at once.
|
||||
|
||||
It keeps its steps rather than handing them to QUndoCommand as
|
||||
children: Qt6.11 has no addChild() any more.
|
||||
*/
|
||||
class BatchCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
BatchCommand(QUndoCommand *first, QUndoCommand *second);
|
||||
BatchCommand(const QList<QUndoCommand *> &steps, const QString &text);
|
||||
~BatchCommand() override;
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
QList<QUndoCommand *> m_steps;
|
||||
};
|
||||
|
||||
/**
|
||||
@brief Takes one or more drawn cable lines off their folios -- and the
|
||||
cable itself with them when no line of it is left anywhere.
|
||||
|
||||
Undo puts everything back: the lines on their folios, the sections in
|
||||
the cable, the cores on those sections, and the cable in the project
|
||||
when it had to leave it.
|
||||
*/
|
||||
class RemoveCableCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
RemoveCableCommand(Diagram *diagram, const QList<CablePart *> &parts);
|
||||
~RemoveCableCommand() override;
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
private:
|
||||
QETProject *project() const;
|
||||
|
||||
private:
|
||||
struct Entry
|
||||
{
|
||||
///The line as it is drawn on the folio
|
||||
QPointer<CablePart> part;
|
||||
///Where the cable holds that section
|
||||
CablePartData data;
|
||||
///The cable the section belongs to
|
||||
QPointer<Cable> cable;
|
||||
};
|
||||
|
||||
QPointer<Diagram> m_diagram;
|
||||
QList<Entry> m_entries;
|
||||
///Every cable which loses a section here, with all the cores
|
||||
///it wires at this moment: removing a section drops the cores
|
||||
///drawn on it, and undo has to bring them back
|
||||
QList<QPointer<Cable>> m_cables;
|
||||
QHash<Cable *, QList<CableCore>> m_cores;
|
||||
};
|
||||
|
||||
#endif // EDITCABLECOMMAND_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 3 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 "cableexportdialog.h"
|
||||
|
||||
#include "cablelistmodel.h"
|
||||
#include "cablelistrows.h"
|
||||
#include "cablequerywidget.h"
|
||||
|
||||
#include "../bomexport.h"
|
||||
#include "../qet.h"
|
||||
#include "../qetapp.h"
|
||||
#include "../qetproject.h"
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QCheckBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileDialog>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QTableView>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
/**
|
||||
@brief CableExportDialog::CableExportDialog
|
||||
@param project the project whose cables are exported
|
||||
@param parent
|
||||
*/
|
||||
CableExportDialog::CableExportDialog(QETProject *project, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
m_project(project)
|
||||
{
|
||||
setWindowTitle(tr("Export the cable list as CSV"));
|
||||
QET::trackDialogGeometry(this);
|
||||
|
||||
auto *main_layout = new QVBoxLayout(this);
|
||||
|
||||
//The same column picker the cable list is built with, so the
|
||||
//export asks for its columns the same way the list does
|
||||
m_query_widget = new CableQueryWidget(this);
|
||||
main_layout->addWidget(m_query_widget);
|
||||
|
||||
auto *page_group = new QGroupBox(tr("Layout"), this);
|
||||
auto *page_layout = new QVBoxLayout(page_group);
|
||||
m_include_headers = new QCheckBox(tr("include headers"), page_group);
|
||||
m_include_headers->setChecked(true);
|
||||
page_layout->addWidget(m_include_headers);
|
||||
page_layout->addStretch();
|
||||
main_layout->addWidget(page_group);
|
||||
|
||||
auto *preview_layout = new QHBoxLayout;
|
||||
auto *preview_pb = new QPushButton(tr("Preview"), this);
|
||||
preview_layout->addWidget(preview_pb);
|
||||
preview_layout->addStretch();
|
||||
main_layout->addLayout(preview_layout);
|
||||
|
||||
m_preview_table = new QTableView(this);
|
||||
m_preview_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
main_layout->addWidget(m_preview_table);
|
||||
|
||||
auto *buttons = new QDialogButtonBox(
|
||||
QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
main_layout->addWidget(buttons);
|
||||
connect(buttons, &QDialogButtonBox::accepted,
|
||||
this, &CableExportDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected,
|
||||
this, &CableExportDialog::reject);
|
||||
connect(preview_pb, &QPushButton::clicked,
|
||||
this, &CableExportDialog::preview);
|
||||
|
||||
m_model = new CableListModel(m_project, this);
|
||||
m_preview_table->setModel(m_model);
|
||||
preview();
|
||||
|
||||
setMinimumSize(QSize(760, 560));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableExportDialog::exec
|
||||
Ask for the file once the user accepted, and write the chosen
|
||||
columns to it -- the same flow as the material list export
|
||||
@return
|
||||
*/
|
||||
int CableExportDialog::exec()
|
||||
{
|
||||
const auto r = QDialog::exec();
|
||||
if (r == QDialog::Accepted)
|
||||
{
|
||||
QString dir = m_project->currentDir();
|
||||
if (dir.isEmpty()) dir = QETApp::documentDir();
|
||||
const QString file_name = dir + QLatin1Char('/')
|
||||
+ tr("cable_list_") + m_project->title()
|
||||
+ QStringLiteral(".csv");
|
||||
const QString file_path = QFileDialog::getSaveFileName(
|
||||
this, tr("Save As... "), file_name,
|
||||
tr("CSV files (*.csv)"));
|
||||
if (!file_path.isEmpty())
|
||||
{
|
||||
QString error;
|
||||
const auto csv = CableList::toCsv(
|
||||
m_project, selectedColumns(),
|
||||
m_include_headers->isChecked());
|
||||
if (!BomExport::writeCsv(file_path, csv, &error)) {
|
||||
QMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("Cannot save the cable list to %1.\n%2")
|
||||
.arg(file_path, error));
|
||||
}
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableExportDialog::selectedColumns
|
||||
@return the picked columns, and the default ones when the user
|
||||
picked nothing -- an export of everything is what a dialog
|
||||
confirmed without choosing asks for, the same fallback the drawn
|
||||
table uses
|
||||
*/
|
||||
QStringList CableExportDialog::selectedColumns() const
|
||||
{
|
||||
auto keys = m_query_widget->selectedKeys();
|
||||
if (keys.isEmpty()) {
|
||||
keys = CableList::defaultKeys();
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief CableExportDialog::preview
|
||||
Show what will be written: the chosen columns, worked out from the
|
||||
cables of the project as they are right now
|
||||
*/
|
||||
void CableExportDialog::preview()
|
||||
{
|
||||
m_model->setFields(selectedColumns());
|
||||
m_preview_table->resizeColumnsToContents();
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
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 3 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 CABLEEXPORTDIALOG_H
|
||||
#define CABLEEXPORTDIALOG_H
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDialog>
|
||||
|
||||
class QCheckBox;
|
||||
class QTableView;
|
||||
|
||||
class CableListModel;
|
||||
class CableQueryWidget;
|
||||
class QETProject;
|
||||
|
||||
/**
|
||||
@brief The CableExportDialog asks which columns of the cable list
|
||||
are exported and writes them to a CSV file, built the same way the
|
||||
BOMExportDialog does it for the material list: the column picker
|
||||
the cable list is built with on top, the page settings under it, a
|
||||
preview of what will be written, and the file asked for when the
|
||||
dialog is accepted.
|
||||
*/
|
||||
class CableExportDialog : public QDialog
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(CableExportDialog)
|
||||
|
||||
public:
|
||||
explicit CableExportDialog(QETProject *project, QWidget *parent = nullptr);
|
||||
int exec() override;
|
||||
|
||||
private:
|
||||
QStringList selectedColumns() const;
|
||||
void preview();
|
||||
|
||||
QETProject *m_project;
|
||||
CableQueryWidget *m_query_widget;
|
||||
CableListModel *m_model;
|
||||
QCheckBox *m_include_headers;
|
||||
QTableView *m_preview_table;
|
||||
};
|
||||
|
||||
#endif // CABLEEXPORTDIALOG_H
|
||||