From 4da7e54d75c048c456f5ee00b3888efdd0f3e434 Mon Sep 17 00:00:00 2001 From: xavier Date: Fri, 3 Apr 2009 19:30:25 +0000 Subject: [PATCH] Rapatriement de la branche 0.2 dans le trunk git-svn-id: svn+ssh://svn.tuxfamily.org/svnroot/qet/qet/trunk@558 bfdf4180-ca20-0410-9c96-a3a8aa849046 --- Doxyfile | 11 +- elements/automatisme/lader/input/entree.elmt | 1 + .../automatisme/lader/input/entree_fn.elmt | 1 + .../automatisme/lader/input/qet_directory | 1 + .../automatisme/lader/output/qet_directory | 1 + elements/automatisme/lader/output/sortie.elmt | 1 + .../automatisme/lader/output/sortie_r.elmt | 1 + .../automatisme/lader/output/sortie_s.elmt | 1 + elements/automatisme/lader/qet_directory | 1 + elements/automatisme/qet_directory | 1 + .../appareils_de_mesure/amperemetre-h.elmt | 16 + .../appareils_de_mesure/amperemetre-v.elmt | 16 + .../appareils_de_mesure/ohmmetre-h.elmt | 19 + .../appareils_de_mesure/ohmmetre-v.elmt | 19 + .../appareils_de_mesure/qet_directory | 7 + .../appareils_de_mesure/voltmetre-h.elmt | 15 + .../appareils_de_mesure/voltmetre-v.elmt | 15 + .../appareils_de_mesure/wattmetre-h.elmt | 19 + .../appareils_de_mesure/wattmetre-v.elmt | 19 + elements/capteurs/qet_directory | 1 + elements/capteurs/sondes/qet_directory | 1 + .../sondes/tores_de_courant/qet_directory | 1 + .../sondes/tores_de_courant/tore1.elmt | 1 + .../sondes/tores_de_courant/tore2.elmt | 1 + .../sondes/tores_de_courant/tore3.elmt | 1 + .../sondes/tores_de_courant/tore4.elmt | 1 + elements/contacts/interupteur/inter_2.elmt | 1 + elements/contacts/interupteur/inter_2n.elmt | 1 + .../contacts/interupteur/interupteur.elmt | 1 + elements/contacts/interupteur/qet_directory | 1 + elements/contacts/qet_directory | 1 + elements/convertisseurs/blocs/ac1_ac1.elmt | 1 + elements/convertisseurs/blocs/ac1_dc.elmt | 1 + elements/convertisseurs/blocs/dc_ac1.elmt | 1 + elements/convertisseurs/blocs/dc_dc.elmt | 1 + elements/convertisseurs/blocs/qet_directory | 1 + elements/convertisseurs/qet_directory | 1 + .../disjoncteurs/disjoncteur1.elmt | 1 + .../disjoncteurs/disjoncteur2.elmt | 1 + .../disjoncteurs/disjoncteur3.elmt | 1 + .../disjoncteurs/disjoncteur4.elmt | 1 + .../disjoncteurs/disjoncteur5.elmt | 1 + .../disjoncteurs/disjoncteur6.elmt | 1 + .../protections/disjoncteurs/qet_directory | 1 + .../disjoncteurs_differentiels/ddr1.elmt | 1 + .../disjoncteurs_differentiels/ddr2.elmt | 1 + .../disjoncteurs_differentiels/ddr3.elmt | 1 + .../disjoncteurs_differentiels/ddr4.elmt | 1 + .../disjoncteurs_differentiels/ddr5.elmt | 1 + .../disjoncteurs_differentiels/ddr6.elmt | 1 + .../disjoncteurs_differentiels/qet_directory | 1 + .../int_diff1.elmt | 20 + .../int_diff2.elmt | 25 + .../int_diff3.elmt | 30 + .../int_diff4.elmt | 35 + .../interrupteurs_differentiels/qet_directory | 7 + .../interrupteurs_sectionneurs/qet_directory | 6 + .../sectionneur1.elmt | 15 + .../sectionneur4.elmt | 27 + elements/protections/qet_directory | 1 + .../recepteurs/domestiques/convecteur.elmt | 2 +- elements/recepteurs/domestiques/four.elmt | 2 +- .../recepteurs/domestiques/lave_linge.elmt | 2 +- .../recepteurs/domestiques/lave_vaiselle.elmt | 2 +- .../domestiques/plaque_cuisson.elmt | 2 +- elements/recepteurs/machines/gene_tri.elmt | 1 + elements/recepteurs/machines/generatrice.elmt | 1 + .../recepteurs/machines/generatrice_dc.elmt | 1 + elements/recepteurs/machines/moteur.elmt | 1 + elements/recepteurs/machines/moteur_dc.elmt | 1 + elements/recepteurs/machines/moteur_mono.elmt | 1 + elements/recepteurs/machines/moteur_tri.elmt | 1 + elements/recepteurs/machines/qet_directory | 1 + elements/recepteurs/qet_directory | 1 + .../recepteurs/transformateurs/qet_directory | 1 + .../transformateurs/transfo_mono.elmt | 1 + .../transformateurs/transfo_tri.elmt | 1 + elements/semiconducteurs/del.elmt | 1 + elements/semiconducteurs/diode.elmt | 1 + elements/semiconducteurs/qet_directory | 1 + elements/semiconducteurs/thyristor.elmt | 1 + elements/sources/multifilaire/qet_directory | 1 + elements/sources/multifilaire/src_1pn.elmt | 1 + elements/sources/multifilaire/src_3p.elmt | 1 + elements/sources/qet_directory | 1 + .../sources/tevenin_norton/courant_n.elmt | 1 + .../sources/tevenin_norton/courant_o.elmt | 1 + elements/sources/tevenin_norton/qet_directory | 1 + .../sources/tevenin_norton/tention_n.elmt | 1 + .../sources/tevenin_norton/tention_o.elmt | 1 + ico/1leftarrow.png | Bin 0 -> 832 bytes ico/1rightarrow.png | Bin 0 -> 807 bytes ico/2leftarrow.png | Bin 0 -> 1143 bytes ico/2rightarrow.png | Bin 0 -> 1084 bytes ico/add_col.png | Bin ico/add_row.png | Bin ico/all_pages.png | Bin 0 -> 146 bytes ico/allowed.png | Bin ico/bring_forward.png | Bin ico/category_delete.png | Bin ico/category_edit.png | Bin ico/category_new.png | Bin ico/conf_new_diagram_110.png | Bin 0 -> 983 bytes ico/conf_new_diagram_128.png | Bin 0 -> 1060 bytes ...ew_diagram.png => conf_new_diagram_48.png} | Bin ico/confprint.png | Bin 0 -> 1000 bytes ico/diagram.png | Bin 0 -> 138 bytes ico/diagram_add.png | Bin 0 -> 313 bytes ico/diagram_del.png | Bin 0 -> 340 bytes ico/edit.png | Bin ico/endline-circle.png | Bin 0 -> 149 bytes ico/endline-diamond.png | Bin 0 -> 144 bytes ico/endline-none.png | Bin 0 -> 101 bytes ico/endline-simple.png | Bin 0 -> 132 bytes ico/endline-triangle.png | Bin 0 -> 139 bytes ico/erase.png | Bin ico/folder.png | Bin ico/folder_home.png | Bin ico/forbidden.png | Bin ico/item_cancel.png | Bin 0 -> 911 bytes ico/item_copy.png | Bin 0 -> 777 bytes ico/item_move.png | Bin 0 -> 592 bytes ico/landscape.png | Bin 0 -> 308 bytes ico/lock.png | Bin 0 -> 692 bytes ico/lower.png | Bin ico/mdiarea_bg.png | Bin 0 -> 9554 bytes ico/names.png | Bin ico/portrait.png | Bin 0 -> 297 bytes ico/printtype_pdf.png | Bin 0 -> 1943 bytes ico/printtype_printer.png | Bin 0 -> 1331 bytes ico/printtype_ps.png | Bin 0 -> 1568 bytes ico/project.png | Bin 0 -> 587 bytes ico/raise.png | Bin ico/rectangle.png | Bin 0 -> 292 bytes ico/remove_col.png | Bin ico/remove_row.png | Bin ico/save_all.png | Bin 0 -> 999 bytes ico/send_backward.png | Bin ico/settings.png | Bin 0 -> 14109 bytes ico/single_page.png | Bin 0 -> 137 bytes ico/splash.png | Bin 26062 -> 29015 bytes ico/start.png | Bin 0 -> 1324 bytes ico/two_pages.png | Bin 0 -> 133 bytes ico/unlock.png | Bin 0 -> 939 bytes ico/view_fit_width.png | Bin 0 -> 746 bytes ico/view_fit_window.png | Bin 0 -> 816 bytes ico/window_new.png | Bin lang/qet_en.qm | Bin 56160 -> 83514 bytes lang/qet_en.ts | 3710 ++++++--- lang/qet_es.qm | Bin 0 -> 86547 bytes lang/qet_es.ts | 4274 ++++++++++ lang/qet_fr.qm | Bin 0 -> 546 bytes lang/qet_fr.ts | 45 + lang/qt_es.qm | Bin 0 -> 117693 bytes lang/qt_es.ts | 7044 +++++++++++++++++ lang/qt_fr.qm | Bin 149725 -> 148544 bytes lang/qt_fr.ts | 597 +- man/files/fr.ISO8859-1/man1/qelectrotech.1 | 4 +- man/files/fr.UTF-8/man1/qelectrotech.1 | 4 +- man/files/fr/man1/qelectrotech.1 | 4 +- man/files/man1/qelectrotech.1 | 4 +- misc/launch_qet.sh | 2 +- misc/qelectrotech.conf | 2 +- misc/qelectrotech.desktop | 5 + misc/qelectrotech.spec | 2 +- misc/x-qet-element.desktop | 1 + misc/x-qet-element.xml | 1 + misc/x-qet-project.desktop | 1 + misc/x-qet-project.xml | 1 + qelectrotech.pro | 78 +- qelectrotech.qrc | 52 +- sources/aboutqet.cpp | 21 +- sources/aboutqet.h | 2 +- sources/basicmoveelementshandler.cpp | 126 + sources/basicmoveelementshandler.h | 62 + sources/borderinset.cpp | 48 +- sources/borderinset.h | 24 +- sources/borderproperties.cpp | 73 +- sources/borderproperties.h | 8 +- sources/borderpropertieswidget.cpp | 18 +- sources/borderpropertieswidget.h | 2 +- sources/conductor.cpp | 4 +- sources/conductor.h | 2 +- sources/conductorprofile.cpp | 2 +- sources/conductorprofile.h | 2 +- sources/conductorproperties.cpp | 22 +- sources/conductorproperties.h | 6 +- sources/conductorpropertieswidget.cpp | 2 +- sources/conductorpropertieswidget.h | 3 +- sources/conductorsegment.cpp | 2 +- sources/conductorsegment.h | 2 +- sources/conductorsegmentprofile.h | 2 +- sources/configdialog.cpp | 7 +- sources/configdialog.h | 2 +- sources/configpages.cpp | 118 +- sources/configpages.h | 33 +- sources/customelement.cpp | 325 +- sources/customelement.h | 81 +- sources/diagram.cpp | 217 +- sources/diagram.h | 29 +- sources/diagramcommands.cpp | 79 +- sources/diagramcommands.h | 2 +- sources/diagramcontent.cpp | 2 +- sources/diagramcontent.h | 2 +- sources/diagramprintdialog.cpp | 365 +- sources/diagramprintdialog.h | 63 +- sources/diagramschooser.cpp | 159 + sources/diagramschooser.h | 61 + sources/diagramtextitem.cpp | 6 +- sources/diagramtextitem.h | 2 +- sources/diagramview.cpp | 444 +- sources/diagramview.h | 29 +- sources/editor/arceditor.cpp | 2 +- sources/editor/arceditor.h | 2 +- sources/editor/circleeditor.cpp | 2 +- sources/editor/circleeditor.h | 2 +- sources/editor/customelementgraphicpart.cpp | 2 +- sources/editor/customelementgraphicpart.h | 3 +- sources/editor/customelementpart.cpp | 2 +- sources/editor/customelementpart.h | 2 +- sources/editor/editorcommands.cpp | 116 +- sources/editor/editorcommands.h | 50 +- sources/editor/elementcontent.h | 30 + sources/editor/elementitemeditor.cpp | 2 +- sources/editor/elementitemeditor.h | 4 +- sources/editor/elementscene.cpp | 607 +- sources/editor/elementscene.h | 56 +- sources/editor/elementview.cpp | 320 +- sources/editor/elementview.h | 25 +- sources/editor/ellipseeditor.cpp | 8 +- sources/editor/ellipseeditor.h | 2 +- sources/editor/lineeditor.cpp | 83 +- sources/editor/lineeditor.h | 8 +- sources/editor/partarc.cpp | 3 +- sources/editor/partarc.h | 4 +- sources/editor/partcircle.cpp | 3 +- sources/editor/partcircle.h | 4 +- sources/editor/partellipse.cpp | 3 +- sources/editor/partellipse.h | 4 +- sources/editor/partline.cpp | 325 +- sources/editor/partline.h | 31 +- sources/editor/partpolygon.cpp | 3 +- sources/editor/partpolygon.h | 4 +- sources/editor/partrectangle.cpp | 207 + sources/editor/partrectangle.h | 61 + sources/editor/partterminal.cpp | 3 +- sources/editor/partterminal.h | 4 +- sources/editor/parttext.cpp | 16 +- sources/editor/parttext.h | 4 +- sources/editor/parttextfield.cpp | 15 +- sources/editor/parttextfield.h | 4 +- sources/editor/polygoneditor.cpp | 6 +- sources/editor/polygoneditor.h | 2 +- sources/editor/qetelementeditor.cpp | 389 +- sources/editor/qetelementeditor.h | 61 +- sources/editor/rectangleeditor.cpp | 108 + sources/editor/rectangleeditor.h | 53 + sources/editor/styleeditor.cpp | 22 +- sources/editor/styleeditor.h | 2 +- sources/editor/terminaleditor.cpp | 2 +- sources/editor/terminaleditor.h | 2 +- sources/editor/texteditor.cpp | 4 +- sources/editor/texteditor.h | 2 +- sources/editor/textfieldeditor.cpp | 4 +- sources/editor/textfieldeditor.h | 2 +- sources/element.cpp | 16 +- sources/element.h | 12 +- sources/elementdefinition.cpp | 461 ++ sources/elementdefinition.h | 126 + sources/elementdeleter.cpp | 43 +- sources/elementdeleter.h | 11 +- sources/elementdialog.cpp | 348 + sources/elementdialog.h | 83 + sources/elementscategorieslist.cpp | 176 +- sources/elementscategorieslist.h | 31 +- sources/elementscategorieswidget.cpp | 24 +- sources/elementscategorieswidget.h | 2 +- sources/elementscategory.cpp | 671 +- sources/elementscategory.h | 79 +- sources/elementscategorydeleter.cpp | 81 +- sources/elementscategorydeleter.h | 18 +- sources/elementscategoryeditor.cpp | 110 +- sources/elementscategoryeditor.h | 8 +- sources/elementscollection.cpp | 406 + sources/elementscollection.h | 92 + sources/elementscollectionitem.h | 206 + sources/elementslocation.cpp | 207 + sources/elementslocation.h | 56 + sources/elementspanel.cpp | 939 ++- sources/elementspanel.h | 99 +- sources/elementspanelwidget.cpp | 437 +- sources/elementspanelwidget.h | 38 +- sources/elementtextitem.cpp | 2 +- sources/elementtextitem.h | 2 +- sources/exportdialog.cpp | 697 +- sources/exportdialog.h | 80 +- sources/fileelementdefinition.cpp | 198 + sources/fileelementdefinition.h | 59 + sources/fileelementscategory.cpp | 495 ++ sources/fileelementscategory.h | 83 + sources/fileelementscollection.cpp | 125 + sources/fileelementscollection.h | 60 + sources/fixedelement.cpp | 10 +- sources/fixedelement.h | 12 +- sources/ghostelement.cpp | 154 + sources/ghostelement.h | 54 + sources/hotspoteditor.cpp | 10 +- sources/hotspoteditor.h | 3 +- sources/insetproperties.cpp | 103 +- sources/insetproperties.h | 16 +- sources/insetpropertieswidget.cpp | 14 +- sources/insetpropertieswidget.h | 2 +- sources/integrationmoveelementshandler.cpp | 201 + sources/integrationmoveelementshandler.h | 73 + sources/interactivemoveelementshandler.cpp | 371 + sources/interactivemoveelementshandler.h | 96 + sources/main.cpp | 2 +- sources/moveelementsdescription.cpp | 163 + sources/moveelementsdescription.h | 76 + sources/moveelementshandler.h | 101 + sources/nameslist.cpp | 2 +- sources/nameslist.h | 2 +- sources/nameslistwidget.cpp | 7 +- sources/nameslistwidget.h | 2 +- sources/newelementwizard.cpp | 99 +- sources/newelementwizard.h | 14 +- sources/orientationset.cpp | 2 +- sources/orientationset.h | 2 +- sources/orientationsetwidget.cpp | 2 +- sources/orientationsetwidget.h | 2 +- sources/projectview.cpp | 793 ++ sources/projectview.h | 98 + sources/qet.cpp | 147 +- sources/qet.h | 71 +- sources/qetapp.cpp | 262 +- sources/qetapp.h | 37 +- sources/qetarguments.cpp | 2 +- sources/qetarguments.h | 2 +- sources/qetdiagrameditor.cpp | 1186 ++- sources/qetdiagrameditor.h | 74 +- sources/qetprintpreviewdialog.cpp | 359 + sources/qetprintpreviewdialog.h | 107 + sources/qetproject.cpp | 979 +++ sources/qetproject.h | 157 + sources/qetregexpvalidator.cpp | 33 + sources/qetregexpvalidator.h | 43 + sources/qetsingleapplication.cpp | 2 +- sources/qetsingleapplication.h | 2 +- sources/qettabbar.cpp | 323 + sources/qettabbar.h | 78 + sources/qettabwidget.cpp | 74 + sources/qettabwidget.h | 57 + sources/qfilenameedit.cpp | 83 + sources/qfilenameedit.h | 60 + sources/qgimanager.cpp | 2 +- sources/qgimanager.h | 2 +- sources/recentfiles.cpp | 4 +- sources/recentfiles.h | 2 +- sources/terminal.cpp | 4 +- sources/terminal.h | 2 +- sources/xmlelementdefinition.cpp | 220 + sources/xmlelementdefinition.h | 69 + sources/xmlelementscategory.cpp | 487 ++ sources/xmlelementscategory.h | 95 + sources/xmlelementscollection.cpp | 135 + sources/xmlelementscollection.h | 66 + 366 files changed, 31970 insertions(+), 3696 deletions(-) create mode 100644 elements/capteurs/appareils_de_mesure/amperemetre-h.elmt create mode 100644 elements/capteurs/appareils_de_mesure/amperemetre-v.elmt create mode 100644 elements/capteurs/appareils_de_mesure/ohmmetre-h.elmt create mode 100644 elements/capteurs/appareils_de_mesure/ohmmetre-v.elmt create mode 100644 elements/capteurs/appareils_de_mesure/qet_directory create mode 100644 elements/capteurs/appareils_de_mesure/voltmetre-h.elmt create mode 100644 elements/capteurs/appareils_de_mesure/voltmetre-v.elmt create mode 100644 elements/capteurs/appareils_de_mesure/wattmetre-h.elmt create mode 100644 elements/capteurs/appareils_de_mesure/wattmetre-v.elmt create mode 100644 elements/protections/interrupteurs_differentiels/int_diff1.elmt create mode 100644 elements/protections/interrupteurs_differentiels/int_diff2.elmt create mode 100644 elements/protections/interrupteurs_differentiels/int_diff3.elmt create mode 100644 elements/protections/interrupteurs_differentiels/int_diff4.elmt create mode 100644 elements/protections/interrupteurs_differentiels/qet_directory create mode 100644 elements/protections/interrupteurs_sectionneurs/qet_directory create mode 100644 elements/protections/interrupteurs_sectionneurs/sectionneur1.elmt create mode 100644 elements/protections/interrupteurs_sectionneurs/sectionneur4.elmt create mode 100644 ico/1leftarrow.png create mode 100644 ico/1rightarrow.png create mode 100644 ico/2leftarrow.png create mode 100644 ico/2rightarrow.png mode change 100755 => 100644 ico/add_col.png mode change 100755 => 100644 ico/add_row.png create mode 100644 ico/all_pages.png mode change 100755 => 100644 ico/allowed.png mode change 100755 => 100644 ico/bring_forward.png mode change 100755 => 100644 ico/category_delete.png mode change 100755 => 100644 ico/category_edit.png mode change 100755 => 100644 ico/category_new.png create mode 100644 ico/conf_new_diagram_110.png create mode 100644 ico/conf_new_diagram_128.png rename ico/{conf_new_diagram.png => conf_new_diagram_48.png} (100%) create mode 100644 ico/confprint.png create mode 100644 ico/diagram.png create mode 100644 ico/diagram_add.png create mode 100644 ico/diagram_del.png mode change 100755 => 100644 ico/edit.png create mode 100644 ico/endline-circle.png create mode 100644 ico/endline-diamond.png create mode 100644 ico/endline-none.png create mode 100644 ico/endline-simple.png create mode 100644 ico/endline-triangle.png mode change 100755 => 100644 ico/erase.png mode change 100755 => 100644 ico/folder.png mode change 100755 => 100644 ico/folder_home.png mode change 100755 => 100644 ico/forbidden.png create mode 100644 ico/item_cancel.png create mode 100644 ico/item_copy.png create mode 100644 ico/item_move.png create mode 100644 ico/landscape.png create mode 100644 ico/lock.png mode change 100755 => 100644 ico/lower.png create mode 100644 ico/mdiarea_bg.png mode change 100755 => 100644 ico/names.png create mode 100644 ico/portrait.png create mode 100644 ico/printtype_pdf.png create mode 100644 ico/printtype_printer.png create mode 100644 ico/printtype_ps.png create mode 100644 ico/project.png mode change 100755 => 100644 ico/raise.png create mode 100644 ico/rectangle.png mode change 100755 => 100644 ico/remove_col.png mode change 100755 => 100644 ico/remove_row.png create mode 100644 ico/save_all.png mode change 100755 => 100644 ico/send_backward.png create mode 100644 ico/settings.png create mode 100644 ico/single_page.png create mode 100644 ico/start.png create mode 100644 ico/two_pages.png create mode 100644 ico/unlock.png create mode 100644 ico/view_fit_width.png create mode 100644 ico/view_fit_window.png mode change 100755 => 100644 ico/window_new.png create mode 100644 lang/qet_es.qm create mode 100644 lang/qet_es.ts create mode 100644 lang/qet_fr.qm create mode 100644 lang/qet_fr.ts create mode 100644 lang/qt_es.qm create mode 100644 lang/qt_es.ts create mode 100644 sources/basicmoveelementshandler.cpp create mode 100644 sources/basicmoveelementshandler.h create mode 100644 sources/diagramschooser.cpp create mode 100644 sources/diagramschooser.h create mode 100644 sources/editor/elementcontent.h create mode 100644 sources/editor/partrectangle.cpp create mode 100644 sources/editor/partrectangle.h create mode 100644 sources/editor/rectangleeditor.cpp create mode 100644 sources/editor/rectangleeditor.h create mode 100644 sources/elementdefinition.cpp create mode 100644 sources/elementdefinition.h create mode 100644 sources/elementdialog.cpp create mode 100644 sources/elementdialog.h create mode 100644 sources/elementscollection.cpp create mode 100644 sources/elementscollection.h create mode 100644 sources/elementscollectionitem.h create mode 100644 sources/elementslocation.cpp create mode 100644 sources/elementslocation.h create mode 100644 sources/fileelementdefinition.cpp create mode 100644 sources/fileelementdefinition.h create mode 100644 sources/fileelementscategory.cpp create mode 100644 sources/fileelementscategory.h create mode 100644 sources/fileelementscollection.cpp create mode 100644 sources/fileelementscollection.h create mode 100644 sources/ghostelement.cpp create mode 100644 sources/ghostelement.h create mode 100644 sources/integrationmoveelementshandler.cpp create mode 100644 sources/integrationmoveelementshandler.h create mode 100644 sources/interactivemoveelementshandler.cpp create mode 100644 sources/interactivemoveelementshandler.h create mode 100644 sources/moveelementsdescription.cpp create mode 100644 sources/moveelementsdescription.h create mode 100644 sources/moveelementshandler.h create mode 100644 sources/projectview.cpp create mode 100644 sources/projectview.h create mode 100644 sources/qetprintpreviewdialog.cpp create mode 100644 sources/qetprintpreviewdialog.h create mode 100644 sources/qetproject.cpp create mode 100644 sources/qetproject.h create mode 100644 sources/qetregexpvalidator.cpp create mode 100644 sources/qetregexpvalidator.h create mode 100644 sources/qettabbar.cpp create mode 100644 sources/qettabbar.h create mode 100644 sources/qettabwidget.cpp create mode 100644 sources/qettabwidget.h create mode 100644 sources/qfilenameedit.cpp create mode 100644 sources/qfilenameedit.h create mode 100644 sources/xmlelementdefinition.cpp create mode 100644 sources/xmlelementdefinition.h create mode 100644 sources/xmlelementscategory.cpp create mode 100644 sources/xmlelementscategory.h create mode 100644 sources/xmlelementscollection.cpp create mode 100644 sources/xmlelementscollection.h diff --git a/Doxyfile b/Doxyfile index 6d6c8af80..d6c5511da 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,11 +1,11 @@ -# Doxyfile 1.5.5 +# Doxyfile 1.5.6 #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = QElectroTech -PROJECT_NUMBER = 0.1 +PROJECT_NUMBER = 0.2 OUTPUT_DIRECTORY = CREATE_SUBDIRS = NO OUTPUT_LANGUAGE = French @@ -43,6 +43,7 @@ OPTIMIZE_OUTPUT_VHDL = NO BUILTIN_STL_SUPPORT = NO CPP_CLI_SUPPORT = NO SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES DISTRIBUTE_GROUP_DOC = NO SUBGROUPING = YES TYPEDEF_HIDES_STRUCT = NO @@ -76,6 +77,8 @@ ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 SHOW_USED_FILES = YES SHOW_DIRECTORIES = NO +SHOW_FILES = YES +SHOW_NAMESPACES = YES FILE_VERSION_FILTER = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages @@ -144,12 +147,14 @@ HTML_DYNAMIC_SECTIONS = NO CHM_FILE = HHC_LOCATION = GENERATE_CHI = NO +CHM_INDEX_ENCODING = BINARY_TOC = NO TOC_EXPAND = NO DISABLE_INDEX = NO ENUM_VALUES_PER_LINE = 4 GENERATE_TREEVIEW = NO TREEVIEW_WIDTH = 250 +FORMULA_FONTSIZE = 10 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- @@ -227,6 +232,8 @@ CLASS_DIAGRAMS = YES MSCGEN_PATH = HIDE_UNDOC_RELATIONS = YES HAVE_DOT = YES +DOT_FONTNAME = FreeSans +DOT_FONTPATH = CLASS_GRAPH = YES COLLABORATION_GRAPH = YES GROUP_GRAPHS = YES diff --git a/elements/automatisme/lader/input/entree.elmt b/elements/automatisme/lader/input/entree.elmt index 936f038eb..03f0bce37 100644 --- a/elements/automatisme/lader/input/entree.elmt +++ b/elements/automatisme/lader/input/entree.elmt @@ -2,6 +2,7 @@ Input Entrée + Entrada diff --git a/elements/automatisme/lader/input/entree_fn.elmt b/elements/automatisme/lader/input/entree_fn.elmt index 8f0b18c44..3aedd818e 100644 --- a/elements/automatisme/lader/input/entree_fn.elmt +++ b/elements/automatisme/lader/input/entree_fn.elmt @@ -2,6 +2,7 @@ Input (down front) Entrée (front descendant) + Entrada (front descendant) diff --git a/elements/automatisme/lader/input/qet_directory b/elements/automatisme/lader/input/qet_directory index 57db71c16..1de262434 100644 --- a/elements/automatisme/lader/input/qet_directory +++ b/elements/automatisme/lader/input/qet_directory @@ -2,5 +2,6 @@ Inputs Entrées + Entradas diff --git a/elements/automatisme/lader/output/qet_directory b/elements/automatisme/lader/output/qet_directory index f75360504..30e565a99 100644 --- a/elements/automatisme/lader/output/qet_directory +++ b/elements/automatisme/lader/output/qet_directory @@ -2,5 +2,6 @@ Outputs Sorties + Salidas diff --git a/elements/automatisme/lader/output/sortie.elmt b/elements/automatisme/lader/output/sortie.elmt index 4bb57208e..6ca5053bf 100644 --- a/elements/automatisme/lader/output/sortie.elmt +++ b/elements/automatisme/lader/output/sortie.elmt @@ -2,6 +2,7 @@ Output Sortie + Salida diff --git a/elements/automatisme/lader/output/sortie_r.elmt b/elements/automatisme/lader/output/sortie_r.elmt index b319afb4b..6a4752d52 100644 --- a/elements/automatisme/lader/output/sortie_r.elmt +++ b/elements/automatisme/lader/output/sortie_r.elmt @@ -2,6 +2,7 @@ Output (reset) Sortie (reset) + Salida (reset) diff --git a/elements/automatisme/lader/output/sortie_s.elmt b/elements/automatisme/lader/output/sortie_s.elmt index bca928002..41e1a6fb9 100644 --- a/elements/automatisme/lader/output/sortie_s.elmt +++ b/elements/automatisme/lader/output/sortie_s.elmt @@ -2,6 +2,7 @@ Output (set) Sortie (set) + Salida (set) diff --git a/elements/automatisme/lader/qet_directory b/elements/automatisme/lader/qet_directory index 36c9529ae..aae1d9160 100644 --- a/elements/automatisme/lader/qet_directory +++ b/elements/automatisme/lader/qet_directory @@ -2,5 +2,6 @@ Ladder Ladder + Ladder diff --git a/elements/automatisme/qet_directory b/elements/automatisme/qet_directory index 17338d38d..ab783d2f8 100644 --- a/elements/automatisme/qet_directory +++ b/elements/automatisme/qet_directory @@ -2,5 +2,6 @@ Automatisme Automatism + Automática diff --git a/elements/capteurs/appareils_de_mesure/amperemetre-h.elmt b/elements/capteurs/appareils_de_mesure/amperemetre-h.elmt new file mode 100644 index 000000000..c3aba38ba --- /dev/null +++ b/elements/capteurs/appareils_de_mesure/amperemetre-h.elmt @@ -0,0 +1,16 @@ + + + Horizontal ammeter + Ampèremètre horizontal + Amperímetro horizontal + + + + + + + + + + + diff --git a/elements/capteurs/appareils_de_mesure/amperemetre-v.elmt b/elements/capteurs/appareils_de_mesure/amperemetre-v.elmt new file mode 100644 index 000000000..588e0bd32 --- /dev/null +++ b/elements/capteurs/appareils_de_mesure/amperemetre-v.elmt @@ -0,0 +1,16 @@ + + + Vertical ammeter + Ampèremètre vertical + Amperímetro vertical + + + + + + + + + + + diff --git a/elements/capteurs/appareils_de_mesure/ohmmetre-h.elmt b/elements/capteurs/appareils_de_mesure/ohmmetre-h.elmt new file mode 100644 index 000000000..7e5f80f92 --- /dev/null +++ b/elements/capteurs/appareils_de_mesure/ohmmetre-h.elmt @@ -0,0 +1,19 @@ + + + Horizontal ohmmeter + Ohmmètre horizontal + Óhmetro horizontal + + + + + + + + + + + + + + diff --git a/elements/capteurs/appareils_de_mesure/ohmmetre-v.elmt b/elements/capteurs/appareils_de_mesure/ohmmetre-v.elmt new file mode 100644 index 000000000..1923b444b --- /dev/null +++ b/elements/capteurs/appareils_de_mesure/ohmmetre-v.elmt @@ -0,0 +1,19 @@ + + + Vertical ohmmeter + Ohmmètre vertical + Óhmetro vertical + + + + + + + + + + + + + + diff --git a/elements/capteurs/appareils_de_mesure/qet_directory b/elements/capteurs/appareils_de_mesure/qet_directory new file mode 100644 index 000000000..42679014d --- /dev/null +++ b/elements/capteurs/appareils_de_mesure/qet_directory @@ -0,0 +1,7 @@ + + + Measuring instruments + Appareils de mesure + Instrumento de medición + + diff --git a/elements/capteurs/appareils_de_mesure/voltmetre-h.elmt b/elements/capteurs/appareils_de_mesure/voltmetre-h.elmt new file mode 100644 index 000000000..44540d0e6 --- /dev/null +++ b/elements/capteurs/appareils_de_mesure/voltmetre-h.elmt @@ -0,0 +1,15 @@ + + + Horizontal voltmeter + Voltmètre horizontal + Voltímetro horizontal + + + + + + + + + + diff --git a/elements/capteurs/appareils_de_mesure/voltmetre-v.elmt b/elements/capteurs/appareils_de_mesure/voltmetre-v.elmt new file mode 100644 index 000000000..6d2ee1e94 --- /dev/null +++ b/elements/capteurs/appareils_de_mesure/voltmetre-v.elmt @@ -0,0 +1,15 @@ + + + Vertical voltmeter + Voltmètre vertical + Voltímetro vertical + + + + + + + + + + diff --git a/elements/capteurs/appareils_de_mesure/wattmetre-h.elmt b/elements/capteurs/appareils_de_mesure/wattmetre-h.elmt new file mode 100644 index 000000000..3444a2465 --- /dev/null +++ b/elements/capteurs/appareils_de_mesure/wattmetre-h.elmt @@ -0,0 +1,19 @@ + + + Horizontal wattmeter + Wattmètre horizontal + Vatímetro horizontal + + + + + + + + + + + + + + diff --git a/elements/capteurs/appareils_de_mesure/wattmetre-v.elmt b/elements/capteurs/appareils_de_mesure/wattmetre-v.elmt new file mode 100644 index 000000000..b1d27eb07 --- /dev/null +++ b/elements/capteurs/appareils_de_mesure/wattmetre-v.elmt @@ -0,0 +1,19 @@ + + + Vertical wattmeter + Wattmètre vertical + Vatímetro vertical + + + + + + + + + + + + + + diff --git a/elements/capteurs/qet_directory b/elements/capteurs/qet_directory index 1da1b8f4b..034499249 100644 --- a/elements/capteurs/qet_directory +++ b/elements/capteurs/qet_directory @@ -2,5 +2,6 @@ Transducers Capteurs + Sensores diff --git a/elements/capteurs/sondes/qet_directory b/elements/capteurs/sondes/qet_directory index c0b76a7d6..5cc84876f 100644 --- a/elements/capteurs/sondes/qet_directory +++ b/elements/capteurs/sondes/qet_directory @@ -2,5 +2,6 @@ Sensors Sondes + Sondeos diff --git a/elements/capteurs/sondes/tores_de_courant/qet_directory b/elements/capteurs/sondes/tores_de_courant/qet_directory index e98cd49d6..856480d72 100644 --- a/elements/capteurs/sondes/tores_de_courant/qet_directory +++ b/elements/capteurs/sondes/tores_de_courant/qet_directory @@ -2,5 +2,6 @@ Current toroidal core Tores de courant + Toros de corriente diff --git a/elements/capteurs/sondes/tores_de_courant/tore1.elmt b/elements/capteurs/sondes/tores_de_courant/tore1.elmt index 3f820d631..3e7f9dfa3 100644 --- a/elements/capteurs/sondes/tores_de_courant/tore1.elmt +++ b/elements/capteurs/sondes/tores_de_courant/tore1.elmt @@ -2,6 +2,7 @@ 1-pole toroidal core Tore 1 pôle + Toro 1 polo diff --git a/elements/capteurs/sondes/tores_de_courant/tore2.elmt b/elements/capteurs/sondes/tores_de_courant/tore2.elmt index fea4c2ab3..c4ba2729e 100644 --- a/elements/capteurs/sondes/tores_de_courant/tore2.elmt +++ b/elements/capteurs/sondes/tores_de_courant/tore2.elmt @@ -2,6 +2,7 @@ 2-poles toroidal core Tore 2 pôles + Toro 2 polos diff --git a/elements/capteurs/sondes/tores_de_courant/tore3.elmt b/elements/capteurs/sondes/tores_de_courant/tore3.elmt index 4ff63bf12..4aea75ac3 100644 --- a/elements/capteurs/sondes/tores_de_courant/tore3.elmt +++ b/elements/capteurs/sondes/tores_de_courant/tore3.elmt @@ -2,6 +2,7 @@ 3-poles toroidal core Tore 3 pôles + Toro 3 polos diff --git a/elements/capteurs/sondes/tores_de_courant/tore4.elmt b/elements/capteurs/sondes/tores_de_courant/tore4.elmt index a0b2c7586..780c12e9a 100644 --- a/elements/capteurs/sondes/tores_de_courant/tore4.elmt +++ b/elements/capteurs/sondes/tores_de_courant/tore4.elmt @@ -2,6 +2,7 @@ 4-poles toroidal core Tore 4 pôles + Toro 4 polos diff --git a/elements/contacts/interupteur/inter_2.elmt b/elements/contacts/interupteur/inter_2.elmt index cfedab295..2f838acb7 100644 --- a/elements/contacts/interupteur/inter_2.elmt +++ b/elements/contacts/interupteur/inter_2.elmt @@ -2,6 +2,7 @@ Switch 2 positions Interrupteur 2 positions + Interruptor 2 posiciones diff --git a/elements/contacts/interupteur/inter_2n.elmt b/elements/contacts/interupteur/inter_2n.elmt index ad654ed39..7e430cbf3 100644 --- a/elements/contacts/interupteur/inter_2n.elmt +++ b/elements/contacts/interupteur/inter_2n.elmt @@ -2,6 +2,7 @@ Switch 3 positions including neutral Interrupteur 3 positions dont neutre + Interruptor 3 posiciones incluso neutro diff --git a/elements/contacts/interupteur/interupteur.elmt b/elements/contacts/interupteur/interupteur.elmt index 843fec638..7396424ec 100644 --- a/elements/contacts/interupteur/interupteur.elmt +++ b/elements/contacts/interupteur/interupteur.elmt @@ -2,6 +2,7 @@ Switch Interrupteur + Interruptor diff --git a/elements/contacts/interupteur/qet_directory b/elements/contacts/interupteur/qet_directory index 00d8cded7..74dcb25cd 100644 --- a/elements/contacts/interupteur/qet_directory +++ b/elements/contacts/interupteur/qet_directory @@ -2,5 +2,6 @@ Switchs Interrupteurs + Interruptores diff --git a/elements/contacts/qet_directory b/elements/contacts/qet_directory index d8b0895dd..4078b65dc 100644 --- a/elements/contacts/qet_directory +++ b/elements/contacts/qet_directory @@ -2,5 +2,6 @@ Contacts Contacts + Contactos diff --git a/elements/convertisseurs/blocs/ac1_ac1.elmt b/elements/convertisseurs/blocs/ac1_ac1.elmt index 0565ccbbe..f71274dda 100644 --- a/elements/convertisseurs/blocs/ac1_ac1.elmt +++ b/elements/convertisseurs/blocs/ac1_ac1.elmt @@ -2,6 +2,7 @@ Alternatif monophasé > Alternatif monophasé One-phase alternating > One-phase alternating + Alterna monofásica > Alterna monofásica diff --git a/elements/convertisseurs/blocs/ac1_dc.elmt b/elements/convertisseurs/blocs/ac1_dc.elmt index 2b5c7ea1f..3890d752b 100644 --- a/elements/convertisseurs/blocs/ac1_dc.elmt +++ b/elements/convertisseurs/blocs/ac1_dc.elmt @@ -2,6 +2,7 @@ Alternatif monophasé > Continu One-phase alternating > Direct + Alterna monofásica > Continua diff --git a/elements/convertisseurs/blocs/dc_ac1.elmt b/elements/convertisseurs/blocs/dc_ac1.elmt index f3347fdee..1dd19ac36 100644 --- a/elements/convertisseurs/blocs/dc_ac1.elmt +++ b/elements/convertisseurs/blocs/dc_ac1.elmt @@ -2,6 +2,7 @@ Continu > Alternatif monophasé Direct > One-phase alternating + Continua > Alterna monofásica diff --git a/elements/convertisseurs/blocs/dc_dc.elmt b/elements/convertisseurs/blocs/dc_dc.elmt index 6ef4e2879..558d5a1d3 100644 --- a/elements/convertisseurs/blocs/dc_dc.elmt +++ b/elements/convertisseurs/blocs/dc_dc.elmt @@ -2,6 +2,7 @@ Continu > Continu Direct > Direct + Continua > Continua diff --git a/elements/convertisseurs/blocs/qet_directory b/elements/convertisseurs/blocs/qet_directory index a0dccb05c..79b51b913 100644 --- a/elements/convertisseurs/blocs/qet_directory +++ b/elements/convertisseurs/blocs/qet_directory @@ -2,5 +2,6 @@ Boxes Blocs + Bloques diff --git a/elements/convertisseurs/qet_directory b/elements/convertisseurs/qet_directory index 0d9ce2768..71cc07b09 100644 --- a/elements/convertisseurs/qet_directory +++ b/elements/convertisseurs/qet_directory @@ -2,5 +2,6 @@ Convertisseurs Converters + Convertidores diff --git a/elements/protections/disjoncteurs/disjoncteur1.elmt b/elements/protections/disjoncteurs/disjoncteur1.elmt index b0023b60c..90cee77bc 100644 --- a/elements/protections/disjoncteurs/disjoncteur1.elmt +++ b/elements/protections/disjoncteurs/disjoncteur1.elmt @@ -2,6 +2,7 @@ Circuit-breaker Disjoncteur + Interruptor magnetotérmico diff --git a/elements/protections/disjoncteurs/disjoncteur2.elmt b/elements/protections/disjoncteurs/disjoncteur2.elmt index 45707ef40..3102b9961 100644 --- a/elements/protections/disjoncteurs/disjoncteur2.elmt +++ b/elements/protections/disjoncteurs/disjoncteur2.elmt @@ -2,6 +2,7 @@ Circuit-breaker Disjoncteur + Interruptor magnetotérmico diff --git a/elements/protections/disjoncteurs/disjoncteur3.elmt b/elements/protections/disjoncteurs/disjoncteur3.elmt index 1d199a0d0..e6a61a385 100644 --- a/elements/protections/disjoncteurs/disjoncteur3.elmt +++ b/elements/protections/disjoncteurs/disjoncteur3.elmt @@ -2,6 +2,7 @@ Circuit-breaker Disjoncteur + Interruptor magnetotérmico diff --git a/elements/protections/disjoncteurs/disjoncteur4.elmt b/elements/protections/disjoncteurs/disjoncteur4.elmt index 85df33c65..aa88ebf15 100644 --- a/elements/protections/disjoncteurs/disjoncteur4.elmt +++ b/elements/protections/disjoncteurs/disjoncteur4.elmt @@ -2,6 +2,7 @@ Circuit-breaker Disjoncteur + Interruptor magnetotérmico diff --git a/elements/protections/disjoncteurs/disjoncteur5.elmt b/elements/protections/disjoncteurs/disjoncteur5.elmt index 5a63b0c87..036a29de5 100644 --- a/elements/protections/disjoncteurs/disjoncteur5.elmt +++ b/elements/protections/disjoncteurs/disjoncteur5.elmt @@ -2,6 +2,7 @@ Circuit-breaker Disjoncteur + Interruptor magnetotérmico diff --git a/elements/protections/disjoncteurs/disjoncteur6.elmt b/elements/protections/disjoncteurs/disjoncteur6.elmt index 7d14e9e59..a6a105ded 100644 --- a/elements/protections/disjoncteurs/disjoncteur6.elmt +++ b/elements/protections/disjoncteurs/disjoncteur6.elmt @@ -2,6 +2,7 @@ Circuit-breaker Disjoncteur + Interruptor magnetotérmico diff --git a/elements/protections/disjoncteurs/qet_directory b/elements/protections/disjoncteurs/qet_directory index d4c0f0fe4..5eb441f83 100644 --- a/elements/protections/disjoncteurs/qet_directory +++ b/elements/protections/disjoncteurs/qet_directory @@ -2,5 +2,6 @@ Circuit-breakers Disjoncteurs + Interruptores magnetotérmicos diff --git a/elements/protections/disjoncteurs_differentiels/ddr1.elmt b/elements/protections/disjoncteurs_differentiels/ddr1.elmt index 8a0ebc42b..79a83ab64 100644 --- a/elements/protections/disjoncteurs_differentiels/ddr1.elmt +++ b/elements/protections/disjoncteurs_differentiels/ddr1.elmt @@ -2,6 +2,7 @@ Differential circuit-breaker Disjoncteur différentiel + Dispositivo diferenciale residual diff --git a/elements/protections/disjoncteurs_differentiels/ddr2.elmt b/elements/protections/disjoncteurs_differentiels/ddr2.elmt index 96cf3fd99..749386026 100644 --- a/elements/protections/disjoncteurs_differentiels/ddr2.elmt +++ b/elements/protections/disjoncteurs_differentiels/ddr2.elmt @@ -2,6 +2,7 @@ Differential circuit-breaker Disjoncteur différentiel + Dispositivo diferenciale residual diff --git a/elements/protections/disjoncteurs_differentiels/ddr3.elmt b/elements/protections/disjoncteurs_differentiels/ddr3.elmt index d9daa4179..30502845f 100644 --- a/elements/protections/disjoncteurs_differentiels/ddr3.elmt +++ b/elements/protections/disjoncteurs_differentiels/ddr3.elmt @@ -2,6 +2,7 @@ Differential circuit-breaker Disjoncteur différentiel + Dispositivo diferenciale residual diff --git a/elements/protections/disjoncteurs_differentiels/ddr4.elmt b/elements/protections/disjoncteurs_differentiels/ddr4.elmt index ef5f0eb66..8c6c3ae71 100644 --- a/elements/protections/disjoncteurs_differentiels/ddr4.elmt +++ b/elements/protections/disjoncteurs_differentiels/ddr4.elmt @@ -2,6 +2,7 @@ Differential circuit-breaker Disjoncteur différentiel + Dispositivo diferenciale residual diff --git a/elements/protections/disjoncteurs_differentiels/ddr5.elmt b/elements/protections/disjoncteurs_differentiels/ddr5.elmt index f68de3ce8..55bf77d7e 100644 --- a/elements/protections/disjoncteurs_differentiels/ddr5.elmt +++ b/elements/protections/disjoncteurs_differentiels/ddr5.elmt @@ -2,6 +2,7 @@ Differential circuit-breaker Disjoncteur différentiel + Dispositivo diferenciale residual diff --git a/elements/protections/disjoncteurs_differentiels/ddr6.elmt b/elements/protections/disjoncteurs_differentiels/ddr6.elmt index 84083ab19..9d45dbba8 100644 --- a/elements/protections/disjoncteurs_differentiels/ddr6.elmt +++ b/elements/protections/disjoncteurs_differentiels/ddr6.elmt @@ -2,6 +2,7 @@ Differential circuit-breaker Disjoncteur différentiel + Dispositivo diferenciale residual diff --git a/elements/protections/disjoncteurs_differentiels/qet_directory b/elements/protections/disjoncteurs_differentiels/qet_directory index 1031c58c3..8b85a07d8 100644 --- a/elements/protections/disjoncteurs_differentiels/qet_directory +++ b/elements/protections/disjoncteurs_differentiels/qet_directory @@ -2,5 +2,6 @@ Differential circuit-breakers Disjoncteurs différentiels + Dispositivos diferenciales residuales diff --git a/elements/protections/interrupteurs_differentiels/int_diff1.elmt b/elements/protections/interrupteurs_differentiels/int_diff1.elmt new file mode 100644 index 000000000..ac598afab --- /dev/null +++ b/elements/protections/interrupteurs_differentiels/int_diff1.elmt @@ -0,0 +1,20 @@ + + + Differential switch + Interrupteur différentiel + Interruptor diferencial + + + + + + + + + + + + + + + diff --git a/elements/protections/interrupteurs_differentiels/int_diff2.elmt b/elements/protections/interrupteurs_differentiels/int_diff2.elmt new file mode 100644 index 000000000..c00679c6b --- /dev/null +++ b/elements/protections/interrupteurs_differentiels/int_diff2.elmt @@ -0,0 +1,25 @@ + + + Differential switch + Interrupteur différentiel + Interruptor diferencial + + + + + + + + + + + + + + + + + + + + diff --git a/elements/protections/interrupteurs_differentiels/int_diff3.elmt b/elements/protections/interrupteurs_differentiels/int_diff3.elmt new file mode 100644 index 000000000..9dc840316 --- /dev/null +++ b/elements/protections/interrupteurs_differentiels/int_diff3.elmt @@ -0,0 +1,30 @@ + + + Differential switch + Interrupteur différentiel + Interruptor diferencial + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/elements/protections/interrupteurs_differentiels/int_diff4.elmt b/elements/protections/interrupteurs_differentiels/int_diff4.elmt new file mode 100644 index 000000000..ff88436e4 --- /dev/null +++ b/elements/protections/interrupteurs_differentiels/int_diff4.elmt @@ -0,0 +1,35 @@ + + + Differential switch + Interrupteur différentiel + Interruptor diferencial + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/elements/protections/interrupteurs_differentiels/qet_directory b/elements/protections/interrupteurs_differentiels/qet_directory new file mode 100644 index 000000000..694849586 --- /dev/null +++ b/elements/protections/interrupteurs_differentiels/qet_directory @@ -0,0 +1,7 @@ + + + Differential switches + Interrupteurs différentiels + Interruptores diferenciales + + diff --git a/elements/protections/interrupteurs_sectionneurs/qet_directory b/elements/protections/interrupteurs_sectionneurs/qet_directory new file mode 100644 index 000000000..069b5c489 --- /dev/null +++ b/elements/protections/interrupteurs_sectionneurs/qet_directory @@ -0,0 +1,6 @@ + + + Interrupteurs sectionneurs + Disconnecting switches + + diff --git a/elements/protections/interrupteurs_sectionneurs/sectionneur1.elmt b/elements/protections/interrupteurs_sectionneurs/sectionneur1.elmt new file mode 100644 index 000000000..dde181887 --- /dev/null +++ b/elements/protections/interrupteurs_sectionneurs/sectionneur1.elmt @@ -0,0 +1,15 @@ + + + Disconnecting switch + Sectionneur + + + + + + + + + + + diff --git a/elements/protections/interrupteurs_sectionneurs/sectionneur4.elmt b/elements/protections/interrupteurs_sectionneurs/sectionneur4.elmt new file mode 100644 index 000000000..4821387d3 --- /dev/null +++ b/elements/protections/interrupteurs_sectionneurs/sectionneur4.elmt @@ -0,0 +1,27 @@ + + + Disconnecting switch + Sectionneur + + + + + + + + + + + + + + + + + + + + + + + diff --git a/elements/protections/qet_directory b/elements/protections/qet_directory index 9793e61fe..63db70389 100644 --- a/elements/protections/qet_directory +++ b/elements/protections/qet_directory @@ -2,5 +2,6 @@ Protections Protections + Protecciones diff --git a/elements/recepteurs/domestiques/convecteur.elmt b/elements/recepteurs/domestiques/convecteur.elmt index 27296782b..2b90f725f 100644 --- a/elements/recepteurs/domestiques/convecteur.elmt +++ b/elements/recepteurs/domestiques/convecteur.elmt @@ -5,7 +5,7 @@ - + diff --git a/elements/recepteurs/domestiques/four.elmt b/elements/recepteurs/domestiques/four.elmt index bd1822062..c111b080c 100644 --- a/elements/recepteurs/domestiques/four.elmt +++ b/elements/recepteurs/domestiques/four.elmt @@ -5,7 +5,7 @@ - + diff --git a/elements/recepteurs/domestiques/lave_linge.elmt b/elements/recepteurs/domestiques/lave_linge.elmt index 97b64a706..b66bae4c6 100644 --- a/elements/recepteurs/domestiques/lave_linge.elmt +++ b/elements/recepteurs/domestiques/lave_linge.elmt @@ -5,7 +5,7 @@ - + diff --git a/elements/recepteurs/domestiques/lave_vaiselle.elmt b/elements/recepteurs/domestiques/lave_vaiselle.elmt index 94d2553a9..b1f0c17f0 100644 --- a/elements/recepteurs/domestiques/lave_vaiselle.elmt +++ b/elements/recepteurs/domestiques/lave_vaiselle.elmt @@ -5,7 +5,7 @@ - + diff --git a/elements/recepteurs/domestiques/plaque_cuisson.elmt b/elements/recepteurs/domestiques/plaque_cuisson.elmt index 3aa7913d1..1356cd5c9 100644 --- a/elements/recepteurs/domestiques/plaque_cuisson.elmt +++ b/elements/recepteurs/domestiques/plaque_cuisson.elmt @@ -5,7 +5,7 @@ - + diff --git a/elements/recepteurs/machines/gene_tri.elmt b/elements/recepteurs/machines/gene_tri.elmt index 6bac25e7c..d6b9bcde8 100644 --- a/elements/recepteurs/machines/gene_tri.elmt +++ b/elements/recepteurs/machines/gene_tri.elmt @@ -2,6 +2,7 @@ Three-phase generator Génératrice triphasée + Generador trifasico diff --git a/elements/recepteurs/machines/generatrice.elmt b/elements/recepteurs/machines/generatrice.elmt index 38bd91755..e33d91339 100644 --- a/elements/recepteurs/machines/generatrice.elmt +++ b/elements/recepteurs/machines/generatrice.elmt @@ -2,6 +2,7 @@ Generator Génératrice + Generador diff --git a/elements/recepteurs/machines/generatrice_dc.elmt b/elements/recepteurs/machines/generatrice_dc.elmt index 4f3797148..a7653e417 100644 --- a/elements/recepteurs/machines/generatrice_dc.elmt +++ b/elements/recepteurs/machines/generatrice_dc.elmt @@ -2,6 +2,7 @@ DC generator Générateur à courant continu + Generador de corriente continuo diff --git a/elements/recepteurs/machines/moteur.elmt b/elements/recepteurs/machines/moteur.elmt index 9e0d628a3..cf2cf9096 100644 --- a/elements/recepteurs/machines/moteur.elmt +++ b/elements/recepteurs/machines/moteur.elmt @@ -2,6 +2,7 @@ Engine Moteur + Motor diff --git a/elements/recepteurs/machines/moteur_dc.elmt b/elements/recepteurs/machines/moteur_dc.elmt index 6cd0f8bda..7b2c7be86 100644 --- a/elements/recepteurs/machines/moteur_dc.elmt +++ b/elements/recepteurs/machines/moteur_dc.elmt @@ -2,6 +2,7 @@ DC motor Moteur à courant continu + Motor de corriente continuo diff --git a/elements/recepteurs/machines/moteur_mono.elmt b/elements/recepteurs/machines/moteur_mono.elmt index 7aad52958..4b91d186b 100644 --- a/elements/recepteurs/machines/moteur_mono.elmt +++ b/elements/recepteurs/machines/moteur_mono.elmt @@ -2,6 +2,7 @@ One-phase engine Moteur monophasé + Motor monofásico diff --git a/elements/recepteurs/machines/moteur_tri.elmt b/elements/recepteurs/machines/moteur_tri.elmt index 8f0141919..ebdbcb96b 100644 --- a/elements/recepteurs/machines/moteur_tri.elmt +++ b/elements/recepteurs/machines/moteur_tri.elmt @@ -2,6 +2,7 @@ Three-phase engine Moteur triphasé + Motor trifásico diff --git a/elements/recepteurs/machines/qet_directory b/elements/recepteurs/machines/qet_directory index d95ea8d0b..7b8477d7d 100644 --- a/elements/recepteurs/machines/qet_directory +++ b/elements/recepteurs/machines/qet_directory @@ -2,5 +2,6 @@ Machines Machines + Máquinas diff --git a/elements/recepteurs/qet_directory b/elements/recepteurs/qet_directory index d449fa96c..5cfe6f203 100644 --- a/elements/recepteurs/qet_directory +++ b/elements/recepteurs/qet_directory @@ -2,5 +2,6 @@ Receivers Récepteurs + Receptores diff --git a/elements/recepteurs/transformateurs/qet_directory b/elements/recepteurs/transformateurs/qet_directory index de3fd3288..bc370e7eb 100644 --- a/elements/recepteurs/transformateurs/qet_directory +++ b/elements/recepteurs/transformateurs/qet_directory @@ -2,5 +2,6 @@ Transducers Transformateurs + Transformadores diff --git a/elements/recepteurs/transformateurs/transfo_mono.elmt b/elements/recepteurs/transformateurs/transfo_mono.elmt index 1a8841065..1100aa559 100644 --- a/elements/recepteurs/transformateurs/transfo_mono.elmt +++ b/elements/recepteurs/transformateurs/transfo_mono.elmt @@ -2,6 +2,7 @@ Single phase transducer Transformateur monophasé + Transformador monofásica diff --git a/elements/recepteurs/transformateurs/transfo_tri.elmt b/elements/recepteurs/transformateurs/transfo_tri.elmt index afc0ce5c2..205d6c8ba 100644 --- a/elements/recepteurs/transformateurs/transfo_tri.elmt +++ b/elements/recepteurs/transformateurs/transfo_tri.elmt @@ -2,6 +2,7 @@ Three-phase transducer Transformateur triphasé + Transformador trifásico diff --git a/elements/semiconducteurs/del.elmt b/elements/semiconducteurs/del.elmt index e5b0df649..c39b61ee5 100644 --- a/elements/semiconducteurs/del.elmt +++ b/elements/semiconducteurs/del.elmt @@ -2,6 +2,7 @@ LED DEL + LED diff --git a/elements/semiconducteurs/diode.elmt b/elements/semiconducteurs/diode.elmt index f98360448..b3a31d684 100644 --- a/elements/semiconducteurs/diode.elmt +++ b/elements/semiconducteurs/diode.elmt @@ -2,6 +2,7 @@ Diode Diode + Diodo diff --git a/elements/semiconducteurs/qet_directory b/elements/semiconducteurs/qet_directory index e8eafdc9c..470ec3ca0 100644 --- a/elements/semiconducteurs/qet_directory +++ b/elements/semiconducteurs/qet_directory @@ -2,5 +2,6 @@ Semiconductors Semi-conducteurs + Semiconductores diff --git a/elements/semiconducteurs/thyristor.elmt b/elements/semiconducteurs/thyristor.elmt index d41f70d80..7b2a9d946 100644 --- a/elements/semiconducteurs/thyristor.elmt +++ b/elements/semiconducteurs/thyristor.elmt @@ -2,6 +2,7 @@ Thyristor Thyristor + Tiristor diff --git a/elements/sources/multifilaire/qet_directory b/elements/sources/multifilaire/qet_directory index eb5419190..9689f1275 100644 --- a/elements/sources/multifilaire/qet_directory +++ b/elements/sources/multifilaire/qet_directory @@ -2,5 +2,6 @@ Multiline Multifilaire + Multihilo diff --git a/elements/sources/multifilaire/src_1pn.elmt b/elements/sources/multifilaire/src_1pn.elmt index 0c9556277..f75c92caf 100644 --- a/elements/sources/multifilaire/src_1pn.elmt +++ b/elements/sources/multifilaire/src_1pn.elmt @@ -2,6 +2,7 @@ Single-pole source + neutral Source unipolaire + neutre + Fuente unipolo + neutro diff --git a/elements/sources/multifilaire/src_3p.elmt b/elements/sources/multifilaire/src_3p.elmt index 69bf932f8..58fb8bdfb 100644 --- a/elements/sources/multifilaire/src_3p.elmt +++ b/elements/sources/multifilaire/src_3p.elmt @@ -2,6 +2,7 @@ Three-pole source Source tripolaire + Fuente tripolar diff --git a/elements/sources/qet_directory b/elements/sources/qet_directory index 8ca03a963..ba057d31a 100644 --- a/elements/sources/qet_directory +++ b/elements/sources/qet_directory @@ -2,5 +2,6 @@ Sources Sources + Fuentes diff --git a/elements/sources/tevenin_norton/courant_n.elmt b/elements/sources/tevenin_norton/courant_n.elmt index 36458366f..5fa9ce8cf 100644 --- a/elements/sources/tevenin_norton/courant_n.elmt +++ b/elements/sources/tevenin_norton/courant_n.elmt @@ -2,6 +2,7 @@ Current source (new) Source de courant (nouveau) + Fuente de corriente (nuevo) diff --git a/elements/sources/tevenin_norton/courant_o.elmt b/elements/sources/tevenin_norton/courant_o.elmt index fbefe6a4c..b62bca300 100644 --- a/elements/sources/tevenin_norton/courant_o.elmt +++ b/elements/sources/tevenin_norton/courant_o.elmt @@ -2,6 +2,7 @@ Current source (old) Source de courant (vieux) + Fuente de corriente (antiguo) diff --git a/elements/sources/tevenin_norton/qet_directory b/elements/sources/tevenin_norton/qet_directory index eb848599b..2b5f1f5ac 100644 --- a/elements/sources/tevenin_norton/qet_directory +++ b/elements/sources/tevenin_norton/qet_directory @@ -2,5 +2,6 @@ Tevenin / Norton Tevenin / Norton + Tevenin / Norton diff --git a/elements/sources/tevenin_norton/tention_n.elmt b/elements/sources/tevenin_norton/tention_n.elmt index d5c534158..e073c1385 100644 --- a/elements/sources/tevenin_norton/tention_n.elmt +++ b/elements/sources/tevenin_norton/tention_n.elmt @@ -2,6 +2,7 @@ Voltage source (new) Source de tension (nouveau) + Fuente de tensión (nuevo) diff --git a/elements/sources/tevenin_norton/tention_o.elmt b/elements/sources/tevenin_norton/tention_o.elmt index 030270dd9..8b75eb118 100644 --- a/elements/sources/tevenin_norton/tention_o.elmt +++ b/elements/sources/tevenin_norton/tention_o.elmt @@ -2,6 +2,7 @@ Voltage source (old) Source de tension (vieux) + Fuente de tensión (antiguo) diff --git a/ico/1leftarrow.png b/ico/1leftarrow.png new file mode 100644 index 0000000000000000000000000000000000000000..a6c46680eb2b8e9071fca6319c227638eb987f68 GIT binary patch literal 832 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 NgQu&X%Q~loCID{c*J=O& literal 0 HcmV?d00001 diff --git a/ico/conf_new_diagram_128.png b/ico/conf_new_diagram_128.png new file mode 100644 index 0000000000000000000000000000000000000000..0b182267cffe163b00e217edabd1f5f8eeef8136 GIT binary patch literal 1060 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV1DiC;uumf=j~m?Y!OF^V;{@k znW$wb@}&YF>_|!1A|~eySEf{_5?QHTUPr$?@MU+SwL-NoT+P zWz+R)p|-!0Q9?7+KEFPb{mNrMw}!1_+SmURKN>!=f4#X)lu8SvH!-q5c* z^g3sm`TrHqk9}_2IQ`xpHN|zSOaAWq{odYP^|+-8!&3d7s=OCzb+QbL7r(G_u$lR>qNkWmt0BDM$j6GyT6^za;WO4~$^jO5? za?CQMfkTVIGo;~!MLU;(BdbCumw@E+07j)13@U+)N{?l<99TpfRJ0sY<~XxZ<%9RfB4Lq zy1L5Pwc<`2-o4~5%X{{%v9#*Agyy@ryJybaF1x+oRS!<7d++}P1KJMqM$;;optJ`+_WB&esJZE~ZzrG*; z-|*M-El{Z4A77KVshZpHIz!ifgM5ojvot?)|SjPv*S;cdxko z``W8Zv;M99EPQo``TqYF#^G{1cK`mpHG5yZ=brEN73|#heBXA0b-~s5tXtkD zd3A1kJNG*>P)r3G~_eJJ=~VPNe<2wT=d{ll(wYqw=JO>Ay%b8~O*J%4P76E`rq+M)n>9+ynvxS9)mficu$H&*| zaWOSD7g$@{5WAJn=S3os5Q?G*MN!1;?5q$%h?$w0YHlY~RV~#8U%NINc=+&X4LF@n z3jl(1E*enLRS#^MCKzKiImSg@6ML1(WH2!?;r$PAsRqor0B;biX&R=dr=|Y^(<+p5 z0YQLpINT5%0P2EQR&Kw6|85KB@;_aOAWc^oV%p+=P!Qf^?GU+p0b0; zOM&X#;lL*I?cHBj>+93&V2mM|OacJN1)%Ux>{V-P1jmlO z3IHgVEtsZ}kxaArXE50OJOF5#fqVCUMt65NVzJmh$5hec;vx(~!@$5G0Kha0D3{CY zQYN$UjmJY?_W7DYh=P%kvp9YFO*A!m8v@5-9WV?7vh0TtfU0Jxs;YC65d5qA_gB8? z>w8U7lps!>8boL3k%pj-!w?FEvAUXuEC<0D!}9W-)aK^qV*&v2c)Yc@_xLA~NVMnD zr4I(<@fY^Jvy?JW%0LLg&6{)Eckli*zp;^CnxFsvYOz?!FAkZ|ds*mV?ZW&{sl!8(Rlv0$-W%&IE-Ue`Wr{-_M WWg3*gggCGO0000jv*GkZ>JdYH5l+PzYqBT zKh?NV>x$8Z4OgR$<|Li$^IQ?c95B7pGw1FH$t6q$YvQhCA2!QbWW6Q)gIB_NwiSoN ndcN&9d2sdFnO`OGKb!nS^2GFYipx&}4QKFl^>bP0l+XkK4kI)g literal 0 HcmV?d00001 diff --git a/ico/diagram_add.png b/ico/diagram_add.png new file mode 100644 index 0000000000000000000000000000000000000000..9be14a2aaf51de6c02f965c2d9b21676bf98691d GIT binary patch literal 313 zcmV-90mlA`P)H~* zPxiCrPfx%7Y?8Qy)&OR}%zD4lt`!yV2~=doD-

zZGC>o<38||%Nz7~$oJ^o@!i(rODAvUC-9tTH%NQ^2>ws}R(KviEO44T;DLY>00000 LNkvXXu0mjfS`dhG literal 0 HcmV?d00001 diff --git a/ico/diagram_del.png b/ico/diagram_del.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6d23dc681594773ca63b15f1c017a24ac97dfa GIT binary patch literal 340 zcmV-a0jvIrP)_dVcEE4t;>1%49uHPzk}xxk8$#_umhfSkm*N%Amv=a+7?GVVnx!hv zb?ZCb`l&RI+%u1j#2%|B0Tw$(vs7if=puS~48LR3%>Inh`(68jc8&L^Cbb*apZ)7s zIO&)VU3&c>8VUKc;E27EYUSeoo7Qe!qMIR{w+{9GQG3_bI9184se68;#&Kl8uP>l3 ms0>1ytRD84HOw!AB=HIBV6C=loAOKm0000UvMb8}7ENf9cx>Q(*Q>KQfi7`_y#Y-DBlqPulZwJI)|`nuywCh=VofRiFZ7;S>yE#U+)SZ|i-Gnqc)I$ztaD0e0syF-G4B8X literal 0 HcmV?d00001 diff --git a/ico/endline-diamond.png b/ico/endline-diamond.png new file mode 100644 index 0000000000000000000000000000000000000000..3b26723359cf4571182162434ee52cd8a8db63e0 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`;hrvzAr`%7CpmH*P~c%H|MlPA z-@kQHn@CGi-)du(z$CvBdGoVk$4vKtgl7_Yt?mc01boFyt=akR{0Cd+fDgXcg literal 0 HcmV?d00001 diff --git a/ico/endline-none.png b/ico/endline-none.png new file mode 100644 index 0000000000000000000000000000000000000000..0a02935b2821f189bffd931f25140c1cb2f205c2 GIT binary patch literal 101 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`I-V|$Ar`&KfByfsXI5?KRJyyE yE#NqV8yg$jqQ@Wq|Nq}3!Pc{^IgnXGLW1FU50|J_^EN)9J_b)$KbLh*2~7Yq8y)-r literal 0 HcmV?d00001 diff --git a/ico/endline-simple.png b/ico/endline-simple.png new file mode 100644 index 0000000000000000000000000000000000000000..599dd738fa4933b0c6c84fe4181ffd400c4edaee GIT binary patch literal 132 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`-kvUwAr`%7CmrNGU?6aK&Aa`Z zjU2pd1T;RzGu@XoRj=%O@tf7b_CnZ#ODbEPCQnHD<34qI!Y1Ye`WBKQHx7ngc#sm2 gIQ{hWAKeLzeX2^rA!oevfyOd;y85}Sb4q9e0F@&y$p8QV literal 0 HcmV?d00001 diff --git a/ico/endline-triangle.png b/ico/endline-triangle.png new file mode 100644 index 0000000000000000000000000000000000000000..bf38ac681ab5dea0e7e07fe866178670265a649f GIT binary patch literal 139 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`L7py-Ar`%FCmHfJIB>9}_x%6= z>DG$YkA2=5I!jvK=tLTD&b<}&>OJ!nqXk9HTtQ5ed=#02+h3p2@~k=cefu+C`?>B( m5vLP0;+W1HVfeTH4!aCjmV#7Ir|AKp=?tE(elF{r5}E)K@-e3X literal 0 HcmV?d00001 diff --git a/ico/erase.png b/ico/erase.png old mode 100755 new mode 100644 diff --git a/ico/folder.png b/ico/folder.png old mode 100755 new mode 100644 diff --git a/ico/folder_home.png b/ico/folder_home.png old mode 100755 new mode 100644 diff --git a/ico/forbidden.png b/ico/forbidden.png old mode 100755 new mode 100644 diff --git a/ico/item_cancel.png b/ico/item_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..45c03d5d4da2fe9c511eee178e9b84e855c9e2f0 GIT binary patch literal 911 zcmV;A191F_P)X5d88b1Vi~h!o&Z61*!pIpcnqH1nLC{AchkVd%*&KeSHy{ zpFjWq*Vq`0L2Q`#AD|kLT9BUQK)nC~#IPC|bPt)C|NmoW|Nl=-?f<{KcmMx;`}Y4o zM@O(CFb1(fVjww?8lc+CKs^mWy#N8kxDb+rx7hIVs>px>5*Wt}5)#1FaSRf$An@}i zBprZa2pGUOfc)j3Ki>g*#RnjOn1g^x1<2U@==X0=AwE7qepXgUA`%b)r++X8MJY%O zlmUQ&y!`$9d!K>%0hrit00a;?GXcZ@+L!`5MwaJBtM@oe`{G-n1L>1#4rFLfG`YT1giP__YZ@fo(hAR znKpx_rV>z$QPI-UfPt5nhXLIs00G2;%>ZVgGd_L##_;|7F9tq7ZU#$BJv&iRfmy(C z-~=k+1{$*c-@kvefnH+(2p|jtzJ6t3c>n$jFdV)x7#S%6ZDj)qD+9%qKY#uT45Pn5 zH9rD@VZ#qJum>Q3Pz?C-gMs1Ow;$kG0J(yl9U;xl%>axcPKMX7KQMg${23TF9Nj;E zeij1=Ab1#ny!PwYZw3|?CZHq>$lna;0nft12n=9W21!X#1{D=K1}-j+LVy5*8}REF z$p6gXFu(;svB1vG${;Q-%pff-0yL170U&@_Kmmbl0}~SqFcv_HF(L@)yN<0EYv}D}Vklf^C8% zb4IW?kpVye!3_Y}qO2^-ps%k6POks|{|0MhWn}<|#lL^7K>8mL|AD4hCa^d_0I?t^ zVPFblIC}IvG+q4vdGqE25c3O^4|WxV{THnM%a`we00ImEj`sC{+hY1`00000NkvXX Hu0mjfs4zfj literal 0 HcmV?d00001 diff --git a/ico/item_move.png b/ico/item_move.png new file mode 100644 index 0000000000000000000000000000000000000000..b762fc4f4569677e7794de047e9267bf5f76e02e GIT binary patch literal 592 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 literal 0 HcmV?d00001 diff --git a/ico/printtype_pdf.png b/ico/printtype_pdf.png new file mode 100644 index 0000000000000000000000000000000000000000..fca2c672e2a2313bc743223a28d035bc414d08a3 GIT binary patch literal 1943 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 Z0RYli#1~V?u=D@`002ovPDHLkV1fa4?k4~M literal 0 HcmV?d00001 diff --git a/ico/raise.png b/ico/raise.png old mode 100755 new mode 100644 diff --git a/ico/rectangle.png b/ico/rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..2889ce03602cad1b730b9afb616e38a4fbc1fc61 GIT binary patch literal 292 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fEa{HEjtmSN`?>!lvI6;RN#5=* z4F5rJ!QSPQfg+p*9+AZi4BSE>%y{W;-5;PJdx@v7EBhsO86Gb6T%&d~pwL237srr_ zId88lFVdQ&MBb@0KI-|`v3p{ literal 0 HcmV?d00001 diff --git a/ico/remove_col.png b/ico/remove_col.png old mode 100755 new mode 100644 diff --git a/ico/remove_row.png b/ico/remove_row.png old mode 100755 new mode 100644 diff --git a/ico/save_all.png b/ico/save_all.png new file mode 100644 index 0000000000000000000000000000000000000000..af29975e1a15fea28f34abe014bd076e0dd42688 GIT binary patch literal 999 zcmVWFU8GbZ8({Xk{QrNlj4iWF>9@00T=&L_t(|+O3q&Yh6VY z$3OSId1+n~lQx2u3Prk5T!`*miXf;6Qt&?z7lJO_cBhDB-Mxq#S3(gKD)?g|1s9P* zYZos1C{ZJA9!=iQJ9o}GGvngk_mbD7?ZOTWGjr$iopU}j-&1f*yYj=<2hO!ORU;kO zVl*zPf)Xv#ac#^ZB8pi=)li*{#_hlR^mhl}6~_tx_|vuh=UzB>3JT7h-Qbf;-!dF| z)}H9{>Bkp%So{8$>-=&37Vfys5R|eK3hHxzyVm3M+DV*i@u)T!l_WS`Seo>Tq88p* zU*Y-lXL#n!=|>8S7{lR&;sHF@bIwJl-6rSEbm~bdV;0Fd&v|a|+~fSSPxJnJZ?U@C zC8f-zFR$?D%^uFV_U8ZP`Pt`R^WjGq*xw(sdv}l0SGdQ)^XkP#Z^m=`kmmQ%Jf&`p4C{jwC>@Hz3HMPJfav~YDa-^Lj%bhl@?h>6= zvA{E>#L`k5NXTNu#`1{{Z@=~uDJ8rYybpw+s2Vjco2NVLGoGr(cKu<*Eq<71jFFTg zDP>dy6|D^tf?}!^MP_BiAp)x4T*f(Qw=-tYYGqV2MUfYH#uO1LQ8lLH5uu#c5l^Ni z(TvHYVlXJ@QPmM16!kjIx~L!KSydrlzWnPjrOZyRPw5p``M`AQ$tj^~q*U{|znjG- zWjn&tEDpD}zF4RM&Q}5N1Eh?Zk#i!Yy4UmAtm`qxbcAP0@rdP}pH@rwrlZ1p{lUeZzwT~UA#m$vj~Mej;uteIr$gTk_73w*iHr9O{|8JN Vt|Cc)g+%}W002ovPDHLkV1oTN)Rh1L literal 0 HcmV?d00001 diff --git a/ico/send_backward.png b/ico/send_backward.png old mode 100755 new mode 100644 diff --git a/ico/settings.png b/ico/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..7f537fd6a68f762f450bfe5275dfb49758debf15 GIT binary patch literal 14109 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 QKJNyRmsXalk@yh!fAcV#O#lD@ literal 0 HcmV?d00001 diff --git a/ico/single_page.png b/ico/single_page.png new file mode 100644 index 0000000000000000000000000000000000000000..f1e7922643b6508148380fedc353f52368076a13 GIT binary patch literal 137 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4f0iG_7Ar`%FuNrbSI0!gjblJ1L z>QjE=)?`Ix?T9Cb&4O)iXt8~9&9=SaWOYj*M~}Tt{i5HkmFGVFTF=kQCu33Yp#H$R k+2y{$_7@C%%`3vxi#(&l?qu_10?lUdboFyt=akR{0RJ{FL;wH) literal 0 HcmV?d00001 diff --git a/ico/splash.png b/ico/splash.png index 09f1ed7e8d84879991eee474e53bff529e50bf0c..18fe98d308010f0be29ea30664dcb9f7efe20a75 100644 GIT binary patch literal 29015 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 J%AXnf{~xG?50L-> literal 26062 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 diff --git a/ico/start.png b/ico/start.png new file mode 100644 index 0000000000000000000000000000000000000000..7fa9bf7cb654023f1db4aee7673b8c7a65bd478d GIT binary patch literal 1324 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 literal 0 HcmV?d00001 diff --git a/ico/view_fit_window.png b/ico/view_fit_window.png new file mode 100644 index 0000000000000000000000000000000000000000..f6c0e150b9e69b3498de1e802e32260cc4a3deb7 GIT binary patch literal 816 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 M_JyFYvRLf@118w}9smFU delta 8804 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 diff --git a/lang/qet_en.ts b/lang/qet_en.ts index e94a1d09f..ebd8e9c55 100644 --- a/lang/qet_en.ts +++ b/lang/qet_en.ts @@ -1,51 +1,56 @@ + AboutQET - - &Accord de licence - &License Agreement - - - - À &propos - &About - - - - À propos de QElectrotech - About QElectroTech - - - - A&uteurs - A&uthors - - - + Ce programme est sous licence GNU/GPL. This program is under the GNU/GPL license. - + Idée originale Original concept - + Programmation Programming - + QElectroTech, une application de réalisation de schémas électriques. QElectroTech, an application to design electric diagrams. - - © 2006-2008 Les développeurs de QElectroTech - © 2006-2008 QElectroTech developers + + © 2006-2009 Les développeurs de QElectroTech + © 2006-2009 QElectroTech developers + + + + À propos de QElectrotech + window title + About QElectroTech + + + + À &propos + tab title + &About + + + + A&uteurs + tab title + A&uthors + + + + &Accord de licence + tab title + &License Agreement @@ -113,30 +118,35 @@ BorderInset - - - Auteur : - Author: - - Date : - Date: - - - - Fichier : - File: - - - - Folio : - Folio: + Auteur : %1 + inset content + Author: %1 - Titre du document : - Document title: + Date : %1 + inset content + Date: %1 + + + + Titre du document : %1 + inset content + Document title: %1 + + + + Fichier : %1 + inset content + File: %1 + + + + Folio : %1 + inset content + Folio: %1 @@ -151,16 +161,6 @@ Colonnes : Columns: - - - × - × - - - - px - px - Afficher les en-têtes @@ -171,6 +171,24 @@ Lignes : Rows: + + + × + multiplication symbol + × + + + + px + unit for cols width + px + + + + px + unit for rows height + px + CircleEditor @@ -220,7 +238,7 @@ Unifilaire - SIngleline + Singleline @@ -248,262 +266,384 @@ Configurer QElectroTech + window title Configure QElectroTech DiagramPrintDialog - + Options d'impression + window title Print options - - - Utiliser toute la feuille - Use full page - - Adapter le schéma à la page - Fit diagram to page + Quel type d'impression désirez-vous effectuer ? + What kind of printing do you wish? - - à - to + + Choix du type d'impression + Printing type choice - - Nombre total de pages : - Total pages count: + + Fichier manquant + message box title + File missing - - Si cette option est cochée, les marges de la feuille seront ignorées et toute sa surface sera utilisée pour l'impression. Cela peut ne pas être supporté par votre imprimante. - If this option is checked, the paper margins are ignored and its whole surface is used for the printing. This may not be supported by your printer. + + Vous devez indiquer le chemin du fichier PDF/PS à créer. + message box content + You must enter the path of the PDF/PS file to create. - - Si cette option est cochée, le schéma sera agrandi ou rétréci de façon à remplir toute la surface imprimable d'une et une seule page. - If this option is checked, the diagram will be shrinked or expanded to fit the printable surface of a single page. + + Fichiers PDF (*.pdf) + file filter + PDF Files (*.pdf) - - Pages à imprimer : plage de - Pages to print: from + + Fichiers PostScript (*.ps) + file filter + PostScript Files (*.ps) DiagramView - - ? - ? + + Schéma sans titre + Untitled diagram - - Enregistrer le schéma en cours ? - Save the current diagram ? - - - - Enregistrer sous - Save as - - - - Erreur - Error - - - - Impossible d'ecrire dans ce fichier - Can't write to the file - - - - Schéma QElectroTech (*.qet) - QElectroTech Diagram (*.qet) - - - - Voulez-vous enregistrer le schéma - Do you wish to save the diagram - - - - nouveau schéma - new diagram - - - - Éditer les propriétés d'un conducteur - Edit conductor properties - - - - Propriétés du schéma - Diagram properties - - - - Éditer les propriétés par défaut des conducteurs - Edit conductors default properties - - - + Coller ici + context menu action Paste Here - - Avertissement - Warning + + Schéma %1 + %1 is a diagram title + Diagram %1 - - Ce document semble avoir été enregistré avec une version ultérieure de QElectroTech. Il est possible que l'ouverture de tout ou partie de ce document échoue. - This document seems to have been saved by a more recent version of QElectroTech. The opening of the document may fail totally or partially. + + Propriétés du schéma + window title + Diagram properties - - schema - diagram + + Éditer les propriétés d'un conducteur + window title + Edit conductor properties + + + + Éditer les propriétés par défaut des conducteurs + window title + Edit conductors default properties + + + + DiagramsChooser + + + Schéma sans titre + Untitled diagram + + + + ElementDefinition + + + L'élément cible n'a pu être créé. + The target element could not be created. + + + + La suppression de cet élément a échoué. + The deletion of this element failed. ElementDeleter - + Supprimer l'élément ? - Delete element ? + message box title + Delete element? - + Êtes-vous sûr de vouloir supprimer cet élément ? - Do you really wish to delete this element ? + message box content + Do you really wish to delete this element? - + Suppression de l'élément + message box title Deleting element - - La suppression de l'élément a échoué. -Vérifiez vos droits sur le fichier - Deleting element failed. -Check your rights on the file + + La suppression de l'élément a échoué. + message box content + Deleting element failed. + + + + ElementDialog + + + Nom : + Name: - - . - . + + Ouvrir un élément + dialog title + Open an element + + + + Choisissez l'élément que vous souhaitez ouvrir. + dialog content + Choose the element you iwsh to open. + + + + Enregistrer un élément + dialog title + Save an element + + + + Choisissez l'élément dans lequel vous souhaitez enregistrer votre définition. + dialog content + Choose the element you wish to save your definition into. + + + + Ouvrir une catégorie + dialog title + OPen a category + + + + Choisissez une catégorie. + dialog content + Choose a category. + + + + Enregistrer une catégorie + dialog title + Save a category + + + + Pas de sélection + message box title + No selection + + + + Vous devez sélectionner un élément. + message box content + You must select an element. + + + + Sélection inexistante + message box title + Non-existent selection + + + + La sélection n'existe pas. + message box content + The selection does not exist. + + + + Sélection incorrecte + message box title + Wrong selection + + + + La sélection n'est pas un élément. + message box content + The selection is not an element. + + + + Vous devez sélectionner une catégorie ou un élément. + message box content + You must select a category or an element. + + + + Nom manquant + message box title + Name required + + + + Vous devez entrer un nom pour l'élément + message box content + You must provide a name for the element + + + + Nom invalide + message box title + Invalid name + + + + Vous ne pouvez pas utiliser les caractères suivants dans le nom de l'élément : %1 + You can not use one of the following characters in the element name: %1 + + + + Écraser l'élément ? + message box title + Overwrite the element? + + + + L'élément existe déjà. Voulez-vous l'écraser ? + message box content + The element already exists. Do you want to overwrite it? ElementScene - + ligne line - + ellipse ellipse - + arc arc - + cercle circle - + borne terminal - + texte text - + champ de texte textfield - + polygone polygon - - Ce document XML n'est pas une definition d'élément. - This XML document is not an element definition. - - - - Les dimensions ou le point de saisie ne sont pas valides. - The size or the hotspot are not valid. - - - - Les orientations ne sont pas valides. - Orientations are not valids. - - - - Éditer la taille et le point de saisie - Edit size and hotspot - - - - Éditer les orientations - Edit orientations - - - + L'orientation par défaut est l'orientation dans laquelle s'effectue la création de l'élément. Default orientation is the orientation which the drawing of the element takes place with. - - Éditer les noms - Edit names - - - + Vous pouvez spécifier le nom de l'élément dans plusieurs langues. You may enter the element name in several languages. - + Autoriser les connexions internes Allow internal connections + + + Ce document XML n'est pas une définition d'élément. + error message + This XML document is not an element definition. + + + + Les dimensions ou le point de saisie ne sont pas valides. + error message + The size or the hotspot are not valid. + + + + Les orientations ne sont pas valides. + error message + Orientations are not valid. + + + + Éditer la taille et le point de saisie + window title + Edit size and hotspot + + + + Éditer les orientations + window title + Edit orientations + + + + Éditer les noms + window title + Edit names + + + + rectangle + rectangle + ElementsCategoriesList - + Collection utilisateur User Collection - + Collection QET QET Collection + + + Collection projet + Project Collection + ElementsCategoriesWidget @@ -529,167 +669,339 @@ Check your rights on the file - ElementsCategoryDeleter + ElementsCategory - - Supprimer la catégorie ? - Delete category ? + + La copie d'une catégorie vers elle-même ou vers l'une de ses sous-catégories n'est pas gérée. + Copying a category to itself or to one of its subcategories is not handled. - + + Il n'est pas possible de déplacer une collection. + It is not possible to move a collection. + + + + Le déplacement d'une catégorie dans une de ses sous-catégories n'est pas possible. + Moving a category to one of its subcategories is not possible. + + + + La suppression de cette catégorie a échoué. + The deletion of this category failed. + + + + Impossible de supprimer l'élément + Unable to delete the element + + + + Impossible de supprimer la catégorie + Unable to delete the category + + + + ElementsCategoryDeleter + + + Vider la collection ? + message box title + Empty the collection? + + + + Êtes-vous sûr de vouloir vider cette collection ? + message box content + Do you really want to empty this collection? + + + + Supprimer la catégorie ? + message box title + Delete category? + + + + Êtes-vous sûr de vouloir supprimer la catégorie ? +Tous les éléments et les catégories contenus dans cette catégorie seront supprimés. + message box content + Do you really want to delete the category? + + + + + Êtes-vous vraiment sûr de vouloir supprimer cette catégorie ? +Les changements seront définitifs. + message box content + Do you really really want to delete this category? +Changes will be definitive. + + + Suppression de la catégorie + message box title Category deletion - - La suppression de la catégorie a échoué. -Vérifiez vos droits sur le dossier - Category deletion failed. -Please check rights of the directory - - - - . - . - - - - Êtes-vous sûr de vouloir supprimer la catégorie <b> - Do you really wish to delete this category <b> - - - - </b> ? -Tous les éléments et les catégories contenus dans cette catégorie seront supprimés - </b>? -Every elements and categories nested in this category will be deleted - - - - Êtes-vous vraiment sûr de vouloir supprimer cette catégorie (<b> - Are you really really sure you want to delete this category (<b> - - - - </b>) ? -Les changements seront définitifs. - </b>) ? -Changes will be permanent. + + La suppression de la catégorie a échoué. + message box content + Deleting the category failed. ElementsCategoryEditor - - Créer une nouvelle catégorie - Add a new category - - - - Éditer une catégorie - Edit category - - - - Nom de la nouvelle catégorie - Name of the new category - - - + Vous pouvez spécifier un nom par langue pour la catégorie. You can add a name per language for the category. + + + Nom interne : + Internal name: + + + + Catégorie inexistante + message box title + Non-existent category + + + + La catégorie demandée n'existe pas. Abandon. + message box content + The required category does not exist. Giving up. + + Éditer une catégorie + window title + Edit category + + + + Créer une nouvelle catégorie + window title + Add a new category + + + + Nom de la nouvelle catégorie + default name when creating a new category + Name of the new category + + + Édition en lecture seule + message box title Read only edition - + Vous n'avez pas les privilèges nécessaires pour modifier cette catégorie. Elle sera donc ouverte en lecture seule. + message box content You are not allowed to modify this category. Thus it will be edited read-only. + + + Nom interne manquant + message box title + Missing internal name + + + + Vous devez spécifier un nom interne. + message box content + You must provide an internal name. + + + + Nom interne déjà utilisé + message box title + Internal name already used + + + + Le nom interne que vous avez choisi est déjà utilisé par une catégorie existante. Veuillez en choisir un autre. + message box content + The internal name you chose is already used by another category. Please choose another one. + + + + Erreur + message box title + Error + + + + Impossible de créer la catégorie + message box content + Unable to create the category + + + + Impossible d'enregistrer la catégorie + message box content + Unable to save the category + + + + ElementsCollection + + + Il n'est pas possible de déplacer une collection. + It is not possible to move a collection. + ElementsPanel - + Ceci est un élément que vous pouvez insérer dans votre schéma par cliquer-déplacer This is an element you can drag'n drop onto your diagram - + Cliquer-déposez cet élément sur le schéma pour insérer un élément Drag'n drop this element to the diagram to insert a - + Collection QET QET Collection - + Collection utilisateur User Collection + + + Collection projet + Project collection + + + + Schéma sans titre + Untitled diagram + + + + %1 [non utilisé dans le projet] + %1 [unused in the project] + + + + Pas de fichier + tooltip for a file-less project in the element panel + No file + ElementsPanelWidget - + Nouvel élément New element - + Recharger les collections Reload collections - + Nouvelle catégorie New category - + Éditer la catégorie Edit category - + Supprimer la catégorie Delete category - + Éditer l'élément Edit element - + Supprimer l'élément Delete element - - Gestionnaire de catégories - Categories manager - - - + Vous pouvez utiliser ce gestionnaire pour ajouter, supprimer ou modifier les catégories. Use this manager to add, delete or modify categories. - + Filtrer : Filter: - + Effacer le filtre Erase filter + + + Vider la collection + Empty the collection + + + + Fermer ce projet + Close this project + + + + Ajouter un schéma + Add a diagram + + + + Supprimer ce schéma + Delete this diagram + + + + Propriétés du projet + Project properties + + + + Déplacer dans cette catégorie + Move into this category + + + + Copier dans cette catégorie + Copy into this category + + + + Annuler + Undo + + + + Gestionnaire de catégories + window title + Categories manager + + + + Propriétés du schéma + Diagram properties + EllipseEditor @@ -737,198 +1049,261 @@ Changes will be permanent. ExportDialog - + Aperçu Preview - + Bitmap (*.bmp) Bitmap (*.bmp) - - Conserver les proportions - Keep aspect ratio - - - + Dessiner la grille Draw the grid - + Dessiner le cadre Draw the border - + Dessiner le cartouche Draw the inset - - Dessiner les colonnes - Draw columns - - - + Dimensions Dimensions - - Exporter - Export - - - + Exporter le cadre Export the border - + Exporter les éléments Export only elements - - Exporter vers le fichier - Export to file - - - - Fichier non spécifié - Filename not given - - - + Format : Format: - - Hauteur : - Height: - - - - Il semblerait que vous n'ayez pas les permissions nécessaires pour écrire dans ce fichier.. - It seems you don't have the permissions needed to write this file.. - - - - Impossible d'écrire dans ce fichier - Can't Write to the file - - - + JPEG (*.jpg) JPEG (*.jpg) - - Largeur : - Width: - - - - Nom de fichier : - Filename: - - - + Options Options - + Parcourir Browse - + PNG (*.png) PNG (*.png) - - px - px - - - - Vous devez spécifier le chemin du fichier dans lequel sera enregistrée l'image. - You must give a filename to save the picture. - - - + Dessiner les bornes Draw terminals - + SVG (*.svg) SVG (*.svg) - - Images (*.png *.bmp *.jpg *.svg) - Pictures (*.png *.bmp *.jpg *.svg) + + Impossible d'écrire dans ce fichier + message box title + Can not write to this file - - Dessiner les lignes - Draw rows + + Exporter les schémas du projet + window title + Export the project diagrams + + + + Exporter + Export + + + + Choisissez les schémas que vous désirez exporter ainsi que leurs dimensions : + Choose the diagrams you wish to export and specify their size: + + + + Schéma + Diagram + + + + Nom de fichier + Filename + + + + Dossier cible : + Target directory: + + + + Exporter dans le dossier + dialog title + Export in the directory + + + + Noms des fichiers cibles + message box title + Target files names + + + + Vous devez entrer un nom de fichier distinct pour chaque schéma à exporter. + message box content + You must specify a distinct filename for each diagram to export. + + + + Dossier non spécifié + message box title + Directory missing + + + + Vous devez spécifier le chemin du dossier dans lequel seront enregistrés les fichiers images. + message box content + You must specify the path of the directory in which the images files will be saved. + + + + Il semblerait que vous n'ayez pas les permissions nécessaires pour écrire dans le fichier %1. + message box content + It appears you do not have the required permissions to write the file %1. + + + + ExportDialog::ExportDiagramLine + + + px + pxpx + + + + GeneralConfigurationPage + + + Projets + Projects + + + + Utiliser des fenêtres + Use windows + + + + Utiliser des onglets + Use tabs + + + + Ces paramètres s'appliqueront dès la prochaine ouverture d'un éditeur de schémas. + These settings will be applied at the next opening of a diagram editor. + + + + Gestion des éléments + Elements management + + + + Intégrer automatiquement les éléments dans les projets (recommandé) + Integrate automatically the elements into the projects (recommended) + + + + Général + configuration page title + General + + + + GhostElement + + + <u>Élément manquant :</u> %1 + <u>Missing element:</u> %1 HotspotEditor - + ×10 px ×10 px - + px px - + Déplacer l'élément avec le hotspot Translate element along with hotspot - + <span style="text-decoration:underline;">Dimensions</span> <span style="text-decoration:underline;">Size</span> - + Largeur : Width: - + Hauteur : Height: - + <span style="text-decoration:underline;">Hotspot</span> <span style="text-decoration:underline;">Hotspot</span> - + Abscisse : Abscissa: - + Ordonnée : Ordinate: + + + L'élément doit être assez grand pour contenir tout sa représentation graphique. + The element must be large enough to contain its whole graphical representation. + InsetPropertiesWidget @@ -953,53 +1328,271 @@ Changes will be permanent. Fixed date: - + Titre : Title: - + Auteur : Author: - + Date : Date: - + Fichier : File: - + Folio : Folio: + + + Les variables suivantes sont utilisables dans le champ Folio : + - %id : numéro du schéma courant dans le projet + - %total : nombre total de schémas dans le projet + The following variables can be used imn the Folio field: + - %id : the current diagram's number in the project + - %total : total number of diagrams in the project + + + + IntegrationMoveElementsHandler + + + L'élément a déjà été intégré dans le projet. Toutefois, la version que vous tentez de poser semble différente. Que souhaitez-vous faire ? + dialog content - %1 is an element's path name + The element has already been integrated in the project. However, the version you're trying to drop appears to be different. What do you wish to do ? + + + + Utiliser l'élément déjà intégré + dialog content + Use the already integrated element + + + + Intégrer l'élément déposé + dialog content + Integrate the dropped element + + + + Écraser l'élément déjà intégré + dialog content + Erase the already integrated element + + + + Faire cohabiter les deux éléments + dialog content + Make the two elements coexist + + + + Intégration d'un élément + Integration of an element + + + + InteractiveMoveElementsHandler + + + Renommer + Rename + + + + Écraser + Erase + + + + Écraser tout + Erase all + + + + Ignorer + Ignore + + + + Ignorer tout + Ignore all + + + + Annuler + Undo + + + + Copie de %1 vers %2 + dialog title + Copy from %1 to %2 + + + + La catégorie « %1 » (%2) existe déjà. Que souhaitez-vous faire ? + dialog content + The category "%1" (%2) already exists. What do you wish to do? + + + + L'élément « %1 » existe déjà. Que souhaitez-vous faire ? + dialog content + The element "%1" already exists. What do you wish to do? + + + + La catégorie %1 n'est pas accessible en lecture. + message box content + The category %1 is not readable. + + + + L'élément %1 n'est pas accessible en lecture. + message box content + The element %1 is not readable. + + + + La catégorie %1 n'est pas accessible en écriture. + message box content + The category %1 is not writable. + + + + L'élément %1 n'est pas accessible en écriture. + message box content + The element %1 is not writable. + + + + Erreur + message box title + Error + LineEditor - + abscisse point 1 abscissa point 1 - + ordonnée point 1 ordinate point 1 - + abscisse point 2 abscissa point 2 - + ordonnée point 2 ordinate point 2 + + + Fin 1 + End 1 + + + + Fin 2 + End 2 + + + + type fin 1 + end 1 type + + + + longueur fin 1 + end 1 length + + + + type fin 2 + end 2 type + + + + longueur fin 2 + end 2 length + + + + Normale + type of the 1st end of a line + Normal + + + + Flèche simple + type of the 1st end of a line + Simple arrow + + + + Flèche triangulaire + type of the 1st end of a line + Triangle arrow + + + + Cercle + type of the 1st end of a line + Circle + + + + Carré + type of the 1st end of a line + Diamond + + + + Normale + type of the 2nd end of a line + Normal + + + + Flèche simple + type of the 2nd end of a line + Simple arrow + + + + Flèche triangulaire + type of the 2nd end of a line + Triangle arrow + + + + Cercle + type of the 2nd end of a line + Circle + + + + Carré + type of the 2nd end of a line + Diamond + NamesListWidget @@ -1021,117 +1614,136 @@ Changes will be permanent. Il doit y avoir au moins un nom. + message box title There must be at least one name. Vous devez entrer au moins un nom. + message box content You must enter at least one name. NewDiagramPage - + Nouveau schéma + configuration page title New Diagram NewElementWizard - - Créer un nouvel élément : Assistant - Create a new element: wizard - - - - Erreur - Error - - - - Nom du nouvel élément - New element name - - - - Vous devez entrer un nom de fichier - You must enter a filename - - - - Vous devez sélectionner une catégorie. - You must select a category. - - - + Vous n'êtes pas obligé de préciser l'extension *.elmt. Elle sera ajoutée automatiquement. You don't have to specify the *.elmt extension. It will be added automatically. - + nouvel_element new_element - + &Suivant > &Next > - + + Créer un nouvel élément : Assistant + window title + Create a new element: wizard + + + Étape 1/5 : Catégorie parente + wizard page title Step 1 of 5: Parent category - + Sélectionnez une catégorie dans laquelle enregistrer le nouvel élément. + wizard page subtitle Select a category which to save the new element in. - + Étape 2/5 : Nom du fichier + wizard page title Step 2 of 5: Filename - + Indiquez le nom du fichier dans lequel enregistrer le nouvel élément. + wizard page subtitle Enter the name of the file for the new element. - + Étape 3/5 : Noms de l'élément + wizard page title Step 3 of 5: Element names - + Indiquez le ou les noms de l'élément. + wizard page subtitle Enter one or more names for the element. - + + Nom du nouvel élément + default name when creating a new element + New element name + + + Étape 4/5 : Dimensions et point de saisie + wizard page title Step 4 of 5: Size and hotspot - + Saisissez les dimensions du nouvel élément ainsi que la position du hotspot (point de saisie de l'élément à la souris) en considérant que l'élément est dans son orientation par défaut. + wizard page subtitle Enter the new element size and its hotspot, considering the element is default-oriented. - + Étape 5/5 : Orientations + wizard page title Step 5 of 5: Orientations - + Indiquez les orientations possibles pour le nouvel élément. + wizard page subtitle Enter the allowed and forbidden orientations for the new element. - + + Erreur + message box title + Error + + + + Vous devez sélectionner une catégorie. + message box content + You must select a category. + + + + Vous devez entrer un nom de fichier + message box content + You must enter a filename + + + Merci de ne pas utiliser les caractères suivants : \ / : * ? " < > | + message box content Please avoid the following characters : \ / : * ? < > | @@ -1195,106 +1807,227 @@ Changes will be permanent. Points du polygone : Polygon points: + + + fermeture du polygone + Polygon closure + Erreur + message box title Error Le polygone doit comporter au moins deux points. + message box content The polygon must contain at least two points. + + + ProjectView - - fermeture du polygone - Polygon closure + + Ce projet ne contient aucun schéma + This project does not contain any diagram + + + + Titre du projet : + Project title: + + + + Supprimer les éléments inutilisés dans le projet + Delete unused elements in the project + + + + Supprimer les catégories vides + Delete empty categories + + + + Enregistrer le schéma en cours ? + message box title + Save the current diagram? + + + + Voulez-vous enregistrer le schéma %1 ? + message box content - %1 is a diagram title + Do you wish to save the diagram %1? + + + + Enregistrer le nouveau schéma ? + message box title + Save the new diagram? + + + + Ce schéma a été ajouté mais n'a été ni modifié ni enregistré. Voulez-vous le conserver ? + message box content + This diagram has been added but it hasn't been modified nor saved. Do you wish to keep it? + + + + Supprimer le schéma ? + message box title + Delete the diagram? + + + + Êtes-vous sûr de vouloir supprimer ce schéma du projet ? Ce changement est irréversible. + message box content + Do you really want to delete this diagram from the project? This change is irreversible. + + + + Propriétés du projet + window title + Project properties + + + + Projet en lecture seule + message box title + Read-only project + + + + Ce projet est en lecture seule. Il n'est donc pas possible de le nettoyer. + message box content + This project is read-only. Thus it can not be cleaned. + + + + Nettoyer le projet + window title + Clean project + + + + Enregistrer sous + dialog title + Save as + + + + Schéma QElectroTech (*.qet) + filetypes allowed when saving a diagram file + QElectroTech Diagram (*.qet) + + + + Projet + window title for a project-less ProjectView + Project + + + + Enregistrer le projet en cours ? + message box title + Save current project? + + + + Voulez-vous enregistrer le projet ? + message box content + Do you wish to save the project? + + + + projet + string used to generate a filename + project + + + + Propriétés à utiliser lors de l'ajout d'un nouveau schéma au projet : + Properties used when adding a new diagram to the project: QETApp - - QElectroTech - QElectroTech - - - + &Quitter &Quit - + &Masquer &Hide - + &Restaurer &Show - + &Masquer tous les éditeurs de schéma &Hide diagram editors - + &Restaurer tous les éditeurs de schéma &Show diagram editors - + &Masquer tous les éditeurs d'élément &Hide element editors - + &Restaurer tous les éditeurs d'élément &Show element editors - + &Nouvel éditeur de schéma &New diagram editor - + &Nouvel éditeur d'élément &New element editor - + Ferme l'application QElectroTech Closes QElectroTech - + Réduire QElectroTech dans le systray Reduces QElectroTech into the systray - + Restaurer QElectroTech Restore QElectroTech - + Éditeurs de schémas Diagram editors - + Éditeurs d'élément Element editors - + Usage : Usage: - + QElectroTech, une application de réalisation de schémas électriques. Options disponibles : @@ -1311,7 +2044,7 @@ Available options: - + [options] [fichier]... @@ -1320,1334 +2053,1912 @@ Available options: - + --common-elements-dir=DIR Definir le dossier de la collection d'elements --common-elements-dir=DIR Define the elements collection directory - + --config-dir=DIR Definir le dossier de configuration --config-dir=DIR Define configuration directory - + + --lang-dir=DIR Definir le dossier contenant les fichiers de langue + + --lang-dir=DIR Define the language files directory + + + Chargement... Éditeur de schémas + splash screen caption Loading... Diagrams editor - + Chargement... Ouverture des fichiers + splash screen caption Loading... Opening files - + Chargement... + splash screen caption Loading... - + Chargement... icône du systray + splash screen caption Loading... Systray icon + + + QElectroTech + systray menu title + QElectroTech + - --lang-dir=DIR Definir le dossier contenant les fichiers de langue - - --lang-dir=DIR Define the language files directory - + QElectroTech + systray icon tooltip + QElectroTech QETDiagramEditor - - Active la fenêtre - Activates the window - - - - Active la fenêtre précédente - Activates the previous window - - - - Active la fenêtre suivante - Activates the next window - - - - Adapte la taille du schéma afin qu'il soit entièrement visible - Changes the size of the plan so that it fits in the view - - - + Afficha&ge Displ&ay - - Affiche des informations sur la bibliothèque Qt - Displays informations about Qt library - - - - Affiche des informations sur QElectroTech - Displays informations about QElectroTech - - - + Affiche ou non le panel d'appareils Displays or not the elements panel - - Affiche QElectroTech en mode fenêtré - Displays QElectroTech in windowed mode - - - - Affiche QElectroTech en mode plein écran - Displays QELectroTech in full screen mode - - - + Afficher Display - - Agrandit le schéma - Expand the diagram - - - - Agrandit le schéma en hauteur - Expand the diagram's height - - - + &Aide &Help - + Ajouter une colonne Add a column - - Ajoute une colonne au schéma - Add a column to the diagram - - - - Aligne les fenêtres réduites - Arranges all iconized windows at the bottom of the workspace - - - - Annule l'action précédente - Undoes the previous action - - - + À &propos de QElectroTech A&bout QElectroTech - + À propos de &Qt About &Qt - - Arranger les fenêtres réduites - Arranges iconized windows - - - + &Cascade &Cascade - - Ce fichier n'est pas un document XML valide. - This file is not a valid XML Document. - - - - Ce fichier n'existe pas. - This file does not exist. - - - + C&oller &Paste - + &Configuration &Settings - + &Configurer QElectroTech &Configure QElectroTech - - Copie les éléments sélectionnés dans le presse-papier - Copies selected elements - - - + Cop&ier &Copy - + Co&uper Cu&t - - Crée un nouveau schéma - Opens a new diagram - - - + Ctrl+0 - + Ctrl+9 - + Ctrl+I - + Ctrl+Q - + Ctrl+R Ctrl+R - + Ctrl+Shift+A - + Ctrl+Shift+F Ctrl+Shift+F - + Ctrl+Shift+I - + Ctrl+Shift+X - - Désélectionne les éléments sélectionnés et sélectionne les éléments non sélectionnés - Deselects selected elements and select non-selected elements - - - + Désélectionner tout Select none - - Désélectionne tous les éléments du schéma - Deselect all elements on the plan - - - - Dispose les fenêtres en cascade - Arranges windows in a cascade pattern - - - - Dispose les fenêtres en mosaïque - Arranges windows in a tile pattern - - - - Édite les informations affichées par le cartouche - Edit informations displayed by the inset - - - + &Édition &Edit - - Enlève les éléments sélectionnés du schéma - Removes selected elements from the plan - - - + Enlever une colonne Remove a column - - Enlève une colonne au schéma - Remove a column from the diagram - - - - Enregistre le schéma courant - Saves the current plan - - - - Enregistre le schéma courant avec un autre nom de fichier - Saves the current plan as another filename - - - + &Enregistrer &Save - + Enregistrer sous Save as - - Erreur - Error - - - - Exporte le schéma courant dans un autre format - Exports the curent plan to another format - - - + E&xporter &Export - + Fenêtre précédente Previous Window - + Fe&nêtres Wi&ndows - + Fenêtre suivante Next Window - - Ferme l'application QElectroTech - Closes QElectroTech - - - - Ferme le schéma courant - Closes the current plan - - - + &Fermer &Close - + &Fichier &File - + &Importer &Import - - Importe un schéma dans le schéma courant - Imports a plan into the current plan - - - - Impossible de lire ce fichier. - Could not read file. - - - - Imprime le schéma courant - Prints the current plan - - - + Imprimer Print - + Inverser la sélection Invert selection - + Mode Selection Selection Mode - + Mode Visualisation View Mode - + &Mosaïque &Tile - + &Nouveau &New - + Outils Tools - - Ouvre un schéma existant - Open an existing diagram - - - + &Ouvrir &Open - + Ouvrir un fichier Open a file - + Pas de zoom Reset zoom - + Passer en &mode plein écran F&ullScreen Mode - - Permet de régler différents paramètres de QElectroTech - Allows to specify various parameters for QElectroTech - - - - Permet de sélectionner les éléments - Allows to select elements - - - - Permet de visualiser le schéma sans pouvoir le modifier - Allows to view the plan without modifying it - - - - Pivote les éléments sélectionnés - Rotates selected elements - - - + Pivoter Rotate - - Place les éléments du presse-papier sur le schéma - Pastes elements from the clipboard into the plan - - - - QElectroTech - QElectroTech - - - + &Quitter &Quit - - Restaure l'action annulée - Restores the undone action - - - - Restaure le zoom par défaut - Restores default zoom level - - - - Rétrécit le schéma - Shrinks the plan - - - - Rétrécit le schéma en hauteur - Shrink the diagram's height - - - + Schémas QElectroTech (*.qet);;Fichiers XML (*.xml);;Tous les fichiers (*) QElectroTech Diagrams (*.qet);;XML Files (*.xml);;All files (*) - - Sélectionne tous les éléments du schéma - Selects all elements on the plan - - - + Sortir du &mode plein écran - Exit F&ullScreen Screen Mode + Exit f&ullScreen mode - + Supprimer Delete - + Tout sélectionner Select All - - Transfère les éléments sélectionnés dans le presse-papier - Puts selected elements into the clipboard - - - - Une erreur s'est produite lors de l'ouverture du fichier. - An error occured while opening the file. - - - + Zoom adapté Fit in view - + Zoom arrière Zoom Out - + Zoom avant Zoom In - + Annuler Undo - + Refaire Redo - + Propriétés du conducteur Conductor properties - - Édite les propriétés du conducteur sélectionné - Edit the selected conductor properties - - - + Réinitialiser les conducteurs Reset conductors - + Suppr Del - + Ctrl+J Ctrl+J - + Ctrl+K Ctrl+K - + Propriétés du schéma Diagram Properties - + Ctrl+L Ctrl+L - - Recalcule les chemins des conducteurs sans tenir compte des modifications - Reset the conductors path ignoring the user changes - - - + Affiche ou non la barre d'outils principale Display or hide the main toolbar - + Affiche ou non la barre d'outils Affichage Display or hide the Display toolbar - + Affiche ou non la barre d'outils Schéma Display or hide the Diagram toolbar - + Affichage Display - + Schéma Diagram - + Conducteurs par défaut Default conductors - + Ctrl+D Ctrl+D - - Spécifie les propriétés par défaut des conducteurs - Specify the conductors default properties - - - + Ajouter un champ de texte Add a textfield - - Annulations - Undo - - - + Aucune modification No modification - + Affiche ou non la liste des modifications Display or hide the undo list - - Panel d'éléments - Elements Panel - - - + Ajouter une ligne Add a row - + Enlever une ligne Remove a row + + + Ajouter un schéma + Add a diagram + + + + Supprimer le schéma + Delete the diagram + + + + Ctrl+T + Ctrl+T + + + + &Projet + &Project + + + + Impossible d'ouvrir le fichier + Unable to open file + + + + Il semblerait que le fichier que vous essayez d'ouvrir ne soit pas accessible en lecture. Il est donc impossible de l'ouvrir. Veuillez vérifier les permissions du fichier. + The file you try to open does not seem readable and can not be opened. Please check the file permissions. + + + + Ouverture du projet en lecture seule + Opening the file read-only + + + + Il semblerait que le projet que vous essayez d'ouvrir ne soit pas accessible en écriture. Il sera donc ouvert en lecture seule. + The project you try to open does not seem writable. It will be opened read-only. + + + + en utilisant des onglets + using tabs + + + + en utilisant des fenêtres + using windows + + + + Afficher les projets + Display projects + + + + Propriétés du projet + Project properties + + + + Nettoyer le projet + Clean project + + + + Échec de l'ouverture du projet + message box title + Unable to open project + + + + Il semblerait que le fichier %1 ne soit pas un fichier projet QElectroTech. Il ne peut donc être ouvert. + message box content + The file %1 does not appear to be a QElectroTech project file. Thus it cannot be opened. + + + + QElectroTech + window title + QElectroTech + + + + QElectroTech + status bar message + QElectroTech + + + + Panel d'éléments + dock title + Elements Panel + + + + Annulations + dock title + Undo + + + + Crée un nouveau schéma + status bar tip + Opens a new diagram + + + + Ouvre un schéma existant + status bar tip + Open an existing diagram + + + + Ferme le schéma courant + status bar tip + Closes the current diagram + + + + Enregistre le schéma courant + status bar tip + Saves the current diagram + + + + Enregistre le schéma courant avec un autre nom de fichier + status bar tip + Saves the current diagram as another filename + + + + Importe un schéma dans le schéma courant + status bar tip + Imports a diagram into the current diagram + + + + Exporte le schéma courant dans un autre format + status bar tip + Exports the current diagram to another format + + + + Imprime le schéma courant + status bar tip + Prints the current diagram + + + + Ferme l'application QElectroTech + status bar tip + Closes QElectroTech + + + + Annule l'action précédente + status bar tip + Undoes the previous action + + + + Restaure l'action annulée + status bar tip + Restores the undone action + + + + Transfère les éléments sélectionnés dans le presse-papier + status bar tip + Puts selected elements into the clipboard + + + + Copie les éléments sélectionnés dans le presse-papier + status bar tip + Copies selected elements + + + + Place les éléments du presse-papier sur le schéma + status bar tip + Pastes elements from the clipboard into the diagram + + + + Sélectionne tous les éléments du schéma + status bar tip + Selects all elements on the diagram + + + + Désélectionne tous les éléments du schéma + status bar tip + Deselect all elements on the plan + + + + Désélectionne les éléments sélectionnés et sélectionne les éléments non sélectionnés + status bar tip + Deselects selected elements and select non-selected elements + + + + Enlève les éléments sélectionnés du schéma + status bar tip + Removes selected elements from the diagram + + + + Pivote les éléments sélectionnés + status bar tip + Rotates selected elements + + + + Édite les propriétés du conducteur sélectionné + status bar tip + Edit the selected conductor properties + + + + Recalcule les chemins des conducteurs sans tenir compte des modifications + status bar tip + Reset the conductors path ignoring the user changes + + + + Spécifie les propriétés par défaut des conducteurs + status bar tip + Specify the conductors default properties + + + + Édite les informations affichées par le cartouche + status bar tip + Edit informations displayed by the inset + + + + Ajoute une colonne au schéma + status bar tip + Add a column to the diagram + + + + Enlève une colonne au schéma + status bar tip + Remove a column from the diagram + + + + Agrandit le schéma en hauteur + status bar tip + Expand the diagram's height + + + + Rétrécit le schéma en hauteur + status bar tip + Shrink the diagram's height + + + + Agrandit le schéma + status bar tip + Expand the diagram + + + + Rétrécit le schéma + status bar tip + Shrinks the diagram + + + + Adapte la taille du schéma afin qu'il soit entièrement visible + status bar tip + Changes the size of the plan so that it fits in the view + + + + Restaure le zoom par défaut + status bar tip + Restores default zoom level + + + + Présente les différents projets ouverts dans des sous-fenêtres + status bar tip + Shows the various opened projects in windows + + + + Présente les différents projets ouverts des onglets + status bar tip + Shows the various opened projects in tabs + + + + Permet de sélectionner les éléments + status bar tip + Allows to select elements + + + + Permet de visualiser le schéma sans pouvoir le modifier + status bar tip + Allows to view the diagram without modifying it + + + + Affiche QElectroTech en mode plein écran + status bar tip + Displays QElectroTech in full screen mode + + + + Affiche QElectroTech en mode fenêtré + status bar tip + Displays QElectroTech in windowed mode + + + + Permet de régler différents paramètres de QElectroTech + status bar tip + Allows to specify various parameters for QElectroTech + + + + Dispose les fenêtres en mosaïque + status bar tip + Arranges windows in a tile pattern + + + + Dispose les fenêtres en cascade + status bar tip + Arranges windows in a cascade pattern + + + + Active la fenêtre suivante + status bar tip + Activates the next window + + + + Active la fenêtre précédente + status bar tip + Activates the previous window + + + + Affiche des informations sur QElectroTech + status bar tip + Displays informations about QElectroTech + + + + Affiche des informations sur la bibliothèque Qt + status bar tip + Displays informations about Qt library + + + + Active la fenêtre %1 + Activates the window %1 + + + + &Enregistrer tous les schémas + Sav&e all diagrams + + + + Enregistre tous les schémas du projet courant + status bar tip + Saves all the diagrams of the current project + QETElementEditor - - QElectroTech - Éditeur d'élément - QElectroTech - Element Editor - - - + &Nouveau &New - + &Ouvrir &Open - + &Enregistrer &Save - + Enregistrer sous Save as - + &Quitter &Quit - + Tout sélectionner Select All - + Désélectionner tout Select none - + Inverser la sélection Invert selection - + &Supprimer &Delete - + Éditer la taille et le point de saisie Edit size and hotspot - + Éditer les noms Edit names - + Éditer les orientations Edit orientations - + Déplacer un objet Move an object - + Ajouter une ligne Add a line - + Ajouter une ellipse Add an ellipse - + Ajouter un cercle Add a circle - + Ajouter un polygone Add a polygon - + Ajouter du texte Add text - + Ajouter un arc de cercle Add an arc - + Ajouter une borne Add a terminal - + Ajouter un champ de texte Add a textfield - + Annuler Undo - + Refaire Redo - + Ctrl+Q Ctrl+Q - + Ctrl+Shift+A Ctrl+Shift+A - + Ctrl+I Ctrl+I - - Parties - Parts - - - + Fichier File - + Édition Edit - + Affichage Display - + Outils Tools - + Aide Help - - [Modifié] - [Changed] - - - - [lecture seule] - [Read only] - - - - Informations - Informations - - - - Annulations - Undo - - - - Éditeur d'éléments - Elements Editor - - - - parties sélectionnées. - selected parts. - - - - Aucune partie sélectionnée. - No part selected. - - - - Le fichier - The file - - - - n'existe pas. - does not exist. - - - - Impossible d'ouvrir le fichier - Unable to open file - - - - Ce fichier n'est pas un document XML valide - This file is not a valid XML document - - - - Erreur - Error - - - - Édition en lecture seule - Read only edition - - - - Vous n'avez pas les privilèges nécessaires pour modifier cet élement. Il sera donc ouvert en lecture seule. - You are not allowed to modify this element. Thus it will be edited read-only. - - - - Impossible d'ecrire dans ce fichier - Can't write to the file - - - - Ouvrir un fichier - Open a file - - - - Éléments QElectroTech (*.elmt);;Fichiers XML (*.xml);;Tous les fichiers (*) - QElectroTech elements (*.elmt);;XML files (*.xml);;All files (*) - - - - Éléments QElectroTech (*.elmt) - QElectroTech elements (*.elmt) - - - - Enregistrer l'élément en cours ? - Save current element ? - - - - Voulez-vous enregistrer l'élément - Do you wish to save the element - - - - ? - ? - - - + Afficher Display - + Suppr Del - + Ctrl+E Ctrl+E - + Ctrl+R Ctrl+R - + Ctrl+T Ctrl+T - + Rapprocher Raise - + Éloigner Lower - + Envoyer au fond Send backward - + Amener au premier plan Bring forward - + Ctrl+Shift+Up Ctrl+Shift+Up - + Ctrl+Shift+Down Ctrl+Shift+Down - + Ctrl+Shift+End Ctrl+Shift+End - + Ctrl+Shift+Home Ctrl+Shift+Home - + Aucune modification No modification - + Recharger Reload - - Recharger l'élément - Reload element - - - - Vous avez efffectué des modifications sur cet élément. Si vous le rechargez, ces modifications seront perdues. Voulez-vous vraiment recharger l'élément ? - This element has been modified since last save. If you reload it, these changes will be lost. Do you really want to reload this element ? - - - + Zoom avant Zoom In - + Zoom arrière Zoom Out - + Zoom adapté Fit in view - + Pas de zoom Reset zoom - + Ctrl+9 Ctrl+9 - + Ctrl+0 Ctrl+0 - + + &Ouvrir depuis un fichier + &Open from a file + + + + Enregistrer dans un fichier + Save to a file + + + + Ctrl+Shift+O + Ctrl+Shift+O + + + + Ctrl+Shift+S + Ctrl+Shift+S + + + + QElectroTech - Éditeur d'élément + window title + QElectroTech - Element Editor + + + + Parties + toolbar title + Parts + + + + Outils + toolbar title + Tools + + + + Affichage + toolbar title + Display + + + Élément + toolbar title Element - + Profondeur + toolbar title Depth + + + [Modifié] + window title tag + [Changed] + + + + [lecture seule] + window title tag + [Read only] + + + + Informations + dock title + Informations + + + + Annulations + dock title + Undo + + + + Parties + dock title + Parts + + + + Éditeur d'éléments + status bar message + Elements Editor + + + + %n partie(s) sélectionnée(s). + + %n selected part. + %n selected parts. + + + + + Le fichier %1 n'existe pas. + message box content + The file %1 does not exist. + + + + Impossible d'ouvrir le fichier %1. + message box content + Unable to open the file %1. + + + + Ce fichier n'est pas un document XML valide + message box content + This file is not a valid XML document + + + + Erreur + toolbar title + Error + + + + Édition en lecture seule + message box title + Read only edition + + + + Vous n'avez pas les privilèges nécessaires pour modifier cet élement. Il sera donc ouvert en lecture seule. + message box content + You are not allowed to modify this element. Thus it will be edited read-only. + + + + Erreur + message box title + Error + + + + Impossible d'écrire dans ce fichier + message box content + Unable to write to this file + + + + Impossible d'atteindre l'élément + message box content + Unable to reach the element + + + + Impossible d'enregistrer l'élément + message box content + Unable to save the element + + + + Ouvrir un fichier + dialog title + Open a file + + + + Éléments QElectroTech (*.elmt);;Fichiers XML (*.xml);;Tous les fichiers (*) + filetypes allowed when opening an element file + QElectroTech elements (*.elmt);;XML files (*.xml);;All files (*) + + + + Recharger l'élément + dialog title + Reload element + + + + Vous avez efffectué des modifications sur cet élément. Si vous le rechargez, ces modifications seront perdues. Voulez-vous vraiment recharger l'élément ? + dialog content + This element has been modified since last save. If you reload it, these changes will be lost. Do you really want to reload this element? + + + + Enregistrer sous + dialog title + Save as + + + + Éléments QElectroTech (*.elmt) + filetypes allowed when saving an element file + QElectroTech elements (*.elmt) + + + + Enregistrer l'élément en cours ? + dialog title + Save current element? + + + + Voulez-vous enregistrer l'élément %1 ? + dialog content - %1 is an element name + Do you wish to save the element %1? + + + + Élément inexistant. + message box title + Non-existent element. + + + + L'élément n'existe pas. + message box content + The element does not exist. + + + + Le chemin virtuel choisi ne correspond pas à un élément. + message box content + The chosen virtual path does not match an element. + + + + Maintenez la touche Shift enfoncée pour effectuer plusieurs ajouts d'affilée + Hold down the Shift key to add several parts in a row + + + + Utilisez le bouton droit de la souris pour poser le dernier point du polygone + Use the right mouse button to set the polygon's last point + + + + Dimensions de l'élément + messagebox title + Element size + + + + Attention : certaines parties graphiques (textes, cercles, lignes...) semblent déborder du cadre de l'élément. Cela risque de générer des bugs graphiques lors de leur manipulation sur un schéma. Vous pouvez corriger cela soit en déplaçant ces parties, soit en vous rendant dans Édition > Éditer la taille et le point de saisie. + messagebox content + Warning: some graphical parts (texts, circles, lines, ...) seem to be out of the element border. This may generate graphical bugs when the element is moved on a diagram. You can fix this by moving those parts or using Edit > Edit size and hotspot. + + + + Co&uper + Cu&t + + + + Cop&ier + &Copy + + + + C&oller + &Paste + + + + C&oller dans la zone... + Pa&ste in the area... + + + + Ctrl+Shift+V + Ctrl+Shift+V + + + + Ajouter un rectangle + Add a rectangle + + + + QETPrintPreviewDialog + + + QElectroTech : Aperçu avant impression + QElectroTech: Print preview + + + + Schémas à imprimer : + Diagrams to print: + + + + Cacher la liste des schémas + Hide the diagrams list + + + + Cacher les options d'impression + Hide the print options + + + + Ajuster la largeur + Fit to width + + + + Ajuster la page + Fit to page + + + + Zoom arrière + Zoom Out + + + + Zoom avant + Zoom In + + + + Paysage + Landscape + + + + Portrait + Portrait + + + + Première page + First page + + + + Page précédente + Previous page + + + + Page suivante + Next page + + + + Dernière page + Last page + + + + Afficher une seule page + Display a single page + + + + Afficher deux pages + Display facing pages + + + + Afficher un aperçu de toutes les pages + Display all pages + + + + Mise en page + Page layout + + + + Mise en page (non disponible sous Windows pour l'impression PDF/PS) + Page layout (not available under Windows for PDF/PS printing) + + + + Options d'impression + Print options + + + + Utiliser toute la feuille + Use the whole page + + + + Si cette option est cochée, les marges de la feuille seront ignorées et toute sa surface sera utilisée pour l'impression. Cela peut ne pas être supporté par votre imprimante. + If this option is checked, the paper margins are ignored and its whole surface is used for the printing. This may not be supported by your printer. + + + + Adapter le schéma à la page + Fit diagram to page + + + + Si cette option est cochée, le schéma sera agrandi ou rétréci de façon à remplir toute la surface imprimable d'une et une seule page. + If this option is checked, the diagram will be shrinked or expanded to fit the printable surface of a single page. + + + + Imprimer + Print + + + + Afficher la liste des schémas + Display + + + + Afficher les options d'impression + Display the print options + + + + %1 % + %1% + + + + QETProject + + + Impossible de créer la catégorie pour l'intégration des éléments + Unable to create the category dedicated to the elements integration + + + + Impossible d'accéder à l'élément a intégrer + Unable to reach the element to integrate + + + + Un problème s'est produit pendant la copie de la catégorie %1 + An error occured during the copy of the category %1 + + + + Un problème s'est produit pendant la copie de l'élément %1 + An error occured during the copy of the element %1 + + + + Avertissement + message box title + Warning + + + + Ce document semble avoir été enregistré avec une version ultérieure de QElectroTech. Il est possible que l'ouverture de tout ou partie de ce document échoue. + message box content + This document seems to have been saved by a more recent version of QElectroTech. The opening of the document may fail totally or partially. + + + + Projet « %1 » + displayed title for a ProjectView - %1 is the project title + Project "%1" + + + + Projet %1 + displayed title for a title-less project - %1 is the file name + Project %1 + + + + Projet sans titre + displayed title for a project-less, file-less project + Untitled project + + + + %1 [lecture seule] + displayed title for a read-only project - %1 is a displayable title + %1 [read-only] + + + + QFileNameEdit + + + Les caractères autorisés sont : + - les chiffres [0-9] + - les minuscules [a-z] + - le tiret [-], l'underscore [_] et le point [.] + + tooltip content when editing a filename + The allowed characters are: + - digits [0-9] + - lower-case letters [a-z] + - dash [-], underscore [_] and dot [.] + + QObject + + + Le fichier texte contenant la licence GNU/GPL est introuvable - bon bah de toute façon, vous la connaissez par coeur non ? + The text file containing the GNU/GPL license could not be found - however, you know it by heart, don't you? + + + + Le fichier texte contenant la licence GNU/GPL existe mais n'a pas pu être ouvert - bon bah de toute façon, vous la connaissez par coeur non ? + The text file containing the GNU/GPL license exists but could not be opened - however, you know it by heart, don't you? + + + + Avertissement : l'élément a été enregistré avec une version ultérieure de QElectroTech. + Warning : the element has been saved with a more recent version of QElectroTech. + + + + ajouter 1 %1 + undo caption - %1 is an element name + add 1 %1 + + + + Ajouter un champ de texte + undo caption + Add a textfield + + + + ajouter un conducteur + undo caption + add a conductor + + + + supprimer %1 + undo caption - %1 is a sentence listing the removed content + delete %1 + + + + coller %1 + undo caption - %1 is a sentence listing the content to paste + paste %1 + + + + couper %1 + undo caption - %1 is a sentence listing the content to cut + cut %1 + + + + déplacer %1 + undo caption - %1 is a sentence listing the moved content + move %1 + + + + modifier le texte + undo caption + modify text + + + + pivoter %1 + undo caption - %1 is a sentence listing the rotated content + rotate %1 + + + + modifier un conducteur + undo caption + modify a conductor + + + + Réinitialiser %1 + undo caption - %1 is a sentence listing the reset content + + + + + modifier le cartouche + undo caption + modify the inset + + + + modifier les dimensions du schéma + undo caption + modify the diagram size + + + + modifier les propriétés d'un conducteur + undo caption + modify conductor properties + suppression + undo caption deletion - + déplacement + undo caption move - - ajout - insert + + ajout %1 + undo caption + insert %1 - - modification - modification + + modification %1 + undo caption + modify %1 - + modification points polygone + undo caption modification polygon points + + + modification dimensions/hotspot + undo caption + modify size/hotspot + + + + modification noms + undo caption + modify names + + + + modification orientations + undo caption + modify orientations + + + + amener au premier plan + undo caption + bring forward + + + + rapprocher + undo caption + raise + + + + éloigner + undo caption + lower + + + + envoyer au fond + undo caption + send backward + + + + modification connexions internes + undo caption + modify internal connections + arc + element part name arc cercle + element part name circle ellipse + element part name ellipse - + ligne + element part name line polygone + element part name polygon borne + element part name terminal T + default text when adding a text in the element editor T texte + element part name text _ + default text when adding a textfield in the element editor _ - + champ de texte + element part name textfield - - - ajouter 1 - add 1 + + + %n élément(s) + part of a sentence listing the content of a diagram + + %n element + %n elements + - - ajouter un conducteur - add a conductor + + , + separator between elements and conductors in a sentence listing the content of a diagram + , - - supprimer - delete - - - - coller - paste - - - - couper - cut - - - - déplacer - move - - - - modifier le texte - modify text - - - - pivoter - rotate - - - - modifier un conducteur - modify a conductor - - - - modifier le cartouche - modify the inset - - - - modifier les dimensions du schéma - modify the diagram size - - - - modification dimensions/hotspot - modify size/hotspot - - - - modification noms - modify names - - - - modification orientations - modify orientations - - - - éléments - elements - - - - élément - element - - - + et + separator between elements and conductors (or texts) in a sentence listing the content of a diagram and - - - conducteurs - conductors + + + %n conducteur(s) + part of a sentence listing the content of a diagram + + %n conductor + %n conductors + - - conducteur - conductor + + et + separator between conductors and texts in a sentence listing the content of a diagram + and + + + + %n champ(s) de texte + part of a sentence listing the content of a diagram + + %n textfield + %n textfields + Borne + tooltip Terminal - - modifier les propriétés d'un conducteur - modify conductor properties + + coller + paste - - Avertissement : l'élément - Warning: the element + + couper des parties + undo caption + cut parts - - a été enregistré avec une version ultérieure de QElectroTech. - was saved with a more recent version of QElectroTech. + + rectangle + element part name + rectangle - - Réinitialiser - Reset + + Schéma sans titre + Untitled diagram - - amener au premier plan - bring forward + + schema + diagram - - rapprocher - raise + + Conserver les proportions + Keep aspect ratio - - éloigner - lower + + Réinitialiser les dimensions + Reset size - - envoyer au fond - send backward - - - - Ajouter un champ de texte - Add a textfield - - - - , - , - - - - champs de texte - textfields - - - - Le fichier texte contenant la licence GNU/GPL est introuvable - bon bah de toute façon, vous la connaissez par coeur non ? - The text file containing the GNU/GPL license could not be found - however, you know it by heart, don't you ? - - - - Le fichier texte contenant la licence GNU/GPL existe mais n'a pas pu être ouvert - bon bah de toute façon, vous la connaissez par coeur non ? - The text file containing the GNU/GPL license exists but could not be opened - however, you know it by heart, don't you ? - - - - modification connexions internes - modification internal connections + + Aperçu + Preview @@ -2655,51 +3966,54 @@ Available options: &Récemment ouvert(s) - &Recently opened + &Recently Opened + + + + RectangleEditor + + + Coin supérieur gauche : + Top left corner: + + + + Dimensions : + Size: + + + + Largeur : + Width: + + + + Hauteur : + Height: + + + + abscisse + abscissa + + + + ordonnée + ordinate + + + + largeur + width + + + + hauteur + height StyleEditor - - - Noir - Black - - - - Blanc - White - - - - Normal - Normal - - - - Pointillé - Dashed - - - - Nulle - None - - - - Fine - Thin - - - - Normale - Normal - - - - Aucun - None - Antialiasing @@ -2730,6 +4044,66 @@ Available options: Remplissage : Filling: + + + Noir + element part color + Black + + + + Blanc + element part color + White + + + + Normal + element part line style + Normal + + + + Pointillé + element part line style + Dashed + + + + Nulle + element part weight + None + + + + Fine + element part weight + Thin + + + + Normale + element part weight + Normal + + + + Aucun + element part filling + None + + + + Noir + element part filling + Black + + + + Blanc + element part filling + White + TerminalEditor @@ -2826,16 +4200,16 @@ Available options: ordonnée ordinate - - - texte - text - taille size + + + contenu + content + TextFieldEditor @@ -2881,11 +4255,6 @@ Available options: ordonnée ordinate - - - texte - text - taille @@ -2896,5 +4265,10 @@ Available options: propriété property + + + contenu + content + diff --git a/lang/qet_es.qm b/lang/qet_es.qm new file mode 100644 index 0000000000000000000000000000000000000000..a1f3aae1a928164e03e3e24aed7d3af81d28065a GIT binary patch literal 86547 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 D3yIDq literal 0 HcmV?d00001 diff --git a/lang/qet_es.ts b/lang/qet_es.ts new file mode 100644 index 000000000..79e4f2899 --- /dev/null +++ b/lang/qet_es.ts @@ -0,0 +1,4274 @@ + + + + AboutQET + + + QElectroTech, une application de réalisation de schémas électriques. + QElectroTech, una aplicación de realización de esquemas eléctricos. + + + + © 2006-2009 Les développeurs de QElectroTech + © 2006-2009 Los desarrolladores de QElectroTech + + + + Idée originale + Idea original + + + + Programmation + Programación + + + + Ce programme est sous licence GNU/GPL. + Este programa está bajo licencia GNU/GPL. + + + + À propos de QElectrotech + window title + Acerca de QElectroTech + + + + À &propos + tab title + &Acerca de + + + + A&uteurs + tab title + A&utores + + + + &Accord de licence + tab title + Acuerdo de &licencia + + + + ArcEditor + + + Centre : + Centro: + + + + Diamètres : + Diámetros: + + + + horizontal : + horizontal: + + + + vertical : + vertical: + + + + Angle de départ : + Ángulo inicial: + + + + Angle : + Ángulo: + + + + abscisse + abscisa + + + + ordonnée + ordenada + + + + diamètre horizontal + diámetro horizontal + + + + diamètre vertical + diámetro vertical + + + + angle de départ + ángulo inicial + + + + angle + ángulo + + + + BorderInset + + + Auteur : %1 + inset content + Autor: %1 + + + + Date : %1 + inset content + Fecha: %1 + + + + Titre du document : %1 + inset content + Título del documento: %1 + + + + Fichier : %1 + inset content + Archivo: %1 + + + + Folio : %1 + inset content + Folio: %1 + + + + BorderPropertiesWidget + + + Dimensions du schéma + Dimensiones del esquema + + + + Colonnes : + Columnas: + + + + Afficher les en-têtes + Mostrar los encabezamientos + + + + Lignes : + Filas: + + + + × + multiplication symbol + × + + + + px + unit for cols width + px + + + + px + unit for rows height + px + + + + CircleEditor + + + Centre : + Centra: + + + + Diamètre : + Diámetro: + + + + abscisse + abscisa + + + + ordonnée + ordenada + + + + diamètre + diámetro + + + + ConductorPropertiesWidget + + + Type de conducteur + Tipo de conductor + + + + Simple + Simple + + + + Multifilaire + Multilínea + + + + Texte : + Texto: + + + + Unifilaire + Monolínea + + + + phase + fasor + + + + terre + tierra + + + + neutre + neutro + + + + ConfigDialog + + + Configurer QElectroTech + window title + Configurar QElectroTech + + + + DiagramPrintDialog + + + Options d'impression + window title + Opciones de impresión + + + + Quel type d'impression désirez-vous effectuer ? + ¿Qué tipo de impresión desea hacer? + + + + Choix du type d'impression + Elección del tipo de impresión + + + + Fichier manquant + message box title + Archivo faltando + + + + Vous devez indiquer le chemin du fichier PDF/PS à créer. + message box content + Debe especificar la ruta del archivo PDF / PS a crear. + + + + Fichiers PDF (*.pdf) + file filter + Archivos PDF (*.pdf) + + + + Fichiers PostScript (*.ps) + file filter + Archivos PostScript (*.ps) + + + + DiagramView + + + Schéma sans titre + Esquema sin título + + + + Coller ici + context menu action + Pegar aquí + + + + Schéma %1 + %1 is a diagram title + Esquema %1 + + + + Propriétés du schéma + window title + Propiedades del esquema + + + + Éditer les propriétés d'un conducteur + window title + Editar las propiedades de un conductor + + + + Éditer les propriétés par défaut des conducteurs + window title + Editar las propiedades predeterminadas de los conductores + + + + DiagramsChooser + + + Schéma sans titre + Esquema sin título + + + + ElementDefinition + + + L'élément cible n'a pu être créé. + No se ha podido crear el elemento. + + + + La suppression de cet élément a échoué. + No se ha podido eliminar este elemento. + + + + ElementDeleter + + + Supprimer l'élément ? + message box title + ¿Borrar el elemento? + + + + Êtes-vous sûr de vouloir supprimer cet élément ? + + message box content + ¿Realmente quiere borrar este elemento? + + + + + Suppression de l'élément + message box title + Borrado del elemento + + + + La suppression de l'élément a échoué. + message box content + El borrado del elemento falló. + + + + ElementDialog + + + Nom : + Nombre: + + + + Ouvrir un élément + dialog title + Abrir un elemento + + + + Choisissez l'élément que vous souhaitez ouvrir. + dialog content + Elige el elemento que quiere abrir. + + + + Enregistrer un élément + dialog title + Guardar un elemento + + + + Choisissez l'élément dans lequel vous souhaitez enregistrer votre définition. + dialog content + Elige un elemento en lo que quieres guardar su definición. + + + + Ouvrir une catégorie + dialog title + Abrir una categoría + + + + Choisissez une catégorie. + dialog content + Elige una categoría. + + + + Enregistrer une catégorie + dialog title + Guardar un elemento + + + + Pas de sélection + message box title + No hay selección + + + + Vous devez sélectionner un élément. + message box content + Tiene que seleccionar un elemento. + + + + Sélection inexistante + message box title + No existe selección + + + + La sélection n'existe pas. + message box content + Lo selectado no existe. + + + + Sélection incorrecte + message box title + Selección incorrecto + + + + La sélection n'est pas un élément. + message box content + Lo selectado no es un elemento. + + + + Vous devez sélectionner une catégorie ou un élément. + message box content + Tienes que seleccionar una categoría o un elemento. + + + + Nom manquant + message box title + Nombre faltando + + + + Vous devez entrer un nom pour l'élément + message box content + Tienes que escribir un nombre para el elemento + + + + Nom invalide + message box title + Nombre no válido + + + + Vous ne pouvez pas utiliser les caractères suivants dans le nom de l'élément : %1 + No puede usar los caracteres siguientes en el nombre del elemento: %1 + + + + Écraser l'élément ? + message box title + Sobrescribir un elemento + + + + L'élément existe déjà. Voulez-vous l'écraser ? + message box content + El elemento ya existe. Quiere sobrescribirlo? + + + + ElementScene + + + ligne + linea + + + + ellipse + elipse + + + + arc + arco + + + + cercle + círculo + + + + borne + conector + + + + texte + texto + + + + champ de texte + campo de texto + + + + polygone + polígono + + + + L'orientation par défaut est l'orientation dans laquelle s'effectue la création de l'élément. + Por defecto, la orientación es la orientación usada durante la creación del elemento. + + + + Autoriser les connexions internes + Permitir las conecciones internas + + + + Vous pouvez spécifier le nom de l'élément dans plusieurs langues. + Puede escribir el nombre del elemento en varias lenguas. + + + + Ce document XML n'est pas une définition d'élément. + error message + Este documento XML no es una definición de elemento. + + + + Les dimensions ou le point de saisie ne sont pas valides. + error message + Los tamaños o el hotspot no son valides. + + + + Les orientations ne sont pas valides. + error message + Las orientaciones no son valides. + + + + Éditer la taille et le point de saisie + window title + Editar el tamaño o el hotspot + + + + Éditer les orientations + window title + Editar las orientaciones + + + + Éditer les noms + window title + Editar los nombres + + + + rectangle + rectángulo + + + + ElementsCategoriesList + + + Collection projet + Colección proyecto + + + + Collection QET + Colección QET + + + + Collection utilisateur + Colección usuario + + + + ElementsCategoriesWidget + + + Recharger les catégories + Recargar las categorías + + + + Nouvelle catégorie + Nueva categoría + + + + Éditer la catégorie + Editar la categoría + + + + Supprimer la catégorie + Borrar la categoría + + + + ElementsCategory + + + La copie d'une catégorie vers elle-même ou vers l'une de ses sous-catégories n'est pas gérée. + Copiar una categoría en si mismo o en una de sus subcategorías no es posible. + + + + Il n'est pas possible de déplacer une collection. + No es posible mover una colección. + + + + Le déplacement d'une catégorie dans une de ses sous-catégories n'est pas possible. + Mover una categoría en unas de sus subcategorías no es posible. + + + + La suppression de cette catégorie a échoué. + Fracasó la eliminación de esta categoría. + + + + Impossible de supprimer l'élément + Imposible de eliminar el elemento + + + + Impossible de supprimer la catégorie + Imposible de eliminar una categoría + + + + ElementsCategoryDeleter + + + Vider la collection ? + message box title + ¿Vaciar la colección? + + + + Êtes-vous sûr de vouloir vider cette collection ? + message box content + ¿Está seguro de querer vaciar esta colección? + + + + Supprimer la catégorie ? + message box title + ¿Borrar la categoría? + + + + Êtes-vous sûr de vouloir supprimer la catégorie ? +Tous les éléments et les catégories contenus dans cette catégorie seront supprimés. + message box content + ¿Realmente quiere borrar esta categoría? +Todos los elementos y categorías de estas categorías van a ser borrados. + + + + Êtes-vous vraiment sûr de vouloir supprimer cette catégorie ? +Les changements seront définitifs. + message box content + ¿Est realmente seguro de borrar esta categoría? +Los cambios no podrían ser revertidos. + + + + Suppression de la catégorie + message box title + Borrado de la categoría + + + + La suppression de la catégorie a échoué. + message box content + El borrado de la categoría falló. + + + + ElementsCategoryEditor + + + Nom interne : + Nombre interno: + + + + Vous pouvez spécifier un nom par langue pour la catégorie. + Se puede dar un nombre por cada idioma para la categoría. + + + + Catégorie inexistante + message box title + Esta categoría no existe + + + + La catégorie demandée n'existe pas. Abandon. + message box content + La categoría pedida no existe. Aborto. + + + + Éditer une catégorie + window title + Editar una categoría + + + + Créer une nouvelle catégorie + window title + Crear una nueva categoría + + + + Nom de la nouvelle catégorie + default name when creating a new category + Nombre de la nueva categoría + + + + Édition en lecture seule + message box title + Edición en lectura sola + + + + Vous n'avez pas les privilèges nécessaires pour modifier cette catégorie. Elle sera donc ouverte en lecture seule. + message box content + No tiene los derechos para editar esta categoría. Por eso se abre en lectura sola. + + + + Nom interne manquant + message box title + Falta un nombre interno + + + + Vous devez spécifier un nom interne. + message box content + Puede dar un nombre interno. + + + + Nom interne déjà utilisé + message box title + Nombre interno ya usado + + + + Le nom interne que vous avez choisi est déjà utilisé par une catégorie existante. Veuillez en choisir un autre. + message box content + El nombre interno que ha escogido es ya usado para una categoría que existe. Por favor elige otro nombre. + + + + Erreur + message box title + Error + + + + Impossible de créer la catégorie + message box content + Imposible de crear esta categoría + + + + Impossible d'enregistrer la catégorie + message box content + Imposible de guardar la categoría + + + + ElementsCollection + + + Il n'est pas possible de déplacer une collection. + No es posible mover una colección. + + + + ElementsPanel + + + Collection projet + Colección proyecto + + + + Schéma sans titre + Esquema sin título + + + + Ceci est un élément que vous pouvez insérer dans votre schéma par cliquer-déplacer + Eso es un elemento que se puede insertar en su esquema con clicar-mover + + + + Cliquer-déposez cet élément sur le schéma pour insérer un élément + Arrastar y soltar este elemento al esquema para insertar un elemento + + + + Collection QET + Colección QET + + + + Collection utilisateur + Colección usuario + + + + %1 [non utilisé dans le projet] + %1 [no usado en el proyecto] + + + + Pas de fichier + tooltip for a file-less project in the element panel + No hay archivo + + + + ElementsPanelWidget + + + Recharger les collections + Recargar las colecciónes + + + + Nouvelle catégorie + Nueva categoría + + + + Éditer la catégorie + Editar la categoría + + + + Supprimer la catégorie + Eliminar la categoría + + + + Vider la collection + Vaciar la colección + + + + Nouvel élément + Nuevo elemento + + + + Éditer l'élément + Editar el elemento + + + + Supprimer l'élément + Eliminar el elemento + + + + Fermer ce projet + Cerrar este proyecto + + + + Propriétés du projet + Propiedades del proyecto + + + + Ajouter un schéma + Añadir un esquema + + + + Supprimer ce schéma + Eliminar este esquema + + + + Effacer le filtre + Borrar un filtro + + + + Filtrer : + Filtrar: + + + + Vous pouvez utiliser ce gestionnaire pour ajouter, supprimer ou modifier les catégories. + Puede usar este gestionario para agregar, eliminar o editar las categorías. + + + + Déplacer dans cette catégorie + Mover en esta categoría + + + + Copier dans cette catégorie + Copiar en esta categoría + + + + Annuler + Deshacer + + + + Gestionnaire de catégories + window title + Gestionario de categorías + + + + Propriétés du schéma + Propiedades del esquema + + + + EllipseEditor + + + Centre : + Centro: + + + + Diamètres : + Diámetros: + + + + horizontal : + horizontal: + + + + vertical : + vertical: + + + + abscisse + abscisa + + + + ordonnée + ordenada + + + + diamètre horizontal + diámetro horizontal + + + + diamètre vertical + diámetro vertical + + + + ExportDialog + + + Dimensions + Tamaños + + + + Options + Opciones + + + + Exporter le cadre + Exportar el cuadro + + + + Exporter les éléments + Exportar los elementos + + + + Dessiner la grille + Dibujar la reja + + + + Dessiner le cadre + Dibujar el cuadro + + + + Dessiner le cartouche + Dibujar el cartucho + + + + Dessiner les bornes + Dibujar conectores + + + + Parcourir + + + + + Format : + Formato: + + + + PNG (*.png) + PNG (*.png) + + + + JPEG (*.jpg) + JPEG (*.jpg) + + + + Bitmap (*.bmp) + Bitmap (*.bmp) + + + + SVG (*.svg) + SVG (*.svg) + + + + Aperçu + Vista previa + + + + Impossible d'écrire dans ce fichier + message box title + Imposible escribir en el archivo + + + + Exporter les schémas du projet + window title + + + + + Exporter + Exportar + + + + Choisissez les schémas que vous désirez exporter ainsi que leurs dimensions : + + + + + Schéma + Esquema + + + + Nom de fichier + + + + + Dossier cible : + + + + + Exporter dans le dossier + dialog title + + + + + Noms des fichiers cibles + message box title + + + + + Vous devez entrer un nom de fichier distinct pour chaque schéma à exporter. + message box content + + + + + Dossier non spécifié + message box title + + + + + Vous devez spécifier le chemin du dossier dans lequel seront enregistrés les fichiers images. + message box content + + + + + Il semblerait que vous n'ayez pas les permissions nécessaires pour écrire dans le fichier %1. + message box content + + + + + ExportDialog::ExportDiagramLine + + + px + px + + + + GeneralConfigurationPage + + + Projets + Proyectos + + + + Utiliser des fenêtres + Utilizar ventanas + + + + Utiliser des onglets + Utilizar pestañas + + + + Ces paramètres s'appliqueront dès la prochaine ouverture d'un éditeur de schémas. + Estos parámetros van ser activados la próxima vez que se va a abrir un editor de esquemas. + + + + Gestion des éléments + Gestionario de elementos + + + + Intégrer automatiquement les éléments dans les projets (recommandé) + Automáticamente integrar los elementos en los proyectos (recomendado) + + + + Général + configuration page title + General + + + + GhostElement + + + <u>Élément manquant :</u> %1 + + + + + HotspotEditor + + + ×10 px + ×10 px + + + + px + px + + + + Déplacer l'élément avec le hotspot + Mover el elemento con el hotspot + + + + <span style="text-decoration:underline;">Dimensions</span> + <span style="text-decoration:underline;">Dimensiones</span> + + + + Largeur : + Anchura: + + + + Hauteur : + Altura: + + + + <span style="text-decoration:underline;">Hotspot</span> + <span style="text-decoration:underline;">Hotspot</span> + + + + Abscisse : + Abscisa: + + + + Ordonnée : + Ordenada: + + + + L'élément doit être assez grand pour contenir tout sa représentation graphique. + El elemento tiene que ser suficientemente grande para contener la representación gráfica entera. + + + + InsetPropertiesWidget + + + Informations du cartouche + Informaciones sobre el cartucho + + + + Pas de date + No fecha + + + + Date courante + Fecha actual + + + + Date fixe : + Fecha fixada: + + + + Titre : + Título: + + + + Auteur : + Autor: + + + + Date : + Fecha: + + + + Fichier : + Archivo: + + + + Folio : + Folio: + + + + Les variables suivantes sont utilisables dans le champ Folio : + - %id : numéro du schéma courant dans le projet + - %total : nombre total de schémas dans le projet + Las variables siguientes pueden ser usadas en el campo Folio: + - %id: número del esquema corriente en el proyecto + - %total: número total de esquemas en el proyecto + + + + IntegrationMoveElementsHandler + + + L'élément a déjà été intégré dans le projet. Toutefois, la version que vous tentez de poser semble différente. Que souhaitez-vous faire ? + dialog content - %1 is an element's path name + El elemento ya fue incluido en el proyecto. Sin embargo, la versión que trate de poner parece ser diferente. ¿Que desea hacer? + + + + Utiliser l'élément déjà intégré + dialog content + Usar un elemento ya incluido + + + + Intégrer l'élément déposé + dialog content + Incluir el elemento colocado + + + + Écraser l'élément déjà intégré + dialog content + Sobreescribir el elemento ya incluido + + + + Faire cohabiter les deux éléments + dialog content + Hacer que dos elementos coexisten + + + + Intégration d'un élément + Inclusión de un elemento + + + + InteractiveMoveElementsHandler + + + Renommer + Renombrar + + + + Écraser + Sobreescribir + + + + Écraser tout + Sobreescribir todo + + + + Ignorer + Ignorar + + + + Ignorer tout + Ignorar todo + + + + Annuler + Deshacer + + + + Copie de %1 vers %2 + dialog title + Copia de %1 hacia %2 + + + + La catégorie « %1 » (%2) existe déjà. Que souhaitez-vous faire ? + dialog content + La categoría « %1 » (%2) ya existe. ¿ Que desea hacer? + + + + L'élément « %1 » existe déjà. Que souhaitez-vous faire ? + dialog content + El elemento « %1 » ya existe. ¿Que desea hacer? + + + + La catégorie %1 n'est pas accessible en lecture. + message box content + La categoría %1 no se puede acceder en lectura. + + + + L'élément %1 n'est pas accessible en lecture. + message box content + El elemento %1 no se puede acceder en lectura. + + + + La catégorie %1 n'est pas accessible en écriture. + message box content + La categoría %1 no se puede acceder en escritura. + + + + L'élément %1 n'est pas accessible en écriture. + message box content + El elemento %1 no se puede acceder en escritura. + + + + Erreur + message box title + Error + + + + LineEditor + + + Fin 1 + Fin 1 + + + + Fin 2 + Fin 2 + + + + abscisse point 1 + abscisa punto 1 + + + + ordonnée point 1 + ordenada punto 1 + + + + abscisse point 2 + abscisa punto 2 + + + + ordonnée point 2 + ordenada punto 2 + + + + type fin 1 + tipo fin 1 + + + + longueur fin 1 + longitud fin 1 + + + + type fin 2 + tipo fin 2 + + + + longueur fin 2 + longitud fin 2 + + + + Normale + type of the 1st end of a line + Normal + + + + Flèche simple + type of the 1st end of a line + Flecha simple + + + + Flèche triangulaire + type of the 1st end of a line + Flecha triangular + + + + Cercle + type of the 1st end of a line + Círculo + + + + Carré + type of the 1st end of a line + Cuadrado + + + + Normale + type of the 2nd end of a line + Normal + + + + Flèche simple + type of the 2nd end of a line + Flecha simple + + + + Flèche triangulaire + type of the 2nd end of a line + Flecha triangular + + + + Cercle + type of the 2nd end of a line + Círculo + + + + Carré + type of the 2nd end of a line + Cuadrado + + + + NamesListWidget + + + Langue + Idioma + + + + Nom + Nombre + + + + Ajouter une ligne + Añadir una línea + + + + Il doit y avoir au moins un nom. + message box title + Se necesita al menos un nombre. + + + + Vous devez entrer au moins un nom. + message box content + Tiene que escribir al menos un nombre. + + + + NewDiagramPage + + + Nouveau schéma + configuration page title + Nuevo esquema + + + + NewElementWizard + + + &Suivant > + &Siguiente > + + + + nouvel_element + nuevo_elemento + + + + Vous n'êtes pas obligé de préciser l'extension *.elmt. Elle sera ajoutée automatiquement. + No es obligatorio dar la extensión *.elmt. Se va agregar esta extensión automáticamente. + + + + Créer un nouvel élément : Assistant + window title + Crear un nuevo elemento: Asistente + + + + Étape 1/5 : Catégorie parente + wizard page title + Etapa 1/5: Caegoría pariente + + + + Sélectionnez une catégorie dans laquelle enregistrer le nouvel élément. + wizard page subtitle + Selecciona una categoría donde grabar el nuevo elemento. + + + + Étape 2/5 : Nom du fichier + wizard page title + Etapa 2/5: Nombre del archivo + + + + Indiquez le nom du fichier dans lequel enregistrer le nouvel élément. + wizard page subtitle + Escribe el nombre del archivo donde grabar el nuevo elemento. + + + + Étape 3/5 : Noms de l'élément + wizard page title + Etapa 3/5: Nombres del elemento + + + + Indiquez le ou les noms de l'élément. + wizard page subtitle + Escribe el nombre o los nombres del elemento. + + + + Nom du nouvel élément + default name when creating a new element + Nombre del nuevo elemento + + + + Étape 4/5 : Dimensions et point de saisie + wizard page title + Etapa 4/5: Dimensiones y hotspot + + + + Saisissez les dimensions du nouvel élément ainsi que la position du hotspot (point de saisie de l'élément à la souris) en considérant que l'élément est dans son orientation par défaut. + wizard page subtitle + Escribe las dimensiones del nuevo elemento y la posición del hotspot (punto de referencia de la imagen cuando arrastan con el ratón), suponiendo que el elemento es un su orientación por defecto. + + + + Étape 5/5 : Orientations + wizard page title + Etapa 5/5: Orientaciones + + + + Indiquez les orientations possibles pour le nouvel élément. + wizard page subtitle + De las oientaciones posibles del nuevo elemento. + + + + Erreur + message box title + Error + + + + Vous devez sélectionner une catégorie. + message box content + Tiene que seleccionar una categoría. + + + + Vous devez entrer un nom de fichier + message box content + Tiene que dar un nombre de archivo + + + + Merci de ne pas utiliser les caractères suivants : \ / : * ? " < > | + message box content + Por favor no uses los caracteres siguientes: \ / : * ? " < > | + + + + OrientationSetWidget + + + Possible + Posible + + + + Impossible + Imposible + + + + Nord : + Norte: + + + + Est : + Este: + + + + Sud : + Sur: + + + + Ouest : + Oeste: + + + + Par défaut + Prédeterminado + + + + PolygonEditor + + + Polygone fermé + Polígono cerrado + + + + x + x + + + + y + y + + + + Points du polygone : + Puntos del polígono: + + + + fermeture du polygone + cierre del polígono + + + + Erreur + message box title + Error + + + + Le polygone doit comporter au moins deux points. + message box content + El polígono tiene que implicar al menos dos puntos. + + + + ProjectView + + + Ce projet ne contient aucun schéma + No hay ningun esquema en este proyecto + + + + Titre du projet : + Título del proyecto: + + + + Supprimer les éléments inutilisés dans le projet + Eliminar los elementos no usados en el proyecto + + + + Supprimer les catégories vides + Eliminar categorías vacias + + + + Enregistrer le schéma en cours ? + message box title + ¿Grabar el esquema corriente? + + + + Voulez-vous enregistrer le schéma %1 ? + message box content - %1 is a diagram title + ¿Quiere grabar el esquema %1? + + + + Enregistrer le nouveau schéma ? + message box title + ¿Grabar el nuevo esquema? + + + + Ce schéma a été ajouté mais n'a été ni modifié ni enregistré. Voulez-vous le conserver ? + message box content + Se agregó el esquema, pero no fue editado tampoco guardado. ¿Quiere mantenerlo? + + + + Supprimer le schéma ? + message box title + ¿Eliminar el esquema? + + + + Êtes-vous sûr de vouloir supprimer ce schéma du projet ? Ce changement est irréversible. + message box content + ¿Estás seguro de eliminar el esquema del proyecto? Este cambio es irreversible. + + + + Propriétés du projet + window title + Propiedades del proyecto + + + + Projet en lecture seule + message box title + Proyecto en sólo lectura + + + + Ce projet est en lecture seule. Il n'est donc pas possible de le nettoyer. + message box content + Este proyecto es en sólo lectura. Por eso es imposible limpiarlo. + + + + Nettoyer le projet + window title + Limpiar el proyecto + + + + Enregistrer sous + dialog title + Guardar como + + + + Schéma QElectroTech (*.qet) + filetypes allowed when saving a diagram file + Esquema QElectroTech (*.qet) + + + + Projet + window title for a project-less ProjectView + Proyecto + + + + Enregistrer le projet en cours ? + message box title + ¿Guardar el proyecto corriente? + + + + Voulez-vous enregistrer le projet ? + message box content + ¿Quiere guardar el proyecto? + + + + projet + string used to generate a filename + proyecto + + + + Propriétés à utiliser lors de l'ajout d'un nouveau schéma au projet : + Propiedades a utilizar al añadir un nuevo esquema al proyecto: + + + + QETApp + + + &Quitter + &Salir + + + + &Masquer + &Esconder + + + + &Restaurer + &Restaurar + + + + &Masquer tous les éditeurs de schéma + &Esconder todos los editores de esquema + + + + &Restaurer tous les éditeurs de schéma + &Restaurar todos los editores de esquema + + + + &Masquer tous les éditeurs d'élément + &Esconder todos los editores de elementos + + + + &Restaurer tous les éditeurs d'élément + &Restaurar todos los editores de elementos + + + + &Nouvel éditeur de schéma + &Nuevo editor de esquema + + + + &Nouvel éditeur d'élément + &Nuevo editor de elemento + + + + Ferme l'application QElectroTech + Cierre el programa QElectroTech + + + + Réduire QElectroTech dans le systray + Minimizar QElectroTech en el systray + + + + Restaurer QElectroTech + Restaurar QElectroTech + + + + Éditeurs de schémas + Editores de esquemas + + + + Éditeurs d'élément + Editores de elementos + + + + Usage : + Uso: + + + + [options] [fichier]... + + + [opciones][archivo]... + + + + + + QElectroTech, une application de réalisation de schémas électriques. + +Options disponibles : + --help Afficher l'aide sur les options + -v, --version Afficher la version + --license Afficher la licence + + QElectroTech, un programa para hacer esquemas de electricidad. + +Opciones disponibles: + --help Mostrar la ayuda sobre las opciones + -v, --version Mostrar la versión + --license Mostrar la licencia + + + + + --common-elements-dir=DIR Definir le dossier de la collection d'elements + + --common-elements-dir=DIR Definir la carpeta de la colección de elementos + + + + + --config-dir=DIR Definir le dossier de configuration + + --config-dir=DIR Definir la carpeta de configuración + + + + + --lang-dir=DIR Definir le dossier contenant les fichiers de langue + + --lang-dir=DIR Definir la carpeta con los archivos de idioma + + + + + Chargement... Éditeur de schémas + splash screen caption + Cargando...Editor de esquemas + + + + Chargement... Ouverture des fichiers + splash screen caption + Cargando....Abriendo archivos + + + + Chargement... + splash screen caption + Cargando... + + + + Chargement... icône du systray + splash screen caption + Cargando icono del systray + + + + QElectroTech + systray menu title + QElectroTech + + + + QElectroTech + systray icon tooltip + QElectroTech + + + + QETDiagramEditor + + + Aucune modification + Ninguna modificación + + + + &Nouveau + &Nuevo + + + + &Ouvrir + &Abrir + + + + &Fermer + &Cerrar + + + + &Enregistrer + &Guardar + + + + Enregistrer sous + Guardar como + + + + &Importer + &Importar + + + + E&xporter + E&xportar + + + + Imprimer + Imprimir + + + + &Quitter + &Salir + + + + Annuler + Deshacer + + + + Refaire + Rehacer + + + + Co&uper + C&ortar + + + + Cop&ier + &Copiar + + + + C&oller + &Pegar + + + + Tout sélectionner + Seleccionar todo + + + + Désélectionner tout + Deseleccionar todo + + + + Inverser la sélection + Invertir selección + + + + Supprimer + Eliminar + + + + Pivoter + Rotar + + + + Propriétés du conducteur + Propiedades del conductor + + + + Réinitialiser les conducteurs + Reinicializar los conductores + + + + Conducteurs par défaut + Predeterminados conductores + + + + Propriétés du schéma + Propiedades del esquema + + + + Ajouter un champ de texte + Añadir un campo de texto + + + + Ajouter une colonne + Añadir una columna + + + + Enlever une colonne + Retirar una columna + + + + Ajouter une ligne + Añadir una línea + + + + Enlever une ligne + Retirar una línea + + + + Propriétés du projet + Propiedades del proyecto + + + + Ajouter un schéma + Añadir un esquema + + + + Supprimer le schéma + Eliminar el esquema + + + + Zoom avant + Ampliar + + + + Zoom arrière + Reducir + + + + Zoom adapté + Ajustar + + + + Pas de zoom + Tamaño actual + + + + en utilisant des onglets + utilizando pestañas + + + + en utilisant des fenêtres + utilizando ventanas + + + + Mode Selection + Modo selección + + + + Mode Visualisation + Modo visualización + + + + Passer en &mode plein écran + Entrar en el modo &de pantalla completa + + + + Sortir du &mode plein écran + Salir del modo &de pantalla completa + + + + &Configurer QElectroTech + &Configurar QElectroTech + + + + &Mosaïque + &Mosaico + + + + &Cascade + &Cascada + + + + Fenêtre suivante + Ventana siguiente + + + + Fenêtre précédente + Ventana anterior + + + + À &propos de QElectroTech + &Acerca de QElectroTech + + + + À propos de &Qt + Acerca de &Qt + + + + Ctrl+Shift+I + Ctrl+Shift+I + + + + Ctrl+Shift+X + Ctrl+Shift+X + + + + Ctrl+Q + Ctrl+Q + + + + Ctrl+Shift+A + Ctrl+Shift+A + + + + Ctrl+I + Ctrl+I + + + + Suppr + Supr + + + + Ctrl+R + Ctrl+R + + + + Ctrl+J + Ctrl+J + + + + Ctrl+K + Ctrl+K + + + + Ctrl+L + Ctrl+L + + + + Ctrl+D + Ctrl+D + + + + Ctrl+T + Ctrl+T + + + + Ctrl+9 + Ctrl+9 + + + + Ctrl+0 + Ctrl+0 + + + + Ctrl+Shift+F + Ctrl+Shift+F + + + + &Fichier + &Archivo + + + + &Édition + &Editar + + + + &Projet + &Proyecto + + + + Afficha&ge + &Ver + + + + &Configuration + &Preferencias + + + + Fe&nêtres + Venta&nas + + + + &Aide + A&yuda + + + + Afficher + Mostrar + + + + Affiche ou non la barre d'outils principale + Mostrar o esconder la barra de herramientas principal + + + + Affiche ou non la barre d'outils Affichage + Mostrar o esconder la barra de herramientas "Mostrar" + + + + Affiche ou non la barre d'outils Schéma + Mostrar o esconder la barra de herramientas "Esquema" + + + + Affiche ou non le panel d'appareils + Mostrar o esconder el panel de aparatos + + + + Affiche ou non la liste des modifications + Mostrar o esconder la lista de ediciones + + + + Afficher les projets + Mostrar proyectos + + + + Outils + Herramientas + + + + Affichage + Ver + + + + Schéma + Esquema + + + + Ouvrir un fichier + Abrir un archivo + + + + Schémas QElectroTech (*.qet);;Fichiers XML (*.xml);;Tous les fichiers (*) + Esquemas QElectroTech (*.qet);;Archivos XML (*.xml);;Todos los archivos (*) + + + + Impossible d'ouvrir le fichier + Imposible de abrir el archivo + + + + Il semblerait que le fichier que vous essayez d'ouvrir ne soit pas accessible en lecture. Il est donc impossible de l'ouvrir. Veuillez vérifier les permissions du fichier. + Parece que el archivo que intente de abrir no se puede acceder en lectura. Por eso es imposible abrirlo. Por favor compruebe los permisos del archivo. + + + + Ouverture du projet en lecture seule + Abriendo el proyecto en sólo lectura + + + + Il semblerait que le projet que vous essayez d'ouvrir ne soit pas accessible en écriture. Il sera donc ouvert en lecture seule. + Parece que el proyecto que intente de abrir no se puede acceder en escritura. Por eso se va abrirlo en sólo lectura. + + + + Nettoyer le projet + Limpiar el proyecto + + + + Échec de l'ouverture du projet + message box title + Fracaso al abrir el proyecto + + + + Il semblerait que le fichier %1 ne soit pas un fichier projet QElectroTech. Il ne peut donc être ouvert. + message box content + Parece que el archivo %1 no es un archivo del proyecto QElectroTech. Por eso no se puede abrirlo. + + + + QElectroTech + window title + QElectroTech + + + + QElectroTech + status bar message + QElectroTech + + + + Panel d'éléments + dock title + Panel de elementos + + + + Annulations + dock title + Anulaciones + + + + Crée un nouveau schéma + status bar tip + Crea un nuevo esquema + + + + Ouvre un schéma existant + status bar tip + Abrir un esquema que existe + + + + Ferme le schéma courant + status bar tip + Cierre el esquema corriente + + + + Enregistre le schéma courant + status bar tip + Guarda el esquema corriente + + + + Enregistre le schéma courant avec un autre nom de fichier + status bar tip + Guarda el esquema corriente con otro nombre de archivo + + + + Importe un schéma dans le schéma courant + status bar tip + Importa un esquema en el esquema corriente + + + + Exporte le schéma courant dans un autre format + status bar tip + Exporta el esquema corriente a otro formato + + + + Imprime le schéma courant + status bar tip + Imprime el esquema corriente + + + + Ferme l'application QElectroTech + status bar tip + Cierre el programa QElectroTech + + + + Annule l'action précédente + status bar tip + Deshacer la última acción + + + + Restaure l'action annulée + status bar tip + Restaura la última acción deshecha + + + + Transfère les éléments sélectionnés dans le presse-papier + status bar tip + Trasladar los elementos seleccionados en el portapapeles + + + + Copie les éléments sélectionnés dans le presse-papier + status bar tip + Copiar los elementos seleccionadas en el portapeles + + + + Place les éléments du presse-papier sur le schéma + status bar tip + Poner los elementos del portapeles en el esquema + + + + Sélectionne tous les éléments du schéma + status bar tip + Seleccionar todos los elementos del esquema + + + + Désélectionne tous les éléments du schéma + status bar tip + Deseleccionar todos los elementos del esquema + + + + Désélectionne les éléments sélectionnés et sélectionne les éléments non sélectionnés + status bar tip + Deseleccionar los elementos ya seleccionados y seleccionar los elementos aún no seleccionados + + + + Enlève les éléments sélectionnés du schéma + status bar tip + Quitar los elementos seleccionados del essquema + + + + Pivote les éléments sélectionnés + status bar tip + Rotar los elementos seleccionados + + + + Édite les propriétés du conducteur sélectionné + status bar tip + Editar las propriedades del conductor seleccionado + + + + Recalcule les chemins des conducteurs sans tenir compte des modifications + status bar tip + Calcular de nuevo los caminos de los conductores sin tenir cuenta de las ediciones + + + + Spécifie les propriétés par défaut des conducteurs + status bar tip + Dar las propriedades por defecto de conductores + + + + Édite les informations affichées par le cartouche + status bar tip + Editar las informaciones mostradas en el cartucho + + + + Ajoute une colonne au schéma + status bar tip + Agregar una columna al esquema + + + + Enlève une colonne au schéma + status bar tip + Eliminar una columna del esquema + + + + Agrandit le schéma en hauteur + status bar tip + Ampliar la altura del esquema + + + + Rétrécit le schéma en hauteur + status bar tip + Reducir la altura del esquema + + + + Agrandit le schéma + status bar tip + Ampliar el esquema + + + + Rétrécit le schéma + status bar tip + Reducir el esquema + + + + Adapte la taille du schéma afin qu'il soit entièrement visible + status bar tip + Adaptar el tamaño del esquema para que se pueda ver todo el esquema + + + + Restaure le zoom par défaut + status bar tip + Restaurar el zoom por defecto + + + + Présente les différents projets ouverts dans des sous-fenêtres + status bar tip + Mostrar los proyectos abiertos en sub ventanas + + + + Présente les différents projets ouverts des onglets + status bar tip + Mostrar los proyectos abiertos en pestañas + + + + Permet de sélectionner les éléments + status bar tip + + + + + Permet de visualiser le schéma sans pouvoir le modifier + status bar tip + Permite mostrar el esquema sin permitir editarlo + + + + Affiche QElectroTech en mode plein écran + status bar tip + Mostrar QElectroTech en pantalla completa + + + + Affiche QElectroTech en mode fenêtré + status bar tip + Mostrar QElectroTech en modo ventana + + + + Permet de régler différents paramètres de QElectroTech + status bar tip + Permite arreglar los párametros de QElectroTech + + + + Dispose les fenêtres en mosaïque + status bar tip + Poner las ventanas en mosaico + + + + Dispose les fenêtres en cascade + status bar tip + Poner las ventanas en cascada + + + + Active la fenêtre suivante + status bar tip + Activar la ventana siguiente + + + + Active la fenêtre précédente + status bar tip + Activar la ventana previa + + + + Affiche des informations sur QElectroTech + status bar tip + Mostrar las informaciones sobre QElectroTech + + + + Affiche des informations sur la bibliothèque Qt + status bar tip + Mostrar las informaciones sobre la biblioteca Qt + + + + Active la fenêtre %1 + Activa la ventana %1 + + + + &Enregistrer tous les schémas + + + + + Enregistre tous les schémas du projet courant + status bar tip + + + + + QETElementEditor + + + &Nouveau + &Nuevo + + + + &Ouvrir + &Abrir + + + + &Ouvrir depuis un fichier + &Abrir desde un archivo + + + + &Enregistrer + &Guardar + + + + Enregistrer sous + Guardar como + + + + Enregistrer dans un fichier + Guardar hacia un archivo + + + + Recharger + Recargar + + + + &Quitter + &Salir + + + + Tout sélectionner + Seleccionar todo + + + + Désélectionner tout + Deseleccionar todo + + + + Inverser la sélection + Invertir selección + + + + &Supprimer + &Eliminar + + + + Zoom avant + Ampliar + + + + Zoom arrière + Reducir + + + + Zoom adapté + Ajustar + + + + Pas de zoom + No zoom + + + + Éditer la taille et le point de saisie + Editar el tamaño o el hotspot + + + + Éditer les noms + Editar los nombres + + + + Éditer les orientations + Editar las orientaciones + + + + Rapprocher + Acercar + + + + Éloigner + Alejar + + + + Envoyer au fond + Poner en el fondo + + + + Amener au premier plan + Poner en el primer plano + + + + Déplacer un objet + Mover un objeto + + + + Ajouter une ligne + Agregar una línea + + + + Ajouter une ellipse + Agregar una elipse + + + + Ajouter un cercle + Agregar un círculo + + + + Ajouter un polygone + Agregar un polígono + + + + Ajouter du texte + Agregar texto + + + + Ajouter un arc de cercle + Agregar un arco de círculo + + + + Ajouter une borne + Agregar un conector + + + + Ajouter un champ de texte + Añadir un campo de texto + + + + Annuler + Deshacer + + + + Refaire + Rehacer + + + + Ctrl+Shift+O + Ctrl+Shift+O + + + + Ctrl+Shift+S + Ctrl+Shift+S + + + + Ctrl+Q + Ctrl+Q + + + + Ctrl+Shift+A + Ctrl+Shift+A + + + + Ctrl+I + Ctrl+I + + + + Suppr + Supr + + + + Ctrl+9 + Ctrl+9 + + + + Ctrl+0 + Ctrl+0 + + + + Ctrl+E + Ctrl+E + + + + Ctrl+R + Ctrl+R + + + + Ctrl+T + Ctrl+T + + + + Ctrl+Shift+Up + Ctrl+Shift+Árriba + + + + Ctrl+Shift+Down + Ctrl+Shift+Abajo + + + + Ctrl+Shift+End + Ctrl+Shift+End + + + + Ctrl+Shift+Home + Ctrl+Shift+Home + + + + Outils + Herramientas + + + + Affichage + Ver + + + + Fichier + Archivo + + + + Édition + Editar + + + + Aide + Ayuda + + + + Afficher + Mostrar + + + + Aucune modification + Ninguna modificación + + + + QElectroTech - Éditeur d'élément + window title + QElectroTech - Editor de elemento + + + + Parties + toolbar title + Partes + + + + Outils + toolbar title + Herramientas + + + + Affichage + toolbar title + Ver + + + + Élément + toolbar title + Elemento + + + + Profondeur + toolbar title + Profundidad + + + + [Modifié] + window title tag + [modificado] + + + + [lecture seule] + window title tag + [sólo lectura] + + + + Informations + dock title + Informaciones + + + + Annulations + dock title + Anulaciones + + + + Parties + dock title + Partes + + + + Éditeur d'éléments + status bar message + Editor de elementos + + + + %n partie(s) sélectionnée(s). + + %n parte selectionada. + %n partes selectionadas. + + + + + Le fichier %1 n'existe pas. + message box content + El archivo %1 no existe. + + + + Impossible d'ouvrir le fichier %1. + message box content + Imposible de abrir el archivo %1. + + + + Ce fichier n'est pas un document XML valide + message box content + Este archivo no es un documento XML válido + + + + Erreur + toolbar title + Error + + + + Édition en lecture seule + message box title + Edición en lectura sola + + + + Vous n'avez pas les privilèges nécessaires pour modifier cet élement. Il sera donc ouvert en lecture seule. + message box content + No tiene los derechos necesarios para editar este elemento. Por eso se va abrirlo en sólo lectura. + + + + Erreur + message box title + Error + + + + Impossible d'écrire dans ce fichier + message box content + Imposible de escribir en el archivo + + + + Impossible d'atteindre l'élément + message box content + Imposible de alcanzar el elemento + + + + Impossible d'enregistrer l'élément + message box content + Imposible de grabar el elemento + + + + Ouvrir un fichier + dialog title + Abrir un archivo + + + + Éléments QElectroTech (*.elmt);;Fichiers XML (*.xml);;Tous les fichiers (*) + filetypes allowed when opening an element file + Elementos QElectroTech (*.elmt);;Archivos XML (*.xml);;Todos los archivos (*) + + + + Recharger l'élément + dialog title + Recargar el elemento + + + + Vous avez efffectué des modifications sur cet élément. Si vous le rechargez, ces modifications seront perdues. Voulez-vous vraiment recharger l'élément ? + dialog content + Editó este elemento. Si recarga las edicciones seran perdidas. ¿Está segura de recargar el elemento? + + + + Enregistrer sous + dialog title + Guardar como + + + + Éléments QElectroTech (*.elmt) + filetypes allowed when saving an element file + Elementos QElectroTech (*.elmt) + + + + Enregistrer l'élément en cours ? + dialog title + ¿Grabar el elemento corriente? + + + + Voulez-vous enregistrer l'élément %1 ? + dialog content - %1 is an element name + ¿Quiere grabar el elemento %1? + + + + Élément inexistant. + message box title + Elemento inexistente. + + + + L'élément n'existe pas. + message box content + El elemento no existe. + + + + Le chemin virtuel choisi ne correspond pas à un élément. + message box content + El camino virtual eligido no corresponde a un elemento. + + + + Maintenez la touche Shift enfoncée pour effectuer plusieurs ajouts d'affilée + + + + + Utilisez le bouton droit de la souris pour poser le dernier point du polygone + Usar el botón de derecho del ratón para colocar el último punto del polígono + + + + Dimensions de l'élément + messagebox title + Tamaños del elemento + + + + Attention : certaines parties graphiques (textes, cercles, lignes...) semblent déborder du cadre de l'élément. Cela risque de générer des bugs graphiques lors de leur manipulation sur un schéma. Vous pouvez corriger cela soit en déplaçant ces parties, soit en vous rendant dans Édition > Éditer la taille et le point de saisie. + messagebox content + + + + + Co&uper + C&ortar + + + + Cop&ier + &Copiar + + + + C&oller + &Pegar + + + + C&oller dans la zone... + + + + + Ctrl+Shift+V + Ctrl+Shift+V + + + + Ajouter un rectangle + Añadir un rectángulo + + + + QETPrintPreviewDialog + + + QElectroTech : Aperçu avant impression + QElectroTech: Vista preliminar + + + + Schémas à imprimer : + + + + + Cacher la liste des schémas + Ocultar la liste de esquemas + + + + Cacher les options d'impression + Ocultar las opciones de impresión + + + + Ajuster la largeur + + + + + Ajuster la page + + + + + Zoom arrière + Reducir + + + + Zoom avant + Ampliar + + + + Paysage + + + + + Portrait + + + + + Première page + Primera página + + + + Page précédente + Página anterior + + + + Page suivante + Página siguiente + + + + Dernière page + Última página + + + + Afficher une seule page + Mostrar sólo una página + + + + Afficher deux pages + Mostrar dos páginas + + + + Afficher un aperçu de toutes les pages + + + + + Mise en page + Configurar página + + + + Mise en page (non disponible sous Windows pour l'impression PDF/PS) + + + + + Options d'impression + Opciones de impresión + + + + Utiliser toute la feuille + Usar la página entera + + + + Si cette option est cochée, les marges de la feuille seront ignorées et toute sa surface sera utilisée pour l'impression. Cela peut ne pas être supporté par votre imprimante. + Si esta opción está seleccionada, se imprimirá sobre la superficie completa de la hoja, ignorando los márgenes. Esta opción puede no estar soportada por su impresora. + + + + Adapter le schéma à la page + Ajustar el diagrama a la página + + + + Si cette option est cochée, le schéma sera agrandi ou rétréci de façon à remplir toute la surface imprimable d'une et une seule page. + Si esta opción está seleccionada, el diagrama será reducido o expandido para ajustarse a la superficie imprimible de la hoja. + + + + Imprimer + Imprimir + + + + Afficher la liste des schémas + Mostrar la liste de esquemas + + + + Afficher les options d'impression + Mostrar las opciones de impresión + + + + %1 % + %1 % + + + + QETProject + + + Impossible de créer la catégorie pour l'intégration des éléments + Imposible de crear la categoría para agregar los elementos + + + + Impossible d'accéder à l'élément a intégrer + Imposible de tener aceso al elemento que se necesita agregar + + + + Un problème s'est produit pendant la copie de la catégorie %1 + Un problema ocurió durante la copia de la categoría %1 + + + + Un problème s'est produit pendant la copie de l'élément %1 + Un problema ocurió durante la copia del elemento %1 + + + + Avertissement + message box title + Advertencia + + + + Ce document semble avoir été enregistré avec une version ultérieure de QElectroTech. Il est possible que l'ouverture de tout ou partie de ce document échoue. + message box content + Parece que este documento fue guardado con una version más nueva de QElectrotech. Abrir una parte del documento o el documento entero podría fracasar. + + + + Projet « %1 » + displayed title for a ProjectView - %1 is the project title + Proyecto «%1» + + + + Projet %1 + displayed title for a title-less project - %1 is the file name + Proyecto %1 + + + + Projet sans titre + displayed title for a project-less, file-less project + Proyecto sín título + + + + %1 [lecture seule] + displayed title for a read-only project - %1 is a displayable title + %1 [sólo lectura] + + + + QFileNameEdit + + + Les caractères autorisés sont : + - les chiffres [0-9] + - les minuscules [a-z] + - le tiret [-], l'underscore [_] et le point [.] + + tooltip content when editing a filename + Los caracteres autorizados son: + - las cifras [0-9] + - las minúsculas [a-z] + - el guión [-], el guión bajo [_] y el punto [.] + + + + + QObject + + + Avertissement : l'élément a été enregistré avec une version ultérieure de QElectroTech. + Aviso: el elemento fue guardado con una versión mas reciente de QElectroTech. + + + + Le fichier texte contenant la licence GNU/GPL est introuvable - bon bah de toute façon, vous la connaissez par coeur non ? + No se puede encontrar el archivo donde se ubica la licencia GNU/GPL. Pues, de todo modo, supongo que la conoce de memoria, ¿no? + + + + Le fichier texte contenant la licence GNU/GPL existe mais n'a pas pu être ouvert - bon bah de toute façon, vous la connaissez par coeur non ? + El archivo donde se ubica la licencia GNU/GPL existe pero no se puede abrir. Pues, de todo modo, supongo que la conoce de memoria, ¿no? + + + + ajouter 1 %1 + undo caption - %1 is an element name + añadir 1 %1 + + + + Ajouter un champ de texte + undo caption + Añadir un campo de texto + + + + ajouter un conducteur + undo caption + añadir un conductor + + + + supprimer %1 + undo caption - %1 is a sentence listing the removed content + eliminar %1 + + + + coller %1 + undo caption - %1 is a sentence listing the content to paste + pegar %1 + + + + couper %1 + undo caption - %1 is a sentence listing the content to cut + cortar %1 + + + + déplacer %1 + undo caption - %1 is a sentence listing the moved content + mover %1 + + + + modifier le texte + undo caption + modificar el texto + + + + pivoter %1 + undo caption - %1 is a sentence listing the rotated content + rotar %1 + + + + modifier un conducteur + undo caption + modificar un conductor + + + + Réinitialiser %1 + undo caption - %1 is a sentence listing the reset content + Reinicializar %1 + + + + modifier le cartouche + undo caption + editar el cartucho + + + + modifier les dimensions du schéma + undo caption + editar las dimensiones del esquema + + + + modifier les propriétés d'un conducteur + undo caption + modificar las propiedades de un conuctor + + + + suppression + undo caption + eliminar + + + + déplacement + undo caption + mover + + + + ajout %1 + undo caption + insertar %1 + + + + modification %1 + undo caption + edición %1 + + + + modification points polygone + undo caption + edición puntos de polígono + + + + modification dimensions/hotspot + undo caption + edicion de dimensiones/hotspot + + + + modification noms + undo caption + edición de nombres + + + + modification orientations + undo caption + edición de orientaciones + + + + amener au premier plan + undo caption + poner en el primer plano + + + + rapprocher + undo caption + acercar + + + + éloigner + undo caption + alejar + + + + envoyer au fond + undo caption + poner en el fondo + + + + modification connexions internes + undo caption + edición de conecciones internas + + + + arc + element part name + arco + + + + cercle + element part name + círculo + + + + ellipse + element part name + elipse + + + + ligne + element part name + linea + + + + polygone + element part name + polígono + + + + borne + element part name + conector + + + + T + default text when adding a text in the element editor + T + + + + texte + element part name + texto + + + + _ + default text when adding a textfield in the element editor + _ + + + + champ de texte + element part name + campo de texto + + + + %n élément(s) + part of a sentence listing the content of a diagram + + %n elemento + %n elementos + + + + + , + separator between elements and conductors in a sentence listing the content of a diagram + , + + + + et + separator between elements and conductors (or texts) in a sentence listing the content of a diagram + y + + + + %n conducteur(s) + part of a sentence listing the content of a diagram + + %n conductor + %n conductores + + + + + et + separator between conductors and texts in a sentence listing the content of a diagram + y + + + + %n champ(s) de texte + part of a sentence listing the content of a diagram + + %n campo de texto + %n campos de texto + + + + + Borne + tooltip + Conector + + + + coller + pegar + + + + couper des parties + undo caption + cortar partes + + + + rectangle + element part name + rectángulo + + + + Schéma sans titre + Esquema sin título + + + + schema + esquema + + + + Conserver les proportions + Mantener proporciones + + + + Réinitialiser les dimensions + + + + + Aperçu + Vista previa + + + + RecentFiles + + + &Récemment ouvert(s) + &Recientemente abierto(s) + + + + RectangleEditor + + + Coin supérieur gauche : + Esquina superior izquierda: + + + + Dimensions : + Dimensiones: + + + + Largeur : + Anchura: + + + + Hauteur : + Altura: + + + + abscisse + abscisa + + + + ordonnée + ordenada + + + + largeur + anchura + + + + hauteur + altura + + + + StyleEditor + + + Antialiasing + Antialiasing + + + + Trait : + rasgo + + + + Couleur : + Color: + + + + Style : + Estilo: + + + + Épaisseur : + Grosor: + + + + Remplissage : + Relleno: + + + + Noir + element part color + Negro + + + + Blanc + element part color + Blanco + + + + Normal + element part line style + Normal + + + + Pointillé + element part line style + Línea de puntos + + + + Nulle + element part weight + Ninguna + + + + Fine + element part weight + Delgada + + + + Normale + element part weight + Normal + + + + Aucun + element part filling + Ninguno + + + + Noir + element part filling + Negro + + + + Blanc + element part filling + Blanco + + + + TerminalEditor + + + Nord + Norte + + + + Est + Este + + + + Sud + Sur + + + + Ouest + Oeste + + + + Position : + Posición: + + + + x : + x: + + + + y : + y: + + + + Orientation : + Orientación: + + + + abscisse + abscisa + + + + ordonnée + ordenada + + + + orientation + orientación + + + + TextEditor + + + Position : + Posición: + + + + x : + x: + + + + y : + y: + + + + Taille : + Tamaño: + + + + Texte : + Texto: + + + + abscisse + abscisa + + + + ordonnée + ordenada + + + + taille + tamaño + + + + contenu + contenido + + + + TextFieldEditor + + + Maintenir horizontal malgré + les rotations de l'élément + Mantener la horizontalidad a pesar +de las rotaciones del elemento + + + + Position : + Posición: + + + + x : + x: + + + + y : + y: + + + + Taille : + Tamaño: + + + + Texte par défaut : + Texto predeterminado: + + + + abscisse + abscisa + + + + ordonnée + ordenada + + + + taille + tamaño + + + + propriété + propiedad + + + + contenu + contenido + + + diff --git a/lang/qet_fr.qm b/lang/qet_fr.qm new file mode 100644 index 0000000000000000000000000000000000000000..f09d636a403ffedb4388e30f28936804799f1b01 GIT binary patch literal 546 zcmaKoze>bF5XL7SdLoEd93pZ;JUq}Qg;-h%spY_4V|L&o$tENlxEB!g0W1VTtOTDx z3$;J+5iGP2w(=GHCLBTTPK*6Bv)|0`+u46qYoqU*tGCV0>&e~o`vDQT^sskL=AfvorouRL zL3aLrrlLl*1eI!4hK9==yeUk3+(!m)@k0j82w-*A!>`uMXl-LF1Dou7f%3VA^8?0i Bbr1jm literal 0 HcmV?d00001 diff --git a/lang/qet_fr.ts b/lang/qet_fr.ts new file mode 100644 index 000000000..d20ac6ad2 --- /dev/null +++ b/lang/qet_fr.ts @@ -0,0 +1,45 @@ + + + + + QETElementEditor + + + %n partie(s) sélectionnée(s). + + %n partie sélectionnée. + %n parties sélectionnées. + + + + + QObject + + + %n élément(s) + part of a sentence listing the content of a diagram + + %n élément + %n éléments + + + + + %n conducteur(s) + part of a sentence listing the content of a diagram + + %n conducteur + %n conducteurs + + + + + %n champ(s) de texte + part of a sentence listing the content of a diagram + + %n champ de texte + %n champs de texte + + + + diff --git a/lang/qt_es.qm b/lang/qt_es.qm new file mode 100644 index 0000000000000000000000000000000000000000..0fa1226633a8b6cba83fe73c68ce1ee153756056 GIT binary patch literal 117693 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 + + + AudioOutput + + + <html>The audio playback device <b>%1</b> does not work.<br/>Falling back to <b>%2</b>.</html> + + + + + <html>Switching to the audio playback device <b>%1</b><br/>which just became available and has higher preference.</html> + + + + + Revert back to device '%1' + + + + + PPDOptionsModel + + + Name + Nombre + + + + Value + Valor + + + + Phonon:: + + + Notifications + + + + + Music + + + + + Video + + + + + Communication + + + + + Games + + + + + Accessibility + + + + + Phonon::Gstreamer::Backend + + + Warning: You do not seem to have the package gstreamer0.10-plugins-good installed. + Some video features have been disabled. + + + + + Warning: You do not seem to have the base GStreamer plugins installed. + All audio and video support has been disabled + + + + + Phonon::Gstreamer::MediaObject + + + Cannot start playback. + +Check your Gstreamer installation and make sure you +have libgstreamer-plugins-base installed. + + + + + A required codec is missing. You need to install the following codec(s) to play this content: %0 + + + + + + + + Could not open media source. + + + + + Invalid source type. + + + + + Could not locate media source. + + + + + Could not open audio device. The device is already in use. + + + + + Could not decode media source. + + + + + Phonon::VolumeSlider + + + Volume: %1% + + + + + Use this slider to adjust the volume. The leftmost position is 0%, the rightmost is %1% + + + + + Q3Accel + + + %1, %2 not defined + La secuencia %1, %2 no está definida + + + + Ambiguous %1 not handled + Secuencia ambigua %1 no tratada + + + + Q3DataTable + + + True + Verdadero + + + + False + Falso + + + + Insert + Insertar + + + + Update + Actualizar + + + + Delete + Borrar + + + + Q3FileDialog + + + Copy or Move a File + Copiar o mover un fichero + + + + Read: %1 + Lectura: %1 + + + + Write: %1 + Escritura: %1 + + + + Cancel + Cancelar + + + + All Files (*) + Todos los ficheros (*) + + + + Name + Nombre + + + + Size + Tamaño + + + + Type + Tipo + + + + Date + Fecha + + + + Attributes + Atributos + + + + &OK + &Aceptar + + + + Look &in: + Buscar &en: + + + + File &name: + &Nombre de fichero: + + + + File &type: + &Tipo de fichero: + + + + Back + Precedente (histórico) + + + + One directory up + Ir al directorio superior + + + + Create New Folder + Crear una nueva carpeta + + + + List View + Vista de lista + + + + Detail View + Vista detallada + + + + Preview File Info + Información del fichero previsualizado + + + + Preview File Contents + Contenido del fichero previsualizado + + + + Read-write + Lectura-escritura + + + + Read-only + Sólo lectura + + + + Write-only + Sólo escritura + + + + Inaccessible + Inaccesible + + + + Symlink to File + Enlace simbólico a un fichero + + + + Symlink to Directory + Enlace simbólico a un directorio + + + + Symlink to Special + Enlace simbólico a un fichero especial + + + + File + Fichero + + + + Dir + Directorio + + + + Special + Fichero especial + + + + Open + Abrir + + + + Save As + Guardar como + + + + &Open + &Abrir + + + + &Save + &Guardar + + + + &Rename + Cambia&r de nombre + + + + &Delete + &Borrar + + + + R&eload + R&ecargar + + + + Sort by &Name + Ordenar por &nombre + + + + Sort by &Size + Ordenar por &tamaño + + + + Sort by &Date + Ordenar por &fecha + + + + &Unsorted + &Sin ordenar + + + + Sort + Ordenar + + + + Show &hidden files + Mostrar los fic&heros ocultos + + + + the file + el fichero + + + + the directory + el directorio + + + + the symlink + el enlace simbólico + + + + Delete %1 + Borrar %1 + + + + <qt>Are you sure you wish to delete %1 "%2"?</qt> + <qt>¿Seguro que desea borrar %1 «%2»?</qt> + + + + &Yes + &Sí + + + + &No + &No + + + + New Folder 1 + Nueva carpeta 1 + + + + New Folder + Nueva carpeta + + + + New Folder %1 + Nueva carpeta %1 + + + + Find Directory + Buscar en el directorio + + + + Directories + Directorios + + + + Directory: + Directorio: + + + + Error + Error + + + + %1 +File not found. +Check path and filename. + %1 +Fichero no encontrado. +Compruebe la ruta y el nombre del fichero. + + + + All Files (*.*) + Todos los ficheros (*.*) + + + + Open + Abrir + + + + Select a Directory + Seleccionar un directorio + + + + Q3LocalFs + + + Could not read directory +%1 + No fue posible leer el directorio +%1 + + + + Could not create directory +%1 + No fue posible crear el directorio +%1 + + + + Could not remove file or directory +%1 + No fue posible eliminar el fichero o directorio +%1 + + + + Could not rename +%1 +to +%2 + No fue posible cambiar el nombre +%1 +a +%2 + + + + Could not open +%1 + No fue posible abrir +%1 + + + + Could not write +%1 + No fue posible escribir +%1 + + + + Q3MainWindow + + + Line up + Alinear + + + + Customize... + Personalizar... + + + + Q3NetworkProtocol + + + Operation stopped by the user + Operación detenida por el usuario + + + + Q3ProgressDialog + + + Cancel + Cancelar + + + + Q3TabDialog + + + OK + Aceptar + + + + Apply + Aplicar + + + + Help + Ayuda + + + + Defaults + Valores por omisión + + + + Cancel + Cancelar + + + + Q3TextEdit + + + &Undo + &Deshacer + + + + &Redo + &Rehacer + + + + Cu&t + Cor&tar + + + + &Copy + &Copiar + + + + &Paste + &Pegar + + + + Clear + Limpiar + + + + Select All + Seleccionar todo + + + + Q3TitleBar + + + System + Sistema + + + + Restore up + Restaurar arriba + + + + Minimize + Minimizar + + + + Restore down + Restaurar abajo + + + + Maximize + Maximizar + + + + Close + Cerrar + + + + Contains commands to manipulate the window + Contiene órdenes para manipular la ventana + + + + Puts a minimized back to normal + Devuelve una ventana minimizada a su aspecto normal + + + + Moves the window out of the way + Aparta la ventana + + + + Puts a maximized window back to normal + Devuelve una ventana maximizada a su aspecto normal + + + + Makes the window full screen + Muestra la ventana en pantalla completa + + + + Closes the window + Cierra la ventana + + + + Displays the name of the window and contains controls to manipulate it + Muestra el nombre de la ventana y contiene controles para manipularla + + + + Q3ToolBar + + + More... + Más... + + + + Q3UrlOperator + + + The protocol `%1' is not supported + El protocolo «%1» no está contemplado + + + + The protocol `%1' does not support listing directories + El protocolo «%1» no permite listar los ficheros de un directorio + + + + The protocol `%1' does not support creating new directories + El protocolo «%1» no permite crear nuevos directorios + + + + The protocol `%1' does not support removing files or directories + El protocolo «%1» no permite eliminar ficheros o directorios + + + + The protocol `%1' does not support renaming files or directories + El protocolo «%1» no permite cambiar de nombre ficheros o directorios + + + + The protocol `%1' does not support getting files + El protocolo «%1» no permite recibir ficheros + + + + The protocol `%1' does not support putting files + El protocolo «%1» no permite enviar ficheros + + + + The protocol `%1' does not support copying or moving files or directories + El protocolo «%1» no permite copiar o mover ficheros o directorios + + + + (unknown) + (desconocido) + + + + Q3Wizard + + + &Cancel + &Cancelar + + + + < &Back + < &Anterior + + + + &Next > + Siguie&nte > + + + + &Finish + &Terminar + + + + &Help + &Ayuda + + + + QAbstractSocket + + + Host not found + Equipo no encontrado + + + + Connection refused + Conexión rechazada + + + + Socket operation timed out + Operación socket expirada + + + + Socket is not connected + El socket no está conectado + + + + QAbstractSpinBox + + + &Step up + &Aumentar + + + + Step &down + Re&ducir + + + + &Select All + &Seleccionar todo + + + + QApplication + + + Activate + Activar + + + + Executable '%1' requires Qt %2, found Qt %3. + El ejecutable «%1» requiere Qt %2 (se encontró Qt %3). + + + + Incompatible Qt Library Error + Error: biblioteca Qt incompatible + + + + QT_LAYOUT_DIRECTION + Translate this string to the string 'LTR' in left-to-right languages or to 'RTL' in right-to-left languages (such as Hebrew and Arabic) to get proper widget layout. + LTR + + + + Activates the program's main window + Activa la ventana principal del programa + + + + QAxSelect + + + Select ActiveX Control + Seleccionar un control ActiveX + + + + OK + Aceptar + + + + &Cancel + &Cancelar + + + + COM &Object: + &Objeto COM: + + + + QCheckBox + + + Uncheck + Desmarcar + + + + Check + Marcar + + + + Toggle + Conmutar + + + + QColorDialog + + + Hu&e: + &Tono: + + + + &Sat: + &Saturación: + + + + &Val: + &Valor: + + + + &Red: + &Rojo: + + + + &Green: + &Verde: + + + + Bl&ue: + Az&ul: + + + + A&lpha channel: + Canal a&lfa: + + + + &Basic colors + Colores &básicos + + + + &Custom colors + &Colores personalizados + + + + &Define Custom Colors >> + &Definir colores personalizados >> + + + + OK + Aceptar + + + + Cancel + Cancelar + + + + &Add to Custom Colors + &Añadir a los colores personalizados + + + + Select color + Seleccionar color + + + + QComboBox + + + Open + Abrir + + + + False + Falso + + + + True + Verdadero + + + + Close + Cerrar + + + + QCoreApplication + + + %1: permission denied + QSystemSemaphore + + + + + %1: already exists + QSystemSemaphore + + + + + %1: doesn't exists + QSystemSemaphore + + + + + %1: out of resources + QSystemSemaphore + + + + + %1: unknown error %2 + QSystemSemaphore + + + + + %1: key is empty + QSystemSemaphore + + + + + %1: unable to make key + QSystemSemaphore + + + + + %1: ftok failed + QSystemSemaphore + + + + + QDB2Driver + + + Unable to connect + Imposible establecer una conexión + + + + Unable to commit transaction + Incapaz de enviar la transacción + + + + Unable to rollback transaction + Incapaz de anular la transacción + + + + Unable to set autocommit + Incapaz de activar el envío automático + + + + QDB2Result + + + Unable to execute statement + Imposible ejecutar la instrucción + + + + Unable to prepare statement + Imposible preparar la instrucción + + + + Unable to bind variable + No es posible ligar la variable + + + + Unable to fetch record %1 + Imposible obtener el registro %1 + + + + Unable to fetch next + Imposible recuperar el siguiente + + + + Unable to fetch first + Imposible recuperar el primero + + + + QDateTimeEdit + + + AM + AM + + + + am + am + + + + PM + PM + + + + pm + pm + + + + QDial + + + QDial + QDial + + + + SpeedoMeter + Velocímetro + + + + SliderHandle + Asa del deslizador + + + + QDialog + + + What's This? + ¿Qué es esto? + + + + Done + Terminar + + + + QDialogButtonBox + + + OK + Aceptar + + + + Save + Guardar + + + + Open + Abrir + + + + Cancel + Cancelar + + + + Close + Cerrar + + + + Apply + Aplicar + + + + Reset + Reinicializar + + + + Help + Ayuda + + + + Don't Save + No guardar + + + + Discard + Descartar + + + + &Yes + &Sí + + + + Yes to &All + Sí a &todo + + + + &No + &No + + + + N&o to All + N&o a todo + + + + Save All + Guardar todo + + + + Abort + Interrumpir + + + + Retry + Reintentar + + + + Ignore + Ignorar + + + + Restore Defaults + Restaurar los valores predeterminados + + + + Close without Saving + Cerrar sin guardar + + + + &OK + &Aceptar + + + + QDirModel + + + Name + Nombre + + + + Size + Tamaño + + + + Kind + Match OS X Finder + + Clase + + + + Type + All other platforms + Tipo + + + + Date Modified + Última modificación + + + + Kind + Match OS X Finder + Clase + + + + QDockWidget + + + Close + Cerrar + + + + Dock + Anclada + + + + Float + Flotante + + + + QDoubleSpinBox + + + More + Más + + + + Less + Menos + + + + QErrorMessage + + + Debug Message: + Mensaje de depuración: + + + + Warning: + Aviso: + + + + Fatal Error: + Error fatal: + + + + &Show this message again + Mo&strar este mensaje de nuevo + + + + &OK + &Aceptar + + + + QFileDialog + + + All Files (*) + Todos los ficheros (*) + + + + Directories + Directorios + + + + &Open + &Abrir + + + + &Save + &Guardar + + + + Open + Abrir + + + + %1 already exists. +Do you want to replace it? + El fichero %1 ya existe. +¿Desea reemplazarlo? + + + + %1 +File not found. +Please verify the correct file name was given. + %1 +Fichero no encontrado. +Verifique que el nombre del fichero es correcto. + + + + My Computer + Mi equipo + + + + &Rename + Cambia&r de nombre + + + + &Delete + &Borrar + + + + Show &hidden files + Mostrar los fic&heros ocultos + + + + Back + Anterior (histórico) + + + + Parent Directory + Directorio superior + + + + List View + Vista de lista + + + + Detail View + Vista detallada + + + + Files of type: + Ficheros de tipo: + + + + Directory: + Directorio: + + + + +File not found. +Please verify the correct file name was given + +Fichero no encontrado. +Compruebe que el nombre del fichero es correcto + + + + %1 +Directory not found. +Please verify the correct directory name was given. + %1 +Directorio no encontrado. +Verique que el nombre del directorio es correcto. + + + + '%1' is write protected. +Do you want to delete it anyway? + «%1» está protegido contra escritura. +¿Desea borrarlo de todas formas? + + + + Are sure you want to delete '%1'? + ¿Seguro que desea borrar «%1»? + + + + Could not delete directory. + No fue posible borrar el directorio. + + + + All Files (*.*) + Todos los ficheros (*.*) + + + + Save As + Guardar como + + + + Drive + Unidad + + + + File + Fichero + + + + Unknown + Desconocido + + + + Find Directory + Buscar en el directorio + + + + Show + Mostrar + + + + Forward + Siguiente (histórico) + + + + New Folder + Nueva carpeta + + + + &New Folder + &Nueva carpeta + + + + &Choose + &Seleccionar + + + + Remove + Eliminar + + + + File &name: + &Nombre de fichero: + + + + Look in: + Ver en: + + + + Create New Folder + Crear una nueva carpeta + + + + QFileSystemModel + + + %1 TB + %1 TiB + + + + %1 GB + %1 GiB + + + + %1 MB + %1 MiB + + + + %1 KB + %1 KiB + + + + %1 bytes + %1 bytes + + + + Invalid filename + Nombre de fichero no válido + + + + <b>The name "%1" can not be used.</b><p>Try using another name, with fewer characters or no punctuations marks. + <b>No se puede utilizar el nombre «%1».</b><p>Intente usar otro nombre con menos caracteres o sin signos de puntuación. + + + + Name + Nombre + + + + Size + Tamaño + + + + Kind + Match OS X Finder + Clase + + + + Type + +All other platforms + Tipo + + + + Date Modified + Última modificación + + + + My Computer + Mi equipo + + + + Computer + Equipo + + + + Type + All other platforms + Tipo + + + + QFontDatabase + + + Normal + + + + + Bold + + + + + Demi Bold + + + + + Black + + + + + Demi + + + + + Light + + + + + Italic + + + + + Oblique + + + + + Any + + + + + Latin + + + + + Greek + + + + + Cyrillic + + + + + Armenian + + + + + Hebrew + + + + + Arabic + + + + + Syriac + + + + + Thaana + + + + + Devanagari + + + + + Bengali + + + + + Gurmukhi + + + + + Gujarati + + + + + Oriya + + + + + Tamil + + + + + Telugu + + + + + Kannada + + + + + Malayalam + + + + + Sinhala + + + + + Thai + + + + + Lao + + + + + Tibetan + + + + + Myanmar + + + + + Georgian + + + + + Khmer + + + + + Simplified Chinese + + + + + Traditional Chinese + + + + + Japanese + + + + + Korean + + + + + Vietnamese + + + + + Symbol + + + + + Ogham + + + + + Runic + + + + + QFontDialog + + + &Font + &Tipo de letra + + + + Font st&yle + &Estilo del tipo de letra + + + + &Size + &Tamaño + + + + Effects + Efectos + + + + Stri&keout + &Tachado + + + + &Underline + S&ubrayado + + + + Sample + Muestra + + + + Wr&iting System + Sistema de escr&itura + + + + Select Font + Seleccionar un tipo de letra + + + + QFtp + + + Not connected + No conectado + + + + Host %1 not found + Equipo %1 no encontrado + + + + Connection refused to host %1 + Conexión rechazada al equipo %1 + + + + Connected to host %1 + Conectado al equipo %1 + + + + Connection refused for data connection + Conexión para conexión de datos rechazada + + + + Unknown error + Error desconocido + + + + Connecting to host failed: +%1 + La conexión con el equipo ha fallado: +%1 + + + + Login failed: +%1 + Identificación fallida: +%1 + + + + Listing directory failed: +%1 + El listado del directorio ha fallado: +%1 + + + + Changing directory failed: +%1 + Fallo del cambio de directorio: +%1 + + + + Downloading file failed: +%1 + Fallo de la descarga del fichero: +%1 + + + + Uploading file failed: +%1 + El envío del fichero ha fallado: +%1 + + + + Removing file failed: +%1 + Eliminación de fichero fallida: +%1 + + + + Creating directory failed: +%1 + Fallo de la creación de un directorio: +%1 + + + + Removing directory failed: +%1 + Eliminación de directorio fallida: +%1 + + + + Connection closed + Conexión cerrada + + + + Host %1 found + Equipo %1 encontrado + + + + Connection to %1 closed + Conexión a %1 cerrada + + + + Host found + Equipo encontrado + + + + Connected to host + Conectado al equipo + + + + QHostInfo + + + Unknown error + Error desconocido + + + + QHostInfoAgent + + + Host not found + Equipo no encontrado + + + + Unknown address type + Dirección de tipo desconocido + + + + Unknown error + Error desconocido + + + + QHttp + + + Unknown error + Error desconocido + + + + Request aborted + Solicitud interrumpida + + + + No server set to connect to + No se ha indicado ningún servidor al que conectarse + + + + Wrong content length + Longitud del contenido errónea + + + + Server closed connection unexpectedly + El servidor cerró la conexión inesperadamente + + + + Connection refused + Conexión rechazada + + + + Host %1 not found + Equipo %1 no encontrado + + + + HTTP request failed + Solicitud HTTP fallida + + + + Invalid HTTP response header + Cabecera de respuesta HTTP no válida + + + + Invalid HTTP chunked body + Fragmento HTTP no válido + + + + Host %1 found + Equipo %1 encontrado + + + + Connected to host %1 + Conectado al equipo %1 + + + + Connection to %1 closed + Conexión a %1 cerrada + + + + Host found + Equipo encontrado + + + + Connected to host + Conectado al equipo + + + + Connection closed + Conexión cerrada + + + + Proxy authentication required + El proxy requiere autenticación + + + + Authentication required + Se precisa autenticación + + + + Connection refused (or timed out) + + + + + Proxy requires authentication + + + + + Host requires authentication + + + + + Data corrupted + + + + + Unknown protocol specified + + + + + SSL handshake failed + + + + + QHttpSocketEngine + + + Authentication required + Se precisa autenticación + + + + QIBaseDriver + + + Error opening database + Error al abrir la base de datos + + + + Could not start transaction + No fue posible iniciar la transacción + + + + Unable to commit transaction + Incapaz de enviar la transacción + + + + Unable to rollback transaction + Incapaz de anular la transacción + + + + QIBaseResult + + + Unable to create BLOB + Imposible crear un BLOB + + + + Unable to write BLOB + Imposible escribir el BLOB + + + + Unable to open BLOB + Imposible abrir el BLOB + + + + Unable to read BLOB + Imposible leer el BLOB + + + + Could not find array + No fue posible encontrar la tabla + + + + Could not get array data + No fue posible obtener los datos de la tabla + + + + Could not get query info + No fue posible obtener información sobre la consulta + + + + Could not start transaction + No fue posible iniciar la transacción + + + + Unable to commit transaction + Incapaz de enviar la transacción + + + + Could not allocate statement + No fue posible asignar la instrucción + + + + Could not prepare statement + No fue posible preparar la instrucción + + + + Could not describe input statement + No fue posible describir la instrucción de entrada + + + + Could not describe statement + No fue posible describir la instrucción + + + + Unable to close statement + No fue posible cerrar la instrucción + + + + Unable to execute query + No fue posible ejecutar la consulta + + + + Could not fetch next item + No fue posible obtener el elemento siguiente + + + + Could not get statement info + No fue posible obtener información sobre la instrucción + + + + QIODevice + + + Permission denied + Permiso denegado + + + + Too many open files + Demasiados ficheros abiertos simultáneamente + + + + No such file or directory + No hay ningún fichero o directorio con ese nombre + + + + No space left on device + No queda espacio en el dispositivo + + + + Unknown error + Error desconocido + + + + QInputContext + + + XIM + XIM + + + + XIM input method + Método de entrada XIM + + + + Windows input method + Método de entrada Windows + + + + Mac OS X input method + Método de entrada Mac OS X + + + + QLibrary + + + QLibrary::load_sys: Cannot load %1 (%2) + QLibrary::load_sys: No se puede cargar %1 (%2) + + + + QLibrary::unload_sys: Cannot unload %1 (%2) + QLibrary::unload_sys: No se puede cargar %1 (%2) + + + + QLibrary::resolve_sys: Symbol "%1" undefined in %2 (%3) + QLibrary::resolve_sys: Símbolo «%1» no definido en %2 (%3) + + + + Could not mmap '%1': %2 + No fu posible establecer la proyección en memoria de «%1»: %2 + + + + Plugin verification data mismatch in '%1' + Los datos de verificación del complemento no coinciden en «%1» + + + + Could not unmap '%1': %2 + No fue posible suprimir la proyección en memoria de «%1»: %2 + + + + The plugin '%1' uses incompatible Qt library. (%2.%3.%4) [%5] + El complemento «%1» usa una biblioteca Qt incompatible. (%2.%3.%4) [%5] + + + + The plugin '%1' uses incompatible Qt library. Expected build key "%2", got "%3" + El complemento «%1» usa una biblioteca Qt incompatible. Se esperaba la clave «%2», pero se ha recibido «%3» + + + + Unknown error + Error desconocido + + + + The shared library was not found. + No se ha encontrado la biblioteca compartida. + + + + The file '%1' is not a valid Qt plugin. + El fichero «%1» no es un complemento de Qt válido. + + + + The plugin '%1' uses incompatible Qt library. (Cannot mix debug and release libraries.) + El complemento «%1» usa una biblioteca Qt incompatible. (No se pueden mezclar las bibliotecas «debug» y «release».) + + + + QLineEdit + + + &Undo + &Deshacer + + + + &Redo + &Rehacer + + + + Cu&t + Cor&tar + + + + &Copy + &Copiar + + + + &Paste + &Pegar + + + + Delete + Borrar + + + + Select All + Seleccionar todo + + + + QLocalServer + + + %1: Name error + + + + + %1: Permission denied + + + + + %1: Address in use + + + + + %1: Unknown error %2 + + + + + QLocalSocket + + + %1: Connection refused + + + + + %1: Remote closed + + + + + %1: Invalid name + + + + + %1: Socket access error + + + + + %1: Socket resource error + + + + + %1: Socket operation timed out + + + + + %1: Datagram too large + + + + + %1: Connection error + + + + + %1: The socket operation is not supported + + + + + %1: Unknown error %2 + + + + + QMYSQLDriver + + + Unable to open database ' + Imposible abrir la base de datos ' + + + + Unable to connect + No es posible establecer una conexión + + + + Unable to begin transaction + No es posible iniciar la transacción + + + + Unable to commit transaction + No es posible enviar la transacción + + + + Unable to rollback transaction + No es posible anular la transacción + + + + QMYSQLResult + + + Unable to fetch data + No es posible obtener los datos + + + + Unable to execute query + No es posible ejecutar la consulta + + + + Unable to store result + No es posible almacenar el resultado + + + + Unable to prepare statement + No es posible preparar la instrucción + + + + Unable to reset statement + No es posible reinicializar la instrucción + + + + Unable to bind value + No es posible ligar el valor + + + + Unable to execute statement + No es posible ejecutar la instrucción + + + + Unable to bind outvalues + No es posible ligar los valores de salida + + + + Unable to store statement results + No es posible almacenar los resultados de la instrucción + + + + Unable to execute next query + + + + + Unable to store next result + + + + + QMdiArea + + + (Untitled) + + + + + QMdiSubWindow + + + %1 - [%2] + %1 - [%2] + + + + Close + Cerrar + + + + Minimize + Minimizar + + + + Restore Down + Restaurar abajo + + + + &Restore + &Restaurar + + + + &Move + &Mover + + + + &Size + Redimen&sionar + + + + Mi&nimize + Mi&nimizar + + + + Ma&ximize + Ma&ximizar + + + + Stay on &Top + Permanecer en &primer plano + + + + &Close + &Cerrar + + + + - [%1] + + + + + Maximize + Maximizar + + + + Unshade + + + + + Shade + + + + + Restore + + + + + Help + Ayuda + + + + Menu + Menú + + + + QMenu + + + Close + Cerrar + + + + Open + Abrir + + + + Execute + Ejecutar + + + + QMenuBar + + + About + Acerca de + + + + Config + Configuración + + + + Preference + Preferencia + + + + Options + Opciones + + + + Setting + Parámetro + + + + Setup + Configuración + + + + Quit + Salir + + + + Exit + Salir + + + + About %1 + Acerca de %1 + + + + About Qt + Acerca de Qt + + + + Preferences + Preferencias + + + + Quit %1 + Salir de %1 + + + + QMessageBox + + + Help + Ayuda + + + + OK + Aceptar + + + + About Qt + Acerca de Qt + + + + <p>This program uses Qt version %1.</p> + <p>Este programa utiliza la versión %1 de Qt.</p> + + + + <h3>About Qt</h3>%1<p>Qt is a C++ toolkit for cross-platform application development.</p><p>Qt provides single-source portability across MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux, and all major commercial Unix variants. Qt is also available for embedded devices as Qtopia Core.</p><p>Qt is a Trolltech product. See <a href="http://www.trolltech.com/qt/">www.trolltech.com/qt/</a> for more information.</p> + <h3>Acerca de Qt</h3>%1<p>Qt es un toolkit en C++ para desarrollo de aplicaciones multiplataforma.</p><p>Qt proporciona portabilidad del código entre MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux y todas las variantes comerciales de Unix importantes. Qt también está disponible para sistemas empotrados bajo el nombre Qtopia Core.</p><p>Qt es un producto de Trolltech. Visite <a href="http://www.trolltech.com/qt/">www.trolltech.com/qt/</a> para obtener más información.</p> + + + + Show Details... + Mostrar los detalles... + + + + Hide Details... + Ocultar los detalles... + + + + <p>This program uses Qt Open Source Edition version %1.</p><p>Qt Open Source Edition is intended for the development of Open Source applications. You need a commercial Qt license for development of proprietary (closed source) applications.</p><p>Please see <a href="http://www.trolltech.com/company/model/">www.trolltech.com/company/model/</a> for an overview of Qt licensing.</p> + <p>Este programa utiliza Qt Open Source Edition versión %1.</p><p>Qt Open Source Edition está dirigida al desarrollo de aplicaciones libres. Para desarrollar aplicaciones privativas (de código cerrado) necesita una licencia comercial de Qt.</p><p>Visite <a href="http://www.trolltech.com/company/model/">www.trolltech.com/company/model/</a> para obtener una visión global de las licencias de Qt.</p> + + + + <h3>About Qt</h3>%1<p>Qt is a C++ toolkit for cross-platform application development.</p><p>Qt provides single-source portability across MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux, and all major commercial Unix variants. Qt is also available for embedded devices as Qt Embedded.</p><p>Qt is a Trolltech product. See <a href="http://www.trolltech.com/qt/">www.trolltech.com/qt/</a> for more information.</p> + + + + + QMultiInputContext + + + Select IM + Seleccionar IM + + + + QMultiInputContextPlugin + + + Multiple input method switcher + Seleccionador de varios métodos de entrada + + + + Multiple input method switcher that uses the context menu of the text widgets + Seleccionador de varios métodos de entrada que usa el menú contextual de los elementos de texto + + + + QNativeSocketEngine + + + The remote host closed the connection + El equipo remoto ha cerrado la conexión + + + + Network operation timed out + La operación de red ha expirado + + + + Out of resources + Insuficientes recursos + + + + Unsupported socket operation + Operación socket no admitida + + + + Protocol type not supported + Tipo de protocolo no admitido + + + + Invalid socket descriptor + Descriptor de socket no válido + + + + Network unreachable + Red inalcanzable + + + + Permission denied + Permiso denegado + + + + Connection timed out + Conexión expirada + + + + Connection refused + Conexión rechazada + + + + The bound address is already in use + La dirección enlazada ya está en uso + + + + The address is not available + La dirección no está disponible + + + + The address is protected + La dirección está protegida + + + + Unable to send a message + Imposible enviar un mensaje + + + + Unable to receive a message + Imposible recibir un mensaje + + + + Unable to write + Imposible escribir + + + + Network error + Error de red + + + + Another socket is already listening on the same port + Ya hay otro socket escuchando por el mismo puerto + + + + Unable to initialize non-blocking socket + Imposible inicializar el socket no bloqueante + + + + Unable to initialize broadcast socket + Imposible inicializar el socket de difusión + + + + Attempt to use IPv6 socket on a platform with no IPv6 support + Intento de usar un socket IPv6 sobre una plataforma que no contempla IPv6 + + + + Host unreachable + Equipo inaccesible + + + + Datagram was too large to send + El datagrama era demasiado grande para poder ser enviado + + + + Operation on non-socket + Operación sobre un no-socket + + + + Unknown error + Error desconocido + + + + The proxy type is invalid for this operation + + + + + QNetworkAccessFileBackend + + + Request for opening non-local file %1 + + + + + Error opening %1: %2 + + + + + Write error writing to %1: %2 + + + + + Cannot open %1: Path is a directory + + + + + Read error reading from %1: %2 + + + + + QNetworkAccessFtpBackend + + + Cannot open %1: is a directory + + + + + Logging in to %1 failed: authentication required + + + + + Error while downloading %1: %2 + + + + + Error while uploading %1: %2 + + + + + QNetworkReply + + + Error downloading %1 - server replied: %2 + + + + + Protocol "%1" is unknown + + + + + QNetworkReplyImpl + + + Operation canceled + + + + + QOCIDriver + + + Unable to logon + No es posible abrir sesión + + + + Unable to initialize + QOCIDriver + La inicialización ha fallado + + + + Unable to begin transaction + No es posible iniciar la transacción + + + + Unable to commit transaction + + + + + Unable to rollback transaction + + + + + QOCIResult + + + Unable to bind column for batch execute + No es posible ligar la columna para una ejecución por lotes + + + + Unable to execute batch statement + No es posible ejecutar la instrucción por lotes + + + + Unable to goto next + No es posible pasar al siguiente + + + + Unable to alloc statement + No es posible asignar la instrucción + + + + Unable to prepare statement + No es posible preparar la instrucción + + + + Unable to bind value + No es posible ligar el valor + + + + Unable to execute select statement + No es posible ejecutar la instrucción select + + + + Unable to execute statement + No es posible ejecutar la instrucción + + + + QODBCDriver + + + Unable to connect + No es posible establecer una conexión + + + + Unable to connect - Driver doesn't support all needed functionality + No es posible conectarse - El controlador no ofrece todas las funciones necesarias + + + + Unable to disable autocommit + No es posible inhabilitar el envío automático + + + + Unable to commit transaction + No es posible enviar la transacción + + + + Unable to rollback transaction + No es posible anular la transacción + + + + Unable to enable autocommit + No es posible habilitar el envío automático + + + + QODBCResult + + + QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. Please check your ODBC driver configuration + QODBCResult::reset: No es posible establecer «SQL_CURSOR_STATIC» como atributo de instrucción. Compruebe la configuración de su controlador ODBC + + + + Unable to execute statement + No es posible ejecutar la instrucción + + + + Unable to fetch next + No es posible obtener el siguiente + + + + Unable to prepare statement + No es posible preparar la instrucción + + + + Unable to bind variable + No es posible ligar la variable + + + + Unable to fetch last + + + + + Unable to fetch + + + + + Unable to fetch first + Imposible recuperar el primero + + + + Unable to fetch previous + + + + + QObject + + + Home + Inicio + + + + Operation not supported on %1 + + + + + Invalid URI: %1 + + + + + Write error writing to %1: %2 + + + + + Read error reading from %1: %2 + + + + + Socket error on %1: %2 + + + + + Remote host closed the connection prematurely on %1 + + + + + Protocol error: packet of size 0 received + + + + + QPPDOptionsModel + + + Name + Nombre + + + + Value + Valor + + + + QPSQLDriver + + + Unable to connect + No es posible establecer conexión + + + + Could not begin transaction + No fue posible iniciar la transacción + + + + Could not commit transaction + No fue posible enviar la transacción + + + + Could not rollback transaction + No fue posible anular la transacción + + + + Unable to subscribe + + + + + Unable to unsubscribe + + + + + QPSQLResult + + + Unable to create query + No es posible crear la consulta + + + + Unable to prepare statement + + + + + QPageSetupWidget + + + Centimeters (cm) + + + + + Millimeters (mm) + + + + + Inches (in) + + + + + Points (pt) + + + + + Form + + + + + Paper + + + + + Page size: + Tamaño de página: + + + + Width: + + + + + Height: + + + + + Paper source: + Fuente del papel: + + + + Orientation + + + + + Portrait + Vertical + + + + Landscape + Apaisado + + + + Reverse landscape + + + + + Reverse portrait + + + + + Margins + + + + + top margin + + + + + left margin + + + + + right margin + + + + + bottom margin + + + + + QPluginLoader + + + Unknown error + Error desconocido + + + + The plugin was not loaded. + El complemento no fue cargado. + + + + QPrintDialog + + + locally connected + conectado localmente + + + + Aliases: %1 + Alias: %1 + + + + unknown + desconocido + + + + Print in color if available + Imprimir en color si es posible + + + + Print all + Imprimir todo + + + + Print selection + Imprimir la selección + + + + Print range + Imprimir el intervalo + + + + Print last page first + Imprimir primero la última página + + + + Number of copies: + Número de copias: + + + + Paper format + Formato del papel + + + + Portrait + Vertical + + + + Landscape + Apaisado + + + + A0 (841 x 1189 mm) + A0 (841 x 1189 mm) + + + + A1 (594 x 841 mm) + A1 (594 x 841 mm) + + + + A2 (420 x 594 mm) + A2 (420 x 594 mm) + + + + A3 (297 x 420 mm) + A3 (297 x 420 mm) + + + + A4 (210 x 297 mm, 8.26 x 11.7 inches) + A4 (210 x 297 mm, 8,26 x 11,7 pulgadas) + + + + A5 (148 x 210 mm) + A5 (148 x 210 mm) + + + + A6 (105 x 148 mm) + A6 (105 x 148 mm) + + + + A7 (74 x 105 mm) + A7 (74 x 105 mm) + + + + A8 (52 x 74 mm) + A8 (52 x 74 mm) + + + + A9 (37 x 52 mm) + A9 (37 x 52 mm) + + + + B0 (1000 x 1414 mm) + B0 (1000 x 1414 mm) + + + + B1 (707 x 1000 mm) + B1 (707 x 1000 mm) + + + + B2 (500 x 707 mm) + B2 (500 x 707 mm) + + + + B3 (353 x 500 mm) + B3 (353 x 500 mm) + + + + B4 (250 x 353 mm) + B4 (250 x 353 mm) + + + + B5 (176 x 250 mm, 6.93 x 9.84 inches) + B5 (176 x 250 mm, 6,93 x 9,84 pulgadas) + + + + B6 (125 x 176 mm) + B6 (125 x 176 mm) + + + + B7 (88 x 125 mm) + B7 (88 x 125 mm) + + + + B8 (62 x 88 mm) + B8 (62 x 88 mm) + + + + B9 (44 x 62 mm) + B9 (44 x 62 mm) + + + + B10 (31 x 44 mm) + B10 (31 x 44 mm) + + + + C5E (163 x 229 mm) + C5E (163 x 229 mm) + + + + DLE (110 x 220 mm) + DLE (110 x 220 mm) + + + + Executive (7.5 x 10 inches, 191 x 254 mm) + Ejecutivo (7,5 x 10 pulgadas, 191 x 254 mm) + + + + Folio (210 x 330 mm) + Folio (210 x 330 mm) + + + + Ledger (432 x 279 mm) + Ledger (432 x 279 mm) + + + + Legal (8.5 x 14 inches, 216 x 356 mm) + Legal (8,5 x 14 pulgadas, 216 x 356 mm) + + + + Letter (8.5 x 11 inches, 216 x 279 mm) + Carta (8,5 x 11 pulgadas, 216 x 279 mm) + + + + Tabloid (279 x 432 mm) + Tabloide (279 x 432 mm) + + + + US Common #10 Envelope (105 x 241 mm) + Sobre US Common #10 (105 x 241 mm) + + + + OK + Aceptar + + + + Cancel + Cancelar + + + + Page size: + Tamaño de página: + + + + Orientation: + Orientación: + + + + Paper source: + Fuente del papel: + + + + Print + Imprimir + + + + File + Fichero + + + + Printer + Impresora + + + + Print To File ... + Imprimir a fichero... + + + + Print dialog + Ventana de impresión + + + + Size: + Tamaño: + + + + Properties + Propiedades + + + + Printer info: + Información de la impresora: + + + + Browse + Explorar + + + + Print to file + Imprimir a fichero + + + + Pages from + Páginas + + + + to + a + + + + Selection + Selección + + + + Copies + Copias + + + + Collate + Recopilar + + + + Other + Otro + + + + Double side printing + Impresión a doble cara + + + + File %1 is not writable. +Please choose a different file name. + No se puede escribir en el fichero %1. +Elija un nombre de fichero diferente. + + + + %1 already exists. +Do you want to overwrite it? + %1 ya existe. +¿Desea sobrescribirlo? + + + + File exists + El fichero existe + + + + <qt>Do you want to overwrite it?</qt> + <qt>¿Desea sobrescribirlo?</qt> + + + + %1 is a directory. +Please choose a different file name. + %1 es un directorio. +Elija un nombre de fichero diferente. + + + + A0 + + + + + A1 + + + + + A2 + + + + + A3 + + + + + A4 + + + + + A5 + + + + + A6 + + + + + A7 + + + + + A8 + + + + + A9 + + + + + B0 + + + + + B1 + + + + + B2 + + + + + B3 + + + + + B4 + + + + + B5 + + + + + B6 + + + + + B7 + + + + + B8 + + + + + B9 + + + + + B10 + + + + + C5E + + + + + DLE + + + + + Executive + + + + + Folio + + + + + Ledger + + + + + Legal + + + + + Letter + + + + + Tabloid + + + + + US Common #10 Envelope + + + + + Custom + + + + + &Options >> + + + + + &Print + + + + + &Options << + + + + + Print to File (PDF) + + + + + Print to File (Postscript) + + + + + Local file + + + + + Write %1 file + + + + + The 'From' value cannot be greater than the 'To' value. + + + + + QPrintPreviewDialog + + + Page Setup + + + + + Print Preview + + + + + Next page + + + + + Previous page + + + + + First page + + + + + Last page + + + + + Fit width + + + + + Fit page + + + + + Zoom in + + + + + Zoom out + + + + + Portrait + Vertical + + + + Landscape + Apaisado + + + + Show single page + + + + + Show facing pages + + + + + Show overview of all pages + + + + + Print + + + + + Page setup + + + + + Close + Cerrar + + + + QPrintPropertiesDialog + + + PPD Properties + Propiedades PPD + + + + Save + Guardar + + + + OK + Aceptar + + + + QPrintPropertiesWidget + + + Form + + + + + Page + + + + + Advanced + + + + + QPrintSettingsOutput + + + Form + + + + + Copies + Copias + + + + Print range + Imprimir el intervalo + + + + Print all + Imprimir todo + + + + Pages from + Páginas + + + + to + a + + + + Selection + Selección + + + + Output Settings + + + + + Copies: + + + + + Collate + Recopilar + + + + Reverse + + + + + Options + Opciones + + + + Color Mode + + + + + Color + + + + + Grayscale + + + + + Duplex Printing + + + + + None + + + + + Long side + + + + + Short side + + + + + QPrintWidget + + + Form + + + + + Printer + Impresora + + + + &Name: + + + + + P&roperties + + + + + Location: + + + + + Preview + + + + + Type: + + + + + Output &file: + + + + + ... + + + + + QProgressDialog + + + Cancel + Cancelar + + + + QPushButton + + + Open + Abrir + + + + QRadioButton + + + Check + Marcar + + + + QRegExp + + + no error occurred + no se ha producido ningún error + + + + disabled feature used + se ha usado una característica no habilitada + + + + bad char class syntax + sintaxis no válida para clase de caracteres + + + + bad lookahead syntax + sintaxis no válida para lookahead + + + + bad repetition syntax + sintaxis no válida para repetición + + + + invalid octal value + valor octal no válido + + + + missing left delim + falta el delimitador izquierdo + + + + unexpected end + fin inesperado + + + + met internal limit + se alcanzó el límite interno + + + + QSQLite2Driver + + + Error to open database + Error al abrir la base de datos + + + + Unable to begin transaction + No es posible iniciar la transacción + + + + Unable to commit transaction + No es posible enviar la transacción + + + + Unable to rollback Transaction + No es posible anular la transacción + + + + QSQLite2Result + + + Unable to fetch results + No es posible obtener los resultados + + + + Unable to execute statement + No es posible ejecutar la instrucción + + + + QSQLiteDriver + + + Error opening database + Error al abrir la base de datos + + + + Error closing database + Error al cerrar la base de datos + + + + Unable to begin transaction + No es posible iniciar la transacción + + + + Unable to commit transaction + No es posible enviar la transacción + + + + Unable to roll back transaction + No es posible anular la transacción + + + + Unable to rollback transaction + + + + + QSQLiteResult + + + Unable to fetch row + No es posible obtener la fila + + + + Unable to execute statement + No es posible ejecutar la instrucción + + + + Unable to reset statement + No es posible reinicializar la instrucción + + + + Unable to bind parameters + No es posible ligar los parámetros + + + + Parameter count mismatch + Número de parámetros incorrecto + + + + No query + + + + + QScrollBar + + + Scroll here + Desplazar hasta aquí + + + + Left edge + Borde izquierdo + + + + Top + Parte superior + + + + Right edge + Borde derecho + + + + Bottom + Parte inferior + + + + Page left + Una página a la izquierda + + + + Page up + Una página hacia arriba + + + + Page right + Una página a la derecha + + + + Page down + Una página hacia abajo + + + + Scroll left + Desplazar hacia la izquierda + + + + Scroll up + Desplazar hacia arriba + + + + Scroll right + Desplazar hacia la derecha + + + + Scroll down + Desplazar hacia abajo + + + + Line up + Alinear + + + + Position + Posición + + + + Line down + Alinear por abajo + + + + QSharedMemory + + + %1: unable to set key on lock + + + + + %1: create size is less then 0 + + + + + %1: unable to lock + + + + + %1: unable to unlock + + + + + %1: permission denied + + + + + %1: already exists + + + + + %1: doesn't exists + + + + + %1: out of resources + + + + + %1: unknown error %2 + + + + + %1: key is empty + + + + + %1: unix key file doesn't exists + + + + + %1: ftok failed + + + + + %1: unable to make key + + + + + %1: system-imposed size restrictions + + + + + %1: not attached + + + + + %1: invalid size + + + + + %1: key error + + + + + %1: size query failed + + + + + QShortcut + + + Space + Espacio + + + + Esc + Esc + + + + Tab + Tabulador + + + + Backtab + Tabulador hacia atrás + + + + Backspace + Borrar + + + + Return + Retorno + + + + Enter + Intro + + + + Ins + Ins + + + + Del + Supr + + + + Pause + Pausa + + + + Print + Impr Pant + + + + SysReq + PetSis + + + + Home + Inicio + + + + End + Fin + + + + Left + Izquierda + + + + Up + Arriba + + + + Right + Derecha + + + + Down + Abajo + + + + PgUp + Re Pág + + + + PgDown + Av Pág + + + + CapsLock + Bloq Mayús + + + + NumLock + Bloq Num + + + + ScrollLock + Bloq Despl + + + + Menu + Menú + + + + Help + Ayuda + + + + Back + Anterior (histórico) + + + + Forward + Siguiente (histórico) + + + + Stop + Detener + + + + Refresh + Actualizar + + + + Volume Down + Bajar el volumen + + + + Volume Mute + Silenciar + + + + Volume Up + Subir el volumen + + + + Bass Boost + Potenciar los graves + + + + Bass Up + Subir los graves + + + + Bass Down + Bajar los graves + + + + Treble Up + Subir los agudos + + + + Treble Down + Bajar los agudos + + + + Media Play + Reproducir el medio + + + + Media Stop + Detener el medio + + + + Media Previous + Medio anterior + + + + Media Next + Siguiente medio + + + + Media Record + Grabar medio + + + + Favorites + Favoritos + + + + Search + Búsqueda + + + + Standby + Reposo + + + + Open URL + Abrir URL + + + + Launch Mail + Lanzar correo + + + + Launch Media + Lanzar medio + + + + Launch (0) + Lanzar (0) + + + + Launch (1) + Lanzar (1) + + + + Launch (2) + Lanzar (2) + + + + Launch (3) + Lanzar (3) + + + + Launch (4) + Lanzar (4) + + + + Launch (5) + Lanzar (5) + + + + Launch (6) + Lanzar (6) + + + + Launch (7) + Lanzar (7) + + + + Launch (8) + Lanzar (8) + + + + Launch (9) + Lanzar (9) + + + + Launch (A) + Lanzar (A) + + + + Launch (B) + Lanzar (B) + + + + Launch (C) + Lanzar (C) + + + + Launch (D) + Lanzar (D) + + + + Launch (E) + Lanzar (E) + + + + Launch (F) + Lanzar (F) + + + + Print Screen + Imprimir pantalla + + + + Page Up + Retroceder página + + + + Page Down + Avanzar página + + + + Caps Lock + Bloqueo de mayúsculas + + + + Num Lock + Bloq num + + + + Number Lock + Bloqueo numérico + + + + Scroll Lock + Bloqueo del desplazamiento + + + + Insert + Insertar + + + + Delete + Borrar + + + + Escape + Escape + + + + System Request + Petición del sistema + + + + Select + Seleccionar + + + + Yes + + + + + No + No + + + + Context1 + Contexto1 + + + + Context2 + Contexto2 + + + + Context3 + Contexto3 + + + + Context4 + Contexto4 + + + + Call + Llamar + + + + Hangup + Descolgar + + + + Flip + Voltear + + + + Ctrl + Ctrl + + + + Shift + May + + + + Alt + Alt + + + + Meta + Meta + + + + + + + + + + + F%1 + F%1 + + + + Home Page + Página de inicio + + + + QSlider + + + Page left + Una página a la izquierda + + + + Page up + Una página hacia arriba + + + + Position + Posición + + + + Page right + Una página a la derecha + + + + Page down + Una página hacia abajo + + + + QSocks5SocketEngine + + + Socks5 timeout error connecting to socks server + Error de expiración socks5 mientras se establecía una conexión al servidor socks + + + + Network operation timed out + La operación de red ha expirado + + + + QSpinBox + + + More + Más + + + + Less + Menos + + + + QSql + + + Delete + Borrar + + + + Delete this record? + ¿Borrar este registro? + + + + Yes + + + + + No + No + + + + Insert + Insertar + + + + Update + Actualizar + + + + Save edits? + ¿Guardar las modificaciones? + + + + Cancel + Cancelar + + + + Confirm + Confirmar + + + + Cancel your edits? + ¿Cancelar sus modificaciones? + + + + QSslSocket + + + Unable to write data: %1 + No es posible escribir los datos: %1 + + + + Error while reading: %1 + Error al leer: %1 + + + + Error during SSL handshake: %1 + Error durante el saludo SSL: %1 + + + + Error creating SSL context (%1) + Error al crear el contexto SSL (%1) + + + + Invalid or empty cipher list (%1) + Lista de cifras vacía o no válida (%1) + + + + Error creating SSL session, %1 + Error al crear la sesión SSL, %1 + + + + Error creating SSL session: %1 + Error al crear la sesión SSL: %1 + + + + Cannot provide a certificate with no key, %1 + No se puede proporcionar un certificado sin clave, %1 + + + + Error loading local certificate, %1 + Error al cargar el certificado local, %1 + + + + Error loading private key, %1 + Error al cargar la clave privada, %1 + + + + Private key do not certificate public key, %1 + La clave privada no certifica la clave pública, %1 + + + + Private key does not certificate public key, %1 + + + + + QSystemSemaphore + + + %1: out of resources + + + + + %1: permission denied + + + + + %1: unknown error %2 + + + + + QTDSDriver + + + Unable to open connection + No es posible abrir la conexión + + + + Unable to use database + No es posible utilizar la base de datos + + + + QTabBar + + + Scroll Left + Desplazar hacia la izquierda + + + + Scroll Right + Desplazar hacia la derecha + + + + QTcpServer + + + Socket operation unsupported + Operación socket no admitida + + + + QTextControl + + + &Undo + &Deshacer + + + + &Redo + &Rehacer + + + + Cu&t + Cor&tar + + + + &Copy + &Copiar + + + + Copy &Link Location + Copiar la ubicación del en&lace + + + + &Paste + &Pegar + + + + Delete + Borrar + + + + Select All + Seleccionar todo + + + + QToolButton + + + Press + Pulsar + + + + Open + Abrir + + + + QUdpSocket + + + This platform does not support IPv6 + La plataforma no contempla IPv6 + + + + QUndoGroup + + + Undo + Deshacer + + + + Redo + Rehacer + + + + QUndoModel + + + <empty> + <vacío> + + + + QUndoStack + + + Undo + Deshacer + + + + Redo + Rehacer + + + + QUnicodeControlCharacterMenu + + + LRM Left-to-right mark + LRM Left-to-right mark + + + + RLM Right-to-left mark + RLM Right-to-left mark + + + + ZWJ Zero width joiner + ZWJ Zero width joiner + + + + ZWNJ Zero width non-joiner + ZWNJ Zero width non-joiner + + + + ZWSP Zero width space + ZWSP Zero width space + + + + LRE Start of left-to-right embedding + LRE Start of left-to-right embedding + + + + RLE Start of right-to-left embedding + RLE Start of right-to-left embedding + + + + LRO Start of left-to-right override + LRO Start of left-to-right override + + + + RLO Start of right-to-left override + RLO Start of right-to-left override + + + + PDF Pop directional formatting + PDF Pop directional formatting + + + + Insert Unicode control character + Insertar carácter de control Unicode + + + + QWebFrame + + + Request cancelled + + + + + Request blocked + + + + + Cannot show URL + + + + + Frame load interruped by policy change + + + + + Cannot show mimetype + + + + + File does not exist + + + + + QWebPage + + + Bad HTTP request + + + + + Submit + default label for Submit buttons in forms on web pages + + + + + Submit + Submit (input element) alt text for <input> elements with no alt, title, or value + + + + + Reset + default label for Reset buttons in forms on web pages + Reinicializar + + + + This is a searchable index. Enter search keywords: + text that appears at the start of nearly-obsolete web pages in the form of a 'searchable index' + + + + + Choose File + title for file button used in HTML forms + + + + + No file selected + text to display in file button used in HTML forms when no file is selected + + + + + Open in New Window + Open in New Window context menu item + + + + + Save Link... + Download Linked File context menu item + + + + + Copy Link + Copy Link context menu item + + + + + Open Image + Open Image in New Window context menu item + + + + + Save Image + Download Image context menu item + + + + + Copy Image + Copy Link context menu item + + + + + Open Frame + Open Frame in New Window context menu item + + + + + Copy + Copy context menu item + + + + + Go Back + Back context menu item + Precedente + + + + Go Forward + Forward context menu item + + + + + Stop + Stop context menu item + Detener + + + + Reload + Reload context menu item + + + + + Cut + Cut context menu item + + + + + Paste + Paste context menu item + + + + + No Guesses Found + No Guesses Found context menu item + + + + + Ignore + Ignore Spelling context menu item + Ignorar + + + + Add To Dictionary + Learn Spelling context menu item + + + + + Search The Web + Search The Web context menu item + + + + + Look Up In Dictionary + Look Up in Dictionary context menu item + + + + + Open Link + Open Link context menu item + + + + + Ignore + Ignore Grammar context menu item + Ignorar + + + + Spelling + Spelling and Grammar context sub-menu item + + + + + Show Spelling and Grammar + menu item title + + + + + Hide Spelling and Grammar + menu item title + + + + + Check Spelling + Check spelling context menu item + + + + + Check Spelling While Typing + Check spelling while typing context menu item + + + + + Check Grammar With Spelling + Check grammar with spelling context menu item + + + + + Fonts + Font context sub-menu item + + + + + Bold + Bold context menu item + + + + + Italic + Italic context menu item + + + + + Underline + Underline context menu item + + + + + Outline + Outline context menu item + + + + + Direction + Writing direction context sub-menu item + + + + + Default + Default writing direction context menu item + + + + + LTR + Left to Right context menu item + + + + + RTL + Right to Left context menu item + + + + + Inspect + Inspect Element context menu item + + + + + No recent searches + Label for only item in menu that appears when clicking on the search field image, when no searches have been performed + + + + + Recent searches + label for first item in the menu that appears when clicking on the search field image, used as embedded menu title + + + + + Clear recent searches + menu item in Recent Searches menu that empties menu's contents + + + + + Unknown + Unknown filesize FTP directory listing item + Desconocido + + + + %1 (%2x%3 pixels) + Title string for images + + + + + Web Inspector - %2 + + + + + QWhatsThisAction + + + What's This? + ¿Qué es esto? + + + + QWidget + + + * + * + + + + QWizard + + + Go Back + Precedente + + + + Continue + Siguiente + + + + Commit + Enviar + + + + Done + Terminar + + + + Quit + Salir + + + + Help + Ayuda + + + + < &Back + < &Anterior + + + + &Finish + &Terminar + + + + Cancel + Cancelar + + + + &Help + &Ayuda + + + + &Next + + + + + &Next > + Siguie&nte > + + + + QWorkspace + + + &Restore + &Restaurar + + + + &Move + &Mover + + + + &Size + &Tamaño + + + + Mi&nimize + Mi&nimizar + + + + Ma&ximize + Ma&ximizar + + + + &Close + &Cerrar + + + + Stay on &Top + Permanecer en &primer plano + + + + Sh&ade + Sombre&ar + + + + %1 - [%2] + %1 - [%2] + + + + Minimize + Minimizar + + + + Restore Down + Restaurar abajo + + + + Close + Cerrar + + + + &Unshade + Q&uitar sombra + + + + QXml + + + no error occurred + no se ha producido ningún error + + + + error triggered by consumer + error debido al consumidor + + + + unexpected end of file + fin de fichero inesperado + + + + more than one document type definition + más de una definición de tipo de documento + + + + error occurred while parsing element + se ha producido un error durante el análisis de un elemento + + + + tag mismatch + etiqueta desequilibrada + + + + error occurred while parsing content + se ha producido un error durante el análisis del contenido + + + + unexpected character + carácter inesperado + + + + invalid name for processing instruction + nombre de instrucción de tratamiento no válido + + + + version expected while reading the XML declaration + se esperaba la versión al leer la declaración XML + + + + wrong value for standalone declaration + valor erróneo para la declaración independiente + + + + encoding declaration or standalone declaration expected while reading the XML declaration + se esperaba una declaración de codificación o declaración autónoma al leer la declaración XML + + + + standalone declaration expected while reading the XML declaration + se esperaba una declaración independiente al leer la declaración XML + + + + error occurred while parsing document type definition + se ha producido un error durante el análisis de la definición de tipo de documento + + + + letter is expected + se esperaba una letra + + + + error occurred while parsing comment + se ha producido un error durante el análisis de un comentario + + + + error occurred while parsing reference + se ha producido un error durante el análisis de una referencia + + + + internal general entity reference not allowed in DTD + no se permiten referencias a entidades internas generales en la DTD + + + + external parsed general entity reference not allowed in attribute value + no se permiten referencias a entidades externas generales ya analizadas en el valor de un atributo + + + + external parsed general entity reference not allowed in DTD + no se permiten referencias a entidades externas generales ya analizadas en la DTD + + + + unparsed entity reference in wrong context + referencia a entidad no analizada en un contexto no válido + + + + recursive entities + entidades recursivas + + + + error in the text declaration of an external entity + error en la declaración de texto de una entidad externa + + + + QXmlStream + + + Extra content at end of document. + Contenido extra al final del documento. + + + + Invalid entity value. + Valor de la entidad no válido. + + + + Invalid XML character. + Carácter XML no válido. + + + + Sequence ']]>' not allowed in content. + Secuencia «]]>» no permitida en el contenido. + + + + Namespace prefix '%1' not declared + Prefijo de espacio de nombres «%1» no declarado + + + + Attribute redefined. + Atributo redefinido. + + + + Unexpected character '%1' in public id literal. + Carácter «%1» inesperado en un literal de identificación público. + + + + Invalid XML version string. + Cadena de versión XML no válida. + + + + Unsupported XML version. + Versión XML no admitida. + + + + %1 is an invalid encoding name. + %1 es un nombre de codificación no válido. + + + + Encoding %1 is unsupported + No se admite la codificación %1 + + + + Invalid XML encoding name. + Nombre de codificación XML no válido. + + + + Standalone accepts only yes or no. + «Standalone» sólo acepta «sí» o «no». + + + + Invalid attribute in XML declaration. + Atributo no válido en la declaración XML. + + + + Premature end of document. + Final prematuro del documento. + + + + Invalid document. + Documento no válido. + + + + Expected + Se esperaba + + + + , but got ' + , pero se ha recibido ' + + + + Unexpected ' + No se esperaba ' + + + + Expected character data. + Se esperaban datos de carácter. + + + + Recursive entity detected. + Detectada entidad recursiva. + + + + Start tag expected. + Se esperaba etiqueta de inicio. + + + + XML declaration not at start of document. + La declaración XML no está al principio del documento. + + + + NDATA in parameter entity declaration. + NDATA en una declaración de entidad parámetro. + + + + %1 is an invalid processing instruction name. + %1 es un nombre de instrucción de procesamiento no válido. + + + + Invalid processing instruction name. + Nombre de instrucción de procesamiento no válido. + + + + Illegal namespace declaration. + Declaración de espacio de nombres ilegal. + + + + Invalid XML name. + Nombre XML no válido. + + + + Opening and ending tag mismatch. + Las etiquetas de apertura y cierre no coinciden. + + + + Reference to unparsed entity '%1'. + Referencia a una entidad no analizada «%1». + + + + Entity '%1' not declared. + Entidad «%1» no declarada. + + + + Reference to external entity '%1' in attribute value. + Referencia a una entidad externa «%1» en el valor del atributo. + + + + Invalid character reference. + Referencia un carácter no válido. + + + + Encountered incorrectly encoded content. + Encontrado contenido codificado incorrectamente. + + + + The standalone pseudo attribute must appear after the encoding. + El pseudoatributo «standalone» debe aparece después de la codificación. + + + + %1 is an invalid PUBLIC identifier. + %1 no es un identificador PUBLIC válido. + + + + QtXmlPatterns + + + An %1-attribute with value %2 has already been declared. + + + + + An %1-attribute must have a valid %2 as value, which %3 isn't. + + + + + Network timeout. + + + + + Element %1 can't be serialized because it appears outside the document element. + + + + + Attribute element %1 can't be serialized because it appears at the top level. + + + + + Year %1 is invalid because it begins with %2. + + + + + Day %1 is outside the range %2..%3. + + + + + Month %1 is outside the range %2..%3. + + + + + Overflow: Can't represent date %1. + + + + + Day %1 is invalid for month %2. + + + + + Time 24:%1:%2.%3 is invalid. Hour is 24, but minutes, seconds, and milliseconds are not all 0; + + + + + Time %1:%2:%3.%4 is invalid. + + + + + Overflow: Date can't be represented. + + + + + At least one component must be present. + + + + + At least one time component must appear after the %1-delimiter. + + + + + No operand in an integer division, %1, can be %2. + + + + + The first operand in an integer division, %1, cannot be infinity (%2). + + + + + The second operand in a division, %1, cannot be zero (%2). + + + + + %1 is not a valid value of type %2. + + + + + When casting to %1 from %2, the source value cannot be %3. + + + + + Integer division (%1) by zero (%2) is undefined. + + + + + Division (%1) by zero (%2) is undefined. + + + + + Modulus division (%1) by zero (%2) is undefined. + + + + + Dividing a value of type %1 by %2 (not-a-number) is not allowed. + + + + + Dividing a value of type %1 by %2 or %3 (plus or minus zero) is not allowed. + + + + + Multiplication of a value of type %1 by %2 or %3 (plus or minus infinity) is not allowed. + + + + + A value of type %1 cannot have an Effective Boolean Value. + + + + + Effective Boolean Value cannot be calculated for a sequence containing two or more atomic values. + + + + + Value %1 of type %2 exceeds maximum (%3). + + + + + Value %1 of type %2 is below minimum (%3). + + + + + A value of type %1 must contain an even number of digits. The value %2 does not. + + + + + %1 is not valid as a value of type %2. + + + + + Operator %1 cannot be used on type %2. + + + + + Operator %1 cannot be used on atomic values of type %2 and %3. + + + + + The namespace URI in the name for a computed attribute cannot be %1. + + + + + The name for a computed attribute cannot have the namespace URI %1 with the local name %2. + + + + + Type error in cast, expected %1, received %2. + + + + + When casting to %1 or types derived from it, the source value must be of the same type, or it must be a string literal. Type %2 is not allowed. + + + + + No casting is possible with %1 as the target type. + + + + + It is not possible to cast from %1 to %2. + + + + + Casting to %1 is not possible because it is an abstract type, and can therefore never be instantiated. + + + + + It's not possible to cast the value %1 of type %2 to %3 + + + + + Failure when casting from %1 to %2: %3 + + + + + A comment cannot contain %1 + + + + + A comment cannot end with a %1. + + + + + No comparisons can be done involving the type %1. + + + + + Operator %1 is not available between atomic values of type %2 and %3. + + + + + An attribute node cannot be a child of a document node. Therefore, the attribute %1 is out of place. + + + + + A library module cannot be evaluated directly. It must be imported from a main module. + + + + + A value of type %1 cannot be a predicate. A predicate must have either a numeric type or an Effective Boolean Value type. + + + + + A positional predicate must evaluate to a single numeric value. + + + + + The target name in a processing instruction cannot be %1 in any combination of upper and lower case. Therefore, is %2 invalid. + + + + + %1 is not a valid target name in a processing instruction. It must be a %2 value, e.g. %3. + + + + + The last step in a path must contain either nodes or atomic values. It cannot be a mixture between the two. + + + + + The data of a processing instruction cannot contain the string %1 + + + + + No namespace binding exists for the prefix %1 + + + + + No namespace binding exists for the prefix %1 in %2 + + + + + %1 is an invalid %2 + + + + + %1 takes at most %n argument(s). %2 is therefore invalid. + + + + + + + + %1 requires at least %n argument(s). %2 is therefore invalid. + + + + + + + + The first argument to %1 cannot be of type %2. It must be a numeric type, xs:yearMonthDuration or xs:dayTimeDuration. + + + + + The first argument to %1 cannot be of type %2. It must be of type %3, %4, or %5. + + + + + The second argument to %1 cannot be of type %2. It must be of type %3, %4, or %5. + + + + + %1 is not a valid XML 1.0 character. + + + + + The first argument to %1 cannot be of type %2. + + + + + If both values have zone offsets, they must have the same zone offset. %1 and %2 are not the same. + + + + + %1 was called. + + + + + %1 must be followed by %2 or %3, not at the end of the replacement string. + + + + + In the replacement string, %1 must be followed by at least one digit when not escaped. + + + + + In the replacement string, %1 can only be used to escape itself or %2, not %3 + + + + + %1 matches newline characters + + + + + %1 and %2 match the start and end of a line. + + + + + Matches are case insensitive + + + + + Whitespace characters are removed, except when they appear in character classes + + + + + %1 is an invalid regular expression pattern: %2 + + + + + %1 is an invalid flag for regular expressions. Valid flags are: + + + + + If the first argument is the empty sequence or a zero-length string (no namespace), a prefix cannot be specified. Prefix %1 was specified. + + + + + It will not be possible to retrieve %1. + + + + + The root node of the second argument to function %1 must be a document node. %2 is not a document node. + + + + + The default collection is undefined + + + + + %1 cannot be retrieved + + + + + The normalization form %1 is unsupported. The supported forms are %2, %3, %4, and %5, and none, i.e. the empty string (no normalization). + + + + + A zone offset must be in the range %1..%2 inclusive. %3 is out of range. + + + + + %1 is not a whole number of minutes. + + + + + Required cardinality is %1; got cardinality %2. + + + + + The item %1 did not match the required type %2. + + + + + %1 is an unknown schema type. + + + + + Only one %1 declaration can occur in the query prolog. + + + + + The initialization of variable %1 depends on itself + + + + + No variable by name %1 exists + + + + + The variable %1 is unused + + + + + Version %1 is not supported. The supported XQuery version is 1.0. + + + + + The encoding %1 is invalid. It must contain Latin characters only, must not contain whitespace, and must match the regular expression %2. + + + + + No function with signature %1 is available + + + + + A default namespace declaration must occur before function, variable, and option declarations. + + + + + Namespace declarations must occur before function, variable, and option declarations. + + + + + Module imports must occur before function, variable, and option declarations. + + + + + It is not possible to redeclare prefix %1. + + + + + Only the prefix %1 can be declared to bind the namespace %2. By default, it is already bound to the prefix %1. + + + + + Prefix %1 is already declared in the prolog. + + + + + The name of an option must have a prefix. There is no default namespace for options. + + + + + The Schema Import feature is not supported, and therefore %1 declarations cannot occur. + + + + + The target namespace of a %1 cannot be empty. + + + + + The module import feature is not supported + + + + + A variable by name %1 has already been declared in the prolog. + + + + + No value is available for the external variable by name %1. + + + + + The namespace for a user defined function cannot be empty(try the predefined prefix %1 which exists for cases like this) + + + + + The namespace %1 is reserved; therefore user defined functions may not use it. Try the predefined prefix %2, which exists for these cases. + + + + + The namespace of a user defined function in a library module must be equivalent to the module namespace. In other words, it should be %1 instead of %2 + + + + + A function already exists with the signature %1. + + + + + No external functions are supported. All supported functions can be used directly, without first declaring them as external + + + + + An argument by name %1 has already been declared. Every argument name must be unique. + + + + + The name of a variable bound in a for-expression must be different from the positional variable. Hence, the two variables named %1 collide. + + + + + The Schema Validation Feature is not supported. Hence, %1-expressions may not be used. + + + + + None of the pragma expressions are supported. Therefore, a fallback expression must be present + + + + + The %1-axis is unsupported in XQuery + + + + + %1 is not a valid numeric literal. + + + + + No function by name %1 is available. + + + + + The namespace URI cannot be the empty string when binding to a prefix, %1. + + + + + %1 is an invalid namespace URI. + + + + + It is not possible to bind to the prefix %1 + + + + + Namespace %1 can only be bound to %2 (and it is, in either case, pre-declared). + + + + + Prefix %1 can only be bound to %2 (and it is, in either case, pre-declared). + + + + + Two namespace declaration attributes have the same name: %1. + + + + + The namespace URI must be a constant and cannot use enclosed expressions. + + + + + An attribute by name %1 has already appeared on this element. + + + + + A direct element constructor is not well-formed. %1 is ended with %2. + + + + + The name %1 does not refer to any schema type. + + + + + %1 is an complex type. Casting to complex types is not possible. However, casting to atomic types such as %2 works. + + + + + %1 is not an atomic type. Casting is only possible to atomic types. + + + + + %1 is not a valid name for a processing-instruction. Therefore this name test will never match. + + + + + %1 is not in the in-scope attribute declarations. Note that the schema import feature is not supported. + + + + + The name of an extension expression must be in a namespace. + + + + + empty + + + + + zero or one + + + + + exactly one + + + + + one or more + + + + + zero or more + + + + + Required type is %1, but %2 was found. + + + + + Promoting %1 to %2 may cause loss of precision. + + + + + The focus is undefined. + + + + + It's not possible to add attributes after any other kind of node. + + + + + An attribute by name %1 has already been created. + + + + + Only the Unicode Codepoint Collation is supported(%1). %2 is unsupported. + + + + + VolumeSlider + + + Muted + + + + + Volume: %1% + + + + + WebCore::PlatformScrollbar + + + Scroll here + Desplazar hasta aquí + + + + Left edge + Borde izquierdo + + + + Top + Parte superior + + + + Right edge + Borde derecho + + + + Bottom + Parte inferior + + + + Page left + Una página a la izquierda + + + + Page up + Una página hacia arriba + + + + Page right + Una página a la derecha + + + + Page down + Una página hacia abajo + + + + Scroll left + Desplazar hacia la izquierda + + + + Scroll up + Desplazar hacia arriba + + + + Scroll right + Desplazar hacia la derecha + + + + Scroll down + Desplazar hacia abajo + + + diff --git a/lang/qt_fr.qm b/lang/qt_fr.qm index 7c8c2e595a1ed73802779057aa5df81e5f036028..5553086f8f61003b79e4223989eee1c5ba711bdd 100644 GIT binary patch delta 12267 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 C%sR9H delta 13705 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 + AudioOutput @@ -74,12 +75,12 @@ Print - Impr écran + Impr écran Location: - Emplacement: + Emplacement: @@ -202,7 +203,7 @@ Le support audio et vidéo est désactivé. Phonon::Gstreamer::MediaObject - + Cannot start playback. Check your Gstreamer installation and make sure you @@ -250,37 +251,37 @@ have libgstreamer-plugins-base installed. Impossible de charger la source - + A required codec is missing. You need to install the following codec(s) to play this content: %0 - - - + + Un codec requis est manquant. Vous devez installer le codec suivant pour jouer le contenu: %0 + Des codecs requis sont manquants. Vous devez installer les codecs suivants pour jouer le contenu: %0 - + Could not open media source. - + Impossible d'ouvrir le média source. - + Invalid source type. - + Type de source invalide. - + Could not locate media source. - + Impossible de localiser le média source. - + Could not open audio device. The device is already in use. - + Impossible d'ouvrir le périphérique audio. Celui-ci est déjà en cours d'utilisation. - + Could not decode media source. - + Impossible de décoder le média source. @@ -953,7 +954,7 @@ en QAbstractSocket - + Host not found Hôte introuvable @@ -963,12 +964,12 @@ en Connexion refusée - + Socket operation timed out Opération socket expirée - + Socket is not connected Le socket n'est pas connecté @@ -976,17 +977,17 @@ en QAbstractSpinBox - + &Step up &Augmenter - + Step &down &Diminuer - + &Select All Tout &sélectionner @@ -994,7 +995,7 @@ en QApplication - + QT_LAYOUT_DIRECTION Translate this string to the string 'LTR' in left-to-right languages or to 'RTL' in right-to-left languages (such as Hebrew and Arabic) to get proper widget layout. LTR @@ -1267,22 +1268,22 @@ en QDateTimeEdit - + AM AM - + am am - + PM PM - + pm pm @@ -1321,7 +1322,7 @@ en QDialogButtonBox - + OK OK @@ -1429,29 +1430,29 @@ en QDirModel - + Name Nom - + Size Taille - + Kind Match OS X Finder Type - + Type All other platforms Type - + Date Modified Dernière Modification @@ -1538,12 +1539,12 @@ en Ouvrir - + &Open &Ouvrir - + &Save &Enregistrer @@ -1563,7 +1564,7 @@ en Afficher les fic&hiers cachés - + Directories Dossiers @@ -1573,13 +1574,13 @@ en Tous les fichiers (*) - + %1 already exists. Do you want to replace it? Le fichier %1 existe déjà. Voulez-vous l'écraser ? - + %1 File not found. Please verify the correct file name was given. @@ -1588,7 +1589,7 @@ Fichier introuvable. Veuillez vérifier que le nom du fichier est correct. - + My Computer Mon ordinateur @@ -1603,7 +1604,7 @@ Veuillez vérifier que le nom du fichier est correct. Fichiers de type : - + Directory: Dossier : @@ -1617,7 +1618,7 @@ Fichier introuvable. Veuillez vérifier que le nom du fichier est correct - + %1 Directory not found. Please verify the correct directory name was given. @@ -1626,19 +1627,19 @@ Dossier introuvable. Veuillez vérifier que le nom du dossier est correct. - + '%1' is write protected. Do you want to delete it anyway? '%1' est protégé en écriture. Voulez-vous quand même le supprimer? - + Are sure you want to delete '%1'? Etes-vous sûr de vouloir supprimer '%1'? - + Could not delete directory. Impossible de supprimer le dossier. @@ -1683,7 +1684,7 @@ Voulez-vous quand même le supprimer? Successeur - + New Folder Nouveau dossier @@ -1693,7 +1694,7 @@ Voulez-vous quand même le supprimer? &Nouveau dossier - + &Choose &Choisir @@ -1703,7 +1704,7 @@ Voulez-vous quand même le supprimer? Supprimer - + File &name: &Nom de fichier : @@ -1721,64 +1722,64 @@ Voulez-vous quand même le supprimer? QFileSystemModel - + %1 TB %1 To - + %1 GB %1 Go - + %1 MB %1 Mo - + %1 KB %1 Ko - + %1 bytes %1 octets - + Invalid filename Nom de fichier invalide - + <b>The name "%1" can not be used.</b><p>Try using another name, with fewer characters or no punctuations marks. <b>Le nom "%1" ne peut pas être utilisé.</b><p>Essayez un autre nom avec moins de caractères ou sans ponctuation. - + Name Nom - + Size Taille - + Kind Match OS X Finder Type - + Type All other platforms Type - + Date Modified Dernière modification @@ -2279,44 +2280,49 @@ Voulez-vous quand même le supprimer? Fragment HTTP invalide - + Proxy authentication required Le proxy requiert une authentification - + Authentication required Authentification requise - + Proxy requires authentication Le proxy requiert une authentification - + Host requires authentication L'hôte requiert une authentification - + Data corrupted Données corrompues - + Unknown protocol specified Protocole spécifié inconnu - + SSL handshake failed le handshake SSL a échoué - + Connection refused (or timed out) - + Connexion refusée (ou délai expiré) + + + + HTTPS connection requested but SSL support not compiled in + Connexion HTTPS requise mais le support SSL n'est pas compilé @@ -2441,27 +2447,27 @@ Voulez-vous quand même le supprimer? QIODevice - + Permission denied Accès refusé - + Too many open files Trop de fichiers ouverts simultanément - + No such file or directory Aucun fichier ou dossier de ce nom - + No space left on device Aucun espace disponible sur le périphérique - + Unknown error Erreur inconnue @@ -2469,22 +2475,22 @@ Voulez-vous quand même le supprimer? QInputContext - + XIM XIM - + XIM input method Méthode d'entrée XIM - + Windows input method Méthode d'entrée Windows - + Mac OS X input method Méthode d'entrée Mac OS X @@ -2522,32 +2528,32 @@ Voulez-vous quand même le supprimer? Impossible de supprimer la projection en mémoire de '%1' : %2 - + The plugin '%1' uses incompatible Qt library. (%2.%3.%4) [%5] Le plugin '%1' utilise une bibliothèque Qt incompatible. (%2.%3.%4) [%5] - + The plugin '%1' uses incompatible Qt library. Expected build key "%2", got "%3" Le plugin '%1' utilise une bibliothèque Qt incompatible. Clé attendue "%2", reçue "%3" - + Unknown error Erreur inconnue - + The shared library was not found. La bibliothèque partagée est introuvable. - + The file '%1' is not a valid Qt plugin. Le fichier '%1' n'est pas un plugin Qt valide. - + The plugin '%1' uses incompatible Qt library. (Cannot mix debug and release libraries.) Le plugin '%1' utilise une bibliothèque Qt incompatible. (Il est impossible de mélanger des bibliothèques 'debug' et 'release'.) @@ -2555,37 +2561,37 @@ Voulez-vous quand même le supprimer? QLineEdit - + Select All Tout sélectionner - + &Undo &Annuler - + &Redo A&nnuler Annuler - + Cu&t Co&uper - + &Copy Cop&ier - + &Paste Co&ller - + Delete Supprimer @@ -2669,27 +2675,27 @@ Voulez-vous quand même le supprimer? QMYSQLDriver - + Unable to open database ' Impossible d'ouvrir la base de données ' - + Unable to connect Impossible d'établir une connexion - + Unable to begin transaction Impossible de démarrer la transaction - + Unable to commit transaction Impossible de soumettre la transaction - + Unable to rollback transaction Impossible d'annuler la transaction @@ -2697,57 +2703,57 @@ Voulez-vous quand même le supprimer? QMYSQLResult - + Unable to fetch data Impossible de récuperer des données - + Unable to execute query Impossible d'exécuter la requête - + Unable to store result Impossible de stocker le résultat - + Unable to prepare statement Impossible de préparer l'instruction - + Unable to reset statement Impossible de réinitialiser l'instruction - + Unable to bind value Impossible d'attacher la valeur - + Unable to execute statement Impossible d'exécuter la requête - + Unable to bind outvalues Impossible d'attacher les valeurs de sortie - + Unable to store statement results Impossible de stocker les résultats de la requête - + Unable to execute next query Impossible d'exécuterla prochaine requête - + Unable to store next result Impossible de stocker le prochain résultat @@ -2755,7 +2761,7 @@ Voulez-vous quand même le supprimer? QMdiArea - + (Untitled) (Sans titre) @@ -2763,92 +2769,92 @@ Voulez-vous quand même le supprimer? QMdiSubWindow - + %1 - [%2] %1 - [%2] - + Close Fermer - + Minimize Réduire - + Restore Down Restaurer en bas - + &Restore &Restaurer - + &Move &Déplacer - + &Size &Taille - + Mi&nimize Réd&uire - + Ma&ximize Ma&ximiser - + Stay on &Top &Rester au premier plan - + &Close &Fermer - + - [%1] - + Maximize Maximiser - + Unshade Restaurer - + Shade Ombrer - + Restore Restaurer - + Help Aide - + Menu Menu @@ -2874,62 +2880,62 @@ Voulez-vous quand même le supprimer? QMenuBar - + About A propos - + Config Configuration - + Preference Préférence - + Options Options - + Setting Paramètre - + Setup Réglage - + Quit Quitter - + Exit Quitter - + About %1 A propos de %1 - + About Qt À propos de Qt - + Preferences Préférences - + Quit %1 Quitter %1 @@ -2942,17 +2948,17 @@ Voulez-vous quand même le supprimer? OK - + About Qt À propos de Qt - + Help Aide - + <p>This program uses Qt version %1.</p> <p>Ce programme utilise la version %1 de Qt.</p> @@ -2972,12 +2978,12 @@ Voulez-vous quand même le supprimer? Cacher les détails... - + <p>This program uses Qt Open Source Edition version %1.</p><p>Qt Open Source Edition is intended for the development of Open Source applications. You need a commercial Qt license for development of proprietary (closed source) applications.</p><p>Please see <a href="http://www.trolltech.com/company/model/">www.trolltech.com/company/model/</a> for an overview of Qt licensing.</p> <p>Ce programme utilise Qt Open Source Edition version %1.</p><p>Qt Open Source Edition est prévu pour le développement d'applications Open Source. Vous devez avoir un license commerciale de Qt pour développer des applications propiétaires (Closed Source).</p><p>Vous pouvez aller sur <a href="http://www.trolltech.com/company/model/">www.trolltech.com/company/model/</a> pour plus d'informations sur les licenses Qt.</p> - + <h3>About Qt</h3>%1<p>Qt is a C++ toolkit for cross-platform application development.</p><p>Qt provides single-source portability across MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux, and all major commercial Unix variants. Qt is also available for embedded devices as Qt Embedded.</p><p>Qt is a Trolltech product. See <a href="http://www.trolltech.com/qt/">www.trolltech.com/qt/</a> for more information.</p> <h3>A propos de Qt</h3>%1<p>Qt est un toolkit C++ pour le développement d'application multi-plateforme.</p><p>Qt fournit la portabilité de votre source pour MS&nbsp;Windows, Mac&nbsp;OS&nbsp;X, Linux, toutes les variantes majeures d'Unix. Qt est aussi disponible pour les périphériques embarqués avec Qt Embedded.</p><p>Qt est un produit de Trolltech. Voir <a href="http://www.trolltech.com/qt/">www.trolltech.com/qt/</a> pour plus d'informations.</p> @@ -3190,7 +3196,7 @@ Voulez-vous quand même le supprimer? QNetworkReply - + Error downloading %1 - server replied: %2 Erreur lors du téléchargement de %1 - le serveur a répondu: %2 @@ -3407,12 +3413,12 @@ Voulez-vous quand même le supprimer? QPPDOptionsModel - + Name Nom - + Value Valeur @@ -3420,32 +3426,32 @@ Voulez-vous quand même le supprimer? QPSQLDriver - + Unable to connect Impossible d'établir une connexion - + Could not begin transaction Impossible de démarrer la transaction - + Could not commit transaction Impossible de soumettre la transaction - + Could not rollback transaction Impossible d'annuler la transaction - + Unable to subscribe Impossible de s'inscrire - + Unable to unsubscribe Impossible de se désinscrire @@ -3569,12 +3575,12 @@ Voulez-vous quand même le supprimer? QPluginLoader - + Unknown error Erreur inconnue - + The plugin was not loaded. Le plugin n'a pas été chargé. @@ -3582,12 +3588,12 @@ Voulez-vous quand même le supprimer? QPrintDialog - + locally connected connecté en local - + unknown inconnu @@ -3767,7 +3773,7 @@ Voulez-vous quand même le supprimer? US Common #10 Envelope (105 x 241 mm) - + Aliases: %1 Alias : %1 @@ -3832,7 +3838,7 @@ Voulez-vous quand même le supprimer? Imprimante - + Print To File ... Imprimer dans un fichier... @@ -3902,14 +3908,14 @@ Voulez-vous quand même le supprimer? Impression recto verso - + File %1 is not writable. Please choose a different file name. Impossible d'écrire dans le fichier %1. Veuillez choisir un nom de fichier différent. - + %1 already exists. Do you want to overwrite it? %1 existe. @@ -3926,7 +3932,7 @@ Voulez-vous l'écraser? <qt>voulez-vous l'écraser?</qt> - + %1 is a directory. Please choose a different file name. %1 est un dossier. @@ -4088,37 +4094,37 @@ Veuillez choisir un nom de fichier différent. Personnalisé - + &Options >> - + &Print Im&primer - + &Options << - + Print to File (PDF) Imprimer dans un fichier (PDF) - + Print to File (Postscript) Imprimer dans un fichier (PostScript) - + Local file Fichier local - + Write %1 file Ecriture du fichier %1 @@ -4580,32 +4586,32 @@ Veuillez choisir un nom de fichier différent. QScrollBar - + Scroll here Défiler jusqu'ici - + Left edge Extrême gauche - + Top En haut - + Right edge Extrême droite - + Bottom En bas - + Page left Page précédente @@ -4615,7 +4621,7 @@ Veuillez choisir un nom de fichier différent. Page précédente - + Page right Page suivante @@ -4625,22 +4631,22 @@ Veuillez choisir un nom de fichier différent. Page suivante - + Scroll left Défiler vers la gauche - + Scroll up Défiler vers le haut - + Scroll right Défiler vers la droite - + Scroll down Défiler vers le bas @@ -5182,32 +5188,32 @@ Veuillez choisir un nom de fichier différent. Retourner - + Ctrl Ctrl - + Shift Maj - + Alt Alt - + Meta Méta - + + + - + F%1 F%1 @@ -5327,52 +5333,52 @@ Veuillez choisir un nom de fichier différent. QSslSocket - + Unable to write data: %1 Impossible d'écrire les données : %1 - + Error while reading: %1 Erreur lors de la lecture : %1 - + Error during SSL handshake: %1 Erreur lors de la poignée de main SSL : %1 - + Error creating SSL context (%1) Erreur lors de la création du contexte SSL (%1) - + Invalid or empty cipher list (%1) La list de chiffrements est invalide ou vide (%1) - + Error creating SSL session, %1 Erreur lors de la création de la session SSL, %1 - + Error creating SSL session: %1 Erreur lors de la création de la session SSL : %1 - + Cannot provide a certificate with no key, %1 Impossible de fournir un certificat sans clé, %1 - + Error loading local certificate, %1 Erreur lors du chargement du certificat local, %1 - + Error loading private key, %1 Erreur lors du chargement de la clé privée, %1 @@ -5382,7 +5388,7 @@ Veuillez choisir un nom de fichier différent. La clé privée ne certifie pas la clé publique, %1 - + Private key does not certificate public key, %1 La clé privée ne certifie pas la clé publique, %1 @@ -5442,42 +5448,42 @@ Veuillez choisir un nom de fichier différent. QTextControl - + &Undo &Annuler - + &Redo &Répéter - + Cu&t Co&uper - + &Copy Cop&ier - + Copy &Link Location Copier l'adresse du &lien - + &Paste Co&ller - + Delete Supprimer - + Select All Tout sélectionner @@ -5527,12 +5533,12 @@ Veuillez choisir un nom de fichier différent. QUndoStack - + Undo Annuler - + Redo Répéter @@ -5540,57 +5546,57 @@ Veuillez choisir un nom de fichier différent. QUnicodeControlCharacterMenu - + LRM Left-to-right mark LRM Left-to-right mark - + RLM Right-to-left mark RLM Right-to-left mark - + ZWJ Zero width joiner ZWJ Zero width joiner - + ZWNJ Zero width non-joiner ZWNJ Zero width non-joiner - + ZWSP Zero width space ZWSP Zero width space - + LRE Start of left-to-right embedding LRE Start of left-to-right embedding - + RLE Start of right-to-left embedding RLE Start of right-to-left embedding - + LRO Start of left-to-right override LRO Start of left-to-right override - + RLO Start of right-to-left override RLO Start of right-to-left override - + PDF Pop directional formatting PDF Pop directional formatting - + Insert Unicode control character Insérer caractère de contrôle Unicode @@ -5598,32 +5604,32 @@ Veuillez choisir un nom de fichier différent. QWebFrame - + Request cancelled Requête annulée - + Request blocked Requête bloquée - + Cannot show URL Impossible d'afficher l'URL - + Frame load interruped by policy change Chargement de la frame interrompu par un changement de configuration. - + Cannot show mimetype Impossible d'afficher le mimetype - + File does not exist Le fichier n'existe pas @@ -5919,26 +5925,26 @@ Veuillez choisir un nom de fichier différent. %1 (%2x%3 pixels) - + Web Inspector - %2 Inspecteur Web - %2 - + Bad HTTP request - + Requête HTTP erronée This is a searchable index. Enter search keywords: text that appears at the start of nearly-obsolete web pages in the form of a 'searchable index' - + Ceci est un index. Veuillez saisir les mots-clé : QWhatsThisAction - + What's This? Qu'est-ce que c'est ? @@ -5946,7 +5952,7 @@ Veuillez choisir un nom de fichier différent. QWidget - + * * @@ -6067,17 +6073,17 @@ Veuillez choisir un nom de fichier différent. Fermer - + Sh&ade &Enrouler - + %1 - [%2] %1 - [%2] - + &Unshade &Dérouler @@ -6208,52 +6214,52 @@ Veuillez choisir un nom de fichier différent. Conteny supplémentaire à la fin du document. - + Invalid entity value. Valeur de l'entité invalide. - + Invalid XML character. Caractère XML invalide. - + Sequence ']]>' not allowed in content. Séquence ']]>' interdite dans le contenu. - + Namespace prefix '%1' not declared Le préfixe de namespace '%1' non déclaré - + Attribute redefined. Attribut redéfini. - + Unexpected character '%1' in public id literal. Caractère '%1' inattendu dans un 'public id literal'. - + Invalid XML version string. Version XML invalide. - + Unsupported XML version. Version XML non supportée. - + %1 is an invalid encoding name. %1 n'est pas un encodage valide. - + Encoding %1 is unsupported Encodage %1 n'est pas supporté @@ -6263,42 +6269,42 @@ Veuillez choisir un nom de fichier différent. Encodage XML invalide. - + Standalone accepts only yes or no. 'Standalone' n'accepte que 'yes' ou 'no'. - + Invalid attribute in XML declaration. Attribut invalide dans la déclaration XML. - + Premature end of document. Fin de document prématurée. - + Invalid document. Document invalide. - + Expected Attendu - + , but got ' , mais eu ' - + Unexpected ' Inattendu ' - + Expected character data. Character data attendu. @@ -6373,7 +6379,7 @@ Veuillez choisir un nom de fichier différent. Encodage du contenu incorrect. - + The standalone pseudo attribute must appear after the encoding. Le pseudo attribut standalone doit apparaître après l'encodage. @@ -6386,12 +6392,12 @@ Veuillez choisir un nom de fichier différent. QtXmlPatterns - + An %1-attribute with value %2 has already been declared. Un attribute %1 avec la valeur %2 est déjà déclaré. - + An %1-attribute must have a valid %2 as value, which %3 isn't. Un attribute %1 doit avoir un %2 valide, %3 ne l'a pas. @@ -6401,14 +6407,14 @@ Veuillez choisir un nom de fichier différent. Le réseau ne répond pas. - + Element %1 can't be serialized because it appears outside the document element. L'élément %1 ne peut pas être sérialisé parce qu'il est hors de l'élément document. Attribute element %1 can't be serialized because it appears at the top level. - L'élément attribute %1 ne peut pas être sérialisé parce qu'il apparaît comme racine. + L'élément attribute %1 ne peut pas être sérialisé parce qu'il apparaît comme racine. @@ -6762,12 +6768,12 @@ Veuillez choisir un nom de fichier différent. Les blancs sont supprimés excepté quand ils apparaissent dans les classes de caractère - + %1 is an invalid regular expression pattern: %2 %1 est un modèle d'expression régulière invalide: %2 - + %1 is an invalid flag for regular expressions. Valid flags are: %1 est un flag invalide pour des expressions régulières. Les flags valides sont : @@ -6777,7 +6783,7 @@ Veuillez choisir un nom de fichier différent. Si le premier argument est une sequence vide ou un chaîne vide (sans namespace), un préfixe ne peut être spécifié. Le préfixe %1 a été spécifié. - + It will not be possible to retrieve %1. Il sera impossible de récupérer %1. @@ -7114,7 +7120,12 @@ Veuillez choisir un nom de fichier différent. %1 is not a whole number of minutes. - + %1 n'est pas un nombre entier de minutes. + + + + Attribute %1 can't be serialized because it appears at the top level. + L'attribut %1 ne peut pas être sérialisé car il apparaît à la racine. @@ -7130,4 +7141,72 @@ Veuillez choisir un nom de fichier différent. + + WebCore::PlatformScrollbar + + + Scroll here + Défiler jusqu'ici + + + + Left edge + Extrême gauche + + + + Top + En haut + + + + Right edge + Extrême droite + + + + Bottom + En bas + + + + Page left + Page précédente + + + + Page up + Page précédente + + + + Page right + Page suivante + + + + Page down + Page suivante + + + + Scroll left + Défiler vers la gauche + + + + Scroll up + Défiler vers le haut + + + + Scroll right + Défiler vers la droite + + + + Scroll down + Défiler vers le bas + + diff --git a/man/files/fr.ISO8859-1/man1/qelectrotech.1 b/man/files/fr.ISO8859-1/man1/qelectrotech.1 index ad0162a94..452231433 100644 --- a/man/files/fr.ISO8859-1/man1/qelectrotech.1 +++ b/man/files/fr.ISO8859-1/man1/qelectrotech.1 @@ -46,7 +46,7 @@ Beno Xavier Guerrin .SH SIGNALER DES BUGS -Si vous rencontrez un comportement qui vous parat anormal dans l'application, consultez notre FAQ et notre BugTracker pour voir si le problme n'est pas dj connu. Dans la ngative, soumettez un rapport de bug via le BugTracker. +Si vous rencontrez un comportement qui vous parat anormal dans l'application, consultez notre FAQ et notre BugTracker pour voir si le problme n'est pas dj connu. Dans la ngative, soumettez un rapport de bug via le BugTracker. .SH COPYRIGHT Copyright Les dveloppeurs de QElectroTech @@ -56,4 +56,4 @@ Licence : GNU/GPL v2+ : +Site officiel : diff --git a/man/files/fr.UTF-8/man1/qelectrotech.1 b/man/files/fr.UTF-8/man1/qelectrotech.1 index 2b468fedf..e67276109 100644 --- a/man/files/fr.UTF-8/man1/qelectrotech.1 +++ b/man/files/fr.UTF-8/man1/qelectrotech.1 @@ -46,7 +46,7 @@ Benoît Ansieau Xavier Guerrin .SH SIGNALER DES BUGS -Si vous rencontrez un comportement qui vous paraît anormal dans l'application, consultez notre FAQ et notre BugTracker pour voir si le problème n'est pas déjà connu. Dans la négative, soumettez un rapport de bug via le BugTracker. +Si vous rencontrez un comportement qui vous paraît anormal dans l'application, consultez notre FAQ et notre BugTracker pour voir si le problème n'est pas déjà connu. Dans la négative, soumettez un rapport de bug via le BugTracker. .SH COPYRIGHT Copyright © Les développeurs de QElectroTech @@ -56,4 +56,4 @@ Licence : GNU/GPL v2+ : +Site officiel : diff --git a/man/files/fr/man1/qelectrotech.1 b/man/files/fr/man1/qelectrotech.1 index 2b468fedf..e67276109 100644 --- a/man/files/fr/man1/qelectrotech.1 +++ b/man/files/fr/man1/qelectrotech.1 @@ -46,7 +46,7 @@ Benoît Ansieau Xavier Guerrin .SH SIGNALER DES BUGS -Si vous rencontrez un comportement qui vous paraît anormal dans l'application, consultez notre FAQ et notre BugTracker pour voir si le problème n'est pas déjà connu. Dans la négative, soumettez un rapport de bug via le BugTracker. +Si vous rencontrez un comportement qui vous paraît anormal dans l'application, consultez notre FAQ et notre BugTracker pour voir si le problème n'est pas déjà connu. Dans la négative, soumettez un rapport de bug via le BugTracker. .SH COPYRIGHT Copyright © Les développeurs de QElectroTech @@ -56,4 +56,4 @@ Licence : GNU/GPL v2+ : +Site officiel : diff --git a/man/files/man1/qelectrotech.1 b/man/files/man1/qelectrotech.1 index 772b04079..bf89ea612 100644 --- a/man/files/man1/qelectrotech.1 +++ b/man/files/man1/qelectrotech.1 @@ -46,7 +46,7 @@ Benoit Ansieau Xavier Guerrin .SH REPORTING BUGS -If you encounter a behavior in the application that looks unusual to you, browse our FAQ and our BugTracker to check if the problem is already known. In the negative, please submit a bugreport via the BugTracker. +If you encounter a behavior in the application that looks unusual to you, browse our FAQ and our BugTracker to check if the problem is already known. In the negative, please submit a bugreport via the BugTracker. .SH COPYRIGHT Copyright (c) QElectroTech developers. @@ -56,4 +56,4 @@ License : GNU/GPL v2+ : +Official website : diff --git a/misc/launch_qet.sh b/misc/launch_qet.sh index a8bb43709..a8091cfe4 100755 --- a/misc/launch_qet.sh +++ b/misc/launch_qet.sh @@ -5,7 +5,7 @@ QET_EXE="../qelectrotech" QET_ELEMENTS_DIR="../elements/" QET_CONFIG_DIR="" QET_LANG_DIR="../lang/" -REDEFINE_LANG="" +# REDEFINE_LANG="es" # checks for the qelectrotech binary executable file if [ ! -x "${QET_EXE}" ]; then diff --git a/misc/qelectrotech.conf b/misc/qelectrotech.conf index 05e157a53..f9454429c 100644 --- a/misc/qelectrotech.conf +++ b/misc/qelectrotech.conf @@ -11,7 +11,7 @@ defaultconductortext=_ defaultconductortype=multi defaultdate=null defaultfilename= -defaultfolio= +defaultfolio=%id/%total defaulttitle= [elementeditor] diff --git a/misc/qelectrotech.desktop b/misc/qelectrotech.desktop index 387c7e746..fc92c90f7 100644 --- a/misc/qelectrotech.desktop +++ b/misc/qelectrotech.desktop @@ -1,4 +1,6 @@ [Desktop Entry] +Version=1.0 +Encoding=UTF-8 Name=QElectroTech Exec=qelectrotech Icon=qet @@ -8,5 +10,8 @@ MimeType=application/x-qet-project;application/x-qet-element; Categories=Office;Qt;VectorGraphics;Science;Electricity;Engineering; Comment=An electric diagrams editor. Comment[fr]=Un éditeur de schémas électriques +Comment[fr_FR]=Un éditeur de schémas électriques +Comment[es]=Un editor de esquemas eléctricos GenericName=Electric diagrams editor GenericName[fr]=Un éditeur de schémas électriques +GenericName[fr_FR]=Un éditeur de schémas électriques diff --git a/misc/qelectrotech.spec b/misc/qelectrotech.spec index d30389400..8b125a19b 100644 --- a/misc/qelectrotech.spec +++ b/misc/qelectrotech.spec @@ -8,7 +8,7 @@ Release: 0.1.svn%{svnrel}%{?dist} Group: Applications/Productivity License: GPLv2 -Url: http://qelectrotech.tuxfamily.org/ +Url: http://qelectrotech.org/ # svn co svn://svn.tuxfamily.org/svnroot/qet/qet/trunk qelectrotech # tar cvjf /home/rpmbuild/SOURCES/qelectrotech-svn.tar.bz2 --exclude .svn qelectrotech Source0: qelectrotech-svn.tar.bz2 diff --git a/misc/x-qet-element.desktop b/misc/x-qet-element.desktop index 5b9e51ee1..fce26165a 100644 --- a/misc/x-qet-element.desktop +++ b/misc/x-qet-element.desktop @@ -6,3 +6,4 @@ DefaultApp=qelectrotech Patterns=*.elmt; Comment=QElectroTech element file Comment[fr]=Fichier élément QElectroTech +Comment[es]=Archivo elemento QElectroTech diff --git a/misc/x-qet-element.xml b/misc/x-qet-element.xml index ffeecb3b9..08bd43c8b 100644 --- a/misc/x-qet-element.xml +++ b/misc/x-qet-element.xml @@ -2,4 +2,5 @@ QElectroTech element file Fichier élément QElectroTech + Archivo elemento QElectroTech diff --git a/misc/x-qet-project.desktop b/misc/x-qet-project.desktop index 3ad0025e7..f21de9d96 100644 --- a/misc/x-qet-project.desktop +++ b/misc/x-qet-project.desktop @@ -6,3 +6,4 @@ DefaultApp=qelectrotech Patterns=*.qet; Comment=QElectroTech project file Comment[fr]=Fichier projet QElectroTech +Comment[es]=Archivo proyecto QElectroTech diff --git a/misc/x-qet-project.xml b/misc/x-qet-project.xml index cfcc6d666..8f2718238 100644 --- a/misc/x-qet-project.xml +++ b/misc/x-qet-project.xml @@ -2,4 +2,5 @@ QElectroTech project file Fichier projet QElectroTech + Archivo proyecto QElectroTech diff --git a/qelectrotech.pro b/qelectrotech.pro index 543756f30..400100c42 100644 --- a/qelectrotech.pro +++ b/qelectrotech.pro @@ -3,7 +3,7 @@ ###################################################################### # Chemins utilises pour la compilation et l'installation de QET -!win32 { +unix { # Chemins UNIX COMPIL_PREFIX = '/usr/local/' INSTALL_PREFIX = '/usr/local/' @@ -18,7 +18,8 @@ QET_DESKTOP_PATH = 'share/applications/' QET_ICONS_PATH = 'share/icons/' QET_MAN_PATH = 'man/' -} else { +} +win32 { # Chemins Windows COMPIL_PREFIX = './' INSTALL_PREFIX = './' @@ -27,6 +28,21 @@ QET_LANG_PATH = 'lang/' QET_LICENSE_PATH = './' } +macx { + # Chemins MacOS X + COMPIL_PREFIX = '/usr/local/' + INSTALL_PREFIX = '/usr/local/' + QET_BINARY_PATH = 'bin/' + QET_COMMON_COLLECTION_PATH = 'share/qelectrotech/elements/' + QET_LANG_PATH = 'share/qelectrotech/lang/' + QET_EXAMPLES_PATH = 'share/qelectrotech/examples/' + QET_LICENSE_PATH = 'doc/qelectrotech/' + QET_MIME_XML_PATH = '../share/mime/application/' + QET_MIME_DESKTOP_PATH = '../share/mimelnk/application/' + QET_DESKTOP_PATH = 'share/applications/' + QET_ICONS_PATH = 'share/icons/' + QET_MAN_PATH = 'man/' +} # Commenter la ligne ci-dessous pour desactiver l'option --common-elements-dir DEFINES += QET_ALLOW_OVERRIDE_CED_OPTION @@ -42,6 +58,7 @@ INCLUDEPATH += sources sources/editor # Fichiers sources HEADERS += sources/aboutqet.h \ + sources/basicmoveelementshandler.h \ sources/borderinset.h \ sources/borderproperties.h \ sources/borderpropertieswidget.h \ @@ -58,41 +75,66 @@ HEADERS += sources/aboutqet.h \ sources/diagramcommands.h \ sources/diagramcontent.h \ sources/diagramprintdialog.h \ - sources/diagramview.h \ + sources/diagramschooser.h \ sources/diagramtextitem.h \ + sources/diagramview.h \ sources/element.h \ + sources/elementdefinition.h \ sources/elementdeleter.h \ + sources/elementdialog.h \ sources/elementscategorieslist.h \ sources/elementscategorieswidget.h \ sources/elementscategory.h \ sources/elementscategorydeleter.h \ sources/elementscategoryeditor.h \ + sources/elementscollection.h \ + sources/elementscollectionitem.h \ + sources/elementslocation.h \ sources/elementspanel.h \ sources/elementspanelwidget.h \ sources/elementtextitem.h \ sources/exportdialog.h \ + sources/fileelementscategory.h \ + sources/fileelementscollection.h \ + sources/fileelementdefinition.h \ sources/fixedelement.h \ + sources/ghostelement.h \ sources/hotspoteditor.h \ sources/insetproperties.h \ sources/insetpropertieswidget.h \ + sources/integrationmoveelementshandler.h \ + sources/interactivemoveelementshandler.h \ + sources/moveelementsdescription.h \ + sources/moveelementshandler.h \ sources/nameslist.h \ sources/nameslistwidget.h \ sources/newelementwizard.h \ sources/orientationset.h \ sources/orientationsetwidget.h \ + sources/projectview.h \ sources/qet.h \ sources/qetapp.h \ sources/qetarguments.h \ sources/qetdiagrameditor.h \ + sources/qetproject.h \ + sources/qetprintpreviewdialog.h \ + sources/qetregexpvalidator.h \ + sources/qettabbar.h \ + sources/qettabwidget.h \ sources/qetsingleapplication.h \ + sources/qfilenameedit.h \ sources/qgimanager.h \ sources/recentfiles.h \ sources/terminal.h \ + sources/xmlelementdefinition.h \ + sources/xmlelementscategory.h \ + sources/xmlelementscollection.h \ sources/editor/arceditor.h \ sources/editor/circleeditor.h \ sources/editor/customelementgraphicpart.h \ sources/editor/customelementpart.h \ sources/editor/editorcommands.h \ + sources/editor/elementcontent.h \ sources/editor/elementitemeditor.h \ sources/editor/elementscene.h \ sources/editor/elementview.h \ @@ -103,16 +145,19 @@ HEADERS += sources/aboutqet.h \ sources/editor/partellipse.h \ sources/editor/partline.h \ sources/editor/partpolygon.h \ + sources/editor/partrectangle.h \ sources/editor/partterminal.h \ sources/editor/parttext.h \ sources/editor/parttextfield.h \ sources/editor/polygoneditor.h \ sources/editor/qetelementeditor.h \ + sources/editor/rectangleeditor.h \ sources/editor/styleeditor.h \ sources/editor/terminaleditor.h \ sources/editor/texteditor.h \ sources/editor/textfieldeditor.h SOURCES += sources/aboutqet.cpp \ + sources/basicmoveelementshandler.cpp \ sources/borderinset.cpp \ sources/borderproperties.cpp \ sources/borderpropertieswidget.cpp \ @@ -128,37 +173,59 @@ SOURCES += sources/aboutqet.cpp \ sources/diagramcommands.cpp \ sources/diagramcontent.cpp \ sources/diagramprintdialog.cpp \ + sources/diagramschooser.cpp \ sources/diagramtextitem.cpp \ sources/diagramview.cpp \ sources/element.cpp \ + sources/elementdefinition.cpp \ sources/elementdeleter.cpp \ + sources/elementdialog.cpp \ sources/elementscategorieslist.cpp \ sources/elementscategorieswidget.cpp \ sources/elementscategory.cpp \ sources/elementscategorydeleter.cpp \ sources/elementscategoryeditor.cpp \ + sources/elementscollection.cpp \ + sources/elementslocation.cpp \ sources/elementspanel.cpp \ sources/elementspanelwidget.cpp \ sources/elementtextitem.cpp \ sources/exportdialog.cpp \ sources/fixedelement.cpp \ + sources/fileelementscategory.cpp \ + sources/fileelementscollection.cpp \ + sources/fileelementdefinition.cpp \ + sources/ghostelement.cpp \ sources/hotspoteditor.cpp \ sources/insetproperties.cpp \ sources/insetpropertieswidget.cpp \ + sources/integrationmoveelementshandler.cpp \ + sources/interactivemoveelementshandler.cpp \ sources/main.cpp \ + sources/moveelementsdescription.cpp \ sources/nameslist.cpp \ sources/nameslistwidget.cpp \ sources/newelementwizard.cpp \ sources/orientationset.cpp \ sources/orientationsetwidget.cpp \ + sources/projectview.cpp \ sources/qet.cpp \ sources/qetapp.cpp \ sources/qetarguments.cpp \ sources/qetdiagrameditor.cpp \ + sources/qetproject.cpp \ + sources/qetprintpreviewdialog.cpp \ + sources/qetregexpvalidator.cpp \ + sources/qettabbar.cpp \ + sources/qettabwidget.cpp \ sources/qetsingleapplication.cpp \ + sources/qfilenameedit.cpp \ sources/qgimanager.cpp \ sources/recentfiles.cpp \ sources/terminal.cpp \ + sources/xmlelementdefinition.cpp \ + sources/xmlelementscategory.cpp \ + sources/xmlelementscollection.cpp \ sources/editor/arceditor.cpp \ sources/editor/circleeditor.cpp \ sources/editor/customelementgraphicpart.cpp \ @@ -174,17 +241,20 @@ SOURCES += sources/aboutqet.cpp \ sources/editor/partellipse.cpp \ sources/editor/partline.cpp \ sources/editor/partpolygon.cpp \ + sources/editor/partrectangle.cpp \ sources/editor/partterminal.cpp \ sources/editor/parttext.cpp \ sources/editor/parttextfield.cpp \ sources/editor/polygoneditor.cpp \ sources/editor/qetelementeditor.cpp \ + sources/editor/rectangleeditor.cpp \ sources/editor/styleeditor.cpp \ sources/editor/terminaleditor.cpp \ sources/editor/texteditor.cpp \ sources/editor/textfieldeditor.cpp RESOURCES += qelectrotech.qrc -TRANSLATIONS += lang/qet_en.ts lang/qt_fr.ts +TRANSLATIONS += lang/qet_en.ts lang/qet_es.ts lang/qet_fr.ts +TRANSLATIONS += lang/qt_es.ts lang/qt_fr.ts RC_FILE = ico/windows_icon/application_icon/qelectrotech.rc QT += xml svg network CONFIG += debug_and_release warn_on diff --git a/qelectrotech.qrc b/qelectrotech.qrc index 5bf4894f4..1192afdf2 100644 --- a/qelectrotech.qrc +++ b/qelectrotech.qrc @@ -1,12 +1,13 @@ - LICENSE - ico/qet.png - ico/qet-16.png - ico/qelectrotech.png - ico/allowed.png + ico/1leftarrow.png + ico/1rightarrow.png + ico/2leftarrow.png + ico/2rightarrow.png ico/add_col.png ico/add_row.png + ico/allowed.png + ico/all_pages.png ico/arc.png ico/bring_forward.png ico/button_cancel.png @@ -15,32 +16,47 @@ ico/category_edit.png ico/category_new.png ico/circle.png - ico/conductor.png ico/conductor2.png ico/conductor3.png - ico/conf_new_diagram.png + ico/conductor.png ico/configure.png + ico/conf_new_diagram_110.png + ico/confprint.png ico/copy.png ico/cut.png ico/delete.png + ico/diagram_add.png + ico/diagram_del.png + ico/diagram.png ico/east.png ico/edit.png ico/ellipse.png + ico/endline-circle.png + ico/endline-diamond.png + ico/endline-none.png + ico/endline-simple.png + ico/endline-triangle.png ico/entrer_fs.png ico/erase.png ico/exit.png ico/export.png ico/fileclose.png - ico/folder.png ico/folder_home.png + ico/folder.png ico/forbidden.png ico/ground.png ico/hotspot.png ico/import.png ico/info.png + ico/item_cancel.png + ico/item_copy.png + ico/item_move.png + ico/landscape.png ico/line.png + ico/lock.png ico/lower.png ico/masquer.png + ico/mdiarea_bg.png ico/move.png ico/names.png ico/neutral.png @@ -52,31 +68,49 @@ ico/phase.png ico/pivoter.png ico/polygon.png + ico/portrait.png ico/print.png + ico/printtype_pdf.png + ico/printtype_printer.png + ico/printtype_ps.png + ico/project.png + ico/qelectrotech.png + ico/qet-16.png + ico/qet.png ico/qt.png ico/raise.png + ico/rectangle.png ico/redo.png ico/reload.png ico/remove_col.png ico/remove_row.png ico/restaurer.png + ico/save_all.png ico/saveas.png ico/save.png ico/select.png ico/send_backward.png + ico/settings.png + ico/single_page.png ico/sortir_fs.png ico/south.png ico/splash.png + ico/start.png ico/terminal.png - ico/text.png ico/textfield.png + ico/text.png ico/toolbars.png + ico/two_pages.png ico/undo.png + ico/unlock.png + ico/view_fit_width.png + ico/view_fit_window.png ico/viewmagfit.png ico/viewmag-.png ico/viewmag.png ico/viewmag+.png ico/west.png ico/window_new.png + LICENSE diff --git a/sources/aboutqet.cpp b/sources/aboutqet.cpp index 455dc7784..c34a4d324 100644 --- a/sources/aboutqet.cpp +++ b/sources/aboutqet.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -16,6 +16,7 @@ along with QElectroTech. If not, see . */ #include +#include "qettabwidget.h" #include "aboutqet.h" #include "qet.h" @@ -25,16 +26,16 @@ */ AboutQET::AboutQET(QWidget *parent) : QDialog(parent) { // Titre, taille, comportement... - setWindowTitle(tr("\300 propos de QElectrotech")); + setWindowTitle(tr("\300 propos de QElectrotech", "window title")); setMinimumWidth(680); setMinimumHeight(350); setModal(true); // Trois onglets - QTabWidget *onglets = new QTabWidget(this); - onglets -> addTab(ongletAPropos(), tr("\300 &propos")); - onglets -> addTab(ongletAuteurs(), tr("A&uteurs")); - onglets -> addTab(ongletLicence(), tr("&Accord de licence")); + QETTabWidget *onglets = new QETTabWidget(this); + onglets -> addTab(ongletAPropos(), tr("\300 &propos","tab title")); + onglets -> addTab(ongletAuteurs(), tr("A&uteurs", "tab title")); + onglets -> addTab(ongletLicence(), tr("&Accord de licence", "tab title")); // Un bouton pour fermer la boite de dialogue QDialogButtonBox *boutons = new QDialogButtonBox(QDialogButtonBox::Close); @@ -64,7 +65,7 @@ QWidget *AboutQET::titre() const { QLabel *icone = new QLabel(); icone -> setPixmap(QIcon(":/ico/qelectrotech.png").pixmap(48, 48)); // label "QElectroTech" - QLabel *titre = new QLabel("QElectroTech v" + QET::version + ""); + QLabel *titre = new QLabel("QElectroTech v" + QET::displayedVersion + ""); titre -> setTextFormat(Qt::RichText); // le tout dans une grille QGridLayout *dispo_horiz = new QGridLayout(); @@ -83,10 +84,10 @@ QWidget *AboutQET::ongletAPropos() const { QLabel *apropos = new QLabel( tr("QElectroTech, une application de r\351alisation de sch\351mas \351lectriques.") + "

" + - tr("\251 2006-2008 Les d\351veloppeurs de QElectroTech") + + tr("\251 2006-2009 Les d\351veloppeurs de QElectroTech") + "

" - "" - "http://qelectrotech.tuxfamily.org/" + "" + "http://qelectrotech.org/" ); apropos -> setAlignment(Qt::AlignCenter); apropos -> setOpenExternalLinks(true); diff --git a/sources/aboutqet.h b/sources/aboutqet.h index 26893b3ac..dddb4c6dc 100644 --- a/sources/aboutqet.h +++ b/sources/aboutqet.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/basicmoveelementshandler.cpp b/sources/basicmoveelementshandler.cpp new file mode 100644 index 000000000..a07a2d703 --- /dev/null +++ b/sources/basicmoveelementshandler.cpp @@ -0,0 +1,126 @@ +#include "basicmoveelementshandler.h" + +/** + Constructeur + @param parent QObject parent +*/ +BasicMoveElementsHandler::BasicMoveElementsHandler(QObject *parent) : + MoveElementsHandler(parent), + already_exists_(QET::Erase), + not_readable_(QET::Ignore), + not_writable_(QET::Ignore), + error_(QET::Ignore), + rename_("renamed") +{ +} + +/** + Destructeur +*/ +BasicMoveElementsHandler::~BasicMoveElementsHandler() { +} + +/** + @param action Action a renvoyer si un item existe deja +*/ +void BasicMoveElementsHandler::setActionIfItemAlreadyExists(QET::Action action) { + already_exists_ = action; +} + +/** + @param action Action a renvoyer si un item n'est pas lisible +*/ +void BasicMoveElementsHandler::setActionIfItemIsNotReadable(QET::Action action) { + not_readable_ = action; +} + +/** + @param action Action a renvoyer si un item n'est pas accessible en ecriture +*/ +void BasicMoveElementsHandler::setActionIfItemIsNotWritable(QET::Action action) { + not_writable_ = action; +} + +/** + @param action Action a renvoyer si un item provoque une erreur +*/ +void BasicMoveElementsHandler::setActionIfItemTriggersAnError(QET::Action action) { + error_ = action; +} + +/** + @param name Nom a renvoyer pour une eventuelle operation de renommage + Il est toutefois deconseille de proceder a un renommage systematique, vu que + cette propriete est invariable. +*/ +void BasicMoveElementsHandler::setNameForRenamingOperation(const QString &name) { + rename_ = name; +} + +/** + @return l'action a effectuer si la categorie cible existe deja +*/ +QET::Action BasicMoveElementsHandler::categoryAlreadyExists(ElementsCategory *, ElementsCategory *) { + return(already_exists_); +} + +/** + @return l'action a effectuer si l'element cible existe deja +*/ +QET::Action BasicMoveElementsHandler::elementAlreadyExists(ElementDefinition *, ElementDefinition *) { + return(already_exists_); +} + +/** + @return l'action a effectuer si la categorie existe deja +*/ +QET::Action BasicMoveElementsHandler::categoryIsNotReadable(ElementsCategory *) { + return(not_readable_); +} + +/** + @return l'action a effectuer si l'element existe deja +*/ +QET::Action BasicMoveElementsHandler::elementIsNotReadable(ElementDefinition *) { + return(not_readable_); +} + +/** + @return l'action a effectuer si la categorie cible n'est pas accessible + en ecriture +*/ +QET::Action BasicMoveElementsHandler::categoryIsNotWritable(ElementsCategory *) { + return(not_writable_); +} + +/** + @return l'action a effectuer si l'element cible n'est pas accessible + en ecriture +*/ +QET::Action BasicMoveElementsHandler::elementIsNotWritable(ElementDefinition *) { + return(not_writable_); +} + +/** + @return l'action a effectuer lorsque l'erreur decrite dans la QString + s'est produite avec la categorie indiquee +*/ +QET::Action BasicMoveElementsHandler::errorWithACategory(ElementsCategory *, const QString &) { + return(error_); +} + +/** + @return l'action a effectuer lorsque l'erreur decrite dans la QString + s'est produite avec l'element indique +*/ +QET::Action BasicMoveElementsHandler::errorWithAnElement(ElementDefinition *, const QString &) { + return(error_); +} + +/** + @return le nom a utiliser pour le renommage si une methode de cet objet + a precedemment renvoye QET::Rename. +*/ +QString BasicMoveElementsHandler::nameForRenamingOperation() { + return(rename_); +} diff --git a/sources/basicmoveelementshandler.h b/sources/basicmoveelementshandler.h new file mode 100644 index 000000000..8007f93ce --- /dev/null +++ b/sources/basicmoveelementshandler.h @@ -0,0 +1,62 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef BASIC_MOVE_ELEMENTS_HANDLER +#define BASIC_MOVE_ELEMENTS_HANDLER +#include "moveelementshandler.h" +/** + Cette classe implemente basiquement la classe strategie MoveElementsHandler + Elle retourne toujours la meme action (parametrable) pour une methode + donnee. +*/ +class BasicMoveElementsHandler : public MoveElementsHandler { + Q_OBJECT + + // constructeurs, destructeur + public: + BasicMoveElementsHandler(QObject * = 0); + virtual ~BasicMoveElementsHandler(); + private: + BasicMoveElementsHandler(const BasicMoveElementsHandler &); + + // methodes + public: + virtual void setActionIfItemAlreadyExists(QET::Action); + virtual void setActionIfItemIsNotReadable(QET::Action); + virtual void setActionIfItemIsNotWritable(QET::Action); + virtual void setActionIfItemTriggersAnError(QET::Action); + virtual void setNameForRenamingOperation(const QString &); + + virtual QET::Action categoryAlreadyExists(ElementsCategory *src, ElementsCategory *dst); + virtual QET::Action elementAlreadyExists(ElementDefinition *src, ElementDefinition *dst); + virtual QET::Action categoryIsNotReadable(ElementsCategory *); + virtual QET::Action elementIsNotReadable(ElementDefinition *); + virtual QET::Action categoryIsNotWritable(ElementsCategory *); + virtual QET::Action elementIsNotWritable(ElementDefinition *); + virtual QET::Action errorWithACategory(ElementsCategory *, const QString &); + virtual QET::Action errorWithAnElement(ElementDefinition *, const QString &); + virtual QString nameForRenamingOperation(); + + // attributs + private: + QET::Action already_exists_; + QET::Action not_readable_; + QET::Action not_writable_; + QET::Action error_; + QString rename_; +}; +#endif diff --git a/sources/borderinset.cpp b/sources/borderinset.cpp index d82360485..b103cc5cf 100644 --- a/sources/borderinset.cpp +++ b/sources/borderinset.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -18,7 +18,6 @@ #include #include "borderinset.h" #include "qetapp.h" -#include "qetdiagrameditor.h" #include "math.h" /** @@ -28,16 +27,17 @@ */ BorderInset::BorderInset(QObject *parent) : QObject(parent) { // dimensions par defaut du schema - importBorder(QETDiagramEditor::defaultBorderProperties()); + importBorder(BorderProperties()); // contenu par defaut du cartouche - importInset(QETDiagramEditor::defaultInsetProperties()); + importInset(InsetProperties()); // hauteur du cartouche inset_height = 50.0; display_inset = true; display_border = true; + setFolioData(1, 1); updateRectangles(); } @@ -94,9 +94,10 @@ InsetProperties BorderInset::exportInset() { void BorderInset::importInset(const InsetProperties &ip) { bi_author = ip.author; bi_date = ip.date; - bi_title = ip.title; + setTitle(ip.title); bi_folio = ip.folio; bi_filename = ip.filename; + emit(needFolioData()); } /** @@ -158,6 +159,8 @@ void BorderInset::displayRows(bool dr) { /** @param db true pour afficher la bordure du schema, false sinon + Note : si l'affichage de la bordure est ainsi desactivee, les lignes et + colonnes ne seront pas dessinees. */ void BorderInset::displayBorder(bool db) { bool change = (db != display_border); @@ -208,10 +211,10 @@ void BorderInset::draw(QPainter *qp, qreal x, qreal y) { // dessine le cadre if (display_border) qp -> drawRect(diagram); - qp -> setFont(QFont(QETApp::diagramTextsFont(), qp -> font().pointSize())); + qp -> setFont(QFont(QETApp::diagramTextsFont(), QETApp::diagramTextsSize())); // dessine la case vide qui apparait des qu'il y a un entete - if (display_columns || display_rows) { + if (display_border && (display_columns || display_rows)) { qp -> setBrush(Qt::white); QRectF first_rectangle( diagram.topLeft().x(), @@ -223,7 +226,7 @@ void BorderInset::draw(QPainter *qp, qreal x, qreal y) { } // dessine la numerotation des colonnes - if (display_columns) { + if (display_border && display_columns) { for (int i = 1 ; i <= nb_columns ; ++ i) { QRectF numbered_rectangle = QRectF( diagram.topLeft().x() + (rows_header_width + ((i - 1) * columns_width)), @@ -237,7 +240,7 @@ void BorderInset::draw(QPainter *qp, qreal x, qreal y) { } // dessine la numerotation des lignes - if (display_rows) { + if (display_border && display_rows) { QString row_string("A"); for (int i = 1 ; i <= nb_rows ; ++ i) { QRectF lettered_rectangle = QRectF( @@ -258,19 +261,19 @@ void BorderInset::draw(QPainter *qp, qreal x, qreal y) { qp -> drawRect(inset); qp -> drawRect(inset_author); - qp -> drawText(inset_author, Qt::AlignVCenter | Qt::AlignLeft, tr(" Auteur : ") + bi_author); + qp -> drawText(inset_author, Qt::AlignVCenter | Qt::AlignLeft, QString(tr(" Auteur : %1", "inset content")).arg(bi_author)); qp -> drawRect(inset_date); - qp -> drawText(inset_date, Qt::AlignVCenter | Qt::AlignLeft, tr(" Date : ") + bi_date.toString("dd/MM/yyyy")); + qp -> drawText(inset_date, Qt::AlignVCenter | Qt::AlignLeft, QString(tr(" Date : %1", "inset content")).arg(bi_date.toString("dd/MM/yyyy"))); qp -> drawRect(inset_title); - qp -> drawText(inset_title, Qt::AlignVCenter | Qt::AlignCenter, tr("Titre du document : ") + bi_title); + qp -> drawText(inset_title, Qt::AlignVCenter | Qt::AlignCenter, QString(tr("Titre du document : %1", "inset content")).arg(bi_title)); qp -> drawRect(inset_file); - qp -> drawText(inset_file, Qt::AlignVCenter | Qt::AlignLeft, tr(" Fichier : ") + bi_filename); + qp -> drawText(inset_file, Qt::AlignVCenter | Qt::AlignLeft, QString(tr(" Fichier : %1", "inset content")).arg(bi_filename)); qp -> drawRect(inset_folio); - qp -> drawText(inset_folio, Qt::AlignVCenter | Qt::AlignLeft, tr(" Folio : ") + bi_folio); + qp -> drawText(inset_folio, Qt::AlignVCenter | Qt::AlignLeft, QString(tr(" Folio : %1", "inset content")).arg(bi_final_folio)); } qp -> restore(); @@ -439,3 +442,20 @@ QString BorderInset::incrementLetters(const QString &string) { } } } + +/** + @param index numero du schema (de 1 a total) + @param total nombre total de schemas dans le projet +*/ +void BorderInset::setFolioData(int index, int total) { + if (index < 1 || total < 1 || index > total) return; + + // memorise les informations + folio_index_ = index; + folio_total_ = total; + + // regenere le contenu du champ folio + bi_final_folio = bi_folio; + bi_final_folio.replace("%id", QString::number(folio_index_)); + bi_final_folio.replace("%total", QString::number(folio_total_)); +} diff --git a/sources/borderinset.h b/sources/borderinset.h index 93d3cb2d8..834595b7f 100644 --- a/sources/borderinset.h +++ b/sources/borderinset.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -130,9 +130,15 @@ class BorderInset : public QObject { /// @param date le nouveau contenu du champ "Date" void setDate (const QDate &date) { bi_date = date; } /// @param title le nouveau contenu du champ "Titre" - void setTitle (const QString &title) { bi_title = title; } + void setTitle (const QString &title) { + if (bi_title != title) { + bi_title = title; + emit(diagramTitleChanged(title)); + } + } /// @param folio le nouveau contenu du champ "Folio" void setFolio (const QString &folio) { bi_folio = folio; } + void setFolioData(int, int); /// @param filename le nouveau contenu du champ "Fichier" void setFileName (const QString &filename) { bi_filename = filename; } @@ -164,6 +170,17 @@ class BorderInset : public QObject { */ void displayChanged(); + /** + Signal emis lorsque le titre du schema change + */ + void diagramTitleChanged(const QString &); + + /** + Signal emis lorsque le cartouche requiert une mise a jour des donnees + utilisees pour generer le folio. + */ + void needFolioData(); + // attributs private: // informations du cartouche @@ -171,6 +188,9 @@ class BorderInset : public QObject { QDate bi_date; QString bi_title; QString bi_folio; + QString bi_final_folio; + int folio_index_; + int folio_total_; QString bi_filename; // dimensions du cadre (lignes et colonnes) diff --git a/sources/borderproperties.cpp b/sources/borderproperties.cpp index 6eb7c53a8..227e14d4e 100644 --- a/sources/borderproperties.cpp +++ b/sources/borderproperties.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -18,9 +18,21 @@ #include "borderproperties.h" /** - Constructeur + Constructeur. Initialise un objet BorderProperties avec les proprietes par + defaut suivantes : + * 17 colonnes affichees de 60.0 px de large pour 20.0px de haut + * 8 lignes affichees de 80.0 px de haut pour 20.0px de large */ -BorderProperties::BorderProperties() { +BorderProperties::BorderProperties() : + columns_count(17), + columns_width(60.0), + columns_header_height(20.0), + display_columns(true), + rows_count(8), + rows_height(80.0), + rows_header_width(20.0), + display_rows(true) +{ } /** @@ -53,3 +65,58 @@ bool BorderProperties::operator==(const BorderProperties &bp) { bool BorderProperties::operator!=(const BorderProperties &bp) { return(!(*this == bp)); } + +/** + Exporte les dimensions sous formes d'attributs XML ajoutes a l'element e. + @param e Element XML auquel seront ajoutes des attributs +*/ +void BorderProperties::toXml(QDomElement &e) const { + e.setAttribute("cols", columns_count); + e.setAttribute("colsize", QString("%1").arg(columns_width)); + e.setAttribute("rows", rows_count); + e.setAttribute("rowsize", QString("%1").arg(rows_height)); + e.setAttribute("displaycols", display_columns ? "true" : "false"); + e.setAttribute("displayrows", display_rows ? "true" : "false"); +} + +/** + Importe les dimensions a partir des attributs XML de l'element e + @param e Element XML dont les attributs seront lus +*/ +void BorderProperties::fromXml(QDomElement &e) { + if (e.hasAttribute("cols")) columns_count = e.attribute("cols").toInt(); + if (e.hasAttribute("colsize")) columns_width = e.attribute("colsize").toInt(); + if (e.hasAttribute("rows")) rows_count = e.attribute("rows").toInt(); + if (e.hasAttribute("rowsize")) rows_height = e.attribute("rowsize").toInt(); + if (e.hasAttribute("displaycols")) display_columns = e.attribute("displaycols") == "true"; + if (e.hasAttribute("displayrows")) display_rows = e.attribute("displayrows") == "true"; +} + +/** + Exporte les dimensions dans une configuration. + @param settings Parametres a ecrire + @param prefix prefixe a ajouter devant les noms des parametres +*/ +void BorderProperties::toSettings(QSettings &settings, const QString &prefix) const { + settings.setValue(prefix + "cols", columns_count); + settings.setValue(prefix + "colsize", columns_width); + settings.setValue(prefix + "displaycols", display_columns); + settings.setValue(prefix + "rows", rows_count); + settings.setValue(prefix + "rowsize", rows_height); + settings.setValue(prefix + "displayrows", display_rows); +} + +/** + Importe les dimensions depuis une configuration. + @param settings Parametres a lire + @param prefix prefixe a ajouter devant les noms des parametres +*/ +void BorderProperties::fromSettings(QSettings &settings, const QString &prefix) { + columns_count = settings.value(prefix + "cols", columns_count).toInt(); + columns_width = qRound(settings.value(prefix + "colsize", columns_width).toDouble()); + display_columns = settings.value(prefix + "displaycols", display_columns).toBool(); + + rows_count = settings.value(prefix + "rows", rows_count).toInt(); + rows_height = qRound(settings.value(prefix + "rowsize", rows_height).toDouble()); + display_rows = settings.value(prefix + "displayrows", display_rows).toBool(); +} diff --git a/sources/borderproperties.h b/sources/borderproperties.h index e40b4bac4..eb8ec4058 100644 --- a/sources/borderproperties.h +++ b/sources/borderproperties.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -18,6 +18,7 @@ #ifndef BORDER_PROPERTIES_H #define BORDER_PROPERTIES_H #include +#include /** Cette classe est un conteneur pour les dimensions et proprietes d'affichage d'un schema : affichage, nombre et dimensions des colonnes et lignes, ... @@ -31,6 +32,11 @@ class BorderProperties { bool operator==(const BorderProperties &); bool operator!=(const BorderProperties &); + void toXml(QDomElement &) const; + void fromXml(QDomElement &); + void toSettings(QSettings &, const QString & = QString()) const; + void fromSettings(QSettings &, const QString & = QString()); + // attributs int columns_count; ///< Nombre de colonnes qreal columns_width; ///< Largeur des colonnes diff --git a/sources/borderpropertieswidget.cpp b/sources/borderpropertieswidget.cpp index c25e7b695..f6a87e1f0 100644 --- a/sources/borderpropertieswidget.cpp +++ b/sources/borderpropertieswidget.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -59,10 +59,10 @@ const BorderProperties &BorderPropertiesWidget::borderProperties() { void BorderPropertiesWidget::setEditedBorder(const BorderProperties &bp) { border_ = bp; columns_count -> setValue(border_.columns_count); - columns_width -> setValue(border_.columns_width); + columns_width -> setValue(qRound(border_.columns_width)); display_columns -> setChecked(border_.display_columns); rows_count -> setValue(border_.rows_count); - rows_height -> setValue(border_.rows_height); + rows_height -> setValue(qRound(border_.rows_height)); display_rows -> setChecked(border_.display_rows); } @@ -85,10 +85,10 @@ void BorderPropertiesWidget::build() { columns_width = new QSpinBox(diagram_size_box); columns_width -> setMinimum(qRound(BorderInset::minColumnsWidth())); columns_width -> setSingleStep(10); - columns_width -> setPrefix(tr("\327")); - columns_width -> setSuffix(tr("px")); + columns_width -> setPrefix(tr("\327", "multiplication symbol")); + columns_width -> setSuffix(tr("px", "unit for cols width")); - display_columns = new QCheckBox(tr("Afficher les en-ttes"), diagram_size_box); + display_columns = new QCheckBox(tr("Afficher les en-t\352tes"), diagram_size_box); // lignes : nombre et largeur QLabel *ds2 = new QLabel(tr("Lignes :")); @@ -99,10 +99,10 @@ void BorderPropertiesWidget::build() { rows_height = new QSpinBox(diagram_size_box); rows_height -> setMinimum(qRound(BorderInset::minRowsHeight())); rows_height -> setSingleStep(10); - rows_height -> setPrefix(tr("\327")); - rows_height -> setSuffix(tr("px")); + rows_height -> setPrefix(tr("\327", "multiplication symbol")); + rows_height -> setSuffix(tr("px", "unit for rows height")); - display_rows = new QCheckBox(tr("Afficher les en-ttes"), diagram_size_box); + display_rows = new QCheckBox(tr("Afficher les en-t\352tes"), diagram_size_box); // layout diagram_size_box_layout -> addWidget(ds1, 0, 0); diff --git a/sources/borderpropertieswidget.h b/sources/borderpropertieswidget.h index 87c1834b8..e7ee62ec4 100644 --- a/sources/borderpropertieswidget.h +++ b/sources/borderpropertieswidget.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/conductor.cpp b/sources/conductor.cpp index f17881ab9..ce9b036f3 100644 --- a/sources/conductor.cpp +++ b/sources/conductor.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -937,7 +937,7 @@ QDomElement Conductor::toXml(QDomDocument &d, QHash &table_adr_ } // exporte la "configuration" du conducteur - properties_.toXml(d, e); + properties_.toXml(e); return(e); } diff --git a/sources/conductor.h b/sources/conductor.h index 1c4014df6..3740f3b7a 100644 --- a/sources/conductor.h +++ b/sources/conductor.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/conductorprofile.cpp b/sources/conductorprofile.cpp index ebcb9413c..827b71aed 100644 --- a/sources/conductorprofile.cpp +++ b/sources/conductorprofile.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/conductorprofile.h b/sources/conductorprofile.h index eb30c9e21..e31513f1a 100644 --- a/sources/conductorprofile.h +++ b/sources/conductorprofile.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/conductorproperties.cpp b/sources/conductorproperties.cpp index 58ba967da..ad82146c4 100644 --- a/sources/conductorproperties.cpp +++ b/sources/conductorproperties.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -159,19 +159,18 @@ void SingleLineProperties::drawNeutral(QPainter *painter, QET::ConductorSegmentT } /** - exporte les parametres du conducteur unifilaire sous formes d'attributs XML + Exporte les parametres du conducteur unifilaire sous formes d'attributs XML ajoutes a l'element e. - @param d Document XML ; utilise pour ajouter (potentiellement) des elements XML @param e Element XML auquel seront ajoutes des attributs */ -void SingleLineProperties::toXml(QDomDocument &, QDomElement &e) const { +void SingleLineProperties::toXml(QDomElement &e) const { e.setAttribute("ground", hasGround ? "true" : "false"); e.setAttribute("neutral", hasNeutral ? "true" : "false"); e.setAttribute("phase", phases); } /** - importe les parametres du conducteur unifilaire a partir des attributs XML + Importe les parametres du conducteur unifilaire a partir des attributs XML de l'element e @param e Element XML dont les attributs seront lus */ @@ -182,22 +181,21 @@ void SingleLineProperties::fromXml(QDomElement &e) { } /** - exporte les parametres du conducteur sous formes d'attributs XML + Exporte les parametres du conducteur sous formes d'attributs XML ajoutes a l'element e. - @param d Document XML ; utilise pour ajouter (potentiellement) des elements XML @param e Element XML auquel seront ajoutes des attributs */ -void ConductorProperties::toXml(QDomDocument &d, QDomElement &e) const { +void ConductorProperties::toXml(QDomElement &e) const { e.setAttribute("type", typeToString(type)); if (type == Single) { - singleLineProperties.toXml(d, e); + singleLineProperties.toXml(e); } else if (type == Multi) { e.setAttribute("num", text); } } /** - importe les parametres du conducteur unifilaire a partir des attributs XML + Importe les parametres du conducteur unifilaire a partir des attributs XML de l'element e @param e Element XML dont les attributs seront lus */ @@ -217,7 +215,7 @@ void ConductorProperties::fromXml(QDomElement &e) { /** @param settings Parametres a ecrire - @param prefix prefix a ajouter devant les noms des parametres + @param prefix prefixe a ajouter devant les noms des parametres */ void ConductorProperties::toSettings(QSettings &settings, const QString &prefix) const { settings.setValue(prefix + "type", typeToString(type)); @@ -227,7 +225,7 @@ void ConductorProperties::toSettings(QSettings &settings, const QString &prefix) /** @param settings Parametres a lire - @param prefix prefix a ajouter devant les noms des parametres + @param prefix prefixe a ajouter devant les noms des parametres */ void ConductorProperties::fromSettings(QSettings &settings, const QString &prefix) { QString setting_type = settings.value(prefix + "type", typeToString(Multi)).toString(); diff --git a/sources/conductorproperties.h b/sources/conductorproperties.h index 961532898..24da5e2a4 100644 --- a/sources/conductorproperties.h +++ b/sources/conductorproperties.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -31,7 +31,7 @@ class SingleLineProperties { void setPhasesCount(int); unsigned short int phasesCount(); void draw(QPainter *, QET::ConductorSegmentType, const QRectF &); - void toXml(QDomDocument &, QDomElement &) const; + void toXml(QDomElement &) const; void fromXml(QDomElement &); void toSettings(QSettings &, const QString & = QString()) const; void fromSettings(QSettings &, const QString & = QString()); @@ -87,7 +87,7 @@ class ConductorProperties { SingleLineProperties singleLineProperties; // methodes - void toXml(QDomDocument &, QDomElement &) const; + void toXml(QDomElement &) const; void fromXml(QDomElement &); void toSettings(QSettings &, const QString & = QString()) const; void fromSettings(QSettings &, const QString & = QString()); diff --git a/sources/conductorpropertieswidget.cpp b/sources/conductorpropertieswidget.cpp index 85770fca6..a828a45a4 100644 --- a/sources/conductorpropertieswidget.cpp +++ b/sources/conductorpropertieswidget.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/conductorpropertieswidget.h b/sources/conductorpropertieswidget.h index 49423387b..e740fa8bd 100644 --- a/sources/conductorpropertieswidget.h +++ b/sources/conductorpropertieswidget.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -19,7 +19,6 @@ #define CONDUCTOR_PROPERTIES_WIDGET_H #include "conductorproperties.h" #include - /** Ce widget permet a l utilisateur d'editer les proprietes d'un conducteur. Par proprietes, on entend non pas le trajet effectue par le conducteur mais diff --git a/sources/conductorsegment.cpp b/sources/conductorsegment.cpp index 5aad18edf..78922f047 100644 --- a/sources/conductorsegment.cpp +++ b/sources/conductorsegment.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/conductorsegment.h b/sources/conductorsegment.h index fba548d5c..eb32d6129 100644 --- a/sources/conductorsegment.h +++ b/sources/conductorsegment.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/conductorsegmentprofile.h b/sources/conductorsegmentprofile.h index b232ef107..bdf69e034 100644 --- a/sources/conductorsegmentprofile.h +++ b/sources/conductorsegmentprofile.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/configdialog.cpp b/sources/configdialog.cpp index 0b37aff9c..86f029258 100644 --- a/sources/configdialog.cpp +++ b/sources/configdialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -24,12 +24,12 @@ */ ConfigDialog::ConfigDialog(QWidget *parent) : QDialog(parent) { - setWindowTitle(tr("Configurer QElectroTech")); + setWindowTitle(tr("Configurer QElectroTech", "window title")); // liste des pages pages_list = new QListWidget(); pages_list -> setViewMode(QListView::IconMode); - pages_list -> setIconSize(QSize(48, 48)); + pages_list -> setIconSize(QSize(110, 110)); pages_list -> setMovement(QListView::Static); pages_list -> setMinimumWidth(135); pages_list -> setMaximumWidth(135); @@ -37,6 +37,7 @@ ConfigDialog::ConfigDialog(QWidget *parent) : QDialog(parent) { // pages pages_widget = new QStackedWidget(); + addPage(new GeneralConfigurationPage()); addPage(new NewDiagramPage()); buildPagesList(); diff --git a/sources/configdialog.h b/sources/configdialog.h index 1f519315e..538ab4801 100644 --- a/sources/configdialog.h +++ b/sources/configdialog.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/configpages.cpp b/sources/configpages.cpp index 02a9f6d9e..87c01400b 100644 --- a/sources/configpages.cpp +++ b/sources/configpages.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -28,10 +28,6 @@ @param parent QWidget parent */ NewDiagramPage::NewDiagramPage(QWidget *parent) : ConfigPage(parent) { - - // acces a la configuration de QElectroTech - QSettings &settings = QETApp::settings(); - // dimensions par defaut d'un schema bpw = new BorderPropertiesWidget(QETDiagramEditor::defaultBorderProperties()); @@ -39,14 +35,12 @@ NewDiagramPage::NewDiagramPage(QWidget *parent) : ConfigPage(parent) { ipw = new InsetPropertiesWidget(QETDiagramEditor::defaultInsetProperties(), true); // proprietes par defaut des conducteurs - ConductorProperties cp; - cp.fromSettings(settings, "diagrameditor/defaultconductor"); - cpw = new ConductorPropertiesWidget(cp); + cpw = new ConductorPropertiesWidget(QETDiagramEditor::defaultConductorProperties()); cpw -> setContentsMargins(0, 0, 0, 0); QVBoxLayout *vlayout1 = new QVBoxLayout(); - QLabel *title = new QLabel(tr("Nouveau sch\351ma")); + QLabel *title = new QLabel(this -> title()); vlayout1 -> addWidget(title); QFrame *horiz_line = new QFrame(); @@ -78,28 +72,10 @@ void NewDiagramPage::applyConf() { QSettings &settings = QETApp::settings(); // dimensions des nouveaux schemas - BorderProperties border = bpw -> borderProperties(); - settings.setValue("diagrameditor/defaultcols", border.columns_count); - settings.setValue("diagrameditor/defaultcolsize", border.columns_width); - settings.setValue("diagrameditor/defaultdisplaycols", border.display_columns); - settings.setValue("diagrameditor/defaultrows", border.rows_count); - settings.setValue("diagrameditor/defaultrowsize", border.rows_height); - settings.setValue("diagrameditor/defaultdisplayrows", border.display_rows); + bpw -> borderProperties().toSettings(settings, "diagrameditor/default"); // proprietes du cartouche - InsetProperties inset = ipw-> insetProperties(); - settings.setValue("diagrameditor/defaulttitle", inset.title); - settings.setValue("diagrameditor/defaultauthor", inset.author); - settings.setValue("diagrameditor/defaultfilename", inset.filename); - settings.setValue("diagrameditor/defaultfolio", inset.folio); - QString date_setting_value; - if (inset.useDate == InsetProperties::UseDateValue) { - if (inset.date.isNull()) date_setting_value = "null"; - else date_setting_value = inset.date.toString("yyyyMMdd"); - } else { - date_setting_value = "now"; - } - settings.setValue("diagrameditor/defaultdate", date_setting_value); + ipw-> insetProperties().toSettings(settings, "diagrameditor/default"); // proprietes par defaut des conducteurs cpw -> conductorProperties().toSettings(settings, "diagrameditor/defaultconductor"); @@ -107,10 +83,90 @@ void NewDiagramPage::applyConf() { /// @return l'icone de cette page QIcon NewDiagramPage::icon() const { - return(QIcon(":/ico/conf_new_diagram.png")); + return(QIcon(":/ico/conf_new_diagram_110.png")); } /// @return le titre de cette page QString NewDiagramPage::title() const { - return(tr("Nouveau sch\351ma")); + return(tr("Nouveau sch\351ma", "configuration page title")); +} + + +/** + Constructeur + @param parent QWidget parent +*/ +GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) : ConfigPage(parent) { + + // acces a la configuration de QElectroTech + QSettings &settings = QETApp::settings(); + bool tabbed = settings.value("diagrameditor/viewmode", "windowed") == "tabbed"; + bool integrate_elements = settings.value("diagrameditor/integrate-elements", true).toBool(); + + projects_view_mode_ = new QGroupBox(tr("Projets"), this); + windowed_mode_ = new QRadioButton(tr("Utiliser des fen\352tres"), projects_view_mode_); + tabbed_mode_ = new QRadioButton(tr("Utiliser des onglets"), projects_view_mode_); + warning_view_mode_ = new QLabel(tr("Ces param\350tres s'appliqueront d\350s la prochaine ouverture d'un \351diteur de sch\351mas.")); + + elements_management_ = new QGroupBox(tr("Gestion des \351l\351ments"), this); + integrate_elements_ = new QCheckBox(tr("Int\351grer automatiquement les \351l\351ments dans les projets (recommand\351)"), elements_management_); + + if (tabbed) { + tabbed_mode_ -> setChecked(true); + } else { + windowed_mode_ -> setChecked(true); + } + + integrate_elements_ -> setChecked(integrate_elements); + + QVBoxLayout *projects_view_mode_layout = new QVBoxLayout(); + projects_view_mode_layout -> addWidget(windowed_mode_); + projects_view_mode_layout -> addWidget(tabbed_mode_); + projects_view_mode_layout -> addWidget(warning_view_mode_); + projects_view_mode_ -> setLayout(projects_view_mode_layout); + + QVBoxLayout *elements_management_layout = new QVBoxLayout(); + elements_management_layout -> addWidget(integrate_elements_); + elements_management_ -> setLayout(elements_management_layout); + + QVBoxLayout *vlayout1 = new QVBoxLayout(); + + QLabel *title_label_ = new QLabel(title()); + vlayout1 -> addWidget(title_label_); + + QFrame *horiz_line_ = new QFrame(); + horiz_line_ -> setFrameShape(QFrame::HLine); + vlayout1 -> addWidget(horiz_line_); + + vlayout1 -> addWidget(projects_view_mode_); + vlayout1 -> addWidget(elements_management_); + vlayout1 -> addStretch(); + + setLayout(vlayout1); +} + +/// Destructeur +GeneralConfigurationPage::~GeneralConfigurationPage() { +} + +/** + Applique la configuration de cette page +*/ +void GeneralConfigurationPage::applyConf() { + QSettings &settings = QETApp::settings(); + + QString view_mode = tabbed_mode_ -> isChecked() ? "tabbed" : "windowed"; + settings.setValue("diagrameditor/viewmode", view_mode) ; + + settings.setValue("diagrameditor/integrate-elements", integrate_elements_ -> isChecked()); +} + +/// @return l'icone de cette page +QIcon GeneralConfigurationPage::icon() const { + return(QIcon(":/ico/settings.png")); +} + +/// @return le titre de cette page +QString GeneralConfigurationPage::title() const { + return(tr("G\351n\351ral", "configuration page title")); } diff --git a/sources/configpages.h b/sources/configpages.h index 9bc8af990..13dac0e25 100644 --- a/sources/configpages.h +++ b/sources/configpages.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -21,7 +21,6 @@ class BorderPropertiesWidget; class ConductorPropertiesWidget; class InsetPropertiesWidget; - /** Cette classe abstraite contient les methodes que toutes les pages de configuration doivent implementer. @@ -68,4 +67,34 @@ class NewDiagramPage : public ConfigPage { InsetPropertiesWidget *ipw; ///< Widget d'edition des proprietes par defaut du cartouche ConductorPropertiesWidget *cpw; ///< Widget d'edition des proprietes par defaut des conducteurs }; + +/** + Cette classe represente la page de configuration generale. +*/ +class GeneralConfigurationPage : public ConfigPage { + Q_OBJECT + // constructeurs, destructeur + public: + GeneralConfigurationPage(QWidget * = 0); + virtual ~GeneralConfigurationPage(); + private: + GeneralConfigurationPage(const GeneralConfigurationPage &); + + // methodes + public: + void applyConf(); + QString title() const; + QIcon icon() const; + + // attributs + public: + QLabel *title_label_; + QFrame *horiz_line_; + QGroupBox *projects_view_mode_; + QRadioButton *windowed_mode_; + QRadioButton *tabbed_mode_; + QLabel *warning_view_mode_; + QGroupBox *elements_management_; + QCheckBox *integrate_elements_; +}; #endif diff --git a/sources/customelement.cpp b/sources/customelement.cpp index 8a96b6d49..6b18e5a1f 100644 --- a/sources/customelement.cpp +++ b/sources/customelement.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -19,72 +19,103 @@ #include "elementtextitem.h" #include "diagram.h" #include "qetapp.h" +#include "partline.h" +#include "elementdefinition.h" #include + /** - Constructeur de la classe ElementPerso. Permet d'instancier un element - utilisable comme un element fixe a la difference que l'element perso lit - sa description (noms, dessin, comportement) dans un fichier XML a fournir - en parametre. - @param nom_fichier Le chemin du fichier XML decrivant l'element + Constructeur de la classe CustomElement. Permet d'instancier un element + utilisable comme un element fixe a la difference que l'element perso est + construit a partir d'une description au format XML. Celle-ci est recuperee + a l'emplacement indique. + @param location Emplacement de la definition d'element a utiliser @param qgi Le QGraphicsItem parent de cet element @param s Le Schema affichant cet element - @param etat Un pointeur facultatif vers un entier. La valeur de cet entier + @param state Un pointeur facultatif vers un entier. La valeur de cet entier sera changee de maniere a refleter le deroulement de l'instanciation : - 0 : L'instanciation a reussi - - 1 : Le fichier n'existe pas - - 2 : Le fichier n'a pu etre ouvert - - 3 : Le fichier n'est pas un document XML - - 4 : Le document XML n'a pas une "definition" comme racine + - 1 : l'emplacement n'a pas permis d'acceder a une definition d'element + - 2 : la definition n'etait pas lisible + - 3 : la definition n'etait pas valide / exploitable / utilisable + - 4 : Le document XML n'est pas un element "definition" - 5 : Les attributs de la definition ne sont pas presents et / ou valides - 6 : La definition est vide - 7 : L'analyse d'un element XML decrivant une partie du dessin de l'element a echoue - 8 : Aucune partie du dessin n'a pu etre chargee */ -CustomElement::CustomElement(QString &nom_fichier, QGraphicsItem *qgi, Diagram *s, int *etat) : FixedElement(qgi, s) { - nomfichier = nom_fichier; - // pessimisme inside : par defaut, ca foire - elmt_etat = -1; - - // le fichier doit exister - QFileInfo infos_file(nomfichier); - if (!infos_file.exists() || !infos_file.isFile()) { - if (etat != NULL) *etat = 1; - elmt_etat = 1; +CustomElement::CustomElement(const ElementsLocation &location, QGraphicsItem *qgi, Diagram *s, int *state) : + FixedElement(qgi, s), + elmt_state(-1), + location_(location) +{ + // recupere la definition de l'element + ElementsCollectionItem *element_item = QETApp::collectionItem(location); + ElementDefinition *element_definition; + if ( + !element_item ||\ + !element_item -> isElement() ||\ + !(element_definition = qobject_cast(element_item)) + ) { + if (state) *state = 1; + elmt_state = 1; return; } - // le fichier doit etre lisible - QFile fichier(nomfichier); - if (!fichier.open(QIODevice::ReadOnly)) { - if (etat != NULL) *etat = 2; - elmt_etat = 2; + if (!element_definition -> isReadable()) { + if (state) *state = 2; + elmt_state = 2; return; } - // le fichier doit etre un document XML - QDomDocument document_xml; - if (!document_xml.setContent(&fichier)) { - if (etat != NULL) *etat = 3; - elmt_etat = 3; + if (element_definition -> isNull()) { + if (state) *state = 3; + elmt_state = 3; return; } - // la racine est supposee etre une definition d'element - QDomElement racine = document_xml.documentElement(); - if (racine.tagName() != "definition" || racine.attribute("type") != "element") { - if (etat != NULL) *etat = 4; - elmt_etat = 4; - return; + buildFromXml(element_definition -> xml(), &elmt_state); + if (state) *state = elmt_state; + if (elmt_state) return; + + if (state) *state = 0; + elmt_state = 0; +} + +CustomElement::CustomElement(const QDomElement &xml_def_elmt, QGraphicsItem *qgi, Diagram *s, int *state) : FixedElement(qgi, s) { + int elmt_state = -1; + buildFromXml(xml_def_elmt, &elmt_state); + if (state) *state = elmt_state; +} + +/** + Construit l'element personnalise a partir d'un element XML representant sa + definition. + @param xml_def_elmt + @param state Un pointeur facultatif vers un entier. La valeur de cet entier + sera changee de maniere a refleter le deroulement de l'instanciation : + - 0 : La construction s'est bien passee + - 4 : Le document XML n'est pas un element "definition" + - 5 : Les attributs de la definition ne sont pas presents et / ou valides + - 6 : La definition est vide + - 7 : L'analyse d'un element XML decrivant une partie du dessin de l'element a echoue + - 8 : Aucune partie du dessin n'a pu etre chargee + @return true si le chargement a reussi, false sinon +*/ +bool CustomElement::buildFromXml(const QDomElement &xml_def_elmt, int *state) { + + if (xml_def_elmt.tagName() != "definition" || xml_def_elmt.attribute("type") != "element") { + if (state) *state = 4; + return(false); } // verifie basiquement que la version actuelle est capable de lire ce fichier - if (racine.hasAttribute("version")) { + if (xml_def_elmt.hasAttribute("version")) { bool conv_ok; - qreal element_version = racine.attribute("version").toDouble(&conv_ok); + qreal element_version = xml_def_elmt.attribute("version").toDouble(&conv_ok); if (conv_ok && QET::version.toDouble() < element_version) { std::cerr << qPrintable( - QObject::tr("Avertissement : l'\351l\351ment ") + nom_fichier - + QObject::tr(" a \351t\351 enregistr\351 avec une version" + QObject::tr("Avertissement : l'\351l\351ment " + " a \351t\351 enregistr\351 avec une version" " ult\351rieure de QElectroTech.") ) << std::endl; } @@ -93,32 +124,30 @@ CustomElement::CustomElement(QString &nom_fichier, QGraphicsItem *qgi, Diagram * // ces attributs doivent etre presents et valides int w, h, hot_x, hot_y; if ( - !QET::attributeIsAnInteger(racine, QString("width"), &w) ||\ - !QET::attributeIsAnInteger(racine, QString("height"), &h) ||\ - !QET::attributeIsAnInteger(racine, QString("hotspot_x"), &hot_x) ||\ - !QET::attributeIsAnInteger(racine, QString("hotspot_y"), &hot_y) ||\ - !validOrientationAttribute(racine) + !QET::attributeIsAnInteger(xml_def_elmt, QString("width"), &w) ||\ + !QET::attributeIsAnInteger(xml_def_elmt, QString("height"), &h) ||\ + !QET::attributeIsAnInteger(xml_def_elmt, QString("hotspot_x"), &hot_x) ||\ + !QET::attributeIsAnInteger(xml_def_elmt, QString("hotspot_y"), &hot_y) ||\ + !validOrientationAttribute(xml_def_elmt) ) { - if (etat != NULL) *etat = 5; - elmt_etat = 5; - return; + if (state) *state = 5; + return(false); } // on peut d'ores et deja specifier la taille et le hotspot setSize(w, h); setHotspot(QPoint(hot_x, hot_y)); - setInternalConnections(racine.attribute("ic") == "true"); + setInternalConnections(xml_def_elmt.attribute("ic") == "true"); // la definition est supposee avoir des enfants - if (racine.firstChild().isNull()) { - if (etat != NULL) *etat = 6; - elmt_etat = 6; - return; + if (xml_def_elmt.firstChild().isNull()) { + if (state) *state = 6; + return(false); } // initialisation du QPainter (pour dessiner l'element) QPainter qp; - qp.begin(&dessin); + qp.begin(&drawing); QPen t; t.setColor(Qt::black); t.setWidthF(1.0); @@ -126,12 +155,12 @@ CustomElement::CustomElement(QString &nom_fichier, QGraphicsItem *qgi, Diagram * qp.setPen(t); // extrait les noms de la definition XML - names.fromXml(racine); - setToolTip(nom()); + names.fromXml(xml_def_elmt); + setToolTip(name()); // parcours des enfants de la definition : parties du dessin - int nb_elements_parses = 0; - for (QDomNode node = racine.firstChild() ; !node.isNull() ; node = node.nextSibling()) { + int parsed_elements_count = 0; + for (QDomNode node = xml_def_elmt.firstChild() ; !node.isNull() ; node = node.nextSibling()) { QDomElement elmts = node.toElement(); if (elmts.isNull()) continue; if (elmts.tagName() == "description") { @@ -140,11 +169,10 @@ CustomElement::CustomElement(QString &nom_fichier, QGraphicsItem *qgi, Diagram * for (QDomNode n = node.firstChild() ; !n.isNull() ; n = n.nextSibling()) { QDomElement qde = n.toElement(); if (qde.isNull()) continue; - if (parseElement(qde, qp)) ++ nb_elements_parses; + if (parseElement(qde, qp)) ++ parsed_elements_count; else { - if (etat != NULL) *etat = 7; - elmt_etat = 7; - return; + if (state) *state = 7; + return(false); } } } @@ -154,17 +182,13 @@ CustomElement::CustomElement(QString &nom_fichier, QGraphicsItem *qgi, Diagram * qp.end(); // il doit y avoir au moins un element charge - if (!nb_elements_parses) { - if (etat != NULL) *etat = 8; - elmt_etat = 8; - return; + if (!parsed_elements_count) { + if (state) *state = 8; + return(false); + } else { + if (state) *state = 0; + return(true); } - - // fermeture du fichier - fichier.close(); - - if (etat != NULL) *etat = 0; - elmt_etat = 0; } /** @@ -188,7 +212,7 @@ QList CustomElement::conductors() const { /** @return Le nombre de bornes que l'element possede */ -int CustomElement::nbTerminals() const { +int CustomElement::terminalsCount() const { return(list_terminals.size()); } @@ -198,7 +222,7 @@ int CustomElement::nbTerminals() const { @param options Les options graphiques */ void CustomElement::paint(QPainter *qp, const QStyleOptionGraphicsItem *) { - dessin.play(qp); + drawing.play(qp); } /** @@ -217,6 +241,7 @@ void CustomElement::paint(QPainter *qp, const QStyleOptionGraphicsItem *) { bool CustomElement::parseElement(QDomElement &e, QPainter &qp) { if (e.tagName() == "terminal") return(parseTerminal(e)); else if (e.tagName() == "line") return(parseLine(e, qp)); + else if (e.tagName() == "rect") return(parseRect(e, qp)); else if (e.tagName() == "ellipse") return(parseEllipse(e, qp)); else if (e.tagName() == "circle") return(parseCircle(e, qp)); else if (e.tagName() == "arc") return(parseArc(e, qp)); @@ -244,9 +269,125 @@ bool CustomElement::parseLine(QDomElement &e, QPainter &qp) { if (!QET::attributeIsAReal(e, QString("y1"), &y1)) return(false); if (!QET::attributeIsAReal(e, QString("x2"), &x2)) return(false); if (!QET::attributeIsAReal(e, QString("y2"), &y2)) return(false); + + QET::EndType first_end = QET::endTypeFromString(e.attribute("end1")); + QET::EndType second_end = QET::endTypeFromString(e.attribute("end2")); + qreal length1, length2; + if (!QET::attributeIsAReal(e, QString("length1"), &length1)) length1 = 1.5; + if (!QET::attributeIsAReal(e, QString("length2"), &length2)) length2 = 1.5; + qp.save(); setPainterStyle(e, qp); - qp.drawLine(QLineF(x1, y1, x2, y2)); + QPen t = qp.pen(); + t.setJoinStyle(Qt::MiterJoin); + qp.setPen(t); + + //qp.drawLine(QLineF(x1, y1, x2, y2)); + QLineF line(x1, y1, x2, y2); + QPointF point1(line.p1()); + QPointF point2(line.p2()); + + qreal line_length(line.length()); + qreal pen_width = qp.pen().widthF(); + + // determine s'il faut dessiner les extremites + bool draw_1st_end, draw_2nd_end; + qreal reduced_line_length = line_length - (length1 * PartLine::requiredLengthForEndType(first_end)); + draw_1st_end = first_end && reduced_line_length >= 0; + if (draw_1st_end) { + reduced_line_length -= (length2 * PartLine::requiredLengthForEndType(second_end)); + } else { + reduced_line_length = line_length - (length2 * PartLine::requiredLengthForEndType(second_end)); + } + draw_2nd_end = second_end && reduced_line_length >= 0; + + // dessine la premiere extremite + QPointF start_point, stop_point; + if (draw_1st_end) { + QList four_points1(PartLine::fourEndPoints(point1, point2, length1)); + if (first_end == QET::Circle) { + qp.drawEllipse(QRectF(four_points1[0] - QPointF(length1, length1), QSizeF(length1 * 2.0, length1 * 2.0))); + start_point = four_points1[1]; + } else if (first_end == QET::Diamond) { + qp.drawPolygon(QPolygonF() << four_points1[1] << four_points1[2] << point1 << four_points1[3]); + start_point = four_points1[1]; + } else if (first_end == QET::Simple) { + qp.drawPolyline(QPolygonF() << four_points1[3] << point1 << four_points1[2]); + start_point = point1; + + } else if (first_end == QET::Triangle) { + qp.drawPolygon(QPolygonF() << four_points1[0] << four_points1[2] << point1 << four_points1[3]); + start_point = four_points1[0]; + } + + // ajuste le depart selon l'epaisseur du trait + if (pen_width && (first_end == QET::Simple || first_end == QET::Circle)) { + start_point = QLineF(start_point, point2).pointAt(pen_width / 2.0 / line_length); + } + } else { + start_point = point1; + } + + // dessine la seconde extremite + if (draw_2nd_end) { + QList four_points2(PartLine::fourEndPoints(point2, point1, length2)); + if (second_end == QET::Circle) { + qp.drawEllipse(QRectF(four_points2[0] - QPointF(length2, length2), QSizeF(length2 * 2.0, length2 * 2.0))); + stop_point = four_points2[1]; + } else if (second_end == QET::Diamond) { + qp.drawPolygon(QPolygonF() << four_points2[2] << point2 << four_points2[3] << four_points2[1]); + stop_point = four_points2[1]; + } else if (second_end == QET::Simple) { + qp.drawPolyline(QPolygonF() << four_points2[3] << point2 << four_points2[2]); + stop_point = point2; + } else if (second_end == QET::Triangle) { + qp.drawPolygon(QPolygonF() << four_points2[0] << four_points2[2] << point2 << four_points2[3] << four_points2[0]); + stop_point = four_points2[0]; + } + + // ajuste l'arrivee selon l'epaisseur du trait + if (pen_width && (second_end == QET::Simple || second_end == QET::Circle)) { + stop_point = QLineF(point1, stop_point).pointAt((line_length - (pen_width / 2.0)) / line_length); + } + } else { + stop_point = point2; + } + + qp.drawLine(start_point, stop_point); + + qp.restore(); + return(true); +} + +/** + Analyse un element XML suppose representer un rectangle. Si l'analyse + reussit, le rectangle est ajoute au dessin. + Le rectangle est defini par les attributs suivants : + - x : abscisse du coin superieur gauche du rectangle + - y : ordonnee du coin superieur gauche du rectangle + - width : largeur du rectangle + - height : hauteur du rectangle + + @param e L'element XML a analyser + @param qp Le QPainter a utiliser pour dessiner l'element perso + @return true si l'analyse reussit, false sinon +*/ +bool CustomElement::parseRect(QDomElement &e, QPainter &qp) { + // verifie la presence des attributs obligatoires + double rect_x, rect_y, rect_w, rect_h; + if (!QET::attributeIsAReal(e, QString("x"), &rect_x)) return(false); + if (!QET::attributeIsAReal(e, QString("y"), &rect_y)) return(false); + if (!QET::attributeIsAReal(e, QString("width"), &rect_w)) return(false); + if (!QET::attributeIsAReal(e, QString("height"), &rect_h)) return(false); + qp.save(); + setPainterStyle(e, qp); + + // force le type de jointures pour les rectangles + QPen p = qp.pen(); + p.setJoinStyle(Qt::MiterJoin); + qp.setPen(p); + + qp.drawRect(QRectF(rect_x, rect_y, rect_w, rect_h)); qp.restore(); return(true); } @@ -262,7 +403,6 @@ bool CustomElement::parseLine(QDomElement &e, QPainter &qp) { @param e L'element XML a analyser @param qp Le QPainter a utiliser pour dessiner l'element perso @return true si l'analyse reussit, false sinon - @todo utiliser des attributs plus coherents : x et y = centre, rayon = vrai rayon */ bool CustomElement::parseCircle(QDomElement &e, QPainter &qp) { // verifie la presence des attributs obligatoires @@ -289,7 +429,6 @@ bool CustomElement::parseCircle(QDomElement &e, QPainter &qp) { @param e L'element XML a analyser @param qp Le QPainter a utiliser pour dessiner l'element perso @return true si l'analyse reussit, false sinon - @todo utiliser des attributs plus coherents : x et y = centre */ bool CustomElement::parseEllipse(QDomElement &e, QPainter &qp) { // verifie la presence des attributs obligatoires @@ -405,23 +544,23 @@ bool CustomElement::parseText(QDomElement &e, QPainter &qp) { - une taille - le fait de subir les rotations de l'element ou non @param e L'element XML a analyser - @param s Le schema sur lequel l'element perso sera affiche - @return true si l'analyse reussit, false sinon + @return Un pointeur vers l'objet ElementTextItem ainsi cree si l'analyse reussit, 0 sinon */ -bool CustomElement::parseInput(QDomElement &e) { +ElementTextItem *CustomElement::parseInput(QDomElement &e) { qreal pos_x, pos_y; int size; if ( !QET::attributeIsAReal(e, "x", &pos_x) ||\ !QET::attributeIsAReal(e, "y", &pos_y) ||\ !QET::attributeIsAnInteger(e, "size", &size) - ) return(false); + ) return(0); ElementTextItem *eti = new ElementTextItem(e.attribute("text"), this); + eti -> setFont(QFont(QETApp::diagramTextsFont(), size)); eti -> setPos(pos_x, pos_y); eti -> setOriginalPos(QPointF(pos_x, pos_y)); if (e.attribute("rotate") == "true") eti -> setFollowParentRotations(true); - return(true); + return(eti); } /** @@ -432,23 +571,23 @@ bool CustomElement::parseInput(QDomElement &e) { - orientation : orientation de la borne = Nord (n), Sud (s), Est (e) ou Ouest (w) @param e L'element XML a analyser - @param s Le schema sur lequel l'element perso sera affiche - @return true si l'analyse reussit, false sinon + @return Un pointeur vers l'objet Terminal ainsi cree, 0 sinon */ -bool CustomElement::parseTerminal(QDomElement &e) { +Terminal *CustomElement::parseTerminal(QDomElement &e) { // verifie la presence et la validite des attributs obligatoires double terminalx, terminaly; QET::Orientation terminalo; - if (!QET::attributeIsAReal(e, QString("x"), &terminalx)) return(false); - if (!QET::attributeIsAReal(e, QString("y"), &terminaly)) return(false); - if (!e.hasAttribute("orientation")) return(false); + if (!QET::attributeIsAReal(e, QString("x"), &terminalx)) return(0); + if (!QET::attributeIsAReal(e, QString("y"), &terminaly)) return(0); + if (!e.hasAttribute("orientation")) return(0); if (e.attribute("orientation") == "n") terminalo = QET::North; else if (e.attribute("orientation") == "s") terminalo = QET::South; else if (e.attribute("orientation") == "e") terminalo = QET::East; else if (e.attribute("orientation") == "w") terminalo = QET::West; - else return(false); - list_terminals << new Terminal(terminalx, terminaly, terminalo, this, qobject_cast(scene())); - return(true); + else return(0); + Terminal *new_terminal = new Terminal(terminalx, terminaly, terminalo, this, qobject_cast(scene())); + list_terminals << new_terminal; + return(new_terminal); } /** @@ -481,7 +620,7 @@ void CustomElement::setQPainterAntiAliasing(QPainter &qp, bool aa) { @param e Element XML @return true si l'attribut "orientation" est valide, false sinon */ -bool CustomElement::validOrientationAttribute(QDomElement &e) { +bool CustomElement::validOrientationAttribute(const QDomElement &e) { return(ori.fromString(e.attribute("orientation"))); } diff --git a/sources/customelement.h b/sources/customelement.h index ab35ab3a7..5d70e5ba3 100644 --- a/sources/customelement.h +++ b/sources/customelement.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -15,13 +15,14 @@ You should have received a copy of the GNU General Public License along with QElectroTech. If not, see . */ -#ifndef ELEMENTPERSO_H -#define ELEMENTPERSO_H +#ifndef CUSTOM_ELEMENT_H +#define CUSTOM_ELEMENT_H #include "fixedelement.h" #include #include "nameslist.h" -class CustomElementPart; - +#include "elementslocation.h" +class ElementTextItem; +class Terminal; /** Cette classe represente un element electrique. Elle est utilisable comme un element fixe. La difference est que l'element perso lit @@ -29,65 +30,69 @@ class CustomElementPart; en parametre. */ class CustomElement : public FixedElement { + + Q_OBJECT + // constructeurs, destructeur public: - CustomElement(QString &, QGraphicsItem * = 0, Diagram * = 0, int * = NULL); + CustomElement(const ElementsLocation &, QGraphicsItem * = 0, Diagram * = 0, int * = 0); + CustomElement(const QDomElement &, QGraphicsItem * = 0, Diagram * = 0, int * = 0); virtual ~CustomElement(); - friend class CustomElementPart; - private: CustomElement(const CustomElement &); // attributs - private: - int elmt_etat; // contient le code d'erreur si l'instanciation a echoue ou 0 si l'instanciation s'est bien passe + protected: + int elmt_state; // contient le code d'erreur si l'instanciation a echoue ou 0 si l'instanciation s'est bien passe NamesList names; - QString nomfichier; - QPicture dessin; + ElementsLocation location_; + QPicture drawing; QList list_terminals; // methodes public: virtual QList terminals() const; virtual QList conductors() const; - virtual int nbTerminals() const; + virtual int terminalsCount() const; virtual void paint(QPainter *, const QStyleOptionGraphicsItem *); QString typeId() const; - QString fichier() const; + ElementsLocation location() const; bool isNull() const; - int etat() const; - QString nom() const; + int state() const; + QString name() const; - private: - bool parseElement(QDomElement &, QPainter &); - bool parseLine(QDomElement &, QPainter &); - bool parseEllipse(QDomElement &, QPainter &); - bool parseCircle(QDomElement &, QPainter &); - bool parseArc(QDomElement &, QPainter &); - bool parsePolygon(QDomElement &, QPainter &); - bool parseText(QDomElement &, QPainter &); - bool parseInput(QDomElement &); - bool parseTerminal(QDomElement &); - void setQPainterAntiAliasing(QPainter &, bool); - bool validOrientationAttribute(QDomElement &); - void setPainterStyle(QDomElement &, QPainter &); + protected: + virtual bool buildFromXml(const QDomElement &, int * = 0); + virtual bool parseElement(QDomElement &, QPainter &); + virtual bool parseLine(QDomElement &, QPainter &); + virtual bool parseRect(QDomElement &, QPainter &); + virtual bool parseEllipse(QDomElement &, QPainter &); + virtual bool parseCircle(QDomElement &, QPainter &); + virtual bool parseArc(QDomElement &, QPainter &); + virtual bool parsePolygon(QDomElement &, QPainter &); + virtual bool parseText(QDomElement &, QPainter &); + virtual ElementTextItem *parseInput(QDomElement &); + virtual Terminal *parseTerminal(QDomElement &); + virtual void setQPainterAntiAliasing(QPainter &, bool); + virtual bool validOrientationAttribute(const QDomElement &); + virtual void setPainterStyle(QDomElement &, QPainter &); }; /** @return L'ID du type de l'element ; pour un CustomElement, cela revient au nom du fichier - @see fichier() + @see location() */ inline QString CustomElement::typeId() const { - return(nomfichier); + return(location_.path()); } /** @return L'adresse du fichier contenant la description XML de cet element */ -inline QString CustomElement::fichier() const { - return(nomfichier); +inline ElementsLocation CustomElement::location() const { + return(location_); } /** @@ -95,7 +100,7 @@ inline QString CustomElement::fichier() const { description XML a echoue */ inline bool CustomElement::isNull() const { - return(elmt_etat != 0); + return(elmt_state); } /** @@ -110,15 +115,15 @@ inline bool CustomElement::isNull() const { - 7 : L'analyse d'un element XML decrivant une partie du dessin de l'element a echoue - 8 : Aucune partie du dessin n'a pu etre chargee */ -inline int CustomElement::etat() const { - return(elmt_etat); +inline int CustomElement::state() const { + return(elmt_state); } /** @return Le nom de l'element */ -inline QString CustomElement::nom() const { - return(names.name(QFileInfo(nomfichier).baseName())); +inline QString CustomElement::name() const { + return(names.name(location_.baseName())); } #endif diff --git a/sources/diagram.cpp b/sources/diagram.cpp index 350d7a4fe..4545e351b 100644 --- a/sources/diagram.cpp +++ b/sources/diagram.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -21,6 +21,7 @@ #include "customelement.h" #include "diagram.h" #include "exportdialog.h" +#include "ghostelement.h" #include "diagramcommands.h" #include "diagramcontent.h" @@ -37,7 +38,9 @@ Diagram::Diagram(QObject *parent) : draw_grid(true), use_border(true), moved_elements_fetched(false), - draw_terminals(true) + draw_terminals(true), + project_(0), + read_only_(false) { undo_stack = new QUndoStack(); qgi_manager = new QGIManager(this); @@ -51,9 +54,6 @@ Diagram::Diagram(QObject *parent) : conductor_setter -> setPen(t); conductor_setter -> setLine(QLineF(QPointF(0.0, 0.0), QPointF(0.0, 0.0))); connect(this, SIGNAL(selectionChanged()), this, SLOT(slot_checkSelectionEmptinessChange())); - - // lit les caracteristiques des conducteurs par defaut dans la configuration - defaultConductorProperties.fromSettings(QETApp::settings(), "diagrameditor/defaultconductor"); } /** @@ -77,6 +77,7 @@ Diagram::~Diagram() { foreach(QGraphicsItem *qgi_d, deletable_items) { delete qgi_d; } + // qDebug() << "Suppression du schema" << ((void*)this); } /** @@ -125,15 +126,17 @@ void Diagram::drawBackground(QPainter *p, const QRectF &r) { @param e QKeyEvent decrivant l'evenement clavier */ void Diagram::keyPressEvent(QKeyEvent *e) { - QPointF movement; - switch(e -> key()) { - case Qt::Key_Left: movement = QPointF(-xGrid, 0.0); break; - case Qt::Key_Right: movement = QPointF(+xGrid, 0.0); break; - case Qt::Key_Up: movement = QPointF(0.0, -yGrid); break; - case Qt::Key_Down: movement = QPointF(0.0, +yGrid); break; - } - if (!movement.isNull() && !focusItem()) { - moveElements(movement); + if (!isReadOnly()) { + QPointF movement; + switch(e -> key()) { + case Qt::Key_Left: movement = QPointF(-xGrid, 0.0); break; + case Qt::Key_Right: movement = QPointF(+xGrid, 0.0); break; + case Qt::Key_Up: movement = QPointF(0.0, -yGrid); break; + case Qt::Key_Down: movement = QPointF(0.0, +yGrid); break; + } + if (!movement.isNull() && !focusItem()) { + moveElements(movement); + } } QGraphicsScene::keyPressEvent(e); } @@ -143,16 +146,18 @@ void Diagram::keyPressEvent(QKeyEvent *e) { @param e QKeyEvent decrivant l'evenement clavier */ void Diagram::keyReleaseEvent(QKeyEvent *e) { - // detecte le relachement d'une touche de direction ( = deplacement d'elements) - if ( - (e -> key() == Qt::Key_Left || e -> key() == Qt::Key_Right ||\ - e -> key() == Qt::Key_Up || e -> key() == Qt::Key_Down) &&\ - !current_movement.isNull() && !e -> isAutoRepeat() - ) { - // cree un objet d'annulation pour le mouvement qui vient de se finir - undoStack().push(new MoveElementsCommand(this, selectedContent(), current_movement)); - invalidateMovedElements(); - current_movement = QPointF(); + if (!isReadOnly()) { + // detecte le relachement d'une touche de direction ( = deplacement d'elements) + if ( + (e -> key() == Qt::Key_Left || e -> key() == Qt::Key_Right ||\ + e -> key() == Qt::Key_Up || e -> key() == Qt::Key_Down) &&\ + !current_movement.isNull() && !e -> isAutoRepeat() + ) { + // cree un objet d'annulation pour le mouvement qui vient de se finir + undoStack().push(new MoveElementsCommand(this, selectedContent(), current_movement)); + invalidateMovedElements(); + current_movement = QPointF(); + } } QGraphicsScene::keyReleaseEvent(e); } @@ -227,6 +232,14 @@ QSize Diagram::imageSize() const { return(QSizeF(image_width, image_height).toSize()); } +/** + @return true si le schema est considere comme vide, false sinon. + Un schema vide ne contient ni element, ni conducteur, ni champ de texte +*/ +bool Diagram::isEmpty() const { + return(!items().count()); +} + /** Exporte tout ou partie du schema @param diagram Booleen (a vrai par defaut) indiquant si le XML genere doit @@ -258,7 +271,7 @@ QDomDocument Diagram::toXml(bool diagram) { // type de conducteur par defaut QDomElement default_conductor = document.createElement("defaultconductor"); - defaultConductorProperties.toXml(document, default_conductor); + defaultConductorProperties.toXml(default_conductor); racine.appendChild(default_conductor); } document.appendChild(racine); @@ -324,20 +337,38 @@ QDomDocument Diagram::toXml(bool diagram) { } /** - Importe le diagram decrit dans un document XML. Si une position est + Importe le schema decrit dans un document XML. Si une position est precisee, les elements importes sont positionnes de maniere a ce que le coin superieur gauche du plus petit rectangle pouvant les entourant tous (le bounding rect) soit a cette position. @param document Le document XML a analyser - @param position La position du diagram importe + @param position La position du schema importe @param consider_informations Si vrai, les informations complementaires (auteur, titre, ...) seront prises en compte - @param content_ptr si ce pointeur vers un DiagramContentn'est pas NULL, il + @param content_ptr si ce pointeur vers un DiagramContent n'est pas NULL, il sera rempli avec le contenu ajoute au schema par le fromXml @return true si l'import a reussi, false sinon */ bool Diagram::fromXml(QDomDocument &document, QPointF position, bool consider_informations, DiagramContent *content_ptr) { QDomElement root = document.documentElement(); + return(fromXml(root, position, consider_informations, content_ptr)); +} + +/** + Importe le schema decrit dans un element XML. Si une position est + precisee, les elements importes sont positionnes de maniere a ce que le + coin superieur gauche du plus petit rectangle pouvant les entourant tous + (le bounding rect) soit a cette position. + @param document Le document XML a analyser + @param position La position du schema importe + @param consider_informations Si vrai, les informations complementaires + (auteur, titre, ...) seront prises en compte + @param content_ptr si ce pointeur vers un DiagramContent n'est pas NULL, il + sera rempli avec le contenu ajoute au schema par le fromXml + @return true si l'import a reussi, false sinon +*/ +bool Diagram::fromXml(QDomElement &document, QPointF position, bool consider_informations, DiagramContent *content_ptr) { + QDomElement root = document; // le premier element doit etre un schema if (root.tagName() != "diagram") return(false); @@ -390,7 +421,10 @@ bool Diagram::fromXml(QDomDocument &document, QPointF position, bool consider_in } // si la racine n'a pas d'enfant : le chargement est fini (schema vide) - if (root.firstChild().isNull()) return(true); + if (root.firstChild().isNull()) { + write(document); + return(true); + } // chargement de tous les elements du fichier XML QList added_elements; @@ -400,13 +434,17 @@ bool Diagram::fromXml(QDomDocument &document, QPointF position, bool consider_in // cree un element dont le type correspond a l'id type QString type_id = e.attribute("type"); - QString chemin_fichier = QETApp::realPath(type_id); - CustomElement *nvel_elmt = new CustomElement(chemin_fichier); + ElementsLocation element_location = ElementsLocation(type_id); + if (type_id.startsWith("embed://")) element_location.setProject(project_); + + CustomElement *nvel_elmt = new CustomElement(element_location); if (nvel_elmt -> isNull()) { - QString debug_message = QString("Le chargement de la description de l'element %1 a echoue avec le code d'erreur %2").arg(chemin_fichier).arg(nvel_elmt -> etat()); + QString debug_message = QString("Le chargement de la description de l'element %1 a echoue avec le code d'erreur %2").arg(element_location.path()).arg(nvel_elmt -> state()); + qDebug() << debug_message; delete nvel_elmt; - qDebug(debug_message.toLatin1().data()); - continue; + + qDebug() << "Utilisation d'un GhostElement en lieu et place de cet element."; + nvel_elmt = new GhostElement(element_location); } // charge les caracteristiques de l'element @@ -484,7 +522,7 @@ bool Diagram::fromXml(QDomDocument &document, QPointF position, bool consider_in added_conductors << c; } } - } else qDebug() << "Le chargement du conductor" << id_p1 << id_p2 << "a echoue"; + } else qDebug() << "Le chargement du conducteur" << id_p1 << id_p2 << "a echoue"; } // remplissage des listes facultatives @@ -494,9 +532,52 @@ bool Diagram::fromXml(QDomDocument &document, QPointF position, bool consider_in content_ptr -> textFields = added_texts; } + write(document); return(true); } +/** + Enregistre le schema XML dans son document XML interne et emet le signal + written(). +*/ +void Diagram::write() { + qDebug() << qPrintable(QString("Diagram::write() : saving changes from diagram \"%1\" [%2]").arg(title()).arg(QET::pointerString(this))); + write(toXml().documentElement()); + undoStack().setClean(); +} + +/** + Enregistre un element XML dans son document XML interne et emet le signal + written(). + @param element xml a enregistrer +*/ +void Diagram::write(const QDomElement &element) { + xml_document.clear(); + xml_document.appendChild(xml_document.importNode(element, true)); + emit(written()); +} + +/** + @return true si la fonction write a deja ete appele (pour etre plus exact : + si le document XML utilise en interne n'est pas vide), false sinon +*/ +bool Diagram::wasWritten() const { + return(!xml_document.isNull()); +} + +/** + @return le schema en XML tel qu'il doit etre enregistre dans le fichier projet + @param xml_doc document XML a utiliser pour creer l'element +*/ +QDomElement Diagram::writeXml(QDomDocument &xml_doc) const { + // si le schema n'a pas ete enregistre explicitement, on n'ecrit rien + if (!wasWritten()) return(QDomElement()); + + QDomElement diagram_elmt = xml_document.documentElement(); + QDomNode new_node = xml_doc.importNode(diagram_elmt, true); + return(new_node.toElement()); +} + /** Gere le fait qu'un texte du schema ait ete modifie @param text_item Texte modifie @@ -536,6 +617,26 @@ QRectF Diagram::border() const { ); } +/** + @return le titre du cartouche +*/ +QString Diagram::title() const { + return(border_and_inset.title()); +} + +/** + @return la liste des elements de ce schema +*/ +QList Diagram::customElements() const { + QList elements_list; + foreach(QGraphicsItem *qgi, items()) { + if (CustomElement *elmt = qgraphicsitem_cast(qgi)) { + elements_list << elmt; + } + } + return(elements_list); +} + /// oublie la liste des elements et conducteurs en mouvement void Diagram::invalidateMovedElements() { if (!moved_elements_fetched) return; @@ -617,6 +718,20 @@ void Diagram::moveElements(const QPointF &diff, QGraphicsItem *dontmove) { } } +/** + Permet de savoir si un element est utilise sur un schema + @param location Emplacement d'un element + @return true si l'element location est utilise sur ce schema, false sinon +*/ +bool Diagram::usesElement(const ElementsLocation &location) { + foreach(CustomElement *element, customElements()) { + if (element -> location() == location) { + return(true); + } + } + return(false); +} + /** Definit s'il faut afficher ou non les bornes @param dt true pour afficher les bornes, false sinon @@ -649,6 +764,38 @@ bool Diagram::clipboardMayContainDiagram() { return(may_be_diagram); } +/** + @return le projet auquel ce schema appartient ou 0 s'il s'agit d'un schema + independant. +*/ +QETProject *Diagram::project() const { + return(project_); +} + +/** + @param project le nouveau projet auquel ce schema appartient ou 0 s'il + s'agit d'un schema independant. Indiquer 0 pour rendre ce schema independant. +*/ +void Diagram::setProject(QETProject *project) { + project_ = project; +} +/** + @return true si le schema est en lecture seule +*/ +bool Diagram::isReadOnly() const { + return(read_only_); +} + +/** + @param read_only true pour passer le schema en lecture seule, false sinon +*/ +void Diagram::setReadOnly(bool read_only) { + if (read_only_ != read_only) { + read_only_ = read_only; + emit(readOnlyChanged(read_only_)); + } +} + /** @return Le contenu du schema. Les conducteurs sont tous places dans conductorsToMove. diff --git a/sources/diagram.h b/sources/diagram.h index 7f757b819..faf6fa15c 100644 --- a/sources/diagram.h +++ b/sources/diagram.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -19,15 +19,17 @@ #define SCHEMA_H #include #include -#include "qetdiagrameditor.h" #include "borderinset.h" #include "qgimanager.h" #include "conductorproperties.h" class Element; +class CustomElement; class Terminal; class Conductor; class DiagramTextItem; class DiagramContent; +class QETProject; +class ElementsLocation; /** Cette classe represente un schema electrique. Elle gere les differents elements et conducteurs qui le composent @@ -78,6 +80,9 @@ class Diagram : public QGraphicsScene { QGIManager *qgi_manager; QUndoStack *undo_stack; bool draw_terminals; + QDomDocument xml_document; + QETProject *project_; + bool read_only_; // methodes protected: @@ -88,6 +93,14 @@ class Diagram : public QGraphicsScene { public: static bool clipboardMayContainDiagram(); + // fonctions relatives au projet parent + QETProject *project() const; + void setProject(QETProject *); + + // fonctions relatives a la lecture seule + bool isReadOnly() const; + void setReadOnly(bool); + // fonctions relatives a la pose de conducteurs void setConductor(bool); void setConductorStart (QPointF); @@ -96,6 +109,11 @@ class Diagram : public QGraphicsScene { // fonctions relatives a l'import / export XML QDomDocument toXml(bool = true); bool fromXml(QDomDocument &, QPointF = QPointF(), bool = true, DiagramContent * = NULL); + bool fromXml(QDomElement &, QPointF = QPointF(), bool = true, DiagramContent * = NULL); + void write(); + void write(const QDomElement &); + bool wasWritten() const; + QDomElement writeXml(QDomDocument &) const; // fonctions relatives aux options graphiques void setDisplayGrid(bool); @@ -109,9 +127,13 @@ class Diagram : public QGraphicsScene { void setDrawTerminals(bool); QRectF border() const; + QString title() const; bool toPaintDevice(QPaintDevice &, int = -1, int = -1, Qt::AspectRatioMode = Qt::KeepAspectRatio); QSize imageSize() const; + bool isEmpty() const; + + QList customElements() const; void invalidateMovedElements(); void fetchMovedElements(); const QSet &elementsToMove(); @@ -122,6 +144,7 @@ class Diagram : public QGraphicsScene { DiagramContent content() const; DiagramContent selectedContent(); void moveElements(const QPointF &, QGraphicsItem * = NULL); + bool usesElement(const ElementsLocation &); QUndoStack &undoStack(); QGIManager &qgiManager(); @@ -139,6 +162,8 @@ class Diagram : public QGraphicsScene { vice-versa. */ void selectionEmptinessChanged(); + void written(); + void readOnlyChanged(bool); }; /** diff --git a/sources/diagramcommands.cpp b/sources/diagramcommands.cpp index 21ef2d70f..3fab618c5 100644 --- a/sources/diagramcommands.cpp +++ b/sources/diagramcommands.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -33,7 +33,7 @@ AddElementCommand::AddElementCommand( const QPointF &p, QUndoCommand *parent ) : - QUndoCommand(QObject::tr("ajouter 1 ") + elmt -> nom(), parent), + QUndoCommand(QString(QObject::tr("ajouter 1 %1", "undo caption - %1 is an element name")).arg(elmt -> name()), parent), element(elmt), diagram(d), position(p) @@ -66,7 +66,7 @@ void AddElementCommand::redo() { @param parent QUndoCommand parent */ AddTextCommand::AddTextCommand(Diagram *dia, DiagramTextItem *text, const QPointF &pos, QUndoCommand *parent) : - QUndoCommand(QObject::tr("Ajouter un champ de texte"), parent), + QUndoCommand(QObject::tr("Ajouter un champ de texte", "undo caption"), parent), textitem(text), diagram(dia), position(pos) @@ -113,7 +113,7 @@ AddConductorCommand::AddConductorCommand( Conductor *c, QUndoCommand *parent ) : - QUndoCommand(QObject::tr("ajouter un conducteur"), parent), + QUndoCommand(QObject::tr("ajouter un conducteur", "undo caption"), parent), conductor(c), diagram(d) { @@ -153,7 +153,14 @@ DeleteElementsCommand::DeleteElementsCommand( removed_content(content), diagram(dia) { - setText(QObject::tr("supprimer ") + removed_content.sentence(DiagramContent::All)); + setText( + QString( + QObject::tr( + "supprimer %1", + "undo caption - %1 is a sentence listing the removed content" + ) + ).arg(removed_content.sentence(DiagramContent::All)) + ); diagram -> qgiManager().manage(removed_content.items(DiagramContent::All)); } @@ -220,7 +227,14 @@ PasteDiagramCommand::PasteDiagramCommand( first_redo(true) { - setText(QObject::tr("coller ") + content.sentence(filter)); + setText( + QString( + QObject::tr( + "coller %1", + "undo caption - %1 is a sentence listing the content to paste" + ).arg(content.sentence(filter)) + ) + ); diagram -> qgiManager().manage(content.items(filter)); } @@ -280,7 +294,14 @@ CutDiagramCommand::CutDiagramCommand( ) : DeleteElementsCommand(dia, content, parent) { - setText(QObject::tr("couper ") + content.sentence(DiagramContent::All)); + setText( + QString( + QObject::tr( + "couper %1", + "undo caption - %1 is a sentence listing the content to cut" + ).arg(content.sentence(DiagramContent::All)) + ) + ); } /// Destructeur @@ -306,7 +327,21 @@ MoveElementsCommand::MoveElementsCommand( movement(m), first_redo(true) { - setText(QObject::tr("d\351placer ") + content_to_move.sentence(DiagramContent::Elements|DiagramContent::TextFields|DiagramContent::ConductorsToUpdate|DiagramContent::ConductorsToMove)); + QString moved_content_sentence = content_to_move.sentence( + DiagramContent::Elements | + DiagramContent::TextFields | + DiagramContent::ConductorsToUpdate | + DiagramContent::ConductorsToMove + ); + + setText( + QString( + QObject::tr( + "d\351placer %1", + "undo caption - %1 is a sentence listing the moved content" + ).arg(moved_content_sentence) + ) + ); } /// Destructeur @@ -367,7 +402,7 @@ ChangeDiagramTextCommand::ChangeDiagramTextCommand( const QString &after, QUndoCommand *parent ) : - QUndoCommand(QObject::tr("modifier le texte"), parent), + QUndoCommand(QObject::tr("modifier le texte", "undo caption"), parent), text_item(dti), text_before(before), text_after(after), @@ -400,9 +435,17 @@ void ChangeDiagramTextCommand::redo() { @param parent QUndoCommand parent */ RotateElementsCommand::RotateElementsCommand(const QHash &elements, QUndoCommand *parent) : - QUndoCommand(QObject::tr("pivoter ") + QET::ElementsAndConductorsSentence(elements.count(), 0), parent), + QUndoCommand(parent), elements_to_rotate(elements) { + setText( + QString( + QObject::tr( + "pivoter %1", + "undo caption - %1 is a sentence listing the rotated content" + ) + ).arg(QET::ElementsAndConductorsSentence(elements.count(), 0)) + ); } /// Destructeur @@ -439,7 +482,7 @@ ChangeConductorCommand::ChangeConductorCommand( Qt::Corner path_t, QUndoCommand *parent ) : - QUndoCommand(QObject::tr("modifier un conducteur"), parent), + QUndoCommand(QObject::tr("modifier un conducteur", "undo caption"), parent), conductor(c), old_profile(old_p), new_profile(new_p), @@ -472,9 +515,15 @@ ResetConductorCommand::ResetConductorCommand( const QHash &cp, QUndoCommand *parent ) : - QUndoCommand(QObject::tr("R\351initialiser ") + QET::ElementsAndConductorsSentence(0, cp.count()), parent), + QUndoCommand(parent), conductors_profiles(cp) { + setText( + QObject::tr( + "R\351initialiser %1", + "undo caption - %1 is a sentence listing the reset content" + ).arg(QET::ElementsAndConductorsSentence(0, cp.count())) + ); } /// Destructeur @@ -508,7 +557,7 @@ ChangeInsetCommand::ChangeInsetCommand( const InsetProperties &new_ip, QUndoCommand *parent ) : - QUndoCommand(QObject::tr("modifier le cartouche"), parent), + QUndoCommand(QObject::tr("modifier le cartouche", "undo caption"), parent), diagram(d), old_inset(old_ip), new_inset(new_ip) @@ -537,7 +586,7 @@ void ChangeInsetCommand::redo() { @param parent QUndoCommand parent */ ChangeBorderCommand::ChangeBorderCommand(Diagram *dia, const BorderProperties &old_bp, const BorderProperties &new_bp, QUndoCommand *parent) : - QUndoCommand(QObject::tr("modifier les dimensions du sch\351ma"), parent), + QUndoCommand(QObject::tr("modifier les dimensions du sch\351ma", "undo caption"), parent), diagram(dia), old_properties(old_bp), new_properties(new_bp) @@ -564,7 +613,7 @@ void ChangeBorderCommand::redo() { @param parent QUndoCommand parent */ ChangeConductorPropertiesCommand::ChangeConductorPropertiesCommand(Conductor *c, QUndoCommand *parent) : - QUndoCommand(QObject::tr("modifier les propri\351t\351s d'un conducteur"), parent), + QUndoCommand(QObject::tr("modifier les propri\351t\351s d'un conducteur", "undo caption"), parent), conductor(c), old_settings_set(false), new_settings_set(false) diff --git a/sources/diagramcommands.h b/sources/diagramcommands.h index 30af25dfa..64f971fc3 100644 --- a/sources/diagramcommands.h +++ b/sources/diagramcommands.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/diagramcontent.cpp b/sources/diagramcontent.cpp index cecc2a508..d318dd431 100644 --- a/sources/diagramcontent.cpp +++ b/sources/diagramcontent.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/diagramcontent.h b/sources/diagramcontent.h index b8c0a7b48..a053ef6f1 100644 --- a/sources/diagramcontent.h +++ b/sources/diagramcontent.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/diagramprintdialog.cpp b/sources/diagramprintdialog.cpp index 556ef1c94..7d43ef54e 100644 --- a/sources/diagramprintdialog.cpp +++ b/sources/diagramprintdialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -16,7 +16,9 @@ along with QElectroTech. If not, see . */ #include "diagramprintdialog.h" +#include "qetprintpreviewdialog.h" #include +#include "diagramschooser.h" /** Constructeur @@ -24,52 +26,52 @@ @param printer Imprimante a utiliser @param parent Widget parent du dialogue */ -DiagramPrintDialog::DiagramPrintDialog(Diagram *dia, QWidget *parent) : +DiagramPrintDialog::DiagramPrintDialog(QETProject *project, QWidget *parent) : QWidget(parent), - diagram(dia), - dialog(0) + project_(project), + dialog_(0) { // initialise l'imprimante - printer = new QPrinter(); + printer_ = new QPrinter(); // orientation paysage par defaut - printer -> setOrientation(QPrinter::Landscape); + printer_ -> setOrientation(QPrinter::Landscape); } /** Destructeur */ DiagramPrintDialog::~DiagramPrintDialog() { - delete dialog; - delete printer; + delete dialog_; + delete printer_; } /** Definit le nom du PDF si l'utilisateur choisit une sortie vers un PDF */ -void DiagramPrintDialog::setPDFName(const QString &name) { - pdf_name = name; +void DiagramPrintDialog::setFileName(const QString &name) { + file_name_ = name; } /** @return le nom du PDF */ -QString DiagramPrintDialog::PDFName() const { - return(pdf_name); +QString DiagramPrintDialog::fileName() const { + return(file_name_); } /** Definit le nom du document */ void DiagramPrintDialog::setDocName(const QString &name) { - doc_name = name; + doc_name_ = name; } /** @return le nom du document */ QString DiagramPrintDialog::docName() const { - return(doc_name); + return(doc_name_); } /** @@ -77,31 +79,38 @@ QString DiagramPrintDialog::docName() const { */ void DiagramPrintDialog::exec() { -#ifndef Q_OS_WIN32 - if (!pdf_name.isEmpty()) printer -> setOutputFileName(pdf_name); - if (!doc_name.isEmpty()) printer -> setDocName(doc_name); -#endif + // prise en compte du nom du document + if (!doc_name_.isEmpty()) printer_ -> setDocName(doc_name_); - // affichage du dialogue d'impression standard - QPrintDialog print_dialog(printer, parentWidget()); - print_dialog.setWindowTitle(tr("Options d'impression")); - print_dialog.setEnabledOptions(QAbstractPrintDialog::PrintToFile | QAbstractPrintDialog::PrintShowPageSize); + // affichage d'un premier dialogue demandant a l'utilisateur le type + // d'impression qu'il souhaite effectuer + buildPrintTypeDialog(); + if (dialog_ -> exec() == QDialog::Rejected) return; - if (print_dialog.exec() == QDialog::Rejected) return; + // parametrage de l'imprimante en fonction du type d'impression choisi + if (printer_choice_ -> isChecked()) { + // affichage du dialogue d'impression standard pour parametrer l'imprimante + QPrintDialog print_dialog(printer_, parentWidget()); + print_dialog.setWindowTitle(tr("Options d'impression", "window title")); + print_dialog.setEnabledOptions(QAbstractPrintDialog::PrintShowPageSize); + if (print_dialog.exec() == QDialog::Rejected) return; + } else if (pdf_choice_ -> isChecked()) { + printer_ -> setOutputFormat(QPrinter::PdfFormat); + printer_ -> setOutputFileName(filepath_field_ -> text()); + } else { + printer_ -> setOutputFormat(QPrinter::PostScriptFormat); + printer_ -> setOutputFileName(filepath_field_ -> text()); + } - /* - Apres l'execution de ce premier dialogue, on connait le format papier a - utiliser, son orientation et on est sur que tout cela est supporte par - l'imprimante. - On peut donc en deduire le nombre de pages a imprimer - */ - - // affichage d'un second dialogue, non standard, pour connaitre les pages a imprimer - buildDialog(); - if (dialog -> exec() == QDialog::Rejected) return; + // Apercu avant impression + QETPrintPreviewDialog preview_dialog(project_, printer_, parentWidget()); + connect(&preview_dialog, SIGNAL(paintRequested(const QList &, bool, QPrinter *)), this, SLOT(print(const QList &, bool, QPrinter *))); + DiagramsChooser *dc = preview_dialog.diagramsChooser(); + dc -> setSelectedAllDiagrams(); + if (preview_dialog.exec() == QDialog::Rejected) return; // effectue l'impression en elle-meme - print(); + print(dc -> selectedDiagrams(), preview_dialog.fitDiagramsToPages(), printer_); } /** @@ -109,8 +118,8 @@ void DiagramPrintDialog::exec() { @return Le nombre de pages necessaires pour imprimer le schema avec l'orientation et le format papier utilise dans l'imprimante en cours. */ -int DiagramPrintDialog::pagesCount(bool fullpage) const { - return(horizontalPagesCount(fullpage) * verticalPagesCount(fullpage)); +int DiagramPrintDialog::pagesCount(Diagram *diagram, bool fullpage) const { + return(horizontalPagesCount(diagram, fullpage) * verticalPagesCount(diagram, fullpage)); } /** @@ -118,9 +127,9 @@ int DiagramPrintDialog::pagesCount(bool fullpage) const { @return La largeur du "poster" en nombre de pages pour imprimer le schema avec l'orientation et le format papier utilise dans l'imprimante en cours. */ -int DiagramPrintDialog::horizontalPagesCount(bool fullpage) const { +int DiagramPrintDialog::horizontalPagesCount(Diagram *diagram, bool fullpage) const { // note : pageRect et Paper Rect tiennent compte de l'orientation du papier - QRect printable_area = fullpage ? printer -> paperRect() : printer -> pageRect(); + QRect printable_area = fullpage ? printer_ -> paperRect() : printer_ -> pageRect(); QRect diagram_rect = diagram -> border().toRect(); int h_pages_count = int(ceil(qreal(diagram_rect.width()) / qreal(printable_area.width()))); @@ -132,9 +141,9 @@ int DiagramPrintDialog::horizontalPagesCount(bool fullpage) const { @return La largeur du "poster" en nombre de pages pour imprimer le schema avec l'orientation et le format papier utilise dans l'imprimante en cours. */ -int DiagramPrintDialog::verticalPagesCount(bool fullpage) const { +int DiagramPrintDialog::verticalPagesCount(Diagram *diagram, bool fullpage) const { // note : pageRect et Paper Rect tiennent compte de l'orientation du papier - QRect printable_area = fullpage ? printer -> paperRect() : printer -> pageRect(); + QRect printable_area = fullpage ? printer_ -> paperRect() : printer_ -> pageRect(); QRect diagram_rect = diagram -> border().toRect(); int v_pages_count = int(ceil(qreal(diagram_rect.height()) / qreal(printable_area.height()))); @@ -142,128 +151,184 @@ int DiagramPrintDialog::verticalPagesCount(bool fullpage) const { } /** - Construit un dialogue non standard pour demander les pages a imprimer a l'utilisateur + Construit un dialogue non standard pour demander a l'utilisateur quelle type + d'impression il souhaite effectuer : PDF, PS ou imprimante physique */ -void DiagramPrintDialog::buildDialog() { - dialog = new QDialog(parentWidget()); - dialog -> setMinimumWidth(460); - dialog -> setWindowTitle(tr("Options d'impression")); - options_label = new QLabel(); - use_full_page = new QCheckBox(tr("Utiliser toute la feuille")); - use_full_page_label_ = new QLabel(tr( - "Si cette option est coch\351e, les marges de la feuille seront " - "ignor\351es et toute sa surface sera utilis\351e pour l'impression. " - "Cela peut ne pas \352tre support\351 par votre imprimante." - )); - use_full_page_label_ -> setWordWrap(true); - use_full_page_label_ -> setContentsMargins(20, 0, 0, 0); - fit_diagram_to_page = new QCheckBox(tr("Adapter le sch\351ma \340 la page")); - fit_diagram_to_page_label_ = new QLabel(tr( - "Si cette option est coch\351e, le sch\351ma sera agrandi ou " - "r\351tr\351ci de fa\347on \340 remplir toute la surface imprimable " - "d'une et une seule page." - )); - fit_diagram_to_page_label_ -> setWordWrap(true); - fit_diagram_to_page_label_ -> setContentsMargins(20, 0, 0, 0); - fit_diagram_to_page -> setChecked(true); - range_from_label = new QLabel(tr("Pages \340 imprimer : plage de ")); - start_page = new QSpinBox(); - to_label = new QLabel(tr(" \340 ")); - end_page = new QSpinBox(); - buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); +void DiagramPrintDialog::buildPrintTypeDialog() { + // initialisation des widgets + dialog_ = new QDialog(parentWidget()); + printtype_label_ = new QLabel(tr("Quel type d'impression d\351sirez-vous effectuer ?")); + printer_icon_ = new QLabel(); + pdf_icon_ = new QLabel(); + ps_icon_ = new QLabel(); + printtype_choice_ = new QButtonGroup(); + printer_choice_ = new QRadioButton("Impression sur une imprimante physique"); + pdf_choice_ = new QRadioButton("Impression vers un fichier au format PDF"); + ps_choice_ = new QRadioButton("Impression vers un fichier au format PostScript (PS)"); + filepath_field_ = new QLineEdit(); + browse_button_ = new QPushButton("..."); + buttons_ = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - QHBoxLayout *pages_layout = new QHBoxLayout(); - pages_layout -> addWidget(range_from_label); - pages_layout -> addWidget(start_page); - pages_layout -> addWidget(to_label); - pages_layout -> addWidget(end_page); - pages_layout -> addStretch(); + dialog_ -> setWindowTitle(tr("Choix du type d'impression")); + printer_icon_ -> setPixmap(QPixmap(":/ico/printtype_printer.png")); + pdf_icon_ -> setPixmap(QPixmap(":/ico/printtype_pdf.png")); + ps_icon_ -> setPixmap(QPixmap(":/ico/printtype_ps.png")); + printtype_choice_ -> addButton(printer_choice_); + printtype_choice_ -> addButton(pdf_choice_); + printtype_choice_ -> addButton(ps_choice_); + printer_choice_ -> setChecked(true); + if (!file_name_.isEmpty()) filepath_field_ -> setText(file_name_ + ".pdf"); - QVBoxLayout *dialog_layout = new QVBoxLayout(dialog); - dialog_layout -> addWidget(options_label); - dialog_layout -> addWidget(use_full_page); - dialog_layout -> addWidget(use_full_page_label_); - dialog_layout -> addWidget(fit_diagram_to_page); - dialog_layout -> addWidget(fit_diagram_to_page_label_); - dialog_layout -> addLayout(pages_layout); - dialog_layout -> addStretch(); - dialog_layout -> addWidget(buttons); + // connexions signaux / slots + connect(printer_choice_, SIGNAL(toggled(bool)), this, SLOT(updatePrintTypeDialog())); + connect(pdf_choice_, SIGNAL(toggled(bool)), this, SLOT(updatePrintTypeDialog())); + connect(ps_choice_, SIGNAL(toggled(bool)), this, SLOT(updatePrintTypeDialog())); + connect(browse_button_, SIGNAL(clicked(bool)), this, SLOT(browseFilePrintTypeDialog())); + connect(buttons_, SIGNAL(accepted()), this, SLOT(acceptPrintTypeDialog())); + connect(buttons_, SIGNAL(rejected()), dialog_, SLOT(reject())); - connect(use_full_page, SIGNAL(stateChanged(int)), this, SLOT(updateDialog())); - connect(fit_diagram_to_page, SIGNAL(stateChanged(int)), this, SLOT(updateDialog())); - connect(start_page, SIGNAL(valueChanged(int)), this, SLOT(checkStartPage())); - connect(end_page, SIGNAL(valueChanged(int)), this, SLOT(checkEndPage())); - connect(buttons, SIGNAL(accepted()), dialog, SLOT(accept())); - connect(buttons, SIGNAL(rejected()), dialog, SLOT(reject())); + // organisation graphique + glayout0_ = new QGridLayout(); + hlayout0_ = new QHBoxLayout(); + vlayout0_ = new QVBoxLayout(); - updateDialog(); + hlayout0_ -> addWidget(filepath_field_); + hlayout0_ -> addWidget(browse_button_); + glayout0_ -> addWidget(printer_icon_, 0, 0); + glayout0_ -> addWidget(printer_choice_, 0, 1); + glayout0_ -> addWidget(pdf_icon_, 1, 0); + glayout0_ -> addWidget(pdf_choice_, 1, 1); + glayout0_ -> addWidget(ps_icon_, 2, 0); + glayout0_ -> addWidget(ps_choice_, 2, 1); + glayout0_ -> addLayout(hlayout0_, 3, 1); + + vlayout0_ -> addWidget(printtype_label_); + vlayout0_ -> addLayout(glayout0_); + vlayout0_ -> addWidget(buttons_); + + dialog_ -> setLayout(vlayout0_); + + updatePrintTypeDialog(); } /** - Assure la coherence du dialogue + Assure la coherence du dialogue permettant le choix du type d'impression */ -void DiagramPrintDialog::updateDialog() { - int pages_count; - // si on adapte le schema a la page, alors il n'y a qu'une page a imprimer - if (fit_diagram_to_page -> isChecked()) { - pages_count = 1; +void DiagramPrintDialog::updatePrintTypeDialog() { + // imprime-t-on vers un fichier ? + bool file_print = !(printer_choice_ -> isChecked()); + + // on n'active le champ fichier que pour les impressions vers un fichier + filepath_field_ -> setEnabled(file_print); + browse_button_ -> setEnabled(file_print); + + // on corrige eventuellement l'extension du fichier deja selectionne + if (file_print) { + QString filepath = filepath_field_ -> text(); + if (!filepath.isEmpty()) { + if (pdf_choice_ -> isChecked() && filepath.endsWith(".ps")) { + QRegExp re("\\.ps$", Qt::CaseInsensitive); + filepath.replace(re, ".pdf"); + filepath_field_ -> setText(filepath); + } else if (ps_choice_ -> isChecked() && filepath.endsWith(".pdf")) { + QRegExp re("\\.pdf$", Qt::CaseInsensitive); + filepath.replace(re, ".ps"); + filepath_field_ -> setText(filepath); + } + } + } +} + +/** + Verifie l'etat du dialogue permettant le choix du type d'impression lorsque + l'utilisateur le valide. +*/ +void DiagramPrintDialog::acceptPrintTypeDialog() { + bool file_print = !(printer_choice_ -> isChecked()); + if (file_print) { + // un fichier doit avoir ete entre + if (filepath_field_ -> text().isEmpty()) { + QMessageBox::information( + parentWidget(), + tr("Fichier manquant", "message box title"), + tr("Vous devez indiquer le chemin du fichier PDF/PS \340 cr\351er.", "message box content") + ); + } else dialog_ -> accept(); } else { - pages_count = pagesCount(use_full_page -> isChecked()); - } - options_label -> setText(tr("Nombre total de pages : ") + QString("%1").arg(pages_count)); - setPagesRangeVisible(pages_count > 1); - start_page -> setRange(1, pages_count); - end_page -> setRange(1, pages_count); - end_page -> setValue(pages_count); -} - -/** - S'assure que la premiere page ne soit pas superieure a la derniere page -*/ -void DiagramPrintDialog::checkStartPage() { - if (start_page -> value() > end_page -> value()) { - start_page -> blockSignals(true); - start_page -> setValue(end_page -> value()); - start_page -> blockSignals(false); + // une imprimante doit avoir ete selectionnee + /// @todo + dialog_ -> accept(); } } /** - S'assure que la derniere page ne soit pas inferieure a la premiere page + Permet a l'utilisateur de choisir un fichier */ -void DiagramPrintDialog::checkEndPage() { - if (end_page -> value() < start_page -> value()) { - end_page -> blockSignals(true); - end_page -> setValue(start_page -> value()); - end_page -> blockSignals(false); +void DiagramPrintDialog::browseFilePrintTypeDialog() { + QString extension; + QString filter; + if (printer_choice_ -> isChecked()) return; + else if (pdf_choice_ -> isChecked()) { + extension = ".pdf"; + filter = tr("Fichiers PDF (*.pdf)", "file filter"); + } + else if (ps_choice_ -> isChecked()) { + extension = ".ps"; + filter = tr("Fichiers PostScript (*.ps)", "file filter"); + } + + QString filepath = QFileDialog::getSaveFileName( + parentWidget(), + QString(), + filepath_field_ -> text(), + filter + ); + + if (!filepath.isEmpty()) { + if (!filepath.endsWith(extension)) filepath += extension; + filepath_field_ -> setText(filepath); } -} - -/** - @param visible true pour afficher les pages, false sinon -*/ -void DiagramPrintDialog::setPagesRangeVisible(bool visible) { - range_from_label -> setVisible(visible); - start_page -> setVisible(visible); - to_label -> setVisible(visible); - end_page -> setVisible(visible); } /** Effectue l'impression elle-meme + @param diagrams Schemas a imprimer + @param fit_page Booleen indiquant s'il faut adapter les schemas aux pages + ou non + @param printer L'imprimante a utiliser */ -void DiagramPrintDialog::print() { - // recupere les informations collectees dans le second dialogue - bool full_page = use_full_page -> isChecked(); - bool fit_page = fit_diagram_to_page -> isChecked(); - int first_page = start_page -> value(); - int last_page = end_page -> value(); - - // parametre l'imprimante - printer -> setFullPage(full_page); +void DiagramPrintDialog::print(const QList &diagrams, bool fit_page, QPrinter */*printer*/) { + //qDebug() << "Demande d'impression de " << diagrams.count() << "schemas."; // QPainter utiliser pour effectuer le rendu - QPainter qp(printer); + QPainter qp(printer_); + + // cas special : il n'y a aucun schema a imprimer + if (!diagrams.count()) { + qp.end(); + return; + } + + // imprime les schemas + for (int i = 0 ; i < diagrams.count() ; ++ i) { + printDiagram(diagrams[i], fit_page, &qp, printer_); + if (i != diagrams.count() - 1) { + printer_ -> newPage(); + } + } +} + +/** + Imprime un schema + @param diagram Schema a imprimer + @param fit_page True pour adapter les schemas aux pages, false sinon + @param qp QPainter a utiliser (deja initialise sur printer) + @param printer Imprimante a utiliser +*/ +void DiagramPrintDialog::printDiagram(Diagram *diagram, bool fit_page, QPainter *qp, QPrinter *printer) { + //qDebug() << printer -> paperSize() << printer -> paperRect() << diagram -> title(); + // l'imprimante utilise-t-elle toute la feuille ? + bool full_page = printer -> fullPage (); // impression physique (!= fichier PDF) if (printer -> outputFileName().isEmpty()) { @@ -275,15 +340,19 @@ void DiagramPrintDialog::print() { if (fit_page) { // impression adaptee sur une seule page - diagram -> render(&qp, QRectF(), diagram -> border(), Qt::KeepAspectRatio); + diagram -> render(qp, QRectF(), diagram -> border(), Qt::KeepAspectRatio); } else { // impression sur une ou plusieurs pages - QRect diagram_rect = diagram -> border().toRect(); + QRect diagram_rect = diagram -> border().adjusted(0.0, 0.0, 1.0, 1.0).toAlignedRect(); QRect printed_area = full_page ? printer -> paperRect() : printer -> pageRect(); + //qDebug() << "impression sur une ou plusieurs pages"; + //qDebug() << " schema :" << diagram_rect; + //qDebug() << " page :" << printed_area; + int used_width = printed_area.width(); int used_height = printed_area.height(); - int h_pages_count = horizontalPagesCount(full_page); - int v_pages_count = verticalPagesCount(full_page); + int h_pages_count = horizontalPagesCount(diagram, full_page); + int v_pages_count = verticalPagesCount(diagram, full_page); QVector< QVector< QRect > > pages_grid; // le schema est imprime sur une matrice de feuilles @@ -312,18 +381,20 @@ void DiagramPrintDialog::print() { QVector pages_to_print; for (int i = 0 ; i < v_pages_count ; ++ i) { for (int j = 0 ; j < h_pages_count ; ++ j) { - int page_number = (i * h_pages_count) + j + 1; - if (page_number >= first_page && page_number <= last_page) { + //int page_number = (i * h_pages_count) + j + 1; + //if (page_number >= first_page && page_number <= last_page) { pages_to_print << pages_grid.at(i).at(j); - } + //} } } + //qDebug() << " " << pages_to_print.count() << " pages a imprimer :"; // parcourt les pages pour impression for (int i = 0 ; i < pages_to_print.count() ; ++ i) { QRect current_rect(pages_to_print.at(i)); + //qDebug() << " " << current_rect; diagram -> render( - &qp, + qp, QRect(QPoint(0,0), current_rect.size()), current_rect.translated(diagram_rect.topLeft()), Qt::KeepAspectRatio diff --git a/sources/diagramprintdialog.h b/sources/diagramprintdialog.h index c2c8ee6b6..645ba0569 100644 --- a/sources/diagramprintdialog.h +++ b/sources/diagramprintdialog.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -14,11 +14,11 @@ You should have received a copy of the GNU General Public License along with QElectroTech. If not, see . - */ #ifndef DIAGRAM_PRINT_DIALOG_H #define DIAGRAM_PRINT_DIALOG_H #include +#include "qetproject.h" #include "diagram.h" /** Cette classe represente le dialogue de configuration de l'impression d'un @@ -29,48 +29,55 @@ class DiagramPrintDialog : public QWidget { Q_OBJECT // Constructeurs, destructeur public: - DiagramPrintDialog(Diagram *, QWidget * = 0); + DiagramPrintDialog(QETProject *, QWidget * = 0); virtual ~DiagramPrintDialog(); private: DiagramPrintDialog(const DiagramPrintDialog &); // methodes public: - void setPDFName(const QString &); - QString PDFName() const; + void setFileName(const QString &); + QString fileName() const; void setDocName(const QString &); QString docName() const; - int pagesCount(bool = false) const; - int horizontalPagesCount(bool = false) const; - int verticalPagesCount(bool = false) const; + int pagesCount(Diagram *, bool = false) const; + int horizontalPagesCount(Diagram *, bool = false) const; + int verticalPagesCount(Diagram *, bool = false) const; void exec(); private: + void buildPrintTypeDialog(); void buildDialog(); - void print(); private slots: - void updateDialog(); - void checkStartPage(); - void checkEndPage(); - void setPagesRangeVisible(bool); + void print(const QList &, bool, QPrinter *); + void printDiagram(Diagram *, bool, QPainter *, QPrinter * = 0); + void updatePrintTypeDialog(); + void acceptPrintTypeDialog(); + void browseFilePrintTypeDialog(); // attributs private: - Diagram *diagram; - QPrinter *printer; - QString doc_name; - QString pdf_name; - QDialog *dialog; - QLabel *options_label; - QLabel *range_from_label; - QLabel *to_label; - QCheckBox *use_full_page; - QLabel *use_full_page_label_; - QCheckBox *fit_diagram_to_page; - QLabel *fit_diagram_to_page_label_; - QSpinBox *start_page; - QSpinBox *end_page; - QDialogButtonBox *buttons; + QETProject *project_; + QPrinter *printer_; + QString doc_name_; + QString file_name_; + + /// Attributs relatifs au 1er dialogue + QDialog *dialog_; + QLabel *printtype_label_; + QGridLayout *glayout0_; + QVBoxLayout *vlayout0_; + QHBoxLayout *hlayout0_; + QLabel *printer_icon_; + QLabel *pdf_icon_; + QLabel *ps_icon_; + QButtonGroup *printtype_choice_; + QRadioButton *printer_choice_; + QRadioButton *pdf_choice_; + QRadioButton *ps_choice_; + QLineEdit *filepath_field_; + QPushButton *browse_button_; + QDialogButtonBox *buttons_; }; #endif diff --git a/sources/diagramschooser.cpp b/sources/diagramschooser.cpp new file mode 100644 index 000000000..77489bc64 --- /dev/null +++ b/sources/diagramschooser.cpp @@ -0,0 +1,159 @@ +#include "diagramschooser.h" +#include "qetproject.h" +#include "diagram.h" + +/** + Constructeur + @param project Projet dont il faut afficher les schemas + @param parent QWidget parent de ce widget +*/ +DiagramsChooser::DiagramsChooser(QETProject *project, QWidget *parent) : + QFrame(parent), + project_(project), + vlayout0_(0) +{ + setFrameShadow(QFrame::Sunken); + setFrameShape(QFrame::StyledPanel); + setLineWidth(3); + setMidLineWidth(3); + updateList(); +} + +/** + Destructeur +*/ +DiagramsChooser::~DiagramsChooser() { +} + +/** + @return le projet dont ce widget affiche les schemas +*/ +QETProject *DiagramsChooser::project() const { + return(project_); +} + +/** + @return la liste des schemas selectionnes +*/ +QList DiagramsChooser::selectedDiagrams() const { + QList selected_diagrams; + foreach(Diagram *diagram, project_ -> diagrams()) { + QCheckBox *check_box = diagrams_[diagram]; + if (check_box && check_box -> isChecked()) { + selected_diagrams << diagram; + } + } + return(selected_diagrams); +} + +/** + @return la liste des schemas qui ne sont pas selectionnes +*/ +QList DiagramsChooser::nonSelectedDiagrams() const { + QList selected_diagrams; + foreach(Diagram *diagram, diagrams_.keys()) { + if (!(diagrams_[diagram] -> isChecked())) { + selected_diagrams << diagram; + } + } + return(selected_diagrams); +} + +/** + @param diagram Un schema cense etre present dans ce widget +*/ +bool DiagramsChooser::diagramIsSelected(Diagram *const diagram) const { + QCheckBox *checkbox = diagrams_.value(diagram); + if (!checkbox) return(false); + return(checkbox -> isChecked()); +} + +/** + Selectionne les schemas contenus dans la liste diagrams_list + @param diagrams_list Liste de schemas a selectionner + @param select true pour selectionne les schemas de la liste, false pour les + deselectionner + @param reset true pour deselectionner tous les schemas avant de + selectionner ceux de la liste +*/ +void DiagramsChooser::setSelectedDiagrams(const QList &diagrams_list, bool select, bool reset) { + // evite d'emettre une rafale de signaux pour cette operation + blockSignals(true); + + // deselectionne tous les schemas si demande + if (reset) { + foreach(QCheckBox *check_box, diagrams_.values()) { + check_box -> setChecked(false); + } + } + + int changes = 0; + QCheckBox *check_box; + foreach(Diagram *diagram, diagrams_list) { + if ((check_box = diagrams_[diagram])) { + if (check_box -> isChecked() != select) { + check_box -> setChecked(select); + ++ changes; + } + } + } + + blockSignals(false); + if (reset || changes) { + emit(selectionChanged()); + } +} + +/** + Selectionne ou deselectionne tous les schemas + @param select true pour selectionne les schemas de la liste, false pour les + deselectionner +*/ +void DiagramsChooser::setSelectedAllDiagrams(bool select) { + blockSignals(true); + foreach(QCheckBox *check_box, diagrams_.values()) { + check_box -> setChecked(select); + } + blockSignals(false); + emit(selectionChanged()); +} + +/** + Met a jour la liste des schemas du projet +*/ +void DiagramsChooser::updateList() { + if (!project_) return; + + // retient la liste des schemas deja selectionnes + QList selected_diagrams = selectedDiagrams(); + + // detruit les checkbox existantes + QList checkboxes = diagrams_.values(); + qDeleteAll(checkboxes.begin(), checkboxes.end()); + + buildLayout(); + + // recree les checkbox necessaires + foreach(Diagram *diagram, project_ -> diagrams()) { + // titre du schema + QString diagram_title = diagram -> title(); + if (diagram_title.isEmpty()) diagram_title = tr("Sch\351ma sans titre"); + + QCheckBox *checkbox = new QCheckBox(diagram_title); + checkbox -> setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum)); + checkbox -> setChecked(selected_diagrams.contains(diagram)); + connect(checkbox, SIGNAL(toggled(bool)), this, SIGNAL(selectionChanged())); + diagrams_.insert(diagram, checkbox); + vlayout0_ -> addWidget(checkbox, 0, Qt::AlignLeft | Qt::AlignTop); + } + vlayout0_ -> addStretch(); +} + +/** + Met en place la disposition du widget +*/ +void DiagramsChooser::buildLayout() { + if (vlayout0_) return; + vlayout0_ = new QVBoxLayout(); + setLayout(vlayout0_); +} diff --git a/sources/diagramschooser.h b/sources/diagramschooser.h new file mode 100644 index 000000000..43d8c4ce8 --- /dev/null +++ b/sources/diagramschooser.h @@ -0,0 +1,61 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef DIAGRAMS_CHOOSER_H +#define DIAGRAMS_CHOOSER_H +#include +class QETProject; +class Diagram; +/** + Cette classe represente un widget permettant de choisir 0 a n schemas parmi + ceux d'un projet. +*/ +class DiagramsChooser : public QFrame { + Q_OBJECT + + // constructeurs, destructeur + public: + DiagramsChooser(QETProject *, QWidget * = 0); + virtual ~DiagramsChooser(); + private: + DiagramsChooser(const DiagramsChooser &); + + // methodes + public: + QETProject *project() const; + QList selectedDiagrams() const; + QList nonSelectedDiagrams() const; + bool diagramIsSelected(Diagram * const) const; + void setSelectedDiagrams(const QList &, bool = true, bool = true); + void setSelectedAllDiagrams(bool = true); + + public slots: + void updateList(); + + signals: + void selectionChanged(); + + private: + void buildLayout(); + + // attributs + private: + QETProject *project_; + QVBoxLayout *vlayout0_; + QHash diagrams_; +}; +#endif diff --git a/sources/diagramtextitem.cpp b/sources/diagramtextitem.cpp index 65d8ca490..d56af9299 100644 --- a/sources/diagramtextitem.cpp +++ b/sources/diagramtextitem.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -28,7 +28,7 @@ DiagramTextItem::DiagramTextItem(QGraphicsItem *parent, QGraphicsScene *scene) : QGraphicsTextItem(parent, scene) { setDefaultTextColor(Qt::black); - setFont(QFont(QETApp::diagramTextsFont(), 9)); + setFont(QFont(QETApp::diagramTextsFont(), QETApp::diagramTextsSize())); setFlags(QGraphicsItem::ItemIsSelectable|QGraphicsItem::ItemIsMovable); connect(this, SIGNAL(lostFocus()), this, SLOT(setNonFocusable())); } @@ -44,7 +44,7 @@ DiagramTextItem::DiagramTextItem(const QString &text, QGraphicsItem *parent, QGr previous_text(text) { setDefaultTextColor(Qt::black); - setFont(QFont(QETApp::diagramTextsFont(), 9)); + setFont(QFont(QETApp::diagramTextsFont(), QETApp::diagramTextsSize())); setFlags(QGraphicsItem::ItemIsSelectable|QGraphicsItem::ItemIsMovable); connect(this, SIGNAL(lostFocus()), this, SLOT(setNonFocusable())); } diff --git a/sources/diagramtextitem.h b/sources/diagramtextitem.h index 41019ce6c..b8e62b91c 100644 --- a/sources/diagramtextitem.h +++ b/sources/diagramtextitem.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/diagramview.cpp b/sources/diagramview.cpp index c057d72c1..7c9f3963e 100644 --- a/sources/diagramview.cpp +++ b/sources/diagramview.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -18,48 +18,53 @@ #include "diagramview.h" #include "diagram.h" #include "customelement.h" -#include "exportdialog.h" -#include "diagramprintdialog.h" #include "conductor.h" #include "diagramcommands.h" #include "conductorpropertieswidget.h" #include "insetpropertieswidget.h" +#include "qetapp.h" +#include "qetproject.h" #include "borderpropertieswidget.h" +#include "integrationmoveelementshandler.h" +#include "qetdiagrameditor.h" /** Constructeur @param parent Le QWidget parent de cette vue de schema */ -DiagramView::DiagramView(QWidget *parent) : QGraphicsView(parent), is_adding_text(false) { +DiagramView::DiagramView(Diagram *diagram, QWidget *parent) : QGraphicsView(parent), is_adding_text(false) { setAttribute(Qt::WA_DeleteOnClose, true); setInteractive(true); setCacheMode(QGraphicsView::CacheBackground); - setOptimizationFlags(QGraphicsView::DontSavePainterState|QGraphicsView::DontAdjustForAntialiasing); // active l'antialiasing setRenderHint(QPainter::Antialiasing, true); setRenderHint(QPainter::TextAntialiasing, true); setRenderHint(QPainter::SmoothPixmapTransform, true); - setScene(scene = new Diagram(this)); + scene = diagram ? diagram : new Diagram(this); + setScene(scene); scene -> undoStack().setClean(); - setDragMode(RubberBandDrag); - setAcceptDrops(true); setWindowIcon(QIcon(":/ico/qet-16.png")); setTransformationAnchor(QGraphicsView::AnchorUnderMouse); setResizeAnchor(QGraphicsView::AnchorUnderMouse); setAlignment(Qt::AlignLeft | Qt::AlignTop); + setSelectionMode(); adjustSceneRect(); updateWindowTitle(); context_menu = new QMenu(this); - paste_here = new QAction(QIcon(":/ico/paste.png"), tr("Coller ici"), this); + paste_here = new QAction(QIcon(":/ico/paste.png"), tr("Coller ici", "context menu action"), this); connect(paste_here, SIGNAL(triggered()), this, SLOT(pasteHere())); connect(scene, SIGNAL(selectionEmptinessChanged()), this, SIGNAL(selectionChanged())); + connect(scene, SIGNAL(readOnlyChanged(bool)), this, SLOT(applyReadOnly())); connect(&(scene -> border_and_inset), SIGNAL(borderChanged(QRectF, QRectF)), this, SLOT(adjustSceneRect())); connect(&(scene -> border_and_inset), SIGNAL(displayChanged()), this, SLOT(adjustSceneRect())); + connect(&(scene -> border_and_inset), SIGNAL(diagramTitleChanged(const QString &)), this, SLOT(updateWindowTitle())); connect(&(scene -> undoStack()), SIGNAL(cleanChanged(bool)), this, SLOT(updateWindowTitle())); + + connect(this, SIGNAL(aboutToAddElement()), this, SLOT(addDroppedElement()), Qt::QueuedConnection); } /** @@ -98,6 +103,7 @@ void DiagramView::selectInvert() { Supprime les composants selectionnes */ void DiagramView::deleteSelection() { + if (scene -> isReadOnly()) return; DiagramContent removed_content = scene -> selectedContent(); scene -> clearSelection(); scene -> undoStack().push(new DeleteElementsCommand(scene, removed_content)); @@ -108,6 +114,7 @@ void DiagramView::deleteSelection() { Pivote les composants selectionnes */ void DiagramView::rotateSelection() { + if (scene -> isReadOnly()) return; QHash elements_to_rotate; foreach (QGraphicsItem *item, scene -> selectedItems()) { if (Element *e = qgraphicsitem_cast(item)) { @@ -123,8 +130,11 @@ void DiagramView::rotateSelection() { @param e le QDragEnterEvent correspondant au drag'n drop tente */ void DiagramView::dragEnterEvent(QDragEnterEvent *e) { - if (e -> mimeData() -> hasFormat("text/plain")) e -> acceptProposedAction(); - else e-> ignore(); + if (e -> mimeData() -> hasFormat("application/x-qet-element-uri")) { + e -> acceptProposedAction(); + } else { + e -> ignore(); + } } /** @@ -144,18 +154,23 @@ void DiagramView::dragMoveEvent(QDragMoveEvent *e) { } /** - Gere les depots (drop) acceptes sur le Diagram + Gere les depots (drop) acceptes sur le schema. Cette methode emet le signal + aboutToAddElement si l'element depose est accessible. @param e le QDropEvent correspondant au drag'n drop effectue */ void DiagramView::dropEvent(QDropEvent *e) { - QString fichier = e -> mimeData() -> text(); - int etat; - Element *el = new CustomElement(fichier, 0, 0, &etat); - if (etat) delete el; - else { - diagram() -> undoStack().push(new AddElementCommand(diagram(), el, mapToScene(e -> pos()))); - adjustSceneRect(); - } + // recupere l'emplacement de l'element depuis le drag'n drop + QString elmt_path = e -> mimeData() -> text(); + ElementsLocation location(ElementsLocation::locationFromString(elmt_path)); + + // verifie qu'il existe un element correspondant a cet emplacement + ElementsCollectionItem *dropped_item = QETApp::collectionItem(location); + if (!dropped_item) return; + + next_location_ = location; + next_position_ = e-> pos(); + + emit(aboutToAddElement()); } /** @@ -163,6 +178,7 @@ void DiagramView::dropEvent(QDropEvent *e) { */ void DiagramView::setVisualisationMode() { setDragMode(ScrollHandDrag); + applyReadOnly(); setInteractive(false); emit(modeChanged()); } @@ -173,6 +189,7 @@ void DiagramView::setVisualisationMode() { void DiagramView::setSelectionMode() { setDragMode(RubberBandDrag); setInteractive(true); + applyReadOnly(); emit(modeChanged()); } @@ -238,6 +255,8 @@ void DiagramView::copy() { @param clipboard_mode Type de presse-papier a prendre en compte */ void DiagramView::paste(const QPointF &pos, QClipboard::Mode clipboard_mode) { + if (scene -> isReadOnly()) return; + QString texte_presse_papier = QApplication::clipboard() -> text(clipboard_mode); if ((texte_presse_papier).isEmpty()) return; @@ -270,7 +289,7 @@ void DiagramView::mousePressEvent(QMouseEvent *e) { if (e -> buttons() == Qt::MidButton) { paste(mapToScene(e -> pos()), QClipboard::Selection); } else { - if (is_adding_text && e -> buttons() == Qt::LeftButton) { + if (!scene -> isReadOnly() && is_adding_text && e -> buttons() == Qt::LeftButton) { addDiagramTextAtPos(mapToScene(e -> pos())); is_adding_text = false; } @@ -278,199 +297,6 @@ void DiagramView::mousePressEvent(QMouseEvent *e) { } } -/** - Ouvre un fichier *.qet dans cette DiagramView - @param n_fichier Nom du fichier a ouvrir - @param erreur Si le pointeur est specifie, cet entier est mis a 0 en cas de reussite de l'ouverture, 1 si le fichier n'existe pas, 2 si le fichier n'est pas lisible, 3 si le fichier n'est pas un element XML, 4 si l'ouverture du fichier a echoue pour une autre raison (c'est pas ca qui manque ^^) - @return true si l'ouverture a reussi, false sinon -*/ -bool DiagramView::open(QString n_fichier, int *erreur) { - // verifie l'existence du fichier - if (!QFileInfo(n_fichier).exists()) { - if (erreur != NULL) *erreur = 1; - return(false); - } - - // ouvre le fichier - QFile fichier(n_fichier); - if (!fichier.open(QIODevice::ReadOnly)) { - if (erreur != NULL) *erreur = 2; - return(false); - } - - // lit son contenu dans un QDomDocument - QDomDocument document; - if (!document.setContent(&fichier)) { - if (erreur != NULL) *erreur = 3; - fichier.close(); - return(false); - } - fichier.close(); - - /** - La notion de projet (ensemble de documents [schemas, nomenclatures, - ...] et d'elements) n'est pas encore geree. - Toutefois, pour gerer au mieux la transition de la 0.1 a la 0.2, - les schemas enregistres (element XML "diagram") sont integres dans un - pseudo projet (element XML "project"). - S'il y a plusieurs schemas dans un projet, tous les schemas seront - ouverts comme etant des fichiers separes - */ - // repere les schemas dans le fichier - QDomElement root = document.documentElement(); - // cas 1 : l'element racine est un "diagram" : un seul schema, pas de probleme - if (root.tagName() == "diagram") { - // construit le schema a partir du QDomDocument - QDomDocument &doc = document; - if (scene -> fromXml(doc)) { - if (erreur != NULL) *erreur = 0; - file_name = n_fichier; - scene -> undoStack().setClean(); - updateWindowTitle(); - return(true); - } else { - if (erreur != NULL) *erreur = 4; - return(false); - } - // cas 2 : l'element racine est un "project" - } else if (root.tagName() == "project") { - // verifie basiquement que la version actuelle est capable de lire ce fichier - if (root.hasAttribute("version")) { - bool conv_ok; - qreal diagram_version = root.attribute("version").toDouble(&conv_ok); - if (conv_ok && QET::version.toDouble() < diagram_version) { - QMessageBox::warning( - this, - tr("Avertissement"), - tr("Ce document semble avoir \351t\351 enregistr\351 avec une " - "version ult\351rieure de QElectroTech. Il est possible que " - "l'ouverture de tout ou partie de ce document \351choue.") - ); - } - } - - // compte le nombre de schemas dans le projet - QList diagrams; - - QDomNodeList diagram_nodes = root.elementsByTagName("diagram"); - for (uint i = 0 ; i < diagram_nodes.length() ; ++ i) { - if (diagram_nodes.at(i).isElement()) { - diagrams << diagram_nodes.at(i).toElement(); - } - } - - // il n'y aucun schema la-dedans - if (!diagrams.count()) { - if (erreur != NULL) *erreur = 4; - return(false); - } else { - - bool keep_doc_name = diagrams.count() == 1; - bool current_dv_loaded = false; - for (int i = 0 ; i < diagrams.count() ; ++ i) { - // cree un QDomDocument representant le schema - QDomDocument diagram_doc; - diagram_doc.appendChild(diagram_doc.importNode(diagrams[i], true)); - - // charge le premier schema valide et cree de nouveau DiagramView pour les suivants - if (!current_dv_loaded) { - if (scene -> fromXml(diagram_doc)) { - if (keep_doc_name) file_name = n_fichier; - scene -> undoStack().setClean(); - updateWindowTitle(); - current_dv_loaded = true; - } - } else { - DiagramView *new_dv = new DiagramView(parentWidget()); - if (new_dv -> scene -> fromXml(diagram_doc)) { - if (keep_doc_name) new_dv -> file_name = n_fichier; - new_dv -> scene -> undoStack().setClean(); - new_dv -> updateWindowTitle(); - diagramEditor() -> addDiagramView(new_dv); - } else { - delete(new_dv); - } - } - } - return(true); - } - - } else { - if (erreur != NULL) *erreur = 4; - return(false); - } -} - -/** - Gere la fermeture du schema. - @param event Le QCloseEvent decrivant l'evenement -*/ -void DiagramView::closeEvent(QCloseEvent *event) { - bool retour; - // si le schema est modifie - if (!isWindowModified()) { - retour = true; - } else { - // demande d'abord a l'utilisateur s'il veut enregistrer le schema en cours - QMessageBox::StandardButton reponse = QMessageBox::question( - this, - tr("Enregistrer le sch\351ma en cours ?"), - tr("Voulez-vous enregistrer le sch\351ma ") + windowTitle() + tr(" ?"), - QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel, - QMessageBox::Cancel - ); - switch(reponse) { - case QMessageBox::Cancel: retour = false; break; // l'utilisateur annule : echec de la fermeture - case QMessageBox::Yes: retour = save(); break; // l'utilisateur dit oui : la reussite depend de l'enregistrement - default: retour = true; // l'utilisateur dit non ou ferme le dialogue: c'est reussi - } - } - if (retour) event -> accept(); - else event -> ignore(); - -} - -/** - Methode enregistrant le schema dans le dernier nom de fichier connu. - Si aucun nom de fichier n'est connu, cette methode appelle la methode saveAs - @return true si l'enregistrement a reussi, false sinon -*/ -bool DiagramView::save() { - if (file_name.isEmpty()) return(saveAs()); - else return(saveDiagramToFile(file_name)); -} - -/** - Cette methode demande un nom de fichier a l'utilisateur pour enregistrer le schema - Si aucun nom n'est entre, elle renvoie faux. - Si le nom ne se termine pas par l'extension .qet, celle-ci est ajoutee. - Si l'enregistrement reussit, le nom du fichier est conserve et la fonction renvoie true. - Sinon, faux est renvoye. - @return true si l'enregistrement a reussi, false sinon -*/ -bool DiagramView::saveAs() { - // demande un nom de fichier a l'utilisateur pour enregistrer le schema - QString n_fichier = QFileDialog::getSaveFileName( - this, - tr("Enregistrer sous"), - (file_name.isEmpty() ? QDir::homePath() : QDir(file_name)).absolutePath(), - tr("Sch\351ma QElectroTech (*.qet)") - ); - // si aucun nom n'est entre, renvoie faux. - if (n_fichier.isEmpty()) return(false); - // si le nom ne se termine pas par l'extension .qet, celle-ci est ajoutee - if (!n_fichier.endsWith(".qet", Qt::CaseInsensitive)) n_fichier += ".qet"; - // tente d'enregistrer le fichier - bool resultat_enregistrement = saveDiagramToFile(n_fichier); - // si l'enregistrement reussit, le nom du fichier est conserve - if (resultat_enregistrement) { - file_name = n_fichier; - updateWindowTitle(); - } - // retourne un booleen representatif de la reussite de l'enregistrement - return(resultat_enregistrement); -} - /** Gere les actions liees a la rollette de la souris @param e QWheelEvent decrivant l'evenement rollette @@ -489,70 +315,28 @@ void DiagramView::wheelEvent(QWheelEvent *e) { } /** - Methode privee gerant l'enregistrement du fichier XML. S'il n'est pas possible - d'ecrire dans le fichier, cette fonction affiche un message d'erreur et renvoie false. - Autrement, elle renvoie true. - @param n_fichier Nom du fichier dans lequel l'arbre XML doit etre ecrit - @return true si l'enregistrement a reussi, false sinon + @return le titre de cette vue ; cela correspond au titre du schema + visualise precede de la mention "Schema". Si le titre du schema est vide, + la mention "Schema sans titre" est utilisee + @see Diagram::title() */ -bool DiagramView::saveDiagramToFile(QString &n_fichier) { - QFile fichier(n_fichier); - if (!fichier.open(QIODevice::WriteOnly | QIODevice::Text)) { - QMessageBox::warning(this, tr("Erreur"), tr("Impossible d'ecrire dans ce fichier")); - return(false); - } - QTextStream out(&fichier); - out.setCodec("UTF-8"); - - // l'export XML du schema est encapsule dans un pseudo-projet - QDomDocument final_document; - QDomElement project_root = final_document.createElement("project"); - project_root.setAttribute("version", QET::version); - project_root.appendChild(final_document.importNode(scene -> toXml().documentElement(), true)); - final_document.appendChild(project_root); - - out << final_document.toString(4); - fichier.close(); - scene -> undoStack().setClean(); - return(true); -} - -/** - Exporte le schema. -*/ -void DiagramView::dialogExport() { - ExportDialog ed(scene, diagramEditor()); - ed.exec(); -} - -/** - Imprime le schema. -*/ -void DiagramView::dialogPrint() { - - // determine un nom possible pour le document et le pdf - QString doc_name; - QString pdf_file_name; - if (!file_name.isEmpty()) { - doc_name = QFileInfo(file_name).fileName(); - pdf_file_name = file_name; - pdf_file_name.replace(QRegExp("\\.qet$", Qt::CaseInsensitive), ""); +QString DiagramView::title() const { + QString view_title; + QString diagram_title(scene -> title()); + if (diagram_title.isEmpty()) { + view_title = tr("Sch\351ma sans titre"); } else { - doc_name = tr("schema"); - pdf_file_name = QDir::toNativeSeparators(QDir::homePath() + "/" + tr("schema")); + view_title = QString(tr("Sch\351ma %1", "%1 is a diagram title")).arg(diagram_title); } - pdf_file_name += ".pdf"; - - DiagramPrintDialog print_dialog(scene, this); - print_dialog.setDocName(doc_name); - print_dialog.setPDFName(pdf_file_name); - print_dialog.exec(); + return(view_title); } /** Edite les informations du schema. */ -void DiagramView::dialogEditInfos() { +void DiagramView::editDiagramProperties() { + if (scene -> isReadOnly()) return; + // recupere le cartouche et les dimensions du schema InsetProperties inset = scene -> border_and_inset.exportInset(); BorderProperties border = scene -> border_and_inset.exportBorder(); @@ -560,7 +344,7 @@ void DiagramView::dialogEditInfos() { // construit le dialogue QDialog popup(diagramEditor()); popup.setMinimumWidth(400); - popup.setWindowTitle(tr("Propri\351t\351s du sch\351ma")); + popup.setWindowTitle(tr("Propri\351t\351s du sch\351ma", "window title")); BorderPropertiesWidget *border_infos = new BorderPropertiesWidget(border, &popup); InsetPropertiesWidget *inset_infos = new InsetPropertiesWidget(inset, false, &popup); @@ -603,6 +387,7 @@ bool DiagramView::hasSelectedItems() { Ajoute une colonne au schema. */ void DiagramView::addColumn() { + if (scene -> isReadOnly()) return; BorderProperties old_bp = scene -> border_and_inset.exportBorder(); BorderProperties new_bp = scene -> border_and_inset.exportBorder(); new_bp.columns_count += 1; @@ -613,6 +398,7 @@ void DiagramView::addColumn() { Enleve une colonne au schema. */ void DiagramView::removeColumn() { + if (scene -> isReadOnly()) return; BorderProperties old_bp = scene -> border_and_inset.exportBorder(); BorderProperties new_bp = scene -> border_and_inset.exportBorder(); new_bp.columns_count -= 1; @@ -623,6 +409,7 @@ void DiagramView::removeColumn() { Agrandit le schema en hauteur */ void DiagramView::addRow() { + if (scene -> isReadOnly()) return; BorderProperties old_bp = scene -> border_and_inset.exportBorder(); BorderProperties new_bp = scene -> border_and_inset.exportBorder(); new_bp.rows_count += 1; @@ -633,6 +420,7 @@ void DiagramView::addRow() { Retrecit le schema en hauteur */ void DiagramView::removeRow() { + if (scene -> isReadOnly()) return; BorderProperties old_bp = scene -> border_and_inset.exportBorder(); BorderProperties new_bp = scene -> border_and_inset.exportBorder(); new_bp.rows_count -= 1; @@ -665,12 +453,19 @@ void DiagramView::adjustSceneRect() { Met a jour le titre du widget */ void DiagramView::updateWindowTitle() { - QString window_title; - if (file_name.isNull()) window_title += tr("nouveau sch\351ma"); - else window_title += file_name; - window_title += "[*]"; - setWindowTitle(window_title); - setWindowModified(!(scene -> undoStack().isClean())); + QString view_title(title()); + + // verifie si le document a ete modifie + bool modified_diagram = !(scene -> undoStack().isClean()); + + // specifie le titre du widget + setWindowTitle(view_title + " [*]"); + setWindowModified(modified_diagram); + + // emet le signal titleChanged en ajoutant manuellement [*] si le schema a ete modifie + QString emitted_title = view_title; + if (modified_diagram) emitted_title += " [*]"; + emit(titleChanged(this, emitted_title)); } /** @@ -699,6 +494,63 @@ QRectF DiagramView::viewedSceneRect() const { return(QRectF(scene_left_top, scene_right_bottom)); } +/** + Cette methode permet de determiner s'il faut ou non integrer au projet un + element dont on connait l'emplacement. + L'element droppe est integre a la collection du projet : + * s'il appartient a un autre projet, quelque soit la specification de + l'utilisateur a ce propos ; + * s'il appartient a la collection commune ou a la collection + personnelle ET que l'utilisateur a autorise l'integration automatique + des elements dans les projets. + @param location Emplacement de l'element + @return true si l'element doit etre integre, false sinon + +*/ +bool DiagramView::mustIntegrateElement(const ElementsLocation &location) const { + // l'utilisateur a-t-il autorise l'integration automatique des elements dans les projets ? + bool auto_integration_enabled = QETApp::settings().value("diagrameditor/integrate-elements", true).toBool(); + + // l'element appartient-il a un projet et si oui, est-ce un autre projet ? + bool elmt_from_project = location.project(); + bool elmt_from_another_project = elmt_from_project && location.project() != scene -> project(); + + // faut-il integrer l'element ? + bool must_integrate_element = (elmt_from_another_project || (auto_integration_enabled && !elmt_from_project)); + + return(must_integrate_element); +} + +/** + @param location Emplacement de l'element a ajouter sur le schema + @param pos Position (dans les coordonnees de la vue) a laquelle l'element sera ajoute +*/ +bool DiagramView::addElementAtPos(const ElementsLocation &location, const QPoint &pos) { + // construit une instance de l'element correspondant a l'emplacement + int etat; + Element *el = new CustomElement(location, 0, 0, &etat); + if (etat) { + delete el; + return(false); + } + + // pose de l'element sur le schema + diagram() -> undoStack().push(new AddElementCommand(diagram(), el, mapToScene(pos))); + return(true); +} + +/** + Fait en sorte que le schema ne soit editable que s'il n'est pas en lecture + seule +*/ +void DiagramView::applyReadOnly() { + if (!scene) return; + + bool is_writable = !scene -> isReadOnly(); + setInteractive(is_writable); + setAcceptDrops(is_writable); +} + /** Affiche un dialogue permettant d'editer le conducteur selectionne. Ne fait rien s'il y a 0 ou plusieurs conducteurs selectionnes. @@ -718,6 +570,7 @@ void DiagramView::editConductor() { @param edited_conductor Conducteur a editer */ void DiagramView::editConductor(Conductor *edited_conductor) { + if (scene -> isReadOnly()) return; if (!edited_conductor) return; // initialise l'editeur de proprietes pour le conducteur @@ -726,7 +579,7 @@ void DiagramView::editConductor(Conductor *edited_conductor) { // l'insere dans un dialogue QDialog conductor_dialog(diagramEditor()); - conductor_dialog.setWindowTitle(tr("\311diter les propri\351t\351s d'un conducteur")); + conductor_dialog.setWindowTitle(tr("\311diter les propri\351t\351s d'un conducteur", "window title")); QVBoxLayout *dialog_layout = new QVBoxLayout(&conductor_dialog); dialog_layout -> addWidget(cpw); dialog_layout -> addStretch(); @@ -754,6 +607,7 @@ void DiagramView::editConductor(Conductor *edited_conductor) { Reinitialise le profil des conducteurs selectionnes */ void DiagramView::resetConductors() { + if (scene -> isReadOnly()) return; // recupere les conducteurs selectionnes QSet selected_conductors = scene -> selectedConductors(); @@ -780,12 +634,13 @@ void DiagramView::resetConductors() { futurs nouveaux conducteurs */ void DiagramView::editDefaultConductorProperties() { + if (scene -> isReadOnly()) return; // initialise l'editeur de proprietes pour le conducteur ConductorPropertiesWidget *cpw = new ConductorPropertiesWidget(scene -> defaultConductorProperties); // l'insere dans un dialogue QDialog conductor_dialog(diagramEditor()); - conductor_dialog.setWindowTitle(tr("\311diter les propri\351t\351s par d\351faut des conducteurs")); + conductor_dialog.setWindowTitle(tr("\311diter les propri\351t\351s par d\351faut des conducteurs", "window title")); QVBoxLayout *dialog_layout = new QVBoxLayout(&conductor_dialog); dialog_layout -> addWidget(cpw); QDialogButtonBox *dbb = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); @@ -818,6 +673,7 @@ bool DiagramView::event(QEvent *e) { nouveau champ de texte. */ void DiagramView::addText() { + if (scene -> isReadOnly()) return; is_adding_text = true; } @@ -935,8 +791,36 @@ void DiagramView::mouseDoubleClickEvent(QMouseEvent *e) { } } else if (inset_rect.contains(click_pos) || columns_rect.contains(click_pos) || rows_rect.contains(click_pos)) { // edite les proprietes du schema - dialogEditInfos(); + editDiagramProperties(); } else { QGraphicsView::mouseDoubleClickEvent(e); } } + +/** + Cette methode ajoute l'element deisgne par l'emplacement location a la + position pos. Si necessaire, elle demande l'integration de l'element au + projet. + @param location emplacement d'un element a ajouter sur le schema + @param pos position voulue de l'element sur le schema + @see mustIntegrateElement +*/ +void DiagramView::addDroppedElement() { + ElementsLocation location = next_location_; + QPoint pos = next_position_; + + if (!mustIntegrateElement(location)) { + addElementAtPos(location, pos); + } else { + QString error_msg; + IntegrationMoveElementsHandler *integ_handler = new IntegrationMoveElementsHandler(this); + QString integ_path = scene -> project() -> integrateElement(location.toString(), integ_handler, error_msg); + delete integ_handler; + if (integ_path.isEmpty()) { + qDebug() << error_msg; + return; + } + addElementAtPos(ElementsLocation::locationFromString(integ_path), pos); + } + adjustSceneRect(); +} diff --git a/sources/diagramview.h b/sources/diagramview.h index d8acfe8a1..e5ae53949 100644 --- a/sources/diagramview.h +++ b/sources/diagramview.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -18,6 +18,7 @@ #ifndef DIAGRAMVIEW_H #define DIAGRAMVIEW_H #include +#include "elementslocation.h" class Diagram; class DiagramTextItem; class QETDiagramEditor; @@ -30,33 +31,26 @@ class DiagramView : public QGraphicsView { // constructeurs, destructeur public: - DiagramView(QWidget * = 0); + DiagramView(Diagram * = 0, QWidget * = 0); virtual ~DiagramView(); private: DiagramView(const DiagramView &); // attributs - public: - /// Nom de fichier du schema edite - QString file_name; - private: Diagram *scene; QMenu *context_menu; QAction *paste_here; QPoint paste_here_pos; bool is_adding_text; + ElementsLocation next_location_; + QPoint next_position_; // methodes public: - bool open(QString, int * = NULL); - void closeEvent(QCloseEvent *); - bool save(); - bool saveAs(); - void dialogExport(); - void dialogEditInfos(); - void dialogPrint(); + QString title() const; + void editDiagramProperties(); void addColumn(); void removeColumn(); void addRow(); @@ -75,13 +69,14 @@ class DiagramView : public QGraphicsView { virtual bool event(QEvent *); private: - bool saveDiagramToFile(QString &); void mousePressEvent(QMouseEvent *); void dragEnterEvent(QDragEnterEvent *); void dragLeaveEvent(QDragLeaveEvent *); void dragMoveEvent(QDragMoveEvent *); void dropEvent(QDropEvent *); QRectF viewedSceneRect() const; + bool mustIntegrateElement(const ElementsLocation &) const; + bool addElementAtPos(const ElementsLocation &, const QPoint &); signals: /// Signal emis lorsque la selection change @@ -90,6 +85,10 @@ class DiagramView : public QGraphicsView { void modeChanged(); /// Signal emis lorsqu'un texte a ete pose void textAdded(bool); + /// Signal emis lorsque le titre du schema change + void titleChanged(DiagramView *, const QString &); + /// Signal emis avant l'integration d'un element + void aboutToAddElement(); public slots: void selectNothing(); @@ -115,6 +114,8 @@ class DiagramView : public QGraphicsView { void editDefaultConductorProperties(); private slots: + void addDroppedElement(); void adjustGridToZoom(); + void applyReadOnly(); }; #endif diff --git a/sources/editor/arceditor.cpp b/sources/editor/arceditor.cpp index a8cd04d36..3d18fd8a5 100644 --- a/sources/editor/arceditor.cpp +++ b/sources/editor/arceditor.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/editor/arceditor.h b/sources/editor/arceditor.h index 2349ec8bf..5c3e1fc9a 100644 --- a/sources/editor/arceditor.h +++ b/sources/editor/arceditor.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/editor/circleeditor.cpp b/sources/editor/circleeditor.cpp index 569fe16d7..571cf668c 100644 --- a/sources/editor/circleeditor.cpp +++ b/sources/editor/circleeditor.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/editor/circleeditor.h b/sources/editor/circleeditor.h index d71b610cb..f7de93c30 100644 --- a/sources/editor/circleeditor.h +++ b/sources/editor/circleeditor.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/editor/customelementgraphicpart.cpp b/sources/editor/customelementgraphicpart.cpp index 653d6590d..3e38ed73c 100644 --- a/sources/editor/customelementgraphicpart.cpp +++ b/sources/editor/customelementgraphicpart.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/editor/customelementgraphicpart.h b/sources/editor/customelementgraphicpart.h index a5556013d..7c7eb6fcd 100644 --- a/sources/editor/customelementgraphicpart.h +++ b/sources/editor/customelementgraphicpart.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -74,6 +74,7 @@ class CustomElementGraphicPart : public CustomElementPart { /// Destructeur virtual ~CustomElementGraphicPart() { + if (style_editor -> parentWidget()) return; // l'editeur de style sera supprime par son parent delete style_editor; }; diff --git a/sources/editor/customelementpart.cpp b/sources/editor/customelementpart.cpp index cb71595b9..29b9135d9 100644 --- a/sources/editor/customelementpart.cpp +++ b/sources/editor/customelementpart.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/editor/customelementpart.h b/sources/editor/customelementpart.h index 269fafbd9..8db523dde 100644 --- a/sources/editor/customelementpart.h +++ b/sources/editor/customelementpart.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/editor/editorcommands.cpp b/sources/editor/editorcommands.cpp index 83a5c63c0..34eec2d53 100644 --- a/sources/editor/editorcommands.cpp +++ b/sources/editor/editorcommands.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -29,7 +29,7 @@ DeletePartsCommand::DeletePartsCommand( const QList parts, QUndoCommand *parent ) : - QUndoCommand(QObject::tr("suppression"), parent), + QUndoCommand(QObject::tr("suppression", "undo caption"), parent), deleted_parts(parts), editor_scene(scene) { @@ -59,6 +59,94 @@ void DeletePartsCommand::redo() { } } +/*** CutPartsCommand ***/ +/** + Constructeur + @param scene ElementScene concernee + @param parts Liste des parties collees + @param parent QUndoCommand parent +*/ +PastePartsCommand::PastePartsCommand( + ElementView *view, + const ElementContent &c, + QUndoCommand *parent +) : + QUndoCommand(parent), + content_(c), + editor_view_(view), + editor_scene_(view -> scene()), + uses_offset(false), + first_redo(true) +{ + setText(QObject::tr("coller")); + editor_scene_ -> qgiManager().manage(content_); +} + +/// Destructeur +PastePartsCommand::~PastePartsCommand() { + editor_scene_ -> qgiManager().release(content_); +} + +/// annule le coller +void PastePartsCommand::undo() { + // enleve les parties + foreach(QGraphicsItem *part, content_) editor_scene_ -> removeItem(part); + if (uses_offset) { + editor_view_ -> offset_paste_count_ = old_offset_paste_count_; + editor_view_ -> start_top_left_corner_ = old_start_top_left_corner_; + } + editor_view_ -> adjustSceneRect(); +} + +/// refait le coller +void PastePartsCommand::redo() { + if (first_redo) first_redo = false; + else { + // pose les parties + foreach(QGraphicsItem *part, content_) editor_scene_ -> addItem(part); + if (uses_offset) { + editor_view_ -> offset_paste_count_ = new_offset_paste_count_; + editor_view_ -> start_top_left_corner_ = new_start_top_left_corner_; + } + } + foreach(QGraphicsItem *part, content_) part -> setSelected(true); + editor_view_ -> adjustSceneRect(); +} + +/** + Indique a cet objet d'annulation que le c/c a annuler ou refaire etait un + c/c avec decalage ; il faut plus d'informations pour annuler ce type de + collage. +*/ +void PastePartsCommand::setOffset(int old_offset_pc, const QPointF &old_start_tlc, int new_offset_pc, const QPointF &new_start_tlc) { + old_offset_paste_count_ = old_offset_pc; + old_start_top_left_corner_ = old_start_tlc; + new_offset_paste_count_ = new_offset_pc; + new_start_top_left_corner_ = new_start_tlc; + uses_offset = true; +} + +/*** CutPartsCommand ***/ +/** + Constructeur + @param scene ElementScene concernee + @param parts Liste des parties coupees + @param parent QUndoCommand parent +*/ +CutPartsCommand::CutPartsCommand( + ElementScene *scene, + const QList parts, + QUndoCommand *parent +) : + DeletePartsCommand(scene, parts, parent) +{ + setText(QString(QObject::tr("couper des parties", "undo caption"))); +} + +/// Destructeur +CutPartsCommand::~CutPartsCommand() { +} + /*** MovePartsCommand ***/ /** Constructeur @@ -73,7 +161,7 @@ MovePartsCommand::MovePartsCommand( const QList parts, QUndoCommand *parent ) : - QUndoCommand(QObject::tr("d\351placement"), parent), + QUndoCommand(QObject::tr("d\351placement", "undo caption"), parent), movement(m), first_redo(true) { @@ -114,7 +202,7 @@ AddPartCommand::AddPartCommand( QGraphicsItem *p, QUndoCommand *parent ) : - QUndoCommand(QObject::tr("ajout ") + name, parent), + QUndoCommand(QString(QObject::tr("ajout %1", "undo caption")).arg(name), parent), part(p), editor_scene(scene), first_redo(true) @@ -161,7 +249,7 @@ ChangePartCommand::ChangePartCommand( const QVariant &new_v, QUndoCommand *parent ) : - QUndoCommand(QObject::tr("modification ") + name, parent), + QUndoCommand(QString(QObject::tr("modification %1", "undo caption")).arg(name), parent), cep(part), property(prop), old_value(old_v), @@ -196,7 +284,7 @@ ChangePolygonPointsCommand::ChangePolygonPointsCommand( const QVector &n_points, QUndoCommand *parent ) : - QUndoCommand(QObject::tr("modification points polygone"), parent), + QUndoCommand(QObject::tr("modification points polygone", "undo caption"), parent), polygon(p), old_points(o_points), new_points(n_points) @@ -236,7 +324,7 @@ ChangeHotspotCommand::ChangeHotspotCommand( const QPoint &o, QUndoCommand *parent ) : - QUndoCommand(QObject::tr("modification dimensions/hotspot"), parent), + QUndoCommand(QObject::tr("modification dimensions/hotspot", "undo caption"), parent), element(element_scene), size_before(size_1), size_after(size_2), @@ -297,7 +385,7 @@ ChangeNamesCommand::ChangeNamesCommand( const NamesList &after, QUndoCommand *parent ) : - QUndoCommand(QObject::tr("modification noms"), parent), + QUndoCommand(QObject::tr("modification noms", "undo caption"), parent), names_before(before), names_after(after), element(element_scene) @@ -331,7 +419,7 @@ ChangeOrientationsCommand::ChangeOrientationsCommand( const OrientationSet &after, QUndoCommand *parent ) : - QUndoCommand(QObject::tr("modification orientations"), parent), + QUndoCommand(QObject::tr("modification orientations", "undo caption"), parent), ori_before(before), ori_after(after), element(element_scene) @@ -375,16 +463,16 @@ ChangeZValueCommand::ChangeZValueCommand( // choisit le nom en fonction du traitement if (option == BringForward) { - setText(QObject::tr("amener au premier plan")); + setText(QObject::tr("amener au premier plan", "undo caption")); applyBringForward(items_list); } else if (option == Raise) { - setText(QObject::tr("rapprocher")); + setText(QObject::tr("rapprocher", "undo caption")); applyRaise(items_list); } else if (option == Lower) { - setText(QObject::tr("\351loigner")); + setText(QObject::tr("\351loigner", "undo caption")); applyLower(items_list); } else if (option == SendBackward) { - setText(QObject::tr("envoyer au fond")); + setText(QObject::tr("envoyer au fond", "undo caption")); applySendBackward(items_list); } } @@ -483,7 +571,7 @@ void ChangeZValueCommand::applySendBackward(const QList &items_ @param parent QUndoCommand parent */ AllowInternalConnectionsCommand::AllowInternalConnectionsCommand(ElementScene *elmt, bool allow, QUndoCommand *parent) : - QUndoCommand(QObject::tr("modification connexions internes"), parent), + QUndoCommand(QObject::tr("modification connexions internes", "undo caption"), parent), element(elmt), ic(allow) { diff --git a/sources/editor/editorcommands.h b/sources/editor/editorcommands.h index 5e48903e6..03cb2ca20 100644 --- a/sources/editor/editorcommands.h +++ b/sources/editor/editorcommands.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -19,7 +19,9 @@ #define EDITOR_COMMANDS_H #include "customelementpart.h" #include "partpolygon.h" +#include "elementview.h" #include "elementscene.h" +#include "elementcontent.h" #include "qgimanager.h" #include /** @@ -47,6 +49,52 @@ class DeletePartsCommand : public QUndoCommand { ElementScene *editor_scene; }; +/** + Cette classe represente l'action de coller quelque chose dans un element +*/ +class PastePartsCommand : public QUndoCommand { + // constructeurs, destructeur + public: + PastePartsCommand(ElementView *, const ElementContent &, QUndoCommand * = 0); + virtual ~PastePartsCommand(); + private: + PastePartsCommand(const PastePartsCommand &); + + // methodes + public: + virtual void undo(); + virtual void redo(); + virtual void setOffset(int, const QPointF &, int, const QPointF &); + + // attributs + private: + /// contenu ajoute + ElementContent content_; + /// schema sur lequel on colle les elements et conducteurs + ElementView *editor_view_; + ElementScene *editor_scene_; + /// Informations pour annuler un c/c avec decalage + int old_offset_paste_count_; + QPointF old_start_top_left_corner_; + int new_offset_paste_count_; + QPointF new_start_top_left_corner_; + bool uses_offset; + /// booleen pour empecher le premier appel a redo + bool first_redo; +}; + +/** + Cette classe represente l'action de supprimer des parties d'un element +*/ +class CutPartsCommand : public DeletePartsCommand { + // constructeurs, destructeur + public: + CutPartsCommand(ElementScene *, const QList, QUndoCommand * = 0); + virtual ~CutPartsCommand(); + private: + CutPartsCommand(const CutPartsCommand &); +}; + /** Cette classe represente l'action de deplacer une ou plusieurs parties lors de l'edition d'un element diff --git a/sources/editor/elementcontent.h b/sources/editor/elementcontent.h new file mode 100644 index 000000000..1240a7238 --- /dev/null +++ b/sources/editor/elementcontent.h @@ -0,0 +1,30 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef ELEMENT_CONTENT_H +#define ELEMENT_CONTENT_H +#include +class QGraphicsItem; +/** + Lors de son edition dans l'editeur d'element, un element est decompose en + parties graphiques. La classe ElementContent represente un ensemble de parties + graphiques constituant tout ou partie d'un element. + Note : pour le moment, ElementContent est un typedef pour QList\ + @see la documentation Qt de la classe QList +*/ +typedef QList ElementContent; +#endif diff --git a/sources/editor/elementitemeditor.cpp b/sources/editor/elementitemeditor.cpp index f907ea1aa..9a715f81e 100644 --- a/sources/editor/elementitemeditor.cpp +++ b/sources/editor/elementitemeditor.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/editor/elementitemeditor.h b/sources/editor/elementitemeditor.h index 33803b264..7762a1507 100644 --- a/sources/editor/elementitemeditor.h +++ b/sources/editor/elementitemeditor.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -22,7 +22,7 @@ class QETElementEditor; class ElementScene; class CustomElementPart; /** - Cette classe est la classe de base pour les editeurs de aprties dans + Cette classe est la classe de base pour les editeurs de parties dans l'editeur d'element. Elle fournit des methodes pour acceder facilement a l'editeur, a la pile d'annulation, a la scene d'edition ou encore pour ajouter facilement une annulation de type ChangePartCommand. diff --git a/sources/editor/elementscene.cpp b/sources/editor/elementscene.cpp index f6096aad8..b1100d5df 100644 --- a/sources/editor/elementscene.cpp +++ b/sources/editor/elementscene.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -19,6 +19,7 @@ #include "qetelementeditor.h" #include #include "partline.h" +#include "partrectangle.h" #include "partellipse.h" #include "partcircle.h" #include "partpolygon.h" @@ -28,9 +29,7 @@ #include "partarc.h" #include "hotspoteditor.h" #include "editorcommands.h" - -const int ElementScene::xGrid = 10; -const int ElementScene::yGrid = 10; +#include "elementcontent.h" /** Constructeur @@ -47,6 +46,8 @@ ElementScene::ElementScene(QETElementEditor *editor, QObject *parent) : element_editor(editor) { current_polygon = NULL; + setGrid(1, 1); + initPasteArea(); undo_stack.setClean(); } @@ -68,6 +69,13 @@ void ElementScene::slot_addLine() { behavior = Line; } +/** + Passe la scene en mode "ajout de rectangle" +*/ +void ElementScene::slot_addRectangle() { + behavior = Rectangle; +} + /** Passe la scene en mode "ajout de cercle" */ @@ -104,7 +112,6 @@ void ElementScene::slot_addTerminal() { behavior = Terminal; } - /** Passe la scene en mode "ajout d'arc de cercle" */ @@ -124,7 +131,17 @@ void ElementScene::slot_addTextField() { @param e objet decrivant l'evenement */ void ElementScene::mouseMoveEvent(QGraphicsSceneMouseEvent *e) { + QPointF event_pos = e -> scenePos(); + if (mustSnapToGrid(e)) snapToGrid(event_pos); + if (behavior != Polygon && current_polygon != NULL) current_polygon = NULL; + if (behavior == PasteArea) { + QRectF current_rect(paste_area_ -> rect()); + current_rect.moveCenter(event_pos); + paste_area_ -> setRect(current_rect); + return; + } + QRectF temp_rect; qreal radius; QPointF temp_point; @@ -132,21 +149,26 @@ void ElementScene::mouseMoveEvent(QGraphicsSceneMouseEvent *e) { if (e -> buttons() & Qt::LeftButton) { switch(behavior) { case Line: - current_line -> setLine(QLineF(current_line -> line().p1(), e -> scenePos())); + current_line -> setLine(QLineF(current_line -> line().p1(), event_pos)); + break; + case Rectangle: + temp_rect = current_rectangle -> rect(); + temp_rect.setBottomRight(event_pos); + current_rectangle -> setRect(temp_rect); break; case Ellipse: temp_rect = current_ellipse -> rect(); - temp_rect.setBottomRight(e -> scenePos()); + temp_rect.setBottomRight(event_pos); current_ellipse -> setRect(temp_rect); break; case Arc: temp_rect = current_arc -> rect(); - temp_rect.setBottomRight(e -> scenePos()); + temp_rect.setBottomRight(event_pos); current_arc -> setRect(temp_rect); break; case Circle: temp_rect = current_circle -> rect(); - temp_point = e -> scenePos() - current_circle -> mapToScene(temp_rect.center()); + temp_point = event_pos - current_circle -> mapToScene(temp_rect.center()); radius = sqrt(pow(temp_point.x(), 2) + pow(temp_point.y(), 2)); temp_rect = QRectF( temp_rect.center() - QPointF(radius, radius), @@ -158,17 +180,35 @@ void ElementScene::mouseMoveEvent(QGraphicsSceneMouseEvent *e) { if (current_polygon == NULL) break; temp_polygon = current_polygon -> polygon(); temp_polygon.pop_back(); - temp_polygon << e -> scenePos(); + temp_polygon << event_pos; current_polygon -> setPolygon(temp_polygon); break; case Normal: default: - QGraphicsScene::mouseMoveEvent(e); + QList selected_items = selectedItems(); + if (!selected_items.isEmpty()) { + // mouvement de souris realise depuis le dernier press event + QPointF mouse_movement = e -> scenePos() - moving_press_pos; + + // application de ce mouvement a la fsi_pos enregistre dans le dernier press event + QPointF new_fsi_pos = fsi_pos + mouse_movement; + + // snap eventuel de la nouvelle fsi_pos + if (mustSnapToGrid(e)) snapToGrid(new_fsi_pos); + + // difference entre la fsi_pos finale et la fsi_pos courante = mouvement a appliquer + + QPointF current_fsi_pos = selected_items.first() -> scenePos(); + QPointF final_movement = new_fsi_pos - current_fsi_pos; + foreach(QGraphicsItem *qgi, selected_items) { + qgi -> moveBy(final_movement.x(), final_movement.y()); + } + } } } else if (behavior == Polygon && current_polygon != NULL) { temp_polygon = current_polygon -> polygon(); temp_polygon.pop_back(); - temp_polygon << e -> scenePos(); + temp_polygon << event_pos; current_polygon -> setPolygon(temp_polygon); } else QGraphicsScene::mouseMoveEvent(e); } @@ -178,27 +218,34 @@ void ElementScene::mouseMoveEvent(QGraphicsSceneMouseEvent *e) { @param e objet decrivant l'evenement */ void ElementScene::mousePressEvent(QGraphicsSceneMouseEvent *e) { + QPointF event_pos = e -> scenePos(); + if (mustSnapToGrid(e)) snapToGrid(event_pos); + if (behavior != Polygon && current_polygon != NULL) current_polygon = NULL; QPolygonF temp_polygon; if (e -> button() & Qt::LeftButton) { switch(behavior) { case Line: current_line = new PartLine(element_editor, 0, this); - current_line -> setLine(QLineF(e -> scenePos(), e -> scenePos())); + current_line -> setLine(QLineF(event_pos, event_pos)); + break; + case Rectangle: + current_rectangle = new PartRectangle(element_editor, 0, this); + current_rectangle -> setRect(QRectF(event_pos, QSizeF(0.0, 0.0))); break; case Ellipse: current_ellipse = new PartEllipse(element_editor, 0, this); - current_ellipse -> setRect(QRectF(e -> scenePos(), QSizeF(0.0, 0.0))); + current_ellipse -> setRect(QRectF(event_pos, QSizeF(0.0, 0.0))); current_ellipse -> setProperty("antialias", true); break; case Arc: current_arc = new PartArc(element_editor, 0, this); - current_arc -> setRect(QRectF(e -> scenePos(), QSizeF(0.0, 0.0))); + current_arc -> setRect(QRectF(event_pos, QSizeF(0.0, 0.0))); current_arc -> setProperty("antialias", true); break; case Circle: current_circle = new PartCircle(element_editor, 0, this); - current_circle -> setRect(QRectF(e -> scenePos(), QSizeF(0.0, 0.0))); + current_circle -> setRect(QRectF(event_pos, QSizeF(0.0, 0.0))); current_circle -> setProperty("antialias", true); break; case Polygon: @@ -207,14 +254,23 @@ void ElementScene::mousePressEvent(QGraphicsSceneMouseEvent *e) { temp_polygon = QPolygonF(0); } else temp_polygon = current_polygon -> polygon(); // au debut, on insere deux points - if (!temp_polygon.count()) temp_polygon << e -> scenePos(); - temp_polygon << e -> scenePos(); + if (!temp_polygon.count()) temp_polygon << event_pos; + temp_polygon << event_pos; current_polygon -> setPolygon(temp_polygon); break; case Normal: default: QGraphicsScene::mousePressEvent(e); - if (!selectedItems().isEmpty()) fsi_pos = selectedItems().first() -> scenePos(); + // gestion des deplacements de parties + if (!selectedItems().isEmpty()) { + fsi_pos = selectedItems().first() -> scenePos(); + moving_press_pos = e -> scenePos(); + moving_parts_ = true; + } else { + fsi_pos = QPoint(); + moving_press_pos = QPoint(); + moving_parts_ = false; + } } } else QGraphicsScene::mousePressEvent(e); } @@ -224,71 +280,97 @@ void ElementScene::mousePressEvent(QGraphicsSceneMouseEvent *e) { @param e objet decrivant l'evenement */ void ElementScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *e) { + QPointF event_pos = e -> scenePos(); + if (mustSnapToGrid(e)) snapToGrid(event_pos); + PartTerminal *terminal; PartText *text; PartTextField *textfield; if (behavior != Polygon && current_polygon != NULL) current_polygon = NULL; + + if (behavior == PasteArea) { + defined_paste_area_ = paste_area_ -> rect(); + removeItem(paste_area_); + emit(pasteAreaDefined(defined_paste_area_)); + behavior = Normal; + return; + } + if (e -> button() & Qt::LeftButton) { switch(behavior) { case Line: if (qgiManager().manages(current_line)) break; undo_stack.push(new AddPartCommand(tr("ligne"), this, current_line)); emit(partsAdded()); + endCurrentBehavior(e); + break; + case Rectangle: + if (qgiManager().manages(current_rectangle)) break; + current_rectangle -> setRect(current_rectangle -> rect().normalized()); + undo_stack.push(new AddPartCommand(tr("rectangle"), this, current_rectangle)); + emit(partsAdded()); + endCurrentBehavior(e); break; case Ellipse: if (qgiManager().manages(current_ellipse)) break; current_ellipse -> setRect(current_ellipse -> rect().normalized()); undo_stack.push(new AddPartCommand(tr("ellipse"), this, current_ellipse)); emit(partsAdded()); + endCurrentBehavior(e); break; case Arc: if (qgiManager().manages(current_arc)) break; current_arc-> setRect(current_arc -> rect().normalized()); undo_stack.push(new AddPartCommand(tr("arc"), this, current_arc)); emit(partsAdded()); + endCurrentBehavior(e); break; case Circle: if (qgiManager().manages(current_circle)) break; current_circle -> setRect(current_circle -> rect().normalized()); undo_stack.push(new AddPartCommand(tr("cercle"), this, current_circle)); emit(partsAdded()); + endCurrentBehavior(e); break; case Terminal: terminal = new PartTerminal(element_editor, 0, this); - terminal -> setPos(e -> scenePos()); + terminal -> setPos(event_pos); undo_stack.push(new AddPartCommand(tr("borne"), this, terminal)); emit(partsAdded()); + endCurrentBehavior(e); break; case Text: text = new PartText(element_editor, 0, this); - text -> setPos(e -> scenePos()); + text -> setPos(event_pos); undo_stack.push(new AddPartCommand(tr("texte"), this, text)); emit(partsAdded()); + endCurrentBehavior(e); break; case TextField: textfield = new PartTextField(element_editor, 0, this); - textfield -> setPos(e -> scenePos()); + textfield -> setPos(event_pos); undo_stack.push(new AddPartCommand(tr("champ de texte"), this, textfield)); emit(partsAdded()); + endCurrentBehavior(e); break; case Normal: default: - QGraphicsScene::mouseReleaseEvent(e); // detecte les deplacements de parties - if (!selectedItems().isEmpty()) { + if (!selectedItems().isEmpty() && moving_parts_) { QPointF movement = selectedItems().first() -> scenePos() - fsi_pos; if (!movement.isNull()) { undo_stack.push(new MovePartsCommand(movement, this, selectedItems())); } } + QGraphicsScene::mouseReleaseEvent(e); + moving_parts_ = false; } } else if (e -> button() & Qt::RightButton) { if (behavior == Polygon && current_polygon != NULL) { - behavior = Normal; undo_stack.push(new AddPartCommand(tr("polygone"), this, current_polygon)); current_polygon = NULL; emit(partsAdded()); - emit(needNormalMode()); + endCurrentBehavior(e); } else QGraphicsScene::mouseReleaseEvent(e); } else QGraphicsScene::mouseReleaseEvent(e); } @@ -317,6 +399,10 @@ void ElementScene::drawBackground(QPainter *p, const QRectF &r) { p -> setBrush(Qt::NoBrush); p -> drawRect(drawable_area); + // on dessine un point de la grille sur 10 + int drawn_x_grid = x_grid * 10; + int drawn_y_grid = y_grid * 10; + if (r.width() < 2500 && r.height() < 2500) { // dessine les points de la grille p -> setPen(Qt::black); @@ -325,12 +411,12 @@ void ElementScene::drawBackground(QPainter *p, const QRectF &r) { qreal limite_y = r.y() + r.height(); int g_x = (int)ceil(r.x()); - while (g_x % xGrid) ++ g_x; + while (g_x % drawn_x_grid) ++ g_x; int g_y = (int)ceil(r.y()); - while (g_y % yGrid) ++ g_y; + while (g_y % drawn_y_grid) ++ g_y; - for (int gx = g_x ; gx < limite_x ; gx += xGrid) { - for (int gy = g_y ; gy < limite_y ; gy += yGrid) { + for (int gx = g_x ; gx < limite_x ; gx += drawn_x_grid) { + for (int gy = g_y ; gy < limite_y ; gy += drawn_y_grid) { p -> drawPoint(gx, gy); } } @@ -358,11 +444,50 @@ void ElementScene::drawForeground(QPainter *p, const QRectF &) { p -> restore(); } +/** + A partir d'un evenement souris, cette methode regarde si la touche shift est + enfoncee ou non. Si oui, elle laisse le comportement en cours (cercle, + texte, polygone, ...). Si non, elle repasse en mode normal / selection. + @param e objet decrivant l'evenement souris +*/ +void ElementScene::endCurrentBehavior(const QGraphicsSceneMouseEvent *event) { + if (!(event -> modifiers() & Qt::ShiftModifier)) { + // la touche Shift n'est pas enfoncee : on demande le mode normal + behavior = Normal; + emit(needNormalMode()); + } +} + +/** + @return la taille horizontale de la grille +*/ +int ElementScene::xGrid() const { + return(x_grid); +} + +/** + @return la taille verticale de la grille +*/ +int ElementScene::yGrid() const { + return(y_grid); +} + +/** + @param x_grid Taille horizontale de la grille + @param y_grid Taille verticale de la grille +*/ +void ElementScene::setGrid(int x_g, int y_g) { + x_grid = x_g ? x_g : 1; + y_grid = y_g ? y_g : 1; +} + /** Exporte l'element en XML + @param diagram Booleen (a vrai par defaut) indiquant si le XML genere doit + representer tout l'element ou seulement les elements selectionnes @return un document XML decrivant l'element */ -const QDomDocument ElementScene::toXml() const { +const QDomDocument ElementScene::toXml(bool all_parts) const { // document XML QDomDocument xml_document; @@ -383,6 +508,8 @@ const QDomDocument ElementScene::toXml() const { QDomElement description = xml_document.createElement("description"); // description de l'element foreach(QGraphicsItem *qgi, zItems(true)) { + // si l'export ne concerne que la selection, on ignore les parties non selectionnees + if (!all_parts && !qgi -> isSelected()) continue; if (CustomElementPart *ce = dynamic_cast(qgi)) { if (ce -> isUseless()) continue; description.appendChild(ce -> toXml(xml_document)); @@ -395,91 +522,90 @@ const QDomDocument ElementScene::toXml() const { } /** - Lit un element depuis un document XML - @param xml_document un document XML decrivant l'element + @param xml_document un document XML decrivant un element + @return le boundingRect du contenu de l'element */ -void ElementScene::fromXml(const QDomDocument &xml_document) { +QRectF ElementScene::boundingRectFromXml(const QDomDocument &xml_document) { + // charge les parties depuis le document XML + ElementContent loaded_content = loadContent(xml_document); + if (loaded_content.isEmpty()) return(QRectF()); + // calcule le boundingRect + QRectF bounding_rect = elementContentBoundingRect(loaded_content); + + // detruit les parties chargees + qDeleteAll(loaded_content); + + return(bounding_rect); +} + +/** + Importe l'element decrit dans un document XML. Si une position est + precisee, les elements importes sont positionnes de maniere a ce que le + coin superieur gauche du plus petit rectangle pouvant les entourant tous + (le bounding rect) soit a cette position. + @param xml_document un document XML decrivant l'element + @param position La position des parties importees + @param consider_informations Si vrai, les informations complementaires + (dimensions, hotspot, etc.) seront prises en compte + @param content_ptr si ce pointeur vers un ElementContent est different de 0, + il sera rempli avec le contenu ajoute a l'element par le fromXml + @return true si l'import a reussi, false sinon +*/ +void ElementScene::fromXml( + const QDomDocument &xml_document, + const QPointF &position, + bool consider_informations, + ElementContent *content_ptr +) { QString error_message; bool state = true; - // la racine est supposee etre une definition d'element - QDomElement root = xml_document.documentElement(); - if (root.tagName() != "definition" || root.attribute("type") != "element") { - state = false; - error_message = tr("Ce document XML n'est pas une definition d'\351l\351ment."); - } - - // dimensions et hotspot - if (state) { - // ces attributs doivent etre presents et valides - int w, h, hot_x, hot_y; - if ( - !QET::attributeIsAnInteger(root, QString("width"), &w) ||\ - !QET::attributeIsAnInteger(root, QString("height"), &h) ||\ - !QET::attributeIsAnInteger(root, QString("hotspot_x"), &hot_x) ||\ - !QET::attributeIsAnInteger(root, QString("hotspot_y"), &hot_y) - ) { - state = false; - error_message = tr("Les dimensions ou le point de saisie ne sont pas valides."); - } else { - setWidth(w); - setHeight(h); - setHotspot(QPoint(hot_x, hot_y)); - } - } - - // orientations et connexions internes - if (state) { - internal_connections = (root.attribute("ic") == "true"); - - if (!ori.fromString(root.attribute("orientation"))) { - state = false; - error_message = tr("Les orientations ne sont pas valides."); - } - } - - // extrait les noms de la definition XML - if (state) { - _names.fromXml(root); + // prend en compte les informations de l'element + if (consider_informations) { + state = applyInformations(xml_document, &error_message); } // parcours des enfants de la definition : parties de l'element if (state) { - for (QDomNode node = root.firstChild() ; !node.isNull() ; node = node.nextSibling()) { - QDomElement elmts = node.toElement(); - if (elmts.isNull()) continue; - if (elmts.tagName() == "description") { - // gestion de la description graphique de l'element - // = parcours des differentes parties du dessin - int z = 1; - for (QDomNode n = node.firstChild() ; !n.isNull() ; n = n.nextSibling()) { - QDomElement qde = n.toElement(); - if (qde.isNull()) continue; - CustomElementPart *cep; - if (qde.tagName() == "line") cep = new PartLine (element_editor, 0, this); - else if (qde.tagName() == "ellipse") cep = new PartEllipse (element_editor, 0, this); - else if (qde.tagName() == "circle") cep = new PartCircle (element_editor, 0, this); - else if (qde.tagName() == "polygon") cep = new PartPolygon (element_editor, 0, this); - else if (qde.tagName() == "terminal") cep = new PartTerminal (element_editor, 0, this); - else if (qde.tagName() == "text") cep = new PartText (element_editor, 0, this); - else if (qde.tagName() == "input") cep = new PartTextField(element_editor, 0, this); - else if (qde.tagName() == "arc") cep = new PartArc (element_editor, 0, this); - else continue; - if (QGraphicsItem *qgi = dynamic_cast(cep)) qgi -> setZValue(z++); - cep -> fromXml(qde); - } - } + ElementContent loaded_content = loadContent(xml_document, &error_message); + if (position != QPointF()) { + addContentAtPos(loaded_content, position, &error_message); + } else { + addContent(loaded_content, &error_message); + } + + // renvoie le contenu ajoute a l'element + if (content_ptr) { + *content_ptr = loaded_content; } } } +/** + @return le rectangle representant les limites de l'element. + Ce rectangle a pour dimensions la taille de l'element et pour coin + superieur gauche les coordonnees opposees du hotspot. +*/ +QRectF ElementScene::borderRect() const { + return(QRectF(-_hotspot, QSizeF(width(), height()))); +} + /** @return un rectangle englobant toutes les parties ainsi que le "bounding rect" de l'element */ QRectF ElementScene::sceneContent() const { - return(itemsBoundingRect().unite(QRectF(-_hotspot, QSizeF(width(), height())))); + return(itemsBoundingRect().unite(borderRect())); +} + +/** + @return true si toutes les parties graphiques composant l'element sont + integralement contenues dans le rectangle representant les limites de + l'element. +*/ +bool ElementScene::borderContainsEveryParts() const { + return(borderRect().contains(itemsBoundingRect())); } /** @@ -496,6 +622,64 @@ QGIManager &ElementScene::qgiManager() { return(qgi_manager); } +/** + @return true si le presse-papier semble contenir un element +*/ +bool ElementScene::clipboardMayContainElement() { + QString clipboard_text = QApplication::clipboard() -> text().trimmed(); + bool may_be_element = clipboard_text.startsWith(""); + return(may_be_element); +} + +/** + @param clipboard_content chaine de caractere, provenant vraisemblablement du + presse-papier. + @return true si clipboard_content a ete copie depuis cet element. +*/ +bool ElementScene::wasCopiedFromThisElement(const QString &clipboard_content) { + return(clipboard_content == last_copied_); +} + +/** + Gere le fait de couper la selection = l'exporter en XML dans le + presse-papier puis la supprimer. +*/ +void ElementScene::cut() { + copy(); + QList cut_content = selectedItems(); + clearSelection(); + undoStack().push(new CutPartsCommand(this, cut_content)); +} + +/** + Gere le fait de copier la selection = l'exporter en XML dans le + presse-papier. +*/ +void ElementScene::copy() { + // accede au presse-papier + QClipboard *clipboard = QApplication::clipboard(); + + // genere la description XML de la selection + QString clipboard_content = toXml(false).toString(4); + + // met la description XML dans le presse-papier + if (clipboard -> supportsSelection()) { + clipboard -> setText(clipboard_content, QClipboard::Selection); + } + clipboard -> setText(clipboard_content); + + // retient le dernier contenu copie + last_copied_ = clipboard_content; +} + +/** + Gere le fait de coller le contenu du presse-papier = l'importer dans le + presse-papier a une position donnee. +*/ +void ElementScene::paste() { + +} + /** Selectionne tout */ @@ -538,7 +722,7 @@ void ElementScene::slot_editSizeHotSpot() { // cree un dialogue QDialog dialog_sh(element_editor); dialog_sh.setModal(true); - dialog_sh.setWindowTitle(tr("\311diter la taille et le point de saisie")); + dialog_sh.setWindowTitle(tr("\311diter la taille et le point de saisie", "window title")); QVBoxLayout *dialog_layout = new QVBoxLayout(&dialog_sh); // ajoute un HotspotEditor au dialogue @@ -578,7 +762,7 @@ void ElementScene::slot_editOrientations() { QDialog dialog_ori(element_editor); dialog_ori.setModal(true); dialog_ori.setMinimumSize(400, 260); - dialog_ori.setWindowTitle(tr("\311diter les orientations")); + dialog_ori.setWindowTitle(tr("\311diter les orientations", "window title")); QVBoxLayout *dialog_layout = new QVBoxLayout(&dialog_ori); // ajoute un champ explicatif au dialogue @@ -624,7 +808,7 @@ void ElementScene::slot_editNames() { QDialog dialog(element_editor); dialog.setModal(true); dialog.setMinimumSize(400, 330); - dialog.setWindowTitle(tr("\311diter les noms")); + dialog.setWindowTitle(tr("\311diter les noms", "window title")); QVBoxLayout *dialog_layout = new QVBoxLayout(&dialog); // ajoute un champ explicatif au dialogue @@ -716,6 +900,30 @@ QList ElementScene::zItems(bool include_terminals) const { return(all_items_list); } +/** + @return les parties graphiques selectionnees +*/ +ElementContent ElementScene::selectedContent() const { + ElementContent content; + foreach(QGraphicsItem *qgi, zItems(true)) { + if (qgi -> isSelected()) content << qgi; + } + return(content); +} + +/** + @param to_paste Rectangle englobant les parties a coller + @return le rectangle ou il faudra coller ces parties +*/ +void ElementScene::getPasteArea(const QRectF &to_paste) { + // on le dessine sur la scene + paste_area_ -> setRect(to_paste); + addItem(paste_area_); + + // on passe la scene en mode "recherche de zone pour copier/coller" + behavior = PasteArea; +} + /** Supprime les parties de l'element et les objets d'annulations. Les autres caracteristiques sont conservees. @@ -729,3 +937,200 @@ void ElementScene::reset() { qgiManager().release(qgi); } } + +/** + @param content Contenu ( = parties) d'un element + @return le boundingRect de ces parties, exprime dans les coordonnes de la + scene +*/ +QRectF ElementScene::elementContentBoundingRect(const ElementContent &content) { + QRectF bounding_rect; + foreach(QGraphicsItem *qgi, content) { + bounding_rect |= qgi -> sceneBoundingRect(); + } + return(bounding_rect); +} + +/** + Applique les informations (dimensions, hostpot, orientations, connexions + internes et noms) contenu dans un document XML. + @param xml_document Document XML a analyser + @param error_message pointeur vers une QString ; si error_message est + different de 0, un message d'erreur sera stocke dedans si necessaire + @return true si la lecture et l'application des informations s'est bien + passee, false sinon. +*/ +bool ElementScene::applyInformations(const QDomDocument &xml_document, QString *error_message) { + // la racine est supposee etre une definition d'element + QDomElement root = xml_document.documentElement(); + if (root.tagName() != "definition" || root.attribute("type") != "element") { + if (error_message) { + *error_message = tr("Ce document XML n'est pas une d\351finition d'\351l\351ment.", "error message"); + } + return(false); + } + + // dimensions et hotspot : ces attributs doivent etre presents et valides + int w, h, hot_x, hot_y; + if ( + !QET::attributeIsAnInteger(root, QString("width"), &w) ||\ + !QET::attributeIsAnInteger(root, QString("height"), &h) ||\ + !QET::attributeIsAnInteger(root, QString("hotspot_x"), &hot_x) ||\ + !QET::attributeIsAnInteger(root, QString("hotspot_y"), &hot_y) + ) { + if (error_message) { + *error_message = tr("Les dimensions ou le point de saisie ne sont pas valides.", "error message"); + } + return(false); + } + // + setWidth(w); + setHeight(h); + setHotspot(QPoint(hot_x, hot_y)); + + // orientations + internal_connections = (root.attribute("ic") == "true"); + + // connexions internes + if (!ori.fromString(root.attribute("orientation"))) { + if (error_message) { + *error_message = tr("Les orientations ne sont pas valides.", "error message"); + } + return(false); + } + + // extrait les noms de la definition XML + _names.fromXml(root); + + return(true); +} + +/** + Par le document XML xml_document et retourne le contenu ( = liste de + parties) correspondant. + @param xml_document Document XML a analyser + @param error_message pointeur vers une QString ; si error_message est + different de 0, un message d'erreur sera stocke dedans si necessaire +*/ +ElementContent ElementScene::loadContent(const QDomDocument &xml_document, QString *error_message) { + ElementContent loaded_parts; + + // la racine est supposee etre une definition d'element + QDomElement root = xml_document.documentElement(); + if (root.tagName() != "definition" || root.attribute("type") != "element") { + if (error_message) { + *error_message = tr("Ce document XML n'est pas une d\351finition d'\351l\351ment.", "error message"); + } + return(loaded_parts); + } + + // chargement de la description graphique de l'element + for (QDomNode node = root.firstChild() ; !node.isNull() ; node = node.nextSibling()) { + QDomElement elmts = node.toElement(); + if (elmts.isNull()) continue; + if (elmts.tagName() == "description") { + + // = parcours des differentes parties du dessin + int z = 1; + for (QDomNode n = node.firstChild() ; !n.isNull() ; n = n.nextSibling()) { + QDomElement qde = n.toElement(); + if (qde.isNull()) continue; + CustomElementPart *cep; + if (qde.tagName() == "line") cep = new PartLine (element_editor, 0, 0); + else if (qde.tagName() == "rect") cep = new PartRectangle(element_editor, 0, 0); + else if (qde.tagName() == "ellipse") cep = new PartEllipse (element_editor, 0, 0); + else if (qde.tagName() == "circle") cep = new PartCircle (element_editor, 0, 0); + else if (qde.tagName() == "polygon") cep = new PartPolygon (element_editor, 0, 0); + else if (qde.tagName() == "terminal") cep = new PartTerminal (element_editor, 0, 0); + else if (qde.tagName() == "text") cep = new PartText (element_editor, 0, 0); + else if (qde.tagName() == "input") cep = new PartTextField(element_editor, 0, 0); + else if (qde.tagName() == "arc") cep = new PartArc (element_editor, 0, 0); + else continue; + if (QGraphicsItem *qgi = dynamic_cast(cep)) { + qgi -> setZValue(z++); + loaded_parts << qgi; + } + cep -> fromXml(qde); + } + } + } + + return(loaded_parts); +} + +/** + Ajoute le contenu content a cet element + @param content contenu ( = liste de parties) a charger + @param error_message pointeur vers une QString ; si error_message est + different de 0, un message d'erreur sera stocke dedans si necessaire + @return Le contenu ajoute +*/ +ElementContent ElementScene::addContent(const ElementContent &content, QString */*error_message*/) { + foreach(QGraphicsItem *part, content) { + addItem(part); + } + return(content); +} + +/** + Ajoute le contenu content a cet element + @param content contenu ( = liste de parties) a charger + @param pos Position du coin superieur gauche du contenu apres avoir ete ajoute + @param error_message pointeur vers une QString ; si error_message est + different de 0, un message d'erreur sera stocke dedans si necessaire + @return Le contenu ajoute +*/ +ElementContent ElementScene::addContentAtPos(const ElementContent &content, const QPointF &pos, QString */*error_message*/) { + // calcule le boundingRect du contenu a ajouter + QRectF bounding_rect = elementContentBoundingRect(content); + + // en deduit le decalage a appliquer aux parties pour les poser au point demander + QPointF offset = pos - bounding_rect.topLeft(); + + // ajoute les parties avec le decalage adequat + foreach(QGraphicsItem *part, content) { + part -> setPos(part -> pos() + offset); + addItem(part); + } + return(content); +} + +/** + Initialise la zone de collage +*/ +void ElementScene::initPasteArea() { + paste_area_ = new QGraphicsRectItem(); + paste_area_ -> setZValue(1000000); + + QPen paste_area_pen; + paste_area_pen.setStyle(Qt::DashDotLine); + paste_area_pen.setColor(QColor(30, 56, 86, 255)); + + QBrush paste_area_brush; + paste_area_brush.setStyle(Qt::SolidPattern); + paste_area_brush.setColor(QColor(90, 167, 255, 64)); + + paste_area_ -> setPen(paste_area_pen); + paste_area_ -> setBrush(paste_area_brush); +} + +/** + Arrondit les coordonnees du point passees en parametre de facon a ce que ce + point soit aligne sur la grille. + @param point une reference vers un QPointF. Cet objet sera modifie. + +*/ +void ElementScene::snapToGrid(QPointF &point) { + point.rx() = qRound(point.x() / x_grid) * x_grid; + point.ry() = qRound(point.y() / y_grid) * y_grid; +} + +/** + @param e Evenement souris + @return true s'il faut utiliser le snap-to-grid + Typiquement, cette methode retourne true si l'evenement souris se produit + sans la touche Ctrl enfoncee. +*/ +bool ElementScene::mustSnapToGrid(QGraphicsSceneMouseEvent *e) { + return(!(e -> modifiers() & Qt::ControlModifier)); +} diff --git a/sources/editor/elementscene.h b/sources/editor/elementscene.h index 367c8f10b..3703910f4 100644 --- a/sources/editor/elementscene.h +++ b/sources/editor/elementscene.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -22,8 +22,10 @@ #include "nameslistwidget.h" #include "orientationsetwidget.h" #include "qgimanager.h" +#include "elementcontent.h" class QETElementEditor; class PartLine; +class PartRectangle; class PartEllipse; class PartCircle; class PartPolygon; @@ -38,7 +40,7 @@ class ElementScene : public QGraphicsScene { Q_OBJECT // enum - enum Behavior { Normal, Line, Circle, Ellipse, Polygon, Text, Terminal, Arc, TextField }; + enum Behavior { Normal, Line, Rectangle, Circle, Ellipse, Polygon, Text, Terminal, Arc, TextField, PasteArea }; // constructeurs, destructeur public: @@ -49,10 +51,6 @@ class ElementScene : public QGraphicsScene { ElementScene(const ElementScene &); // attributs - public: - static const int xGrid; ///< Taille horizontale de la grille - static const int yGrid; ///< Taille verticale de la grille - private: /// longueur de l'element en dizaines de pixels uint _width; @@ -72,16 +70,31 @@ class ElementScene : public QGraphicsScene { QUndoStack undo_stack; /// Position du premier item selectionne (utilise pour annuler les deplacements) QPointF fsi_pos; + QPointF moving_press_pos; + bool moving_parts_; /// Variables relatives a la gestion du dessin des parties sur la scene Behavior behavior; PartLine *current_line; + PartRectangle *current_rectangle; PartEllipse *current_ellipse; PartCircle *current_circle; PartPolygon *current_polygon; PartArc *current_arc; QETElementEditor *element_editor; + /// Variables relatives a la gestion de la zone de collage sur la scene + QGraphicsRectItem *paste_area_; + QRectF defined_paste_area_; + + /// Variables relatives au copier-coller avec decalage + QString last_copied_; + + ///< Taille horizontale de la grille + int x_grid; + ///< Taille verticale de la grille + int y_grid; + // methodes public: void setWidth(const uint &); @@ -96,13 +109,26 @@ class ElementScene : public QGraphicsScene { void setOrientations(const OrientationSet &); bool internalConnections(); void setInternalConnections(bool); - virtual const QDomDocument toXml() const; - virtual void fromXml(const QDomDocument &); + virtual int xGrid() const; + virtual int yGrid() const; + virtual void setGrid(int, int); + virtual const QDomDocument toXml(bool = true) const; + virtual QRectF boundingRectFromXml(const QDomDocument &); + virtual void fromXml(const QDomDocument &, const QPointF & = QPointF(), bool = true, ElementContent * = 0); virtual void reset(); virtual QList zItems(bool = false) const; + virtual ElementContent selectedContent() const; + virtual void getPasteArea(const QRectF &); + QRectF borderRect() const; QRectF sceneContent() const; + bool borderContainsEveryParts() const; QUndoStack &undoStack(); QGIManager &qgiManager(); + static bool clipboardMayContainElement(); + bool wasCopiedFromThisElement(const QString &); + void cut(); + void copy(); + void paste(); protected: virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *); @@ -110,10 +136,22 @@ class ElementScene : public QGraphicsScene { virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *); virtual void drawBackground(QPainter *, const QRectF &); virtual void drawForeground(QPainter *, const QRectF &); + virtual void endCurrentBehavior(const QGraphicsSceneMouseEvent *); + + private: + QRectF elementContentBoundingRect(const ElementContent &); + bool applyInformations(const QDomDocument &, QString * = 0); + ElementContent loadContent(const QDomDocument &, QString * = 0); + ElementContent addContent(const ElementContent &, QString * = 0); + ElementContent addContentAtPos(const ElementContent &, const QPointF &, QString * = 0); + void initPasteArea(); + void snapToGrid(QPointF &); + bool mustSnapToGrid(QGraphicsSceneMouseEvent *); public slots: void slot_move(); void slot_addLine(); + void slot_addRectangle(); void slot_addCircle(); void slot_addEllipse(); void slot_addPolygon(); @@ -145,6 +183,8 @@ class ElementScene : public QGraphicsScene { void partsRemoved(); /// Signal emis lorsque la zValue d'une ou plusieurs parties change void partsZValueChanged(); + /// Signal emis lorsque l'utilisateur a fini de choisir une zone pour un copier/coller + void pasteAreaDefined(const QRectF &); }; /** diff --git a/sources/editor/elementview.cpp b/sources/editor/elementview.cpp index 4fe3fcf37..be726e602 100644 --- a/sources/editor/elementview.cpp +++ b/sources/editor/elementview.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -16,6 +16,8 @@ along with QElectroTech. If not, see . */ #include "elementview.h" +#include "qetelementeditor.h" +#include "editorcommands.h" /** Constructeur @param scene ElementScene visualisee par cette ElementView @@ -23,12 +25,14 @@ */ ElementView::ElementView(ElementScene *scene, QWidget *parent) : QGraphicsView(scene, parent), - scene_(scene) + scene_(scene), + offset_paste_count_(0) { setInteractive(true); setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); setResizeAnchor(QGraphicsView::AnchorUnderMouse); zoomReset(); + connect(scene_, SIGNAL(pasteAreaDefined(const QRectF &)), this, SLOT(pasteAreaDefined(const QRectF &))); } /// Destructeur @@ -40,6 +44,24 @@ ElementScene *ElementView::scene() const { return(scene_); } +/** + @return le rectangle de l'element visualise par cet ElementView +*/ +QRectF ElementView::viewedSceneRect() const { + // recupere la taille du widget viewport + QSize viewport_size = viewport() -> size(); + + // recupere la transformation viewport -> scene + QTransform view_to_scene = viewportTransform().inverted(); + + // mappe le coin superieur gauche et le coin inferieur droit de la viewport sur la scene + QPointF scene_left_top = view_to_scene.map(QPointF(0.0, 0.0)); + QPointF scene_right_bottom = view_to_scene.map(QPointF(viewport_size.width(), viewport_size.height())); + + // en deduit le rectangle visualise par la scene + return(QRectF(scene_left_top, scene_right_bottom)); +} + /** Definit l'ElementScene visualisee par cette ElementView @param s l'ElementScene visualisee par cette ElementView @@ -110,6 +132,196 @@ void ElementView::adjustSceneRect() { scene_ -> update(old_scene_rect.united(new_scene_rect)); } +/** + Gere le fait de couper la selection = l'exporter en XML dans le + presse-papier puis la supprimer. +*/ +void ElementView::cut() { + // delegue cette action a la scene + scene_ -> cut(); +} + +/** + Gere le fait de copier la selection = l'exporter en XML dans le + presse-papier. +*/ +void ElementView::copy() { + // delegue cette action a la scene + scene_ -> copy(); + offset_paste_count_ = 0; +} + +/** + Gere le fait de coller le contenu du presse-papier = l'importer dans + l'element. Cette methode examine le contenu du presse-papier. Si celui-ci + semble avoir ete copie depuis cet element, il est colle a cote de sa zone + d'origine ; s'il est recolle, il sera colle un cran a cote de la zone deja + recollee, etc. + Sinon, cette methode demande a l'utilisateur de definir la zone ou le + collage devra s'effectuer. + @see pasteAreaDefined(const QRectF &) +*/ +void ElementView::paste() { + QString clipboard_text = QApplication::clipboard() -> text(); + if (clipboard_text.isEmpty()) return; + + QDomDocument document_xml; + if (!document_xml.setContent(clipboard_text)) return; + + if (scene_ -> wasCopiedFromThisElement(clipboard_text)) { + // copier/coller avec decalage + pasteWithOffset(document_xml); + } else { + // copier/coller par choix de la zone de collage + QRectF pasted_content_bounding_rect = scene_ -> boundingRectFromXml(document_xml); + if (pasted_content_bounding_rect.isEmpty()) return; + + to_paste_in_area_ = clipboard_text; + getPasteArea(pasted_content_bounding_rect); + } +} + +/** + Colle le contenu du presse-papier en demandant systematiquement a + l'utilisateur de choisir une zone de collage +*/ +void ElementView::pasteInArea() { + QString clipboard_text = QApplication::clipboard() -> text(); + if (clipboard_text.isEmpty()) return; + + QDomDocument document_xml; + if (!document_xml.setContent(clipboard_text)) return; + + QRectF pasted_content_bounding_rect = scene_ -> boundingRectFromXml(document_xml); + if (pasted_content_bounding_rect.isEmpty()) return; + + // copier/coller par choix de la zone de collage + to_paste_in_area_ = clipboard_text; + getPasteArea(pasted_content_bounding_rect); +} + +/** + Gere le fait de coller le contenu du presse-papier = l'importer dans + l'element. Cette methode examine le contenu du presse-papier. Si celui-ci + est exploitable, elle le colle a la position passee en parametre. + @see pasteAreaDefined(const QRectF &) + @param position Point de collage +*/ +ElementContent ElementView::paste(const QPointF &position) { + QString clipboard_text = QApplication::clipboard() -> text(); + if (clipboard_text.isEmpty()) return(ElementContent()); + + QDomDocument document_xml; + if (!document_xml.setContent(clipboard_text)) return(ElementContent()); + + // objet pour recuperer le contenu ajoute au schema par le coller + return(paste(document_xml, position)); +} + +/** + @param to_paste Rectangle englobant les parties a coller +*/ +void ElementView::getPasteArea(const QRectF &to_paste) { + // on copie le rectangle fourni - on s'interesse a ses dimensions, pas a sa position + QRectF used_rect(to_paste); + + // on lui attribue pour centre l'origine du repere + if (underMouse()) { + used_rect.moveCenter(mapToScene(mapFromGlobal(QCursor::pos()))); + } else { + used_rect.moveCenter(QPointF(0.0, 0.0)); + } + scene_ -> getPasteArea(used_rect); +} + +/** + Slot appele lorsque la scene annonce avoir defini une zone de collage + @param target_rect Rectangle cible pour le collage +*/ +ElementContent ElementView::pasteAreaDefined(const QRectF &target_rect) { + if (to_paste_in_area_.isEmpty()) return(ElementContent()); + + QDomDocument xml_document; + if (!xml_document.setContent(to_paste_in_area_)) { + to_paste_in_area_.clear(); + return(ElementContent()); + } else { + return(paste(xml_document, target_rect.topLeft())); + } +} + +/** + Colle le document XML xml_document a la position pos + @param xml_document Document XML a coller + @param pos Coin superieur gauche du rectangle cible +*/ +ElementContent ElementView::paste(const QDomDocument &xml_document, const QPointF &pos) { + // objet pour recuperer le contenu ajoute au schema par le coller + ElementContent content_pasted; + scene_ -> fromXml(xml_document, pos, false, &content_pasted); + + // si quelque chose a effectivement ete ajoute au schema, on cree un objet d'annulation + if (content_pasted.count()) { + scene_ -> clearSelection(); + PastePartsCommand *undo_object = new PastePartsCommand(this, content_pasted); + scene_ -> undoStack().push(undo_object); + } + return(content_pasted); +} + +/** + Colle le document XML xml_document a la position pos + @param xml_document Document XML a coller +*/ +ElementContent ElementView::pasteWithOffset(const QDomDocument &xml_document) { + // objet pour recuperer le contenu ajoute au schema par le coller + ElementContent content_pasted; + + // rectangle source + QRectF pasted_content_bounding_rect = scene_ -> boundingRectFromXml(xml_document); + if (pasted_content_bounding_rect.isEmpty()) return(content_pasted); + + // copier/coller avec decalage + ++ offset_paste_count_; + if (offset_paste_count_ == 1) { + start_top_left_corner_ = pasted_content_bounding_rect.topLeft(); + } else { + pasted_content_bounding_rect.moveTopLeft(start_top_left_corner_); + } + + // on applique le decalage qui convient + QRectF final_pasted_content_bounding_rect = applyMovement( + pasted_content_bounding_rect, + QETElementEditor::pasteMovement(), + QETElementEditor::pasteOffset() + ); + + QPointF old_start_top_left_corner_ = start_top_left_corner_; + start_top_left_corner_ = final_pasted_content_bounding_rect.topLeft(); + scene_ -> fromXml(xml_document, start_top_left_corner_, false, &content_pasted); + + // si quelque chose a effectivement ete ajoute au schema, on cree un objet d'annulation + if (content_pasted.count()) { + scene_ -> clearSelection(); + PastePartsCommand *undo_object = new PastePartsCommand(this, content_pasted); + undo_object -> setOffset(offset_paste_count_ - 1, old_start_top_left_corner_, offset_paste_count_, start_top_left_corner_); + scene_ -> undoStack().push(undo_object); + } + return(content_pasted); +} + +/** + Gere les clics sur la vue - permet de coller lorsaue l'on enfonce le bouton + du milieu de la souris. + @param e QMouseEvent decrivant l'evenement souris +*/ +void ElementView::mousePressEvent(QMouseEvent *e) { + if (e -> buttons() & Qt::MidButton) { + paste(mapToScene(e -> pos())); + } + QGraphicsView::mousePressEvent(e); +} + /** Gere les actions liees a la rollette de la souris @param e QWheelEvent decrivant l'evenement rollette @@ -126,3 +338,107 @@ void ElementView::wheelEvent(QWheelEvent *e) { QAbstractScrollArea::wheelEvent(e); } } + +/** + Dessine l'arriere-plan de l'editeur, cad la grille. + @param p Le QPainter a utiliser pour dessiner + @param r Le rectangle de la zone a dessiner +*/ +void ElementView::drawBackground(QPainter *p, const QRectF &r) { + p -> save(); + + // desactive tout antialiasing, sauf pour le texte + p -> setRenderHint(QPainter::Antialiasing, false); + p -> setRenderHint(QPainter::TextAntialiasing, true); + p -> setRenderHint(QPainter::SmoothPixmapTransform, false); + + // dessine un fond blanc + p -> setPen(Qt::NoPen); + p -> setBrush(Qt::white); + p -> drawRect(r); + + // encadre la zone dessinable de l'element + QRectF drawable_area(-scene_ -> hotspot().x(), -scene_ -> hotspot().y(), scene_ -> width(), scene_ -> height()); + p -> setPen(Qt::black); + p -> setBrush(Qt::NoBrush); + p -> drawRect(drawable_area); + + // determine le zoom en cours + qreal zoom_factor = matrix().m11(); + + // choisit la granularite de la grille en fonction du zoom en cours + int drawn_x_grid = scene_ -> xGrid(); + int drawn_y_grid = scene_ -> yGrid(); + bool draw_grid = true; + bool draw_cross = false; + if (zoom_factor < (4.0/3.0)) { + // pas de grille du tout + draw_grid = false; + } else if (zoom_factor < 4.0) { + // grille a 10 px + drawn_x_grid *= 10; + drawn_y_grid *= 10; + } else if (zoom_factor < 6.0) { + // grille a 2 px (avec croix) + drawn_x_grid *= 2; + drawn_y_grid *= 2; + draw_cross = true; + } else { + // grille a 1 px (avec croix) + draw_cross = true; + } + + if (draw_grid) { + // dessine les points de la grille + p -> setPen(Qt::black); + p -> setBrush(Qt::NoBrush); + qreal limite_x = r.x() + r.width(); + qreal limite_y = r.y() + r.height(); + + int g_x = (int)ceil(r.x()); + while (g_x % drawn_x_grid) ++ g_x; + int g_y = (int)ceil(r.y()); + while (g_y % drawn_y_grid) ++ g_y; + + for (int gx = g_x ; gx < limite_x ; gx += drawn_x_grid) { + for (int gy = g_y ; gy < limite_y ; gy += drawn_y_grid) { + if (draw_cross) { + if (!(gx % 10) && !(gy % 10)) { + p -> drawLine(QLineF(gx - 0.25, gy, gx + 0.25, gy)); + p -> drawLine(QLineF(gx, gy - 0.25, gx, gy + 0.25)); + } else { + p -> drawPoint(gx, gy); + } + } else { + p -> drawPoint(gx, gy); + } + } + } + } + p -> restore(); +} + +/** + Applique le decalage offset dans le sens movement au rectangle start + @param start rectangle a decaler + @param movement Orientation du decalage a appliquer + @param offset Decalage a appliquer +*/ +QRectF ElementView::applyMovement(const QRectF &start, const QET::OrientedMovement &movement, const QPointF &offset) { + // calcule le decalage a appliquer a partir de l'offset indique et du mouvement + QPointF final_offset; + if (movement == QET::ToNorthEast || movement == QET::ToEast || movement == QET::ToSouthEast) { + final_offset.rx() = start.width() + offset.x(); + } else if (movement == QET::ToNorthWest || movement == QET::ToWest || movement == QET::ToSouthWest) { + final_offset.rx() = -start.width() - offset.x(); + } + + if (movement == QET::ToNorthWest || movement == QET::ToNorth || movement == QET::ToNorthEast) { + final_offset.ry() = -start.height() - offset.y(); + } else if (movement == QET::ToSouthWest || movement == QET::ToSouth || movement == QET::ToSouthEast) { + final_offset.ry() = start.height() + offset.y(); + } + + // applique le decalage ainsi calcule + return(start.translated(final_offset)); +} diff --git a/sources/editor/elementview.h b/sources/editor/elementview.h index 62264c8e9..c351a460f 100644 --- a/sources/editor/elementview.h +++ b/sources/editor/elementview.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -25,6 +25,8 @@ */ class ElementView : public QGraphicsView { Q_OBJECT + friend class PastePartsCommand; + // constructeurs, destructeur public: ElementView(ElementScene *, QWidget * = 0); @@ -37,9 +39,16 @@ class ElementView : public QGraphicsView { public: ElementScene *scene() const; void setScene(ElementScene *); + QRectF viewedSceneRect() const; + protected: bool event(QEvent *); + void mousePressEvent(QMouseEvent *); void wheelEvent(QWheelEvent *); + virtual void drawBackground(QPainter *, const QRectF &); + + private: + QRectF applyMovement(const QRectF &, const QET::OrientedMovement &, const QPointF &); // slots public slots: @@ -48,9 +57,23 @@ class ElementView : public QGraphicsView { void zoomFit(); void zoomReset(); void adjustSceneRect(); + void cut(); + void copy(); + void paste(); + void pasteInArea(); + + private slots: + void getPasteArea(const QRectF &); + ElementContent pasteAreaDefined(const QRectF &); + ElementContent paste(const QPointF &); + ElementContent paste(const QDomDocument &, const QPointF &); + ElementContent pasteWithOffset(const QDomDocument &); //attributs private: ElementScene *scene_; + QString to_paste_in_area_; + int offset_paste_count_; + QPointF start_top_left_corner_; }; #endif diff --git a/sources/editor/ellipseeditor.cpp b/sources/editor/ellipseeditor.cpp index 288e82cf5..a571e00ce 100644 --- a/sources/editor/ellipseeditor.cpp +++ b/sources/editor/ellipseeditor.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -63,9 +63,9 @@ EllipseEditor::~EllipseEditor() { */ void EllipseEditor::updateEllipse() { part -> setProperty("x", x -> text().toDouble()); - part -> setProperty("y", x -> text().toDouble()); - part -> setProperty("diameter_h", x -> text().toDouble()); - part -> setProperty("diameter_v", x -> text().toDouble()); + part -> setProperty("y", y -> text().toDouble()); + part -> setProperty("diameter_h", h -> text().toDouble()); + part -> setProperty("diameter_v", v -> text().toDouble()); } /// Met a jour l'abscisse du centre de l'ellipse et cree un objet d'annulation diff --git a/sources/editor/ellipseeditor.h b/sources/editor/ellipseeditor.h index 1f1283df2..258184f5b 100644 --- a/sources/editor/ellipseeditor.h +++ b/sources/editor/ellipseeditor.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/editor/lineeditor.cpp b/sources/editor/lineeditor.cpp index 2e7ea4ead..98fee3c2b 100644 --- a/sources/editor/lineeditor.cpp +++ b/sources/editor/lineeditor.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -17,6 +17,7 @@ */ #include "lineeditor.h" #include "partline.h" +#include "qet.h" /** Constructeur @@ -38,16 +39,46 @@ LineEditor::LineEditor(QETElementEditor *editor, PartLine *line, QWidget *parent x2 -> setValidator(new QDoubleValidator(x2)); y2 -> setValidator(new QDoubleValidator(y2)); - QGridLayout *grid = new QGridLayout(this); - grid -> addWidget(new QLabel("x1"), 0, 0); - grid -> addWidget(x1, 0, 1); - grid -> addWidget(new QLabel("y1"), 0, 2); - grid -> addWidget(y1, 0, 3); - grid -> addWidget(new QLabel("x2"), 1, 0); - grid -> addWidget(x2, 1, 1); - grid -> addWidget(new QLabel("y2"), 1, 2); - grid -> addWidget(y2, 1, 3); + end1_type = new QComboBox(); + end1_type -> addItem(QIcon(":/ico/endline-none.png"), tr("Normale", "type of the 1st end of a line"), QET::None ); + end1_type -> addItem(QIcon(":/ico/endline-simple.png"), tr("Fl\350che simple", "type of the 1st end of a line"), QET::Simple ); + end1_type -> addItem(QIcon(":/ico/endline-triangle.png"), tr("Fl\350che triangulaire", "type of the 1st end of a line"), QET::Triangle); + end1_type -> addItem(QIcon(":/ico/endline-circle.png"), tr("Cercle", "type of the 1st end of a line"), QET::Circle ); + end1_type -> addItem(QIcon(":/ico/endline-diamond.png"), tr("Carr\351", "type of the 1st end of a line"), QET::Diamond ); + end2_type = new QComboBox(); + end2_type -> addItem(QIcon(":/ico/endline-none.png"), tr("Normale", "type of the 2nd end of a line"), QET::None ); + end2_type -> addItem(QIcon(":/ico/endline-simple.png"), tr("Fl\350che simple", "type of the 2nd end of a line"), QET::Simple ); + end2_type -> addItem(QIcon(":/ico/endline-triangle.png"), tr("Fl\350che triangulaire", "type of the 2nd end of a line"), QET::Triangle); + end2_type -> addItem(QIcon(":/ico/endline-circle.png"), tr("Cercle", "type of the 2nd end of a line"), QET::Circle ); + end2_type -> addItem(QIcon(":/ico/endline-diamond.png"), tr("Carr\351", "type of the 2nd end of a line"), QET::Diamond ); + end1_length = new QLineEdit(); + end2_length = new QLineEdit(); + + end1_length -> setValidator(new QDoubleValidator(end1_length)); + end2_length -> setValidator(new QDoubleValidator(end2_length)); + + QGridLayout *grid = new QGridLayout(); + grid -> addWidget(new QLabel("x1"), 0, 0); + grid -> addWidget(x1, 0, 1); + grid -> addWidget(new QLabel("y1"), 0, 2); + grid -> addWidget(y1, 0, 3); + grid -> addWidget(new QLabel("x2"), 1, 0); + grid -> addWidget(x2, 1, 1); + grid -> addWidget(new QLabel("y2"), 1, 2); + grid -> addWidget(y2, 1, 3); + + QGridLayout *grid2 = new QGridLayout(); + grid2 -> addWidget(new QLabel(tr("Fin 1")), 0, 0); + grid2 -> addWidget(end1_type, 0, 1); + grid2 -> addWidget(end1_length, 0, 2); + grid2 -> addWidget(new QLabel(tr("Fin 2")), 1, 0); + grid2 -> addWidget(end2_type, 1, 1); + grid2 -> addWidget(end2_length, 1, 2); + + QVBoxLayout *v_layout = new QVBoxLayout(this); + v_layout -> addLayout(grid); + v_layout -> addLayout(grid2); updateForm(); } @@ -59,6 +90,10 @@ LineEditor::~LineEditor() { Met a jour la ligne a partir des donnees du formulaire */ void LineEditor::updateLine() { + part -> setFirstEndType(static_cast(end1_type -> currentIndex())); + part -> setFirstEndLength(end1_length -> text().toDouble()); + part -> setSecondEndType(static_cast(end2_type -> currentIndex())); + part -> setSecondEndLength(end2_length -> text().toDouble()); part -> setLine( QLineF( part -> mapFromScene( @@ -81,6 +116,14 @@ void LineEditor::updateLineY1() { addChangePartCommand(tr("ordonn\351e point 1") void LineEditor::updateLineX2() { addChangePartCommand(tr("abscisse point 2"), part, "x2", x2 -> text().toDouble()); } /// Met a jour l'ordonnee du second point de la ligne et cree un objet d'annulation void LineEditor::updateLineY2() { addChangePartCommand(tr("ordonn\351e point 2"), part, "y2", y2 -> text().toDouble()); } +/// Met a jour le type de la premiere extremite +void LineEditor::updateLineEndType1() { addChangePartCommand(tr("type fin 1"), part, "end1", end1_type -> currentIndex()); } +/// Met a jour la longueur de la premiere extremite +void LineEditor::updateLineEndLength1() { addChangePartCommand(tr("longueur fin 1"), part, "length1", end1_length -> text()); } +/// Met a jour le type de la seconde extremite +void LineEditor::updateLineEndType2() { addChangePartCommand(tr("type fin 2"), part, "end2", end2_type -> currentIndex()); } +/// Met a jour la longueur de la seconde extremite +void LineEditor::updateLineEndLength2() { addChangePartCommand(tr("longueur fin 2"), part, "length2", end2_length -> text()); } /** Met a jour le formulaire d'edition @@ -93,6 +136,10 @@ void LineEditor::updateForm() { y1 -> setText(QString("%1").arg(p1.y())); x2 -> setText(QString("%1").arg(p2.x())); y2 -> setText(QString("%1").arg(p2.y())); + end1_type -> setCurrentIndex(part -> firstEndType()); + end1_length -> setText(QString("%1").arg(part -> firstEndLength())); + end2_type -> setCurrentIndex(part -> secondEndType()); + end2_length -> setText(QString("%1").arg(part -> secondEndLength())); activeConnections(true); } @@ -106,10 +153,18 @@ void LineEditor::activeConnections(bool active) { connect(y1, SIGNAL(editingFinished()), this, SLOT(updateLineY1())); connect(x2, SIGNAL(editingFinished()), this, SLOT(updateLineX2())); connect(y2, SIGNAL(editingFinished()), this, SLOT(updateLineY2())); + connect(end1_type, SIGNAL(currentIndexChanged(int)), this, SLOT(updateLineEndType1())); + connect(end1_length, SIGNAL(editingFinished()), this, SLOT(updateLineEndLength1())); + connect(end2_type, SIGNAL(currentIndexChanged(int)), this, SLOT(updateLineEndType2())); + connect(end2_length, SIGNAL(editingFinished()), this, SLOT(updateLineEndLength2())); } else { - connect(x1, SIGNAL(editingFinished()), this, SLOT(updateLineX1())); - connect(y1, SIGNAL(editingFinished()), this, SLOT(updateLineY1())); - connect(x2, SIGNAL(editingFinished()), this, SLOT(updateLineX2())); - connect(y2, SIGNAL(editingFinished()), this, SLOT(updateLineY2())); + disconnect(x1, SIGNAL(editingFinished()), this, SLOT(updateLineX1())); + disconnect(y1, SIGNAL(editingFinished()), this, SLOT(updateLineY1())); + disconnect(x2, SIGNAL(editingFinished()), this, SLOT(updateLineX2())); + disconnect(y2, SIGNAL(editingFinished()), this, SLOT(updateLineY2())); + disconnect(end1_type, SIGNAL(currentIndexChanged(int)), this, SLOT(updateLineEndType1())); + disconnect(end1_length, SIGNAL(editingFinished()), this, SLOT(updateLineEndLength1())); + disconnect(end2_type, SIGNAL(currentIndexChanged(int)), this, SLOT(updateLineEndType2())); + disconnect(end2_length, SIGNAL(editingFinished()), this, SLOT(updateLineEndLength2())); } } diff --git a/sources/editor/lineeditor.h b/sources/editor/lineeditor.h index ddb96da88..f29868b91 100644 --- a/sources/editor/lineeditor.h +++ b/sources/editor/lineeditor.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -37,6 +37,8 @@ class LineEditor : public ElementItemEditor { private: PartLine *part; QLineEdit *x1, *y1, *x2, *y2; + QComboBox *end1_type, *end2_type; + QLineEdit *end1_length, *end2_length; // methodes public slots: @@ -45,6 +47,10 @@ class LineEditor : public ElementItemEditor { void updateLineY1(); void updateLineX2(); void updateLineY2(); + void updateLineEndType1(); + void updateLineEndLength1(); + void updateLineEndType2(); + void updateLineEndLength2(); void updateForm(); private: diff --git a/sources/editor/partarc.cpp b/sources/editor/partarc.cpp index 784eec56b..7b2b84c8b 100644 --- a/sources/editor/partarc.cpp +++ b/sources/editor/partarc.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -159,6 +159,7 @@ void PartArc::setProperty(const QString &property, const QVariant &value) { } else if (property == "angle") { setAngle(value.toInt()); } + update(); } /** diff --git a/sources/editor/partarc.h b/sources/editor/partarc.h index e754e37d2..0a8bdefaf 100644 --- a/sources/editor/partarc.h +++ b/sources/editor/partarc.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -48,7 +48,7 @@ class PartArc : public QGraphicsEllipseItem, public CustomElementGraphicPart { */ virtual int type() const { return Type; } virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget * = 0); - virtual QString name() const { return(QObject::tr("arc")); } + virtual QString name() const { return(QObject::tr("arc", "element part name")); } virtual const QDomElement toXml(QDomDocument &) const; virtual void fromXml(const QDomElement &); virtual QPointF sceneTopLeft() const; diff --git a/sources/editor/partcircle.cpp b/sources/editor/partcircle.cpp index e0f72501d..1bbebb839 100644 --- a/sources/editor/partcircle.cpp +++ b/sources/editor/partcircle.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -125,6 +125,7 @@ void PartCircle::setProperty(const QString &property, const QVariant &value) { current_rect.setSize(QSizeF(new_diameter, new_diameter)); setRect(current_rect); } + update(); } /** diff --git a/sources/editor/partcircle.h b/sources/editor/partcircle.h index 34aab8b1f..26554d939 100644 --- a/sources/editor/partcircle.h +++ b/sources/editor/partcircle.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -46,7 +46,7 @@ class PartCircle : public QGraphicsEllipseItem, public CustomElementGraphicPart */ virtual int type() const { return Type; } virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget * = 0); - virtual QString name() const { return(QObject::tr("cercle")); } + virtual QString name() const { return(QObject::tr("cercle", "element part name")); } virtual const QDomElement toXml(QDomDocument &) const; virtual void fromXml(const QDomElement &); virtual QPointF sceneTopLeft() const; diff --git a/sources/editor/partellipse.cpp b/sources/editor/partellipse.cpp index e3765c0ec..6bd4a2a0d 100644 --- a/sources/editor/partellipse.cpp +++ b/sources/editor/partellipse.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -129,6 +129,7 @@ void PartEllipse::setProperty(const QString &property, const QVariant &value) { current_rect.setHeight(new_height); setRect(current_rect); } + update(); } /** diff --git a/sources/editor/partellipse.h b/sources/editor/partellipse.h index b16af8bd0..4a1367caf 100644 --- a/sources/editor/partellipse.h +++ b/sources/editor/partellipse.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -46,7 +46,7 @@ class PartEllipse : public QGraphicsEllipseItem, public CustomElementGraphicPart */ virtual int type() const { return Type; } virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget * = 0); - virtual QString name() const { return(QObject::tr("ellipse")); } + virtual QString name() const { return(QObject::tr("ellipse", "element part name")); } virtual const QDomElement toXml(QDomDocument &) const; virtual void fromXml(const QDomElement &); virtual QPointF sceneTopLeft() const; diff --git a/sources/editor/partline.cpp b/sources/editor/partline.cpp index 16eef95e7..c9253705a 100644 --- a/sources/editor/partline.cpp +++ b/sources/editor/partline.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -25,7 +25,14 @@ @param parent Le QGraphicsItem parent de cette ligne @param scene La scene sur laquelle figure cette ligne */ -PartLine::PartLine(QETElementEditor *editor, QGraphicsItem *parent, QGraphicsScene *scene) : QGraphicsLineItem(parent, scene), CustomElementGraphicPart(editor) { +PartLine::PartLine(QETElementEditor *editor, QGraphicsItem *parent, QGraphicsScene *scene) : + QGraphicsLineItem(parent, scene), + CustomElementGraphicPart(editor), + first_end(QET::None), + first_length(1.5), + second_end(QET::None), + second_length(1.5) +{ setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); setAcceptedMouseButtons(Qt::LeftButton); informations = new LineEditor(elementEditor(), this); @@ -38,6 +45,20 @@ PartLine::PartLine(QETElementEditor *editor, QGraphicsItem *parent, QGraphicsSce PartLine::~PartLine() { } +/** + @param end_type Type d'extremite + @return Le nombre de "longueurs" requises pour dessiner une extremite de type end_type +*/ +uint PartLine::requiredLengthForEndType(const QET::EndType &end_type) { + uint length_count_required = 0; + if (end_type == QET::Circle || end_type == QET::Diamond) { + length_count_required = 2; + } else if (end_type == QET::Simple || end_type == QET::Triangle) { + length_count_required = 1; + } + return(length_count_required); +} + /** Dessine la ligne @param painter QPainter a utiliser pour rendre le dessin @@ -45,14 +66,91 @@ PartLine::~PartLine() { @param widget Widget sur lequel le rendu est effectue */ void PartLine::paint(QPainter *painter, const QStyleOptionGraphicsItem */*q*/, QWidget */*w*/) { + // inutile de dessiner une ligne nulle + if (line().p1() == line().p2()) return; applyStylesToQPainter(*painter); QPen t = painter -> pen(); + t.setJoinStyle(Qt::MiterJoin); if (isSelected()) { t.setColor(Qt::red); - painter -> setPen(t); } - painter -> setBrush(Qt::NoBrush); - painter -> drawLine(line()); + painter -> setPen(t); + + QPointF point1(line().p1()); + QPointF point2(line().p2()); + + qreal line_length(line().length()); + qreal pen_width = painter -> pen().widthF(); + + qreal length1 = first_length; + qreal length2 = second_length; + + //debugPaint(painter); + + // determine s'il faut dessiner les extremites + bool draw_1st_end, draw_2nd_end; + qreal reduced_line_length = line_length - (length1 * requiredLengthForEndType(first_end)); + draw_1st_end = first_end && reduced_line_length >= 0; + if (draw_1st_end) { + reduced_line_length -= (length2 * requiredLengthForEndType(second_end)); + } else { + reduced_line_length = line_length - (length2 * requiredLengthForEndType(second_end)); + } + draw_2nd_end = second_end && reduced_line_length >= 0; + + // dessine la premiere extremite + QPointF start_point, stop_point; + if (draw_1st_end) { + QList four_points1(fourEndPoints(point1, point2, length1)); + if (first_end == QET::Circle) { + painter -> drawEllipse(QRectF(four_points1[0] - QPointF(length1, length1), QSizeF(length1 * 2.0, length1 * 2.0))); + start_point = four_points1[1]; + } else if (first_end == QET::Diamond) { + painter -> drawPolygon(QPolygonF() << four_points1[1] << four_points1[2] << point1 << four_points1[3]); + start_point = four_points1[1]; + } else if (first_end == QET::Simple) { + painter -> drawPolyline(QPolygonF() << four_points1[3] << point1 << four_points1[2]); + start_point = point1; + + } else if (first_end == QET::Triangle) { + painter -> drawPolygon(QPolygonF() << four_points1[0] << four_points1[2] << point1 << four_points1[3]); + start_point = four_points1[0]; + } + + // ajuste le depart selon l'epaisseur du trait + if (pen_width && (first_end == QET::Simple || first_end == QET::Circle)) { + start_point = QLineF(start_point, point2).pointAt(pen_width / 2.0 / line_length); + } + } else { + start_point = point1; + } + + // dessine la seconde extremite + if (draw_2nd_end) { + QList four_points2(fourEndPoints(point2, point1, length2)); + if (second_end == QET::Circle) { + painter -> drawEllipse(QRectF(four_points2[0] - QPointF(length2, length2), QSizeF(length2 * 2.0, length2 * 2.0))); + stop_point = four_points2[1]; + } else if (second_end == QET::Diamond) { + painter -> drawPolygon(QPolygonF() << four_points2[2] << point2 << four_points2[3] << four_points2[1]); + stop_point = four_points2[1]; + } else if (second_end == QET::Simple) { + painter -> drawPolyline(QPolygonF() << four_points2[3] << point2 << four_points2[2]); + stop_point = point2; + } else if (second_end == QET::Triangle) { + painter -> drawPolygon(QPolygonF() << four_points2[0] << four_points2[2] << point2 << four_points2[3] << four_points2[0]); + stop_point = four_points2[0]; + } + + // ajuste l'arrivee selon l'epaisseur du trait + if (pen_width && (second_end == QET::Simple || second_end == QET::Circle)) { + stop_point = QLineF(point1, stop_point).pointAt((line_length - (pen_width / 2.0)) / line_length); + } + } else { + stop_point = point2; + } + + painter -> drawLine(start_point, stop_point); } /** @@ -70,6 +168,11 @@ const QDomElement PartLine::toXml(QDomDocument &xml_document) const { xml_element.setAttribute("y1", QString("%1").arg(p1.y())); xml_element.setAttribute("x2", QString("%1").arg(p2.x())); xml_element.setAttribute("y2", QString("%1").arg(p2.y())); + xml_element.setAttribute("end1", QET::endTypeToString(first_end)); + xml_element.setAttribute("length1", first_length); + xml_element.setAttribute("end2", QET::endTypeToString(second_end)); + xml_element.setAttribute("length2", second_length); + stylesToXml(xml_element); return(xml_element); } @@ -92,6 +195,10 @@ void PartLine::fromXml(const QDomElement &qde) { ) ) ); + first_end = QET::endTypeFromString(qde.attribute("end1")); + first_length = qde.attribute("length1", "1.5").toDouble(); + second_end = QET::endTypeFromString(qde.attribute("end2")); + second_length = qde.attribute("length2", "1.5").toDouble(); } /** @@ -101,6 +208,8 @@ void PartLine::fromXml(const QDomElement &qde) { * y1 : ordonnee du second point * x2 : abscisse du premier point * y2 : ordonnee du second point + *end1 : type d'embout du premier point + *end2 : type d'embout du second point @param value Valeur a attribuer a la propriete */ void PartLine::setProperty(const QString &property, const QVariant &value) { @@ -116,8 +225,20 @@ void PartLine::setProperty(const QString &property, const QVariant &value) { new_p2.setX(value.toDouble()); } else if (property == "y2") { new_p2.setY(value.toDouble()); - } else setline = false; - setLine(QLineF(mapFromScene(new_p1), mapFromScene(new_p2))); + } else { + setline = false; + if (property == "end1") { + setFirstEndType(static_cast(value.toUInt())); + } else if (property == "end2") { + setSecondEndType(static_cast(value.toUInt())); + } else if (property == "length1") { + setFirstEndLength(value.toDouble()); + } else if (property == "length2") { + setSecondEndLength(value.toDouble()); + } + } + if (setline) setLine(QLineF(mapFromScene(new_p1), mapFromScene(new_p2))); + update(); } /** @@ -142,6 +263,14 @@ QVariant PartLine::property(const QString &property) { return(sceneP2().x()); } else if (property == "y2") { return(sceneP2().y()); + } else if (property == "end1") { + return(firstEndType()); + } else if (property == "end2") { + return(secondEndType()); + } else if (property == "length1") { + return(firstEndLength()); + } else if (property == "length2") { + return(secondEndLength()); } return(QVariant()); } @@ -186,6 +315,24 @@ QPainterPath PartLine::shape() const { t.lineTo(points.at(2)); t.lineTo(points.at(3)); t.lineTo(points.at(0)); + + // n'en fait pas plus si la ligne se ramene a un point + if (line().p1() == line().p2()) return(t); + + // ajoute un cercle pour l'extremite 1 si besoin + if (first_end) { + QPainterPath t2; + t2.addEllipse(firstEndCircleRect()); + t.addPath(t2.subtracted(t)); + } + + // ajoute un cercle pour l'extremite 2 si besoin + if (second_end) { + QPainterPath t2; + t2.addEllipse(secondEndCircleRect()); + t.addPath(t2.subtracted(t)); + } + return(t); } @@ -233,12 +380,86 @@ QList PartLine::fourShapePoints() const { return(result); } +/** + @return le rectangle encadrant l'integralite de la premiere extremite +*/ +QRectF PartLine::firstEndCircleRect() const { + QList interesting_points = fourEndPoints( + line().p1(), + line().p2(), + first_length + ); + + QRectF end_rect( + interesting_points[0] - QPointF(first_length, first_length), + QSizeF(2.0 * first_length, 2.0 * first_length) + ); + + return(end_rect); +} + +/** + @return le rectangle encadrant l'integralite de la seconde extremite +*/ +QRectF PartLine::secondEndCircleRect() const { + QList interesting_points = fourEndPoints( + line().p2(), + line().p1(), + second_length + ); + + QRectF end_rect( + interesting_points[0] - QPointF(second_length, second_length), + QSizeF(2.0 * second_length, 2.0 * second_length) + ); + + return(end_rect); +} + +/** + Affiche differentes composantes du dessin : + - le boundingRect + - les point speciaux a chaque extremite + - la quadrature du cercle a chaque extremite, meme si celle-ci est d'un + autre type +*/ +void PartLine::debugPaint(QPainter *painter) { + painter -> save(); + painter -> setPen(Qt::gray); + painter -> drawRect(boundingRect()); + + painter -> setPen(Qt::green); + painter -> drawRect(firstEndCircleRect()); + painter -> drawRect(secondEndCircleRect()); + + painter -> setPen(Qt::red); + foreach(QPointF pointy, fourEndPoints(line().p1(), line().p2(), first_length)) { + painter -> drawEllipse(pointy, 0.1, 0.1); + } + foreach(QPointF pointy, fourEndPoints(line().p2(), line().p1(), second_length)) { + painter -> drawEllipse(pointy, 0.1, 0.1); + } + + painter -> restore(); +} + /** @return le rectangle delimitant cette partie. */ QRectF PartLine::boundingRect() const { - qreal adjust = 1.5; QRectF r(QGraphicsLineItem::boundingRect()); + + // cas special : le cercle sort largement du bounding rect originel + if (first_end == QET::Circle) { + r = r.united(firstEndCircleRect()); + } + + if (second_end == QET::Circle) { + r = r.united(secondEndCircleRect()); + } + + // la taille du bounding rect est ajustee de 0.2px + qreal adjust = 0.6; r.adjust(-adjust, -adjust, adjust, adjust); return(r); } @@ -251,3 +472,91 @@ QRectF PartLine::boundingRect() const { bool PartLine::isUseless() const { return(sceneP1() == sceneP2()); } + +/** + @param end_type nouveau type d'embout pour l'extremite 1 +*/ +void PartLine::setFirstEndType(const QET::EndType &end_type) { + first_end = end_type; +} + +/** + @return le type d'embout pour l'extremite 1 +*/ +QET::EndType PartLine::firstEndType() const { + return(first_end); +} + +/** + @param end_type Nouveau type d'embout pour l'extremite 2 +*/ +void PartLine::setSecondEndType(const QET::EndType &end_type) { + second_end = end_type; +} + +/** + @return le type d'embout pour l'extremite 2 +*/ +QET::EndType PartLine::secondEndType() const { + return(second_end); +} + +/** + @return Les quatre points interessants a l'extremite d'une droite + Ces points sont, dans l'ordre : + * O : point sur la ligne, a une distance length de l'extremite + * A : point sur la ligne a une distance 2 x length de l'extremite + * B : point a une distance length de O - O est le projete de B sur la droite + * C : point a une distance length de O - O est le projete de C sur la droite + B et C sont situes de part et d'autre de la ligne + @param end_point Extremite concernee + @param other_point Autre point permettant de definir une ligne + @param length Longueur a utiliser entre l'extremite et le point O +*/ +QList PartLine::fourEndPoints(const QPointF &end_point, const QPointF &other_point, const qreal &length) { + // vecteur et longueur de la ligne + QPointF line_vector = end_point - other_point; + qreal line_length = sqrt(pow(line_vector.x(), 2) + pow(line_vector.y(), 2)); + + // vecteur unitaire et vecteur perpendiculaire + QPointF u(line_vector / line_length * length); + QPointF v(-u.y(), u.x()); + + // points O, A, B et C + QPointF o(end_point - u); + QPointF a(o - u); + QPointF b(o + v); + QPointF c(o - v); + + return(QList() << o << a << b << c); +} + +/** + @param length nouvelle longueur de la premiere extremite + la longueur de l'extemite ne peut exceder celle de la ligne +*/ +void PartLine::setFirstEndLength(const qreal &length) { + first_length = qMin(qAbs(length), line().length()); +} + +/** + @return longueur de la premiere extremite +*/ +qreal PartLine::firstEndLength() const { + return(first_length); +} + +/** + @param length nouvelle longueur de la seconde extremite + la longueur de l'extemite ne peut exceder celle de la ligne +*/ +void PartLine::setSecondEndLength(const qreal &length) { + second_length = qMin(qAbs(length), line().length()); +} + +/** + @return longueur de la seconde extremite +*/ +qreal PartLine::secondEndLength() const { + return(second_length); +} diff --git a/sources/editor/partline.h b/sources/editor/partline.h index 05f588d37..4ef6c315a 100644 --- a/sources/editor/partline.h +++ b/sources/editor/partline.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -19,10 +19,18 @@ #define PART_LINE_H #include #include "customelementgraphicpart.h" +#include "qet.h" class LineEditor; /** Cette classe represente une ligne pouvant etre utilisee pour composer le dessin d'un element dans l'editeur d'element. + Une ligne est composee de deux points. Elle peut comporter des extremites + speciales definissables grace aux methodes setFirstEndType et + setSecondEndType. La taille des extremites est definissable via les + methodes setFirstEndLength et setSecondEndLength. + A noter que les extremites ne sont pas dessinees si la longueur requise + pour leur dessin n'est pas contenue dans la ligne. S'il n'y a de la place + que pour une seule extremite, c'est la premiere qui est privilegiee. */ class PartLine : public QGraphicsLineItem, public CustomElementGraphicPart { // constructeurs, destructeur @@ -36,17 +44,22 @@ class PartLine : public QGraphicsLineItem, public CustomElementGraphicPart { // attributs private: LineEditor *informations; + QET::EndType first_end; + qreal first_length; + QET::EndType second_end; + qreal second_length; // methodes public: enum { Type = UserType + 1104 }; + /** permet de caster un QGraphicsItem en PartLine avec qgraphicsitem_cast @return le type de QGraphicsItem */ virtual int type() const { return Type; } virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget * = 0); - virtual QString name() const { return(QObject::tr("ligne")); } + virtual QString name() const { return(QObject::tr("ligne", "element part name")); } virtual const QDomElement toXml(QDomDocument &) const; virtual void fromXml(const QDomElement &); virtual QPointF sceneP1() const; @@ -56,11 +69,23 @@ class PartLine : public QGraphicsLineItem, public CustomElementGraphicPart { virtual void setProperty(const QString &, const QVariant &); virtual QVariant property(const QString &); virtual bool isUseless() const; - + virtual void setFirstEndType(const QET::EndType &); + virtual QET::EndType firstEndType() const; + virtual void setSecondEndType(const QET::EndType &); + virtual QET::EndType secondEndType() const; + virtual void setFirstEndLength(const qreal &); + virtual qreal firstEndLength() const; + virtual void setSecondEndLength(const qreal &); + virtual qreal secondEndLength() const; + static uint requiredLengthForEndType(const QET::EndType &); + static QList fourEndPoints(const QPointF &, const QPointF &, const qreal &); protected: QVariant itemChange(GraphicsItemChange, const QVariant &); private: QList fourShapePoints() const; + QRectF firstEndCircleRect() const; + QRectF secondEndCircleRect() const; + void debugPaint(QPainter *); }; #endif diff --git a/sources/editor/partpolygon.cpp b/sources/editor/partpolygon.cpp index ba791f66c..ec4d5b769 100644 --- a/sources/editor/partpolygon.cpp +++ b/sources/editor/partpolygon.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -114,6 +114,7 @@ void PartPolygon::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWi void PartPolygon::setProperty(const QString &property, const QVariant &value) { CustomElementGraphicPart::setProperty(property, value); if (property == "closed") closed = value.toBool(); + update(); } /** diff --git a/sources/editor/partpolygon.h b/sources/editor/partpolygon.h index 69a74e8fb..e8f9e14d0 100644 --- a/sources/editor/partpolygon.h +++ b/sources/editor/partpolygon.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -58,7 +58,7 @@ class PartPolygon : public QGraphicsPolygonItem, public CustomElementGraphicPart @return le type de QGraphicsItem */ virtual int type() const { return Type; } - virtual QString name() const { return(QObject::tr("polygone")); } + virtual QString name() const { return(QObject::tr("polygone", "element part name")); } void fromXml(const QDomElement &); const QDomElement toXml(QDomDocument &) const; virtual QRectF boundingRect() const; diff --git a/sources/editor/partrectangle.cpp b/sources/editor/partrectangle.cpp new file mode 100644 index 000000000..c94f21565 --- /dev/null +++ b/sources/editor/partrectangle.cpp @@ -0,0 +1,207 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#include "partrectangle.h" +#include "rectangleeditor.h" + +/** + Constructeur + @param editor L'editeur d'element concerne + @param parent Le QGraphicsItem parent de ce rectangle + @param scene La scene sur laquelle figure ce rectangle +*/ +PartRectangle::PartRectangle(QETElementEditor *editor, QGraphicsItem *parent, QGraphicsScene *scene) : QGraphicsRectItem(parent, scene), CustomElementGraphicPart(editor) { + setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); + setAcceptedMouseButtons(Qt::LeftButton); + informations = new RectangleEditor(elementEditor(), this); + informations -> setElementTypeName(name()); + style_editor -> appendWidget(informations); + style_editor -> setElementTypeName(name()); +} + +/// Destructeur +PartRectangle::~PartRectangle() { +} + +/** + Dessine le rectangle + @param painter QPainter a utiliser pour rendre le dessin + @param options Options pour affiner le rendu + @param widget Widget sur lequel le rendu est effectue +*/ +void PartRectangle::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { + applyStylesToQPainter(*painter); + QPen t = painter -> pen(); + if (isSelected()) { + t.setColor(Qt::red); + } + + // force le type de jointures pour les rectangles + t.setJoinStyle(Qt::MiterJoin); + + // force le dessin avec un trait fin si l'une des dimensions du rectangle est nulle + if (!rect().width() || !rect().height()) { + t.setWidth(0); + } + + painter -> setPen(t); + painter -> drawRect(rect()); + if (isSelected()) { + painter -> setRenderHint(QPainter::Antialiasing, false); + painter -> setPen((painter -> brush().color() == QColor(Qt::black) && painter -> brush().isOpaque()) ? Qt::yellow : Qt::blue); + QPointF center = rect().center(); + painter -> drawLine(QLineF(center.x() - 2.0, center.y(), center.x() + 2.0, center.y())); + painter -> drawLine(QLineF(center.x(), center.y() - 2.0, center.x(), center.y() + 2.0)); + } +} + +/** + Exporte le rectangle en XML + @param xml_document Document XML a utiliser pour creer l'element XML + @return un element XML decrivant le rectangle +*/ +const QDomElement PartRectangle::toXml(QDomDocument &xml_document) const { + QDomElement xml_element = xml_document.createElement("rect"); + QPointF top_left(sceneTopLeft()); + xml_element.setAttribute("x", QString("%1").arg(top_left.x())); + xml_element.setAttribute("y", QString("%1").arg(top_left.y())); + xml_element.setAttribute("width", QString("%1").arg(rect().width())); + xml_element.setAttribute("height", QString("%1").arg(rect().height())); + stylesToXml(xml_element); + return(xml_element); +} + +/** + Importe les proprietes d'une rectangle depuis un element XML + @param qde Element XML a lire +*/ +void PartRectangle::fromXml(const QDomElement &qde) { + stylesFromXml(qde); + setRect( + QRectF( + mapFromScene( + qde.attribute("x", "0").toDouble(), + qde.attribute("y", "0").toDouble() + ), + QSizeF( + qde.attribute("width", "0").toDouble(), + qde.attribute("height", "0").toDouble() + ) + ) + ); +} + +/** + Specifie la valeur d'une propriete donnee du rectangle + @param property propriete a modifier. Valeurs acceptees : + * x : abscisse du coin superieur gauche du rectangle + * y : ordonnee du coin superieur gauche du rectangle + * width : largeur du rectangle + * height : hauteur du rectangle + @param value Valeur a attribuer a la propriete +*/ +void PartRectangle::setProperty(const QString &property, const QVariant &value) { + CustomElementGraphicPart::setProperty(property, value); + if (!value.canConvert(QVariant::Double)) return; + if (property == "x") { + QRectF current_rect = rect(); + QPointF current_pos = mapToScene(current_rect.topLeft()); + setRect(current_rect.translated(value.toDouble() - current_pos.x(), 0.0)); + } else if (property == "y") { + QRectF current_rect = rect(); + QPointF current_pos = mapToScene(current_rect.topLeft()); + setRect(current_rect.translated(0.0, value.toDouble() - current_pos.y())); + } else if (property == "width") { + qreal new_width = qAbs(value.toDouble()); + QRectF current_rect = rect(); + current_rect.setWidth(new_width); + setRect(current_rect); + } else if (property == "height") { + qreal new_height = qAbs(value.toDouble()); + QRectF current_rect = rect(); + current_rect.setHeight(new_height); + setRect(current_rect); + } + update(); +} + +/** + Permet d'acceder a la valeur d'une propriete donnee du rectangle + @param property propriete lue. Valeurs acceptees : + * x : abscisse du coin superieur gauche du rectangle + * y : ordonnee du coin superieur gauche du rectangle + * width : largeur du rectangle + * height : hauteur du rectangle + @return La valeur de la propriete property +*/ +QVariant PartRectangle::property(const QString &property) { + // appelle la methode property de CustomElementGraphicpart pour les styles + QVariant style_property = CustomElementGraphicPart::property(property); + if (style_property != QVariant()) return(style_property); + + if (property == "x") { + return(mapToScene(rect().topLeft()).x()); + } else if (property == "y") { + return(mapToScene(rect().topLeft()).y()); + } else if (property == "width") { + return(rect().width()); + } else if (property == "height") { + return(rect().height()); + } + return(QVariant()); +} + +/** + Gere les changements intervenant sur cette partie + @param change Type de changement + @param value Valeur numerique relative au changement +*/ +QVariant PartRectangle::itemChange(GraphicsItemChange change, const QVariant &value) { + if (scene()) { + if (change == QGraphicsItem::ItemPositionChange || change == QGraphicsItem::ItemSelectedChange) { + informations -> updateForm(); + } + } + return(QGraphicsRectItem::itemChange(change, value)); +} + +/** + @return le coin superieur gauche du rectangle, dans les coordonnees de la + scene. +*/ +QPointF PartRectangle::sceneTopLeft() const { + return(mapToScene(rect().topLeft())); +} + +/** + @return true si cette partie n'est pas pertinente et ne merite pas d'etre + conservee / enregistree. + Un rectangle est pertinent des lors que ses dimensions ne sont pas nulles. +*/ +bool PartRectangle::isUseless() const { + return(rect().isNull()); +} + +/** + @return le rectangle delimitant cette partie. +*/ +QRectF PartRectangle::boundingRect() const { + qreal adjust = 1.5; + QRectF r(QGraphicsRectItem::boundingRect().normalized()); + r.adjust(-adjust, -adjust, adjust, adjust); + return(r); +} diff --git a/sources/editor/partrectangle.h b/sources/editor/partrectangle.h new file mode 100644 index 000000000..f4281d599 --- /dev/null +++ b/sources/editor/partrectangle.h @@ -0,0 +1,61 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef PART_RECTANGLE_H +#define PART_RECTANGLE_H +#include +#include "customelementgraphicpart.h" +class RectangleEditor; +/** + Cette classe represente un rectangle pouvant etre utilise pour composer le + dessin d'un element dans l'editeur d'element. +*/ +class PartRectangle : public QGraphicsRectItem, public CustomElementGraphicPart { + // constructeurs, destructeur + public: + PartRectangle(QETElementEditor *, QGraphicsItem * = 0, QGraphicsScene * = 0); + virtual ~PartRectangle(); + + private: + PartRectangle(const PartRectangle &); + + // attributs + private: + RectangleEditor *informations; + + // methodes + public: + enum { Type = UserType + 1109 }; + /** + permet de caster un QGraphicsItem en PartRectangle avec qgraphicsitem_cast + @return le type de QGraphicsItem + */ + virtual int type() const { return Type; } + virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget * = 0); + virtual QString name() const { return(QObject::tr("rectangle", "element part name")); } + virtual const QDomElement toXml(QDomDocument &) const; + virtual void fromXml(const QDomElement &); + virtual QPointF sceneTopLeft() const; + virtual QRectF boundingRect() const; + virtual void setProperty(const QString &, const QVariant &); + virtual QVariant property(const QString &); + virtual bool isUseless() const; + + protected: + QVariant itemChange(GraphicsItemChange, const QVariant &); +}; +#endif diff --git a/sources/editor/partterminal.cpp b/sources/editor/partterminal.cpp index b059f0493..2c5043cc5 100644 --- a/sources/editor/partterminal.cpp +++ b/sources/editor/partterminal.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -39,6 +39,7 @@ PartTerminal::PartTerminal(QETElementEditor *editor, QGraphicsItem *parent, QGra /// Destructeur PartTerminal::~PartTerminal() { + if (informations -> parentWidget()) return; // le widget sera supprime par son parent delete informations; }; diff --git a/sources/editor/partterminal.h b/sources/editor/partterminal.h index 10eb1f976..7b5555a4a 100644 --- a/sources/editor/partterminal.h +++ b/sources/editor/partterminal.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -48,7 +48,7 @@ class PartTerminal : public CustomElementPart, public QGraphicsItem { @return le type de QGraphicsItem */ virtual int type() const { return Type; } - virtual QString name() const { return(QObject::tr("borne")); } + virtual QString name() const { return(QObject::tr("borne", "element part name")); } virtual void fromXml(const QDomElement &); virtual const QDomElement toXml(QDomDocument &) const; virtual QWidget *elementInformations(); diff --git a/sources/editor/parttext.cpp b/sources/editor/parttext.cpp index 19f00fba9..be130d353 100644 --- a/sources/editor/parttext.cpp +++ b/sources/editor/parttext.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -33,14 +33,14 @@ PartText::PartText(QETElementEditor *editor, QGraphicsItem *parent, ElementScene { setDefaultTextColor(Qt::black); setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); - setPlainText(QObject::tr("T")); + setPlainText(QObject::tr("T", "default text when adding a text in the element editor")); infos = new TextEditor(elementEditor(), this); infos -> setElementTypeName(name()); } - /// Destructeur PartText::~PartText() { + if (infos -> parentWidget()) return; // le widget sera supprime par son parent delete infos; } @@ -130,7 +130,7 @@ void PartText::focusOutEvent(QFocusEvent *e) { if (previous_text != toPlainText()) { undoStack().push( new ChangePartCommand( - TextEditor::tr("texte") + " " + name(), + TextEditor::tr("contenu") + " " + name(), this, "text", previous_text, @@ -139,6 +139,13 @@ void PartText::focusOutEvent(QFocusEvent *e) { ); previous_text = toPlainText(); } + + // deselectionne le texte + QTextCursor qtc = textCursor(); + qtc.clearSelection(); + setTextCursor(qtc); + + setTextInteractionFlags(Qt::NoTextInteraction); setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); } @@ -176,6 +183,7 @@ void PartText::setProperty(const QString &property, const QVariant &value) { } else if (property == "text") { setPlainText(value.toString()); } + update(); } /** diff --git a/sources/editor/parttext.h b/sources/editor/parttext.h index 97cbbcfea..5d805a86f 100644 --- a/sources/editor/parttext.h +++ b/sources/editor/parttext.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -44,7 +44,7 @@ class PartText : public QGraphicsTextItem, public CustomElementPart { @return le type de QGraphicsItem */ virtual int type() const { return Type; } - virtual QString name() const { return(QObject::tr("texte")); } + virtual QString name() const { return(QObject::tr("texte", "element part name")); } void fromXml(const QDomElement &); const QDomElement toXml(QDomDocument &) const; QWidget *elementInformations(); diff --git a/sources/editor/parttextfield.cpp b/sources/editor/parttextfield.cpp index 80c012a2a..4ccbd1841 100644 --- a/sources/editor/parttextfield.cpp +++ b/sources/editor/parttextfield.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -33,13 +33,14 @@ PartTextField::PartTextField(QETElementEditor *editor, QGraphicsItem *parent, QG { setDefaultTextColor(Qt::black); setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); - setPlainText(QObject::tr("_")); + setPlainText(QObject::tr("_", "default text when adding a textfield in the element editor")); infos = new TextFieldEditor(elementEditor(), this); infos -> setElementTypeName(name()); } /// Destructeur PartTextField::~PartTextField() { + if (infos -> parentWidget()) return; // le widget sera supprime par son parent delete infos; } @@ -148,7 +149,7 @@ void PartTextField::focusOutEvent(QFocusEvent *e) { if (previous_text != toPlainText()) { undoStack().push( new ChangePartCommand( - TextFieldEditor::tr("texte") + " " + name(), + TextFieldEditor::tr("contenu") + " " + name(), this, "text", previous_text, @@ -157,6 +158,13 @@ void PartTextField::focusOutEvent(QFocusEvent *e) { ); previous_text = toPlainText(); } + + // deselectionne le texte + QTextCursor qtc = textCursor(); + qtc.clearSelection(); + setTextCursor(qtc); + + setTextInteractionFlags(Qt::NoTextInteraction); setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); } @@ -197,6 +205,7 @@ void PartTextField::setProperty(const QString &property, const QVariant &value) } else if (property == "rotate") { follow_parent_rotations = value.toBool(); } + update(); } /** diff --git a/sources/editor/parttextfield.h b/sources/editor/parttextfield.h index 9fc407230..636655ca1 100644 --- a/sources/editor/parttextfield.h +++ b/sources/editor/parttextfield.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -48,7 +48,7 @@ class PartTextField : public QGraphicsTextItem, public CustomElementPart { @return le type de QGraphicsItem */ virtual int type() const { return Type; } - virtual QString name() const { return(QObject::tr("champ de texte")); } + virtual QString name() const { return(QObject::tr("champ de texte", "element part name")); } void fromXml(const QDomElement &); const QDomElement toXml(QDomDocument &) const; QWidget *elementInformations(); diff --git a/sources/editor/polygoneditor.cpp b/sources/editor/polygoneditor.cpp index 1788d35d6..bebbd695f 100644 --- a/sources/editor/polygoneditor.cpp +++ b/sources/editor/polygoneditor.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -69,8 +69,8 @@ void PolygonEditor::updatePolygonPoints() { if (points.count() < 2) { QMessageBox::warning( this, - tr("Erreur"), - tr("Le polygone doit comporter au moins deux points.") + tr("Erreur", "message box title"), + tr("Le polygone doit comporter au moins deux points.", "message box content") ); return; } diff --git a/sources/editor/polygoneditor.h b/sources/editor/polygoneditor.h index dcbc2e875..808e1ddbd 100644 --- a/sources/editor/polygoneditor.h +++ b/sources/editor/polygoneditor.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/editor/qetelementeditor.cpp b/sources/editor/qetelementeditor.cpp index d19b37137..72b76d586 100644 --- a/sources/editor/qetelementeditor.cpp +++ b/sources/editor/qetelementeditor.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -22,6 +22,8 @@ #include "customelementpart.h" #include "newelementwizard.h" #include "elementitemeditor.h" +#include "elementdefinition.h" +#include "elementdialog.h" #include "recentfiles.h" /** @@ -31,8 +33,8 @@ QETElementEditor::QETElementEditor(QWidget *parent) : QMainWindow(parent), read_only(false), - min_title(tr("QElectroTech - \311diteur d'\351l\351ment")), - _filename(QString()) + min_title(tr("QElectroTech - \311diteur d'\351l\351ment", "window title")), + opened_from_file(false) { setWindowTitle(min_title); setWindowIcon(QIcon(":/ico/qet.png")); @@ -47,6 +49,7 @@ QETElementEditor::QETElementEditor(QWidget *parent) : // lecture des parametres readSettings(); + slot_updateMenus(); // affichage show(); @@ -62,12 +65,18 @@ QETElementEditor::~QETElementEditor() { void QETElementEditor::setupActions() { new_element = new QAction(QIcon(":/ico/new.png"), tr("&Nouveau"), this); open = new QAction(QIcon(":/ico/open.png"), tr("&Ouvrir"), this); + open_file = new QAction(QIcon(":/ico/open.png"), tr("&Ouvrir depuis un fichier"), this); save = new QAction(QIcon(":/ico/save.png"), tr("&Enregistrer"), this); save_as = new QAction(QIcon(":/ico/saveas.png"), tr("Enregistrer sous"), this); + save_as_file = new QAction(QIcon(":/ico/saveas.png"), tr("Enregistrer dans un fichier"), this); reload = new QAction(QIcon(":/ico/reload.png"), tr("Recharger"), this); quit = new QAction(QIcon(":/ico/exit.png"), tr("&Quitter"), this); selectall = new QAction( tr("Tout s\351lectionner"), this); deselectall = new QAction( tr("D\351s\351lectionner tout"), this); + cut = new QAction(QIcon(":/ico/cut.png"), tr("Co&uper"), this); + copy = new QAction(QIcon(":/ico/copy.png"), tr("Cop&ier"), this); + paste = new QAction(QIcon(":/ico/paste.png"), tr("C&oller"), this); + paste_in_area = new QAction(QIcon(":/ico/paste.png"), tr("C&oller dans la zone..."), this); inv_select = new QAction( tr("Inverser la s\351lection"), this); edit_delete = new QAction(QIcon(":/ico/delete.png"), tr("&Supprimer"), this); zoom_in = new QAction(QIcon(":/ico/viewmag+.png"), tr("Zoom avant"), this); @@ -83,6 +92,7 @@ void QETElementEditor::setupActions() { edit_forward = new QAction(QIcon(":/ico/bring_forward.png"),tr("Amener au premier plan"), this); move = new QAction(QIcon(":/ico/select.png"), tr("D\351placer un objet"), this); add_line = new QAction(QIcon(":/ico/line.png"), tr("Ajouter une ligne"), this); + add_rectangle = new QAction(QIcon(":/ico/rectangle.png"), tr("Ajouter un rectangle"), this); add_ellipse = new QAction(QIcon(":/ico/ellipse.png"), tr("Ajouter une ellipse"), this); add_circle = new QAction(QIcon(":/ico/circle.png"), tr("Ajouter un cercle"), this); add_polygon = new QAction(QIcon(":/ico/polygon.png"), tr("Ajouter un polygone"), this); @@ -91,6 +101,17 @@ void QETElementEditor::setupActions() { add_terminal = new QAction(QIcon(":/ico/terminal.png"), tr("Ajouter une borne"), this); add_textfield = new QAction(QIcon(":/ico/textfield.png"), tr("Ajouter un champ de texte"), this); + QString add_status_tip = tr("Maintenez la touche Shift enfonc\351e pour effectuer plusieurs ajouts d'affil\351e"); + add_line -> setStatusTip(add_status_tip); + add_rectangle -> setStatusTip(add_status_tip); + add_ellipse -> setStatusTip(add_status_tip); + add_circle -> setStatusTip(add_status_tip); + add_text -> setStatusTip(add_status_tip); + add_arc -> setStatusTip(add_status_tip); + add_terminal -> setStatusTip(add_status_tip); + add_textfield -> setStatusTip(add_status_tip); + add_polygon -> setStatusTip(tr("Utilisez le bouton droit de la souris pour poser le dernier point du polygone")); + undo = ce_scene -> undoStack().createUndoAction(this, tr("Annuler")); redo = ce_scene -> undoStack().createRedoAction(this, tr("Refaire")); undo -> setIcon(QIcon(":/ico/undo.png")); @@ -100,12 +121,18 @@ void QETElementEditor::setupActions() { new_element -> setShortcut(QKeySequence::New); open -> setShortcut(QKeySequence::Open); + open_file -> setShortcut(tr("Ctrl+Shift+O")); save -> setShortcut(QKeySequence::Save); + save_as_file -> setShortcut(tr("Ctrl+Shift+S")); reload -> setShortcut(Qt::Key_F5); quit -> setShortcut(QKeySequence(tr("Ctrl+Q"))); selectall -> setShortcut(QKeySequence::SelectAll); deselectall -> setShortcut(QKeySequence(tr("Ctrl+Shift+A"))); inv_select -> setShortcut(QKeySequence(tr("Ctrl+I"))); + cut -> setShortcut(QKeySequence::Cut); + copy -> setShortcut(QKeySequence::Copy); + paste -> setShortcut(QKeySequence::Paste); + paste_in_area -> setShortcut(tr("Ctrl+Shift+V")); edit_delete -> setShortcut(QKeySequence(tr("Suppr"))); zoom_in -> setShortcut(QKeySequence::ZoomIn); @@ -124,13 +151,19 @@ void QETElementEditor::setupActions() { connect(new_element, SIGNAL(triggered()), this, SLOT(slot_new())); connect(open, SIGNAL(triggered()), this, SLOT(slot_open())); + connect(open_file, SIGNAL(triggered()), this, SLOT(slot_openFile())); connect(save, SIGNAL(triggered()), this, SLOT(slot_save())); connect(save_as, SIGNAL(triggered()), this, SLOT(slot_saveAs())); + connect(save_as_file, SIGNAL(triggered()), this, SLOT(slot_saveAsFile())); connect(reload, SIGNAL(triggered()), this, SLOT(slot_reload())); connect(quit, SIGNAL(triggered()), this, SLOT(close())); connect(selectall, SIGNAL(triggered()), ce_scene, SLOT(slot_selectAll())); connect(deselectall, SIGNAL(triggered()), ce_scene, SLOT(slot_deselectAll())); connect(inv_select, SIGNAL(triggered()), ce_scene, SLOT(slot_invertSelection())); + connect(cut, SIGNAL(triggered()), ce_view, SLOT(cut())); + connect(copy, SIGNAL(triggered()), ce_view, SLOT(copy())); + connect(paste, SIGNAL(triggered()), ce_view, SLOT(paste())); + connect(paste_in_area, SIGNAL(triggered()), ce_view, SLOT(pasteInArea())); connect(zoom_in, SIGNAL(triggered()), ce_view, SLOT(zoomIn())); connect(zoom_out, SIGNAL(triggered()), ce_view, SLOT(zoomOut())); connect(zoom_fit, SIGNAL(triggered()), ce_view, SLOT(zoomFit())); @@ -145,6 +178,7 @@ void QETElementEditor::setupActions() { connect(edit_backward, SIGNAL(triggered()), ce_scene, SLOT(slot_sendBackward())); connect(move, SIGNAL(triggered()), ce_scene, SLOT(slot_move())); connect(add_line, SIGNAL(triggered()), ce_scene, SLOT(slot_addLine())); + connect(add_rectangle, SIGNAL(triggered()), ce_scene, SLOT(slot_addRectangle())); connect(add_ellipse, SIGNAL(triggered()), ce_scene, SLOT(slot_addEllipse())); connect(add_circle, SIGNAL(triggered()), ce_scene, SLOT(slot_addCircle())); connect(add_polygon, SIGNAL(triggered()), ce_scene, SLOT(slot_addPolygon())); @@ -155,6 +189,7 @@ void QETElementEditor::setupActions() { connect(move, SIGNAL(triggered()), this, SLOT(slot_setRubberBandToView())); connect(add_line, SIGNAL(triggered()), this, SLOT(slot_setNoDragToView())); + connect(add_rectangle, SIGNAL(triggered()), this, SLOT(slot_setNoDragToView())); connect(add_ellipse, SIGNAL(triggered()), this, SLOT(slot_setNoDragToView())); connect(add_circle, SIGNAL(triggered()), this, SLOT(slot_setNoDragToView())); connect(add_polygon, SIGNAL(triggered()), this, SLOT(slot_setNoDragToView())); @@ -167,6 +202,7 @@ void QETElementEditor::setupActions() { move -> setCheckable(true); add_line -> setCheckable(true); + add_rectangle -> setCheckable(true); add_ellipse -> setCheckable(true); add_circle -> setCheckable(true); add_polygon -> setCheckable(true); @@ -178,16 +214,17 @@ void QETElementEditor::setupActions() { parts = new QActionGroup(this); parts -> addAction(move); parts -> addAction(add_line); + parts -> addAction(add_rectangle); parts -> addAction(add_ellipse); parts -> addAction(add_circle); parts -> addAction(add_polygon); - parts -> addAction(add_text); parts -> addAction(add_arc); + parts -> addAction(add_text); parts -> addAction(add_textfield); parts -> addAction(add_terminal); parts -> setExclusive(true); - parts_toolbar = new QToolBar(tr("Parties"), this); + parts_toolbar = new QToolBar(tr("Parties", "toolbar title"), this); parts_toolbar -> setObjectName("parts"); foreach (QAction *action, parts -> actions()) parts_toolbar -> addAction(action); move -> setChecked(true); @@ -199,13 +236,13 @@ void QETElementEditor::setupActions() { parts_toolbar -> addAction(xml_preview); */ - main_toolbar = new QToolBar(tr("Outils"), this); + main_toolbar = new QToolBar(tr("Outils", "toolbar title"), this); main_toolbar -> setObjectName("main_toolbar"); - view_toolbar = new QToolBar(tr("Affichage"), this); + view_toolbar = new QToolBar(tr("Affichage", "toolbar title"), this); view_toolbar -> setObjectName("display"); - element_toolbar = new QToolBar(tr("\311l\351ment"), this); + element_toolbar = new QToolBar(tr("\311l\351ment", "toolbar title"), this); element_toolbar -> setObjectName("element_toolbar"); - depth_toolbar = new QToolBar(tr("Profondeur"), this); + depth_toolbar = new QToolBar(tr("Profondeur", "toolbar title"), this); depth_toolbar -> setObjectName("depth_toolbar"); main_toolbar -> addAction(new_element); @@ -238,6 +275,7 @@ void QETElementEditor::setupActions() { connect(ce_scene, SIGNAL(selectionChanged()), this, SLOT(slot_updateInformations())); connect(ce_scene, SIGNAL(selectionChanged()), this, SLOT(slot_updateMenus())); + connect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(slot_updateMenus())); connect(&(ce_scene -> undoStack()), SIGNAL(cleanChanged(bool)), this, SLOT(slot_updateMenus())); connect(&(ce_scene -> undoStack()), SIGNAL(cleanChanged(bool)), this, SLOT(slot_updateTitle())); connect(&(ce_scene -> undoStack()), SIGNAL(indexChanged(int)), this, SLOT(slot_updatePartsList())); @@ -261,10 +299,12 @@ void QETElementEditor::setupMenus() { file_menu -> addAction(new_element); file_menu -> addAction(open); + file_menu -> addAction(open_file); file_menu -> addMenu(QETApp::elementsRecentFiles() -> menu()); connect(QETApp::elementsRecentFiles(), SIGNAL(fileOpeningRequested(const QString &)), this, SLOT(openRecentFile(const QString &))); file_menu -> addAction(save); file_menu -> addAction(save_as); + file_menu -> addAction(save_as_file); file_menu -> addSeparator(); file_menu -> addAction(reload); file_menu -> addSeparator(); @@ -277,6 +317,11 @@ void QETElementEditor::setupMenus() { edit_menu -> addAction(deselectall); edit_menu -> addAction(inv_select); edit_menu -> addSeparator(); + edit_menu -> addAction(cut); + edit_menu -> addAction(copy); + edit_menu -> addAction(paste); + edit_menu -> addAction(paste_in_area); + edit_menu -> addSeparator(); edit_menu -> addAction(edit_delete); edit_menu -> addSeparator(); edit_menu -> addAction(edit_names); @@ -309,6 +354,13 @@ void QETElementEditor::setupMenus() { */ void QETElementEditor::slot_updateMenus() { bool selected_items = !ce_scene -> selectedItems().isEmpty(); + bool clipboard_elmt = ElementScene::clipboardMayContainElement(); + + deselectall -> setEnabled(selected_items); + cut -> setEnabled(selected_items); + copy -> setEnabled(selected_items); + paste -> setEnabled(clipboard_elmt); + paste_in_area -> setEnabled(clipboard_elmt); edit_delete -> setEnabled(selected_items); edit_forward -> setEnabled(selected_items); edit_raise -> setEnabled(selected_items); @@ -323,10 +375,10 @@ void QETElementEditor::slot_updateMenus() { void QETElementEditor::slot_updateTitle() { QString title = min_title; title += " - " + ce_scene -> names().name() + " "; - if (_filename != QString()) { - if (!ce_scene -> undoStack().isClean()) title += tr("[Modifi\351]"); - if (isReadOnly()) title += tr(" [lecture seule]"); + if (!filename_.isEmpty() || !location_.isNull()) { + if (!ce_scene -> undoStack().isClean()) title += tr("[Modifi\351]", "window title tag"); } + if (isReadOnly()) title += tr(" [lecture seule]", "window title tag"); setWindowTitle(title); } @@ -344,19 +396,25 @@ void QETElementEditor::setupInterface() { // widget par defaut dans le QDockWidget default_informations = new QLabel(); + // ScrollArea pour accueillir un widget d'edition (change a la volee) + tools_dock_scroll_area_ = new QScrollArea(); + + // Pile de widgets pour accueillir les deux widgets precedents + tools_dock_stack_ = new QStackedWidget(); + tools_dock_stack_ -> insertWidget(0, default_informations); + tools_dock_stack_ -> insertWidget(1, tools_dock_scroll_area_); + // panel sur le cote pour editer les parties - tools_dock = new QDockWidget(tr("Informations"), this); + tools_dock = new QDockWidget(tr("Informations", "dock title"), this); tools_dock -> setObjectName("informations"); tools_dock -> setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); tools_dock -> setFeatures(QDockWidget::AllDockWidgetFeatures); - tools_dock -> setMinimumWidth(290); + tools_dock -> setMinimumWidth(380); addDockWidget(Qt::RightDockWidgetArea, tools_dock); - QWidget *info_widget = new QWidget(); - info_widget -> setLayout(new QVBoxLayout(info_widget)); - tools_dock -> setWidget(info_widget); + tools_dock -> setWidget(tools_dock_stack_); // panel sur le cote pour les annulations - undo_dock = new QDockWidget(tr("Annulations"), this); + undo_dock = new QDockWidget(tr("Annulations", "dock title"), this); undo_dock -> setObjectName("undo"); undo_dock -> setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); undo_dock -> setFeatures(QDockWidget::AllDockWidgetFeatures); @@ -374,7 +432,7 @@ void QETElementEditor::setupInterface() { connect(ce_scene, SIGNAL(partsZValueChanged()), this, SLOT(slot_createPartsList())); connect(ce_scene, SIGNAL(selectionChanged()), this, SLOT(slot_updatePartsList())); connect(parts_list, SIGNAL(itemSelectionChanged()), this, SLOT(slot_updateSelectionFromPartsList())); - parts_dock = new QDockWidget(tr("Parties"), this); + parts_dock = new QDockWidget(tr("Parties", "dock title"), this); parts_dock -> setObjectName("parts_list"); parts_dock -> setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); parts_dock -> setFeatures(QDockWidget::AllDockWidgetFeatures); @@ -386,7 +444,7 @@ void QETElementEditor::setupInterface() { slot_createPartsList(); // barre d'etat - statusBar() -> showMessage(tr("\311diteur d'\351l\351ments")); + statusBar() -> showMessage(tr("\311diteur d'\351l\351ments", "status bar message")); } /** @@ -428,31 +486,26 @@ void QETElementEditor::slot_updateInformations() { } } - // recupere le layout - QLayout *layout = tools_dock -> widget() -> layout(); - - // enleve les widgets deja presents - QLayoutItem *qli; - while ((qli = layout -> takeAt(0)) != 0) { - if (QWidget *w = qli -> widget()) { - layout -> removeWidget(w); - w -> setParent(0); - w -> hide(); - } + if (QWidget *previous_widget = tools_dock_scroll_area_ -> takeWidget()) { + previous_widget -> setParent(0); + previous_widget -> hide(); } + if (selected_parts.size() == 1) { // recupere le premier CustomElementPart et en ajoute le widget d'edition QWidget *edit_widget = selected_parts.first() -> elementInformations(); - layout -> addWidget(edit_widget); - edit_widget -> show(); + tools_dock_scroll_area_ -> setWidget(edit_widget); + tools_dock_stack_ -> setCurrentIndex(1); } else { default_informations -> setText( - selected_parts.size() ? - QString("%1").arg(selected_parts.size()) + tr(" parties s\351lectionn\351es.") : - tr("Aucune partie s\351lectionn\351e.") + tr( + "%n partie(s) s\351lectionn\351e(s).", + "", + selected_parts.size() + ) ); - layout -> addWidget(default_informations); - default_informations -> show(); + default_informations -> setAlignment(Qt::AlignHCenter | Qt::AlignTop); + tools_dock_stack_ -> setCurrentIndex(0); } } @@ -468,6 +521,33 @@ void QETElementEditor::xmlPreview() { ); } +/** + Verifie si l'ensemble des parties graphiques consituant l'element en cours + d'edition est bien contenu dans le rectangle representant les limites de + l'element. Si ce n'est pas le cas, l'utilisateur en est informe. + @return true si la situation est ok, false sinon +*/ +bool QETElementEditor::checkElementSize() { + if (ce_scene -> borderContainsEveryParts()) { + return(true); + } else { + QMessageBox::warning( + this, + tr("Dimensions de l'\351l\351ment", "messagebox title"), + tr( + "Attention : certaines parties graphiques (textes, cercles, " + "lignes...) semblent d\351border du cadre de l'\351l\351ment. Cela" + " risque de g\351n\351rer des bugs graphiques lors de leur " + "manipulation sur un sch\351ma. Vous pouvez corriger cela soit " + "en d\351pla\347ant ces parties, soit en vous rendant dans " + "\311dition > \311diter la taille et le point de saisie." + , "messagebox content" + ) + ); + return(false); + } +} + /** Charge un fichier @param filepath Chemin du fichier a charger @@ -480,7 +560,7 @@ void QETElementEditor::fromFile(const QString &filepath) { QFileInfo infos_file(filepath); if (!infos_file.exists() || !infos_file.isFile()) { state = false; - error_message = tr("Le fichier ") + filepath + tr(" n'existe pas."); + error_message = QString(tr("Le fichier %1 n'existe pas.", "message box content")).arg(filepath); } // le fichier doit etre lisible @@ -488,7 +568,7 @@ void QETElementEditor::fromFile(const QString &filepath) { if (state) { if (!file.open(QIODevice::ReadOnly)) { state = false; - error_message = tr("Impossible d'ouvrir le fichier ") + filepath + "."; + error_message = QString(tr("Impossible d'ouvrir le fichier %1.", "message box content")).arg(filepath); } } @@ -497,13 +577,13 @@ void QETElementEditor::fromFile(const QString &filepath) { if (state) { if (!document_xml.setContent(&file)) { state = false; - error_message = tr("Ce fichier n'est pas un document XML valide"); + error_message = tr("Ce fichier n'est pas un document XML valide", "message box content"); } file.close(); } if (!state) { - QMessageBox::critical(this, tr("Erreur"), error_message); + QMessageBox::critical(this, tr("Erreur", "toolbar title"), error_message); return; } @@ -515,8 +595,8 @@ void QETElementEditor::fromFile(const QString &filepath) { if (!infos_file.isWritable()) { QMessageBox::warning( this, - tr("\311dition en lecture seule"), - tr("Vous n'avez pas les privil\350ges n\351cessaires pour modifier cet \351lement. Il sera donc ouvert en lecture seule.") + tr("\311dition en lecture seule", "message box title"), + tr("Vous n'avez pas les privil\350ges n\351cessaires pour modifier cet \351lement. Il sera donc ouvert en lecture seule.", "message box content") ); setReadOnly(true); } @@ -527,7 +607,6 @@ void QETElementEditor::fromFile(const QString &filepath) { slot_updateMenus(); } - /** Enregistre l'element vers un fichier @param fn Chemin du fichier a enregistrer @@ -536,7 +615,7 @@ void QETElementEditor::fromFile(const QString &filepath) { bool QETElementEditor::toFile(const QString &fn) { QFile file(fn); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { - QMessageBox::warning(this, tr("Erreur"), tr("Impossible d'ecrire dans ce fichier")); + QMessageBox::warning(this, tr("Erreur", "message box title"), tr("Impossible d'\351crire dans ce fichier", "message box content")); return(false); } QTextStream out(&file); @@ -546,6 +625,45 @@ bool QETElementEditor::toFile(const QString &fn) { return(true); } +/** + Enregistre l'element vers un emplacement + @param location Emplacement de l'element a enregistrer + @return true en cas de reussite, false sinon +*/ +bool QETElementEditor::toLocation(const ElementsLocation &location) { + ElementsCollectionItem *item = QETApp::collectionItem(location); + ElementDefinition *element; + if (item) { + // l'element existe deja + element = qobject_cast(item); + } else { + // l'element n'existe pas encore, on demande sa creation + element = QETApp::createElement(location); + } + + if (!element) { + QMessageBox::critical( + this, + tr("Erreur", "message box title"), + tr("Impossible d'atteindre l'\351l\351ment", "message box content") + ); + return(false); + } + + // enregistre l'element + element -> setXml(ce_scene -> toXml().documentElement()); + if (!element -> write()) { + QMessageBox::critical( + this, + tr("Erreur", "message box title"), + tr("Impossible d'enregistrer l'\351l\351ment", "message box content") + ); + return(false); + } + + return(true); +} + /** specifie si l'editeur d'element doit etre en mode lecture seule @param ro true pour activer le mode lecture seule, false pour le desactiver @@ -559,6 +677,9 @@ void QETElementEditor::setReadOnly(bool ro) { ce_view -> setInteractive(!ro); // active / desactive l'edition de la taille, du hotspot, des noms et des orientations + cut -> setEnabled(!ro); + copy -> setEnabled(!ro); + paste -> setEnabled(!ro); selectall -> setEnabled(!ro); deselectall -> setEnabled(!ro); inv_select -> setEnabled(!ro); @@ -587,15 +708,33 @@ void QETElementEditor::slot_new() { } /** - Demande un fichier a l'utilisateur et ouvre ce fichier + Ouvre un element */ void QETElementEditor::slot_open() { + // demande le chemin virtuel de l'element a ouvrir a l'utilisateur + ElementsLocation location = ElementDialog::getOpenElementLocation(); + if (location.isNull()) return; + QETElementEditor *cee = new QETElementEditor(); + cee -> fromLocation(location); + cee -> show(); +} + +/** + Ouvre un fichier + Demande un fichier a l'utilisateur et ouvre ce fichier +*/ +void QETElementEditor::slot_openFile() { // demande un nom de fichier a ouvrir a l'utilisateur QString user_filename = QFileDialog::getOpenFileName( this, - tr("Ouvrir un fichier"), - _filename.isEmpty() ? QETApp::customElementsDir() : QDir(_filename).absolutePath(), - tr("\311l\351ments QElectroTech (*.elmt);;Fichiers XML (*.xml);;Tous les fichiers (*)") + tr("Ouvrir un fichier", "dialog title"), + filename_.isEmpty() ? QETApp::customElementsDir() : QDir(filename_).absolutePath(), + tr( + "\311l\351ments QElectroTech (*.elmt);;" + "Fichiers XML (*.xml);;" + "Tous les fichiers (*)", + "filetypes allowed when opening an element file" + ) ); openElement(user_filename); } @@ -628,16 +767,13 @@ void QETElementEditor::openElement(const QString &filepath) { Recharge l'element edite */ void QETElementEditor::slot_reload() { - // impossible de recharger un element non enregistre - if (_filename.isEmpty()) return; - // s'il ya des modifications, on demande a l'utilisateur s'il est certain // de vouloir recharger if (!ce_scene -> undoStack().isClean()) { QMessageBox::StandardButton answer = QMessageBox::question( this, - tr("Recharger l'\351l\351ment"), - tr("Vous avez efffectu\351 des modifications sur cet \351l\351ment. Si vous le rechargez, ces modifications seront perdues. Voulez-vous vraiment recharger l'\351l\351ment ?"), + tr("Recharger l'\351l\351ment", "dialog title"), + tr("Vous avez efffectu\351 des modifications sur cet \351l\351ment. Si vous le rechargez, ces modifications seront perdues. Voulez-vous vraiment recharger l'\351l\351ment ?", "dialog content"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Cancel ); @@ -645,8 +781,19 @@ void QETElementEditor::slot_reload() { } // recharge l'element - ce_scene -> reset(); - fromFile(_filename); + if (opened_from_file) { + // l'element a ete ouvert a partir d'un chemin de fichier + ce_scene -> reset(); + fromFile(filename_); + } else { + // l'element a ete ouvert a partir d'un emplacement (ElementsLocation) + // il peut s'agir aussi bien d'un fichier que d'un element XML + if (ElementsCollectionItem *item = QETApp::collectionItem(location_)) { + item -> reload(); + ce_scene -> reset(); + fromLocation(location_); + } + } } /** @@ -656,24 +803,57 @@ void QETElementEditor::slot_reload() { @see slot_saveAs() */ bool QETElementEditor::slot_save() { + // verification avant d'enregistrer le fichier + checkElementSize(); + // si on ne connait pas le nom du fichier en cours, enregistrer revient a enregistrer sous - if (_filename.isEmpty()) return(slot_saveAs()); - // sinon on enregistre dans le nom de fichier connu - bool result_save = toFile(_filename); - if (result_save) ce_scene -> undoStack().setClean(); + if (opened_from_file) { + if (filename_.isEmpty()) return(slot_saveAsFile()); + // sinon on enregistre dans le nom de fichier connu + bool result_save = toFile(filename_); + if (result_save) ce_scene -> undoStack().setClean(); + return(result_save); + } else { + if (location_.isNull()) return(slot_saveAs()); + // sinon on enregistre a l'emplacement connu + bool result_save = toLocation(location_); + if (result_save) ce_scene -> undoStack().setClean(); + return(result_save); + } +} + +/** + Demande une localisation a l'utilisateur et enregistre l'element +*/ +bool QETElementEditor::slot_saveAs() { + // demande une localisation a l'utilisateur + ElementsLocation location = ElementDialog::getSaveElementLocation(); + if (location.isNull()) return(false); + + // tente l'enregistrement + bool result_save = toLocation(location); + if (result_save) { + setLocation(location); + ce_scene -> undoStack().setClean(); + } + + // retourne un booleen representatif de la reussite de l'enregistrement return(result_save); } /** Demande un nom de fichier a l'utilisateur et enregistre l'element */ -bool QETElementEditor::slot_saveAs() { +bool QETElementEditor::slot_saveAsFile() { // demande un nom de fichier a l'utilisateur pour enregistrer l'element QString fn = QFileDialog::getSaveFileName( this, - tr("Enregistrer sous"), - _filename.isEmpty() ? QETApp::customElementsDir() : QDir(_filename).absolutePath(), - tr("\311l\351ments QElectroTech (*.elmt)") + tr("Enregistrer sous", "dialog title"), + filename_.isEmpty() ? QETApp::customElementsDir() : QDir(filename_).absolutePath(), + tr( + "\311l\351ments QElectroTech (*.elmt)", + "filetypes allowed when saving an element file" + ) ); // si aucun nom n'est entre, renvoie faux. if (fn.isEmpty()) return(false); @@ -702,9 +882,14 @@ bool QETElementEditor::canClose() { // demande d'abord a l'utilisateur s'il veut enregistrer l'element en cours QMessageBox::StandardButton answer = QMessageBox::question( this, - tr("Enregistrer l'\351l\351ment en cours ?"), - tr("Voulez-vous enregistrer l'\351l\351ment ") + ce_scene -> names().name() + tr(" ?"), - QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel, + tr("Enregistrer l'\351l\351ment en cours ?", "dialog title"), + QString( + tr( + "Voulez-vous enregistrer l'\351l\351ment %1 ?", + "dialog content - %1 is an element name" + ) + ).arg(ce_scene -> names().name()), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Cancel ); bool result; @@ -787,6 +972,7 @@ void QETElementEditor::slot_updateSelectionFromPartsList() { parts_list -> blockSignals(false); ce_scene -> blockSignals(false); slot_updateInformations(); + slot_updateMenus(); } /// Lit les parametres de l'editeur d'element @@ -808,3 +994,70 @@ void QETElementEditor::writeSettings() { settings.setValue("elementeditor/geometry", saveGeometry()); settings.setValue("elementeditor/state", saveState()); } + +/** + @return les decalages horizontaux et verticaux (sous la forme d'un point) a + utiliser lors d'un copier/coller avec decalage. +*/ +QPointF QETElementEditor::pasteOffset() { + QPointF paste_offset(5.0, 0.0); + return(paste_offset); +} + +/** + @return Le type de mouvement a effectuer lors d'un copier/coller avec + decalage. +*/ +QET::OrientedMovement QETElementEditor::pasteMovement() { + return(QET::ToEast); +} + +/** + @param location Emplacement de l'element a editer +*/ +void QETElementEditor::fromLocation(const ElementsLocation &location) { + + // l'element doit exister + ElementsCollectionItem *item = QETApp::collectionItem(location); + ElementDefinition *element = 0; + if (!item) { + QMessageBox::critical( + this, + tr("\311l\351ment inexistant.", "message box title"), + tr("L'\351l\351ment n'existe pas.", "message box content") + ); + return; + } + + if (!item -> isElement() || !(element = qobject_cast(item)) || element -> isNull()) { + QMessageBox::critical( + this, + tr("\311l\351ment inexistant.", "message box title"), + tr("Le chemin virtuel choisi ne correspond pas \340 un \351l\351ment.", "message box content") + ); + return; + } + + // le fichier doit etre un document XML + QDomDocument document_xml; + QDomNode node = document_xml.importNode(element -> xml(), true); + document_xml.appendChild(node); + + // chargement de l'element + ce_scene -> fromXml(document_xml); + slot_createPartsList(); + + // gestion de la lecture seule + if (!element -> isWritable()) { + QMessageBox::warning( + this, + tr("\311dition en lecture seule", "message box title"), + tr("Vous n'avez pas les privil\350ges n\351cessaires pour modifier cet \351lement. Il sera donc ouvert en lecture seule.", "message box content") + ); + setReadOnly(true); + } + + // memorise le fichier + setLocation(location); + slot_updateMenus(); +} diff --git a/sources/editor/qetelementeditor.h b/sources/editor/qetelementeditor.h index e54b5045c..982e2b531 100644 --- a/sources/editor/qetelementeditor.h +++ b/sources/editor/qetelementeditor.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -18,8 +18,10 @@ #ifndef CUSTOM_ELEMENT_EDITOR_H #define CUSTOM_ELEMENT_EDITOR_H #include +#include "qet.h" #include "elementscene.h" #include "orientationset.h" +#include "elementslocation.h" class ElementView; /** Cette classe represente un editeur d'element. Elle permet a l'utilisateur @@ -48,6 +50,12 @@ class QETElementEditor : public QMainWindow { ElementScene *ce_scene; /// container pour les widgets d'edition des parties QDockWidget *tools_dock; + /// Pile de widgets pour tools_dock + QStackedWidget *tools_dock_stack_; + /// label affiche lors de la selection de plusieurs elements + QLabel *default_informations; + /// ScrollArea pour le DockWidget affichant des infos sur la partie selectionnee + QScrollArea *tools_dock_scroll_area_; /// container pour la liste des annulations QDockWidget *undo_dock; /// Container pour la liste des parties @@ -55,9 +63,10 @@ class QETElementEditor : public QMainWindow { /// Liste des parties QListWidget *parts_list; /// actions du menu fichier - QAction *new_element, *open, *save, *save_as, *reload, *quit; + QAction *new_element, *open, *open_file, *save, *save_as, *save_as_file, *reload, *quit; /// actions du menu edition QAction *selectall, *deselectall, *inv_select; + QAction *cut, *copy, *paste, *paste_in_area; QAction *undo, *redo; QAction *zoom_in, *zoom_out, *zoom_fit, *zoom_reset; QAction *edit_delete, *edit_size_hs, *edit_names, *edit_ori; @@ -66,14 +75,16 @@ class QETElementEditor : public QMainWindow { QToolBar *parts_toolbar, *main_toolbar, *view_toolbar, *depth_toolbar, *element_toolbar; /// actions de la barre d'outils QActionGroup *parts; - QAction *move, *add_line, *add_circle, *add_ellipse, *add_polygon, *add_text; + QAction *move, *add_line, *add_circle, *add_rectangle, *add_ellipse, *add_polygon, *add_text; QAction *add_arc, *add_terminal, *add_textfield; - /// label affiche lors de la selection de plusieurs elements - QLabel *default_informations; /// titre minimal QString min_title; - /// Nom de fichier - QString _filename; + /// Nom de fichier de l'element edite + QString filename_; + /// Emplacement de l'element edite + ElementsLocation location_; + /// booleen indiquant si l'element en cours d'edition provient d'un fichier ou d'un emplacement + bool opened_from_file; // methodes public: @@ -84,15 +95,21 @@ class QETElementEditor : public QMainWindow { void setNames(const NamesList &); void setOrientations(const OrientationSet &orientation_set); OrientationSet orientations() const; + void setLocation(const ElementsLocation &); + ElementsLocation location() const; void setFileName(const QString &); QString fileName() const; void setReadOnly(bool); bool isReadOnly() const; void fromFile(const QString &); + void fromLocation(const ElementsLocation &); bool toFile(const QString &); + bool toLocation(const ElementsLocation &); ElementScene *elementScene() const; void readSettings(); void writeSettings(); + static QPointF pasteOffset(); + static QET::OrientedMovement pasteMovement(); protected: void closeEvent(QCloseEvent *); @@ -106,11 +123,13 @@ class QETElementEditor : public QMainWindow { public slots: void slot_new(); void slot_open(); + void slot_openFile(); void openRecentFile(const QString &); void openElement(const QString &); void slot_reload(); bool slot_save(); bool slot_saveAs(); + bool slot_saveAsFile(); void slot_setRubberBandToView(); void slot_setNoDragToView(); void slot_setNormalMode(); @@ -121,6 +140,7 @@ class QETElementEditor : public QMainWindow { void slot_updatePartsList(); void slot_updateSelectionFromPartsList(); void xmlPreview(); + bool checkElementSize(); }; /** @@ -179,18 +199,35 @@ inline OrientationSet QETElementEditor::orientations() const { } /** - @param fn Le nouveau nom de fichier de l'element edite + @param el Le nouvel emplacement de l'element edite */ -inline void QETElementEditor::setFileName(const QString &fn) { - _filename = fn; +inline void QETElementEditor::setLocation(const ElementsLocation &el) { + location_ = el; + opened_from_file = false; slot_updateTitle(); } /** - @return le nomde fichier de l'element edite + @return l'emplacement de l'element edite +*/ +inline ElementsLocation QETElementEditor::location() const { + return(location_); +} + +/** + @param fn Le nouveau nom de fichier de l'element edite +*/ +inline void QETElementEditor::setFileName(const QString &fn) { + filename_ = fn; + opened_from_file = true; + slot_updateTitle(); +} + +/** + @return le nom de fichier de l'element edite */ inline QString QETElementEditor::fileName() const { - return(_filename); + return(filename_); } /** diff --git a/sources/editor/rectangleeditor.cpp b/sources/editor/rectangleeditor.cpp new file mode 100644 index 000000000..2aac9adfb --- /dev/null +++ b/sources/editor/rectangleeditor.cpp @@ -0,0 +1,108 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#include "rectangleeditor.h" +#include "partrectangle.h" + +/** + Constructeur + @param editor L'editeur d'element concerne + @param rect Le rectangle a editer + @param parent le Widget parent +*/ +RectangleEditor::RectangleEditor(QETElementEditor *editor, PartRectangle *rect, QWidget *parent) : ElementItemEditor(editor, parent) { + + part = rect; + + x = new QLineEdit(); + y = new QLineEdit(); + w = new QLineEdit(); + h = new QLineEdit(); + + x -> setValidator(new QDoubleValidator(x)); + y -> setValidator(new QDoubleValidator(y)); + w -> setValidator(new QDoubleValidator(w)); + h -> setValidator(new QDoubleValidator(h)); + + QGridLayout *grid = new QGridLayout(this); + grid -> addWidget(new QLabel(tr("Coin sup\351rieur gauche\240: ")), 0, 0); + grid -> addWidget(new QLabel("x"), 1, 0); + grid -> addWidget(x, 1, 1); + grid -> addWidget(new QLabel("y"), 1, 2); + grid -> addWidget(y, 1, 3); + grid -> addWidget(new QLabel(tr("Dimensions\240: ")), 2, 0); + grid -> addWidget(new QLabel(tr("Largeur\240:")), 3, 0); + grid -> addWidget(w, 3, 1); + grid -> addWidget(new QLabel(tr("Hauteur\240:")), 4, 0); + grid -> addWidget(h, 4, 1); + + activeConnections(true); + updateForm(); +} + +/// Destructeur +RectangleEditor::~RectangleEditor() { +} + +/** + Met a jour le rectangle a partir des donnees du formulaire +*/ +void RectangleEditor::updateRectangle() { + part -> setProperty("x", x -> text().toDouble()); + part -> setProperty("y", y -> text().toDouble()); + part -> setProperty("width", w -> text().toDouble()); + part -> setProperty("height", h -> text().toDouble()); +} + +/// Met a jour l'abscisse du coin superieur gauche du rectangle et cree un objet d'annulation +void RectangleEditor::updateRectangleX() { addChangePartCommand(tr("abscisse"), part, "x", x -> text().toDouble()); } +/// Met a jour l'ordonnee du coin superieur gauche du rectangle et cree un objet d'annulation +void RectangleEditor::updateRectangleY() { addChangePartCommand(tr("ordonn\351e"), part, "y", y -> text().toDouble()); } +/// Met a jour la largeur du rectangle et cree un objet d'annulation +void RectangleEditor::updateRectangleW() { addChangePartCommand(tr("largeur"), part, "width", w -> text().toDouble()); } +/// Met a jour la hauteur du rectangle et cree un objet d'annulation +void RectangleEditor::updateRectangleH() { addChangePartCommand(tr("hauteur"), part, "height", h -> text().toDouble()); } + +/** + Met a jour le formulaire d'edition +*/ +void RectangleEditor::updateForm() { + activeConnections(false); + x -> setText(part -> property("x").toString()); + y -> setText(part -> property("y").toString()); + w -> setText(part -> property("width").toString()); + h -> setText(part -> property("height").toString()); + activeConnections(true); +} + +/** + Active ou desactive les connexionx signaux/slots entre les widgets internes. + @param active true pour activer les connexions, false pour les desactiver +*/ +void RectangleEditor::activeConnections(bool active) { + if (active) { + connect(x, SIGNAL(editingFinished()), this, SLOT(updateRectangleX())); + connect(y, SIGNAL(editingFinished()), this, SLOT(updateRectangleY())); + connect(w, SIGNAL(editingFinished()), this, SLOT(updateRectangleW())); + connect(h, SIGNAL(editingFinished()), this, SLOT(updateRectangleH())); + } else { + disconnect(x, SIGNAL(editingFinished()), this, SLOT(updateRectangleX())); + disconnect(y, SIGNAL(editingFinished()), this, SLOT(updateRectangleY())); + disconnect(w, SIGNAL(editingFinished()), this, SLOT(updateRectangleW())); + disconnect(h, SIGNAL(editingFinished()), this, SLOT(updateRectangleH())); + } +} diff --git a/sources/editor/rectangleeditor.h b/sources/editor/rectangleeditor.h new file mode 100644 index 000000000..220e2ba76 --- /dev/null +++ b/sources/editor/rectangleeditor.h @@ -0,0 +1,53 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef RECTANGLE_EDITOR_H +#define RECTANGLE_EDITOR_H +#include +#include "elementitemeditor.h" +class PartRectangle; +/** + Cette classe represente le widget d'edition d'un rectangle dans l'editeur + d'element. +*/ +class RectangleEditor : public ElementItemEditor { + Q_OBJECT + //constructeurs, destructeur + public: + RectangleEditor(QETElementEditor *, PartRectangle *, QWidget * = 0); + ~RectangleEditor(); + private: + RectangleEditor(const RectangleEditor &); + + // attributs + private: + PartRectangle *part; + QLineEdit *x, *y, *w, *h; + + // methodes + public slots: + void updateRectangle(); + void updateRectangleX(); + void updateRectangleY(); + void updateRectangleW(); + void updateRectangleH(); + void updateForm(); + + private: + void activeConnections(bool); +}; +#endif diff --git a/sources/editor/styleeditor.cpp b/sources/editor/styleeditor.cpp index 5aeed0762..379821df1 100644 --- a/sources/editor/styleeditor.cpp +++ b/sources/editor/styleeditor.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -27,26 +27,26 @@ StyleEditor::StyleEditor(QETElementEditor *editor, CustomElementGraphicPart *p, QWidget *parent) : ElementItemEditor(editor, parent), part(p) { // couleur color = new QButtonGroup(this); - color -> addButton(black_color = new QRadioButton(tr("Noir")), CustomElementGraphicPart::BlackColor); - color -> addButton(white_color = new QRadioButton(tr("Blanc")), CustomElementGraphicPart::WhiteColor); + color -> addButton(black_color = new QRadioButton(tr("Noir", "element part color")), CustomElementGraphicPart::BlackColor); + color -> addButton(white_color = new QRadioButton(tr("Blanc", "element part color")), CustomElementGraphicPart::WhiteColor); // style style = new QButtonGroup(this); - style -> addButton(normal_style = new QRadioButton(tr("Normal")), CustomElementGraphicPart::NormalStyle); - style -> addButton(dashed_style = new QRadioButton(tr("Pointill\351")), CustomElementGraphicPart::DashedStyle); + style -> addButton(normal_style = new QRadioButton(tr("Normal", "element part line style")), CustomElementGraphicPart::NormalStyle); + style -> addButton(dashed_style = new QRadioButton(tr("Pointill\351", "element part line style")), CustomElementGraphicPart::DashedStyle); style -> button(part -> lineStyle()) -> setChecked(true); // epaisseur weight = new QButtonGroup(this); - weight -> addButton(none_weight = new QRadioButton(tr("Nulle")), CustomElementGraphicPart::NoneWeight); - weight -> addButton(thin_weight = new QRadioButton(tr("Fine")), CustomElementGraphicPart::ThinWeight); - weight -> addButton(normal_weight = new QRadioButton(tr("Normale")), CustomElementGraphicPart::NormalWeight); + weight -> addButton(none_weight = new QRadioButton(tr("Nulle", "element part weight")), CustomElementGraphicPart::NoneWeight); + weight -> addButton(thin_weight = new QRadioButton(tr("Fine", "element part weight")), CustomElementGraphicPart::ThinWeight); + weight -> addButton(normal_weight = new QRadioButton(tr("Normale", "element part weight")), CustomElementGraphicPart::NormalWeight); // remplissage filling = new QButtonGroup(this); - filling -> addButton(no_filling = new QRadioButton(tr("Aucun")), CustomElementGraphicPart::NoneFilling ); - filling -> addButton(black_filling = new QRadioButton(tr("Noir")), CustomElementGraphicPart::BlackFilling); - filling -> addButton(white_filling = new QRadioButton(tr("Blanc")), CustomElementGraphicPart::WhiteFilling); + filling -> addButton(no_filling = new QRadioButton(tr("Aucun", "element part filling")), CustomElementGraphicPart::NoneFilling ); + filling -> addButton(black_filling = new QRadioButton(tr("Noir", "element part filling")), CustomElementGraphicPart::BlackFilling); + filling -> addButton(white_filling = new QRadioButton(tr("Blanc", "element part filling")), CustomElementGraphicPart::WhiteFilling); // antialiasing antialiasing = new QCheckBox(tr("Antialiasing")); diff --git a/sources/editor/styleeditor.h b/sources/editor/styleeditor.h index 5e21dce88..f95dc22e3 100644 --- a/sources/editor/styleeditor.h +++ b/sources/editor/styleeditor.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/editor/terminaleditor.cpp b/sources/editor/terminaleditor.cpp index 46bf47383..f9592f58c 100644 --- a/sources/editor/terminaleditor.cpp +++ b/sources/editor/terminaleditor.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/editor/terminaleditor.h b/sources/editor/terminaleditor.h index 458cdaa8f..a6b6036dd 100644 --- a/sources/editor/terminaleditor.h +++ b/sources/editor/terminaleditor.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/editor/texteditor.cpp b/sources/editor/texteditor.cpp index 25be32509..75a6442a7 100644 --- a/sources/editor/texteditor.cpp +++ b/sources/editor/texteditor.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -81,7 +81,7 @@ void TextEditor::updateTextX() { addChangePartCommand(tr("abscisse"), part, " /// Met a jour l'ordonnee de la position du texte et cree un objet d'annulation void TextEditor::updateTextY() { addChangePartCommand(tr("ordonn\351e"), part, "y", qle_y -> text().toDouble()); updateForm(); } /// Met a jour le texte et cree un objet d'annulation -void TextEditor::updateTextT() { addChangePartCommand(tr("texte"), part, "text", qle_text -> text()); } +void TextEditor::updateTextT() { addChangePartCommand(tr("contenu"), part, "text", qle_text -> text()); } /// Met a jour la taille du texte et cree un objet d'annulation void TextEditor::updateTextS() { addChangePartCommand(tr("taille"), part, "size", font_size -> value()); } diff --git a/sources/editor/texteditor.h b/sources/editor/texteditor.h index bffd3762b..3b862ff1d 100644 --- a/sources/editor/texteditor.h +++ b/sources/editor/texteditor.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/editor/textfieldeditor.cpp b/sources/editor/textfieldeditor.cpp index 95ad74d18..a25d352f5 100644 --- a/sources/editor/textfieldeditor.cpp +++ b/sources/editor/textfieldeditor.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -86,7 +86,7 @@ void TextFieldEditor::updateTextFieldX() { addChangePartCommand(tr("abscisse"), /// Met a jour l'ordonnee de la position du champ de texte et cree un objet d'annulation void TextFieldEditor::updateTextFieldY() { addChangePartCommand(tr("ordonn\351e"), part, "y", qle_y -> text().toDouble()); updateForm(); } /// Met a jour le texte du champ de texte et cree un objet d'annulation -void TextFieldEditor::updateTextFieldT() { addChangePartCommand(tr("texte"), part, "text", qle_text -> text()); } +void TextFieldEditor::updateTextFieldT() { addChangePartCommand(tr("contenu"), part, "text", qle_text -> text()); } /// Met a jour la taille du champ de texte et cree un objet d'annulation void TextFieldEditor::updateTextFieldS() { addChangePartCommand(tr("taille"), part, "size", font_size -> value()); } /// Met a jour la taille du champ de texte et cree un objet d'annulation diff --git a/sources/editor/textfieldeditor.h b/sources/editor/textfieldeditor.h index f10ce7d87..80689b817 100644 --- a/sources/editor/textfieldeditor.h +++ b/sources/editor/textfieldeditor.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2007 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/element.cpp b/sources/element.cpp index f74199947..d42b53d8d 100644 --- a/sources/element.cpp +++ b/sources/element.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -138,8 +138,8 @@ void Element::deselect() { @return La pixmap de l'element */ QPixmap Element::pixmap() { - if (apercu.isNull()) updatePixmap(); // on genere la pixmap si ce n'est deja fait - return(apercu); + if (preview.isNull()) updatePixmap(); // on genere la pixmap si ce n'est deja fait + return(preview); } /** @@ -220,10 +220,10 @@ void Element::drawSelection(QPainter *painter, const QStyleOptionGraphicsItem *) */ void Element::updatePixmap() { // Pixmap transparente faisant la taille de base de l'element - apercu = QPixmap(dimensions); - apercu.fill(QColor(255, 255, 255, 0)); + preview = QPixmap(dimensions); + preview.fill(QColor(255, 255, 255, 0)); // QPainter sur la pixmap, avec antialiasing - QPainter p(&apercu); + QPainter p(&preview); p.setRenderHint(QPainter::Antialiasing, true); p.setRenderHint(QPainter::SmoothPixmapTransform, true); // Translation de l'origine du repere de la pixmap @@ -416,9 +416,7 @@ QDomElement Element::toXml(QDomDocument &document, QHash &table QDomElement element = document.createElement("element"); // type - QString chemin_elmt = typeId(); - QString type_elmt = QETApp::symbolicPath(chemin_elmt); - element.setAttribute("type", type_elmt); + element.setAttribute("type", typeId()); // position, selection et orientation element.setAttribute("x", QString("%1").arg(pos().x())); diff --git a/sources/element.h b/sources/element.h index 096fc6bf3..c33bbcf72 100644 --- a/sources/element.h +++ b/sources/element.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -53,7 +53,7 @@ class Element : public QObject, public QGraphicsItem { private: QSize dimensions; QPoint hotspot_coord; - QPixmap apercu; + QPixmap preview; // methodes public: @@ -69,11 +69,11 @@ class Element : public QObject, public QGraphicsItem { /// @return la liste des conducteurs relies a cet element virtual QList conductors() const = 0; /// @return le nombre de bornes actuel de cet element - virtual int nbTerminals() const = 0; + virtual int terminalsCount() const = 0; /// @return le nombre de bornes minimum de cet element - virtual int nbTerminalsMin() const = 0; + virtual int minTerminalsCount() const = 0; /// @return le nombre de bornes maximum de cet element - virtual int nbTerminalsMax() const = 0; + virtual int maxTerminalsCount() const = 0; /** Dessine l'element */ @@ -81,7 +81,7 @@ class Element : public QObject, public QGraphicsItem { /// @return L'ID du type de l'element virtual QString typeId() const = 0; /// @return Le nom de l'element - virtual QString nom() const = 0; + virtual QString name() const = 0; Diagram *diagram() const; void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); diff --git a/sources/elementdefinition.cpp b/sources/elementdefinition.cpp new file mode 100644 index 000000000..12fe2952e --- /dev/null +++ b/sources/elementdefinition.cpp @@ -0,0 +1,461 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#include "elementdefinition.h" +#include "elementscollection.h" +#include "moveelementshandler.h" +#include "moveelementsdescription.h" + +/** + @return true si l'element est rattache a une collection d'elements + Un element appartenant a une collection a forcement un chemin virtuel. +*/ +bool ElementDefinition::hasParentCategory() { + return(parent_category_); +} + +/** + @return la categorie a laquelle appartient cet element +*/ +ElementsCategory *ElementDefinition::parentCategory() { + return(parent_category_); +} + +/** + @return la liste des categories parentes de cet item. +*/ +QList ElementDefinition::parentCategories() { + QList cat_list; + if (ElementsCategory *par_cat = parentCategory()) { + cat_list << par_cat -> parentCategories() << par_cat; + } + return(cat_list); +} + +/** + @return true si l'element est rattache a une collection d'elements + Un element appartenant a une collection a forcement un chemin virtuel. +*/ +bool ElementDefinition::hasParentCollection() { + return(parent_collection_); +} + +/** + @param other_item Autre item + @return true si other_item est parent (direct ou indirect) de cet item, false sinon +*/ +bool ElementDefinition::isChildOf(ElementsCollectionItem *other_item) { + // soit l'autre item est le parent direct de cet element + if (ElementsCategory *other_item_cat = other_item -> toCategory()) { + if (other_item_cat == parentCategory()) { + return(true); + } + } + + // soit il est un parent indirect, auquel cas, on peut demander a la categorie parente de repondre a la question + if (ElementsCategory *parent_cat = parentCategory()) { + return(parent_cat -> isChildOf(other_item)); + } + + // arrive ici, l'autre item n'est pas parent de cet item + return(false); +} + +/** + @return la collection d'element a laquelle appartient cet element +*/ +ElementsCollection *ElementDefinition::parentCollection() { + return(parent_collection_); +} + +/** + @return le projet auquel appartient cette categorie, si celle-ci + appartient a une collection. +*/ +QETProject *ElementDefinition::project() { + if (hasParentCollection()) { + return(parentCollection() -> project()); + } + return(0); +} + +/** + Ne fait rien ; le projet doit etre defini au niveau d'une collection +*/ +void ElementDefinition::setProject(QETProject *) { +} + +/** + @return le protocole utilise par la collection a laquelle appartient cet element +*/ +QString ElementDefinition::protocol() { + // il n'est pas possible d'avoir un protocole sans appartenir a une collection + if (!hasParentCollection()) return(QString()); + + return(parentCollection() -> protocol()); +} + +/** + Ne fait rien +*/ +void ElementDefinition::setProtocol(const QString &) { +} + +/** + @return le chemin virtuel complet de cet element (protocole + chemin) +*/ +QString ElementDefinition::fullVirtualPath() { + // il n'est pas possible d'avoir un chemin virtuel sans appartenir a une collection + if (!hasParentCollection()) return(QString()); + + return(protocol() + "://" + virtualPath()); +} + +/** + @return l'emplacement de l'element +*/ +ElementsLocation ElementDefinition::location() { + return(ElementsLocation(fullVirtualPath(), project())); +} + +/** + @return une liste vide - un element ne possede pas de categorie +*/ +QList ElementDefinition::categories() { + return(QList()); +} + +/** + @return toujours 0 - un element ne possede pas de categorie +*/ +ElementsCategory *ElementDefinition::category(const QString &) { + return(0); +} + +/** + @return toujours 0 - un element ne possede pas de categorie +*/ +ElementsCategory *ElementDefinition::createCategory(const QString &) { + return(0); +} + +/** + @return une liste contenant seulement cet element +*/ +QList ElementDefinition::elements() { + return(QList() << this); +} + +/** + @return cet element si path est vide, 0 sinon +*/ +ElementDefinition *ElementDefinition::element(const QString &path) { + if (path.isEmpty()) return(this); + return(0); +} + +/** + @return toujours 0 - un element n'en cree pas d'autre +*/ +ElementDefinition *ElementDefinition::createElement(const QString &) { + return(0); +} + +/** + @return toujours 0 - un element n'est pas une collection +*/ +ElementsCollection *ElementDefinition::toCollection() { + return(0); +} + +/** + @return la categorie parente de cet element +*/ +ElementsCategory *ElementDefinition::toCategory() { + return(parentCategory()); +} + +/** + @return toujours 0 - un element n'est pas une categorie +*/ +ElementsCategory *ElementDefinition::toPureCategory() { + return(0); +} + +/** + @return un pointeur ElementDefinition * sur cet element +*/ +ElementDefinition *ElementDefinition::toElement() { + return(this); +} + +/** + @return true si cette definition d'element est egale (en termes de contenu) + a la definition d'element other, false sinon. +*/ +bool ElementDefinition::equals(ElementDefinition &other) { + /* + Pour le moment, cette methode compare simplement l'export au format + texte des documents XML. Cela peut entrainer de faux positifs. + Exemple : un espace de plus ou de moins dans le XML n'en change pas + forcement la semantique. Mais cela changera l'export au format texte. + */ + QDomDocument this_xml_document; + this_xml_document.appendChild(this_xml_document.importNode(xml(), true)); + QString this_text = this_xml_document.toString(0); + + QDomDocument other_xml_document; + other_xml_document.appendChild(other_xml_document.importNode(other.xml(), true)); + QString other_text = other_xml_document.toString(0); + + return(other_text == this_text); +} + +/** + @param target_category Categorie cible pour la copie ; elle doit exister + @param handler Gestionnaire d'erreurs a utiliser pour effectuer la copie + @param deep_copy Argument ignore - une copie "recursive" n'a pas de sens pour un element + @return La copie de l'element ou 0 si le processus a echoue +*/ +ElementsCollectionItem *ElementDefinition::copy(ElementsCategory *target_category, MoveElementsHandler *handler, bool) { + if (!target_category) return(0); + + // echec si le path name de cet element est vide + QString elmt_name(pathName()); + if (elmt_name.isEmpty()) return(0); + + // cree une description du mouvement a effectuer + MoveElementsDescription mvt_desc; + mvt_desc.setDestinationParentCategory(target_category); + // on tente une copie avec le meme nom interne + mvt_desc.setOriginalDestinationInternalName(pathName()); + mvt_desc.setFinalDestinationInternalName(pathName()); + mvt_desc.setHandler(handler); + + copy(&mvt_desc); + return(mvt_desc.createdItem()); +} + +/** + Methode privee effectuant une copie de cet element a partir d'une + description du mouvement. + @param mvt_desc Description du mouvement +*/ +void ElementDefinition::copy(MoveElementsDescription *mvt_desc) { + // quelques pointeurs pour simplifier l'ecriture de la methode + MoveElementsHandler *handler = mvt_desc -> handler(); + ElementsCategory *target_category = mvt_desc -> destinationParentCategory(); + + ElementDefinition *element_copy = 0; + + // verifie que la categorie parente cible est accessible en lecture + if (!target_category -> isReadable()) { + if (!handler) { + return; + } else { + do { + QET::Action todo = handler -> categoryIsNotReadable(target_category); + + // on agit en fonction de la reponse du handler + if (todo == QET::Abort) { + mvt_desc -> abort(); + return; + } else if (todo == QET::Ignore || todo == QET::Managed) { + return; + } else if (todo == QET::Retry || todo == QET::Erase) { + // reessayer = repasser dans la boucle + } else if (todo == QET::Rename) { + // cas non gere + } + } while (!target_category -> isReadable()); + } + } + + // verifie que la categorie parente cible est accessible en ecriture + if (!target_category -> isWritable()) { + if (!handler) { + return; + } else { + do { + QET::Action todo = handler -> categoryIsNotWritable(target_category); + + // on agit en fonction de la reponse du handler + if (todo == QET::Abort) { + mvt_desc -> abort(); + return; + } else if (todo == QET::Ignore || todo == QET::Managed) { + return; + } else if (todo == QET::Retry || todo == QET::Erase) { + // reessayer = repasser dans la boucle + } else if (todo == QET::Rename) { + // cas non gere + } + } while (!target_category -> isWritable()); + } + } + + // verifie que la cible n'existe pas deja + if ((element_copy = target_category -> element(mvt_desc -> finalDestinationInternalName()))) { + if (!handler) { + return; + } else { + do { + // la cible existe deja : on demande au Handler ce qu'on doit faire + QET::Action todo = handler -> elementAlreadyExists(this, element_copy); + + // on agit en fonction de la reponse du handler + if (todo == QET::Abort) { + mvt_desc -> abort(); + return; + } else if (todo == QET::Ignore || todo == QET::Managed) { + return; + } else if (todo == QET::Erase) { + break; + } else if (todo == QET::Rename) { + mvt_desc -> setFinalDestinationInternalName(handler -> nameForRenamingOperation()); + } + } while ((element_copy = target_category -> element(mvt_desc -> finalDestinationInternalName()))); + } + } + + /* + A ce stade, on peut creer l'element cible : soit il n'existe pas, soit + on a l'autorisation de l'ecraser + */ + + // si la cible existe deja, verifie qu'elle est accessible en ecriture + element_copy = target_category -> element(mvt_desc -> finalDestinationInternalName()); + if (element_copy && !element_copy -> isWritable()) { + if (!handler) { + return; + } else { + do { + // la cible n'est pas accessible en ecriture : on demande au Handler ce qu'on doit faire + QET::Action todo = handler -> elementIsNotWritable(element_copy); + + // on agit en fonction de la reponse du handler + if (todo == QET::Abort) { + mvt_desc -> abort(); + return; + } else if (todo == QET::Ignore || todo == QET::Managed) { + return; + } else if (todo == QET::Retry || todo == QET::Erase) { + // reessayer = repasser dans la boucle + } else if (todo == QET::Rename) { + // cas non gere + } + } while (!element_copy -> isWritable()); + } + } + + // cree l'element cible + element_copy = target_category -> createElement(mvt_desc -> finalDestinationInternalName()); + if (!element_copy) { + if (handler) { + handler -> errorWithAnElement(this, tr("L'\351l\351ment cible n'a pu \352tre cr\351\351.")); + } + return; + } + + // recopie la definition de l'element + element_copy -> setXml(xml()); + element_copy -> write(); + mvt_desc -> setCreatedItem(element_copy); +} + +/** + @param target_category Categorie cible pour le deplacement ; elle doit exister + @param handler Gestionnaire d'erreurs a utiliser pour effectuer le deplacement + @return L'element apres deplacement ou 0 si le processus a echoue + +*/ +ElementsCollectionItem *ElementDefinition::move(ElementsCategory *target_category, MoveElementsHandler *handler) { + if (!target_category) return(0); + + // echec si le path name de cet element est vide + QString elmt_name(pathName()); + if (elmt_name.isEmpty()) return(0); + + // cree une description du mouvement a effectuer + MoveElementsDescription mvt_desc; + mvt_desc.setDestinationParentCategory(target_category); + // on tente un deplacement avec le meme nom interne + mvt_desc.setOriginalDestinationInternalName(pathName()); + mvt_desc.setFinalDestinationInternalName(pathName()); + mvt_desc.setHandler(handler); + + move(&mvt_desc); + return(mvt_desc.createdItem()); +} + +/** + Methode privee effectuant un delacement de cet element a partir d'une + description du mouvement. + Pour etre plus exact, cette methode effectue d'abord une copie de l'element, + puis, si celle-ci a reussi, il supprime l'element d'origine. + @param mvt_desc Description du mouvement +*/ +void ElementDefinition::move(MoveElementsDescription *mvt_desc) { + // effectue une copie de l'element + copy(mvt_desc); + ElementsCollectionItem *item_copy = mvt_desc -> createdItem(); + if (!item_copy) return; + ElementDefinition *element_copy = item_copy -> toElement(); + if (!element_copy) return; + + // supprime cet element + MoveElementsHandler *handler = mvt_desc -> handler(); + + // cet element doit etre accessible en ecriture pour etre supprime + if (!isWritable()) { + if (!handler) { + return; + } else { + do { + // on demande au Handler ce qu'on doit faire + QET::Action todo = handler -> elementIsNotWritable(this); + + // on agit en fonction de la reponse du handler + if (todo == QET::Abort) { + mvt_desc -> abort(); + return; + } else if (todo == QET::Ignore || todo == QET::Managed) { + return; + } else if (todo == QET::Retry || todo == QET::Erase) { + // reessayer = repasser dans la boucle + } else if (todo == QET::Rename) { + // cas non gere + } + } while (!isWritable()); + } + } + + // supprime cet element (sinon il ne s'agirait que d'une copie, pas d'un deplacement) + bool element_deletion = remove(); + mvt_desc -> setSourceItemDeleted(element_deletion); + if (!element_deletion && handler) { + handler -> errorWithAnElement(this, tr("La suppression de cet \351l\351ment a \351chou\351.")); + } +} + +/** + Cette methode n'a aucun effet + @return toujours true +*/ +bool ElementDefinition::removeContent() { + return(true); +} diff --git a/sources/elementdefinition.h b/sources/elementdefinition.h new file mode 100644 index 000000000..bfff99330 --- /dev/null +++ b/sources/elementdefinition.h @@ -0,0 +1,126 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef ELEMENT_DEFINITION_H +#define ELEMENT_DEFINITION_H +#include +#include "elementscategory.h" +class ElementsCollection; +class MoveElementsHandler; +/** + Cette classe abstraite represente une definition XML d'element, + c'est-a-dire qu'elle definit l'interface a implementer pour acceder a la + definition XML d'un element, que celle-ci proviennt d'un fichier *.elmt ou + d'un fichier projet QET. +*/ +class ElementDefinition : public ElementsCollectionItem { + Q_OBJECT + + public: + /** + Constructeur + */ + ElementDefinition(ElementsCategory *category = 0, ElementsCollection *collection = 0) : ElementsCollectionItem(category), parent_category_(category), parent_collection_(collection) {}; + + /** + Destructeur + */ + virtual ~ElementDefinition() {}; + + /** + @return la definition XML de l'element + */ + virtual QDomElement xml() = 0; + + /** + Change la definition XML de l'element + @param xml_element Nouvelle definition XML de l'element + @return true si l'operation s'est bien passee, false sinon + */ + virtual bool setXml(const QDomElement &) = 0; + + /** + @return true si la definition n'est pas disponible + */ + virtual bool isNull() const = 0; + + virtual ElementsCategory *parentCategory(); + virtual QList parentCategories(); + virtual bool hasParentCategory(); + + /** + @return true si l'element est rattache a une collection d'elements + Un element appartenant a une collection a forcement un chemin virtuel. + */ + virtual bool hasParentCollection(); + virtual bool isChildOf(ElementsCollectionItem *); + + /** + @return la collection d'element a laquelle appartient cet element + */ + virtual ElementsCollection *parentCollection(); + + virtual QETProject *project(); + virtual void setProject(QETProject *); + /** + @return le protocole utilise par la collection a laquelle appartient cet element + */ + virtual QString protocol(); + /** + Ne fait rien. + */ + virtual void setProtocol(const QString &); + + /** + @return le chemin virtuel complet de cet element (protocole + chemin) + */ + virtual QString fullVirtualPath(); + + /** + @return l'emplacement de cet element + */ + virtual ElementsLocation location(); + + virtual QList categories(); + virtual ElementsCategory *category(const QString &); + virtual ElementsCategory *createCategory(const QString &); + + virtual QList elements(); + virtual ElementDefinition *element(const QString &); + virtual ElementDefinition *createElement(const QString &); + virtual ElementsCollectionItem *copy(ElementsCategory *, MoveElementsHandler *, bool = true); + virtual ElementsCollectionItem *move(ElementsCategory *, MoveElementsHandler *); + + virtual bool isCollection() const { return(false); } ///< @return toujours false + virtual bool isRootCategory() const { return(false); } ///< @return toujours false + virtual bool isCategory() const { return(false); } ///< @return toujours false + virtual bool isElement() const { return(true ); } ///< @return toujours true + virtual ElementsCollection *toCollection(); + virtual ElementsCategory *toCategory(); + virtual ElementsCategory *toPureCategory(); + virtual ElementDefinition *toElement(); + virtual bool equals(ElementDefinition &); + virtual bool removeContent(); + void copy(MoveElementsDescription *); + void move(MoveElementsDescription *); + + // attributs + private: + ElementsCategory *parent_category_; + ElementsCollection *parent_collection_; +}; +#endif diff --git a/sources/elementdeleter.cpp b/sources/elementdeleter.cpp index 464a5df94..dd4871011 100644 --- a/sources/elementdeleter.cpp +++ b/sources/elementdeleter.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -16,16 +16,25 @@ along with QElectroTech. If not, see . */ #include "elementdeleter.h" +#include "qetapp.h" /** Constructeur - @param elmt_path Chemin du fichier representant l'element a supprimer + @param elmt_path Chemin virtuel du fichier representant l'element a supprimer @param parent QWidget parent */ -ElementDeleter::ElementDeleter(const QString &elmt_path, QWidget *parent) : +ElementDeleter::ElementDeleter(const ElementsLocation &elmt_path, QWidget *parent) : QWidget(parent), - element_path(elmt_path) + element(0) { + // recupere l'element a supprimer + ElementsCollectionItem *element_item = QETApp::collectionItem(elmt_path); + if (!element_item) return; + + // on exige un element + if (!element_item -> isElement()) return; + + element = element_item; } /// Destructeur @@ -36,27 +45,31 @@ ElementDeleter::~ElementDeleter() { Supprime l'element : verifie l'existence du fichier, demande confirmation a l'utilisateur et avertit ce dernier si la suppression a echoue. */ -void ElementDeleter::exec() { +bool ElementDeleter::exec() { // verifie l'existence de l'element - QFile elmt_file(element_path); - if (!elmt_file.exists()) return; + if (!element || !element -> isElement()) return(false); // confirmation #1 QMessageBox::StandardButton answer_1 = QMessageBox::question( this, - tr("Supprimer l'\351l\351ment ?"), - tr("\312tes-vous s\373r de vouloir supprimer cet \351l\351ment ?\n"), - QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel + tr("Supprimer l'\351l\351ment ?", "message box title"), + tr("\312tes-vous s\373r de vouloir supprimer cet \351l\351ment ?\n", "message box content"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel ); - if (answer_1 != QMessageBox::Yes) return; + if (answer_1 != QMessageBox::Yes) return(false); + + /** + @todo Regression : rafficher le chemin de l'element + */ // supprime l'element - if (!elmt_file.remove()) { + if (!element -> remove()) { QMessageBox::warning( this, - tr("Suppression de l'\351l\351ment"), - tr("La suppression de l'\351l\351ment a \351chou\351.\n" - "V\351rifiez vos droits sur le fichier ") + element_path + tr(".") + tr("Suppression de l'\351l\351ment", "message box title"), + tr("La suppression de l'\351l\351ment a \351chou\351.", "message box content") ); + return(false); } + return(true); } diff --git a/sources/elementdeleter.h b/sources/elementdeleter.h index 81e503124..38652c084 100644 --- a/sources/elementdeleter.h +++ b/sources/elementdeleter.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -18,27 +18,28 @@ #ifndef ELEMENT_DELETER_H #define ELEMENT_DELETER_H #include "elementscategory.h" +#include "elementslocation.h" #include /** Cette classe represente une couche d'abstraction pour supprimer un element de la collection d'elements. - Elle demande notamment confirmation a l'utilisateur + Elle demande notamment confirmation a l'utilisateur. */ class ElementDeleter : public QWidget { Q_OBJECT // constructeurs, destructeur public: - ElementDeleter(const QString &, QWidget * = 0); + ElementDeleter(const ElementsLocation &, QWidget * = 0); virtual ~ElementDeleter(); private: ElementDeleter(const ElementsCategory &); // methodes public slots: - void exec(); + bool exec(); // attributs private: - QString element_path; + ElementsCollectionItem *element; }; #endif diff --git a/sources/elementdialog.cpp b/sources/elementdialog.cpp new file mode 100644 index 000000000..8a17785d9 --- /dev/null +++ b/sources/elementdialog.cpp @@ -0,0 +1,348 @@ +#include "elementdialog.h" +#include +#include "qetapp.h" +#include "elementscategorieslist.h" +#include "elementscollectionitem.h" +#include "qfilenameedit.h" + +/** + Constructeur par defaut. + Construit un dialogue permettant d'ouvrir un element + @param mode Mode du dialogue + @see ElementDialog::Mode + @param parent QObject parent +*/ +ElementDialog::ElementDialog(uint mode, QObject *parent) : + QObject(parent), + mode_(mode), + buttons_(0), + list_(0), + textfield_(0) +{ + dialog_ = new QDialog(); + dialog_ -> setWindowModality(Qt::WindowModal); + buttons_ = new QDialogButtonBox(); + + // types selectionnables dans la liste + bool display_elements = (mode_ == OpenElement || mode_ == SaveElement); + int selectables = 0; + switch(mode_) { + case OpenElement: selectables = QET::Element; break; + case SaveElement: selectables = QET::All; break; + case OpenCategory: selectables = QET::Category | QET::Collection; break; + case SaveCategory: selectables = QET::Category | QET::Collection; break; + } + list_ = new ElementsCategoriesList(display_elements, selectables); + connect(list_, SIGNAL(locationChanged(const ElementsLocation &)), this, SLOT(locationChanged(const ElementsLocation &))); + + // titre et label + if (!mode) { + title_ = tr("Ouvrir un \351l\351ment", "dialog title"); + label_ = tr("Choisissez l'\351l\351ment que vous souhaitez ouvrir.", "dialog content"); + } else if (mode == 1) { + title_ = tr("Enregistrer un \351l\351ment", "dialog title"); + label_ = tr("Choisissez l'\351l\351ment dans lequel vous souhaitez enregistrer votre d\351finition.", "dialog content"); + } else if (mode == 2) { + title_ = tr("Ouvrir une cat\351gorie", "dialog title"); + label_ = tr("Choisissez une cat\351gorie.", "dialog content"); + } else { + title_ = tr("Enregistrer une cat\351gorie", "dialog title"); + label_ = tr("Choisissez une cat\351gorie.", "dialog content"); + } + + // mode ouverture / enregistrement + if (mode_ == SaveCategory || mode_ == SaveElement) { + buttons_ -> setStandardButtons(QDialogButtonBox::Save | QDialogButtonBox::Cancel); + textfield_ = new QFileNameEdit(); + connect(textfield_, SIGNAL(textChanged(const QString &)), this, SLOT(textFieldChanged(const QString &))); + } else { + buttons_ -> setStandardButtons(QDialogButtonBox::Open | QDialogButtonBox::Cancel); + } + + // connexions boutons -> dialogue + connect(buttons_, SIGNAL(accepted()), this, SLOT(checkDialog())); + connect(buttons_, SIGNAL(rejected()), dialog_, SLOT(reject())); + + // connexions dialogue -> classe + connect(dialog_, SIGNAL(accepted()), this, SLOT(dialogAccepted())); + connect(dialog_, SIGNAL(rejected()), this, SLOT(dialogRejected())); + + makeInterface(); +} + +/** + Destructeur +*/ +ElementDialog::~ElementDialog() { + delete dialog_; +} + +/** + Affiche un dialogue permettant a l'utilisateur de selectionner une categorie existant deja + @return le chemin virtuel de cette categorie +*/ +ElementsLocation ElementDialog::getExistingCategoryLocation() { + return(ElementDialog::execConfiguredDialog(ElementDialog::OpenCategory)); +} + +/** + Affiche un dialogue permettant a l'utilisateur de selectionner une nouvelle categorie + @return le chemin virtuel de cette categorie +*/ +ElementsLocation ElementDialog::getNewCategoryLocation() { + return(ElementDialog::execConfiguredDialog(ElementDialog::SaveCategory)); +} + +/** + Affiche un dialogue permettant a l'utilisateur de selectionner un element a ouvrir + @return le chemin virtuel de cet element +*/ +ElementsLocation ElementDialog::getOpenElementLocation() { + return(ElementDialog::execConfiguredDialog(ElementDialog::OpenElement)); +} + +/** + Affiche un dialogue permettant a l'utilisateur de selectionner un element (existant ou non) + qu'il souhaite enregistrer + @return le chemin virtuel de cet element +*/ +ElementsLocation ElementDialog::getSaveElementLocation() { + return(ElementDialog::execConfiguredDialog(ElementDialog::SaveElement)); +} + +/** + Lance un dialogue selon la configuration mode + @param mode Mode du dialogue +*/ +ElementsLocation ElementDialog::execConfiguredDialog(int mode) { + ElementDialog element_dialog(mode); + element_dialog.exec(); + return(element_dialog.location()); +} + +/** + Assemble les widgets pour obtenir le dialogue final +*/ +void ElementDialog::makeInterface() { + dialog_ -> setWindowTitle(title_); + + // disposition verticale + QVBoxLayout *layout = new QVBoxLayout(dialog_); + layout -> addWidget(new QLabel(label_)); + layout -> addWidget(list_); + if (textfield_) { + QHBoxLayout *filename_layout = new QHBoxLayout(); + filename_layout -> addWidget(new QLabel(tr("Nom :"))); + filename_layout -> addWidget(textfield_); + layout -> addLayout(filename_layout); + } + layout -> addWidget(buttons_); +} + +/** + Execute le dialogue + @return QDialog::Accepted si le dialogue a ete accepte, false sinon + @see DialogCode +*/ +int ElementDialog::exec() { + return(dialog_ -> exec()); +} + +/** + @return le chemin virtuel choisi via le dialogue + Si l'utilisateur n'a pas pu faire son choix, une chaine vide est retournee. +*/ +ElementsLocation ElementDialog::location() const { + return(location_); +} + +/** + gere le changement de chemin virtuel par l'utilisateur + @param new_loc le nouveau chemin virtuel choisi par l'utilisateur +*/ +void ElementDialog::locationChanged(const ElementsLocation &new_loc) { + ElementsCollectionItem *item = QETApp::collectionItem(new_loc); + if (!item) return; + if (mode_ == OpenElement) { + buttons_ -> button(QDialogButtonBox::Open) -> setEnabled(item -> isElement()); + } else if (mode_ == SaveElement) { + // si l'utilisateur choisit un element existant, on desactive le champ + textfield_ -> setEnabled(!item -> isElement()); + // il faut soit un element selectionne soit une categorie et un nom + buttons_ -> button(QDialogButtonBox::Save) -> setEnabled( + ((item -> isCategory() || item -> isCollection()) && !textfield_ -> text().isEmpty()) ||\ + item -> isElement() + ); + } else if (mode_ == OpenCategory) { + /// @todo + } else if (mode_ == SaveCategory) { + /// @todo + } + location_ = new_loc; +} + +/** + Gere le changement de contenu dans le champ de texte + @param text Contenu du champ de texte +*/ +void ElementDialog::textFieldChanged(const QString &text) { + ElementsCollectionItem *item = QETApp::collectionItem(list_ -> selectedLocation()); + if (!item) return; + if (mode_ == SaveElement) { + // il faut soit un element selectionne soit une categorie et un nom + buttons_ -> button(QDialogButtonBox::Save) -> setEnabled( + ((item -> isCategory() || item -> isCollection()) && !text.isEmpty()) ||\ + item -> isElement() + ); + } else if (mode_ == SaveCategory) { + // il faut forcement un nom pour la nouvelle categorie + buttons_ -> button(QDialogButtonBox::Save) -> setEnabled(!text.isEmpty()); + } +} + +/** + Verifie l'etat du dialogue au moment ou l'utilisateur le valide. +*/ +void ElementDialog::checkDialog() { + // verifie si ce qui a ete selectionne par l'utilisateur correspond au mode du widget + if (mode_ == OpenElement) { + // l'utilisateur doit avoir choisi un element existant + + // on verifie d'abord que l'utilisateur a choisi quelque chose + ElementsLocation location = list_ -> selectedLocation(); + if (location.isNull()) { + QMessageBox::critical( + dialog_, + tr("Pas de s\351lection", "message box title"), + tr("Vous devez s\351lectionner un \351l\351ment.", "message box content") + ); + return; + } + + // on verifie donc que la selection existe + ElementsCollectionItem *item = QETApp::collectionItem(location); + if (!item) { + QMessageBox::critical( + dialog_, + tr("S\351lection inexistante", "message box title"), + tr("La s\351lection n'existe pas.", "message box content") + ); + return; + } + + // puis on verifie qu'il s'agit bien d'un element + if (!item -> isElement()) { + QMessageBox::critical( + dialog_, + tr("S\351lection incorrecte", "message box title"), + tr("La s\351lection n'est pas un \351l\351ment.", "message box content") + ); + return; + } + + location_ = location; + } else if (mode_ == SaveElement) { + /* + l'utilisateur doit avoir choisi soit : + -une categorie et un nom d'element + -un element existant + */ + ElementsLocation location = list_ -> selectedLocation(); + if (location.isNull()) { + QMessageBox::critical( + dialog_, + tr("Pas de s\351lection", "message box title"), + tr("Vous devez s\351lectionner une cat\351gorie ou un \351l\351ment.", "message box content") + ); + return; + } + + // on verifie donc que la selection existe + ElementsCollectionItem *item = QETApp::collectionItem(location); + if (!item) { + QMessageBox::critical( + dialog_, + tr("S\351lection inexistante", "message box title"), + tr("La s\351lection n'existe pas.", "message box content") + ); + return; + } + + ElementsLocation final_location(location); + if (!item -> isElement()) { + QString element_name(textfield_ -> text()); + // si on a une categorie (ou une collection), il nous faut un nom d'element + if (element_name.isEmpty()) { + QMessageBox::critical( + dialog_, + tr("Nom manquant", "message box title"), + tr("Vous devez entrer un nom pour l'\351l\351ment", "message box content") + ); + return; + } + + // ce nom d'element doit etre valide + if (QET::containsForbiddenCharacters(element_name)) { + QMessageBox::critical( + dialog_, + tr("Nom invalide", "message box title"), + QString( + tr( + "Vous ne pouvez pas utiliser les caract\350res " + "suivants dans le nom de l'\351l\351ment : %1" + ) + ).arg(QET::forbiddenCharactersString(true)) + ); + return; + } + + // ajoute .elmt a la fin du nom si necessaire + if (!element_name.endsWith(".elmt", Qt::CaseInsensitive)) { + element_name += ".elmt"; + } + final_location.addToPath(element_name); + } + + // determine si l'element n'existe pas deja + bool element_already_exists = ( + item -> isElement() ||\ + QETApp::collectionItem(final_location) + ); + + // si l'element existe, on demande confirmation pour son ecrasement + if (element_already_exists) { + QMessageBox::StandardButton answer = QMessageBox::question( + dialog_, + tr("\311craser l'\351l\351ment ?", "message box title"), + tr("L'\351l\351ment existe d\351j\340. Voulez-vous l'\351craser ?", "message box content"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, + QMessageBox::No + ); + if (answer != QMessageBox::Yes) return; + } + + location_ = final_location; + } else if (mode_ == OpenCategory) { + // l'utilisateur doit avoir choisi une categorie ou une collection existante + /// @todo effectuer les verifications necessaires + } else if (mode_ == SaveCategory) { + // l'utilisateur doit avoir choisi une categorie inexistante + /// @todo effectuer les verifications necessaires + } + + // le dialogue est verifie, il peut etre accepte + dialog_ -> accept(); +} + +/** + Slot execute apres la validation du dialogue par l'utilisateur +*/ +void ElementDialog::dialogAccepted() { +} + +/** + Gere le rejet du dialogue par l'utilisateur. +*/ +void ElementDialog::dialogRejected() { + location_ = ElementsLocation(); +} diff --git a/sources/elementdialog.h b/sources/elementdialog.h new file mode 100644 index 000000000..9df112c55 --- /dev/null +++ b/sources/elementdialog.h @@ -0,0 +1,83 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef ELEMENT_DIALOG_H +#define ELEMENT_DIALOG_H +#include +#include "elementslocation.h" +class QDialog; +class QDialogButtonBox; +class ElementsCategoriesList; +class QFileNameEdit; +/** + Cette classe permet d'acceder a plusieurs types de dialogues lies a la + selection d'element ou de categorie. +*/ +class ElementDialog : public QObject { + Q_OBJECT + // enumerations + /** + Cette enum represente les configurations possible pour ce dialogue + */ + enum { + OpenElement = 0, ///< Le dialogue sera en mode ouverture + SaveElement = 1, ///< Le dialogue sera en mode enregistrement + OpenCategory = 2, ///< Le dialogue concernera une categorie + SaveCategory = 3 ///< Le dialogue concernera un element + }; + + // constructeurs, destructeur + public: + ElementDialog(uint = ElementDialog::OpenElement, QObject * = 0); + virtual ~ElementDialog(); + private: + ElementDialog(const ElementDialog &); + + // methodes + public: + int exec(); + ElementsLocation location() const; + static ElementsLocation getExistingCategoryLocation(); + static ElementsLocation getNewCategoryLocation(); + static ElementsLocation getOpenElementLocation(); + static ElementsLocation getSaveElementLocation(); + + private: + static ElementsLocation execConfiguredDialog(int); + + private slots: + void locationChanged(const ElementsLocation &); + void textFieldChanged(const QString &); + void dialogAccepted(); + void dialogRejected(); + void checkDialog(); + + private: + void makeInterface(); + + // attributs + private: + uint mode_; + ElementsLocation location_; + QString title_; + QString label_; + QDialog *dialog_; + QDialogButtonBox *buttons_; + ElementsCategoriesList *list_; + QFileNameEdit *textfield_; +}; +#endif diff --git a/sources/elementscategorieslist.cpp b/sources/elementscategorieslist.cpp index 9f037db59..89ca22c00 100644 --- a/sources/elementscategorieslist.cpp +++ b/sources/elementscategorieslist.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -15,17 +15,26 @@ You should have received a copy of the GNU General Public License along with QElectroTech. If not, see . */ -#include #include "elementscategorieslist.h" #include "qetapp.h" +#include "customelement.h" +#include "elementscollection.h" #include "elementscategory.h" -#include "qetdiagrameditor.h" +#include "elementdefinition.h" /** Constructeur + @param display_elements true pour afficher les elements, false sinon + @param selectables Types selectionnables + @see QET::ItemType @param parent QWidget parent de ce widget */ -ElementsCategoriesList::ElementsCategoriesList(QWidget *parent) : QTreeWidget(parent) { +ElementsCategoriesList::ElementsCategoriesList(bool display_elements, uint selectables, QWidget *parent) : + QTreeWidget(parent), + display_elements_(display_elements), + selectables_(selectables), + first_load(true) +{ // selection unique setSelectionMode(QAbstractItemView::SingleSelection); setColumnCount(1); @@ -33,6 +42,8 @@ ElementsCategoriesList::ElementsCategoriesList(QWidget *parent) : QTreeWidget(pa // charge les categories reload(); + + connect(this, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT(selectionChanged(QTreeWidgetItem *, QTreeWidgetItem *))); } /** @@ -46,62 +57,149 @@ ElementsCategoriesList::~ElementsCategoriesList() { */ void ElementsCategoriesList::reload() { // vide l'arbre - while (takeTopLevelItem(0)) {}; + clear(); + + foreach(ElementsCollection *collection, QETApp::availableCollections()) { + if (collection == QETApp::commonElementsCollection()) continue; + if (collection == QETApp::customElementsCollection()) continue; + addCollection(invisibleRootItem(), collection, tr("Collection projet")); + } // chargement des elements de la collection commune si droits d'ecriture - QFileInfo common_collection_info(QETApp::commonElementsDir()); - if ( - common_collection_info.exists() && - common_collection_info.isDir() && - common_collection_info.isWritable() - ) { - addDir(invisibleRootItem(), QETApp::commonElementsDir(), tr("Collection QET")); + if (QETApp::commonElementsCollection() -> isWritable()) { + if (!first_load) QETApp::commonElementsCollection() -> reload(); + addCollection(invisibleRootItem(), QETApp::commonElementsCollection(), tr("Collection QET"), QIcon(":/ico/qet-16.png")); } // chargement des elements de la collection utilisateur - addDir(invisibleRootItem(), QETApp::customElementsDir(), tr("Collection utilisateur")); + if (!first_load) QETApp::customElementsCollection() -> reload(); + addCollection(invisibleRootItem(), QETApp::customElementsCollection(), tr("Collection utilisateur"), QIcon(":/ico/folder_home.png")); + + if (first_load) first_load = false; } /** - Methode privee permettant d'ajouter un dossier au panel d'appareils - @param qtwi_parent QTreeWidgetItem parent sous lequel sera insere l'element - @param dossier Chemin absolu du dossier a inserer - @param nom Parametre facultatif permettant de forcer le nom du dossier. - S'il n'est pas precise, la fonction ouvre le fichier qet_directory situe - dans le dossier et y lit le nom du dossier ; si ce fichier n'existe pas ou - est invalide, la fonction utilise le nom du dossier. + Methode privee permettant d'ajouter une collection d'elements + @param qtwi_parent QTreeWidgetItem parent sous lequel sera insere la collection d'elements + @param collection Collection a inserer dans le panel d'elements + @param coll_name Nom a utiliser pour la collection + @param icon Icone a utiliser pour l'affichage de la collection + @return Le QTreeWidgetItem insere le plus haut */ -void ElementsCategoriesList::addDir(QTreeWidgetItem *qtwi_parent, QString adr_dossier, QString nom) { - ElementsCategory category(adr_dossier); - if (!category.exists()) return; - +QTreeWidgetItem *ElementsCategoriesList::addCollection(QTreeWidgetItem *qtwi_parent, ElementsCollection *collection, const QString &coll_name, const QIcon &icon) { + QTreeWidgetItem *qtwi_coll = addCategory(qtwi_parent, collection -> rootCategory(), coll_name, icon); + qtwi_coll -> setExpanded(true); + Qt::ItemFlags flags_coll = Qt::ItemIsEnabled; + if (selectables_ & QET::Collection) flags_coll |= Qt::ItemIsSelectable; + qtwi_coll -> setFlags(flags_coll); + return(qtwi_coll); +} + +/** + Methode privee permettant d'ajouter une categorie + @param qtwi_parent QTreeWidgetItem parent sous lequel sera insere la categorie + @param category Categorie d'elements a inserer + @param name Parametre facultatif permettant de forcer le nom affiche + S'il n'est pas precise, la methode utilise le nom declare par la categorie. + @param icon Icone a utiliser pour l'affichage de la categorie + Si elle n'est pas precisee, une icone par defaut est utilisee + @return Le QTreeWidgetItem insere le plus haut +*/ +QTreeWidgetItem *ElementsCategoriesList::addCategory(QTreeWidgetItem *qtwi_parent, ElementsCategory *category, const QString &cat_name, const QIcon &icon) { // recupere le nom de la categorie - QString nom_categorie = (nom != QString()) ? nom : category.name(); + QString final_name(cat_name.isEmpty() ? category -> name() : cat_name); + QIcon final_icon(icon.isNull() ? QIcon(":/ico/folder.png") : icon); // creation du QTreeWidgetItem representant le dossier - QTreeWidgetItem *qtwi_dossier = new QTreeWidgetItem(qtwi_parent, QStringList(nom_categorie)); - qtwi_dossier -> setData(0, Qt::UserRole, category.absolutePath()); - qtwi_dossier -> setExpanded(true); + QTreeWidgetItem *qtwi_category = new QTreeWidgetItem(qtwi_parent, QStringList(final_name)); + qtwi_category -> setIcon(0, final_icon); + locations_.insert(qtwi_category, category -> location()); + Qt::ItemFlags flags_category = Qt::ItemIsEnabled; + if (selectables_ & QET::Category) flags_category |= Qt::ItemIsSelectable; + qtwi_category -> setFlags(flags_category); - // ajout des sous-categories / sous-dossiers - QStringList dossiers = category.entryList(QStringList(), QDir::AllDirs | QDir::NoSymLinks | QDir::NoDotAndDotDot, QDir::Name); - foreach(QString dossier, dossiers) addDir(qtwi_dossier, adr_dossier + dossier + "/"); + // ajout des sous-categories + foreach(ElementsCategory *sub_cat, category -> categories()) addCategory(qtwi_category, sub_cat); + + if (display_elements_) { + foreach(ElementDefinition *elmt, category -> elements()) addElement(qtwi_category, elmt); + } + + return(qtwi_category); } /** - @return Le dossier de la categorie selectionnee + Methode privee permettant d'ajouter un element + @param qtwi_parent QTreeWidgetItem parent sous lequel sera insere l'element + @param element Element a inserer + @param name Parametre facultatif permettant de forcer le nom affiche + S'il n'est pas precise, la methode utilise le nom declare par la categorie. + Une icone sera generee a partir de l'element. + @return Le QTreeWidgetItem insere */ -QString ElementsCategoriesList::selectedCategoryPath() { - QTreeWidgetItem *qtwi = currentItem(); - if (qtwi) return(qtwi -> data(0, Qt::UserRole).toString()); - else return(NULL); +QTreeWidgetItem *ElementsCategoriesList::addElement(QTreeWidgetItem *qtwi_parent, ElementDefinition *element, const QString &elmt_name, const QIcon &icon) { + int etat; + CustomElement custom_elmt(element -> xml(), 0, 0, &etat); + if (etat) { + qDebug() << "Le chargement du composant" << element -> location().toString() << "a echoue avec le code d'erreur" << etat; + return(0); + } + QString final_name(elmt_name.isEmpty() ? custom_elmt.name() : elmt_name); + QTreeWidgetItem *qtwi = new QTreeWidgetItem(qtwi_parent, QStringList(final_name)); + qtwi -> setToolTip(0, custom_elmt.name()); + Qt::ItemFlags flags_element = Qt::ItemIsEnabled; + if (selectables_ & QET::Element) flags_element |= Qt::ItemIsSelectable; + qtwi -> setFlags(flags_element); + qtwi -> setIcon(0, icon); + locations_.insert(qtwi, element -> location()); + + return(qtwi); } /** - @return Le nom de la categorie selecti onnee + @return Le nom de la categorie selectionnee */ -QString ElementsCategoriesList::selectedCategoryName() { +QString ElementsCategoriesList::selectedCategoryName() const { QTreeWidgetItem *qtwi = currentItem(); if (qtwi) return(qtwi -> data(0, Qt::DisplayRole).toString()); - else return(NULL); + else return(QString()); +} + +/** + @return l'emplacement correspondant au QTreeWidgetItem selectionne +*/ +ElementsLocation ElementsCategoriesList::selectedLocation() const { + if (QTreeWidgetItem *current_qtwi = currentItem()) { + return(locations_[current_qtwi]); + } else { + return(ElementsLocation()); + } +} + +/** + Selectionne un element dans la liste a partir de son emplacement + @see ElementsLocation + @param location Emplacement a selectionner + @return true si la selection a pu etre effectuee, false sinon +*/ +bool ElementsCategoriesList::selectLocation(const ElementsLocation &location) { + if (QTreeWidgetItem *qtwi = locations_.key(location)) { + setCurrentItem(qtwi); + return(true); + } + return(false); +} + +/** + Recupere le chemin virtuel de l'element selectionne et emet le signal + virtualPathChanged. + @param current QTreeWidgetItem selectionne + @param previous QTreeWidgetItem precedemment selectionne +*/ +void ElementsCategoriesList::selectionChanged(QTreeWidgetItem *current, QTreeWidgetItem */*previous*/) { + ElementsLocation emited_location; + if (current) { + emited_location = locations_[current]; + } + emit(locationChanged(emited_location)); } diff --git a/sources/elementscategorieslist.h b/sources/elementscategorieslist.h index f3ba8cc19..995358023 100644 --- a/sources/elementscategorieslist.h +++ b/sources/elementscategorieslist.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -18,6 +18,11 @@ #ifndef ELEMENTS_CATEGORIES_LIST_H #define ELEMENTS_CATEGORIES_LIST_H #include +#include "qet.h" +#include "elementslocation.h" +class ElementsCollection; +class ElementsCategory; +class ElementDefinition; /** Cette classe fournit une liste graphique des categories d'elements de l'utilisateur. @@ -27,7 +32,7 @@ class ElementsCategoriesList : public QTreeWidget { // Constructeurs, destructeur public: - ElementsCategoriesList(QWidget * = 0); + ElementsCategoriesList(bool = false, uint = QET::All, QWidget * = 0); virtual ~ElementsCategoriesList(); private: @@ -35,14 +40,30 @@ class ElementsCategoriesList : public QTreeWidget { // methodes public: - QString selectedCategoryPath(); - QString selectedCategoryName(); + QString selectedCategoryName() const; + ElementsLocation selectedLocation() const; + bool selectLocation(const ElementsLocation &); private: - void addDir(QTreeWidgetItem *, QString, QString = QString()); + QTreeWidgetItem *addCollection(QTreeWidgetItem *, ElementsCollection *, const QString & = QString(), const QIcon & = QIcon()); + QTreeWidgetItem *addCategory (QTreeWidgetItem *, ElementsCategory *, const QString & = QString(), const QIcon & = QIcon()); + QTreeWidgetItem *addElement (QTreeWidgetItem *, ElementDefinition *, const QString & = QString(), const QIcon & = QIcon()); QString categoryName(QDir &); public slots: void reload(); + + private slots: + void selectionChanged(QTreeWidgetItem *, QTreeWidgetItem *); + + signals: + void locationChanged(const ElementsLocation &); + + // attributs + private: + bool display_elements_; + int selectables_; + bool first_load; + QHash locations_; }; #endif diff --git a/sources/elementscategorieswidget.cpp b/sources/elementscategorieswidget.cpp index 28f47fc06..64da99124 100644 --- a/sources/elementscategorieswidget.cpp +++ b/sources/elementscategorieswidget.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -27,7 +27,7 @@ */ ElementsCategoriesWidget::ElementsCategoriesWidget(QWidget *parent) : QWidget(parent) { // initialise la liste des categories - elementscategorieslist = new ElementsCategoriesList(this); + elementscategorieslist = new ElementsCategoriesList(false, QET::All, this); // actions action_reload = new QAction(QIcon(":/ico/reload.png"), tr("Recharger les cat\351gories"), this); @@ -65,24 +65,31 @@ ElementsCategoriesWidget::ElementsCategoriesWidget(QWidget *parent) : QWidget(pa Destructeur */ ElementsCategoriesWidget::~ElementsCategoriesWidget() { - } /** Lance un editeur de categorie en mode "creation de categorie" */ void ElementsCategoriesWidget::newCategory() { - QString s_c_path = elementscategorieslist -> selectedCategoryPath(); + // recupere le chemin virtuel de la categorie selectionnee + ElementsLocation s_c_path = elementscategorieslist -> selectedLocation(); if (s_c_path.isNull()) return; - (new ElementsCategoryEditor(s_c_path, false, this)) -> exec(); - elementscategorieslist -> reload(); + + // lance un editeur de categorie + ElementsCategoryEditor *editor = new ElementsCategoryEditor(s_c_path, false, this); + int result = editor -> exec(); + + // recharge la collection si besoin + if (result == QDialog::Accepted) { + elementscategorieslist -> reload(); + } } /** Lance un editeur de categorie en mode "edition de categorie" */ void ElementsCategoriesWidget::editCategory() { - QString s_c_path = elementscategorieslist -> selectedCategoryPath(); + ElementsLocation s_c_path = elementscategorieslist -> selectedLocation(); if (s_c_path.isNull()) return; (new ElementsCategoryEditor(s_c_path, true, this)) -> exec(); elementscategorieslist -> reload(); @@ -93,7 +100,8 @@ void ElementsCategoriesWidget::editCategory() { */ void ElementsCategoriesWidget::removeCategory() { // recupere le chemin de la categorie - QString s_c_path = elementscategorieslist -> selectedCategoryPath(); + ElementsLocation s_c_path = elementscategorieslist -> selectedLocation(); + if (s_c_path.isNull()) return; // supprime la categorie ElementsCategoryDeleter cat_deleter(s_c_path, this); diff --git a/sources/elementscategorieswidget.h b/sources/elementscategorieswidget.h index a8077eb60..0a9c3e1b4 100644 --- a/sources/elementscategorieswidget.h +++ b/sources/elementscategorieswidget.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/elementscategory.cpp b/sources/elementscategory.cpp index 7b56b90c3..7558208d9 100644 --- a/sources/elementscategory.cpp +++ b/sources/elementscategory.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -16,14 +16,21 @@ along with QElectroTech. If not, see . */ #include "elementscategory.h" -#include +#include "elementscollection.h" +#include "elementdefinition.h" +#include "moveelementshandler.h" +#include "moveelementsdescription.h" +#include "qet.h" +#include "qetproject.h" /** Constructeur - @param path Chemin du dossier de la categorie */ -ElementsCategory::ElementsCategory(const QString &path) : QDir(path) { - if (path != QString()) loadNames(); +ElementsCategory::ElementsCategory(ElementsCategory *parent, ElementsCollection *collection) : + ElementsCollectionItem(parent), + parent_collection_(collection), + parent_category_(parent) +{ } /** @@ -33,51 +40,579 @@ ElementsCategory::~ElementsCategory() { } /** - Supprime un repertoire recursivement. - @return true si la suppression a reussie, false sinon + @return le projet auquel appartient cette categorie, si celle-ci + appartient a une collection. */ -bool ElementsCategory::rmdir(const QString &path) const { - QDir directory(path); - - // supprime les dossiers, recursivement - foreach (QString file, directory.entryList(QDir::AllDirs | QDir::NoDotAndDotDot)) { - if (!rmdir(directory.absolutePath() + "/" + file)) return(false); +QETProject *ElementsCategory::project() { + if (parent_collection_) { + return(parent_collection_ -> project()); } - - // supprime les fichiers - foreach (QString file, directory.entryList(QDir::Files | QDir::NoDotAndDotDot)) { - if (!directory.remove(file)) return(false); - } - - // supprime le dossier lui-meme - return(directory.rmdir(path)); + return(0); } /** - Charge la liste des noms possibles pour la categorie + Ne fait rien ; le projet doit etre defini au niveau d'une collection */ -void ElementsCategory::loadNames() { - // repere le chemin du fichier de configuration de la categorie - QFile directory_conf(canonicalPath() + "/qet_directory"); +void ElementsCategory::setProject(QETProject *) { +} + +/** + @return le protocole utilise pour designer la collection a laquelle + appartient cette categorie +*/ +QString ElementsCategory::protocol() { + // il n'est pas possible d'avoir un protocole sans appartenir a une collection + if (!hasParentCollection()) return(QString()); - // verifie l'existence du fichier - if (!directory_conf.exists()) return; + return(parentCollection() -> protocol()); +} + +/** + Ne fait rien +*/ +void ElementsCategory::setProtocol(const QString &) { +} + +/** + @return la categorie parente de cette categorie, ou 0 si celle-ci n'en possede +*/ +ElementsCategory *ElementsCategory::parentCategory() { + return(parent_category_); +} + +/** + @return la liste des categories parentes de cet item. + Cette liste peut etre vide s'il s'agit d'une categorie racine +*/ +QList ElementsCategory::parentCategories() { + QList cat_list; + if (ElementsCategory *par_cat = parentCategory()) { + cat_list << par_cat -> parentCategories() << par_cat; + } + return(cat_list); +} + +/** + @return true si cette categorie possede une categorie parente +*/ +bool ElementsCategory::hasParentCategory() { + return(parent_category_); +} + +/** + @return la collection a laquelle appartient la categorie +*/ +ElementsCollection *ElementsCategory::parentCollection() { + return(parent_collection_); +} + +/** + @return true si la categorie appartient a une collection +*/ +bool ElementsCategory::hasParentCollection() { + return(parent_collection_); +} + +/** + @param other_item Un autre item + @return true si other_item est parent (direct ou indirect) de cet item, false sinon +*/ +bool ElementsCategory::isChildOf(ElementsCollectionItem *other_item) { + // verifie si l'autre item n'est pas la collection a laquelle cette categorie appartient + if (ElementsCollection *other_item_coll = other_item -> toCollection()) { + if (other_item_coll == parentCollection()) { + return(true); + } + } - // ouvre le fichier - if (!directory_conf.open(QIODevice::ReadOnly | QIODevice::Text)) return; + // remonte les categories parentes pour voir si l'une d'entre elles correspond a cet autre item + ElementsCategory *parent_cat = this; + while ((parent_cat = parent_cat -> parentCategory())) { + if (parent_cat == other_item) { + return(true); + } + } - // lit le contenu du fichier dans un QDomDocument XML - QDomDocument document; - if (!document.setContent(&directory_conf)) return; + // arrive ici, l'autre item n'est pas parent de cet item + return(false); +} + +/** + @return le chemin virtuel de la categorie, avec le protocole +*/ +QString ElementsCategory::fullVirtualPath() { + // il n'est pas possible d'avoir un chemin virtuel sans appartenir a une collection + if (!hasParentCollection()) return(QString()); - // verifie la racine - QDomElement root = document.documentElement(); - if (root.tagName() != "qet-directory") return; + return(protocol() + "://" + virtualPath()); +} + +/** + @return l'emplacement de la categorie +*/ +ElementsLocation ElementsCategory::location() { + return(ElementsLocation(fullVirtualPath(), project())); +} + +/** + @return true si cette categorie est la categorie racine de la collection + a laquelle elle appartient, false sinon +*/ +bool ElementsCategory::isRootCategory() const { + // il faut appartenir a une collection pour etre categorie racine + if (!parent_collection_) return(false); - category_names.fromXml(root); + return(this == parent_collection_ -> rootCategory()); +} + +/** + @return toujours false +*/ +bool ElementsCategory::isCollection() const { + return(false); +} + +/** + @return toujours true +*/ +bool ElementsCategory::isCategory() const { + return(true); +} + +/** + @return toujours false +*/ +bool ElementsCategory::isElement() const { + return(false); +} + +/** + @return toujours 0 - une categorie n'est pas une collection +*/ +ElementsCollection *ElementsCategory::toCollection() { + return(0); +} + +/** + @return un pointeur ElementsCategory * sur cette categorie +*/ +ElementsCategory *ElementsCategory::toCategory() { + return(this); +} + +/** + @return un pointeur ElementsCategory * sur cette categorie +*/ +ElementsCategory *ElementsCategory::toPureCategory() { + return(this); +} + +/** + @return toujours 0 - une categorie n'est pas un element +*/ +ElementDefinition *ElementsCategory::toElement() { + return(0); +} + +/** + @param target_category Categorie cible pour la copie ; cette categorie + sera copiee en tant que sous-categorie de la categorie cible + @param handler Gestionnaire d'erreurs a utiliser pour effectuer la copie + Si handler vaut 0, les erreurs, problemes et questions sont purement et + simplement ignores. + @param deep_copy true pour copier recursivement le contenu (elements et + sous-categories) de cette categorie, false sinon + @return La copie de la categorie, ou 0 si le processus a echoue. +*/ +ElementsCollectionItem *ElementsCategory::copy(ElementsCategory *target_category, MoveElementsHandler *handler, bool deep_copy) { + if (!target_category) return(0); - // ferme le fichier - directory_conf.close(); + // echec si le path name de cette categorie est vide + QString cat_name(pathName()); + if (cat_name.isEmpty()) return(0); + + // cree une description du mouvement a effectuer + MoveElementsDescription mvt_desc; + mvt_desc.setDestinationParentCategory(target_category); + // on tente une copie avec le meme nom interne + mvt_desc.setOriginalDestinationInternalName(cat_name); + mvt_desc.setFinalDestinationInternalName(cat_name); + mvt_desc.setHandler(handler); + mvt_desc.setRecursive(deep_copy); + + // effectue le mouvement + copy(&mvt_desc); + return(mvt_desc.createdItem()); +} + +/** + Methode privee effectuant une copie de cette categorie a partir d'une + description du mouvement +*/ +void ElementsCategory::copy(MoveElementsDescription *mvt_desc) { + // quelques pointeurs pour simplifier l'ecriture de la methode + MoveElementsHandler *handler = mvt_desc -> handler(); + ElementsCategory *target_category = mvt_desc -> destinationParentCategory(); + + // verifie que la categorie parente cible est accessible en lecture + if (!target_category -> isReadable()) { + if (!handler) { + return; + } else { + do { + QET::Action todo = handler -> categoryIsNotReadable(target_category); + + // on agit en fonction de la reponse du handler + if (todo == QET::Abort) { + mvt_desc -> abort(); + return; + } else if (todo == QET::Ignore || todo == QET::Managed) { + return; + } else if (todo == QET::Retry || todo == QET::Erase) { + // reessayer = repasser dans la boucle + } else if (todo == QET::Rename) { + // cas non gere + } + } while (!target_category -> isReadable()); + } + } + + // verifie que la source et la destination ne sont pas identiques + if (target_category == this || target_category -> isChildOf(this)) { + if (handler) { + handler -> errorWithACategory(this, tr("La copie d'une cat\351gorie vers elle-m\352me ou vers l'une de ses sous-cat\351gories n\'est pas g\351r\351e.")); + } + return; + } + + // verifie que la categorie parente cible est accessible en ecriture + if (!target_category -> isWritable()) { + if (!handler) { + return; + } else { + do { + QET::Action todo = handler -> categoryIsNotWritable(target_category); + + // on agit en fonction de la reponse du handler + if (todo == QET::Abort) { + mvt_desc -> abort(); + return; + } else if (todo == QET::Ignore || todo == QET::Managed) { + return; + } else if (todo == QET::Retry || todo == QET::Erase) { + // reessayer = repasser dans la boucle + } else if (todo == QET::Rename) { + // cas non gere + } + } while (!target_category -> isWritable()); + } + } + + ElementsCategory *category_copy = 0; + + // verifie que la cible n'existe pas deja + if ((category_copy = target_category -> category(mvt_desc -> finalDestinationInternalName()))) { + if (!handler) { + return; + } else { + do { + // la cible existe deja : on demande au Handler ce qu'on doit faire + QET::Action todo = handler -> categoryAlreadyExists(this, category_copy); + + // on agit en fonction de la reponse du handler + if (todo == QET::Abort) { + mvt_desc -> abort(); + return; + } else if (todo == QET::Ignore || todo == QET::Managed) { + return; + } else if (todo == QET::Erase) { + break; + } else if (todo == QET::Rename) { + mvt_desc -> setFinalDestinationInternalName(handler -> nameForRenamingOperation()); + } + } while ((category_copy = target_category -> category(mvt_desc -> finalDestinationInternalName()))); + } + } + + /* + A ce stade, on peut creer la categorie cible : soit elle n'existe pas, + soit on a l'autorisation de l'ecraser + */ + + // si la cible existe deja, verifie qu'elle est accessible en ecriture + category_copy = target_category -> category(mvt_desc -> finalDestinationInternalName()); + if (category_copy && !category_copy -> isWritable()) { + if (!handler) { + return; + } else { + do { + // la cible n'est pas accessible en ecriture : on demande au Handler ce qu'on doit faire + QET::Action todo = handler -> categoryIsNotWritable(category_copy); + + // on agit en fonction de la reponse du handler + if (todo == QET::Abort) { + mvt_desc -> abort(); + return; + } else if (todo == QET::Ignore || todo == QET::Managed) { + return; + } else if (todo == QET::Retry || todo == QET::Erase) { + // reessayer = repasser dans la boucle + } else if (todo == QET::Rename) { + // cas non gere + } + } while (!category_copy -> isWritable()); + } + } + + // memorise la liste des sous-categories et elements directs + QList categories_list = categories(); + QList elements_list = elements(); + + // cree la categorie cible + category_copy = target_category -> createCategory(mvt_desc -> finalDestinationInternalName()); + if (!category_copy) { + /// @todo la creation a echoue : gerer ce cas + return; + } + + // recopie les noms de la categorie + category_copy -> category_names = category_names; + category_copy -> write(); + mvt_desc -> setCreatedItem(category_copy); + + // copie recursive + if (mvt_desc -> isRecursive()) { + // copie les sous-categories + foreach(ElementsCategory *sub_category, categories_list) { + // cree une description du mouvement a effectuer + MoveElementsDescription sub_category_mvt_desc; + sub_category_mvt_desc.setDestinationParentCategory(category_copy); + // on tente une copie avec le meme nom interne + sub_category_mvt_desc.setOriginalDestinationInternalName(sub_category -> pathName()); + sub_category_mvt_desc.setFinalDestinationInternalName(sub_category -> pathName()); + sub_category_mvt_desc.setHandler(handler); + sub_category_mvt_desc.setRecursive(true); + + // effectue la copie + sub_category -> copy(&sub_category_mvt_desc); + + // abort si besoin + if (sub_category_mvt_desc.mustAbort()) { + mvt_desc -> abort(); + return; + } + } + + // copie les elements + foreach(ElementDefinition *element, elements_list) { + // cree une description du mouvement a effectuer + MoveElementsDescription element_mvt_desc; + element_mvt_desc.setDestinationParentCategory(category_copy); + // on tente une copie avec le meme nom interne + element_mvt_desc.setOriginalDestinationInternalName(element -> pathName()); + element_mvt_desc.setFinalDestinationInternalName(element -> pathName()); + element_mvt_desc.setHandler(handler); + + element -> copy(&element_mvt_desc); + + // abort si besoin + if (element_mvt_desc.mustAbort()) { + mvt_desc -> abort(); + return; + } + } + } +} + +/** + Cette methode copie la categorie recursivement puis la supprime, ce qui + equivaut a un deplacement. Elle cree donc un nouvel objet representant la + categorie, qu'elle retourne ensuite. + @param target_category Categorie cible pour le deplacement ; cette categorie + sera deplacee de faon a devenir une sous-categorie de la categorie cible + @param handler Gestionnaire d'erreurs a utiliser pour effectuer le + deplacement. Si handler vaut 0, les erreurs, problemes et questions sont + purement et simplement ignores. + @return Un pointeur vers la categorie apres le deplacement, ou 0 si le + processus a echoue. +*/ +ElementsCollectionItem *ElementsCategory::move(ElementsCategory *target_category, MoveElementsHandler *handler) { + if (!target_category) return(0); + + // echec si le path name de cette categorie est vide + QString cat_name(pathName()); + if (cat_name.isEmpty()) return(0); + + // cree une description du mouvement a effectuer + MoveElementsDescription mvt_desc; + mvt_desc.setDestinationParentCategory(target_category); + // on tente une copie avec le meme nom interne + mvt_desc.setOriginalDestinationInternalName(cat_name); + mvt_desc.setFinalDestinationInternalName(cat_name); + mvt_desc.setHandler(handler); + mvt_desc.setRecursive(true); // un deplacement est forcement recursif + + // effectue le mouvement + move(&mvt_desc); + return(mvt_desc.createdItem()); +} + +/** + Methode privee effectuant le deplacement de cette categorie a partir d'une + description du mouvement + @param mvt_dsc Description du mouvement +*/ +void ElementsCategory::move(MoveElementsDescription *mvt_desc) { + // quelques pointeurs pour simplifier l'ecriture de la methode + MoveElementsHandler *handler = mvt_desc -> handler(); + ElementsCategory *target_category = mvt_desc -> destinationParentCategory(); + + // empeche le deplacement s'il s'agit d'une categorie racine + if (isRootCategory()) { + if (handler) handler -> errorWithACategory(this, tr("Il n'est pas possible de d\351placer une collection.")); + return; + } + + // empeche le deplacement de la categorie dans une sous-categorie + if (target_category == this || target_category -> isChildOf(this)) { + if (handler) handler -> errorWithACategory(this, tr("Le d\351placement d'une cat\351gorie dans une de ses sous-cat\351gories n'est pas possible.")); + return; + } + + // effectue une copie non recursive de cette categorie + ElementsCollectionItem *item_copy = copy(target_category, handler, false); + if (!item_copy) return; + ElementsCategory *category_copy = item_copy -> toCategory(); + if (!category_copy) return; + + // memorise la liste des sous-categories et elements directs + QList categories_list = categories(); + QList elements_list = elements(); + + // booleen indiquant si on pourra tenter de supprimer la categorie apres la copie + bool remove_category = true; + + // tente de deplacer les sous-categories + foreach(ElementsCategory *sub_category, categories_list) { + // cree une description du mouvement a effectuer + MoveElementsDescription sub_category_mvt_desc; + sub_category_mvt_desc.setDestinationParentCategory(category_copy); + // on tente un deplacement avec le meme nom interne + sub_category_mvt_desc.setOriginalDestinationInternalName(sub_category -> pathName()); + sub_category_mvt_desc.setFinalDestinationInternalName(sub_category -> pathName()); + sub_category_mvt_desc.setHandler(handler); + sub_category_mvt_desc.setRecursive(true); + + // effectue le deplacement + sub_category -> move(&sub_category_mvt_desc); + + // abort si besoin + if (sub_category_mvt_desc.mustAbort()) { + mvt_desc -> abort(); + return; + } + + // si la sous-categorie n'a pas ete supprimee, on ne supprimera pas cette categorie + if (remove_category) remove_category = sub_category_mvt_desc.sourceItemWasDeleted(); + + // si la sous-categorie n'a pas ete supprimee, ... + if (!sub_category_mvt_desc.sourceItemWasDeleted()) { + // on ne supprimera pas cette categorie + if (remove_category) remove_category = false; + } + } + + // tente de deplacer les elements + foreach(ElementDefinition *element, elements_list) { + // cree une description du mouvement a effectuer + MoveElementsDescription element_mvt_desc; + element_mvt_desc.setDestinationParentCategory(category_copy); + // on tente une copie avec le meme nom interne + element_mvt_desc.setOriginalDestinationInternalName(element -> pathName()); + element_mvt_desc.setFinalDestinationInternalName(element -> pathName()); + element_mvt_desc.setHandler(handler); + + element -> move(&element_mvt_desc); + + // abort si besoin + if (element_mvt_desc.mustAbort()) { + mvt_desc -> abort(); + return; + } + + // si l'element n'a pas ete supprime, ... + if (!element_mvt_desc.sourceItemWasDeleted()) { + // on ne supprimera pas cette categorie + if (remove_category) remove_category = false; + } + } + + // supprime cette categorie (sinon il ne s'agirait que d'une copie, pas d'un deplacement) + if (remove_category) { + reload(); + bool category_deletion = remove(); + mvt_desc -> setSourceItemDeleted(category_deletion); + if (!category_deletion && handler) { + handler -> errorWithACategory(this, tr("La suppression de cette cat\351gorie a \351chou\351.")); + } + } +} + +/** + Cette methode supprime recursivement les elements inutilises dans le projet. + Si cette categorie n'est pas rattachee a un projet, elle ne fait rien + @param handler Gestionnaire d'erreurs a utiliser pour effectuer le + nettoyage. Si handler vaut 0, les erreurs, problemes et questions sont + purement et simplement ignores. +*/ +void ElementsCategory::deleteUnusedElements(MoveElementsHandler *handler) { + // si cette categorie n'est pas rattachee a un projet, elle ne fait rien + QETProject *parent_project = project(); + if (!parent_project) return; + + // supprime les elements inutilises dans les sous-categories + foreach(ElementsCategory *sub_category, categories()) { + sub_category -> deleteUnusedElements(handler); + } + + // supprime les elements inutilises dans cette categorie + foreach(ElementDefinition *element, elements()) { + if (!parent_project -> usesElement(element -> location())) { + bool element_deletion = element -> remove(); + if (!element_deletion && handler) { + handler -> errorWithAnElement(element, tr("Impossible de supprimer l'\351l\351ment")); + } + } + } +} + +/** + Cette methode supprime toutes les sous-categories de cette categories qui + ne contiennent pas d'elements ou de categories contenant des elements. + @param handler Gestionnaire d'erreurs a utiliser pour effectuer le + nettoyage. Si handler vaut 0, les erreurs, problemes et questions sont + purement et simplement ignores. +*/ +void ElementsCategory::deleteEmptyCategories(MoveElementsHandler *handler) { + // supprime les sous-categories qui ne comportent pas d'elements + foreach(ElementsCategory *sub_category, categories()) { + sub_category -> deleteEmptyCategories(handler); + sub_category -> reload(); + if (!sub_category -> isEmpty()) { + bool category_deletion = sub_category -> remove(); + if (!category_deletion && handler) { + handler -> errorWithACategory(sub_category, tr("Impossible de supprimer la cat\351gorie")); + } + } + } +} + +/** + @return true si cette collection est vide (pas de sous-categorie, pas + d'element), false sinon. +*/ +bool ElementsCategory::isEmpty() { + return(categories().count() || elements().count()); } /** @@ -85,7 +620,7 @@ void ElementsCategory::loadNames() { @return Le nom affichable de la categorie */ QString ElementsCategory::name() const { - return(category_names.name(dirName())); + return(category_names.name(pathName())); } /** @@ -113,59 +648,9 @@ void ElementsCategory::addName(const QString &lang, const QString &value) { } /** - Cree la categorie - @return true si la creation a reussi, false sinon + Specifie les noms de la categorie. + Tous les noms precedemment connus sont perdus */ -bool ElementsCategory::write() const { - - // cree le dossier de la categorie - if (!mkpath(path())) return(false); - - // prepare la structure XML - QDomDocument document; - QDomElement root = document.createElement("qet-directory"); - document.appendChild(root); - root.appendChild(category_names.toXml(document)); - - // repere le chemin du fichier de configuration de la categorie - QFile directory_conf(canonicalPath() + "/qet_directory"); - - // ouvre le fichier - if (!directory_conf.open(QIODevice::Text | QIODevice::WriteOnly)) return(false); - - // ecrit le fichier - QTextStream out(&directory_conf); - out.setCodec("UTF-8"); - out << document.toString(4); - - // ferme le fichier - directory_conf.close(); - - return(true); -} - -/** - Supprime la categorie - @return true si la suppression a reussie, false sinon -*/ -bool ElementsCategory::remove() const { - return(rmdir(absolutePath())); -} - -/** - @return true s'il est possible d'ecrire le fichier qet_directory dans la - categorie -*/ -bool ElementsCategory::isWritable() const { - // informations sur le dossier de la categorie - QFileInfo category(canonicalPath()); - QFileInfo qet_directory(canonicalPath() + "/.qet_directory"); - /* - soit .qet_directory n'existe pas et le dossier est accessible en ecriture, - soit .qet_directory existe et est accessible en ecriture - */ - return( - (!qet_directory.exists() && category.isWritable()) ||\ - (qet_directory.exists() && qet_directory.isWritable()) - ); +void ElementsCategory::setNames(const NamesList &names_list) { + category_names = names_list; } diff --git a/sources/elementscategory.h b/sources/elementscategory.h index 06368be8e..0e7eeded1 100644 --- a/sources/elementscategory.h +++ b/sources/elementscategory.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -17,39 +17,72 @@ */ #ifndef ELEMENTS_CATEGORY_H #define ELEMENTS_CATEGORY_H -#include +#include "elementscollectionitem.h" #include "nameslist.h" +#include "elementslocation.h" +class ElementDefinition; +class ElementsCollection; +class MoveElementsHandler; +class MoveElementsDescription; /** - Cette classe represente une categorie d'elements. - Une categorie d'elements est en fait un dossier avec un fichier - qet_directory contenant ses caracteristiques (pour le moment : ses noms). + Cette classe abstraite represente une categorie d'elements. */ -class ElementsCategory : public QDir { +class ElementsCategory : public ElementsCollectionItem { + Q_OBJECT + // constructeurs, destructeur public: - ElementsCategory(const QString & = QString()); + ElementsCategory(ElementsCategory * = 0, ElementsCollection * = 0); virtual ~ElementsCategory(); private: ElementsCategory(const ElementsCategory &); - // attributs - private: - NamesList category_names; - - // methodes + // Implementations de methodes virtuelles pures des classes parentes public: - QString name() const; - NamesList categoryNames() const; - void clearNames(); - void addName(const QString &, const QString &); - bool write() const; - bool remove() const; - bool isWritable() const; - //bool move(const QString &new_parent); + virtual QETProject *project(); + virtual void setProject(QETProject *); + virtual QString protocol(); + virtual void setProtocol(const QString &); + virtual ElementsCategory *parentCategory(); + virtual QList parentCategories(); + virtual bool hasParentCategory(); + virtual ElementsCollection *parentCollection(); + virtual bool hasParentCollection(); + virtual bool isChildOf(ElementsCollectionItem *); + virtual QString fullVirtualPath(); + virtual ElementsLocation location(); + virtual bool isRootCategory() const; + virtual bool isCollection() const; + virtual bool isCategory() const; + virtual bool isElement() const; + virtual ElementsCollection *toCollection(); + virtual ElementsCategory *toCategory(); + virtual ElementsCategory *toPureCategory(); + virtual ElementDefinition *toElement(); + virtual ElementsCollectionItem *copy(ElementsCategory *, MoveElementsHandler *, bool = true); + virtual ElementsCollectionItem *move(ElementsCategory *, MoveElementsHandler *); + virtual void deleteUnusedElements(MoveElementsHandler *handler); + virtual void deleteEmptyCategories(MoveElementsHandler *handler); + virtual bool isEmpty(); - private: - bool rmdir(const QString &) const; - void loadNames(); + // Methodes propres a la classe ElementsCategory + public: + virtual QString name() const; + virtual NamesList categoryNames() const; + virtual void clearNames(); + virtual void addName(const QString &, const QString &); + virtual void setNames(const NamesList &); + void copy(MoveElementsDescription *); + void move(MoveElementsDescription *); + + // attributs + protected: + /// Collection parente + ElementsCollection *parent_collection_; + /// Categorie parente + ElementsCategory *parent_category_; + /// Liste des noms de la categorie + NamesList category_names; }; #endif diff --git a/sources/elementscategorydeleter.cpp b/sources/elementscategorydeleter.cpp index 8ee0014e5..1e969eb04 100644 --- a/sources/elementscategorydeleter.cpp +++ b/sources/elementscategorydeleter.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -16,17 +16,25 @@ along with QElectroTech. If not, see . */ #include "elementscategorydeleter.h" +#include "qetapp.h" /** Constructeur - @param category_path Chemin du dossier representant la categorie a supprimer + @param category_path Chemin virtuel de la categorie a supprimer @param parent QWidget parent */ -ElementsCategoryDeleter::ElementsCategoryDeleter(const QString &category_path, QWidget *parent) : +ElementsCategoryDeleter::ElementsCategoryDeleter(const ElementsLocation &category_path, QWidget *parent) : QWidget(parent), - cat(category_path), - empty_category_path(category_path.isNull()) + category(0) { + // recupere la categorie a supprimer + ElementsCollectionItem *category_item = QETApp::collectionItem(category_path); + if (!category_item) return; + + // on exige une collection ou une categorie + if (!category_item -> isCollection() && !category_item -> isCategory()) return; + + category = category_item; } /// Destructeur @@ -37,45 +45,66 @@ ElementsCategoryDeleter::~ElementsCategoryDeleter() { Supprime la categorie et ses elements : verifie l'existence du dossier, demande deux fois confirmation a l'utilisateur et avertit ce dernier si la suppression a echoue. + @return true si la suppression a ete effectuee, false sinon */ -void ElementsCategoryDeleter::exec() { +bool ElementsCategoryDeleter::exec() { // verifie l'existence de la categorie - if (!cat.exists() || empty_category_path) return; + if (!category) return(false); - QString cat_name(cat.name().replace("<", "<").replace(">", ">")); + // gere le cas ou la suppression d'une collection est demandee + if (category -> isCollection()) { + QMessageBox::StandardButton answer_0 = QMessageBox::question( + this, + tr("Vider la collection ?", "message box title"), + tr("\312tes-vous s\373r de vouloir vider cette collection ?", "message box content"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel + ); + if (answer_0 != QMessageBox::Yes) return(false); + } + /** + @todo Regression : rafficher le nom de la categorie supprimee + */ + // QString cat_name(category -> name().replace("<", "<").replace(">", ">")); + + + // avertissement pour la suppression d'une collection // confirmation #1 QMessageBox::StandardButton answer_1 = QMessageBox::question( this, - tr("Supprimer la cat\351gorie ?"), - tr("\312tes-vous s\373r de vouloir supprimer la cat\351gorie ") + - cat_name - + tr(" ?\n" - "Tous les \351l\351ments et les cat\351gories contenus dans cette " - "cat\351gorie seront supprim\351s"), - QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel + tr("Supprimer la cat\351gorie ?", "message box title"), + tr( + "\312tes-vous s\373r de vouloir supprimer la cat\351gorie ?\nTous " + "les \351l\351ments et les cat\351gories contenus dans cette " + "cat\351gorie seront supprim\351s.", + "message box content" + ), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel ); - if (answer_1 != QMessageBox::Yes) return; + if (answer_1 != QMessageBox::Yes) return(false); // confirmation #2 QMessageBox::StandardButton answer_2 = QMessageBox::question( this, - tr("Supprimer la cat\351gorie ?"), - tr("\312tes-vous vraiment s\373r de vouloir supprimer cette " - "cat\351gorie (") + - cat_name - + tr(") ?\nLes changements seront d\351finitifs."), + tr("Supprimer la cat\351gorie ?", "message box title"), + tr( + "\312tes-vous vraiment s\373r de vouloir supprimer cette " + "cat\351gorie ?\nLes changements seront d\351finitifs.", + "message box content" + ), QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel ); - if (answer_2 != QMessageBox::Yes) return; + if (answer_2 != QMessageBox::Yes) return(false); // supprime la categorie - if (!cat.remove()) { + if (!category -> remove()) { QMessageBox::warning( this, - tr("Suppression de la cat\351gorie"), - tr("La suppression de la cat\351gorie a \351chou\351.\n" - "V\351rifiez vos droits sur le dossier ") + cat.absolutePath() + tr(".") + tr("Suppression de la cat\351gorie", "message box title"), + tr("La suppression de la cat\351gorie a \351chou\351.", "message box content") ); + return(false); } + + return(true); } diff --git a/sources/elementscategorydeleter.h b/sources/elementscategorydeleter.h index 7825b564c..5452c6d64 100644 --- a/sources/elementscategorydeleter.h +++ b/sources/elementscategorydeleter.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -17,29 +17,31 @@ */ #ifndef ELEMENTS_CATEGORY_DELETER_H #define ELEMENTS_CATEGORY_DELETER_H -#include "elementscategory.h" +#include "fileelementscategory.h" +#include "elementslocation.h" #include /** - Cette ckasse represente une couche d'abstraction pour supprimer + Cette classe represente une couche d'abstraction pour supprimer une categorie d'elements et les elements qu'elle contient. - Elle demande notamment confirmation a l'utilisateur par deux fois. + Si la categorie racine d'une collection est fournie, elle sera + videe apres un avertissement. + Cette classe demande toujours confirmation a l'utilisateur par deux fois. */ class ElementsCategoryDeleter : public QWidget { Q_OBJECT // constructeurs, destructeur public: - ElementsCategoryDeleter(const QString &, QWidget * = 0); + ElementsCategoryDeleter(const ElementsLocation &, QWidget * = 0); virtual ~ElementsCategoryDeleter(); private: ElementsCategoryDeleter(const ElementsCategory &); // methodes public slots: - void exec(); + bool exec(); // attributs private: - ElementsCategory cat; - bool empty_category_path; + ElementsCollectionItem *category; }; #endif diff --git a/sources/elementscategoryeditor.cpp b/sources/elementscategoryeditor.cpp index f475cdd49..7d214ce72 100644 --- a/sources/elementscategoryeditor.cpp +++ b/sources/elementscategoryeditor.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -16,9 +16,12 @@ along with QElectroTech. If not, see . */ #include "elementscategoryeditor.h" +#include "elementscollection.h" #include "elementscategory.h" #include "nameslistwidget.h" #include "qet.h" +#include "qetapp.h" +#include "qfilenameedit.h" /** Constructeur fournissant un dialogue d'edition de categorie. @@ -26,36 +29,55 @@ @param edit booleen a true pour le mode edition, a false pour le mode creation @param parent QWidget parent du dialogue */ -ElementsCategoryEditor::ElementsCategoryEditor(const QString &category_path, bool edit, QWidget *parent) : QDialog(parent) { - mode_edit = edit; +ElementsCategoryEditor::ElementsCategoryEditor(const ElementsLocation &category_path, bool edit, QWidget *parent) : + QDialog(parent), + mode_edit(edit) +{ // dialogue basique buildDialog(); - category = new ElementsCategory(category_path); + + // recupere la categorie a editer + ElementsCollectionItem *category_item = QETApp::collectionItem(category_path); + if (category_item) category_item = category_item -> toCategory(); + + if (!category_item || !category_item -> isCategory()) { + QMessageBox::warning( + this, + tr("Cat\351gorie inexistante", "message box title"), + tr("La cat\351gorie demand\351e n'existe pas. Abandon.", "message box content") + ); + return; + } else { + category = category_item -> toPureCategory(); + } + if (mode_edit) { - setWindowTitle(tr("\311diter une cat\351gorie")); + setWindowTitle(tr("\311diter une cat\351gorie", "window title")); connect(buttons, SIGNAL(accepted()), this, SLOT(acceptUpdate())); // edition de categorie = affichage des noms deja existants names_list -> setNames(category -> categoryNames()); + internal_name_ -> setText(category -> pathName()); + internal_name_ -> setReadOnly(true); } else { - setWindowTitle(tr("Cr\351er une nouvelle cat\351gorie")); + setWindowTitle(tr("Cr\351er une nouvelle cat\351gorie", "window title")); connect(buttons, SIGNAL(accepted()), this, SLOT(acceptCreation())); // nouvelle categorie = une ligne pre-machee NamesList cat_names; - cat_names.addName(QLocale::system().name().left(2), tr("Nom de la nouvelle cat\351gorie")); + cat_names.addName(QLocale::system().name().left(2), tr("Nom de la nouvelle cat\351gorie", "default name when creating a new category")); names_list -> setNames(cat_names); - //names_list -> openPersistentEditor(qtwi, 1); } // gestion de la lecture seule if (!category -> isWritable()) { QMessageBox::warning( this, - tr("\311dition en lecture seule"), - tr("Vous n'avez pas les privil\350ges n\351cessaires pour modifier cette cat\351gorie. Elle sera donc ouverte en lecture seule.") + tr("\311dition en lecture seule", "message box title"), + tr("Vous n'avez pas les privil\350ges n\351cessaires pour modifier cette cat\351gorie. Elle sera donc ouverte en lecture seule.", "message box content") ); names_list -> setReadOnly(true); + internal_name_ -> setReadOnly(true); } } @@ -63,7 +85,6 @@ ElementsCategoryEditor::ElementsCategoryEditor(const QString &category_path, boo Destructeur */ ElementsCategoryEditor::~ElementsCategoryEditor() { - delete category; } /** @@ -74,10 +95,17 @@ void ElementsCategoryEditor::buildDialog() { setLayout(editor_layout); names_list = new NamesListWidget(); + internal_name_label_ = new QLabel(tr("Nom interne : ")); + internal_name_ = new QFileNameEdit(); buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); + QHBoxLayout *internal_name_layout = new QHBoxLayout(); + internal_name_layout -> addWidget(internal_name_label_); + internal_name_layout -> addWidget(internal_name_); + + editor_layout -> addLayout(internal_name_layout); editor_layout -> addWidget(new QLabel(tr("Vous pouvez sp\351cifier un nom par langue pour la cat\351gorie."))); editor_layout -> addWidget(names_list); editor_layout -> addWidget(buttons); @@ -93,18 +121,57 @@ void ElementsCategoryEditor::acceptCreation() { // il doit y avoir au moins un nom if (!names_list -> checkOneName()) return; - // chargement des noms - category -> clearNames(); - NamesList names = names_list -> names(); - foreach(QString lang, names.langs()) { - category -> addName(lang, names[lang]); + // exige un nom de dossier de la part de l'utilisateur + if (!internal_name_ -> isValid()) { + QMessageBox::critical( + this, + tr("Nom interne manquant", "message box title"), + tr("Vous devez sp\351cifier un nom interne.", "message box content") + ); + return; + } + QString dirname = internal_name_ -> text(); + + // verifie que le nom interne n'est pas deja pris + if (category -> category(dirname)) { + QMessageBox::critical( + this, + tr("Nom interne d\351j\340 utilis\351", "message box title"), + tr( + "Le nom interne que vous avez choisi est d\351j\340 utilis\351 " + "par une cat\351gorie existante. Veuillez en choisir un autre.", + "message box content" + ) + ); + return; } - // cree un nom de dossier a partir du 1er nom de la categorie - QString dirname = names[names.langs().first()].toLower().replace(" ", "_"); - foreach(QChar c, QET::forbiddenCharacters()) dirname = dirname.replace(c, "_"); - category -> setPath(category -> path() + "/" + dirname); - category -> write(); + // cree la nouvelle categorie + ElementsCategory *new_category = category -> createCategory(dirname); + if (!new_category) { + QMessageBox::critical( + this, + tr("Erreur", "message box title"), + tr("Impossible de cr\351er la cat\351gorie", "message box content") + ); + return; + } + + // chargement des noms + NamesList names = names_list -> names(); + foreach(QString lang, names.langs()) { + new_category -> addName(lang, names[lang]); + } + + // ecriture de la + if (!new_category -> write()) { + QMessageBox::critical( + this, + tr("Erreur", "message box title"), + tr("Impossible d'enregistrer la cat\351gorie", "message box content") + ); + return; + } QDialog::accept(); } @@ -114,6 +181,7 @@ void ElementsCategoryEditor::acceptCreation() { categorie */ void ElementsCategoryEditor::acceptUpdate() { + if (!category -> isWritable()) QDialog::accept(); // il doit y avoir au moins un nom diff --git a/sources/elementscategoryeditor.h b/sources/elementscategoryeditor.h index a790fa639..4dc8749ab 100644 --- a/sources/elementscategoryeditor.h +++ b/sources/elementscategoryeditor.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -18,8 +18,10 @@ #ifndef ELEMENTS_CATEGORY_EDITOR_H #define ELEMENTS_CATEGORY_EDITOR_H #include +#include "elementslocation.h" class ElementsCategory; class NamesListWidget; +class QFileNameEdit; /** Cette classe permet d'editer une categorie existante ou de creer une categorie. @@ -29,7 +31,7 @@ class ElementsCategoryEditor : public QDialog { // constructeurs, destructeur public: - ElementsCategoryEditor(const QString &, bool = true, QWidget * = 0); + ElementsCategoryEditor(const ElementsLocation &, bool = true, QWidget * = 0); virtual ~ElementsCategoryEditor(); private: @@ -40,6 +42,8 @@ class ElementsCategoryEditor : public QDialog { ElementsCategory *category; QDialogButtonBox *buttons; NamesListWidget *names_list; + QLabel *internal_name_label_; + QFileNameEdit *internal_name_; bool mode_edit; // methodes diff --git a/sources/elementscollection.cpp b/sources/elementscollection.cpp new file mode 100644 index 000000000..6571be88d --- /dev/null +++ b/sources/elementscollection.cpp @@ -0,0 +1,406 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#include "elementscollection.h" +#include "elementscategory.h" +#include "elementdefinition.h" +#include "moveelementshandler.h" + +/** + Constructeur + @param parent Item parent +*/ +ElementsCollection::ElementsCollection(ElementsCollectionItem *parent) : + ElementsCollectionItem(parent) +{ +} + +/** + Destructeur +*/ +ElementsCollection::~ElementsCollection() { +} + +/** + @return toujours true +*/ +bool ElementsCollection::isCollection() const { + return(true ); +} + +/** + @return toujours false +*/ +bool ElementsCollection::isRootCategory() const { + return(false); +} + +/** + @return toujours false +*/ +bool ElementsCollection::isCategory() const { + return(false); +} + +/** + @return toujours false +*/ +bool ElementsCollection::isElement() const { + return(false); +} + +/** + @return un pointeur ElementsCollection * sur cette collection +*/ +ElementsCollection *ElementsCollection::toCollection() { + return(this); +} + +/** + @return un pointeur vers la categorie racine de cette collection +*/ +ElementsCategory *ElementsCollection::toCategory() { + return(rootCategory()); +} + +/** + @return toujours 0 - une collection n'est pas a proprement parler une + categorie +*/ +ElementsCategory *ElementsCollection::toPureCategory() { + return(0); +} + +/** + @return toujours 0 - une collection n'est pas un element +*/ +ElementDefinition *ElementsCollection::toElement() { + return(0); +} + +/** + @param target_category Categorie cible pour la copie ; la categorie racine + de cette collection sera copiee en tant que sous-categorie de la categorie + cible + @param handler Gestionnaire d'erreurs a utiliser pour effectuer la copie + @param deep_copy true pour copier recursivement le contenu (elements et + sous-categories) de la categorie racine, false sinon + @return La copie de la categorie, ou 0 si le processus a echoue. +*/ +ElementsCollectionItem *ElementsCollection::copy(ElementsCategory *target_category, MoveElementsHandler *handler, bool deep_copy) { + if (ElementsCategory *root = rootCategory()) { + return(root -> copy(target_category, handler, deep_copy)); + } + return(0); +} + +/** + Il n'est pas possible de deplacer une collection. Cette methode demande + simplement au gestionnaire d'erreur handler d'afficher un message. +*/ +ElementsCollectionItem *ElementsCollection::move(ElementsCategory *, MoveElementsHandler *handler) { + if (ElementsCategory *root = rootCategory()) { + if (handler) { + handler -> errorWithACategory(root, tr("Il n'est pas possible de d\351placer une collection.")); + } + } + return(0); +} + + +/** + Vide la collection de son contenu sans la supprimer + @return true si l'operation a reussi, false sinon +*/ +bool ElementsCollection::removeContent() { + if (!rootCategory()) return(true); + + // demande a la categorie racine de supprimer son contenu + return(rootCategory() -> removeContent()); +} + +/** + Vide la collection de son contenu sans la supprimer + @return true si l'operation a reussi, false sinon +*/ +bool ElementsCollection::remove() { + return(removeContent()); +} + +/** + @return le projet auquel est rattachee cette collection ou 0 si + celle-ci n'est pas liee a un projet. +*/ +QETProject *ElementsCollection::project() { + return(project_); +} + +/** + @param project le nouveau projet auquel est rattachee cette collection + Indiquer 0 pour que cette collection ne soit plus liee a un projet. +*/ +void ElementsCollection::setProject(QETProject *project) { + project_ = project; +} + +/** + @return le protocole utilise par cette collection ; exemples : + "common" pour la collection commune qui utilise des URLs en common:// + "custom" pour la collection perso qui utilise des URLs en custom:// + "embed" pour une collection embarquee qui utilise des URLs en embed:// +*/ +QString ElementsCollection::protocol() { + return(protocol_); +} + +/** + Definit le protocole de cette collection + @param p Nouveau protocole de cette collection +*/ +void ElementsCollection::setProtocol(const QString &p) { + if (!p.isEmpty()) protocol_ = p; +} + +/** + @return toujours 0 - une collection n'a pas de categorie parente. En + revanche, elle peut posseder un projet parent. + @see project() +*/ +ElementsCategory *ElementsCollection::parentCategory() { + return(0); +} + +/** + @return toujours une liste vide - une collection n'a pas de categorie + parente. En revanche, elle peut posseder un projet parent. + @see project() +*/ +QList ElementsCollection::parentCategories() { + return(QList()); +} + +/** + @return toujours false0 - une collection n'a pas de categorie parente. En + revanche, elle peut posseder un projet parent. + @see project() +*/ +bool ElementsCollection::hasParentCategory() { + return(false); +} + +/** + @return toujours 0 - une collection n'a pas de collection parente. En + revanche, elle peut posseder un projet parent. + @see project() +*/ +ElementsCollection *ElementsCollection::parentCollection() { + return(0); +} + +/** + @return toujours false - une collection n'a pas de collection parente. En + revanche, elle peut posseder un projet parent. + @see project() +*/ +bool ElementsCollection::hasParentCollection() { + return(false); +} + +/** + @return toujours false - une collection ne peut etre l'enfant de quoi que ce + soit. +*/ +bool ElementsCollection::isChildOf(ElementsCollectionItem *) { + return(false); +} + +/** + @return toujours une chaine vide +*/ +QString ElementsCollection::pathName() const { + return(QString()); +} + +/** + @return toujours une chaine vide +*/ +QString ElementsCollection::virtualPath() { + return(QString()); +} + +/** + @return le protocole suivi de :// ou une chaine vide si cette collection + n'a pas de protocole defini. +*/ +QString ElementsCollection::fullVirtualPath() { + if (protocol().isEmpty()) return(QString()); + return(protocol() + "://"); +} + +/** + @return l'emplacement de cette collection +*/ +ElementsLocation ElementsCollection::location() { + return(ElementsLocation(fullVirtualPath(), project())); +} + +/** + @return une liste ne contenant que la categorie racine de la collection +*/ +QList ElementsCollection::categories() { + QList result; + if (ElementsCategory *root = rootCategory()) { + result << root; + } + return(result); +} + +/** + @param cat_path chemin d'une categorie sous la forme d'une adresse + virtuelle comme common://cat1/cat2/cat3 + @return la categorie demandee, ou 0 si celle-ci n'a pas ete trouvee +*/ +ElementsCategory *ElementsCollection::category(const QString &cat_path) { + ElementsCategory *root = rootCategory(); + // on doit avoir une categorie racine + if (!root) return(0); + + // le protocole de l'adresse virtuelle doit correspondre + if (!cat_path.startsWith(protocol_ + "://", Qt::CaseInsensitive)) return(0); + + // on enleve le protocole + QString cat_path_(cat_path); + cat_path_.remove(QRegExp("^" + protocol_ + ":\\/\\/", Qt::CaseInsensitive)); + + // on fait appel a la categorie racine pour le reste du traitement + return(root -> category(cat_path_)); +} + +/** + Cree une categorie. La categorie parente doit exister. + @param path chemin d'une categorie a creer sous la forme d'une adresse + virtuelle comme common://cat1/cat2/cat3 + @return la nouvelle categorie demandee, ou 0 en cas d'echec +*/ +ElementsCategory *ElementsCollection::createCategory(const QString &path) { + ElementsCategory *root = rootCategory(); + // on doit avoir une categorie racine + if (!root) return(0); + + // on ne doit pas etre en lecture seule + if (!isWritable()) return(0); + + // le protocole de l'adresse virtuelle doit correspondre + if (!path.startsWith(protocol_ + "://", Qt::CaseInsensitive)) return(0); + + // on enleve le protocole + QString path_(path); + path_.remove(QRegExp("^" + protocol_ + ":\\/\\/", Qt::CaseInsensitive)); + + // on fait appel a la categorie racine pour le reste du traitement + return(root -> createCategory(path_)); +} + +/** + @return une liste vide +*/ +QList ElementsCollection::elements() { + return(QList()); +} + +/** + @param elmt_path chemin d'un element sous la forme d'une adresse + virtuelle comme common://cat1/cat2/cat3/dog.elmt + @return l'element demande, ou 0 si celui-ci n'a pas ete trouve +*/ +ElementDefinition *ElementsCollection::element(const QString &elmt_path) { + ElementsCategory *root = rootCategory(); + // on doit avoir une categorie racine + if (!root) return(0); + + // le protocole de l'adresse virtuelle doit correspondre + if (!elmt_path.startsWith(protocol_ + "://", Qt::CaseInsensitive)) return(0); + + // on enleve le protocole + QString elmt_path_(elmt_path); + elmt_path_.remove(QRegExp("^" + protocol_ + ":\\/\\/", Qt::CaseInsensitive)); + + // on fait appel a la categorie racine pour le reste du traitement + return(root -> element(elmt_path_)); +} + +/** + Cree un element. La categorie parente doit exister. + @param path chemin d'un element a creer sous la forme d'une adresse + virtuelle comme common://cat1/cat2/cat3/dog.elmt + @return le nouvel element demande, ou 0 en cas d'echec +*/ +ElementDefinition *ElementsCollection::createElement(const QString &path) { + ElementsCategory *root = rootCategory(); + // on doit avoir une categorie racine + if (!rootCategory()) return(0); + + // on ne doit pas etre en lecture seule + if (!isWritable()) return(0); + + // le protocole de l'adresse virtuelle doit correspondre + if (!path.startsWith(protocol_ + "://", Qt::CaseInsensitive)) return(0); + + // on enleve le protocole + QString path_(path); + path_.remove(QRegExp("^" + protocol_ + ":\\/\\/", Qt::CaseInsensitive)); + + // on fait appel a la categorie racine pour le reste du traitement + return(root -> createElement(path_)); +} + +/** + @return true si cette collection est vide (pas de sous-categorie, pas + d'element), false sinon. +*/ +bool ElementsCollection::isEmpty() { + ElementsCategory *root_category = rootCategory(); + if (!root_category) return(true); + return(root_category -> isEmpty()); +} + +/** + @param item_path chemin d'un item sous la forme d'une adresse + virtuelle comme common://cat1/cat2/cat3 + @param prefer_collections true pour renvoyer la collection lorsque le + chemin correspond aussi bien a une collection qu'a sa categorie racine + @return l'item demande, ou 0 si celui-ci n'a pas ete trouve +*/ +ElementsCollectionItem *ElementsCollection::item(const QString &item_path, bool prefer_collections) { + ElementsCollectionItem *result = 0; + + // essaye de trouver l'item en tant que categorie + result = category(item_path); + // si la categorie est trouvee, ... + if (result) { + // ... qu'il s'agit d'une categorie racine et que l'utilisateur prefere les collections + if (prefer_collections && result -> isRootCategory()) { + // ... alors on renvoie la collection et non la categorie + result = this; + } + } + + // sinon essaye de trouver l'item en tant qu'element + if (!result) result = element(item_path); + + return(result); +} diff --git a/sources/elementscollection.h b/sources/elementscollection.h new file mode 100644 index 000000000..d57a0e789 --- /dev/null +++ b/sources/elementscollection.h @@ -0,0 +1,92 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef ELEMENTS_COLLECTION_H +#define ELEMENTS_COLLECTION_H +#include +#include "elementscollectionitem.h" +class QETProject; +class ElementsCategory; +class ElementDefinition; +class MoveElementsHandler; +/** + Cette classe abstraite represente une collection d'elements. Il peut s'agir + de la collection QET, de la collection utilisateur ou encore de la + collection fournie par un fichier projet. +*/ +class ElementsCollection : public ElementsCollectionItem { + Q_OBJECT + public: + // constructeurs, destructeur + ElementsCollection(ElementsCollectionItem * = 0); + virtual ~ElementsCollection(); + + private: + ElementsCollection(const ElementsCollection &); + + // Implementations de methodes virtuelles pures des classes parentes + public: + virtual bool isCollection() const; + virtual bool isRootCategory() const; + virtual bool isCategory() const; + virtual bool isElement() const; + virtual ElementsCollection *toCollection(); + virtual ElementsCategory *toCategory(); + virtual ElementsCategory *toPureCategory(); + virtual ElementDefinition *toElement(); + virtual ElementsCollectionItem *copy(ElementsCategory *, MoveElementsHandler *, bool = true); + virtual ElementsCollectionItem *move(ElementsCategory *, MoveElementsHandler *); + virtual bool removeContent(); + virtual bool remove(); + virtual QETProject *project(); + virtual void setProject(QETProject *); + virtual QString protocol(); + virtual void setProtocol(const QString &); + virtual ElementsCategory *parentCategory(); + virtual QList parentCategories(); + virtual bool hasParentCategory(); + virtual ElementsCollection *parentCollection(); + virtual bool hasParentCollection(); + virtual bool isChildOf(ElementsCollectionItem *); + virtual QString pathName() const; + virtual QString virtualPath(); + virtual QString fullVirtualPath(); + virtual ElementsLocation location(); + virtual QList categories(); + virtual ElementsCategory *category(const QString &); + virtual ElementsCategory *createCategory(const QString &); + virtual QList elements(); + virtual ElementDefinition *element(const QString &); + virtual ElementDefinition *createElement(const QString &); + virtual bool isEmpty(); + + // Methodes propres a la classe ElementsCollection + public: + /** + @return la categorie racine de cette collection + */ + virtual ElementsCategory *rootCategory() = 0; + virtual ElementsCollectionItem *item(const QString &, bool = true); + + // attributs + protected: + /// Protocole utilise pour acceder a cette collection + QString protocol_; + /// Projet auquel appartient cette collection + QETProject *project_; +}; +#endif diff --git a/sources/elementscollectionitem.h b/sources/elementscollectionitem.h new file mode 100644 index 000000000..56dbdb770 --- /dev/null +++ b/sources/elementscollectionitem.h @@ -0,0 +1,206 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef ELEMENTS_COLLECTION_ITEM_H +#define ELEMENTS_COLLECTION_ITEM_H +#include +#include "elementslocation.h" +class ElementsCollection; +class ElementsCategory; +class ElementDefinition; +class MoveElementsHandler; +/** + Cette interface est la classe mere pour toutes les classes + modelisant une partie d'une collection d'elements. +*/ +class ElementsCollectionItem : public QObject { + Q_OBJECT + + // constructeurs, destructeur + public: + ElementsCollectionItem(ElementsCollectionItem *parent = 0) : QObject(parent) {}; + virtual ~ElementsCollectionItem() {}; + + private: + ElementsCollectionItem(const ElementsCollectionItem &); + + // methodes + public: + /// @return true si l'item est une collection d'elements + virtual bool isCollection() const = 0; + /// @return true si l'item est une categorie d'elements + virtual bool isCategory() const = 0; + /// @return true si l'item est la categorie racine de sa collection + virtual bool isRootCategory() const = 0; + /// @return true si l'item est un element + virtual bool isElement() const = 0; + + /** + @return un pointeur sur cet item en tant que collection, ou 0 si cet item n'est pas une collection + */ + virtual ElementsCollection *toCollection() = 0; + /** + @return un pointeur sur cet item en tant que categorie. + Si cet objet est une collection, un pointeur valide vers sa categorie racine sera renvoye. + Si cet objet est une categorie, un pointeur valide sera renvoye. + Si cet objet est un element, un pointeur valide vers sa categorie parente sera renvoye. + */ + virtual ElementsCategory *toCategory() = 0; + /** + @return un pointeur sur cet item en tant que categorie si et seulement + si cet objet est une categorie non racine. + Si cet objet est une collection, 0 sera retourne + Si cet objet est une categorie, un pointeur valide sera renvoye. + Si cet objet est un element, 0 sera retourne. + */ + virtual ElementsCategory *toPureCategory() = 0; + /** + @return un pointeur sur cet item en tant qu'element + */ + virtual ElementDefinition *toElement() = 0; + + /** + Copie l'item vers la destination indiquee en parametre ; le booleen + doit etre a true pour une copie recursive, a false sinon. + */ + virtual ElementsCollectionItem *copy(ElementsCategory *, MoveElementsHandler *, bool = true) = 0; + + /** + Deplace l'item vers la destination indiquee en parametre. + */ + virtual ElementsCollectionItem *move(ElementsCategory *, MoveElementsHandler *) = 0; + + /// Recharge l'item + virtual void reload() = 0; + /// @return true si l'item existe + virtual bool exists() = 0; + /// @return true si l'item est lisible + virtual bool isReadable() = 0; + /// @return true si l'item est accessible en ecriture + virtual bool isWritable() = 0; + /** + supprime le contenu de l'item (categories et elements) sans supprimer + l'item lui-meme + @return true si l'operation a reussi, false sinon + */ + virtual bool removeContent() = 0; + /** + supprime le contenu de l'item (categories et elements) puis l'item + lui-meme + @return true si l'operation a reussi, false sinon + */ + virtual bool remove() = 0; + /** + Enregistre l'item + @return true si l'operation a reussi, false sinon + */ + virtual bool write() = 0; + /** + @return le projet auquel appartient cet item + */ + virtual QETProject *project() = 0; + /** + Definit le projet auquel appartient cet item + */ + virtual void setProject(QETProject *) = 0; + /** + @return le protocole utilise pour acceder a la collection de cet item + */ + virtual QString protocol() = 0; + /** + Definit le protocole a utiliser pour acceder a la collection de cet item + */ + virtual void setProtocol(const QString &) = 0; + /** + @return la categorie parente de cet item, ou 0 si celui-ci n'en possede pas + */ + virtual ElementsCategory *parentCategory() = 0; + /** + @return la liste des categories parentes de cet item + */ + virtual QList parentCategories() = 0; + /** + @return true si cet item possede une categorie parente, false sinon + */ + virtual bool hasParentCategory() = 0; + /** + @return la collection parente de cet item, ou 0 si celui-ci n'en possede pas + */ + virtual ElementsCollection *parentCollection() = 0; + /** + @return true si cet item possede une collection parente, false sinon + */ + virtual bool hasParentCollection() = 0; + /** + @param other_item Autre item + @return true si other_item est parent (direct ou indirect) de other_item, false sinon + */ + virtual bool isChildOf(ElementsCollectionItem *) = 0; + /** + @return le nom de cet item dans l'arborescence + */ + virtual QString pathName() const = 0; + /** + @return le chemin virtuel vers cet item dans l'arborescence, sans le protocole + */ + virtual QString virtualPath() = 0; + /** + @return le chemin virtuel vers cet item dans l'arborescence, avec le protocole + */ + virtual QString fullVirtualPath() = 0; + /** + @return l'emplacement de cet item + */ + virtual ElementsLocation location() = 0; + /** + @return true si cet item est represente quelque part sur le systeme de fichiers + */ + virtual bool hasFilePath() = 0; + /** + @return le chemin de cet item sur le systeme de fichiers + */ + virtual QString filePath() = 0; + /** + Definit le chemin de cet item sur le systeme de fichiers + */ + virtual void setFilePath(const QString &) = 0; + /** + @return la liste des categories d'elements contenues dans cet item + */ + virtual QList categories() = 0; + /** + @return une categorie a partir de son chemin virtuel + */ + virtual ElementsCategory *category(const QString &) = 0; + /** + @return une nouvelle categorie creee a partir d'un chemin virtuel + */ + virtual ElementsCategory *createCategory(const QString &) = 0; + /** + @return la liste des elements contenus dans cet item + */ + virtual QList elements() = 0; + /** + @return un element a partir de son chemin virtuel + */ + virtual ElementDefinition *element(const QString &) = 0; + /** + @return un nouvel element cree a partir d'un chemin virtuel + */ + virtual ElementDefinition *createElement(const QString &) = 0; +}; +#endif diff --git a/sources/elementslocation.cpp b/sources/elementslocation.cpp new file mode 100644 index 000000000..365b003ff --- /dev/null +++ b/sources/elementslocation.cpp @@ -0,0 +1,207 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#include "elementslocation.h" +#include "qetapp.h" + +/** + Constructeur par defaut +*/ +ElementsLocation::ElementsLocation() : project_(0) { +} + +/** + Constructeur + @param p Chemin de l'emplacement de l'element + @param pr Projet de l'emplacement de l'element +*/ +ElementsLocation::ElementsLocation(const QString &p, QETProject *pr) : + project_(pr) +{ + setPath(p); +} + +/** + Destructeur +*/ +ElementsLocation::~ElementsLocation() { +} + +/** + Constructeur de copie + @param other Autre emplacement d'element a copier +*/ +ElementsLocation::ElementsLocation(const ElementsLocation &other) : + path_(other.path_), + project_(other.project_) +{ +} + +/** + Operateur d'affectation + @param other Autre emplacement d'element a affecter +*/ +ElementsLocation &ElementsLocation::operator=(const ElementsLocation &other) { + path_ = other.path_; + project_ = other.project_; + return(*this); +} + +/** + Operateur de comparaison + @param other Autre emplacement d'element a comparer + @return true si other et cet ElementsLocation sont identiques, false sinon +*/ +bool ElementsLocation::operator==(const ElementsLocation &other) const { + return( + path_ == other.path_ &&\ + project_ == other.project_ + ); +} + +/** + Operateur de comparaison + @param other Autre emplacement d'element a comparer + @return true si other et cet ElementsLocation sont differents, false sinon +*/ +bool ElementsLocation::operator!=(const ElementsLocation &other) const { + return( + path_ != other.path_ ||\ + project_ != other.project_ + ); +} + +/** + @return le nom de base de l'element +*/ +QString ElementsLocation::baseName() const { + QRegExp regexp("^.*([^/]+)\\.elmt$"); + if (regexp.exactMatch(path_)) { + return(regexp.capturedTexts().at(1)); + } + return(QString()); +} + +/** + @return Le chemin virtuel de cet emplacement +*/ +QString ElementsLocation::path() const { + return(path_); +} + +/** + Change le chemin virtuel de cet emplacement + @param p Nouveau chemin virtuel +*/ +void ElementsLocation::setPath(const QString &p) { +#ifdef Q_OS_WIN32 + // sous Windows : on convertit les backslashs en slashs + path_ = QDir::fromNativeSeparators(p); +#else + // ailleurs : si on detecte des backslashs, on tente d'etre "compatible" + path_ = p; + path_.replace("\\", "/"); +#endif +} + +/** + Ajoute une chaine au chemin + @param string Chaine a ajouter + @return true si l'operation a reussi, false si l'operation n'a pas de sens. + Par exemple, il n'y a pas de sens a vouloir ajouter quelque chose apres le + chemin d'un element. +*/ +bool ElementsLocation::addToPath(const QString &string) { + if (path_.endsWith(".elmt", Qt::CaseInsensitive)) return(false); + if (!path_.endsWith("/") && !string.startsWith("/")) path_ += "/"; + path_ += string; + return(true); +} + +/** + @return le projet de cet emplacement ou 0 si celui-ci n'est pas lie a + un projet. +*/ +QETProject *ElementsLocation::project() const { + return(project_); +} + +/** + @param project le nouveau projet pointe par cet emplacement + Indiquer 0 pour que cet emplacement ne soit plus lie a un projet. +*/ +void ElementsLocation::setProject(QETProject *project) { + project_ = project; +} + +/** + @return true si l'emplacement semble utilisable (chemin virtuel non vide). +*/ +bool ElementsLocation::isNull() const { + return(path_.isEmpty()); +} + +/** + @return Une chaine de caracteres representant l'emplacement +*/ +QString ElementsLocation::toString() const { + QString result; + if (project_) { + int project_id = QETApp::projectId(project_); + if (project_id != -1) { + result += "project" + QString().setNum(project_id) + "+"; + } + } + result += path_; + return(result); +} + +/** + Charge l'emplacemant a partir d'une chaine de caractere du type + project42+embed://foo/bar/thing.elmt + @param string Une chaine de caracteres representant l'emplacement +*/ +void ElementsLocation::fromString(const QString &string) { + QRegExp embedded("^project([0-9]+)\\+(embed:\\/\\/.*)$", Qt::CaseInsensitive); + if (embedded.exactMatch(string)) { + bool conv_ok = false; + uint project_id = embedded.capturedTexts().at(1).toUInt(&conv_ok); + if (conv_ok) { + QETProject *the_project = QETApp::project(project_id); + if (the_project) { + path_ = embedded.capturedTexts().at(2); + project_ = the_project; + return; + } + } + } + + // fallback : le chemin devient la chaine complete et aucun projet n'est utilise + path_ = string; + project_ = 0; +} + +/** + @param string Une chaine de caracteres representant l'emplacement + @return un emplacemant a partir d'une chaine de caractere du type + project42+embed://foo/bar/thing.elmt +*/ +ElementsLocation ElementsLocation::locationFromString(const QString &string) { + ElementsLocation location; + location.fromString(string); + return(location); +} diff --git a/sources/elementslocation.h b/sources/elementslocation.h new file mode 100644 index 000000000..b1b847de7 --- /dev/null +++ b/sources/elementslocation.h @@ -0,0 +1,56 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef ELEMENTS_LOCATION_H +#define ELEMENTS_LOCATION_H +#include +class QETProject; +/** + Cette classe represente la localisation, l'emplacement d'un element ou + d'une categorie, voire d'une collection... dans une collection. Elle + encapsule un chemin virtuel. +*/ +class ElementsLocation { + // constructeurs, destructeur et operateur d'affectation + public: + ElementsLocation(); + explicit ElementsLocation(const QString &, QETProject * = 0); + ElementsLocation(const ElementsLocation &); + virtual ~ElementsLocation(); + ElementsLocation &operator=(const ElementsLocation &); + bool operator==(const ElementsLocation &) const; + bool operator!=(const ElementsLocation &) const; + + // methodes + public: + QString baseName() const; + QString path() const; + void setPath(const QString &); + bool addToPath(const QString &); + QETProject *project() const; + void setProject(QETProject *); + bool isNull() const; + QString toString() const; + void fromString(const QString &); + static ElementsLocation locationFromString(const QString &); + + // attributs + private: + QString path_; + QETProject *project_; +}; +#endif diff --git a/sources/elementspanel.cpp b/sources/elementspanel.cpp index f8d75d622..e9eeff918 100644 --- a/sources/elementspanel.cpp +++ b/sources/elementspanel.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -17,28 +17,43 @@ */ #include "elementspanel.h" #include "qetapp.h" +#include "qetproject.h" +#include "diagram.h" #include "elementscategory.h" -#include "elementscategoryeditor.h" -#include "elementscategorydeleter.h" -#include "elementdeleter.h" #include "customelement.h" -#include "qetelementeditor.h" +#include "fileelementscollection.h" +#include "fileelementdefinition.h" + +/* + Lorsque le flag ENABLE_PANEL_DND_CHECKS est defini, le panel d'elements + effectue des verifications lors des drag'n drop d'elements et categories. + Par exemple, il verifie qu'une categorie cible est accessible en ecriture + avant d'y autoriser le drop d'un element. + Supprimer ce flag permet de tester le comportement des fonctions de gestion + des items (copy, move, etc.). +*/ +#define ENABLE_PANEL_DND_CHECKS /** Constructeur @param parent Le QWidget parent du panel d'appareils */ -ElementsPanel::ElementsPanel(QWidget *parent) : QTreeWidget(parent) { +ElementsPanel::ElementsPanel(QWidget *parent) : + QTreeWidget(parent), + common_collection_item_(0), + custom_collection_item_(0) +{ // selection unique setSelectionMode(QAbstractItemView::SingleSelection); setColumnCount(1); + setExpandsOnDoubleClick(false); header() -> hide(); // drag'n drop autorise setDragEnabled(true); - setAcceptDrops(false); - setDropIndicatorShown(false); + setAcceptDrops(true); + setDropIndicatorShown(true); // taille des elements setIconSize(QSize(50, 50)); @@ -47,11 +62,8 @@ ElementsPanel::ElementsPanel(QWidget *parent) : QTreeWidget(parent) { reload(); // la premiere fois, etend le premier niveau des collections - QList items = findItems("*", Qt::MatchWildcard); - if (items.count() == 2) { - items[0] -> setExpanded(true); - items[1] -> setExpanded(true); - } + if (common_collection_item_) common_collection_item_ -> setExpanded(true); + if (custom_collection_item_) custom_collection_item_ -> setExpanded(true); // force du noir sur une alternance de blanc (comme le schema) et de gris // clair, avec du blanc sur bleu pas trop fonce pour la selection @@ -76,251 +88,604 @@ ElementsPanel::ElementsPanel(QWidget *parent) : QTreeWidget(parent) { ElementsPanel::~ElementsPanel() { } -/// @return true si un element est selectionne, false sinon -bool ElementsPanel::selectedItemIsAnElement() const { - QFileInfo infos_file = selectedFile(); - if (!infos_file.exists()) return(false); - return(infos_file.isFile()); +/** + @param qtwi Un QTreeWidgetItem + @return true si qtwi represente une collection, false sinon +*/ +bool ElementsPanel::itemIsACollection(QTreeWidgetItem *qtwi) const { + if (ElementsCollectionItem *qtwi_item = collectionItemForItem(qtwi)) { + return(qtwi_item -> isCollection()); + } + return(false); } -/// @return true si une categorie est selectionnee, false sinon +/** + @param qtwi Un QTreeWidgetItem + @return true si qtwi represente une categorie, false sinon +*/ +bool ElementsPanel::itemIsACategory(QTreeWidgetItem *qtwi) const { + if (ElementsCollectionItem *qtwi_item = collectionItemForItem(qtwi)) { + return(qtwi_item -> isCategory()); + } + return(false); +} + +/** + @param qtwi Un QTreeWidgetItem + @return true si qtwi represente un element, false sinon +*/ +bool ElementsPanel::itemIsAnElement(QTreeWidgetItem *qtwi) const { + if (ElementsCollectionItem *qtwi_item = collectionItemForItem(qtwi)) { + return(qtwi_item -> isElement()); + } + return(false); +} + +/** + @param qtwi Un QTreeWidgetItem + @return true si qtwi represente un projet, false sinon +*/ +bool ElementsPanel::itemIsAProject(QTreeWidgetItem *qtwi) const { + return(projects_.contains(qtwi)); +} + +/** + @param qtwi Un QTreeWidgetItem + @return true si ce que represente qtwi est accessible en ecriture +*/ +bool ElementsPanel::itemIsADiagram(QTreeWidgetItem *qtwi) const { + return(diagrams_.contains(qtwi)); +} + +/** + @param qtwi Un QTreeWidgetItem + @return true si le qtwi est associe a une ElementsLocation +*/ +bool ElementsPanel::itemHasLocation(QTreeWidgetItem *qtwi) const { + return(locations_.contains(qtwi)); +} + +/** + @param qtwi Un QTreeWidgetItem + @return true si qtwi represente un element, false sinon +*/ +bool ElementsPanel::itemIsWritable(QTreeWidgetItem *qtwi) const { + if (ElementsCollectionItem *qtwi_item = collectionItemForItem(qtwi)) { + return(qtwi_item -> isWritable()); + } + return(false); +} + +/** + @param qtwi Un QTreeWidgetItem + @return L'ElementsCollectionItem represente par qtwi, ou 0 si qtwi ne + represente pas un ElementsCollectionItem +*/ +ElementsCollectionItem *ElementsPanel::collectionItemForItem(QTreeWidgetItem *qtwi) const { + if (locations_.contains(qtwi)) { + return(QETApp::collectionItem(locations_[qtwi])); + } + return(0); +} + +/** + @param qtwi Un QTreeWidgetItem + @return Le projet represente par qtwi, ou 0 si qtwi ne represente pas un + projet +*/ +QETProject *ElementsPanel::projectForItem(QTreeWidgetItem *qtwi) const { + if (projects_.contains(qtwi)) { + return(projects_[qtwi]); + } + return(0); +} + +/** + @param qtwi Un QTreeWidgetItem + @return Le schema represente par qtwi, ou 0 si qtwi ne represente pas un + schema +*/ +Diagram *ElementsPanel::diagramForItem(QTreeWidgetItem *qtwi) const { + if (diagrams_.contains(qtwi)) { + return(diagrams_[qtwi]); + } + return(0); +} + +/** + @param qtwi QTreeWidgetItem dont on veut connaitre l'emplacement + @return L'emplacement associe a qtwi, ou un emplacement nul s'il n'y a pas + d'emplacement associe a qtwi +*/ +ElementsLocation ElementsPanel::locationForItem(QTreeWidgetItem *qtwi) const { + if (locations_.contains(qtwi)) { + return(locations_[qtwi]); + } + return(ElementsLocation()); +} + +/** + @return true si une collection est selectionnee, false sinon +*/ +bool ElementsPanel::selectedItemIsACollection() const { + if (ElementsCollectionItem *selected_item = selectedItem()) { + return(selected_item -> isCollection()); + } + return(false); +} + +/** + @return true si une categorie est selectionnee, false sinon +*/ bool ElementsPanel::selectedItemIsACategory() const { - QFileInfo infos_file = selectedFile(); - if (!infos_file.exists()) return(false); - return(infos_file.isDir()); + if (ElementsCollectionItem *selected_item = selectedItem()) { + return(selected_item -> isCategory()); + } + return(false); +} + +/** + @return true si un element est selectionne, false sinon +*/ +bool ElementsPanel::selectedItemIsAnElement() const { + if (ElementsCollectionItem *selected_item = selectedItem()) { + return(selected_item -> isElement()); + } + return(false); +} + +/** + @return true si un projet est selectionne, false sinon +*/ +bool ElementsPanel::selectedItemIsAProject() const { + return(projects_.contains(currentItem())); +} + +/** + @return true si un schema est selectionne, false sinon +*/ +bool ElementsPanel::selectedItemIsADiagram() const { + return(diagrams_.contains(currentItem())); +} + +/** + @return true si l'element selectionne est associe a une ElementsLocation +*/ +bool ElementsPanel::selectedItemHasLocation() const { + return(locations_.contains(currentItem())); +} + +/** + @return true si l'item selectionne est accessible en ecriture, false sinon +*/ +bool ElementsPanel::selectedItemIsWritable() const { + if (ElementsCollectionItem *selected_item = selectedItem()) { + return(selected_item -> isWritable()); + } + return(false); +} + +/** + @return la collection, la categorie ou l'element selectionne(e) +*/ +ElementsCollectionItem *ElementsPanel::selectedItem() const { + ElementsLocation selected_location(selectedLocation()); + if (!selected_location.isNull()) { + return(QETApp::collectionItem(selected_location)); + } + return(0); +} + +/** + @return Le projet selectionne, ou 0 s'il n'y en a pas +*/ +QETProject *ElementsPanel::selectedProject() const { + return(projectForItem(currentItem())); +} + +/** + @return Le schema selectionne, ou 0 s'il n'y en a pas +*/ +Diagram *ElementsPanel::selectedDiagram() const { + return(diagramForItem(currentItem())); +} + +/** + @return L'emplacement selectionne, ou un emplacement nul s'il n'y en a pas +*/ +ElementsLocation ElementsPanel::selectedLocation() const { + return(locationForItem(currentItem())); +} + +/** + Gere l'entree d'un drag'n drop. L'evenement est accepte si les donnees + fournies contiennent un type MIME representant une categorie ou un element + QET. + @param e QDragEnterEvent decrivant l'entree du drag'n drop +*/ +void ElementsPanel::dragEnterEvent(QDragEnterEvent *e) { + if (e -> mimeData() -> hasFormat("application/x-qet-category-uri")) { + e -> acceptProposedAction(); + } else if (e -> mimeData() -> hasFormat("application/x-qet-element-uri")) { + e -> acceptProposedAction(); + } } /** Gere le mouvement lors d'un drag'n drop */ -void ElementsPanel::dragMoveEvent(QDragMoveEvent */*e*/) { +void ElementsPanel::dragMoveEvent(QDragMoveEvent *e) { + // scrolle lorsque le curseur est pres des bords + int limit = 40; + QScrollBar *scroll_bar = verticalScrollBar(); + if (e -> pos().y() < limit) { + scroll_bar -> setValue(scroll_bar -> value() - 1); + } else if (e -> pos().y() > height() - limit) { + scroll_bar -> setValue(scroll_bar -> value() + 1); + } + + // recupere la categorie cible pour le deplacement / la copie + ElementsCategory *target_category = categoryForPos(e -> pos()); + if (!target_category) { + e -> ignore(); + return; + } + + // recupere la source (categorie ou element) pour le deplacement / la copie + ElementsLocation dropped_location = ElementsLocation::locationFromString(e -> mimeData() -> text()); + ElementsCollectionItem *source_item = QETApp::collectionItem(dropped_location, false); + if (!source_item) { + e -> ignore(); + return; + } + +#ifdef ENABLE_PANEL_DND_CHECKS + // ne prend pas en consideration le drop d'un item sur lui-meme ou une categorie imbriquee + if ( + source_item -> location() == target_category -> location() ||\ + target_category -> isChildOf(source_item) + ) { + e -> ignore(); + return; + } + + // s'assure que la categorie cible est accessible en ecriture + if (!target_category -> isWritable()) { + e -> ignore(); + return; + } +#endif + + e -> accept(); + /// @todo mettre en valeur le lieu de depot } /** Gere le depot lors d'un drag'n drop + @param e QDropEvent decrivant le depot */ -void ElementsPanel::dropEvent(QDropEvent */*e*/) { +void ElementsPanel::dropEvent(QDropEvent *e) { + // recupere la categorie cible pour le deplacement / la copie + ElementsCategory *target_category = categoryForPos(e -> pos()); + if (!target_category) { + e -> ignore(); + return; + } + + // recupere la source (categorie ou element) pour le deplacement / la copie + ElementsLocation dropped_location = ElementsLocation::locationFromString(e -> mimeData() -> text()); + ElementsCollectionItem *source_item = QETApp::collectionItem(dropped_location, false); + if (!source_item) { + e -> ignore(); + return; + } + +#ifdef ENABLE_PANEL_DND_CHECKS + // ne prend pas en consideration le drop d'un item sur lui-meme ou une categorie imbriquee + if ( + source_item -> location() == target_category -> location() ||\ + target_category -> isChildOf(source_item) + ) { + e -> ignore(); + return; + } + + // s'assure que la categorie cible est accessible en ecriture + if (!target_category -> isWritable()) { + e -> ignore(); + return; + } +#endif + + e -> accept(); + emit(requestForMoveElements(source_item, target_category, e -> pos())); } /** Gere le debut des drag'n drop @param supportedActions Les actions supportees - */ +*/ void ElementsPanel::startDrag(Qt::DropActions) { - // recupere le nom du fichier decrivant l'element - QString nom_fichier = currentItem() -> data(0, 42).toString(); - if (nom_fichier.isEmpty()) return; + // recupere l'emplacement selectionne + ElementsLocation location = selectedLocation(); + if (location.isNull()) return; + + // recupere la selection + ElementsCollectionItem *selected_item = QETApp::collectionItem(location); + if (!selected_item) return; // objet QDrag pour realiser le drag'n drop QDrag *drag = new QDrag(this); // donnees qui seront transmises par le drag'n drop + QString location_string(location.toString()); QMimeData *mimeData = new QMimeData(); + mimeData -> setText(location_string); - // appareil temporaire pour fournir un apercu - int etat; - Element *appar = new CustomElement(nom_fichier, 0, 0, &etat); - if (etat != 0) { - delete appar; - return; + if (selected_item -> isCategory() || selected_item -> isCollection()) { + mimeData -> setData("application/x-qet-category-uri", location_string.toAscii()); + drag -> setPixmap(QPixmap(":/ico/folder.png")); + } else if (selected_item -> isElement()) { + mimeData -> setData("application/x-qet-element-uri", location_string.toAscii()); + + // element temporaire pour fournir un apercu + int elmt_creation_state; + Element *temp_elmt = new CustomElement(location, 0, 0, &elmt_creation_state); + if (elmt_creation_state) { + delete temp_elmt; + return; + } + + // accrochage d'une pixmap representant l'appareil au pointeur + drag -> setPixmap(temp_elmt -> pixmap()); + drag -> setHotSpot(temp_elmt -> hotspot()); + + // suppression de l'appareil temporaire + delete temp_elmt; } - mimeData -> setText(nom_fichier); - drag -> setMimeData(mimeData); - - // accrochage d'une pixmap representant l'appareil au pointeur - drag -> setPixmap(appar -> pixmap()); - drag -> setHotSpot(appar -> hotspot()); - // realisation du drag'n drop - drag -> start(Qt::CopyAction); - - // suppression de l'appareil temporaire - delete appar; + drag -> setMimeData(mimeData); + drag -> start(Qt::MoveAction | Qt::CopyAction); } /** - Methode privee permettant d'ajouter un dossier au panel d'appareils - @param qtwi_parent QTreeWidgetItem parent sous lequel sera insere l'element - @param dossier Chemin absolu du dossier a inserer - @param nom Parametre facultatif permettant de forcer le nom du dossier. - S'il n'est pas precise, la fonction ouvre le fichier qet_directory situe - dans le dossier et y lit le nom du dossier ; si ce fichier n'existe pas ou - est invalide, la fonction utilise le nom du dossier. + Methode permettant d'ajouter un projet au panel d'elements. + @param qtwi_parent QTreeWidgetItem parent sous lequel sera insere le projet + @param project Projet a inserer dans le panel d'elements + @return Le QTreeWidgetItem insere le plus haut */ -void ElementsPanel::addDir(QTreeWidgetItem *qtwi_parent, QString adr_dossier, QString nom) { - ElementsCategory category(adr_dossier); - if (!category.exists()) return; +QTreeWidgetItem *ElementsPanel::addProject(QTreeWidgetItem *qtwi_parent, QETProject *project) { + // le projet sera insere juste avant la collection commune + QTreeWidgetItem *last_project = 0; + if (int common_collection_item_idx = indexOfTopLevelItem(common_collection_item_)) { + last_project = topLevelItem(common_collection_item_idx - 1); + } + + // creation du QTreeWidgetItem representant le projet + QTreeWidgetItem *qtwi_project = new QTreeWidgetItem(qtwi_parent, last_project); + qtwi_project -> setExpanded(true); + projects_.insert(qtwi_project, project); + updateProjectItemInformations(project); + connect( + project, SIGNAL(projectInformationsChanged(QETProject *)), + this, SLOT (projectInformationsChanged(QETProject *)) + ); + + // ajoute les schemas du projet + foreach (Diagram *diagram, project -> diagrams()) { + addDiagram(qtwi_project, diagram); + } + + // ajoute la collection du projet + addCollection(qtwi_project, project -> embeddedCollection(), tr("Collection projet")); + + return(qtwi_project); +} + +/** + Methode permettant d'ajouter un schema au panel d'elements. + @param qtwi_parent QTreeWidgetItem parent sous lequel sera insere le schema + @param diagram Schema a inserer dans le panel d'elements + @param diagram_name Nom a utiliser pour le projet + @return Le QTreeWidgetItem insere le plus haut +*/ +QTreeWidgetItem *ElementsPanel::addDiagram(QTreeWidgetItem *qtwi_parent, Diagram *diagram) { + // determine le nom du schema + QString final_name = diagramTitleToDisplay(diagram); + + // repere le dernier element correspondant a un schema, s'il existe + QTreeWidgetItem *last_diagram = 0; + bool collection_item_exists = false; + if (QETProject *project = diagram -> project()) { + if (ElementsCollection *project_collection = project -> embeddedCollection()) { + if (QTreeWidgetItem *collection_item = locations_.key(project_collection -> location())) { + collection_item_exists = true; + // repere le dernier schema + int common_collection_item_idx = qtwi_parent -> indexOfChild(collection_item); + if (common_collection_item_idx != -1) { + last_diagram = qtwi_parent -> child(common_collection_item_idx - 1); + } + } + } + } + + // creation du QTreeWidgetItem representant le schema + QTreeWidgetItem *qtwi_diagram; + if (collection_item_exists) { + qtwi_diagram = new QTreeWidgetItem(qtwi_parent, last_diagram); + } else { + qtwi_diagram = new QTreeWidgetItem(qtwi_parent); + } + qtwi_diagram -> setText(0, final_name); + qtwi_diagram -> setIcon(0, QIcon(":/ico/diagram.png")); + diagrams_.insert(qtwi_diagram, diagram); + + return(qtwi_diagram); +} + +/** + Methode privee permettant d'ajouter une collection d'elements au panel d'elements + @param qtwi_parent QTreeWidgetItem parent sous lequel sera insere la collection d'elements + @param collection Collection a inserer dans le panel d'elements - si + collection vaut 0, cette methode retourne 0. + @param coll_name Nom a utiliser pour la collection + @param icon Icone a utiliser pour l'affichage de la collection + @return Le QTreeWidgetItem insere le plus haut +*/ +QTreeWidgetItem *ElementsPanel::addCollection(QTreeWidgetItem *qtwi_parent, ElementsCollection *collection, const QString &coll_name, const QIcon &icon) { + if (!collection) return(0); + + QTreeWidgetItem *qtwi_coll = addCategory(qtwi_parent, collection -> rootCategory(), coll_name, icon); + return(qtwi_coll); +} + +/** + Methode privee permettant d'ajouter une categorie au panel d'elements + @param qtwi_parent QTreeWidgetItem parent sous lequel sera insere la categorie + @param category Categorie d'elements a inserer - si category vaut 0, cette + methode retourne 0. + @param name Parametre facultatif permettant de forcer le nom affiche + S'il n'est pas precise, la methode utilise le nom declare par la categorie. + @param icon Icone a utiliser pour l'affichage de la categorie + Si elle n'est pas precisee, une icone par defaut est utilisee + @return Le QTreeWidgetItem insere le plus haut +*/ +QTreeWidgetItem *ElementsPanel::addCategory(QTreeWidgetItem *qtwi_parent, ElementsCategory *category, const QString &cat_name, const QIcon &icon) { + if (!category) return(0); // recupere le nom de la categorie - QString nom_categorie = (nom != QString()) ? nom : category.name(); + QString final_name(cat_name.isEmpty() ? category -> name() : cat_name); + QIcon final_icon(icon.isNull() ? QIcon(":/ico/folder.png") : icon); // creation du QTreeWidgetItem representant le dossier - QTreeWidgetItem *qtwi_dossier = new QTreeWidgetItem(qtwi_parent, QStringList(nom_categorie)); - qtwi_dossier -> setIcon(0, QIcon(":/ico/folder.png")); + QTreeWidgetItem *qtwi_category = new QTreeWidgetItem(qtwi_parent, QStringList(final_name)); + qtwi_category -> setToolTip(0, category -> location().toString()); + qtwi_category -> setIcon(0, final_icon); QLinearGradient t(0, 0, 200, 0); t.setColorAt(0, QColor("#e8e8e8")); t.setColorAt(1, QColor("#ffffff")); - qtwi_dossier -> setBackground(0, QBrush(t)); - qtwi_dossier -> setData(0, 42, adr_dossier); + qtwi_category -> setBackground(0, QBrush(t)); + locations_.insert(qtwi_category, category -> location()); // reduit le dossier si besoin - qtwi_dossier -> setExpanded(expanded_directories.contains(adr_dossier)); + qtwi_category -> setExpanded(expanded_directories.contains(category -> location().toString())); - // ajout des sous-categories / sous-dossiers - QStringList dossiers = category.entryList(QStringList(), QDir::AllDirs | QDir::NoSymLinks | QDir::NoDotAndDotDot, QDir::Name); - foreach(QString dossier, dossiers) addDir(qtwi_dossier, adr_dossier + dossier + "/"); + // ajout des sous-categories + foreach(ElementsCategory *sub_cat, category -> categories()) addCategory(qtwi_category, sub_cat); - // ajout des elements / fichiers - QStringList fichiers = category.entryList(QStringList("*.elmt"), QDir::Files, QDir::Name); - foreach(QString fichier, fichiers) addFile(qtwi_dossier, adr_dossier + fichier); + // ajout des elements + foreach(ElementDefinition *elmt, category -> elements()) addElement(qtwi_category, elmt); + + return(qtwi_category); } /** - Methode privee permettant d'ajouter un element au panel d'appareils + Methode privee permettant d'ajouter un element au panel d'elements @param qtwi_parent QTreeWidgetItem parent sous lequel sera insere l'element - @param fichier Chemin absolu du fichier XML decrivant l'element a inserer + @param element Element a inserer + @param name Parametre facultatif permettant de forcer le nom affiche + S'il n'est pas precise, la methode utilise le nom declare par la categorie. + Une icone sera generee a partir de l'element. + @return Le QTreeWidgetItem insere */ -void ElementsPanel::addFile(QTreeWidgetItem *qtwi_parent, QString fichier) { +QTreeWidgetItem *ElementsPanel::addElement(QTreeWidgetItem *qtwi_parent, ElementDefinition *element, const QString &elmt_name) { + if (!element) return(0); + QString whats_this = tr("Ceci est un \351l\351ment que vous pouvez ins\351rer dans votre sch\351ma par cliquer-d\351placer"); QString tool_tip = tr("Cliquer-d\351posez cet \351l\351ment sur le sch\351ma pour ins\351rer un \351l\351ment "); int etat; - CustomElement elmt_perso(fichier, 0, 0, &etat); - if (etat != 0) { - qDebug() << "Le chargement du composant" << fichier << "a echoue avec le code d'erreur" << etat; - return; + CustomElement custom_elmt(element -> location(), 0, 0, &etat); + if (etat) { + qDebug() << "Le chargement du composant" << element -> location().toString() << "a echoue avec le code d'erreur" << etat; + return(0); } - QTreeWidgetItem *qtwi = new QTreeWidgetItem(qtwi_parent, QStringList(elmt_perso.nom())); - qtwi -> setStatusTip(0, tool_tip + "\253 " + elmt_perso.nom() + " \273"); - qtwi -> setToolTip(0, elmt_perso.nom()); + QString final_name(elmt_name.isEmpty() ? custom_elmt.name() : elmt_name); + QTreeWidgetItem *qtwi = new QTreeWidgetItem(qtwi_parent, QStringList(final_name)); + qtwi -> setStatusTip(0, tool_tip + "\253 " + custom_elmt.name() + " \273"); + qtwi -> setToolTip(0, element -> location().toString()); qtwi -> setWhatsThis(0, whats_this); qtwi -> setFlags(Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled); - qtwi -> setIcon(0, QIcon(elmt_perso.pixmap())); - qtwi -> setData(0, 42, fichier); + qtwi -> setIcon(0, QIcon(custom_elmt.pixmap())); + + // actions speciales pour les elements appartenant a un projet + if (QETProject *element_project = element -> location().project()) { + // affiche en rouge les elements inutilises dans un projet + if (!element_project -> usesElement(element -> location())) { + QLinearGradient t(0, 0, 200, 0); + t.setColorAt(0, QColor("#ffc0c0")); + t.setColorAt(1, QColor("#ffffff")); + qtwi -> setBackground(0, QBrush(t)); + qtwi -> setToolTip(0, QString(tr("%1 [non utilis\351 dans le projet]")).arg(qtwi -> toolTip(0))); + } + } + locations_.insert(qtwi, element -> location()); + + return(qtwi); } /** Recharge l'arbre des elements + @param reload_collections true pour relire les collections depuis leurs sources (fichiers, projets...) */ -void ElementsPanel::reload() { +void ElementsPanel::reload(bool reload_collections) { + // sauvegarde la liste des repertoires reduits saveExpandedCategories(); - // vide l'arbre + if (reload_collections) { + foreach(ElementsCollection *collection, QETApp::availableCollections()) { + collection -> reload(); + } + } + + // vide l'arbre et le hash clear(); + locations_.clear(); + projects_.clear(); + diagrams_.clear(); + common_collection_item_ = 0; + custom_collection_item_ = 0; // chargement des elements de la collection QET - addDir(invisibleRootItem(), QETApp::commonElementsDir(), tr("Collection QET")); + common_collection_item_ = addCollection(invisibleRootItem(), QETApp::commonElementsCollection(), tr("Collection QET"), QIcon(":/ico/qet-16.png")); // chargement des elements de la collection utilisateur - addDir(invisibleRootItem(), QETApp::customElementsDir(), tr("Collection utilisateur")); + custom_collection_item_ = addCollection(invisibleRootItem(), QETApp::customElementsCollection(), tr("Collection utilisateur"), QIcon(":/ico/folder_home.png")); - // icones - QList items = findItems("*", Qt::MatchWildcard); - if (items.count() == 2) { - items[0] -> setIcon(0, QIcon(":/ico/qet-16.png")); - items[1] -> setIcon(0, QIcon(":/ico/folder_home.png")); + // chargement des projets + foreach(QETProject *project, projects_to_display_.values()) { + addProject(invisibleRootItem(), project); } // reselectionne le dernier element selectionne if (!last_selected_item.isNull()) { - QTreeWidgetItem *qtwi = findFile(last_selected_item); + QTreeWidgetItem *qtwi = findPath(last_selected_item); if (qtwi) setCurrentItem(qtwi); } } -/** - Edite la categorie selectionnee -*/ -void ElementsPanel::editCategory() { - QFileInfo infos_file = selectedFile(); - if (!infos_file.exists() || !infos_file.isDir()) return; - launchCategoryEditor(infos_file.absoluteFilePath()); -} /** - Edite l'element selectionne + Gere le double-clic sur un element. + Si un double-clic sur un projet est effectue, le signal requestForProject + est emis. + Si un double-clic sur un schema est effectue, le signal requestForDiagram + est emis. + Si un double-clic sur une collection, une categorie ou un element est + effectue, le signal requestForCollectionItem est emis. + @param qtwi */ -void ElementsPanel::editElement() { - QFileInfo infos_file = selectedFile(); - if (!infos_file.exists() || !infos_file.isFile()) return; - launchElementEditor(infos_file.absoluteFilePath()); -} - -/** - Supprime la categorie selectionnee -*/ -void ElementsPanel::deleteCategory() { - QFileInfo infos_file = selectedFile(); - if (!infos_file.exists() || !infos_file.isDir()) return; - - // supprime la categorie - ElementsCategoryDeleter cat_deleter(infos_file.absoluteFilePath(), this); - cat_deleter.exec(); - - // recharge la liste des categories - reload(); -} - -/** - Supprime l'element selectionne -*/ -void ElementsPanel::deleteElement() { - QFileInfo infos_file = selectedFile(); - if (!infos_file.exists() || !infos_file.isFile()) return; - - // supprime l'element - ElementDeleter elmt_deleter(infos_file.absoluteFilePath(), this); - elmt_deleter.exec(); - - // recharge la liste des categories - reload(); -} - -/** - Gere le double-clic sur un element. Permet de lancer l'editeur de - categorie ou d'element. -*/ -void ElementsPanel::slot_doubleClick(QTreeWidgetItem *, int) { - // le fichier doit exister - QFileInfo infos_file = selectedFile(); - if (!infos_file.exists()) return; - - if (infos_file.isFile()) { - // il s'agit d'un element - launchElementEditor(infos_file.absoluteFilePath()); - } else if (infos_file.isDir()) { - // il s'agit d'une categorie - launchCategoryEditor(infos_file.absoluteFilePath()); +void ElementsPanel::slot_doubleClick(QTreeWidgetItem *qtwi, int) { + if (QETProject *project = projectForItem(qtwi)) { + emit(requestForProject(project)); + } else if (Diagram *diagram = diagramForItem(qtwi)) { + emit(requestForDiagram(diagram)); + } else if (ElementsCollectionItem *item = collectionItemForItem(qtwi)) { + emit(requestForCollectionItem(item)); } } -/// @return un QFileInfo decrivant le fichier ou le dossier correspondant au QTreeWidgetItem selectionne -QFileInfo ElementsPanel::selectedFile() const { - QTreeWidgetItem *current_qtwi = currentItem(); - if(!current_qtwi) return(QFileInfo()); - return(QFileInfo(currentItem() -> data(0, 42).toString())); -} - -/** - Lance l'editeur d'element pour l'element filename - @param filename Chemin du fichier representant l'element -*/ -void ElementsPanel::launchElementEditor(const QString &filename) { - QETElementEditor *editor = new QETElementEditor(); - editor -> fromFile(filename); - editor -> show(); -} - -/** - Lance l'editeur de categorie pour la categorie filename - @param filename Chemin du dossier representant la categorie -*/ -void ElementsPanel::launchCategoryEditor(const QString &filename) { - ElementsCategoryEditor ece(filename, true, this); - if (ece.exec() == QDialog::Accepted) reload(); -} - /** Enregistre la liste des categories repliees ainsi que le dernier element selectionne @@ -329,7 +694,7 @@ void ElementsPanel::saveExpandedCategories() { expanded_directories.clear(); QList items = findItems("*", Qt::MatchRecursive|Qt::MatchWildcard); foreach(QTreeWidgetItem *item, items) { - QString file = item -> data(0, 42).toString(); + QString file = locations_[item].toString(); if (!file.endsWith(".elmt") && item -> isExpanded()) { expanded_directories << file; } @@ -337,21 +702,80 @@ void ElementsPanel::saveExpandedCategories() { // sauvegarde egalement le dernier element selectionne QTreeWidgetItem *current_item = currentItem(); - if (current_item) last_selected_item = current_item -> data(0, 42).toString(); + if (current_item) last_selected_item = locations_[current_item].toString(); } /** - @param file fichier ou dossier a retrouver dans l'arborescence - @return le QTreeWidgetItem correspondant au fichier file ou 0 si celui-ci n'est pas trouve + @param path chemin virtuel a retrouver dans l'arborescence + @return le QTreeWidgetItem correspondant au chemin path ou 0 si celui-ci n'est pas trouve */ -QTreeWidgetItem *ElementsPanel::findFile(const QString &file) const { +QTreeWidgetItem *ElementsPanel::findPath(const QString &path) const { QList items = findItems("*", Qt::MatchRecursive|Qt::MatchWildcard); foreach(QTreeWidgetItem *item, items) { - if (item -> data(0, 42).toString() == file) return(item); + if (locations_[item].toString() == path) return(item); } return(0); } +/** + Enleve et supprime un item du panel en nettoyant les structures le referencant. + Note : Ce nettoyage est recursif + @param removed_item Item a enlever et supprimer +*/ +void ElementsPanel::deleteItem(QTreeWidgetItem *removed_item) { + if (!removed_item) return; + + // supprime les eventuels enfants de l'item + foreach(QTreeWidgetItem *child_item, removed_item -> takeChildren()) { + deleteItem(child_item); + } + + if (locations_.contains(removed_item)) { + locations_.remove(removed_item); + } else if (diagrams_.contains(removed_item)) { + diagrams_.remove(removed_item); + } else if (projects_.contains(removed_item)) { + projects_.remove(removed_item); + } + delete removed_item; +} + +/** + @param pos Position dans l'arborescence + @return La categorie situee sous la position pos, ou 0 s'il n'y a aucune + categorie correspondante. + @see categoryForItem +*/ +ElementsCategory *ElementsPanel::categoryForPos(const QPoint &pos) { + // Accede a l'item sous la position + QTreeWidgetItem *pos_qtwi = itemAt(pos); + if (!pos_qtwi) { + return(0); + } + + return(categoryForItem(pos_qtwi)); +} + +/** + Cette methode permet d'acceder a la categorie correspondant a un item donne. + Si cet item represente une collection, c'est sa categorie racine qui est renvoyee. + Si cet item represente une categorie, c'est cette categorie qui est renvoyee. + Si cet item represente un element, c'est sa categorie parente qui est renvoyee. + @param qtwi un QTreeWidgetItem + @return la categorie correspondant au QTreeWidgetItem qtwi, ou 0 s'il n'y a + aucune categorie correspondante. +*/ +ElementsCategory *ElementsPanel::categoryForItem(QTreeWidgetItem *qtwi) { + if (!qtwi) return(0); + + // Recupere le CollectionItem associe a cet item + ElementsCollectionItem *collection_item = collectionItemForItem(qtwi); + if (!collection_item) return(0); + + // recupere la categorie cible pour le deplacement + return(collection_item -> toCategory()); +} + /** N'affiche que les elements contenant une chaine donnee @param m Chaine a filtrer @@ -359,21 +783,156 @@ QTreeWidgetItem *ElementsPanel::findFile(const QString &file) const { void ElementsPanel::filter(const QString &m) { QList items = findItems("*", Qt::MatchRecursive | Qt::MatchWildcard); if (m.isEmpty()) { + // la chaine est vide : affiche tout foreach(QTreeWidgetItem *item, items) item -> setHidden(false); } else { + // repere les items correspondant au filtre + QList matching_items; foreach(QTreeWidgetItem *item, items) { - QString file = item -> data(0, 42).toString(); bool item_matches = item -> text(0).contains(m, Qt::CaseInsensitive); + if (item_matches) matching_items << item; item -> setHidden(!item_matches); - if (item_matches) { - // remonte l'arborescence pour afficher les categories contenant l'element - QTreeWidgetItem *parent_qtwi = item -> parent(); - while(parent_qtwi && (parent_qtwi -> isHidden() || !parent_qtwi -> isExpanded())) { - parent_qtwi -> setHidden(false); - parent_qtwi -> setExpanded(true); - parent_qtwi = parent_qtwi -> parent(); - } + } + + // remonte l'arborescence pour lister les categories contenant les elements filtres + QSet parent_items; + foreach(QTreeWidgetItem *item, matching_items) { + for (QTreeWidgetItem *parent_qtwi = item -> parent() ; parent_qtwi ; parent_qtwi = parent_qtwi -> parent()) { + parent_items << parent_qtwi; } } + + // etend les parents + foreach(QTreeWidgetItem *parent_qtwi, parent_items) { + if (!parent_qtwi -> isExpanded()) parent_qtwi -> setExpanded(true); + } + + // affiche les parents + foreach(QTreeWidgetItem *parent_qtwi, parent_items) { + if (parent_qtwi -> isHidden()) parent_qtwi -> setHidden(false); + } } } + +/** + Rajoute un projet au panel d'elements + @param project Projet ouvert a rajouter au panel +*/ +void ElementsPanel::projectWasOpened(QETProject *project) { + projects_to_display_ << project; + addProject(invisibleRootItem(), project); +} + +/** + Enleve un projet du panel d'elements + @param project Projet a enlever du panel +*/ +void ElementsPanel::projectWasClosed(QETProject *project) { + if (QTreeWidgetItem *item_to_remove = projects_.key(project, 0)) { + deleteItem(item_to_remove); + projects_to_display_.remove(project); + } +} + +/** + Gere le fait que les proprietes d'un projet change (exemple : fichier, + titre, ...). + @param project Projet modifie +*/ +void ElementsPanel::projectInformationsChanged(QETProject *project) { + updateProjectItemInformations(project); +} + +/** + Gere l'ajout d'un schema dans un projet + @param project Projet auquel a ete ajouter le schema + @param diagram Schema ajoute +*/ +void ElementsPanel::diagramWasAdded(QETProject *project, Diagram *diagram) { + // repere le QTWI du projet + if (QTreeWidgetItem *qtwi_project = projects_.key(project)) { + addDiagram(qtwi_project, diagram); + } +} + +/** + Gere la suppression d'un schema dans un projet + @param project Projet duquel a ete supprime le schema + @param diagram Schema supprime +*/ +void ElementsPanel::diagramWasRemoved(QETProject *project, Diagram *diagram) { + // on verifie que le projet apparait dans le panel + if (projects_.key(project, 0)) { + // on verifie que le schema apparait dans le panel + if (QTreeWidgetItem *item_to_remove = diagrams_.key(diagram, 0)) { + deleteItem(item_to_remove); + } + } +} + +/** + @param project Projet auquel appartient le schema concerne + @param diagram schema dont le titre a change +*/ +void ElementsPanel::diagramTitleChanged(QETProject *project, Diagram *diagram) { + // on verifie que le projet apparait dans le panel + if (projects_.key(project, 0)) { + // on verifie que le schema apparait dans le panel + if (QTreeWidgetItem *qtwi_diagram = diagrams_.key(diagram)) { + qtwi_diagram -> setText(0, diagramTitleToDisplay(diagram)); + } + } +} + +/** + @param project Projet auquel appartiennent les schemas concernes + @param from Index de l'onglet avant le deplacement + @param to Index de l'onglet apres le deplacement +*/ +void ElementsPanel::diagramOrderChanged(QETProject *project, int from, int to) { + // repere le QTWI correspondant au projet + QTreeWidgetItem *qtwi_project = projects_.key(project); + if (!qtwi_project) return; + + // repere le QTWI representant le schema deplace + QTreeWidgetItem *moved_qtwi_diagram = qtwi_project -> child(from); + if (!moved_qtwi_diagram) return; + + // enleve le QTWI et le reinsere au bon endroit + qtwi_project -> removeChild(moved_qtwi_diagram); + qtwi_project -> insertChild(to, moved_qtwi_diagram); +} + +/** + Met a jour le nom, l'info-bulle et l'icone de l'item representant un projet. + @param project le projet dont il faut mettre a jour l'affichage +*/ +void ElementsPanel::updateProjectItemInformations(QETProject *project) { + // determine le QTWI correspondant au projet + QTreeWidgetItem *qtwi_project = projects_.key(project); + if (!qtwi_project) return; + + // determine le nom et l'icone du projet + QString final_name(project -> pathNameTitle()); + QString final_tooltip = project -> filePath(); + if (final_tooltip.isEmpty()) { + final_tooltip = tr( + "Pas de fichier", + "tooltip for a file-less project in the element panel" + ); + } + QIcon final_icon(":/ico/project.png"); + + qtwi_project -> setText(0, final_name); + qtwi_project -> setToolTip(0, final_tooltip); + qtwi_project -> setIcon(0, final_icon); +} + +/** + @param diagram Schema dont on souhaite affiche le titre + @return Un titre affichable, tenant compte du fait que le titre du schema + peut etre vide. +*/ +QString ElementsPanel::diagramTitleToDisplay(Diagram *diagram) const { + return(diagram -> title().isEmpty() ? tr("Sch\351ma sans titre") : diagram -> title()); +} diff --git a/sources/elementspanel.h b/sources/elementspanel.h index f3fcc68f9..5564e42da 100644 --- a/sources/elementspanel.h +++ b/sources/elementspanel.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -18,7 +18,13 @@ #ifndef PANELAPPAREILS_H #define PANELAPPAREILS_H #include -#include "qetdiagrameditor.h" +#include "elementslocation.h" +class QETProject; +class Diagram; +class ElementsCollection; +class ElementsCollectionItem; +class ElementsCategory; +class ElementDefinition; /** Cette classe represente le panel d'appareils (en tant qu'element graphique) dans lequel l'utilisateur choisit les composants de @@ -37,30 +43,83 @@ class ElementsPanel : public QTreeWidget { // methodes public: - bool selectedItemIsAnElement() const; - bool selectedItemIsACategory() const; + // methodes pour determiner ce que represente un item donne + bool itemIsACollection(QTreeWidgetItem *) const; + bool itemIsACategory(QTreeWidgetItem *) const; + bool itemIsAnElement(QTreeWidgetItem *) const; + bool itemIsAProject(QTreeWidgetItem *) const; + bool itemIsADiagram(QTreeWidgetItem *) const; + bool itemHasLocation(QTreeWidgetItem *) const; + bool itemIsWritable(QTreeWidgetItem *) const; - private: - void addFile(QTreeWidgetItem *, QString); - void addDir(QTreeWidgetItem *, QString, QString = QString()); - QFileInfo selectedFile() const; - void launchElementEditor(const QString &); - void launchCategoryEditor(const QString &); - void saveExpandedCategories(); - QTreeWidgetItem *findFile(const QString &) const; - QStringList expanded_directories; - QString last_selected_item; + // methodes pour obtenir ce que represente un item donne + ElementsCollectionItem *collectionItemForItem(QTreeWidgetItem *) const; + QETProject *projectForItem(QTreeWidgetItem *) const; + Diagram *diagramForItem(QTreeWidgetItem *) const; + ElementsLocation locationForItem(QTreeWidgetItem *) const; + ElementsCategory *categoryForItem(QTreeWidgetItem *); + ElementsCategory *categoryForPos(const QPoint &); + + // methodes pour determiner ce que represente l'item selectionne + bool selectedItemIsACollection() const; + bool selectedItemIsACategory() const; + bool selectedItemIsAnElement() const; + bool selectedItemIsAProject() const; + bool selectedItemIsADiagram() const; + bool selectedItemHasLocation() const; + bool selectedItemIsWritable() const; + + // methodes pour obtenir ce que represente l'item selectionne + ElementsCollectionItem *selectedItem() const; + QETProject *selectedProject() const; + Diagram *selectedDiagram() const; + ElementsLocation selectedLocation() const; + + signals: + void requestForProject(QETProject *); + void requestForDiagram(Diagram *); + void requestForCollectionItem(ElementsCollectionItem *); + void requestForMoveElements(ElementsCollectionItem *, ElementsCollectionItem *, QPoint); public slots: void slot_doubleClick(QTreeWidgetItem *, int); + void reload(bool = true); + void filter(const QString &); + void projectWasOpened(QETProject *); + void projectWasClosed(QETProject *); + void projectInformationsChanged(QETProject *); + void diagramWasAdded(QETProject *, Diagram *); + void diagramWasRemoved(QETProject *, Diagram *); + void diagramTitleChanged(QETProject *, Diagram *); + void diagramOrderChanged(QETProject *, int, int); + + protected: + void dragEnterEvent(QDragEnterEvent *); void dragMoveEvent(QDragMoveEvent *); void dropEvent(QDropEvent *); void startDrag(Qt::DropActions); - void reload(); - void editCategory(); - void editElement(); - void deleteCategory(); - void deleteElement(); - void filter(const QString &); + + private: + QTreeWidgetItem *addProject (QTreeWidgetItem *, QETProject *); + QTreeWidgetItem *addDiagram (QTreeWidgetItem *, Diagram *); + QTreeWidgetItem *addCollection(QTreeWidgetItem *, ElementsCollection *, const QString & = QString(), const QIcon & = QIcon()); + QTreeWidgetItem *addCategory (QTreeWidgetItem *, ElementsCategory *, const QString & = QString(), const QIcon & = QIcon()); + QTreeWidgetItem *addElement (QTreeWidgetItem *, ElementDefinition *, const QString & = QString()); + void saveExpandedCategories(); + QTreeWidgetItem *findPath(const QString &) const; + void deleteItem(QTreeWidgetItem *); + void updateProjectItemInformations(QETProject *); + QString diagramTitleToDisplay(Diagram *) const; + + // attributs + private: + QStringList expanded_directories; + QString last_selected_item; + QHash locations_; + QSet projects_to_display_; + QHash projects_; + QHash diagrams_; + QTreeWidgetItem *common_collection_item_; + QTreeWidgetItem *custom_collection_item_; }; #endif diff --git a/sources/elementspanelwidget.cpp b/sources/elementspanelwidget.cpp index ce9481dde..be8e1a5de 100644 --- a/sources/elementspanelwidget.cpp +++ b/sources/elementspanelwidget.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -18,6 +18,26 @@ #include "elementspanelwidget.h" #include "newelementwizard.h" #include "elementscategorieswidget.h" +#include "elementscollectionitem.h" +#include "qetelementeditor.h" +#include "elementdeleter.h" +#include "elementscategoryeditor.h" +#include "elementscategorydeleter.h" +#include "qetapp.h" +#include "interactivemoveelementshandler.h" +#include "qetproject.h" +#include "diagram.h" + +/* + Lorsque le flag ENABLE_PANEL_WIDGET_DND_CHECKS est defini, le panel + effectue des verifications lors des drag'n drop d'elements et categories. + Par exemple, il verifie qu'une categorie cible est accessible en ecriture + avant d'y autoriser le drop d'un element. + Supprimer ce flag permet de tester le comportement des fonctions de gestion + des items (copy, move, etc.). +*/ +#define ENABLE_PANEL_WIDGET_DND_CHECKS + /** Constructeur @param parent Le QWidget parent de ce widget @@ -27,14 +47,23 @@ ElementsPanelWidget::ElementsPanelWidget(QWidget *parent) : QWidget(parent) { elements_panel = new ElementsPanel(this); // initialise les actions - reload = new QAction(QIcon(":/ico/reload.png"), tr("Recharger les collections"), this); - new_category = new QAction(QIcon(":/ico/category_new.png"), tr("Nouvelle cat\351gorie"), this); - edit_category = new QAction(QIcon(":/ico/category_edit.png"), tr("\311diter la cat\351gorie"), this); - delete_category = new QAction(QIcon(":/ico/category_delete.png"), tr("Supprimer la cat\351gorie"), this); - new_element = new QAction(QIcon(":/ico/new.png"), tr("Nouvel \351l\351ment"), this); - edit_element = new QAction(QIcon(":/ico/edit.png"), tr("\311diter l'\351l\351ment"), this); - delete_element = new QAction(QIcon(":/ico/delete.png"), tr("Supprimer l'\351l\351ment"), this); - erase_textfield = new QAction(QIcon(":/ico/erase.png"), tr("Effacer le filtre"), this); + reload = new QAction(QIcon(":/ico/reload.png"), tr("Recharger les collections"), this); + new_category = new QAction(QIcon(":/ico/category_new.png"), tr("Nouvelle cat\351gorie"), this); + edit_category = new QAction(QIcon(":/ico/category_edit.png"), tr("\311diter la cat\351gorie"), this); + delete_category = new QAction(QIcon(":/ico/category_delete.png"), tr("Supprimer la cat\351gorie"), this); + delete_collection = new QAction(QIcon(":/ico/category_delete.png"), tr("Vider la collection"), this); + new_element = new QAction(QIcon(":/ico/new.png"), tr("Nouvel \351l\351ment"), this); + edit_element = new QAction(QIcon(":/ico/edit.png"), tr("\311diter l'\351l\351ment"), this); + delete_element = new QAction(QIcon(":/ico/delete.png"), tr("Supprimer l'\351l\351ment"), this); + prj_close = new QAction(QIcon(":/ico/fileclose.png"), tr("Fermer ce projet"), this); + prj_edit_prop = new QAction(QIcon(":/ico/info.png"), tr("Propri\351t\351s du projet"), this); + prj_prop_diagram = new QAction(QIcon(":/ico/info.png"), tr("Propri\351t\351s du sch\351ma"), this); + prj_add_diagram = new QAction(QIcon(":/ico/diagram_add.png"), tr("Ajouter un sch\351ma"), this); + prj_del_diagram = new QAction(QIcon(":/ico/diagram_del.png"), tr("Supprimer ce sch\351ma"), this); + move_elements_ = new QAction(QIcon(":/ico/item_move.png"), tr("D\351placer dans cette cat\351gorie"), this); + copy_elements_ = new QAction(QIcon(":/ico/item_copy.png"), tr("Copier dans cette cat\351gorie"), this); + cancel_elements_ = new QAction(QIcon(":/ico/item_cancel.png"), tr("Annuler"), this); + erase_textfield = new QAction(QIcon(":/ico/erase.png"), tr("Effacer le filtre"), this); // initialise le champ de texte pour filtrer avec une disposition horizontale QLabel *filter_label = new QLabel(tr("Filtrer : "), this); @@ -44,22 +73,44 @@ ElementsPanelWidget::ElementsPanelWidget(QWidget *parent) : QWidget(parent) { filter_toolbar -> addWidget(filter_label); filter_toolbar -> addWidget(filter_textfield); + // ajoute une petite marge a la droite du champ pour filtrer lorsque le style CleanLooks est utilise + if (qobject_cast(QApplication::style())) { + int l, t, r, b; + filter_toolbar -> getContentsMargins(&l, &t, &r, &b); + filter_toolbar -> setContentsMargins (l, t, r + 4, b); + } + context_menu = new QMenu(this); - connect(reload, SIGNAL(triggered()), this, SLOT(reloadAndFilter())); - connect(new_category, SIGNAL(triggered()), this, SLOT(newCategory())); - connect(edit_category, SIGNAL(triggered()), elements_panel, SLOT(editCategory())); - connect(delete_category, SIGNAL(triggered()), elements_panel, SLOT(deleteCategory())); - connect(new_element, SIGNAL(triggered()), this, SLOT(newElement())); - connect(edit_element, SIGNAL(triggered()), elements_panel, SLOT(editElement())); - connect(delete_element, SIGNAL(triggered()), elements_panel, SLOT(deleteElement())); + connect(reload, SIGNAL(triggered()), this, SLOT(reloadAndFilter())); + connect(new_category, SIGNAL(triggered()), this, SLOT(newCategory())); + connect(edit_category, SIGNAL(triggered()), this, SLOT(editCategory())); + connect(delete_category, SIGNAL(triggered()), this, SLOT(deleteCategory())); + connect(delete_collection, SIGNAL(triggered()), this, SLOT(deleteCategory())); + connect(new_element, SIGNAL(triggered()), this, SLOT(newElement())); + connect(edit_element, SIGNAL(triggered()), this, SLOT(editElement())); + connect(delete_element, SIGNAL(triggered()), this, SLOT(deleteElement())); + connect(prj_close, SIGNAL(triggered()), this, SLOT(closeProject())); + connect(prj_edit_prop, SIGNAL(triggered()), this, SLOT(editProjectProperties())); + connect(prj_prop_diagram, SIGNAL(triggered()), this, SLOT(editDiagramProperties())); + connect(prj_add_diagram, SIGNAL(triggered()), this, SLOT(newDiagram())); + connect(prj_del_diagram, SIGNAL(triggered()), this, SLOT(deleteDiagram())); + connect(move_elements_, SIGNAL(triggered()), this, SLOT(moveElements())); + connect(copy_elements_, SIGNAL(triggered()), this, SLOT(copyElements())); - connect(erase_textfield, SIGNAL(triggered()), filter_textfield, SLOT(clear())); - connect(erase_textfield, SIGNAL(triggered()), filter_textfield, SLOT(setFocus())); + connect(erase_textfield, SIGNAL(triggered()), this, SLOT(clearFilterTextField())); connect(filter_textfield, SIGNAL(textEdited(const QString &)), elements_panel, SLOT(filter(const QString &))); connect(elements_panel, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT(updateButtons())); - connect(elements_panel, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(handleContextMenu(const QPoint &))); + connect(elements_panel, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(handleContextMenu(const QPoint &))); + connect(elements_panel, SIGNAL(requestForCollectionItem(ElementsCollectionItem *)), this, SLOT(handleCollectionRequest(ElementsCollectionItem *))); + connect( + elements_panel, + SIGNAL(requestForMoveElements(ElementsCollectionItem *, ElementsCollectionItem *, QPoint)), + this, + SLOT(handleMoveElementsRequest(ElementsCollectionItem *, ElementsCollectionItem *, const QPoint &)), + Qt::QueuedConnection + ); // initialise la barre d'outils toolbar = new QToolBar(this); @@ -91,6 +142,16 @@ ElementsPanelWidget::ElementsPanelWidget(QWidget *parent) : QWidget(parent) { ElementsPanelWidget::~ElementsPanelWidget() { } +/** + Vide le champ de texte permettant a l'utilisateur de filtrer, donne le + focus a ce champ et annule le filtrage. +*/ +void ElementsPanelWidget::clearFilterTextField() { + filter_textfield -> clear(); + filter_textfield -> setFocus(); + elements_panel -> filter(QString()); +} + /** Recharge le panel d'elements */ @@ -102,22 +163,121 @@ void ElementsPanelWidget::reloadAndFilter() { elements_panel -> filter(filter_textfield -> text()); } +/** + Emet le signal requestForProjectClosing avec le projet selectionne +*/ +void ElementsPanelWidget::closeProject() { + if (QETProject *selected_project = elements_panel -> selectedProject()) { + emit(requestForProjectClosing(selected_project)); + } +} + +/** + Emet le signal requestForProjectPropertiesEdition avec le projet selectionne +*/ +void ElementsPanelWidget::editProjectProperties() { + if (QETProject *selected_project = elements_panel -> selectedProject()) { + emit(requestForProjectPropertiesEdition(selected_project)); + } +} + +/** + Emet le signal requestForDiagramPropertiesEdition avec le schema selectionne +*/ +void ElementsPanelWidget::editDiagramProperties() { + if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) { + emit(requestForDiagramPropertiesEdition(selected_diagram)); + } +} + +/** + Emet le signal requestForNewDiagram avec le projet selectionne +*/ +void ElementsPanelWidget::newDiagram() { + if (QETProject *selected_project = elements_panel -> selectedProject()) { + emit(requestForNewDiagram(selected_project)); + } +} + +/** + Emet le signal requestForDiagramDeletion avec le schema selectionne +*/ +void ElementsPanelWidget::deleteDiagram() { + if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) { + emit(requestForDiagramDeletion(selected_diagram)); + } +} /** Appelle l'assistant de creation de nouvel element */ void ElementsPanelWidget::newElement() { + ElementsCategory *selected_category = writableSelectedCategory(); + NewElementWizard new_element_wizard(this); + if (selected_category) { + new_element_wizard.preselectCategory(selected_category); + } new_element_wizard.exec(); } /** - Lance le gestionnaire de categories + Si une categorie accessible en ecriture est selectionnee, cette methode + affiche directement un formulaire de creation de categorie en utilisant la + selection comme categorie parente. + Sinon, elle affiche un gestionnaire de categories, permettant ainsi a + l'utilisateur de choisir une categorie parente. */ void ElementsPanelWidget::newCategory() { + ElementsCategory *selected_category = writableSelectedCategory(); + + if (selected_category) { + ElementsCategoryEditor new_category_dialog(selected_category -> location(), false, this); + if (new_category_dialog.exec() == QDialog::Accepted) { + elements_panel -> reload(); + } + } else { + launchCategoriesManager(); + elements_panel -> reload(); + } +} + +/** + Met a jour les boutons afin d'assurer la coherence de l'interface +*/ +void ElementsPanelWidget::updateButtons() { + bool collection_selected = elements_panel -> selectedItemIsACollection(); + bool category_selected = collection_selected || elements_panel -> selectedItemIsACategory(); + bool element_selected = elements_panel -> selectedItemIsAnElement(); + + if (collection_selected || category_selected || element_selected) { + bool element_writable = elements_panel -> selectedItemIsWritable(); + delete_collection -> setEnabled(collection_selected && element_writable); + new_category -> setEnabled(category_selected && element_writable); + edit_category -> setEnabled(category_selected && !collection_selected); + delete_category -> setEnabled(category_selected && element_writable); + new_element -> setEnabled(category_selected && element_writable); + edit_element -> setEnabled(element_selected); + delete_element -> setEnabled(element_selected && element_writable); + } else if (elements_panel -> selectedItemIsAProject()) { + bool is_writable = !(elements_panel -> selectedProject() -> isReadOnly()); + prj_add_diagram -> setEnabled(is_writable); + } else if (elements_panel -> selectedItemIsADiagram()) { + bool is_writable = !(elements_panel -> selectedDiagram() -> project() -> isReadOnly()); + prj_del_diagram -> setEnabled(is_writable); + } + +} + +/** + Lance le gestionnaire de categories. Il s'agit d'un petit dialogue listant + les categories accessibles en ecriture et permettant de les editer, de les + supprimer et d'en creer de nouvelles. +*/ +int ElementsPanelWidget::launchCategoriesManager() { QDialog new_category_dialog(this); new_category_dialog.setMinimumSize(480, 280); - new_category_dialog.setWindowTitle(tr("Gestionnaire de cat\351gories")); + new_category_dialog.setWindowTitle(tr("Gestionnaire de cat\351gories", "window title")); QVBoxLayout *layout = new QVBoxLayout(&new_category_dialog); QLabel *explication = new QLabel(tr("Vous pouvez utiliser ce gestionnaire pour ajouter, supprimer ou modifier les cat\351gories.")); @@ -131,20 +291,7 @@ void ElementsPanelWidget::newCategory() { connect(buttons, SIGNAL(rejected()), &new_category_dialog, SLOT(accept())); layout -> addWidget(buttons); - new_category_dialog.exec(); - elements_panel -> reload(); -} - -/** - Met a jour les boutons afin d'assurer la coherence de l'interface -*/ -void ElementsPanelWidget::updateButtons() { - bool category_selected = elements_panel -> selectedItemIsACategory(); - bool element_selected = elements_panel -> selectedItemIsAnElement(); - edit_category -> setEnabled(category_selected); - delete_category -> setEnabled(category_selected); - edit_element -> setEnabled(element_selected); - delete_element -> setEnabled(element_selected); + return(new_category_dialog.exec()); } /** @@ -153,26 +300,218 @@ void ElementsPanelWidget::updateButtons() { */ void ElementsPanelWidget::handleContextMenu(const QPoint &pos) { // recupere l'item concerne par l'evenement ainsi que son chemin - QTreeWidgetItem *item = elements_panel -> itemAt(pos); + QTreeWidgetItem *item = elements_panel -> itemAt(pos); if (!item) return; - // recupere le fichier associe a l'item - QString item_file = item -> data(0, 42).toString(); - QFileInfo item_file_infos(item_file); - if (item_file.isNull() || !item_file_infos.exists()) return; - - // remplit le menu differemment selon qu'il s'agit d'un element ou d'une categorie context_menu -> clear(); - if (item_file_infos.isDir()) { - context_menu -> addAction(new_category); - context_menu -> addAction(edit_category); - context_menu -> addAction(delete_category); - context_menu -> addAction(new_element); + + if (elements_panel -> itemHasLocation(item)) { + // recupere l'emplacement associe a l'item + ElementsCollectionItem *selected_item = elements_panel -> collectionItemForItem(item); + + if (selected_item) { + if (selected_item -> isCategory()) { + context_menu -> addAction(new_category); + context_menu -> addAction(edit_category); + context_menu -> addAction(delete_category); + context_menu -> addAction(new_element); + } else if (selected_item -> isElement()) { + context_menu -> addAction(edit_element); + context_menu -> addAction(delete_element); + } else if (selected_item -> isCollection()) { + // categorie racine / collection + context_menu -> addAction(delete_collection); + context_menu -> addAction(new_category); + context_menu -> addAction(new_element); + } + } } else { - context_menu -> addAction(edit_element); - context_menu -> addAction(delete_element); + if (elements_panel -> itemIsAProject(item)) { + context_menu -> addAction(prj_edit_prop); + context_menu -> addAction(prj_add_diagram); + context_menu -> addAction(prj_close); + } else if (elements_panel -> itemIsADiagram(item)) { + context_menu -> addAction(prj_prop_diagram); + context_menu -> addAction(prj_del_diagram); + } } // affiche le menu + if (!context_menu -> isEmpty()) { + context_menu -> popup(mapToGlobal(elements_panel -> mapTo(this, pos + QPoint(2, 2)))); + } +} + +/** + Gere les demandes d'edition de categories ou d'elements + @param item Item de la collection a editer +*/ +void ElementsPanelWidget::handleCollectionRequest(ElementsCollectionItem *item) { + if (!item) return; + if (item -> isElement()) { + // il s'agit d'un element + launchElementEditor(item -> location()); + } else if (item -> isCategory()) { + // il s'agit d'une categorie + launchCategoryEditor(item -> location()); + } +} + +/** + Gere le drop d'un collectionItem sur un autre. + Elle memorise dans les attributs de cette classe l'item source et l'item + destination du drag'n drop. + Un menu est ensuite affiche pour demander a l'utilisateur ce qu'il + souhaite faire (deplacer, copier ou annuler). + @param src Item source + @param dst Item cible + @param pos Position ou le menu contextuel a ete demande +*/ +void ElementsPanelWidget::handleMoveElementsRequest(ElementsCollectionItem *src, ElementsCollectionItem *dst, const QPoint &pos) { + if (!src || !dst || !dst -> isCategory()) return; + + // memorise les items source et cible du drag'n drop + dnd_item_src_ = src; + dnd_item_dst_ = dst; + +#ifdef ENABLE_PANEL_WIDGET_DND_CHECKS + // active ou desactive les actions selon la source et la cible + copy_elements_ -> setEnabled(src -> isReadable() && dst -> isWritable()); + move_elements_ -> setEnabled(!src -> isRootCategory() && src -> isWritable() && dst -> isWritable()); +#endif + + // affiche un menu contextuel pour que l'utilisateur indique s'il souhaite + // effectuer un deplacement ou une copie + context_menu -> clear(); + context_menu -> addAction(copy_elements_); + context_menu -> addAction(move_elements_); + context_menu -> addSeparator(); + context_menu -> addAction(cancel_elements_); + context_menu -> popup(mapToGlobal(elements_panel -> mapTo(this, pos + QPoint(2, 2)))); } + +/** + Cette classe memorise l'item source et l'item destination du dernier drag'n + drop. Cette methode effectue le deplacement de l'item source memorise dans + l'item destination memorise. + @see handleMoveElementsRequest +*/ +void ElementsPanelWidget::moveElements() { + moveElements(dnd_item_src_, dnd_item_dst_); +} + +/** + Deplace l'item src dans l'item dst +*/ +void ElementsPanelWidget::moveElements(ElementsCollectionItem *src, ElementsCollectionItem *dst) { + InteractiveMoveElementsHandler *interactive_handler = new InteractiveMoveElementsHandler(); + src -> move(dst -> toCategory(), interactive_handler); + delete interactive_handler; +} + +/** + Cette classe memorise l'item source et l'item destination du dernier drag'n + drop. Cette methode effectue la copie de l'item source memorise dans l'item + destination memorise. + @see handleMoveElementsRequest +*/ +void ElementsPanelWidget::copyElements() { + copyElements(dnd_item_src_, dnd_item_dst_); +} + +/** + Copie l'item src dans l'item dst +*/ +void ElementsPanelWidget::copyElements(ElementsCollectionItem *src, ElementsCollectionItem *dst) { + InteractiveMoveElementsHandler *interactive_handler = new InteractiveMoveElementsHandler(); + src -> copy(dst -> toCategory(), interactive_handler, true); + delete interactive_handler; +} + +/** + Edite la categorie selectionnee +*/ +void ElementsPanelWidget::editCategory() { + if (ElementsCollectionItem *selected_item = elements_panel -> selectedItem()) { + if (selected_item -> isCategory()) { + launchCategoryEditor(selected_item -> location()); + } + } +} + +/** + Edite l'element selectionne +*/ +void ElementsPanelWidget::editElement() { + if (ElementsCollectionItem *selected_item = elements_panel -> selectedItem()) { + if (selected_item -> isElement()) { + launchElementEditor(selected_item -> location()); + } + } +} + +/** + Supprime la categorie selectionnee +*/ +void ElementsPanelWidget::deleteCategory() { + if (ElementsCollectionItem *selected_item = elements_panel -> selectedItem()) { + if (selected_item -> isCategory() || selected_item -> isCollection()) { + ElementsCategoryDeleter cat_deleter(selected_item -> location(), this); + if (cat_deleter.exec()) elements_panel -> reload(); + } + } +} + +/** + Supprime l'element selectionne +*/ +void ElementsPanelWidget::deleteElement() { + if (ElementsCollectionItem *selected_item = elements_panel -> selectedItem()) { + if (selected_item -> isElement()) { + ElementDeleter elmt_deleter(selected_item -> location(), this); + if (elmt_deleter.exec()) elements_panel -> reload(); + } + } +} + +/** + Lance l'editeur d'element pour l'element filename + @param location Emplacement de l'element a editer +*/ +void ElementsPanelWidget::launchElementEditor(const ElementsLocation &location) { + QETElementEditor *editor = new QETElementEditor(); + editor -> fromLocation(location); + editor -> show(); +} + +/** + Lance l'editeur de categorie pour la categorie path + @param path Emplacement de la categorie a editer +*/ +void ElementsPanelWidget::launchCategoryEditor(const ElementsLocation &location) { + ElementsCategoryEditor ece(location, true); + if (ece.exec() == QDialog::Accepted) { + elements_panel -> reload(); + } +} + +/** + @return la categorie selectionnee s'il y en a une et que celle-ci est + accessible en ecriture ; sinon retourne 0 + @see ElementsPanel::categoryForItem(QTreeWidgetItem *) +*/ +ElementsCategory *ElementsPanelWidget::writableSelectedCategory() { + // recupere l'element selectionne + QTreeWidgetItem *selected_qtwi = elements_panel -> currentItem(); + if (!selected_qtwi) return(0); + + // l'element selectionne doit pouvoir correspondre a une categorie + ElementsCategory *selected_category = elements_panel -> categoryForItem(selected_qtwi); + if (!selected_category) return(0); + + // la categorie doit etre accessible en ecriture + if (!selected_category -> isWritable()) return(0); + + return(selected_category); +} diff --git a/sources/elementspanelwidget.h b/sources/elementspanelwidget.h index d5d2ca03e..266122238 100644 --- a/sources/elementspanelwidget.h +++ b/sources/elementspanelwidget.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -19,7 +19,6 @@ #define ELEMENTS_PANEL_WIDGET_H #include #include "elementspanel.h" - /** Cette classe est un widget qui contient le panel d'elements surplombe d'une barre d'outils avec differentes actions pour gerer les elements. @@ -41,21 +40,54 @@ class ElementsPanelWidget : public QWidget { QToolBar *toolbar, *filter_toolbar; QAction *reload; QAction *new_category, *edit_category, *delete_category; + QAction *delete_collection; QAction *new_element, *edit_element, *delete_element; + QAction *prj_close, *prj_edit_prop, *prj_prop_diagram, *prj_add_diagram, *prj_del_diagram; + QAction *copy_elements_, *move_elements_, *cancel_elements_; QMenu *context_menu; QAction *erase_textfield; QLineEdit *filter_textfield; + ElementsCollectionItem *dnd_item_src_, *dnd_item_dst_; // methodes public: inline ElementsPanel &elementsPanel() const; + signals: + void requestForNewDiagram(QETProject *); + void requestForProjectClosing(QETProject *); + void requestForProjectPropertiesEdition(QETProject *); + void requestForDiagramPropertiesEdition(Diagram *); + void requestForDiagramDeletion(Diagram *); + public slots: + void clearFilterTextField(); void reloadAndFilter(); - void newElement(); + void closeProject(); + void editProjectProperties(); + void editDiagramProperties(); + void newDiagram(); + void deleteDiagram(); void newCategory(); + void newElement(); + void editCategory(); + void editElement(); + void deleteCategory(); + void deleteElement(); void updateButtons(); + int launchCategoriesManager(); void handleContextMenu(const QPoint &); + void handleCollectionRequest(ElementsCollectionItem *); + void handleMoveElementsRequest(ElementsCollectionItem *, ElementsCollectionItem *, const QPoint & = QPoint()); + void moveElements(); + void moveElements(ElementsCollectionItem *, ElementsCollectionItem *); + void copyElements(); + void copyElements(ElementsCollectionItem *, ElementsCollectionItem *); + + private: + void launchElementEditor(const ElementsLocation &); + void launchCategoryEditor(const ElementsLocation &); + ElementsCategory *writableSelectedCategory(); }; /** diff --git a/sources/elementtextitem.cpp b/sources/elementtextitem.cpp index e68965ced..5fa479bc7 100644 --- a/sources/elementtextitem.cpp +++ b/sources/elementtextitem.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/elementtextitem.h b/sources/elementtextitem.h index dbd658921..ac34d53fa 100644 --- a/sources/elementtextitem.h +++ b/sources/elementtextitem.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/exportdialog.cpp b/sources/exportdialog.cpp index 2443c2739..d941bc36a 100644 --- a/sources/exportdialog.cpp +++ b/sources/exportdialog.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -21,60 +21,40 @@ /** Constructeur - @param dia Le schema a exporter + @param project Le projet a exporter @param parent Le Widget parent de ce dialogue */ -ExportDialog::ExportDialog(Diagram *dia, QWidget *parent) : QDialog(parent) { - if (!dia) return; +ExportDialog::ExportDialog(QETProject *project, QWidget *parent) : QDialog(parent) { + if (!project) return; // recupere le schema a exporter, sa taille et ses proportions - diagram = dia; - diagram_size = diagram -> imageSize(); - diagram_ratio = (qreal)diagram_size.width() / (qreal)diagram_size.height(); - dontchangewidth = dontchangeheight = false; + project_ = project; // la taille minimale du dialogue est fixee - setMinimumSize(800, 360); + setMinimumSize(800, 390); resize(minimumSize()); - setWindowTitle(tr("Exporter")); + setWindowTitle(tr("Exporter les sch\351mas du projet", "window title")); // le dialogue comporte deux boutons buttons = new QDialogButtonBox(this); buttons -> setOrientation(Qt::Horizontal); - buttons -> setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Save); + buttons -> setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Save); + QPushButton *export_button = buttons -> button(QDialogButtonBox::Save); + export_button -> setText(tr("Exporter")); // disposition des elements - QGridLayout *layout = new QGridLayout(this); - setContentsMargins(0, 0, 5, 5); - layout -> setMargin(0); - layout -> setSpacing(0); - layout -> setColumnMinimumWidth(0, 390); - layout -> setColumnMinimumWidth(1, 400); - layout -> addWidget(leftPart(), 0, 0); - layout -> addWidget(rightPart(), 0, 1); - layout -> addWidget(buttons, 1, 1); - layout -> setColumnStretch(0, 1); - layout -> setColumnStretch(1, 500); - - setTabOrder(keep_aspect_ratio, buttons); + QVBoxLayout *layout = new QVBoxLayout(this); + layout -> addWidget(new QLabel(tr("Choisissez les sch\351mas que vous d\351sirez exporter ainsi que leurs dimensions :"))); + layout -> addWidget(initDiagramsListPart(), 1); + layout -> addWidget(leftPart()); + layout -> addWidget(buttons); + slot_changeFilesExtension(true); // connexions signaux/slots - connect(button_browse, SIGNAL(released()), this, SLOT(slot_chooseAFile())); - connect(width, SIGNAL(valueChanged(int)), this, SLOT(slot_correctHeight())); - connect(keep_aspect_ratio, SIGNAL(stateChanged(int)), this, SLOT(slot_correctHeight())); - connect(height, SIGNAL(valueChanged(int)), this, SLOT(slot_correctWidth())); - connect(buttons, SIGNAL(accepted()), this, SLOT(slot_check())); + connect(button_browse, SIGNAL(released()), this, SLOT(slot_chooseADirectory())); + connect(format, SIGNAL(currentIndexChanged(int)), this, SLOT(slot_changeFilesExtension())); + connect(buttons, SIGNAL(accepted()), this, SLOT(slot_export())); connect(buttons, SIGNAL(rejected()), this, SLOT(reject())); - - connect(draw_grid, SIGNAL(stateChanged(int) ), this, SLOT(slot_refreshPreview())); - connect(draw_border, SIGNAL(stateChanged(int) ), this, SLOT(slot_refreshPreview())); - connect(draw_inset, SIGNAL(stateChanged(int) ), this, SLOT(slot_refreshPreview())); - connect(draw_columns, SIGNAL(stateChanged(int) ), this, SLOT(slot_refreshPreview())); - connect(draw_rows, SIGNAL(stateChanged(int) ), this, SLOT(slot_refreshPreview())); - connect(draw_terminals, SIGNAL(stateChanged(int) ), this, SLOT(slot_refreshPreview())); - connect(width, SIGNAL(valueChanged(int) ), this, SLOT(slot_refreshPreview())); - connect(height, SIGNAL(valueChanged(int) ), this, SLOT(slot_refreshPreview())); - connect(export_border, SIGNAL(toggled (bool)), this, SLOT(slot_changeUseBorder())); } /** @@ -84,41 +64,14 @@ ExportDialog::~ExportDialog() { } /** - Met en place la partie du dialogue dans lequel l'utilisateur entre les - dimensions souhaitees de l'image. - @return La QGroupBox permettant de regler les dimensions de l'image + @return lenombre de schemas coches (donc a exporter) */ -QGroupBox *ExportDialog::setupDimensionsGroupBox() { - QGroupBox *groupbox_dimensions = new QGroupBox(tr("Dimensions"), this); - QGridLayout *gridLayout = new QGridLayout(groupbox_dimensions); - - // hauteur - gridLayout -> addWidget(new QLabel(tr("Hauteur :"), groupbox_dimensions), 2, 0, 1, 1); - - width = new QSpinBox(groupbox_dimensions); - width -> setRange(1, 10000); - width -> setValue(diagram_size.width()); - gridLayout -> addWidget(width, 0, 1, 1, 1); - - gridLayout -> addWidget(new QLabel(tr("px"), groupbox_dimensions), 0, 2, 1, 1); - - // largeur - gridLayout -> addWidget(new QLabel(tr("Largeur :"), groupbox_dimensions), 0, 0, 1, 1); - - height = new QSpinBox(groupbox_dimensions); - height -> setRange(1, 10000); - height -> setValue(diagram_size.height()); - gridLayout -> addWidget(height, 2, 1, 1, 1); - - gridLayout -> addWidget(new QLabel(tr("px"), groupbox_dimensions), 2, 2, 1, 1); - - // conserver les proportions - keep_aspect_ratio = new QCheckBox(tr("Conserver les proportions"), groupbox_dimensions); - keep_aspect_ratio -> setChecked(true); - - gridLayout -> addWidget(keep_aspect_ratio, 1, 3, 1, 1); - - return(groupbox_dimensions); +int ExportDialog::diagramsToExportCount() const { + int checked_diagrams_count = 0; + foreach(ExportDiagramLine *diagram_line, diagram_lines_.values()) { + if (diagram_line -> must_export -> isChecked()) ++ checked_diagrams_count; + } + return(checked_diagrams_count); } /** @@ -138,39 +91,91 @@ QGroupBox *ExportDialog::setupOptionsGroupBox() { export_elements = new QRadioButton(tr("Exporter les \351l\351ments"), groupbox_options); optionshlayout -> addWidget(export_elements, 0, 1); exported_content_choices -> addButton(export_elements); - (diagram -> useBorder() ? export_border : export_elements) -> setChecked(true); + export_border -> setChecked(true); + connect(exported_content_choices, SIGNAL(buttonClicked(QAbstractButton *)), this, SLOT(slot_changeUseBorder())); // dessiner la grille draw_grid = new QCheckBox(tr("Dessiner la grille"), groupbox_options); - optionshlayout -> addWidget(draw_grid, 1, 0); + optionshlayout -> addWidget(draw_grid, 1, 1); // dessiner le cadre draw_border = new QCheckBox(tr("Dessiner le cadre"), groupbox_options); - optionshlayout -> addWidget(draw_border, 1, 1); - draw_border -> setChecked(diagram -> border_and_inset.borderIsDisplayed()); + optionshlayout -> addWidget(draw_border, 1, 0); + draw_border -> setChecked(true); // dessiner le cartouche draw_inset = new QCheckBox(tr("Dessiner le cartouche"), groupbox_options); optionshlayout -> addWidget(draw_inset, 2, 0); - draw_inset -> setChecked(diagram -> border_and_inset.insetIsDisplayed()); - - // dessiner les colonnes - draw_columns = new QCheckBox(tr("Dessiner les colonnes"), groupbox_options); - optionshlayout -> addWidget(draw_columns, 2, 1); - draw_columns -> setChecked(diagram -> border_and_inset.columnsAreDisplayed()); - - // dessiner les lignes - draw_rows = new QCheckBox(tr("Dessiner les lignes"), groupbox_options); - optionshlayout -> addWidget(draw_rows, 3, 1); - draw_rows -> setChecked(diagram -> border_and_inset.rowsAreDisplayed()); + draw_inset -> setChecked(true); // dessiner les bornes draw_terminals = new QCheckBox(tr("Dessiner les bornes"), groupbox_options); - optionshlayout -> addWidget(draw_terminals, 3, 0); + optionshlayout -> addWidget(draw_terminals, 2, 1); return(groupbox_options); } +/** + Met en place la liste des schemas + @return Le widget representant la liste des schemas +*/ +QWidget *ExportDialog::initDiagramsListPart() { + preview_mapper_ = new QSignalMapper(this); + width_mapper_ = new QSignalMapper(this); + height_mapper_ = new QSignalMapper(this); + ratio_mapper_ = new QSignalMapper(this); + reset_mapper_ = new QSignalMapper(this); + + connect(preview_mapper_, SIGNAL(mapped(int)), this, SLOT(slot_previewDiagram(int))); + connect(width_mapper_, SIGNAL(mapped(int)), this, SLOT(slot_correctHeight(int))); + connect(height_mapper_, SIGNAL(mapped(int)), this, SLOT(slot_correctWidth(int))); + connect(ratio_mapper_, SIGNAL(mapped(int)), this, SLOT(slot_keepRatioChanged(int))); + connect(reset_mapper_, SIGNAL(mapped(int)), this, SLOT(slot_resetSize(int))); + + diagrams_list_layout_ = new QGridLayout(); + + int line_count = 0; + diagrams_list_layout_ -> addWidget(new QLabel(tr("Sch\351ma")), line_count, 1, Qt::AlignHCenter | Qt::AlignVCenter); + diagrams_list_layout_ -> addWidget(new QLabel(tr("Nom de fichier")), line_count, 2, Qt::AlignHCenter | Qt::AlignVCenter); + diagrams_list_layout_ -> addWidget(new QLabel(tr("Dimensions")), line_count, 3, Qt::AlignHCenter | Qt::AlignVCenter); + + // remplit la liste + foreach (Diagram *diagram, project_ -> diagrams()) { + ++ line_count; + ExportDiagramLine *diagram_line = new ExportDiagramLine(diagram); + diagram_lines_.insert(line_count, diagram_line); + diagrams_list_layout_ -> addWidget(diagram_line -> must_export, line_count, 0); + diagrams_list_layout_ -> addWidget(diagram_line -> title_label, line_count, 1); + diagrams_list_layout_ -> addWidget(diagram_line -> file_name, line_count, 2); + diagrams_list_layout_ -> addLayout(diagram_line -> sizeLayout(), line_count, 3); + + // si on decoche tous les schemas, on desactive le bouton "Exporter" + connect(diagram_line -> must_export, SIGNAL(toggled(bool)), this, SLOT(slot_checkDiagramsCount())); + + // mappings et signaux pour la gestion des dimensions du schema + width_mapper_ -> setMapping(diagram_line -> width, line_count); + height_mapper_ -> setMapping(diagram_line -> height, line_count); + ratio_mapper_ -> setMapping(diagram_line -> keep_ratio, line_count); + reset_mapper_ -> setMapping(diagram_line -> reset_size, line_count); + connect(diagram_line -> width, SIGNAL(valueChanged(int)), width_mapper_, SLOT(map())); + connect(diagram_line -> height, SIGNAL(valueChanged(int)), height_mapper_, SLOT(map())); + connect(diagram_line -> keep_ratio, SIGNAL(toggled(bool)), ratio_mapper_, SLOT(map())); + connect(diagram_line -> reset_size, SIGNAL(clicked(bool)), reset_mapper_, SLOT(map())); + + // mappings et signaux pour l'apercu du schema + preview_mapper_ -> setMapping(diagram_line -> preview, line_count); + connect(diagram_line -> preview, SIGNAL(clicked(bool)), preview_mapper_, SLOT(map())); + } + + QWidget *widget_diagrams_list = new QWidget(); + widget_diagrams_list -> setLayout(diagrams_list_layout_); + + QScrollArea *scroll_diagrams_list = new QScrollArea(); + scroll_diagrams_list -> setWidget(widget_diagrams_list); + + return(scroll_diagrams_list); +} + /** Met en place la partie gauche du dialogue @return Le widget representant la moitie gauche du dialogue @@ -181,15 +186,19 @@ QWidget *ExportDialog::leftPart() { // la partie gauche du dialogue est un empilement vertical d'elements QVBoxLayout *vboxLayout = new QVBoxLayout(retour); - /* le dialogue comprend une ligne permettant d'indiquer un chemin de fichier (hboxLayout) */ + /* le dialogue comprend une ligne permettant d'indiquer un chemin de dossier (hboxLayout) */ QHBoxLayout *hboxLayout = new QHBoxLayout(); - hboxLayout -> addWidget(new QLabel(tr("Nom de fichier :"), this)); - hboxLayout -> addWidget(filename = new QLineEdit(this)); - filename -> setText(QDir::toNativeSeparators(QDir::homePath())); + QLabel *dirpath_label = new QLabel(tr("Dossier cible :"), this); + dirpath = new QLineEdit(this); + dirpath -> setText(QDir::toNativeSeparators(QDir::homePath())); QCompleter *completer = new QCompleter(this); completer -> setModel(new QDirModel(completer)); - filename -> setCompleter(completer); - hboxLayout -> addWidget(button_browse = new QPushButton(tr("Parcourir"), this)); + dirpath -> setCompleter(completer); + button_browse = new QPushButton(tr("Parcourir"), this); + hboxLayout -> addWidget(dirpath_label); + hboxLayout -> addWidget(dirpath); + hboxLayout -> addWidget(button_browse); + hboxLayout -> addStretch(); vboxLayout -> addLayout(hboxLayout); @@ -201,117 +210,192 @@ QWidget *ExportDialog::leftPart() { format -> addItem(tr("JPEG (*.jpg)"), "JPG"); format -> addItem(tr("Bitmap (*.bmp)"), "BMP"); format -> addItem(tr("SVG (*.svg)"), "SVG"); + hboxLayout1 -> addStretch(); vboxLayout -> addLayout(hboxLayout1); - /* un cadre permettant de specifier les dimensions de l'image finale */ - vboxLayout -> addWidget(setupDimensionsGroupBox()); - /* un cadre permettant de specifier les options de l'image finale */ vboxLayout -> addWidget(setupOptionsGroupBox()); vboxLayout -> addStretch(); // ordre des input selectionnes avec la touche tab - setTabOrder(filename, button_browse); + + setTabOrder(dirpath, button_browse); setTabOrder(button_browse, format); - setTabOrder(format, width); - setTabOrder(width, height); - setTabOrder(height, keep_aspect_ratio); - + setTabOrder(format, export_border); + setTabOrder(export_border, draw_border); + setTabOrder(draw_border, draw_grid); + setTabOrder(draw_grid, draw_inset); + setTabOrder(draw_inset, draw_terminals); return(retour); } /** - Met en place la partie droite du dialogue - @return Le widget representant la moitie droite du dialogue + @param diagram Un schema + @return le rapport largeur / hauteur du schema */ -QWidget *ExportDialog::rightPart() { - QWidget *retour = new QWidget(); - QHBoxLayout *hboxlayout0 = new QHBoxLayout(retour); - - - // la partie droite contient une GroupBox intitulee "Apercu" - QGroupBox *groupbox_preview = new QGroupBox(tr("Aper\347u"), this); - groupbox_preview -> setMinimumWidth(390); - QHBoxLayout *hboxlayout1 = new QHBoxLayout(groupbox_preview); - hboxlayout1 -> setMargin(0); - - // cette GroupBox contient l'apercu - preview_scene = new QGraphicsScene(); - preview_view = new QGraphicsView(preview_scene, groupbox_preview); - hboxlayout1 -> addWidget(preview_view); - - // genere le premier apercu - slot_refreshPreview(); - - hboxlayout0 -> addWidget(groupbox_preview); - return(retour); +qreal ExportDialog::diagramRatio(Diagram *diagram) { + QSize diagram_size = diagramSize(diagram); + qreal diagram_ratio = (qreal)diagram_size.width() / (qreal)diagram_size.height(); + return(diagram_ratio); } /** - Slot corrigeant la largeur (typiquement lors d'un changement de la hauteur) + @param diagram Un schema + @return les dimensions du schema, en tenant compte du type d'export : cadre + ou elements */ -void ExportDialog::slot_correctWidth() { - if (!keep_aspect_ratio -> isChecked() || dontchangewidth) return; - dontchangeheight = true; - width -> setValue(qRound(height -> value() * diagram_ratio)); - dontchangeheight = false; +QSize ExportDialog::diagramSize(Diagram *diagram) { + // sauvegarde le parametre useBorder du schema + bool state_useBorder = diagram -> useBorder(); + + // applique le useBorder adequat et calcule le ratio + diagram -> setUseBorder(export_border -> isChecked()); + QSize diagram_size = diagram -> imageSize(); + + // restaure le parametre useBorder du schema + diagram -> setUseBorder(state_useBorder); + + return(diagram_size); } /** - Slot corrigeant la hauteur (typiquement lors d'un changement de la largeur) + Cette methode ajuste la largeur d'un des schemas a exporter en fonction de + sa hauteur si et seulement si l'option "Conserver les proportions" est + activee pour ce schema. + @param diagram_id numero du schema concerne */ -void ExportDialog::slot_correctHeight() { - if (!keep_aspect_ratio -> isChecked() || dontchangeheight) return; - dontchangewidth = true; - height -> setValue(qRound(width -> value() / diagram_ratio)); - dontchangewidth = false; +void ExportDialog::slot_correctWidth(int diagram_id) { + // recupere l'ExportDiagramLine concernee + ExportDialog::ExportDiagramLine *current_diagram = diagram_lines_[diagram_id]; + if (!current_diagram) return; + + // ne fait rien si l'option "Conserver les proportions" n'est pas activee + if (!(current_diagram -> keep_ratio -> isChecked())) return; + + // recupere les proportions du schema + qreal diagram_ratio = diagramRatio(current_diagram -> diagram); + + // ajuste la largeur + current_diagram -> width -> blockSignals(true); + current_diagram -> width -> setValue(qRound(current_diagram -> height -> value() * diagram_ratio)); + current_diagram -> width -> blockSignals(false); } /** - Slot demandant a l'utilisateur de choisir un fichier + Cette methode ajuste la hauteur d'un des schemas a exporter en fonction de + sa largeur si et seulement si l'option "Conserver les proportions" est + activee pour ce schema. + @param diagram_id numero du schema concerne */ -void ExportDialog::slot_chooseAFile() { - QString user_file = QFileDialog::getSaveFileName( +void ExportDialog::slot_correctHeight(int diagram_id) { + // recupere l'ExportDiagramLine concernee + ExportDialog::ExportDiagramLine *current_diagram = diagram_lines_[diagram_id]; + if (!current_diagram) return; + + // ne fait rien si l'option "Conserver les proportions" n'est pas activee + if (!(current_diagram -> keep_ratio -> isChecked())) return; + + // recupere les proportions du schema + qreal diagram_ratio = diagramRatio(current_diagram -> diagram); + + // ajuste la hauteur + current_diagram -> height -> blockSignals(true); + current_diagram -> height -> setValue(qRound(current_diagram -> width -> value() / diagram_ratio)); + current_diagram -> height -> blockSignals(false); +} + +/** + Prend en compte le fait qu'il faut desormais conserver ou non les + proportions d'un des schemas + @param diagram_id numero du schema concerne +*/ +void ExportDialog::slot_keepRatioChanged(int diagram_id) { + // recupere l'ExportDiagramLine concernee + ExportDialog::ExportDiagramLine *current_diagram = diagram_lines_[diagram_id]; + if (!current_diagram) return; + + // gere l'icone du bouton "Conserver les proportions" + if (current_diagram -> keep_ratio -> isChecked()) { + current_diagram -> keep_ratio -> setIcon(QIcon(":/ico/lock.png")); + } else { + current_diagram -> keep_ratio -> setIcon(QIcon(":/ico/unlock.png")); + } + + // ne fait rien si l'option "Conserver les proportions" n'est pas activee + if (!(current_diagram -> keep_ratio -> isChecked())) return; + + // au contraire, si elle est activee, ajuste la hauteur en fonction de la largeur + slot_correctHeight(diagram_id); +} + +/** + Reinitialise les dimensions d'un des schemas + @param diagram_id numero du schema concerne +*/ +void ExportDialog::slot_resetSize(int diagram_id) { + // recupere l'ExportDiagramLine concernee + ExportDialog::ExportDiagramLine *current_diagram = diagram_lines_[diagram_id]; + if (!current_diagram) return; + + // recupere la taille du schema + QSize diagram_size = diagramSize(current_diagram -> diagram); + + // reinitialise les champs largeur et hauteur + current_diagram -> width -> blockSignals(true); + current_diagram -> height -> blockSignals(true); + current_diagram -> width -> setValue(diagram_size.width()); + current_diagram -> height -> setValue(diagram_size.height()); + current_diagram -> width -> blockSignals(false); + current_diagram -> height -> blockSignals(false); +} + +/** + Slot demandant a l'utilisateur de choisir un dossier +*/ +void ExportDialog::slot_chooseADirectory() { + QString user_dir = QFileDialog::getExistingDirectory( this, - tr("Exporter vers le fichier"), - QDir::homePath(), - tr("Images (*.png *.bmp *.jpg *.svg)") + tr("Exporter dans le dossier", "dialog title"), + QDir::homePath() ); - if (!user_file.isEmpty()) { - filename -> setText(user_file); + if (!user_dir.isEmpty()) { + dirpath -> setText(user_dir); } } /** Genere l'image a exporter + @param diagram Schema a exporter en SVG + @param width Largeur de l'export + @param height Hauteur de l'export + @param keep_aspect_ratio True pour conserver le ratio, false sinon @return l'image a exporter */ -QImage ExportDialog::generateImage() { - saveReloadDiagramParameters(true); +QImage ExportDialog::generateImage(Diagram *diagram, int width, int height, bool keep_aspect_ratio) { + saveReloadDiagramParameters(diagram, true); - QImage image(width -> value(), height -> value(), QImage::Format_RGB32); + QImage image(width, height, QImage::Format_RGB32); diagram -> toPaintDevice( image, - width -> value(), - height -> value(), - keep_aspect_ratio -> isChecked() ? Qt::KeepAspectRatio : Qt::IgnoreAspectRatio + width, + height, + keep_aspect_ratio ? Qt::KeepAspectRatio : Qt::IgnoreAspectRatio ); - saveReloadDiagramParameters(false); + saveReloadDiagramParameters(diagram, false); return(image); } /** Sauve ou restaure les parametres du schema + @param diagram Schema dont on sauve ou restaure les parametres @param save true pour memoriser les parametres du schema et appliquer ceux definis par le formulaire, false pour restaurer les parametres */ -void ExportDialog::saveReloadDiagramParameters(bool save) { +void ExportDialog::saveReloadDiagramParameters(Diagram *diagram, bool save) { static bool state_drawBorder; - static bool state_drawColumns; - static bool state_drawRows; static bool state_drawInset; static bool state_drawGrid; static bool state_drawTerm; @@ -320,8 +404,6 @@ void ExportDialog::saveReloadDiagramParameters(bool save) { if (save) { // memorise les parametres relatifs au schema state_drawBorder = diagram -> border_and_inset.borderIsDisplayed(); - state_drawColumns = diagram -> border_and_inset.columnsAreDisplayed(); - state_drawRows = diagram -> border_and_inset.rowsAreDisplayed(); state_drawInset = diagram -> border_and_inset.insetIsDisplayed(); state_drawGrid = diagram -> displayGrid(); state_drawTerm = diagram -> drawTerminals(); @@ -331,14 +413,10 @@ void ExportDialog::saveReloadDiagramParameters(bool save) { diagram -> setDrawTerminals(draw_terminals -> isChecked()); diagram -> setDisplayGrid(draw_grid -> isChecked()); diagram -> border_and_inset.displayBorder(draw_border -> isChecked()); - diagram -> border_and_inset.displayColumns(draw_columns -> isChecked()); - diagram -> border_and_inset.displayRows(draw_rows -> isChecked()); diagram -> border_and_inset.displayInset(draw_inset -> isChecked()); } else { // restaure les parametres relatifs au schema diagram -> border_and_inset.displayBorder(state_drawBorder); - diagram -> border_and_inset.displayColumns(state_drawColumns); - diagram -> border_and_inset.displayRows(state_drawRows); diagram -> border_and_inset.displayInset(state_drawInset); diagram -> setDisplayGrid(state_drawGrid); diagram -> setDrawTerminals(state_drawTerm); @@ -348,54 +426,104 @@ void ExportDialog::saveReloadDiagramParameters(bool save) { /** Exporte le schema en SVG + @param diagram Schema a exporter en SVG + @param width Largeur de l'export SVG + @param height Hauteur de l'export SVG + @param keep_aspect_ratio True pour conserver le ratio, false sinon @param file Fichier dans lequel sera enregistre le code SVG */ -void ExportDialog::generateSvg(QFile &file) { - saveReloadDiagramParameters(true); +void ExportDialog::generateSvg(Diagram *diagram, int width, int height, bool keep_aspect_ratio, QFile &file) { + saveReloadDiagramParameters(diagram, true); // genere une QPicture a partir du schema QPicture picture; diagram -> toPaintDevice( picture, - width -> value(), - height -> value(), - keep_aspect_ratio -> isChecked() ? Qt::KeepAspectRatio : Qt::IgnoreAspectRatio + width, + height, + keep_aspect_ratio ? Qt::KeepAspectRatio : Qt::IgnoreAspectRatio ); // "joue" la QPicture sur un QSvgGenerator QSvgGenerator svg_engine; - svg_engine.setSize(QSize(width -> value(), height -> value())); + svg_engine.setSize(QSize(width, height)); svg_engine.setOutputDevice(&file); QPainter svg_painter(&svg_engine); picture.play(&svg_painter); - saveReloadDiagramParameters(false); + saveReloadDiagramParameters(diagram, false); } /** - Slot effectuant les verifications necessaires apres la validation du - dialogue. + Slot effectuant les exports apres la validation du dialogue. */ -void ExportDialog::slot_check() { - QString diagram_path = filename -> text(); +void ExportDialog::slot_export() { + // recupere la liste des schemas a exporter + QList diagrams_to_export; + foreach(ExportDiagramLine *diagram_line, diagram_lines_.values()) { + if (diagram_line -> must_export -> isChecked()) { + diagrams_to_export << diagram_line; + } + } - // verifie que le fichier a ete specifie - if (diagram_path.isEmpty()) { - QMessageBox::information( + // verification #1 : chaque schema coche doit avoir un nom de fichier distinct + QSet filenames; + foreach(ExportDiagramLine *diagram_line, diagrams_to_export) { + QString diagram_file = diagram_line -> file_name -> text(); + if (!diagram_file.isEmpty()) { + filenames << diagram_file; + } + } + if (filenames.count() != diagrams_to_export.count()) { + QMessageBox::warning( this, - tr("Fichier non sp\351cifi\351"), - tr("Vous devez sp\351cifier le chemin du fichier dans lequel sera enregistr\351e l'image."), + tr("Noms des fichiers cibles", "message box title"), + tr( + "Vous devez entrer un nom de fichier distinct pour chaque " + "sch\351ma \340 exporter.", + "message box content" + ) + ); + return; + } + + // verification #2 : un chemin vers un dossier doit avoir ete specifie + QDir target_dir_path(dirpath -> text()); + if (dirpath -> text().isEmpty() || !target_dir_path.exists()) { + QMessageBox::warning( + this, + tr("Dossier non sp\351cifi\351", "message box title"), + tr("Vous devez sp\351cifier le chemin du dossier dans lequel seront enregistr\351s les fichiers images.", "message box content"), QMessageBox::Ok ); return; } + // exporte chaque schema a exporter + foreach(ExportDiagramLine *diagram_line, diagrams_to_export) { + exportDiagram(diagram_line); + } + + // fermeture du dialogue + accept(); +} + +/** + Exporte un schema + @param diagram_line La ligne decrivant le schema a exporter et la maniere + de l'exporter +*/ +void ExportDialog::exportDiagram(ExportDiagramLine *diagram_line) { // recupere le format a utiliser (acronyme et extension) QString format_acronym = format -> itemData(format -> currentIndex()).toString(); QString format_extension = "." + format_acronym.toLower(); - // corrige l'extension du fichier - if (!diagram_path.endsWith(format_extension, Qt::CaseInsensitive)) diagram_path += format_extension; + // determine le nom de fichier a utiliser + QString diagram_path = diagram_line -> file_name -> text(); + + // determine le chemin du fichier du fichier + QDir target_dir_path(dirpath -> text()); + diagram_path = target_dir_path.absoluteFilePath(diagram_path); // recupere des informations sur le fichier specifie QFileInfo file_infos(diagram_path); @@ -404,56 +532,136 @@ void ExportDialog::slot_check() { if (file_infos.exists() && !file_infos.isWritable()) { QMessageBox::critical( this, - tr("Impossible d'\351crire dans ce fichier"), - tr("Il semblerait que vous n'ayez pas les permissions n\351cessaires pour \351crire dans ce fichier.."), + tr("Impossible d'\351crire dans ce fichier", "message box title"), + QString( + tr( + "Il semblerait que vous n'ayez pas les permissions " + "n\351cessaires pour \351crire dans le fichier %1.", + "message box content" + ) + ).arg(diagram_path), QMessageBox::Ok ); return; } // ouvre le fichier - QFile fichier(diagram_path); + QFile target_file(diagram_path); // enregistre l'image dans le fichier if (format_acronym == "SVG") { - generateSvg(fichier); + generateSvg( + diagram_line -> diagram, + diagram_line -> width -> value(), + diagram_line -> height -> value(), + diagram_line -> keep_ratio -> isChecked(), + target_file + ); } else { - QImage image = generateImage(); - image.save(&fichier, format_acronym.toUtf8().data()); + QImage image = generateImage( + diagram_line -> diagram, + diagram_line -> width -> value(), + diagram_line -> height -> value(), + diagram_line -> keep_ratio -> isChecked() + ); + image.save(&target_file, format_acronym.toUtf8().data()); } - fichier.close(); - - // fermeture du dialogue - accept(); + target_file.close(); } /** Slot appele lorsque l'utilisateur change la zone du schema qui doit etre - exportee. Il faut alors calculer le nouveau ratio et corriger les - dimensions. + exportee. Il faut alors ajuster les dimensons des schemas. */ void ExportDialog::slot_changeUseBorder() { - // calcule le nouveau ratio - bool state_useBorder = diagram -> useBorder(); - diagram -> setUseBorder(export_border -> isChecked()); - diagram_size = diagram -> imageSize(); - diagram_ratio = (qreal)diagram_size.width() / (qreal)diagram_size.height(); - diagram -> setUseBorder(state_useBorder); - - // corrige les dimensions - if (keep_aspect_ratio -> isChecked()) slot_correctHeight(); - - // rafraichit l'apercu - slot_refreshPreview(); + // parcourt les schemas a exporter + foreach(int diagram_id, diagram_lines_.keys()) { + ExportDiagramLine *diagram_line = diagram_lines_[diagram_id]; + + // corrige les dimensions des schemas dont il faut preserver le ratio + if (diagram_line -> keep_ratio -> isChecked()) { + slot_correctHeight(diagram_id); + } + } } /** - Rafraichit l'apercu de l'export + Ce slot active ou desactive le bouton "Exporter" en fonction du nombre de + schemas coches. */ -void ExportDialog::slot_refreshPreview() { +void ExportDialog::slot_checkDiagramsCount() { + QPushButton *export_button = buttons -> button(QDialogButtonBox::Save); + export_button -> setEnabled(diagramsToExportCount()); +} + +/** + Modifie les extensions des fichiers en fonction du format selectionne + @param force_extension true pour ajouter l'extension si elle n'est pas + presente, false pour se contenter de la modifier si elle est incorrecte. +*/ +void ExportDialog::slot_changeFilesExtension(bool force_extension) { + // recupere le format a utiliser (acronyme et extension) + QString format_acronym = format -> itemData(format -> currentIndex()).toString(); + QString format_extension = "." + format_acronym.toLower(); + + // parcourt les schemas a exporter + foreach(ExportDiagramLine *diagram_line, diagram_lines_.values()) { + QString diagram_filename = diagram_line -> file_name -> text(); + + // cas 1 : l'extension est presente et correcte : on ne fait rien + if (diagram_filename.endsWith(format_extension, Qt::CaseInsensitive)) { + continue; + } + + QFileInfo diagram_filename_info(diagram_filename); + // cas 2 : l'extension est absente + if (diagram_filename_info.suffix().isEmpty()) { + if (force_extension) { + diagram_filename = diagram_filename_info.completeBaseName() + format_extension; + } + } else { + // cas 3 : l'extension est presente mais erronee + diagram_filename = diagram_filename_info.completeBaseName() + format_extension; + } + + diagram_line -> file_name -> setText(diagram_filename); + } +} + +/** + Cette methode fait apparaitre un dialogue permettant de redimensionner et + previsualiser un des schemas a exporter + @param diagram_id numero du schema a previsualiser +*/ +void ExportDialog::slot_previewDiagram(int diagram_id) { + // recupere l'ExportDiagramLine concernee + ExportDialog::ExportDiagramLine *current_diagram = diagram_lines_[diagram_id]; + if (!current_diagram) return; + + // initialise un dialogue + QDialog preview_dialog; + preview_dialog.setWindowTitle(tr("Aper\347u")); + preview_dialog.setWindowState(preview_dialog.windowState() | Qt::WindowMaximized); + + QGraphicsScene *preview_scene = new QGraphicsScene(); + preview_scene -> setBackgroundBrush(Qt::lightGray); + QGraphicsView *preview_view = new QGraphicsView(preview_scene); + preview_view -> setDragMode(QGraphicsView::ScrollHandDrag); + QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok); + connect(buttons, SIGNAL(accepted()), &preview_dialog, SLOT(accept())); + + QVBoxLayout *vboxlayout1 = new QVBoxLayout(); + vboxlayout1 -> addWidget(preview_view); + vboxlayout1 -> addWidget(buttons); + preview_dialog.setLayout(vboxlayout1); // genere le nouvel apercu - QImage preview_image = generateImage(); + QImage preview_image = generateImage( + current_diagram -> diagram, + current_diagram -> width -> value(), + current_diagram -> height -> value(), + current_diagram -> keep_ratio -> isChecked() + ); // nettoie l'apercu foreach (QGraphicsItem *qgi, preview_scene -> items()) { @@ -465,4 +673,79 @@ void ExportDialog::slot_refreshPreview() { QGraphicsPixmapItem *qgpi = new QGraphicsPixmapItem(QPixmap::fromImage(preview_image)); preview_scene -> addItem(qgpi); preview_scene -> setSceneRect(QRectF(0.0, 0.0, preview_image.width(), preview_image.height())); + + // montre l'apercu + preview_dialog.exec(); +} + +/** + Constructeur + @param dia Schema concerne +*/ +ExportDialog::ExportDiagramLine::ExportDiagramLine(Diagram *dia) { + diagram = dia; + must_export = new QCheckBox(); + must_export -> setChecked(true); + + // titre et nom de fichier du schema + QString diagram_title = diagram -> title(); + if (diagram_title.isEmpty()) diagram_title = QObject::tr("Sch\351ma sans titre"); + QString diagram_filename = diagram -> title(); + if (diagram_filename.isEmpty()) diagram_filename = QObject::tr("schema"); + diagram_filename = QET::stringToFileName(diagram_filename); + + title_label = new QLabel(diagram_title); + + file_name = new QLineEdit(); + file_name -> setText(diagram_filename); + file_name -> setMinimumWidth(180); + + QSize diagram_size = diagram -> imageSize(); + + width = new QSpinBox(); + width -> setRange(1, 10000); + width -> setSuffix(tr("px")); + width -> setValue(diagram_size.width()); + + height = new QSpinBox(); + height -> setRange(1, 10000); + height -> setSuffix(tr("px")); + height -> setValue(diagram_size.height()); + + x_label = new QLabel("\327"); + + keep_ratio = new QPushButton(); + keep_ratio -> setCheckable(true); + keep_ratio -> setChecked(true); + keep_ratio -> setIcon(QIcon(":/ico/lock.png")); + keep_ratio -> setToolTip(QObject::tr("Conserver les proportions")); + + reset_size = new QPushButton(); + reset_size -> setIcon(QIcon(":/ico/start.png")); + reset_size -> setToolTip(QObject::tr("R\351initialiser les dimensions")); + + preview = new QPushButton(); + preview -> setIcon(QIcon(":/ico/viewmag.png")); + preview -> setToolTip(QObject::tr("Aper\347u")); +} + +/** + Destructeur +*/ +ExportDialog::ExportDiagramLine::~ExportDiagramLine() { +} + +/** + @return un layout contenant les widgets necessaires a la gestion de la + taille d'un schema avant son export. +*/ +QBoxLayout *ExportDialog::ExportDiagramLine::sizeLayout() { + QHBoxLayout *layout = new QHBoxLayout(); + layout -> addWidget(width); + layout -> addWidget(x_label); + layout -> addWidget(height); + layout -> addWidget(keep_ratio); + layout -> addWidget(reset_size); + layout -> addWidget(preview); + return(layout); } diff --git a/sources/exportdialog.h b/sources/exportdialog.h index 783cf4a79..e89d0bae3 100644 --- a/sources/exportdialog.h +++ b/sources/exportdialog.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -19,6 +19,7 @@ #define EXPORTDIALOG_H #include #include "diagram.h" +#include "qetproject.h" class QSvgGenerator; /** Cette classe represente le dialogue permettant d'exporter un schema @@ -29,59 +30,82 @@ class ExportDialog : public QDialog { // constructeurs, destructeur public: - ExportDialog(Diagram *, QWidget * = 0); + ExportDialog(QETProject *, QWidget * = 0); virtual ~ExportDialog(); private: ExportDialog(const ExportDialog &); + // methodes + public: + int diagramsToExportCount() const; + + // classes privees + private: + class ExportDiagramLine { + public: + ExportDiagramLine(Diagram *); + virtual ~ExportDiagramLine(); + QBoxLayout *sizeLayout(); + Diagram *diagram; + QCheckBox *must_export; + QLabel *title_label; + QLineEdit *file_name; + QSpinBox *width; + QLabel *x_label; + QSpinBox *height; + QPushButton *keep_ratio; + QPushButton *reset_size; + QPushButton *preview; + }; + // attributs private: + QHash diagram_lines_; // elements graphiques - QLineEdit *filename; + QGridLayout *diagrams_list_layout_; + QLineEdit *dirpath; QPushButton *button_browse; QComboBox *format; - QSpinBox *width; - QSpinBox *height; - QCheckBox *keep_aspect_ratio; QCheckBox *draw_grid; QCheckBox *draw_border; QCheckBox *draw_inset; - QCheckBox *draw_columns; - QCheckBox *draw_rows; QCheckBox *draw_terminals; QRadioButton *export_elements; QRadioButton *export_border; QDialogButtonBox *buttons; - QGraphicsScene *preview_scene; - QGraphicsView *preview_view; - - // booleens pour ne pas avoir de boucle lors de l'edition des dimensions de l'image - bool dontchangewidth; - bool dontchangeheight; + // mappers + QSignalMapper *preview_mapper_; + QSignalMapper *width_mapper_; + QSignalMapper *height_mapper_; + QSignalMapper *ratio_mapper_; + QSignalMapper *reset_mapper_; // elements relatifs au traitement effectue par le dialogue - Diagram *diagram; - QSize diagram_size; - qreal diagram_ratio; - QVector ColorTab; + QETProject *project_; // methodes private: + QWidget *initDiagramsListPart(); QWidget *leftPart(); - QWidget *rightPart(); - QGroupBox *setupDimensionsGroupBox(); QGroupBox *setupOptionsGroupBox(); - void saveReloadDiagramParameters(bool = true); - void generateSvg(QFile &file); - QImage generateImage(); + void saveReloadDiagramParameters(Diagram *, bool = true); + void generateSvg(Diagram *, int, int, bool, QFile &); + QImage generateImage(Diagram *, int, int, bool); + void exportDiagram(ExportDiagramLine *); + qreal diagramRatio(Diagram *); + QSize diagramSize(Diagram *); public slots: - void slot_correctWidth(); - void slot_correctHeight(); - void slot_chooseAFile(); - void slot_check(); + void slot_correctWidth(int); + void slot_correctHeight(int); + void slot_keepRatioChanged(int); + void slot_resetSize(int); + void slot_chooseADirectory(); + void slot_export(); void slot_changeUseBorder(); - void slot_refreshPreview(); + void slot_checkDiagramsCount(); + void slot_changeFilesExtension(bool = false); + void slot_previewDiagram(int); }; #endif diff --git a/sources/fileelementdefinition.cpp b/sources/fileelementdefinition.cpp new file mode 100644 index 000000000..5a072d88a --- /dev/null +++ b/sources/fileelementdefinition.cpp @@ -0,0 +1,198 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#include "fileelementdefinition.h" +#include "fileelementscategory.h" +#include "fileelementscollection.h" +#include "qetapp.h" +/** + Constructeur + @param uri Chemin du fichier contenant la definition de l'element + @param collection collection parente +*/ +FileElementDefinition::FileElementDefinition(const QString &uri, FileElementsCategory *category, FileElementsCollection *collection) : + ElementDefinition(category, collection), + is_null(true), + file_path(uri) +{ + reload(); +} + +/** + Destructeur +*/ +FileElementDefinition::~FileElementDefinition() { +} + +/** + @return la definition XML de l'element +*/ +QDomElement FileElementDefinition::xml() { + return(xml_element_.documentElement()); +} + +/** + Change la definition XML de l'element + @param xml_element Nouvelle definition XML de l'element + @return true si l'operation s'est bien passee, false sinon +*/ +bool FileElementDefinition::setXml(const QDomElement &xml_element) { + xml_element_.clear(); + xml_element_.appendChild(xml_element_.importNode(xml_element, true)); + return(true); +} + +/** + Enregistre la definition de l'element. + @return true si l'operation a reussi, false sinon +*/ +bool FileElementDefinition::write() { + QFile file(file_path); + + // le fichier doit etre accessible en ecriture + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) return(false); + + QTextStream out(&file); + out.setCodec("UTF-8"); + out << xml_element_.toString(4); + file.close(); + return(true); +} + +/** + @return true si la definition n'est pas disponible +*/ +bool FileElementDefinition::isNull() const { + return(is_null); +} + +/** + @return Le nom de cet element dans l'arborescence +*/ +QString FileElementDefinition::pathName() const { + return(QFileInfo(file_path).fileName()); +} + +/** + @return le chemin virtuel de cet element +*/ +QString FileElementDefinition::virtualPath() { + // il n'est pas possible d'avoir un chemin virtuel sans appartenir a une collection + if (!hasParentCollection()) return(QString()); + // recupere le chemin absolu de la racine de la collection + QString root_abs_path(parentCollection() -> filePath()); + + if (!file_path.startsWith(root_abs_path)) return(QString()); + QString virtual_path(file_path); + virtual_path.remove(root_abs_path); + virtual_path.remove(QRegExp("^/")); + return(virtual_path); +} + +/** + Recharge l'element +*/ +void FileElementDefinition::reload() { + if (file_path.isEmpty()) { + is_null = true; + return; + } + + // recupere le chemin du fichier *.elmt correspondant + QFileInfo file_info(file_path); + if (!file_info.exists() || !file_info.isReadable()) { + is_null = true; + return; + } + file_path = file_info.canonicalFilePath(); + + // ouvre le fichier + QFile file(file_path); + + // charge le contenu du fichier en s'attendant a du XML + bool read_xml = xml_element_.setContent(&file); + if (!read_xml) { + is_null = true; + return; + } + + // l'ouverture de la definition a reussi + is_null = false; +} + +/** + @return true si le fichier existe, false sinon +*/ +bool FileElementDefinition::exists() { + if (isNull()) return(false); + return(QFileInfo(file_path).exists()); +} + +/** + @return true si le fichier representant l'element est accessible en + lecture. +*/ +bool FileElementDefinition::isReadable() { + if (isNull()) return(false); + return(QFileInfo(file_path).isReadable()); +} + +/** + @return true si le fichier representant l'element est accessible en + ecriture. +*/ +bool FileElementDefinition::isWritable() { + if (isNull()) return(false); + return(QFileInfo(file_path).isWritable()); +} + +/** + Supprime le fichier representant l'element + @return true si l'operation s'est bien passee, false sinon +*/ +bool FileElementDefinition::remove() { + QFile elmt_file(file_path); + if (!elmt_file.exists()) return(true); + return(elmt_file.remove()); +} + +/** + @return true si cet element est represente quelque part sur le systeme de + fichiers +*/ +bool FileElementDefinition::hasFilePath() { + return(!file_path.isEmpty()); +} + +/** + @return le fichier representant cet element sur le systeme de fichiers +*/ +QString FileElementDefinition::filePath() { + return(file_path); +} + +/** + Definit le nouveau chemin de cet element dans le systeme de fichiers +*/ +void FileElementDefinition::setFilePath(const QString &path) { + // recupere le chemin du fichier *.elmt correspondant + QFileInfo file_info(path); + if (!file_info.exists() || !file_info.isReadable()) { + return; + } + file_path = file_info.canonicalFilePath(); +} diff --git a/sources/fileelementdefinition.h b/sources/fileelementdefinition.h new file mode 100644 index 000000000..7c484bfb7 --- /dev/null +++ b/sources/fileelementdefinition.h @@ -0,0 +1,59 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef FILE_ELEMENT_DEFINITION +#define FILE_ELEMENT_DEFINITION +#include +#include "elementdefinition.h" +class FileElementsCategory; +class FileElementsCollection; +/** + Cette classe represente la definition d'un element stockee dans un fichier. + +*/ +class FileElementDefinition : public ElementDefinition { + public: + FileElementDefinition(const QString &, FileElementsCategory * = 0, FileElementsCollection * = 0); + virtual ~FileElementDefinition(); + + private: + FileElementDefinition(const FileElementDefinition &); + + // methodes + public: + virtual QDomElement xml(); + virtual bool setXml(const QDomElement &); + virtual bool write(); + virtual bool isNull() const; + virtual QString pathName() const; + virtual QString virtualPath(); + virtual void reload(); + virtual bool exists(); + virtual bool isReadable(); + virtual bool isWritable(); + virtual bool remove(); + virtual bool hasFilePath(); + virtual QString filePath(); + virtual void setFilePath(const QString &); + + // attributs + private: + bool is_null; + QString file_path; + QDomDocument xml_element_; +}; +#endif diff --git a/sources/fileelementscategory.cpp b/sources/fileelementscategory.cpp new file mode 100644 index 000000000..026df9fd8 --- /dev/null +++ b/sources/fileelementscategory.cpp @@ -0,0 +1,495 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#include "fileelementscategory.h" +#include "fileelementscollection.h" +#include "fileelementdefinition.h" +/** + Constructeur + @param path Chemin du dossier de la categorie + @param parent Categorie parente + @param coll Collection parente +*/ +FileElementsCategory::FileElementsCategory(const QString &path, FileElementsCategory *parent, FileElementsCollection *coll) : + ElementsCategory(parent, coll), + file_parent_collection_(coll), + file_parent_category_(parent), + cat_dir(path) +{ + reload(); +} + +/** + Destructeur +*/ +FileElementsCategory::~FileElementsCategory() { + deleteContent(); +} + +/** + @return le nom de la categorie utilisable dans un chemin (virtuel ou reel) +*/ +QString FileElementsCategory::pathName() const { + return(cat_dir.dirName()); +} + +/** + @return le chemin virtuel de la categorie, sans le protocole +*/ +QString FileElementsCategory::virtualPath() { + + // il n'est pas possible d'avoir un chemin virtuel sans appartenir a une collection + if (!hasParentCollection()) return(QString()); + + // recupere le chemin absolu de la racine de la collection + QString root_abs_path(parentCollection() -> filePath()); + if (!filePath().startsWith(root_abs_path)) return(QString()); + + QString virtual_path(filePath()); + virtual_path.remove(root_abs_path); + virtual_path.remove(QRegExp("^/")); + return(virtual_path); +} + +/** + @return true si la categorie possede un chemin sur un systeme de fichiers +*/ +bool FileElementsCategory::hasFilePath() { + return(!filePath().isEmpty()); +} + +/** + @return le chemin sur le systeme de fichiers de la categorie +*/ +QString FileElementsCategory::filePath() { + return(cat_dir.path()); +} + +/** + Definit le chemin du dossier representant la categorie + @param p nouveau chemin du dossier representant la categorie +*/ +void FileElementsCategory::setFilePath(const QString &p) { + cat_dir.setPath(p); +} + +/** + @return la liste des sous-categories de la categorie +*/ +QList FileElementsCategory::categories() { + QList result; + + QList keys(categories_.keys()); + qSort(keys.begin(), keys.end()); + foreach(QString key, keys) result << categories_[key]; + + return(result); +} + +/** + @return la categorie correspondant au chemin virtuel cat_path, ou 0 en cas d'echec + @param cat_path Chemin virtuel de la categorie voulue +*/ +ElementsCategory *FileElementsCategory::category(const QString &cat_path) { + // recupere les differentes parties du chemin + QString cat_path_(cat_path); + QStringList path_parts = cat_path_.split(QChar('/'), QString::SkipEmptyParts, Qt::CaseInsensitive); + + if (!path_parts.count()) { + return(this); + } else { + // a-t-on la premiere sous-categorie ? + if (!categories_.contains(path_parts.first())) { + return(0); + } + + // on a la premiere sous-categorie + ElementsCategory *first_sub_cat = categories_.value(path_parts.first()); + + if (path_parts.count() == 1) return(first_sub_cat); + + // on demande a la premiere sous-categorie de fournir la categorie finale + path_parts.removeFirst(); + return(first_sub_cat -> category(path_parts.join("/"))); + } +} + +/** + Cree une categorie. La categorie parente doit exister. + @param path chemin d'une categorie a creer sous la forme d'une adresse + virtuelle comme common://cat1/cat2/cat3 + @return la nouvelle categorie demandee, ou 0 en cas d'echec +*/ +ElementsCategory *FileElementsCategory::createCategory(const QString &path) { + // on ne doit pas etre en lecture seule + if (!isWritable()) return(0); + + // recupere les differentes parties du chemin + QString cat_path_(path); + QStringList path_parts = cat_path_.split(QChar('/'), QString::SkipEmptyParts, Qt::CaseInsensitive); + + if (!path_parts.count()) { + // le chemin est vide, on renvoie 0 + return(0); + } else if (path_parts.count() == 1) { + // il n'y a plus qu'une categorie dans le chemin : il faut la creer ici + + // on verifie si la categorie n'existe pas deja + if (categories_.contains(path_parts[0])) { + return(categories_.value(path_parts[0])); + } + + // on cree un objet + FileElementsCategory *new_category = new FileElementsCategory( + cat_dir.absolutePath() + "/" + path_parts[0], + this, + file_parent_collection_ + ); + + // on l'integre dans la liste des sous-categories connues + categories_.insert(path_parts[0], new_category); + + // on le renvoie + return(new_category); + } else { + // il y a plusieurs categories dans le chemin : + // on delegue le travail a la premiere sous-categorie + + // a-t-on la premiere sous-categorie ? + if (!categories_.contains(path_parts.first())) { + return(0); + } + + // on a la premiere sous-categorie + ElementsCategory *first_sub_cat = categories_.value(path_parts.first()); + + // on demande a la premiere sous-categorie de fournir la categorie finale + path_parts.removeFirst(); + return(first_sub_cat -> category(path_parts.join("/"))); + } +} + +/** + @return la liste des elements de la categorie +*/ +QList FileElementsCategory::elements() { + QList result; + + QList keys(elements_.keys()); + qSort(keys.begin(), keys.end()); + foreach(QString key, keys) result << elements_[key]; + + return(result); +} + +/** + @return l'element correspondant au chemin virtuel elmt_path, ou 0 en cas d'echec + @param cat_path Chemin virtuel de l'element voulu +*/ +ElementDefinition *FileElementsCategory::element(const QString &elmt_path) { + // recupere les differentes parties du chemin + QString elmt_path_(elmt_path); + QStringList path_parts = elmt_path_.split(QChar('/'), QString::SkipEmptyParts, Qt::CaseInsensitive); + + if (!path_parts.count()) { + // chemin vide + return(0); + } else if (path_parts.count() == 1) { + // seulement le nom de fichier + QString element_filename = path_parts.takeLast(); + if (!elements_.contains(element_filename)) { + return(0); + } else { + return(elements_.value(element_filename)); + } + } else { + // separe le nom de fichier du chemin de la categorie et recupere celle-ci + QString element_filename = path_parts.takeLast(); + ElementsCategory *elmt_cat = category(path_parts.join("/")); + if (!elmt_cat) { + return(0); + } else { + return(elmt_cat -> element(element_filename)); + } + } +} + +/** + Cree un element. La categorie parente doit exister. + @param path chemin d'un element a creer sous la forme d'une adresse + virtuelle comme common://cat1/cat2/cat3/dog.elmt + @return le nouvel element demande, ou 0 en cas d'echec +*/ +ElementDefinition *FileElementsCategory::createElement(const QString &path) { + // on ne doit pas etre en lecture seule + if (!isWritable()) return(0); + + // recupere les differentes parties du chemin + QString cat_path_(path); + QStringList path_parts = cat_path_.split(QChar('/'), QString::SkipEmptyParts, Qt::CaseInsensitive); + + if (!path_parts.count()) { + // le chemin est vide, on renvoie 0 + return(0); + } else if (path_parts.count() == 1) { + // il n'y a plus que l'element dans le chemin : il faut le creer ici + + // on verifie si l'element n'existe pas deja + if (elements_.contains(path_parts[0])) { + return(elements_.value(path_parts[0])); + } + + // on cree un objet + FileElementDefinition *new_element = new FileElementDefinition( + cat_dir.absolutePath() + "/" + path_parts[0], + this, + file_parent_collection_ + ); + + // on l'integre dans la liste des elements connus + elements_.insert(path_parts[0], new_element); + + // on le renvoie + return(new_element); + } else { + // il y a plusieurs categories dans le chemin : + // on delegue le travail a la premiere sous-categorie + + // a-t-on la premiere sous-categorie ? + if (!categories_.contains(path_parts.first())) { + return(0); + } + + // on a la premiere sous-categorie + ElementsCategory *first_sub_cat = categories_.value(path_parts.first()); + + // on demande a la premiere sous-categorie de fournir la categorie finale + path_parts.removeFirst(); + return(first_sub_cat -> createElement(path_parts.join("/"))); + } +} + +/** + @return true si le dossier representant la categorie existe +*/ +bool FileElementsCategory::exists() { + return(cat_dir.exists()); +} + +/** + @return true si la categorie est accessible en lecture + Cett methode retourne true a partir du moment ou le dossier representant + cette categorie est accessible en lecture. Il se peut que les elements ou + le fichier qet_directory a l'interieur ne soient pas accessibles en + ecriture. +*/ +bool FileElementsCategory::isReadable() { + return(QFileInfo(cat_dir.absolutePath()).isReadable()); +} + +/** + @return true s'il est possible d'ecrire le fichier qet_directory dans la + categorie +*/ +bool FileElementsCategory::isWritable() { + // informations sur le dossier de la categorie + QFileInfo category(cat_dir.absolutePath()); + QFileInfo qet_directory(cat_dir.absolutePath() + "/qet_directory"); + /* + soit qet_directory n'existe pas et le dossier est accessible en ecriture, + soit qet_directory existe et est accessible en ecriture + */ + return( + category.isWritable() && // le dossier lui-meme est accessible en ecriture + ( + !qet_directory.exists() || + (qet_directory.exists() && qet_directory.isWritable()) + ) // le fichier qet_directory est accessible en ecriture + ); +} + +/** + Recharge le contenu et les noms de la categorie +*/ +void FileElementsCategory::reload() { + // supprime l'ancien contenu + deleteContent(); + category_names.clearNames(); + + // la categorie doit exister + if (!cat_dir.exists()) return; + + // charge les noms de la categorie + loadNames(); + + // charge les sous-categories + QStringList dirs = cat_dir.entryList(QStringList(), QDir::AllDirs | QDir::NoSymLinks | QDir::NoDotAndDotDot, QDir::Name); + foreach(QString dir, dirs) { + categories_.insert(dir, new FileElementsCategory(cat_dir.absoluteFilePath(dir), this, file_parent_collection_)); + } + + // charge les elements + QStringList elmts = cat_dir.entryList(QStringList("*.elmt"), QDir::Files, QDir::Name); + foreach(QString elmt, elmts) { + elements_.insert(elmt, new FileElementDefinition(cat_dir.absoluteFilePath(elmt), this, file_parent_collection_)); + } +} + +/** + Supprime le contenu de la categorie puis la categorie elle-meme + @return true si l'operation s'est bien passee, false sinon +*/ +bool FileElementsCategory::remove() { + // suppression du contenu de la categorie + if (!removeContent()) return(false); + + // une categorie racine ne se supprime pas elle-meme + if (isRootCategory()) return(true); + + // suppression du fichier de description de la categorie + if (cat_dir.exists("qet_directory")) { + if (!cat_dir.remove("qet_directory")) return(false); + } + + // suppression de la categorie elle-meme + return(cat_dir.rmdir(cat_dir.absolutePath())); +} + +/** + Supprime le contenu de la categorie sans supprimer la categorie elle-meme. + @return true si l'operation s'est bien passee, false sinon +*/ +bool FileElementsCategory::removeContent() { + // suppression des sous-categories + foreach(QString cat_name, categories_.keys()) { + ElementsCategory *cat = categories_.value(cat_name); + if (cat -> remove()) { + categories_.take(cat_name); + delete cat; + } else { + return(false); + } + } + + // suppression des elements + foreach(QString elmt_name, elements_.keys()) { + ElementDefinition *elmt = elements_.value(elmt_name); + if (elmt -> remove()) { + elements_.take(elmt_name); + delete elmt; + } else { + return(false); + } + } + return(true); +} + +/** + Cree la categorie + @return true si la creation a reussi, false sinon +*/ +bool FileElementsCategory::write() { + // cree le dossier de la categorie + if (!cat_dir.mkpath(cat_dir.path())) return(false); + + // prepare la structure XML + QDomDocument document; + QDomElement root = document.createElement("qet-directory"); + document.appendChild(root); + root.appendChild(category_names.toXml(document)); + + // repere le chemin du fichier de configuration de la categorie + QFile directory_conf(cat_dir.absolutePath() + "/qet_directory"); + + // ouvre le fichier + if (!directory_conf.open(QIODevice::Text | QIODevice::WriteOnly)) return(false); + + // ecrit le fichier + QTextStream out(&directory_conf); + out.setCodec("UTF-8"); + out << document.toString(4); + + // ferme le fichier + directory_conf.close(); + + return(true); +} + +/** + Supprime un repertoire recursivement. + @return true si la suppression a reussie, false sinon +*/ +bool FileElementsCategory::rmdir(const QString &path) const { + QDir directory(path); + + // supprime les dossiers, recursivement + foreach (QString file, directory.entryList(QDir::AllDirs | QDir::NoDotAndDotDot)) { + if (!rmdir(directory.absolutePath() + "/" + file)) return(false); + } + + // supprime les fichiers + foreach (QString file, directory.entryList(QDir::Files | QDir::NoDotAndDotDot)) { + if (!directory.remove(file)) return(false); + } + + // supprime le dossier lui-meme + return(directory.rmdir(path)); +} + +/** + Charge la liste des noms possibles pour la categorie +*/ +void FileElementsCategory::loadNames() { + // repere le chemin du fichier de configuration de la categorie + QFile directory_conf(cat_dir.absolutePath() + "/qet_directory"); + + // verifie l'existence du fichier + if (!directory_conf.exists()) return; + + // ouvre le fichier + if (!directory_conf.open(QIODevice::ReadOnly | QIODevice::Text)) return; + + // lit le contenu du fichier dans un QDomDocument XML + QDomDocument document; + if (!document.setContent(&directory_conf)) return; + + // verifie la racine + QDomElement root = document.documentElement(); + if (root.tagName() != "qet-directory") return; + + category_names.fromXml(root); + + // ferme le fichier + directory_conf.close(); +} + +/** + Supprime le contenu de la categorie en memoire +*/ +void FileElementsCategory::deleteContent() { + // suppression des elements + foreach(QString elmt_name, elements_.keys()) { + FileElementDefinition *elmt = elements_.take(elmt_name); + delete elmt; + } + + // suppression des categories + foreach(QString cat_name, categories_.keys()) { + FileElementsCategory *cat = categories_.take(cat_name); + delete cat; + } +} diff --git a/sources/fileelementscategory.h b/sources/fileelementscategory.h new file mode 100644 index 000000000..2b9def069 --- /dev/null +++ b/sources/fileelementscategory.h @@ -0,0 +1,83 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef FILE_ELEMENTS_CATEGORY_H +#define FILE_ELEMENTS_CATEGORY_H +#include +#include "elementscategory.h" +class FileElementsCollection; +class FileElementDefinition; +/** + Cette classe represente une categorie d'elements accessible via un systeme + de fichiers. +*/ +class FileElementsCategory : public ElementsCategory { + Q_OBJECT + + // constructeurs, destructeur + public: + FileElementsCategory(const QString & = QString(), FileElementsCategory * = 0, FileElementsCollection * = 0); + virtual ~FileElementsCategory(); + + private: + FileElementsCategory(const FileElementsCategory &); + + // methodes + public: + virtual QString pathName() const; + virtual QString virtualPath(); + + virtual QString filePath(); + virtual bool hasFilePath(); + virtual void setFilePath(const QString &); + + virtual QList categories(); + virtual ElementsCategory *category(const QString &); + virtual ElementsCategory *createCategory(const QString &); + + virtual QList elements(); + virtual ElementDefinition *element(const QString &); + virtual ElementDefinition *createElement(const QString &); + + virtual bool exists(); + virtual bool isReadable(); + virtual bool isWritable(); + + virtual void reload(); + virtual bool remove(); + virtual bool removeContent(); + virtual bool write(); + + private: + bool rmdir(const QString &) const; + void loadNames(); + void deleteContent(); + + // attributs + protected: + /// Collection parente, de type fichier + FileElementsCollection *file_parent_collection_; + /// Categorie parente, de type fichier + FileElementsCategory *file_parent_category_; + /// Sous-categories contenues dans cette categorie + QHash categories_; + /// Elements contenus dans cette categorie + QHash elements_; + /// Dossier representant cette categorie sur le systeme de fichiers + QDir cat_dir; +}; +#endif diff --git a/sources/fileelementscollection.cpp b/sources/fileelementscollection.cpp new file mode 100644 index 000000000..a0ab4ad6c --- /dev/null +++ b/sources/fileelementscollection.cpp @@ -0,0 +1,125 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#include "fileelementscollection.h" +#include "fileelementscategory.h" + +/** + Constructeur + @param path Chemin du dossier racine de la collection + @param parent QObject parent de la collection +*/ +FileElementsCollection::FileElementsCollection(const QString &path, ElementsCollectionItem *parent) : + ElementsCollection(parent), + coll_path(path) +{ + protocol_ = "unknown"; + project_ = 0; + root = 0; + reload(); +} + +/** + Destructeur +*/ +FileElementsCollection::~FileElementsCollection() { + deleteContent(); +} + +/** + @return la categorie racine de la collection +*/ +ElementsCategory *FileElementsCollection::rootCategory() { + return(root); +} + +/** + Recharge l'arborescence des categories et elements. +*/ +void FileElementsCollection::reload() { + // oublie l'arborescence precedente + deleteContent(); + + // le dossier doit exister et etre lisible + QDir coll_dir(coll_path); + if (!coll_dir.exists() || !coll_dir.isReadable()) return; + coll_path = coll_dir.canonicalPath(); + + root = new FileElementsCategory(coll_path, 0, this); +} + +/** + @return true si cette collection est representee quelque part sur le + systeme de fichiers. +*/ +bool FileElementsCollection::hasFilePath() { + return(!coll_path.isEmpty()); +} + +/** + @return le chemin du repertoire representant cette collection +*/ +QString FileElementsCollection::filePath() { + return(coll_path); +} + +/** + @param path Nouveau chemin de la collection + Cette methode ne recharge pas la collection +*/ +void FileElementsCollection::setFilePath(const QString &path) { + coll_path = path; +} + +/** + Supprime le contenu en memoire de cette collection +*/ +void FileElementsCollection::deleteContent() { + delete root; + root = 0; +} + +/** + @return ttrue si la categorie racine de la collection existe +*/ +bool FileElementsCollection::exists() { + return(root && root -> exists()); +} + +/** + @return true si la collection est accessible en lecture +*/ +bool FileElementsCollection::isReadable() { + // une collection est accessible en lecture si sa categorie racine l'est + return(root && root -> isReadable()); +} + +/** + @return true si la collection est accessible en ecriture +*/ +bool FileElementsCollection::isWritable() { + // une collection est accessible en ecriture si sa categorie racine l'est + return(root && root -> isWritable()); +} + +/** + Ne fait rien + @return toujours true +*/ +bool FileElementsCollection::write() { + return(true); +} diff --git a/sources/fileelementscollection.h b/sources/fileelementscollection.h new file mode 100644 index 000000000..0f110c705 --- /dev/null +++ b/sources/fileelementscollection.h @@ -0,0 +1,60 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef FILE_ELEMENTS_COLLECTION_H +#define FILE_ELEMENTS_COLLECTION_H +#include +#include "elementscollection.h" +class FileElementsCategory; +/** + Cette classe represente une collection d'elements accessible via un + systeme de fichier. Typiquement, il s'agit de la collection QET ou + de la collection utilisateur. +*/ +class FileElementsCollection : public ElementsCollection { + Q_OBJECT + + // constructeurs, destructeur + public: + FileElementsCollection(const QString &, ElementsCollectionItem *parent = 0); + virtual ~FileElementsCollection(); + + private: + FileElementsCollection(const FileElementsCollection &); + + // methodes + public: + virtual void reload(); + virtual ElementsCategory *rootCategory(); + + virtual bool hasFilePath(); + virtual QString filePath(); + virtual void setFilePath(const QString &); + virtual bool exists(); + virtual bool isReadable(); + virtual bool isWritable(); + virtual bool write(); + + private: + void deleteContent(); + + // attributs + private: + QString coll_path; + FileElementsCategory *root; +}; +#endif diff --git a/sources/fixedelement.cpp b/sources/fixedelement.cpp index 2d7e6e4ea..18a60ceaa 100644 --- a/sources/fixedelement.cpp +++ b/sources/fixedelement.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -31,13 +31,13 @@ FixedElement::~FixedElement() { /** @return Le nombre minimal de bornes que l'element peut avoir */ -int FixedElement::nbTerminalsMin() const { - return(nbTerminals()); +int FixedElement::minTerminalsCount() const { + return(terminalsCount()); } /** @return Le nombre maximal de bornes que l'element peut avoir */ -int FixedElement::nbTerminalsMax() const { - return(nbTerminals()); +int FixedElement::maxTerminalsCount() const { + return(terminalsCount()); } diff --git a/sources/fixedelement.h b/sources/fixedelement.h index 4c8617185..725e2149f 100644 --- a/sources/fixedelement.h +++ b/sources/fixedelement.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -23,6 +23,8 @@ */ class FixedElement : public Element { + Q_OBJECT + // constructeurs, destructeur public: FixedElement(QGraphicsItem * = 0, Diagram * = 0); @@ -30,11 +32,11 @@ class FixedElement : public Element { // methodes public: - int nbTerminalsMin() const; - int nbTerminalsMax() const; - virtual int nbTerminals() const = 0; + int minTerminalsCount() const; + int maxTerminalsCount() const; + virtual int terminalsCount() const = 0; virtual void paint(QPainter *, const QStyleOptionGraphicsItem *) = 0; virtual QString typeId() const = 0; - virtual QString nom() const = 0; + virtual QString name() const = 0; }; #endif diff --git a/sources/ghostelement.cpp b/sources/ghostelement.cpp new file mode 100644 index 000000000..7432e5857 --- /dev/null +++ b/sources/ghostelement.cpp @@ -0,0 +1,154 @@ +#include "ghostelement.h" +#include "qet.h" +#include "terminal.h" +#include "elementtextitem.h" + +/** + Constructeur + @param location Emplacement de la definition d'element a utiliser + @param qgi Le QGraphicsItem parent de cet element + @param d Le schema affichant cet element +*/ +GhostElement::GhostElement( + const ElementsLocation &location, + QGraphicsItem *qgi, + Diagram *d +) : + CustomElement(location, qgi, d) +{ + QString tooltip_string = QString( + tr("\311l\351ment manquant\240: %1") + ).arg(location_.toString()); + setToolTip(tooltip_string); +} + +/** + Destructeur +*/ +GhostElement::~GhostElement() { +} + + +/** + @param e L'element XML a analyser. + @param table_id_adr Reference vers la table de correspondance entre les IDs + du fichier XML et les adresses en memoire. Si l'import reussit, il faut y + ajouter les bons couples (id, adresse). + @return true si l'import a reussi, false sinon +*/ +bool GhostElement::fromXml(QDomElement &e, QHash &table_id_adr) { + // instancie les bornes decrites dans l'element XML + terminalsFromXml(e, table_id_adr); + + // instancie les champs de texte decrits dans l'element XML + foreach(QDomElement qde, QET::findInDomElement(e, "inputs", "input")) { + qde.setAttribute("size", 9); // arbitraire + if (ElementTextItem *new_input = CustomElement::parseInput(qde)) { + new_input -> fromXml(qde); + } + qde.removeAttribute("size"); + } + + /* + maintenant que l'element fantome connait toutes les bornes et tous les + champs de texte, on peut determiner une taille appropriee + */ + QRect final_bounding_rect = minimalBoundingRect().united(childrenBoundingRect()).toAlignedRect(); + setSize(final_bounding_rect.width(), final_bounding_rect.height()); + setHotspot(QPoint() - final_bounding_rect.topLeft()); + setInternalConnections(true); + + // on peut desormais confectionner le rendu de l'element + generateDrawing(); + + // position, selection et orientation + setPos(e.attribute("x").toDouble(), e.attribute("y").toDouble()); + setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); + bool conv_ok; + int read_ori = e.attribute("orientation").toInt(&conv_ok); + if (!conv_ok || read_ori < 0 || read_ori > 3) read_ori = ori.defaultOrientation(); + setOrientation((QET::Orientation)read_ori); + + return(true); +} + +/** + @return le bounding rect minimum, utilise si l'element fantome n'a ni champ + de texte ni borne. +*/ +QRectF GhostElement::minimalBoundingRect() const { + return( + QRectF( + QPointF(-10.0, -10.0), + QSizeF(20.0, 20.0) + ) + ); +} + +/** + Gere l'import des bornes + @param e L'element XML a analyser. + @param table_id_adr Reference vers la table de correspondance entre les IDs + du fichier XML et les adresses en memoire. Si l'import reussit, il faut y + ajouter les bons couples (id, adresse). + @return true si l'import a reussi, false sinon +*/ +bool GhostElement::terminalsFromXml(QDomElement &e, QHash &table_id_adr) { + // instancie les bornes decrites dans l'element XML + foreach(QDomElement qde, QET::findInDomElement(e, "terminals", "terminal")) { + if (!Terminal::valideXml(qde)) continue; + + // modifie certains attributs pour que l'analyse par la classe CustomElement reussisse + int previous_x_value = qde.attribute("x").toInt(); + int previous_y_value = qde.attribute("y").toInt(); + int previous_ori_value = qde.attribute("orientation").toInt(); + + qreal x_add = 0.0, y_add = 0.0; + if (previous_ori_value == QET::North) y_add = -Terminal::terminalSize; + else if (previous_ori_value == QET::East) x_add = Terminal::terminalSize; + else if (previous_ori_value == QET::South) y_add = Terminal::terminalSize; + else if (previous_ori_value == QET::West) x_add = -Terminal::terminalSize; + qde.setAttribute("x", previous_x_value + x_add); + qde.setAttribute("y", previous_y_value + y_add); + qde.setAttribute("orientation", QET::orientationToString(static_cast(previous_ori_value))); + + if (Terminal *new_terminal = CustomElement::parseTerminal(qde)) { + table_id_adr.insert(qde.attribute("id").toInt(), new_terminal); + } + + // restaure les attributs modifies + qde.setAttribute("x", previous_x_value); + qde.setAttribute("y", previous_y_value); + qde.setAttribute("orientation", previous_ori_value); + } + return(true); +} + +/** + Genere le rendu de l'element fantome : il s'agit d'un rectangle + representant grosso modo l'espace que devait prendre l'element initial. + En son centre est dessine un point d'interrogation. Une petite croix indique + le point de saisie de l'element. +*/ +void GhostElement::generateDrawing() { + QPainter qp; + qp.begin(&drawing); + + // style de dessin + QPen t; + t.setColor(Qt::black); + t.setWidthF(1.0); + t.setJoinStyle(Qt::BevelJoin); + qp.setPen(t); + + // une petite croix indique le point de saisie de l'element + qp.drawLine(QLineF(-1.0, 0.0, 1.0, 0.0)); + qp.drawLine(QLineF(0.0, -1.0, 0.0, 1.0)); + + // rectangle avec un point d'interrogation au centre + QRectF drawn_rect = boundingRect().adjusted(4.0, 4.0, -4.0, -4.0); + qp.drawRect(drawn_rect); + qp.drawText(drawn_rect, Qt::AlignHCenter | Qt::AlignVCenter, "?"); + + qp.end(); +} diff --git a/sources/ghostelement.h b/sources/ghostelement.h new file mode 100644 index 000000000..e1dcf7d07 --- /dev/null +++ b/sources/ghostelement.h @@ -0,0 +1,54 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef GHOST_ELEMENT_H +#define GHOST_ELEMENT_H +#include "customelement.h" +class Diagram; +class QGraphicsItem; +class ElementsLocation; +class Terminal; +/** + La classe GhostElement herite de la classe CustomElement. Un GhostElement + est destine a remplacer visuellement un CustomElement dont la definition + n'a pu etre trouvee. Ainsi, au lieu de ne pas charger un element, et donc + de perdre potentiellement : + * sa position, son orientation, ses textes, + * les conducteurs qui y sont lies, + on peut lui substituer un GhostElement. Celui-ci extrapolera depuis le + schema la position des bornes et des champs de texte. + Visuellement, il sera represente par un rectangle. +*/ +class GhostElement : public CustomElement { + + Q_OBJECT + + // constructeur, destructeur + public: + GhostElement(const ElementsLocation &, QGraphicsItem * = 0, Diagram * = 0); + virtual ~GhostElement(); + + // methodes + public: + bool fromXml(QDomElement &, QHash &); + + protected: + QRectF minimalBoundingRect() const; + bool terminalsFromXml(QDomElement &, QHash &); + void generateDrawing(); +}; +#endif diff --git a/sources/hotspoteditor.cpp b/sources/hotspoteditor.cpp index e614226de..d025405cb 100644 --- a/sources/hotspoteditor.cpp +++ b/sources/hotspoteditor.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -25,6 +25,13 @@ HotspotEditor::HotspotEditor(QWidget *parent) : QWidget(parent), parts_rect_enabled(false) { + informations_label_ = new QLabel( + tr( + "L'\351l\351ment doit \352tre assez grand pour contenir tout sa " + "repr\351sentation graphique." + ) + ); + sb_width = new QSpinBox(); sb_width -> setMinimum(1); sb_width -> setValue(3); @@ -91,6 +98,7 @@ HotspotEditor::HotspotEditor(QWidget *parent) : vlayout = new QVBoxLayout(this); vlayout -> setSpacing(0); + vlayout -> addWidget(informations_label_); vlayout -> addLayout(hlayout, 1); updateScene(); diff --git a/sources/hotspoteditor.h b/sources/hotspoteditor.h index 776944ff0..2fefbce37 100644 --- a/sources/hotspoteditor.h +++ b/sources/hotspoteditor.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -36,6 +36,7 @@ class HotspotEditor : public QWidget { // attributs private: + QLabel *informations_label_; QSpinBox *sb_width; QSpinBox *sb_height; QSpinBox *sb_hotspot_x; diff --git a/sources/insetproperties.cpp b/sources/insetproperties.cpp index 871b616de..5a976c9c6 100644 --- a/sources/insetproperties.cpp +++ b/sources/insetproperties.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -17,11 +17,19 @@ */ #include "insetproperties.h" -/// Constructeur -InsetProperties::InsetProperties() { +/** + Constructeur. Initialise un objet InsetProperties avec tous les champs + vides (date vide + useDate a UseDateValue). +*/ +InsetProperties::InsetProperties() : + date(), + useDate(UseDateValue) +{ } -/// Destructeur +/** + Destructeur +*/ InsetProperties::~InsetProperties() { } @@ -47,6 +55,57 @@ bool InsetProperties::operator!=(const InsetProperties &ip) { return(!(*this == ip)); } + +/** + Exporte le cartouche sous formes d'attributs XML ajoutes a l'element e. + @param e Element XML auquel seront ajoutes des attributs +*/ +void InsetProperties::toXml(QDomElement &e) const { + e.setAttribute("author", author); + e.setAttribute("title", title); + e.setAttribute("filename", filename); + e.setAttribute("folio", folio); + e.setAttribute("date", exportDate()); +} + +/** + Importe le cartouche a partir des attributs XML de l'element e + @param e Element XML dont les attributs seront lus +*/ +void InsetProperties::fromXml(QDomElement &e) { + if (e.hasAttribute("author")) author = e.attribute("author"); + if (e.hasAttribute("title")) title = e.attribute("title"); + if (e.hasAttribute("filename")) filename = e.attribute("filename"); + if (e.hasAttribute("folio")) folio = e.attribute("folio"); + if (e.hasAttribute("date")) setDateFromString(e.attribute("date")); +} + +/** + Exporte le cartouche dans une configuration. + @param settings Parametres a ecrire + @param prefix prefixe a ajouter devant les noms des parametres +*/ +void InsetProperties::toSettings(QSettings &settings, const QString &prefix) const { + settings.setValue(prefix + "title", title); + settings.setValue(prefix + "author", author); + settings.setValue(prefix + "filename", filename); + settings.setValue(prefix + "folio", folio); + settings.setValue(prefix + "date", exportDate()); +} + +/** + Importe le cartouche depuis une configuration. + @param settings Parametres a lire + @param prefix prefixe a ajouter devant les noms des parametres +*/ +void InsetProperties::fromSettings(QSettings &settings, const QString &prefix) { + title = settings.value(prefix + "title").toString(); + author = settings.value(prefix + "author").toString(); + filename = settings.value(prefix + "filename").toString(); + folio = settings.value(prefix + "folio", "%id/%total").toString(); + setDateFromString(settings.value(prefix + "date").toString()); +} + /** @return La date a utiliser */ @@ -57,3 +116,39 @@ QDate InsetProperties::finalDate() const { return(QDate::currentDate()); } } + +/** + @return une chaine de caracteres decrivant comment gerer la date dans le + cartouche : la chaine peut valoir : + * null pour ne pas afficher de date + * now pour afficher la date courante (a la creation du schema) + * une date au format yyyyMMdd pour utiliser une date fixe +*/ +QString InsetProperties::exportDate() const { + QString date_setting_value; + if (useDate == UseDateValue) { + if (date.isNull()) date_setting_value = "null"; + else date_setting_value = date.toString("yyyyMMdd"); + } else { + date_setting_value = "now"; + } + return(date_setting_value); +} + +/** + Charge les attributs date et useDate a partir d'une chaine de caracteres. + @param date_string Chaine de caracteres a analyser + @see exportDate +*/ +void InsetProperties::setDateFromString(const QString &date_string) { + if (date_string == "now") { + date = QDate::currentDate(); + useDate = CurrentDate; + } else if (date_string.isEmpty() || date_string == "null") { + date = QDate(); + useDate = UseDateValue; + } else { + date = QDate::fromString(date_string, "yyyyMMdd"); + useDate = UseDateValue; + } +} diff --git a/sources/insetproperties.h b/sources/insetproperties.h index 5e030edd0..5d4b5a62e 100644 --- a/sources/insetproperties.h +++ b/sources/insetproperties.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -17,8 +17,8 @@ */ #ifndef INSET_PROPERTIES_H #define INSET_PROPERTIES_H -#include -#include +#include +#include /** Cette classe est un conteneur pour les proprietes d'un cartouche de schema : titre, auteur, date, nom de fichier et folio @@ -35,6 +35,12 @@ class InsetProperties { bool operator==(const InsetProperties &); bool operator!=(const InsetProperties &); + + void toXml(QDomElement &) const; + void fromXml(QDomElement &); + void toSettings(QSettings &, const QString & = QString()) const; + void fromSettings(QSettings &, const QString & = QString()); + QDate finalDate() const ; // attributs @@ -44,5 +50,9 @@ class InsetProperties { QString filename; ///< Nom de fichier affiche par le cartouche QString folio; ///< Folio affiche par le cartouche DateManagement useDate; ///< Indique s'il faut utiliser ou non l'attribut date + + private: + QString exportDate() const; + void setDateFromString(const QString &); }; #endif diff --git a/sources/insetpropertieswidget.cpp b/sources/insetpropertieswidget.cpp index 07bf42faf..cdfcd4c14 100644 --- a/sources/insetpropertieswidget.cpp +++ b/sources/insetpropertieswidget.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -29,7 +29,7 @@ InsetPropertiesWidget::InsetPropertiesWidget(const InsetProperties &inset, bool QVBoxLayout *this_layout = new QVBoxLayout(this); this_layout -> setContentsMargins(0, 0, 0, 0); QGroupBox *inset_infos = new QGroupBox(tr("Informations du cartouche"), this); - inset_infos -> setMinimumSize(300, 260); + inset_infos -> setMinimumSize(300, 370); this_layout -> addWidget(inset_infos); inset_title = new QLineEdit(this); @@ -58,6 +58,15 @@ InsetPropertiesWidget::InsetPropertiesWidget(const InsetProperties &inset, bool inset_filename = new QLineEdit(this); inset_folio = new QLineEdit(this); + QLabel *folio_tip = new QLabel( + tr( + "Les variables suivantes sont utilisables dans le champ Folio :\n" + " - %id : num\351ro du sch\351ma courant dans le projet\n" + " - %total : nombre total de sch\351mas dans le projet" + ) + ); + folio_tip -> setWordWrap(true); + QGridLayout *layout_champs = new QGridLayout(inset_infos); layout_champs -> addWidget(new QLabel(tr("Titre : ")), 0, 0); @@ -70,6 +79,7 @@ InsetPropertiesWidget::InsetPropertiesWidget(const InsetProperties &inset, bool layout_champs -> addWidget(inset_filename, 4, 1); layout_champs -> addWidget(new QLabel(tr("Folio : ")), 5, 0); layout_champs -> addWidget(inset_folio, 5, 1); + layout_champs -> addWidget(folio_tip, 6, 1); inset_current_date -> setVisible(display_current_date = current); setInsetProperties(inset); diff --git a/sources/insetpropertieswidget.h b/sources/insetpropertieswidget.h index 8c5616236..53ed4c471 100644 --- a/sources/insetpropertieswidget.h +++ b/sources/insetpropertieswidget.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/integrationmoveelementshandler.cpp b/sources/integrationmoveelementshandler.cpp new file mode 100644 index 000000000..10f128363 --- /dev/null +++ b/sources/integrationmoveelementshandler.cpp @@ -0,0 +1,201 @@ +#include "integrationmoveelementshandler.h" +#include +#include "elementscategory.h" +#include "elementdefinition.h" +#include "qfilenameedit.h" + +/** + Constructeur + @param parent QWidget parent a utiliser pour l'affichage des dialogues lors + des interactions avec l'utilisateur +*/ +IntegrationMoveElementsHandler::IntegrationMoveElementsHandler(QWidget *parent) : + BasicMoveElementsHandler(parent), + parent_widget_(parent), + integ_dialog_(0) +{ + // actions par defaut : abort + setActionIfItemAlreadyExists(QET::Abort); + setActionIfItemIsNotReadable(QET::Abort); + setActionIfItemIsNotWritable(QET::Abort); + setActionIfItemTriggersAnError(QET::Abort); +} + +/** + Destructeur +*/ +IntegrationMoveElementsHandler::~IntegrationMoveElementsHandler() { +} + +/** + @param src Element source + @param dst Element cible / destination + @return l'action a effectuer si l'element cible existe deja +*/ +QET::Action IntegrationMoveElementsHandler::elementAlreadyExists(ElementDefinition *src, ElementDefinition *dst) { + // premiere etape : on verifie si src et dst ne sont pas identiques + if (src -> equals(*dst)) { + // les deux elements sont identiques - il est inutile d'ecraser l'ancien + return(QET::Ignore); + } + + // les deux elements sont differents - on demande a l'utilisateur ce qu'il + // prefere : ecrasement ou cohabitation + return(askUser(src, dst)); +} + +/** + @return le nom a utiliser pour le renommage si une methode de cet objet + a precedemment renvoye QET::Rename. +*/ +QString IntegrationMoveElementsHandler::nameForRenamingOperation() { + return(rename_); +} + +/** + @return la date courante au format yyyyMMddhhmmss +*/ +QString IntegrationMoveElementsHandler::dateString() const { + return(QDateTime::currentDateTime().toString("yyyyMMddhhmmss")); +} + +/** + @param element Une definition d'element + @return un nom pour dupliquer l'element passe en parametre. Ce nom est base + sur la date courante. +*/ +QString IntegrationMoveElementsHandler::newNameForElement(const ElementDefinition *element) { + QString orig_name = element -> pathName(); + if (orig_name.endsWith(".elmt")) orig_name.chop(5); + return(orig_name + "-" + dateString() + ".elmt"); +} + +/** + Demande a l'utilisateur s'il souhaite ecraser l'elemen deja existant, + renommer le nouveau ou bien annuler + @param src Element source + @param dst Element cible + @return la reponse de l'utilisateur +*/ +QET::Action IntegrationMoveElementsHandler::askUser(ElementDefinition */*src*/, ElementDefinition *dst) { + initDialog(); + int result = integ_dialog_ -> exec(); + if (result == QDialog::Accepted) { + if (use_existing_elmt_ -> isChecked()) { + return(QET::Ignore); + } else if (erase_element_ -> isChecked()) { + return(QET::Erase); + } else { + rename_ = newNameForElement(dst); + return(QET::Rename); + } + } else { + return(QET::Abort); + } +} + +/** + Initialise le dialogue +*/ +void IntegrationMoveElementsHandler::initDialog() { + if (integ_dialog_) return; + integ_dialog_ = new QDialog(parent_widget_); + integ_dialog_ -> setWindowTitle(tr("Int\351gration d'un \351l\351ment")); + + dialog_label_ = new QLabel( + QString( + tr( + "L'\351l\351ment a d\351j\340 \351t\351 " + "int\351gr\351 dans le projet. Toutefois, la version que vous " + "tentez de poser semble diff\351rente. Que souhaitez-vous " + "faire ?", + "dialog content - %1 is an element's path name" + ) + ) + ); + + use_existing_elmt_ = new QRadioButton( + QString( + tr( + "Utiliser l'\351l\351ment d\351j\340 int\351gr\351", + "dialog content" + ) + ) + ); + + integrate_new_element_ = new QRadioButton( + QString( + tr( + "Int\351grer l'\351l\351ment d\351pos\351", + "dialog content" + ) + ) + ); + radioButtonleftMargin(integrate_new_element_); + + erase_element_ = new QRadioButton( + QString( + tr( + "\311craser l'\351l\351ment d\351j\340 int\351gr\351", + "dialog content" + ) + ) + ); + radioButtonleftMargin(erase_element_); + + integrate_both_ = new QRadioButton( + QString( + tr( + "Faire cohabiter les deux \351l\351ments", + "dialog content" + ) + ) + ); + + button_group1_ = new QButtonGroup(this); + button_group1_ -> addButton(use_existing_elmt_); + button_group1_ -> addButton(integrate_new_element_); + button_group2_ = new QButtonGroup(this); + button_group2_ -> addButton(erase_element_); + button_group2_ -> addButton(integrate_both_); + + integrate_new_element_ -> setChecked(true); + integrate_both_ -> setChecked(true); + + buttons_ = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + dialog_glayout = new QGridLayout(); + dialog_glayout -> setColumnMinimumWidth(0, 20); + dialog_glayout -> addWidget(erase_element_, 0, 1); + dialog_glayout -> addWidget(integrate_both_, 1, 1); + + dialog_vlayout_ = new QVBoxLayout(integ_dialog_); + dialog_vlayout_ -> addWidget(dialog_label_); + dialog_vlayout_ -> addWidget(use_existing_elmt_); + dialog_vlayout_ -> addWidget(integrate_new_element_); + dialog_vlayout_ -> addLayout(dialog_glayout); + dialog_vlayout_ -> addWidget(buttons_); + + connect(use_existing_elmt_, SIGNAL(toggled(bool)), this, SLOT(correctRadioButtons())); + connect(integrate_new_element_, SIGNAL(toggled(bool)), this, SLOT(correctRadioButtons())); + connect(buttons_, SIGNAL(accepted()), integ_dialog_, SLOT(accept())); + connect(buttons_, SIGNAL(rejected()), integ_dialog_, SLOT(reject())); +} + +/** + S'asure que le dialogue reste coherent +*/ +void IntegrationMoveElementsHandler::correctRadioButtons() { + erase_element_ -> setEnabled(integrate_new_element_ -> isChecked()); + integrate_both_ -> setEnabled(integrate_new_element_ -> isChecked()); +} + +/** + @param button bouton radio + Augmente la marge gauche d'un bouton radio +*/ +void IntegrationMoveElementsHandler::radioButtonleftMargin(QRadioButton *button) { + int a, b, c, d; + button -> getContentsMargins(&a, &b, &c, &d); + button -> setContentsMargins(a + 15, b, c, d); +} diff --git a/sources/integrationmoveelementshandler.h b/sources/integrationmoveelementshandler.h new file mode 100644 index 000000000..0fe26c06b --- /dev/null +++ b/sources/integrationmoveelementshandler.h @@ -0,0 +1,73 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef INTEGRATION_MOVE_ELEMENTS_HANDLER_H +#define INTEGRATION_MOVE_ELEMENTS_HANDLER_H +#include "basicmoveelementshandler.h" +#include +/** + Cette classe implemente la classe strategie MoveElementsHandler. + Elle correspond a un BasiMoveElementsHandler configure pour repondre + QET::Abort a toutes les questions. + Elle redefinit seulement la methode elementAlreadyExists pour, dans le cadre + de l'integration d'un element dans un projet demander a l'utilisateur s'il souahite : + * ecraser l'element precedent, manifestement different + * conserver l'element precedent, en renommant le nouveau + * annuler l'integration de l'element dans le projet +*/ +class IntegrationMoveElementsHandler : public BasicMoveElementsHandler { + Q_OBJECT + + // constructeurs, destructeur + public: + IntegrationMoveElementsHandler(QWidget * = 0); + virtual ~IntegrationMoveElementsHandler(); + private: + IntegrationMoveElementsHandler(const IntegrationMoveElementsHandler &); + + // methodes + public: + virtual QET::Action elementAlreadyExists(ElementDefinition *, ElementDefinition *); + virtual QString nameForRenamingOperation(); + + private: + QString dateString() const; + QString newNameForElement(const ElementDefinition *); + QET::Action askUser(ElementDefinition *, ElementDefinition *); + void initDialog(); + void radioButtonleftMargin(QRadioButton *); + + private slots: + void correctRadioButtons(); + + // attributs + private: + QWidget *parent_widget_; ///< Widget a utiliser comme parent pour l'affichage des dialogues + QString rename_; ///< Nom a utiliser lors d'une operation de renommage + QDialog *integ_dialog_; ///< Dialogue en cas de conflit lors de l'integration d'un element + QLabel *dialog_label_; + QVBoxLayout *dialog_vlayout_; + QGridLayout *dialog_glayout; + QDialogButtonBox *buttons_; + QRadioButton *use_existing_elmt_; + QRadioButton *integrate_new_element_; + QRadioButton *erase_element_; + QRadioButton *integrate_both_; + QButtonGroup *button_group1_; + QButtonGroup *button_group2_; +}; +#endif diff --git a/sources/interactivemoveelementshandler.cpp b/sources/interactivemoveelementshandler.cpp new file mode 100644 index 000000000..1a1bc8a08 --- /dev/null +++ b/sources/interactivemoveelementshandler.cpp @@ -0,0 +1,371 @@ +#include "interactivemoveelementshandler.h" +#include +#include "elementscategory.h" +#include "elementdefinition.h" +#include "qfilenameedit.h" + +/** + Constructeur + @param parent QWidget parent a utiliser pour l'affichage des dialogues lors + des interactions avec l'utilisateur +*/ +InteractiveMoveElementsHandler::InteractiveMoveElementsHandler(QWidget *parent) : + BasicMoveElementsHandler(parent), + parent_widget_(parent), + rename_(""), + always_erase_(false), + always_skip_(false), + aborted_(false), + conflict_dialog_(0) +{ +} + +/** + Destructeur +*/ +InteractiveMoveElementsHandler::~InteractiveMoveElementsHandler() { +} + +/** + @param src Categorie source + @param dst Categorie cible / destination + @return l'action a effectuer si la categorie cible existe deja +*/ +QET::Action InteractiveMoveElementsHandler::categoryAlreadyExists(ElementsCategory *src, ElementsCategory *dst) { + // verifie si la reponse n'est pas systematique + if (aborted_) return(QET::Abort); + if (always_erase_) return(QET::Erase); + if (always_skip_) return(QET::Ignore); + + // a ce stade, l'action a effectuer pour gerer le conflit doit etre + // demandee a l'utilisateur via un dialogue + initConflictDialog(); + + QString src_location(src -> location().toString()); + QString dst_location(dst -> location().toString()); + + // prepare le dialogue + QString dialog_title(QString(tr("Copie de %1 vers %2", "dialog title")).arg(src_location).arg(dst_location)); + + QLabel *question_label = new QLabel( + QString( + tr( + "La cat\351gorie \253\240%1\240\273 (%2) existe d\351j\340. " + "Que souhaitez-vous faire ?", + "dialog content" + ) + ) + .arg(dst -> name()) + .arg(dst_location) + ); + question_label -> setWordWrap(true); + + setConflictDialogTitle(dialog_title); + setConflictDialogMainWidget(question_label); + + // execute le dialogue + conflict_dialog_ -> exec(); + + // enleve et detruit le widget principal + setConflictDialogMainWidget(0); + delete question_label; + + // renvoie la reponse obtenue via le dialogue + return(conflict_result_); +} + +/** + @param src Element source + @param dst Element cible / destination + @return l'action a effectuer si l'element cible existe deja +*/ +QET::Action InteractiveMoveElementsHandler::elementAlreadyExists(ElementDefinition *src, ElementDefinition *dst) { + // verifie si la reponse n'est pas systematique + if (aborted_) return(QET::Abort); + if (always_erase_) return(QET::Erase); + if (always_skip_) return(QET::Ignore); + + // a ce stade, l'action a effectuer pour gerer le conflit doit etre + // demandee a l'utilisateur via un dialogue + initConflictDialog(); + + QString src_location(src -> location().toString()); + QString dst_location(dst -> location().toString()); + + // prepare le dialogue + QString dialog_title(QString(tr("Copie de %1 vers %2", "dialog title")).arg(src_location).arg(dst_location)); + + QLabel *question_label = new QLabel( + QString( + tr( + "L'\351l\351ment \253\240%1\240\273 existe d\351j\340. " + "Que souhaitez-vous faire ?", + "dialog content" + ) + ) + .arg(dst_location) + ); + question_label -> setWordWrap(true); + + setConflictDialogTitle(dialog_title); + setConflictDialogMainWidget(question_label); + + // execute le dialogue + conflict_dialog_ -> exec(); + + // enleve et detruit le widget principal + setConflictDialogMainWidget(0); + delete question_label; + + if (conflict_result_ == QET::Rename) { + if (!rename_.endsWith(".elmt")) rename_ += ".elmt"; + } + + // renvoie la reponse obtenue via le dialogue + return(conflict_result_); +} + +/** + Cette methode permet de savoir comment agir lorsqu'une categorie n'est pas lisible + @param category La categorie qui n'est pas lisible + @return QET::Retry, QET::Ignore ou QET::Abort +*/ +QET::Action InteractiveMoveElementsHandler::categoryIsNotReadable(ElementsCategory *category) { + QString message = QString(tr("La cat\351gorie %1 n'est pas accessible en lecture.", "message box content")).arg(category -> location().toString()); + return(retryErrorMessage(message)); +} + +/** + Cette methode permet de savoir comment agir lorsqu'un element n'est pas lisible + @param element L'element qui n'est pas lisible + @return QET::Retry, QET::Ignore ou QET::Abort +*/ +QET::Action InteractiveMoveElementsHandler::elementIsNotReadable(ElementDefinition *element) { + QString message = QString(tr("L'\351l\351ment %1 n'est pas accessible en lecture.", "message box content")).arg(element -> location().toString()); + return(retryErrorMessage(message)); +} + +/** + Cette methode permet de savoir comment agir lorsqu'une categorie n'est pas accessible en ecriture + @param category La categorie qui n'est pas lisible + @return QET::Retry, QET::Ignore ou QET::Abort +*/ +QET::Action InteractiveMoveElementsHandler::categoryIsNotWritable(ElementsCategory *category) { + QString message = QString(tr("La cat\351gorie %1 n'est pas accessible en \351criture.", "message box content")).arg(category -> location().toString()); + return(retryErrorMessage(message)); +} + +/** + Cette methode permet de savoir comment agir lorsqu'un element n'est pas accessible en ecriture + @param element L'element qui n'est pas lisible + @return QET::Retry, QET::Ignore ou QET::Abort +*/ +QET::Action InteractiveMoveElementsHandler::elementIsNotWritable(ElementDefinition *element) { + QString message = QString(tr("L'\351l\351ment %1 n'est pas accessible en \351criture.", "message box content")).arg(element -> location().toString()); + return(retryErrorMessage(message)); +} + +/** + Affiche un message d'erreur relatif a une categorie + @param category La categorie concernee par l'erreur + @param message Le message d'erreur a afficher + @return toujours QET::Ignore +*/ +QET::Action InteractiveMoveElementsHandler::errorWithACategory(ElementsCategory *category, const QString &message) { + QString category_location = category -> location().toString(); + QString error_message = QString("Une erreur s'est produite avec la cat\351gorie %1\240: %2").arg(category_location).arg(message); + simpleErrorMessage(error_message); + return(QET::Ignore); +} + +/** + Affiche un message d'erreur relatif a un element + @param element L'element concerne par l'erreur + @param message Le message d'erreur a afficher + @return toujours QET::Ignore +*/ +QET::Action InteractiveMoveElementsHandler::errorWithAnElement(ElementDefinition *element, const QString &message) { + QString element_location = element -> location().toString(); + QString error_message = QString("Une erreur s'est produite avec l'\351l\351ment %1\240: %2").arg(element_location).arg(message); + simpleErrorMessage(error_message); + return(QET::Ignore); +} + +/** + @return le nom a utiliser pour le renommage si une methode de cet objet + a precedemment renvoye QET::Rename. +*/ +QString InteractiveMoveElementsHandler::nameForRenamingOperation() { + return(rename_); +} + +/** + Initialise le dialogue qui sera utilise pour les conflits + elements / categories. +*/ +void InteractiveMoveElementsHandler::initConflictDialog() { + // n'agit qu'une seule fois + if (conflict_dialog_) return; + + conflict_dialog_ = new QDialog(parent_widget_); + conflict_dialog_ -> setMaximumSize(600, 200); + + // initialisation du champ de texte + rename_label_ = new QLabel("Nouveau nom :"); + rename_textfield_ = new QFileNameEdit(); + connect( + rename_textfield_, + SIGNAL(textEdited(const QString &)), + this, + SLOT(conflictDialogFileNameFieldChanged()) + ); + + // initialisation des boutons + rename_button_ = new QPushButton(tr("Renommer")); + erase_button_ = new QPushButton(tr("\311craser")); + erase_all_button_ = new QPushButton(tr("\311craser tout")); + ignore_button_ = new QPushButton(tr("Ignorer")); + ignore_all_button_ = new QPushButton(tr("Ignorer tout")); + abort_button_ = new QPushButton(tr("Annuler")); + + conflict_buttons_ = new QDialogButtonBox(); + conflict_buttons_ -> addButton(rename_button_, QDialogButtonBox::ActionRole); + conflict_buttons_ -> addButton(erase_button_, QDialogButtonBox::AcceptRole); + conflict_buttons_ -> addButton(erase_all_button_, QDialogButtonBox::AcceptRole); + conflict_buttons_ -> addButton(ignore_button_, QDialogButtonBox::AcceptRole); + conflict_buttons_ -> addButton(ignore_all_button_, QDialogButtonBox::AcceptRole); + conflict_buttons_ -> addButton(abort_button_, QDialogButtonBox::AcceptRole); + + rename_button_ -> setEnabled(false); + connect( + conflict_buttons_, + SIGNAL(clicked(QAbstractButton *)), + this, + SLOT(conflictDialogButtonClicked(QAbstractButton *)) + ); + + // layout + conflict_layout1_ = new QHBoxLayout(); + conflict_layout1_ -> addWidget(rename_label_); + conflict_layout1_ -> addWidget(rename_textfield_); + + conflict_layout0_ = new QVBoxLayout(conflict_dialog_); + conflict_layout0_ -> insertLayout(1, conflict_layout1_); + conflict_layout0_ -> insertWidget(2, conflict_buttons_); +} + +/** + Slot appele lorsque l'utilisateur modifie le contenu du champ +*/ +void InteractiveMoveElementsHandler::conflictDialogFileNameFieldChanged() { + if (rename_textfield_ -> isValid()) { + /// @todo verifier que le nom n'est pas deja pris + rename_button_ -> setEnabled(true); + } else { + rename_button_ -> setEnabled(false); + } +} + +/** + Slot appele lorsque l'utilisateur presse un des boutons du dialogue de + conflit. + @param button Bouton presse par l'utilisateur +*/ +void InteractiveMoveElementsHandler::conflictDialogButtonClicked(QAbstractButton *button) { + conflict_dialog_ -> accept(); + // change la valeur de l'attribut + if (button == rename_button_) { + rename_ = rename_textfield_ -> text(); + conflict_result_= QET::Rename; + } else if (button == erase_button_) { + conflict_result_= QET::Erase; + } else if (button == erase_all_button_) { + always_erase_ = true; + conflict_result_= QET::Erase; + } else if (button == ignore_button_) { + conflict_result_= QET::Ignore; + } else if (button == ignore_all_button_) { + always_skip_ = true; + conflict_result_= QET::Ignore; + } else if (button == abort_button_) { + aborted_ = true; + conflict_result_= QET::Abort; + } +} + +/** + Change le titre du dialogue de conflit + @param new_title Nouveau titre pour le dialogue de conflit +*/ +void InteractiveMoveElementsHandler::setConflictDialogTitle(const QString &new_title) { + conflict_dialog_ -> setWindowTitle(new_title); +} + +/** + @return le titre du dialogue de conflit +*/ +QString InteractiveMoveElementsHandler::conflictDialogTitle() const { + return(conflict_dialog_ -> windowTitle()); +} + +/** + Change le widget affiche au centre du dialogue de conflit + @param widget Widget a inserer dans le dialogue de conflit + Si widget vaut 0, le widget central est retire. +*/ +void InteractiveMoveElementsHandler::setConflictDialogMainWidget(QWidget *widget) { + // gere l'enlevement du widget principal + if (!widget) { + if (conflict_layout0_ -> count() != 3) return; + conflict_layout0_ -> removeItem(conflict_layout0_ -> itemAt(0)); + } else { + conflict_layout0_ -> insertWidget(0, widget); + } +} + +/** + @return le widget insere dans le dialogue de conflit, ou 0 s'il n'y en a + aucun. +*/ +QWidget *InteractiveMoveElementsHandler::conflictDialogMainWidget() const { + if (conflict_layout0_ -> count() != 3) return(0); + return(conflict_layout0_ -> itemAt(0) -> widget()); +} + +/** + Affiche un message d'erreur en donnant la possibilite d'ignorer l'item en cours, + d'annuler tout le mouvement ou de le reessayer. + @param message Message d'erreur a afficher + @return L'action choisie par l'utilisateur +*/ +QET::Action InteractiveMoveElementsHandler::retryErrorMessage(const QString &message) const { + int todo = QMessageBox::critical( + parent_widget_, + tr("Erreur", "message box title"), + message, + QMessageBox::Abort | QMessageBox::Retry | QMessageBox::Ignore, + QMessageBox::Ignore + ); + + if (todo == QMessageBox::Abort) { + return(QET::Abort); + } else if (todo == QMessageBox::Retry) { + return(QET::Retry); + } else { + return(QET::Ignore); + } +} + +/** + Affiche un simple message d'erreur + @param message Message d'erreur a afficher +*/ +void InteractiveMoveElementsHandler::simpleErrorMessage(const QString &message) const { + QMessageBox::critical( + parent_widget_, + tr("Erreur", "message box title"), + message, + QMessageBox::Ok, + QMessageBox::Ok + ); +} diff --git a/sources/interactivemoveelementshandler.h b/sources/interactivemoveelementshandler.h new file mode 100644 index 000000000..a72909178 --- /dev/null +++ b/sources/interactivemoveelementshandler.h @@ -0,0 +1,96 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef INTERACTIVE_MOVE_ELEMENTS_HANDLER_H +#define INTERACTIVE_MOVE_ELEMENTS_HANDLER_H +#include "basicmoveelementshandler.h" +class QDialog; +class QDialogButtonBox; +class QAbstractButton; +class QPushButton; +class QFileNameEdit; +class QHBoxLayout; +class QVBoxLayout; +class QLabel; +/** + Cette classe implemente la classe strategie MoveElementsHandler. + Via une interface graphique, elle demande a l'utilisateur comment il faut + traiter tel ou tel probleme puis transmet la reponse via l'API de la classe + MoveElementsHandler. +*/ +class InteractiveMoveElementsHandler : public BasicMoveElementsHandler { + Q_OBJECT + + // constructeurs, destructeur + public: + InteractiveMoveElementsHandler(QWidget * = 0); + virtual ~InteractiveMoveElementsHandler(); + private: + InteractiveMoveElementsHandler(const InteractiveMoveElementsHandler &); + + // methodes + public: + virtual QET::Action categoryAlreadyExists(ElementsCategory *, ElementsCategory *); + virtual QET::Action elementAlreadyExists(ElementDefinition *, ElementDefinition *); + virtual QET::Action categoryIsNotReadable(ElementsCategory *); + virtual QET::Action elementIsNotReadable(ElementDefinition *); + virtual QET::Action categoryIsNotWritable(ElementsCategory *); + virtual QET::Action elementIsNotWritable(ElementDefinition *); + virtual QET::Action errorWithACategory(ElementsCategory *, const QString &); + virtual QET::Action errorWithAnElement(ElementDefinition *, const QString &); + virtual QString nameForRenamingOperation(); + + private slots: + void conflictDialogFileNameFieldChanged(); + void conflictDialogButtonClicked(QAbstractButton *); + + private: + void initConflictDialog(); + void setConflictDialogTitle(const QString &); + QString conflictDialogTitle() const; + void setConflictDialogMainWidget(QWidget *); + QWidget *conflictDialogMainWidget() const; + QET::Action retryErrorMessage(const QString &) const; + void simpleErrorMessage(const QString &) const; + + + // attributs + private: + QWidget *parent_widget_; ///< Widget a utiliser comme parent pour l'affichage des dialogues + QString rename_; ///< Nom a utiliser lors d'une operation de renommage + bool always_erase_; ///< Booleen indiquant qu'il faut toujours ecraser les cibles en conflit sans poser de question + bool always_skip_; ///< Booleen indiquant qu'il faut toujours ignorer les cibles en conflit sans poser de question + bool aborted_; /// Booleen indiquant que le mouvement a ete annule + + // attributs relatifs au dialogue affiche pour les elements et categories deja existants (= dialogue de conflit) + QET::Action conflict_result_; + QDialog *conflict_dialog_; + QVBoxLayout *conflict_layout0_; + QHBoxLayout *conflict_layout1_; + QLabel *rename_label_; + QFileNameEdit *rename_textfield_; + + /// Boutons pour le dialogue de conflit + QDialogButtonBox *conflict_buttons_; + QPushButton *rename_button_; + QPushButton *erase_button_; + QPushButton *erase_all_button_; + QPushButton *ignore_button_; + QPushButton *ignore_all_button_; + QPushButton *abort_button_; +}; +#endif diff --git a/sources/main.cpp b/sources/main.cpp index 5de1a8ee7..a490334bd 100644 --- a/sources/main.cpp +++ b/sources/main.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/moveelementsdescription.cpp b/sources/moveelementsdescription.cpp new file mode 100644 index 000000000..93067a99d --- /dev/null +++ b/sources/moveelementsdescription.cpp @@ -0,0 +1,163 @@ +#include "moveelementsdescription.h" + +/** + Constructeur - construit une description ne comprenant aucun nom internem, + aucune destination, aucun handler. Par defaut, la recursivite est activee. + @param parent QObject parent +*/ +MoveElementsDescription::MoveElementsDescription(QObject *parent) : + QObject(parent), + recursive_(true), + handler_(0), + destination_(0), + dest_internal_name_orig_(""), + dest_internal_name_final_(""), + created_item_(0), + src_deleted_(false), + abort_(false) +{ +} + +/** + Destructeur +*/ +MoveElementsDescription::~MoveElementsDescription() { +} + +/** + @return true si le mouvement decrit est recursif (c'est-a-dire que la copie + d'une categorie entrainera la copie de ses sous-categories et de ses + elements) +*/ +bool MoveElementsDescription::isRecursive() const { + return(recursive_); +} + +/** + @param r true pour activer la recursivite, false sinon + @see isRecursive() +*/ +void MoveElementsDescription::setRecursive(bool r) { + recursive_ = r; +} + +/** + @return le MoveElementsHandler utilise pour gerer les erreurs lors de la + realisation de ce mouvement. Si aucun handler n'est specifie, cette methode + retourne 0. + @see MoveElementsHandler +*/ +MoveElementsHandler *MoveElementsDescription::handler() const { + return(handler_); +} + +/** + @param handler Le MoveElementHandler a utiliser pour gerer les erreurs lors + de la realisation de ce mouvement. Indiquer 0 pour enlever le + MoveElementsHandler. +*/ +void MoveElementsDescription::setHandler(MoveElementsHandler *handler) { + handler_ = handler; +} + +/** + @return la categorie de destination qui accueillera l'element cree par le + mouvement. +*/ +ElementsCategory *MoveElementsDescription::destinationParentCategory() const { + return(destination_); +} + +/** + @param destination la categorie de destination qui accueillera l'element + cree par le mouvement +*/ +void MoveElementsDescription::setDestinationParentCategory(ElementsCategory *destination) { + destination_ = destination; +} + +/** + @return Le nom interne souhaite pour l'item a creer. + Typiquement, il s'agit du meme nom que l'item d'origine. Il faut toutefois + le specifier explicitement. +*/ +QString MoveElementsDescription::originalDestinationInternalName() const { + return(dest_internal_name_orig_); +} + +/** + @param name Le nom interne souhaite pour l'item a creer. + Typiquement, il s'agit du meme nom que l'item d'origine. Il faut toutefois + le specifier explicitement. +*/ +void MoveElementsDescription::setOriginalDestinationInternalName(const QString &name) { + dest_internal_name_orig_ = name; +} + +/** + @return Le nom interne finalement retenu pour creer l'item. + Si le nom interne est deja pris dans la categorie de destination, il est + courant de changer le nom interne de destination (cette decision revient + typiquement au MoveElementsHandler). +*/ +QString MoveElementsDescription::finalDestinationInternalName() const { + return(dest_internal_name_final_); +} + +/** + @param name Le nom interne finalement retenu pour creer l'item. + Si le nom interne est deja pris dans la categorie de destination, il est + courant de changer le nom interne de destination (cette decision revient + typiquement au MoveElementsHandler). +*/ +void MoveElementsDescription::setFinalDestinationInternalName(const QString &name) { + dest_internal_name_final_ = name; +} + +/** + @return l'item cree par le mouvement, ou 0 si celui-ci n'as pas encore ete + cree ou ne sera pas cree. +*/ +ElementsCollectionItem *MoveElementsDescription::createdItem() const { + return(created_item_); +} + +/** + @param item l'item cree par le mouvement. Indiquer 0 si celui-ci n'as pas + encore ete cree ou ne sera pas cree. +*/ +void MoveElementsDescription::setCreatedItem(ElementsCollectionItem *item) { + created_item_ = item; +} + +/** + @return true si, dans le cadre normal du mouvement, l'item source a ete + supprime (exemple : deplacement) avec succes. +*/ +bool MoveElementsDescription::sourceItemWasDeleted() const { + return(src_deleted_); +} + +/** + @param deleted Definit si oui ou non l'item source a ete supprime avec + succes, et ce dans le cadre normal du mouvement (exemple : deplacement). +*/ +void MoveElementsDescription::setSourceItemDeleted(bool deleted) { + src_deleted_ = deleted; +} + +/** + @return true si le mouvement, ainsi que les mouvements qui suivent, doivent + etre annules. +*/ +bool MoveElementsDescription::mustAbort() const { + return(abort_); +} + +/** + Definit ce mouvement ainsi que les mouvements qui suivent comme etant a + annuler. +*/ +void MoveElementsDescription::abort() { + abort_ = true; +} diff --git a/sources/moveelementsdescription.h b/sources/moveelementsdescription.h new file mode 100644 index 000000000..e3e389ce0 --- /dev/null +++ b/sources/moveelementsdescription.h @@ -0,0 +1,76 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef MOVE_ELEMENTS_DESCRIPTION_H +#define MOVE_ELEMENTS_DESCRIPTION_H +#include +class ElementsCollectionItem; +class ElementsCategory; +class MoveElementsHandler; +/** + Cette classe represente la description d'un mouvement d'elements. + Il peut s'agir d'un deplacement ou d'une copie. La source n'est pas + mentionnee +*/ +class MoveElementsDescription : public QObject { + Q_OBJECT + + // constructeurs, destructeur + public: + MoveElementsDescription(QObject * = 0); + virtual ~MoveElementsDescription(); + private: + MoveElementsDescription(const MoveElementsDescription &); + + // methodes + public: + bool isRecursive() const; + void setRecursive(bool); + + MoveElementsHandler *handler() const; + void setHandler(MoveElementsHandler *); + + ElementsCategory *destinationParentCategory() const; + void setDestinationParentCategory(ElementsCategory *); + + QString originalDestinationInternalName() const; + void setOriginalDestinationInternalName(const QString &); + + QString finalDestinationInternalName() const; + void setFinalDestinationInternalName(const QString &); + + ElementsCollectionItem *createdItem() const; + void setCreatedItem(ElementsCollectionItem *); + + bool sourceItemWasDeleted() const; + void setSourceItemDeleted(bool); + + bool mustAbort() const; + void abort(); + + // attributs + private: + bool recursive_; + MoveElementsHandler *handler_; + ElementsCategory *destination_; + QString dest_internal_name_orig_; + QString dest_internal_name_final_; + ElementsCollectionItem *created_item_; + bool src_deleted_; + bool abort_; +}; +#endif diff --git a/sources/moveelementshandler.h b/sources/moveelementshandler.h new file mode 100644 index 000000000..061b9698b --- /dev/null +++ b/sources/moveelementshandler.h @@ -0,0 +1,101 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef MOVE_ELEMENTS_HANDLER_H +#define MOVE_ELEMENTS_HANDLER_H +#include +#include "qet.h" +class ElementDefinition; +class ElementsCategory; +/** + Cette classe definit l'interface minimale pour implementer un objet capable + de prendre en main une operation de deplacement ou de copie d'elements. + Ce type d'objet est typiquement utilise dans les methodes move et copy des + classes ElementDefinition et ElementsCategory. Ces methodes font appel a cet + objet pour qu'il leur indique comment gerer les eventuels problemes + rencontres lors du deplacement / de la copie. + Exemple : lors de la recopie d'un element dans une categorie, il se peut que + cet element existe deja. Il est alors possible d'ecraser l'element cible ou + d'abandonner l'operation. Cette decision est a la charge d'une classe fille + de MoveElementsHandler. + Cet objet peut etre effectuer des interactions avec l'utilisateur ou non. + Cet aspect, ainsi que la politique de gestion des elements, est laisse aux + bons soins de l'implementation. + Il s'agit d'un pattern Strategie qui encapsule non pas l'algorithme de + deplacement / copie des categories / elements mais la gestion des erreurs + durant l'execution de cet algorithme. + @see ElementsCategory + @see ElementDefinition +*/ +class MoveElementsHandler : public QObject { + Q_OBJECT + // constructeurs, destructeur + public: + MoveElementsHandler(QObject * = 0) {}; + virtual ~MoveElementsHandler() {}; + private: + MoveElementsHandler(const MoveElementsHandler &); + + // methodes + public: + /** + @return l'action a effectuer si la categorie cible existe deja + */ + virtual QET::Action categoryAlreadyExists(ElementsCategory *src, ElementsCategory *dst) = 0; + /** + @return l'action a effectuer si l'element cible existe deja + */ + virtual QET::Action elementAlreadyExists(ElementDefinition *src, ElementDefinition *dst) = 0; + + /** + @return l'action a effectuer si la categorie existe deja + */ + virtual QET::Action categoryIsNotReadable(ElementsCategory *) = 0; + /** + @return l'action a effectuer si l'element existe deja + */ + virtual QET::Action elementIsNotReadable(ElementDefinition *) = 0; + + /** + @return l'action a effectuer si la categorie cible n'est pas accessible + en ecriture + */ + virtual QET::Action categoryIsNotWritable(ElementsCategory *) = 0; + /** + @return l'action a effectuer si l'element cible n'est pas accessible + en ecriture + */ + virtual QET::Action elementIsNotWritable(ElementDefinition *) = 0; + + /** + @return l'action a effectuer lorsque l'erreur decrite dans la QString + s'est produite avec la categorie indiquee + */ + virtual QET::Action errorWithACategory(ElementsCategory *, const QString &) = 0; + /** + @return l'action a effectuer lorsque l'erreur decrite dans la QString + s'est produite avec l'element indique + */ + virtual QET::Action errorWithAnElement(ElementDefinition *, const QString &) = 0; + + /** + @return le nom a utiliser pour le renommage si une methode de cet objet + a precedemment renvoye QET::Rename. + */ + virtual QString nameForRenamingOperation() = 0; +}; +#endif diff --git a/sources/nameslist.cpp b/sources/nameslist.cpp index a1eee9d7c..a7ed3be9b 100644 --- a/sources/nameslist.cpp +++ b/sources/nameslist.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/nameslist.h b/sources/nameslist.h index 4b0998511..bd8a35151 100644 --- a/sources/nameslist.h +++ b/sources/nameslist.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/nameslistwidget.cpp b/sources/nameslistwidget.cpp index 9496f0d49..2e8d2b2c0 100644 --- a/sources/nameslistwidget.cpp +++ b/sources/nameslistwidget.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -63,8 +63,8 @@ bool NamesListWidget::checkOneName() { if (!hash_names.count()) { QMessageBox::critical( this, - tr("Il doit y avoir au moins un nom."), - tr("Vous devez entrer au moins un nom.") + tr("Il doit y avoir au moins un nom.", "message box title"), + tr("Vous devez entrer au moins un nom.", "message box content") ); return(false); } @@ -141,6 +141,7 @@ void NamesListWidget::setReadOnly(bool ro) { if (!read_only) flags |= Qt::ItemIsEditable; tree_names -> topLevelItem(i) -> setFlags(flags); } + button_add_line -> setEnabled(!read_only); } /// @return true si la liste de noms est en lecture seule, false sinon diff --git a/sources/nameslistwidget.h b/sources/nameslistwidget.h index 35809ac9e..0d901bf48 100644 --- a/sources/nameslistwidget.h +++ b/sources/nameslistwidget.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/newelementwizard.cpp b/sources/newelementwizard.cpp index cb0abbe9f..09da97b20 100644 --- a/sources/newelementwizard.cpp +++ b/sources/newelementwizard.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -16,6 +16,7 @@ along with QElectroTech. If not, see . */ #include "newelementwizard.h" +#include "elementscategory.h" #include "elementscategorieswidget.h" #include "elementscategorieslist.h" #include "nameslistwidget.h" @@ -24,15 +25,21 @@ #include "element.h" #include "qetelementeditor.h" #include "qet.h" +#include "qetapp.h" +#include "elementscollectionitem.h" +#include "qfilenameedit.h" /** Constructeur @param parent QWidget parent de ce dialogue @param f flags pour le dialogue */ -NewElementWizard::NewElementWizard(QWidget *parent, Qt::WindowFlags f) : QWizard(parent, f) { +NewElementWizard::NewElementWizard(QWidget *parent, Qt::WindowFlags f) : + QWizard(parent, f), + chosen_category(0) +{ setPixmap(LogoPixmap, QPixmap(":/ico/qelectrotech.png").scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); - setWindowTitle(tr("Cr\351er un nouvel \351l\351ment : Assistant")); + setWindowTitle(tr("Cr\351er un nouvel \351l\351ment : Assistant", "window title")); setButtonText(QWizard::NextButton, tr("&Suivant >")); addPage(buildStep1()); addPage(buildStep2()); @@ -48,14 +55,42 @@ NewElementWizard::NewElementWizard(QWidget *parent, Qt::WindowFlags f) : QWizard NewElementWizard::~NewElementWizard() { } +/** + @return la categorie parente selectionnee, ou 0 si celle-ci n'a pas encore + ete choisie. +*/ +ElementsCategory *NewElementWizard::selectedCategory() const { + return(chosen_category); +} + +/** + @param category Categorie d'elements dans laquelle le nouvel element sera + place + @return true si ce choix est possible et a ete pris en compte, false sinon +*/ +bool NewElementWizard::preselectCategory(ElementsCategory *category) { + // verifie si la categorie est utilisable + if (!category || !category -> exists() || !category -> isWritable()) { + return(false); + } + + // selectionne la categorie ainsi demandee dans la liste + if (categories_list -> elementsCategoriesList().selectLocation(category -> location())) { + chosen_category = category; + return(true); + } + + return(false); +} + /** Met en place l'etape 1 : Categorie */ QWizardPage *NewElementWizard::buildStep1() { QWizardPage *page = new QWizardPage(); page -> setProperty("WizardState", Category); - page -> setTitle(tr("\311tape 1/5 : Cat\351gorie parente")); - page -> setSubTitle(tr("S\351lectionnez une cat\351gorie dans laquelle enregistrer le nouvel \351l\351ment.")); + page -> setTitle(tr("\311tape 1/5 : Cat\351gorie parente", "wizard page title")); + page -> setSubTitle(tr("S\351lectionnez une cat\351gorie dans laquelle enregistrer le nouvel \351l\351ment.", "wizard page subtitle")); QVBoxLayout *layout = new QVBoxLayout(); categories_list = new ElementsCategoriesWidget(); @@ -71,11 +106,11 @@ QWizardPage *NewElementWizard::buildStep1() { QWizardPage *NewElementWizard::buildStep2() { QWizardPage *page = new QWizardPage(); page -> setProperty("WizardState", Filename); - page -> setTitle(tr("\311tape 2/5 : Nom du fichier")); - page -> setSubTitle(tr("Indiquez le nom du fichier dans lequel enregistrer le nouvel \351l\351ment.")); + page -> setTitle(tr("\311tape 2/5 : Nom du fichier", "wizard page title")); + page -> setSubTitle(tr("Indiquez le nom du fichier dans lequel enregistrer le nouvel \351l\351ment.", "wizard page subtitle")); QVBoxLayout *layout = new QVBoxLayout(); - qle_filename = new QLineEdit(tr("nouvel_element")); + qle_filename = new QFileNameEdit(tr("nouvel_element")); qle_filename -> selectAll(); QLabel *explication2 = new QLabel(tr("Vous n'\352tes pas oblig\351 de pr\351ciser l'extension *.elmt. Elle sera ajout\351e automatiquement.")); explication2 -> setAlignment(Qt::AlignJustify | Qt::AlignVCenter); @@ -94,13 +129,13 @@ QWizardPage *NewElementWizard::buildStep2() { QWizardPage *NewElementWizard::buildStep3() { QWizardPage *page = new QWizardPage(); page -> setProperty("WizardState", Names); - page -> setTitle(tr("\311tape 3/5 : Noms de l'\351l\351ment")); - page -> setSubTitle(tr("Indiquez le ou les noms de l'\351l\351ment.")); + page -> setTitle(tr("\311tape 3/5 : Noms de l'\351l\351ment", "wizard page title")); + page -> setSubTitle(tr("Indiquez le ou les noms de l'\351l\351ment.", "wizard page subtitle")); QVBoxLayout *layout = new QVBoxLayout(); element_names = new NamesListWidget(); NamesList hash_name; - hash_name.addName(QLocale::system().name().left(2), tr("Nom du nouvel \351l\351ment")); + hash_name.addName(QLocale::system().name().left(2), tr("Nom du nouvel \351l\351ment", "default name when creating a new element")); element_names -> setNames(hash_name); layout -> addWidget(element_names); @@ -114,8 +149,8 @@ QWizardPage *NewElementWizard::buildStep3() { QWizardPage *NewElementWizard::buildStep4() { QWizardPage *page = new QWizardPage(); page -> setProperty("WizardState", Dimensions); - page -> setTitle(tr("\311tape 4/5 : Dimensions et point de saisie")); - page -> setSubTitle(tr("Saisissez les dimensions du nouvel \351l\351ment ainsi que la position du hotspot (point de saisie de l'\351l\351ment \340 la souris) en consid\351rant que l'\351l\351ment est dans son orientation par d\351faut.")); + page -> setTitle(tr("\311tape 4/5 : Dimensions et point de saisie", "wizard page title")); + page -> setSubTitle(tr("Saisissez les dimensions du nouvel \351l\351ment ainsi que la position du hotspot (point de saisie de l'\351l\351ment \340 la souris) en consid\351rant que l'\351l\351ment est dans son orientation par d\351faut.", "wizard page subtitle")); QVBoxLayout *layout = new QVBoxLayout(); hotspot_editor = new HotspotEditor(); @@ -132,8 +167,8 @@ QWizardPage *NewElementWizard::buildStep4() { QWizardPage *NewElementWizard::buildStep5() { QWizardPage *page = new QWizardPage(); page -> setProperty("WizardState", Orientations); - page -> setTitle(tr("\311tape 5/5 : Orientations")); - page -> setSubTitle(tr("Indiquez les orientations possibles pour le nouvel \351l\351ment.")); + page -> setTitle(tr("\311tape 5/5 : Orientations", "wizard page title")); + page -> setSubTitle(tr("Indiquez les orientations possibles pour le nouvel \351l\351ment.", "wizard page subtitle")); QVBoxLayout *layout = new QVBoxLayout(); orientation_set = new OrientationSetWidget(); @@ -163,12 +198,20 @@ bool NewElementWizard::validateCurrentPage() { */ bool NewElementWizard::validStep1() { // il doit y avoir une categorie selectionnee - bool step1_ok = categories_list -> elementsCategoriesList().selectedCategoryPath() != QString(); + bool step1_ok = false; + ElementsLocation selected_location = categories_list -> elementsCategoriesList().selectedLocation(); + if (ElementsCollectionItem *collection_item = QETApp::collectionItem(selected_location, false)) { + if (collection_item -> isCategory()) { + chosen_category = qobject_cast(collection_item); + step1_ok = chosen_category; + } + } + if (!step1_ok) { QMessageBox::critical( this, - tr("Erreur"), - tr("Vous devez s\351lectionner une cat\351gorie.") + tr("Erreur", "message box title"), + tr("Vous devez s\351lectionner une cat\351gorie.", "message box content") ); } return(step1_ok); @@ -179,15 +222,16 @@ bool NewElementWizard::validStep1() { @return true si l'etape est validee, false sinon */ bool NewElementWizard::validStep2() { - QString dir_path = categories_list -> elementsCategoriesList().selectedCategoryPath(); + // il doit y avoir une categorie selectionnee + if (!chosen_category) return(false); QString file_name = qle_filename -> text(); // un nom doit avoir ete entre if (file_name.isEmpty()) { QMessageBox::critical( this, - tr("Erreur"), - tr("Vous devez entrer un nom de fichier") + tr("Erreur", "message box title"), + tr("Vous devez entrer un nom de fichier", "message box content") ); return(false); } @@ -198,14 +242,14 @@ bool NewElementWizard::validStep2() { if (QET::containsForbiddenCharacters(file_name)) { QMessageBox::critical( this, - tr("Erreur"), - tr("Merci de ne pas utiliser les caract\350res suivants : \\ / : * ? \" < > |") + tr("Erreur", "message box title"), + tr("Merci de ne pas utiliser les caract\350res suivants : \\ / : * ? \" < > |", "message box content") ); return(false); } // le fichier existe peut etre deja - if (QFileInfo(dir_path + "/" + file_name).exists()) { + if (chosen_category -> element(file_name)) { QMessageBox::StandardButton answer = QMessageBox::question( this, "\311craser le fichier ?", @@ -216,7 +260,7 @@ bool NewElementWizard::validStep2() { return(answer == QMessageBox::Yes); } - chosen_file = dir_path + "/" + file_name; + chosen_file = file_name; return(true); } @@ -229,6 +273,9 @@ void NewElementWizard::createNewElement() { edit_new_element -> setHotspot(hotspot_editor -> hotspot()); edit_new_element -> setNames(element_names -> names()); edit_new_element -> setOrientations(orientation_set -> orientationSet()); - edit_new_element -> setFileName(chosen_file); + + ElementsLocation new_element_location = chosen_category -> location(); + new_element_location.addToPath(chosen_file); + edit_new_element -> setLocation(new_element_location); edit_new_element -> show(); } diff --git a/sources/newelementwizard.h b/sources/newelementwizard.h index 2e99bbb52..31c006df0 100644 --- a/sources/newelementwizard.h +++ b/sources/newelementwizard.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -19,9 +19,11 @@ #define NEW_ELEMENT_WIZARD_H #include class ElementsCategoriesWidget; +class ElementsCategory; class NamesListWidget; class OrientationSetWidget; class HotspotEditor; +class QFileNameEdit; /** Cette classe represente un dialogue qui permet a l'utilisateur de specifier les premiers parametres de l'element qu'il va construire. @@ -44,16 +46,22 @@ class NewElementWizard : public QWizard { private: NewElementWizard(const NewElementWizard &); + // methodes + public: + ElementsCategory *selectedCategory() const; + bool preselectCategory(ElementsCategory *); + // attributs private: enum WizardState { Category, Filename, Names, Dimensions, Orientations }; - ElementsCategoriesWidget* categories_list; - QLineEdit *qle_filename; + ElementsCategoriesWidget *categories_list; + QFileNameEdit *qle_filename; NamesListWidget *element_names; OrientationSetWidget *orientation_set; HotspotEditor *hotspot_editor; WizardState current_state; QString chosen_file; + ElementsCategory *chosen_category; // methodes private: diff --git a/sources/orientationset.cpp b/sources/orientationset.cpp index 7550bed12..5584aee9b 100644 --- a/sources/orientationset.cpp +++ b/sources/orientationset.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/orientationset.h b/sources/orientationset.h index 09f7ad378..b78c77ca7 100644 --- a/sources/orientationset.h +++ b/sources/orientationset.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/orientationsetwidget.cpp b/sources/orientationsetwidget.cpp index 3daa4da6c..c42fbc6db 100644 --- a/sources/orientationsetwidget.cpp +++ b/sources/orientationsetwidget.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/orientationsetwidget.h b/sources/orientationsetwidget.h index fdd58a49e..a85720c50 100644 --- a/sources/orientationsetwidget.h +++ b/sources/orientationsetwidget.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/projectview.cpp b/sources/projectview.cpp new file mode 100644 index 000000000..041933de0 --- /dev/null +++ b/sources/projectview.cpp @@ -0,0 +1,793 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#include "projectview.h" +#include "qetproject.h" +#include "diagramview.h" +#include "diagram.h" +#include "diagramprintdialog.h" +#include "exportdialog.h" +#include "qetapp.h" +#include "qettabwidget.h" +#include "qetelementeditor.h" +#include "interactivemoveelementshandler.h" +#include "borderpropertieswidget.h" +#include "insetpropertieswidget.h" +#include "conductorpropertieswidget.h" + +/** + Constructeur + @param project projet a visualiser + @param parent Widget parent +*/ +ProjectView::ProjectView(QETProject *project, QWidget *parent) : + QWidget(parent), + project_(0) +{ + setObjectName("ProjectView"); + setWindowIcon(QIcon(":/ico/project.png")); + + // construit le widget "fallback" + fallback_widget_ = new QWidget(); + QVBoxLayout *fallback_widget_layout_ = new QVBoxLayout(fallback_widget_); + QLabel *label_widget = new QLabel(tr("Ce projet ne contient aucun sch\351ma")); + label_widget -> setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); + fallback_widget_layout_ -> addWidget(label_widget); + + tabs_ = new QETTabWidget(); + tabs_ -> setTabsMovable(true); + connect(tabs_, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int))); + connect(tabs_, SIGNAL(tabDoubleClicked(int)), this, SLOT(tabDoubleClicked(int))); + connect(tabs_, SIGNAL(firstTabInserted()), this, SLOT(firstTabInserted())); + connect(tabs_, SIGNAL(lastTabRemoved()), this, SLOT(lastTabRemoved())); + connect(tabs_, SIGNAL(tabMoved(int, int)), this, SLOT(tabMoved(int, int))); + + layout_ = new QVBoxLayout(this); + layout_ -> setMargin(0); + layout_ -> setSpacing(0); + layout_ -> addWidget(fallback_widget_); + layout_ -> addWidget(tabs_); + + fallback_widget_ -> setVisible(false); + tabs_ -> setVisible(false); + + setProject(project); +} + +/** + Destructeur + Supprime les DiagramView embarquees +*/ +ProjectView::~ProjectView() { + // qDebug() << "Suppression du ProjectView" << ((void *)this); + foreach(int id, diagram_ids_.keys()) { + DiagramView *diagram_view = diagram_ids_.take(id); + delete diagram_view; + } +} + +/** + @return le projet actuellement visualise par le ProjectView +*/ +QETProject *ProjectView::project() { + return(project_); +} + +/** + Definit le projet visualise par le ProjectView. Ne fait rien si le projet a + deja ete defini. + @param project projet a visualiser +*/ +void ProjectView::setProject(QETProject *project) { + if (!project_) { + project_ = project; + connect(project_, SIGNAL(projectTitleChanged(QETProject *, const QString &)), this, SLOT(updateWindowTitle())); + connect(project_, SIGNAL(readOnlyChanged (QETProject *, bool)), this, SLOT(updateWindowTitle())); + updateWindowTitle(); + loadDiagrams(); + } +} + +/** + @return la liste des schemas ouverts dans le projet +*/ +QList ProjectView::diagrams() const { + return(diagrams_); +} + +/** + @return le schema actuellement active +*/ +DiagramView *ProjectView::currentDiagram() const { + int current_tab_index = tabs_ -> currentIndex(); + return(diagram_ids_[current_tab_index]); +} + +/** + Gere la fermeture du schema. + @param event Le QCloseEvent decrivant l'evenement +*/ +void ProjectView::closeEvent(QCloseEvent *qce) { + // si la vue n'est pas liee a un projet, on ferme directement + if (!project_) { + qce -> accept(); + emit(projectClosed(this)); + return; + } + + // si le projet est comme neuf et n'est pas enregistre, on ferme directement + if (!project_ -> projectWasModified() && project_ -> filePath().isEmpty()) { + qce -> accept(); + emit(projectClosed(this)); + return; + } + + bool can_close_project = true; + if (!tryClosing()) { + // l'utilisateur a refuse la fermeture du projet - on arrete la + can_close_project = false; + } else { + // a ce stade, l'utilisateur a accepte la fermeture de tout le contenu du projet + if (!project_ -> filePath().isEmpty()) { + // si le projet a un chemin specifie, on l'enregistre et on le ferme + can_close_project = project_ -> write(); + } else { + // l'utilisateur n'enregistre pas son projet + can_close_project = true; + } + } + + if (can_close_project) { + qce -> accept(); + emit(projectClosed(this)); + } else { + qce -> ignore(); + } +} + +/** + Cette methode essaye de fermer successivement les editeurs d'element puis + les schemas du projet. L'utilisateur peut refuser de fermer un schema ou un + editeur. + @return true si tout a pu etre ferme, false sinon + @see tryClosingElementEditors() + @see tryClosingDiagrams() +*/ +bool ProjectView::tryClosing() { + if (!tryClosingElementEditors()) { + return(false); + } + + if (!tryClosingDiagrams()) { + return(false); + } + + // a ce stade, l'utilisateur a accepte de fermer tous les editeurs + // d'elements et tous les schemas + // on regarde s'il reste du contenu dans le projet + if (project_ -> projectWasModified() && project_ -> filePath().isEmpty()) { + // si oui, on propose a l'utilisateur d'enregistrer le projet + QMessageBox::StandardButton answer = QMessageBox::question( + this, + tr("Enregistrer le projet en cours ?", "message box title"), + QString(tr("Voulez-vous enregistrer le projet ?", "message box content")), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, + QMessageBox::Cancel + ); + if (answer == QMessageBox::Cancel) { + return(false); + } else if (answer == QMessageBox::Yes) { + return(save()); + } + } + + return(true); +} + +/** + Un projet comporte des elements integres. Cette methode ferme les editeurs + d'elements associes a ce projet. L'utilisateur peut refuser la fermeture + d'un editeur d'element. + @return true si tous les editeurs d'element ont pu etre fermes, false sinon +*/ +bool ProjectView::tryClosingElementEditors() { + if (!project_) return(true); + /* + La QETApp permet d'acceder rapidement aux editeurs d'element + editant un element du projet. + */ + QList editors = QETApp::elementEditors(project_); + foreach(QETElementEditor *editor, editors) { + if (!editor -> close()) return(false); + } + return(true); +} + +/** + Un projet comporte 0 a n schemas. + Cette methode parcourt les schemas et demande a l'utilisateur s'il veut + enregistrer les schemas modifies afin de les fermer. L'utilisateur peut + refuser la fermeture d'un schema. + Si un schema a ete ajoute sans jamais etre modifie, cette methode demande a + l'utilisateur s'il souhaite l'enlever. + @return true si tous les schemas peuvent etre fermes, false sinon +*/ +bool ProjectView::tryClosingDiagrams() { + if (!project_) return(true); + + foreach(DiagramView *diagram_view, diagrams()) { + if (!diagram_view -> diagram() -> undoStack().isClean()) { + // ce schema a ete modifie - on demande a l'utilisateur s'il veut l'enregistrer + showDiagram(diagram_view -> diagram()); + QMessageBox::StandardButton answer = QMessageBox::question( + this, + tr("Enregistrer le sch\351ma en cours ?", "message box title"), + QString(tr("Voulez-vous enregistrer le sch\351ma %1 ?", "message box content - %1 is a diagram title")).arg(diagram_view -> windowTitle()), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, + QMessageBox::Cancel + ); + if (answer == QMessageBox::Cancel) { + return(false); + } else if (answer == QMessageBox::Yes) { + if (!save()) { + return(false); + } + } + } else if (!diagram_view -> diagram() -> wasWritten()) { + // ce schema a ete ajoute mais pas modifie - on demande a l'utilisateur s'il veut le conserver + showDiagram(diagram_view -> diagram()); + QMessageBox::StandardButton answer = QMessageBox::question( + this, + tr("Enregistrer le nouveau sch\351ma ?", "message box title"), + tr("Ce sch\351ma a \351t\351 ajout\351 mais n'a \351t\351 ni modifi\351 ni enregistr\351. Voulez-vous le conserver ?", "message box content"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, + QMessageBox::Cancel + ); + if (answer == QMessageBox::Cancel) { + return(false); + } else if (answer == QMessageBox::Yes) { + if (!save()) { + return(false); + } + } else if (answer == QMessageBox::No) { + removeDiagram(diagram_view); + } + } + } + return(true); +} + +/** + Ajoute un nouveau schema au ProjectView +*/ +void ProjectView::addNewDiagram() { + if (project_ -> isReadOnly()) return; + + Diagram *new_diagram = project_ -> addNewDiagram(); + DiagramView *new_diagram_view = new DiagramView(new_diagram); + addDiagram(new_diagram_view); + showDiagram(new_diagram_view); +} + +/** + Ajoute un schema au ProjectView + @param diagram Schema a ajouter +*/ +void ProjectView::addDiagram(DiagramView *diagram) { + if (!diagram) return; + + // verifie que le schema n'est pas deja present dans le projet + if (diagram_ids_.values().contains(diagram)) return; + + // ajoute un nouvel onglet pour le nouveau schema + tabs_ -> addTab(diagram, QIcon(":/ico/diagram.png"), diagram -> title()); + diagrams_ << diagram; + rebuildDiagramsMap(); + connect(diagram, SIGNAL(titleChanged(DiagramView *, const QString &)), this, SLOT(updateTabTitle(DiagramView *, const QString &))); + + // signale l'ajout du schema + emit(diagramAdded(diagram)); +} + +/** + Enleve un schema du ProjectView + @param diagram_view Schema a enlever +*/ +void ProjectView::removeDiagram(DiagramView *diagram_view) { + if (!diagram_view) return; + if (project_ -> isReadOnly()) return; + + // verifie que le schema est bien present dans le projet + if (!diagram_ids_.values().contains(diagram_view)) return; + + // demande confirmation a l'utilisateur + if ( + diagram_view -> diagram() -> wasWritten() ||\ + !diagram_view -> diagram() -> undoStack().isClean() + ) { + int answer = QMessageBox::question( + this, + tr("Supprimer le sch\351ma ?", "message box title"), + tr("\312tes-vous s\373r de vouloir supprimer ce sch\351ma du projet ? Ce changement est irr\351versible.", "message box content"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, + QMessageBox::No + ); + if (answer != QMessageBox::Yes) { + return; + } + } + + // notifie le reste du monde que le DiagramView va disparaitre + emit(diagramAboutToBeRemoved(diagram_view)); + + // enleve le DiagramView des onglets + int diagram_tab_id = diagram_ids_.key(diagram_view); + tabs_ -> removeTab(diagram_tab_id); + diagrams_.removeAll(diagram_view); + rebuildDiagramsMap(); + + // supprime le DiagramView, puis le Diagram + project_ -> removeDiagram(diagram_view -> diagram()); + delete diagram_view; + + // signale le retrait du schema + emit(diagramRemoved(diagram_view)); + + // rend definitif le retrait du schema + project_ -> write(); +} + +/** + Enleve un schema du ProjectView + @param diagram_view Schema a enlever +*/ +void ProjectView::removeDiagram(Diagram *diagram) { + if (!diagram) return; + + if (DiagramView *diagram_view = findDiagram(diagram)) { + removeDiagram(diagram_view); + } +} + +/** + Active l'onglet adequat pour afficher le schema passe en parametre + @param diagram Schema a afficher +*/ +void ProjectView::showDiagram(DiagramView *diagram) { + if (!diagram) return; + tabs_ -> setCurrentWidget(diagram); +} + +/** + Active l'onglet adequat pour afficher le schema passe en parametre + @param diagram Schema a afficher +*/ +void ProjectView::showDiagram(Diagram *diagram) { + if (!diagram) return; + if (DiagramView *diagram_view = findDiagram(diagram)) { + tabs_ -> setCurrentWidget(diagram_view); + } +} + +/** + Affiche un dialogue permettant a l'utilisateur d'editer les proprietes du + projet. +*/ +void ProjectView::editProjectProperties() { + if (!project_) return; + + // dialogue d'edition des proprietes du projet + QDialog properties_dialog(parentWidget()); + properties_dialog.setMinimumWidth(786); + properties_dialog.setMinimumHeight(585); + properties_dialog.setWindowTitle(tr("Propri\351t\351s du projet", "window title")); + + // titre du projet + QLabel *title_label = new QLabel(tr("Titre du projet :")); + QLineEdit *title_field = new QLineEdit(project_ -> title()); + + // proprietes des nouveaux schemas + QLabel *new_diagrams_prop = new QLabel(tr("Propri\351t\351s \340 utiliser lors de l'ajout d'un nouveau sch\351ma au projet :")); + + // dimensions par defaut d'un schema + BorderPropertiesWidget *bpw = new BorderPropertiesWidget(project_ -> defaultBorderProperties()); + + // proprietes par defaut d'un cartouche + InsetPropertiesWidget *ipw = new InsetPropertiesWidget(project_ -> defaultInsetProperties(), true); + + // proprietes par defaut des conducteurs + ConductorPropertiesWidget *cpw = new ConductorPropertiesWidget(project_ -> defaultConductorProperties()); + cpw -> setContentsMargins(0, 0, 0, 0); + + // boutons pour valider le dialogue + QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + connect(buttons, SIGNAL(accepted()), &properties_dialog, SLOT(accept())); + connect(buttons, SIGNAL(rejected()), &properties_dialog, SLOT(reject())); + + // agencement avec deux layouts : un vertical, un horizontal + QHBoxLayout *horiz_layout = new QHBoxLayout(); + horiz_layout -> addWidget(title_label); + horiz_layout -> addWidget(title_field); + + QVBoxLayout *vlayout1 = new QVBoxLayout(); + + vlayout1 -> addWidget(new_diagrams_prop); + + QHBoxLayout *hlayout1 = new QHBoxLayout(); + QVBoxLayout *vlayout2 = new QVBoxLayout(); + + vlayout2 -> addWidget(bpw); + vlayout2 -> addWidget(ipw); + vlayout2 -> setSpacing(5); + hlayout1 -> addLayout(vlayout2); + hlayout1 -> addWidget(cpw); + vlayout1 -> addLayout(hlayout1); + vlayout1 -> addStretch(1); + hlayout1 -> setAlignment(cpw, Qt::AlignTop); + + QVBoxLayout *vert_layout = new QVBoxLayout(&properties_dialog); + vert_layout -> addLayout(horiz_layout); + vert_layout -> addLayout(vlayout1); + vert_layout -> addStretch(); + vert_layout -> addWidget(buttons); + + // si le dialogue est accepte + if (properties_dialog.exec() == QDialog::Accepted && !project_ -> isReadOnly()) { + project_ -> setTitle(title_field -> text()); + project_ -> setDefaultBorderProperties(bpw -> borderProperties()); + project_ -> setDefaultInsetProperties(ipw -> insetProperties()); + project_ -> setDefaultConductorProperties(cpw -> conductorProperties()); + } +} + +/** + Edite les proprietes du schema courant +*/ +void ProjectView::editCurrentDiagramProperties() { + editDiagramProperties(currentDiagram()); +} + +/** + Edite les proprietes du schema diagram_view +*/ +void ProjectView::editDiagramProperties(DiagramView *diagram_view) { + if (!diagram_view) return; + showDiagram(diagram_view); + diagram_view -> editDiagramProperties(); +} + +/** + Edite les proprietes du schema diagram +*/ +void ProjectView::editDiagramProperties(Diagram *diagram) { + editDiagramProperties(findDiagram(diagram)); +} + +/** + Ce slot demarre un dialogue permettant a l'utilisateur de parametrer et de + lancer l'impression de toute ou partie du projet. +*/ +void ProjectView::printProject() { + if (!project_) return; + + // transforme le titre du projet en nom utilisable pour le document + QString doc_name; + if (!(project_ -> title().isEmpty())) { + doc_name = project_ -> title(); + } else if (!project_ -> filePath().isEmpty()) { + doc_name = QFileInfo(project_ -> filePath()).baseName(); + } + doc_name = QET::stringToFileName(doc_name); + if (doc_name.isEmpty()) { + doc_name = tr("projet", "string used to generate a filename"); + } + + // recupere le dossier contenant le fichier courant + QString dir_path = project_ -> filePath(); + if (dir_path.isEmpty()) { + dir_path = QDir::homePath(); + } else { + dir_path = QFileInfo(dir_path).absolutePath(); + } + + // determine un chemin pour le pdf / ps + QString file_name = QDir::toNativeSeparators(dir_path + "/" + doc_name); + + DiagramPrintDialog print_dialog(project_, this); + print_dialog.setDocName(doc_name); + print_dialog.setFileName(file_name); + print_dialog.exec(); +} + +/** + Exporte le schema. +*/ +void ProjectView::exportProject() { + if (!project_) return; + + ExportDialog ed(project_, parentWidget()); + ed.exec(); +} + +/** + Enregistre le projet dans un fichier. + @see filePath() + @see setFilePath() + @return true si l'enregistrement a reussi, false sinon +*/ +bool ProjectView::save() { + if (project_) { + if (project_ -> filePath().isEmpty()) { + // le projet n'est pas encore enregistre dans un fichier + // save() equivaut alors a saveAs() + return(saveAs()); + } + // on enregistre le schema en cours + if (DiagramView *current_view = currentDiagram()) { + if (Diagram *diagram = current_view -> diagram()) { + diagram -> write(); + updateWindowTitle(); + return(true); + } + } else { + // s'il n'y a pas de schema, on appelle directement la methode write() + project_ -> write(); + } + return(true); + } + return(false); +} + +/** + Enregistre tous les schemas du projet. + @see filePath() + @see setFilePath() + @return true si l'enregistrement a reussi, false sinon +*/ +bool ProjectView::saveAll() { + if (project_) { + // on fait deja un appel a save + if (!save()) { + return(false); + } else { + // a ce stade, on suppose que l'on a un fichier, et que l'ecriture du schema en cours a reussi + // on enregistre les schemas + foreach(Diagram *diagram, project_ -> diagrams()) { + diagram -> write(); + } + updateWindowTitle(); + return(true); + } + } + return(false); +} + +/** + Propose a l'utilisateur de nettoyer le projet ; cela inclut la possibilite : + * de supprimer les elements inutilises dans le projet + * de supprimer les categories vides + @return le nombre de traitements effectues (0 si rien n'a ete fait, 1 ou + 2 sinon) +*/ +int ProjectView::cleanProject() { + if (!project_) return(0); + + // s'assure que le schema n'est pas en lecture seule + if (project_ -> isReadOnly()) { + QMessageBox::critical( + this, + tr("Projet en lecture seule", "message box title"), + tr("Ce projet est en lecture seule. Il n'est donc pas possible de le nettoyer.", "message box content") + ); + return(0); + } + + // construit un petit dialogue pour parametrer le nettoyage + QCheckBox *clean_elements = new QCheckBox(tr("Supprimer les \351l\351ments inutilis\351s dans le projet")); + QCheckBox *clean_categories = new QCheckBox(tr("Supprimer les cat\351gories vides")); + QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + clean_elements -> setChecked(true); + clean_categories -> setChecked(true); + + QDialog clean_dialog; + clean_dialog.setWindowTitle(tr("Nettoyer le projet", "window title")); + QVBoxLayout *clean_dialog_layout = new QVBoxLayout(); + clean_dialog_layout -> addWidget(clean_elements); + clean_dialog_layout -> addWidget(clean_categories); + clean_dialog_layout -> addWidget(buttons); + clean_dialog.setLayout(clean_dialog_layout); + + connect(buttons, SIGNAL(accepted()), &clean_dialog, SLOT(accept())); + connect(buttons, SIGNAL(rejected()), &clean_dialog, SLOT(reject())); + + int clean_count = 0; + if (clean_dialog.exec() == QDialog::Accepted) { + if (clean_elements -> isChecked()) { + InteractiveMoveElementsHandler *handler = new InteractiveMoveElementsHandler(this); + project_ -> cleanUnusedElements(handler); + delete handler; + ++ clean_count; + } + if (clean_categories -> isChecked()) { + InteractiveMoveElementsHandler *handler = new InteractiveMoveElementsHandler(this); + project_ -> cleanEmptyCategories(handler); + delete handler; + ++ clean_count; + } + } + return(clean_count); +} + +/** + Demande un nom de fichier a l'utilisateur pour enregistrer le projet + Si aucun nom n'est entre, elle renvoie faux. + Si le nom ne se termine pas par l'extension .qet, celle-ci est ajoutee. + Si l'enregistrement reussit, le nom du fichier est conserve et la fonction renvoie true. + Sinon, faux est renvoye. + @return true si l'enregistrement a reussi, false sinon +*/ +bool ProjectView::saveAs() { + // demande un nom de fichier a l'utilisateur pour enregistrer le projet + QString filepath = QFileDialog::getSaveFileName( + this, + tr("Enregistrer sous", "dialog title"), + (project_ -> filePath().isEmpty() ? QDir::homePath() : QDir(project_ -> filePath())).absolutePath(), + tr("Sch\351ma QElectroTech (*.qet)", "filetypes allowed when saving a diagram file") + ); + + // si aucun nom n'est entre, renvoie faux. + if (filepath.isEmpty()) return(false); + + // si le nom ne se termine pas par l'extension .qet, celle-ci est ajoutee + if (!filepath.endsWith(".qet", Qt::CaseInsensitive)) filepath += ".qet"; + + // le fichier est assigne au projet + project_ -> setFilePath(filepath); + + return(save()); +} + +/** + Charge les schemas du projet +*/ +void ProjectView::loadDiagrams() { + if (!project_) return; + + setDisplayFallbackWidget(project_ -> diagrams().isEmpty()); + + foreach(Diagram *diagram, project_ -> diagrams()) { + DiagramView *sv = new DiagramView(diagram); + addDiagram(sv); + } +} + +/** + Met a jour le titre du ProjectView +*/ +void ProjectView::updateWindowTitle() { + QString title; + if (project_) { + title = project_ -> pathNameTitle(); + } else { + title = tr("Projet", "window title for a project-less ProjectView"); + } + setWindowTitle(title); +} + +/** + Met a jour le titre d'un onglet + @param diagram Schema + @param diagram_title Titre du schema +*/ +void ProjectView::updateTabTitle(DiagramView *diagram, const QString &diagram_title) { + int diagram_tab_id = diagram_ids_.key(diagram, -1); + if (diagram_tab_id != -1) { + tabs_ -> setTabText(diagram_tab_id, diagram_title); + } +} + +/** + @param from Index de l'onglet avant le deplacement + @param to Index de l'onglet apres le deplacement +*/ +void ProjectView::tabMoved(int from, int to) { + if (!project_) return; + + // signale au QETProject le changement d'ordre des schemas + project_ -> diagramOrderChanged(from, to); + + // reconstruit la liste associant les index des onglets aux schemas + rebuildDiagramsMap(); + + // emet un signal pour informer le reste du monde que l'ordre des schemas a change + emit(diagramOrderChanged(this, from, to)); +} + +/** + @param diagram Schema a trouver + @return le DiagramView correspondant au schema passe en parametre, ou 0 si + le schema n'est pas trouve +*/ +DiagramView *ProjectView::findDiagram(Diagram *diagram) { + foreach(DiagramView *diagram_view, diagrams()) { + if (diagram_view -> diagram() == diagram) { + return(diagram_view); + } + } + return(0); +} + +/** + Reconstruit la map associant les index des onglets avec les DiagramView +*/ +void ProjectView::rebuildDiagramsMap() { + // vide la map + diagram_ids_.clear(); + + foreach(DiagramView *diagram_view, diagrams_) { + int dv_idx = tabs_ -> indexOf(diagram_view); + if (dv_idx == -1) continue; + diagram_ids_.insert(dv_idx, diagram_view); + } +} + +/** + Gere les changements d'onglets + @param tab_id Index de l'onglet actif +*/ +void ProjectView::tabChanged(int tab_id) { + emit(diagramActivated(diagram_ids_[tab_id])); +} + +/** + Gere le double-clic sur un onglet : edite les proprietes du schema + @param tab_id Index de l'onglet concerne +*/ +void ProjectView::tabDoubleClicked(int tab_id) { + // repere le schema concerne + DiagramView *diagram_view = diagram_ids_[tab_id]; + if (!diagram_view) return; + + diagram_view -> editDiagramProperties(); +} + +/** + Gere le fait que le premier schema d'un projet soit insere +*/ +void ProjectView::firstTabInserted() { + setDisplayFallbackWidget(false); +} + +/** + Gere le fait que le dernier schema d'un projet soit enleve +*/ +void ProjectView::lastTabRemoved() { + setDisplayFallbackWidget(true); +} + +/** + @param fallback true pour afficher le widget de fallback, false pour + afficher les onglets. + Le widget de Fallback est le widget affiche lorsque le projet ne comporte + aucun schema. +*/ +void ProjectView::setDisplayFallbackWidget(bool fallback) { + fallback_widget_ -> setVisible(fallback); + tabs_ -> setVisible(!fallback); +} diff --git a/sources/projectview.h b/sources/projectview.h new file mode 100644 index 000000000..703a38a67 --- /dev/null +++ b/sources/projectview.h @@ -0,0 +1,98 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef PROJECT_VIEW_H +#define PROJECT_VIEW_H +#include +class QETProject; +class DiagramView; +class Diagram; +class QETTabWidget; +/** + Cette classe affiche les schemas d'un projet dans des onglets. +*/ +class ProjectView : public QWidget { + Q_OBJECT + // constructeurs, destructeur + public: + ProjectView(QETProject *, QWidget * = 0); + virtual ~ProjectView(); + private: + ProjectView(const ProjectView &); + + // methodes + public: + QETProject *project(); + void setProject(QETProject *); + QList diagrams() const; + DiagramView *currentDiagram() const; + void closeEvent(QCloseEvent *); + + public slots: + void addNewDiagram(); + void addDiagram(DiagramView *); + void removeDiagram(DiagramView *); + void removeDiagram(Diagram *); + void showDiagram(DiagramView *); + void showDiagram(Diagram *); + void editProjectProperties(); + void editCurrentDiagramProperties(); + void editDiagramProperties(DiagramView *); + void editDiagramProperties(Diagram *); + void printProject(); + void exportProject(); + bool save(); + bool saveAs(); + bool saveAll(); + int cleanProject(); + void updateWindowTitle(); + void updateTabTitle(DiagramView *, const QString &); + void tabMoved(int, int); + + signals: + void diagramAdded(DiagramView *); + void diagramAboutToBeRemoved(DiagramView *); + void diagramRemoved(DiagramView *); + void diagramActivated(DiagramView *); + void diagramOrderChanged(ProjectView *, int, int); + void projectClosed(ProjectView *); + + private: + void loadDiagrams(); + DiagramView *findDiagram(Diagram *); + void rebuildDiagramsMap(); + bool tryClosing(); + bool tryClosingElementEditors(); + bool tryClosingDiagrams(); + + private slots: + void tabChanged(int); + void tabDoubleClicked(int); + void firstTabInserted(); + void lastTabRemoved(); + void setDisplayFallbackWidget(bool); + + // attributs + private: + QETProject *project_; + QVBoxLayout *layout_; + QWidget *fallback_widget_; + QETTabWidget *tabs_; + QMap diagram_ids_; + QList diagrams_; +}; +#endif diff --git a/sources/qet.cpp b/sources/qet.cpp index 93d66ab5c..d1bb9f69a 100644 --- a/sources/qet.cpp +++ b/sources/qet.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -16,6 +16,7 @@ along with QElectroTech. If not, see . */ #include "qet.h" +#include /** Permet de convertir une chaine de caracteres ("n", "s", "e" ou "w") @@ -143,27 +144,57 @@ bool QET::attributeIsAReal(const QDomElement &e, QString nom_attribut, double *r /** Permet de composer rapidement la proposition "x elements et y conducteurs" + ou encore "x elements, y conducteurs et z champs de texte". @param elements_count nombre d'elements @param conductors_count nombre de conducteurs @param texts_count nombre de champs de texte - @return la proposition decrivant le nombre d'elements et de conducteurs + @return la proposition decrivant le nombre d'elements, de conducteurs et de + textes */ QString QET::ElementsAndConductorsSentence(int elements_count, int conductors_count, int texts_count) { QString text; if (elements_count) { - text += QString::number(elements_count) + " "; - text += elements_count > 1 ? QObject::tr("\351l\351ments") : QObject::tr("\351l\351ment"); - if (conductors_count && texts_count) text += QObject::tr(", "); - else if (conductors_count || texts_count) text += QObject::tr(" et "); + text += QObject::tr( + "%n \351l\351ment(s)", + "part of a sentence listing the content of a diagram", + elements_count + ); + if (conductors_count && texts_count) { + text += QObject::tr( + ", ", + "separator between elements and conductors in a sentence " + "listing the content of a diagram" + ); + } else if (conductors_count || texts_count) { + text += QObject::tr( + " et ", + "separator between elements and conductors (or texts) in a " + "sentence listing the content of a diagram" + ); + } } + if (conductors_count) { - text += QString::number(conductors_count) + " "; - text += conductors_count > 1 ? QObject::tr("conducteurs") : QObject::tr("conducteur"); - if (texts_count) text += QObject::tr(" et "); + text += QObject::tr( + "%n conducteur(s)", + "part of a sentence listing the content of a diagram", + conductors_count + ); + if (texts_count) { + text += QObject::tr( + " et ", + "separator between conductors and texts in a sentence listing " + "the content of a diagram" + ); + } } + if (texts_count) { - text += QString::number(texts_count) + " "; - text += texts_count > 1 ? QObject::tr("champs de texte") : QObject::tr("champ de texte"); + text += QObject::tr( + "%n champ(s) de texte", + "part of a sentence listing the content of a diagram", + texts_count + ); } return(text); } @@ -227,6 +258,26 @@ QList QET::forbiddenCharacters() { return(QList() << '\\' << '/' << ':' << '*' << '?' << '"' << '<' << '>' << '|'); } +/** + @return une chaine listant les caracteres interdits dans les noms de fichiers sous + Windows + @param escape true pour remplacer les caracteres < et > par leurs entites HTML +*/ +QString QET::forbiddenCharactersString(bool escape) { + QString result; + foreach(QChar c, QET::forbiddenCharacters()) { + if (escape) { + if (c == '<') result += "<"; + else if (c == '>') result += ">"; + else result += QString(c); + } else { + result += QString(c); + } + result += " "; + } + return(result); +} + /** @param string une chaine de caracteres @return true si string contient un caractere interdit dans les noms de @@ -239,6 +290,29 @@ bool QET::containsForbiddenCharacters(const QString &string) { return(false); } +/** + Cette fonction transforme une chaine de caracteres (typiquement : un nom de + schema, de projet, d'element) en un nom de fichier potable. + Par nom de fichier potable, on entend un nom : + * ne comprenant pas de caracteres interdits sous Windows + * ne comprenant pas d'espace + @param name Chaine de caractere a transformer en nom de fichier potable + @todo virer les caracteres accentues ? +*/ +QString QET::stringToFileName(const QString &name) { + QString file_name(name.toLower()); + + // remplace les caracteres interdits par des tirets + foreach(QChar c, QET::forbiddenCharacters()) { + file_name.replace(c, '-'); + } + + // remplace les espaces par des underscores + file_name.replace(' ', '_'); + + return(file_name); +} + /** @param string une chaine de caracteres @return la meme chaine de caracteres, mais avec les espaces et backslashes @@ -290,3 +364,54 @@ QStringList QET::splitWithSpaces(const QString &string) { } return(returned_list); } + +/** + @param end_type un type d'extremite + @return une chaine representant le type d'extremite +*/ +QString QET::endTypeToString(const QET::EndType &end_type) { + switch(end_type) { + case QET::Simple: return("simple"); + case QET::Triangle: return("triangle"); + case QET::Circle: return("circle"); + case QET::Diamond: return("diamond"); + case QET::None: + default: + return("none"); + } +} + +/** + @param string une chaine representant un type d'extremite + @return le type d'extremite correspondant ; si la chaine est invalide, + QET::None est retourne. +*/ +QET::EndType QET::endTypeFromString(const QString &string) { + if (string == "simple") return(QET::Simple); + else if (string == "triangle") return(QET::Triangle); + else if (string == "circle") return(QET::Circle); + else if (string == "diamond") return(QET::Diamond); + else return(QET::None); +} + +/** + @param ptr pointeur quelconque + @return une representation hexadecimale de l'adresse du pointeur +*/ +QString QET::pointerString(void *ptr) { + static int hexa_digits = -1; + + if (hexa_digits == -1) { + // determine le nombre de bits dans un unsigned long int + hexa_digits = std::numeric_limits::digits / 4; + } + + return( + QString("0x%1").arg( + reinterpret_cast(ptr), + hexa_digits, + 16, + QChar('0') + ) + ); +} diff --git a/sources/qet.h b/sources/qet.h index 456dd1ba5..6b6b12cfb 100644 --- a/sources/qet.h +++ b/sources/qet.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -24,13 +24,71 @@ classes de l'application */ namespace QET { - /// version de QElectroTech - const QString version = "0.11"; + /// version de QElectroTech (utilisee pour estampiller les projets et elements) + const QString version = "0.2"; + /// version affichee de QElectroTech + const QString displayedVersion = version; QString license(); /// Orientation (utilise pour les bornes mais aussi pour les elements) enum Orientation {North, East, South, West}; + + /// Mouvements orientes + enum OrientedMovement { + ToNorth, + ToNorthEast, + ToEast, + ToSouthEast, + ToSouth, + ToSouthWest, + ToWest, + ToNorthWest + }; + /// Types de segment de conducteurs - enum ConductorSegmentType { Horizontal = 1, Vertical = 2, Both = 3 }; + enum ConductorSegmentType { + Horizontal = 1, ///< Segment horizontal + Vertical = 2, ///< Segment vertical + Both = 3 ///< Segment en biais / invalide + }; + + /** + Cet enum represente les differents embouts possibles pour les + extremites d'une ligne. + */ + enum EndType { + None, ///< Ligne normale + Simple, ///< Triangle sans base + Triangle, ///< Triangle + Circle, ///< Cercle + Diamond ///< Losange + }; + + /** + Cet enum represente les differents items manipulables dans une + collection d'elements. + */ + enum ItemType { + Element = 1, ///< Element + Category = 2, ///< Categorie + Collection = 4, ///< Collection + All = 7 ///< Tous + }; + + + /** + Cet enum represente les differentes facons de gerer un probleme lors de + la recopie ou du deplacement d'un element ou d'une categorie. + @see MoveElementsHandler + */ + enum Action { + Retry, ///< il faut reessayer l'operation + Ignore, ///< il faut passer a la suite + Erase, ///< il faut ecraser le contenu cible + Abort, ///< il faut arreter : ignorer l'item en cours et ne pas continuer + Managed, ///< le cas a ete gere par l'objet delegue : ne pas le traiter et passer a la suite + Rename ///< il faut renommer la cible + }; + QET::Orientation nextOrientation(QET::Orientation); QET::Orientation previousOrientation(QET::Orientation); QET::Orientation orientationFromString(const QString &); @@ -43,10 +101,15 @@ namespace QET { QString ElementsAndConductorsSentence(int, int, int = 0); QList findInDomElement(const QDomElement &, const QString &, const QString &); QList forbiddenCharacters(); + QString forbiddenCharactersString(bool = false); bool containsForbiddenCharacters(const QString &); + QString stringToFileName(const QString &); QString escapeSpaces(const QString &); QString unescapeSpaces(const QString &); QString joinWithSpaces(const QStringList &); QStringList splitWithSpaces(const QString &); + QString endTypeToString(const QET::EndType &); + QET::EndType endTypeFromString(const QString &); + QString pointerString(void *); } #endif diff --git a/sources/qetapp.cpp b/sources/qetapp.cpp index 7eaf2216e..b29b66516 100644 --- a/sources/qetapp.cpp +++ b/sources/qetapp.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -18,6 +18,9 @@ #include "qetapp.h" #include "qetdiagrameditor.h" #include "qetelementeditor.h" +#include "elementscollectionitem.h" +#include "fileelementscollection.h" +#include "qetproject.h" #include "recentfiles.h" #include #include @@ -28,6 +31,11 @@ QString QETApp::common_elements_dir = QString(); QString QETApp::config_dir = QString(); QString QETApp::lang_dir = QString(); QString QETApp::diagram_texts_font = QString(); +int QETApp::diagram_texts_size = 9; +FileElementsCollection *QETApp::common_collection = 0; +FileElementsCollection *QETApp::custom_collection = 0; +QMap QETApp::registered_projects_ = QMap(); +uint QETApp::next_project_id = 0; RecentFiles *QETApp::projects_recent_files_ = 0; RecentFiles *QETApp::elements_recent_files_ = 0; @@ -75,10 +83,10 @@ QETApp::QETApp(int &argc, char **argv) : // on ouvre soit les fichiers passes en parametre soit un nouvel editeur de projet if (qet_arguments_.files().isEmpty()) { - setSplashScreenStep(tr("Chargement... \311diteur de sch\351mas")); + setSplashScreenStep(tr("Chargement... \311diteur de sch\351mas", "splash screen caption")); new QETDiagramEditor(); } else { - setSplashScreenStep(tr("Chargement... Ouverture des fichiers")); + setSplashScreenStep(tr("Chargement... Ouverture des fichiers", "splash screen caption")); openFiles(qet_arguments_); } buildSystemTrayMenu(); @@ -92,6 +100,8 @@ QETApp::~QETApp() { delete elements_recent_files_; delete projects_recent_files_; delete qsti; + delete custom_collection; + delete common_collection; } /** @@ -112,14 +122,15 @@ void QETApp::setLanguage(const QString &desired_language) { qtTranslator.load("qt_" + desired_language, languages_path); installTranslator(&qtTranslator); - // determine la langue a utiliser pour l'application - if (desired_language != "fr") { - // utilisation de la version anglaise par defaut - if (!qetTranslator.load("qet_" + desired_language, languages_path)) { + // charge les traductions pour l'application QET + if (!qetTranslator.load("qet_" + desired_language, languages_path)) { + // en cas d'echec, on retombe sur les chaines natives pour le francais + if (desired_language != "fr") { + // utilisation de la version anglaise par defaut qetTranslator.load("qet_en", languages_path); } - installTranslator(&qetTranslator); } + installTranslator(&qetTranslator); } /** @@ -190,6 +201,50 @@ void QETApp::newElementEditor() { new QETElementEditor(); } +/** + @return la collection commune +*/ +ElementsCollection *QETApp::commonElementsCollection() { + if (!common_collection) { + common_collection = new FileElementsCollection(QETApp::commonElementsDir()); + common_collection -> setProtocol("common"); + } + return(common_collection); +} + +/** + @return la collection utilisateur +*/ +ElementsCollection *QETApp::customElementsCollection() { + if (!custom_collection) { + custom_collection = new FileElementsCollection(QETApp::customElementsDir()); + custom_collection -> setProtocol("custom"); + } + return(custom_collection); +} + +/** + @return la liste des collections disponibles + Cela inclut typiquement la collection commune, la collection perso + ainsi que les collections embarquees dans les projets. +*/ +QList QETApp::availableCollections() { + QList coll_list; + + // collection commune + coll_list << commonElementsCollection(); + + // collection perso + coll_list << customElementsCollection(); + + // collections embarquees + foreach(QETProject *opened_project, registered_projects_.values()) { + coll_list << opened_project -> embeddedCollection(); + } + + return(coll_list); +} + /** @return le nom de l'utilisateur courant */ @@ -372,6 +427,9 @@ QString QETApp::languagesPath() { bool QETApp::closeEveryEditor() { // s'assure que toutes les fenetres soient visibles avant de quitter restoreEveryEditor(); + foreach(QETProject *project, registered_projects_) { + project -> close(); + } bool every_window_closed = true; foreach(QETDiagramEditor *e, diagramEditors()) { every_window_closed = every_window_closed && e -> close(); @@ -382,10 +440,60 @@ bool QETApp::closeEveryEditor() { return(every_window_closed); } +/** + @return la police a utiliser pour rendre les textes sur les schemas +*/ QString QETApp::diagramTextsFont() { return(diagram_texts_font); } +/** + @return la taille de police par defaut a utiliser pour rendre les textes + sur les schemas +*/ +int QETApp::diagramTextsSize() { + return(diagram_texts_size); +} + +/** + @return les editeurs de schemas +*/ +QList QETApp::diagramEditors() { + return(static_cast(qApp) -> detectDiagramEditors()); +} + +/** + @return les editeurs d'elements +*/ +QList QETApp::elementEditors() { + return(static_cast(qApp) -> detectElementEditors()); +} + +/** + @param project un projet + @return les editeurs d'elements editant un element appartenant au projet + project +*/ +QList QETApp::elementEditors(QETProject *project) { + QList editors; + if (!project) return(editors); + + // pour chaque editeur d'element... + foreach(QETElementEditor *elmt_editor, elementEditors()) { + // on recupere l'emplacement de l'element qu'il edite + ElementsLocation elmt_editor_loc(elmt_editor -> location()); + + // il se peut que l'editeur edite un element non enregistre ou un fichier + if (elmt_editor_loc.isNull()) continue; + + if (elmt_editor_loc.project() == project) { + editors << elmt_editor; + } + } + + return(editors); +} + /** Nettoie certaines choses avant que l'application ne quitte */ @@ -394,7 +502,7 @@ void QETApp::cleanup() { } /// @return les editeurs de schemas ouverts -QList QETApp::diagramEditors() const { +QList QETApp::detectDiagramEditors() const { QList diagram_editors; foreach(QWidget *qw, topLevelWidgets()) { if (!qw -> isWindow()) continue; @@ -406,7 +514,7 @@ QList QETApp::diagramEditors() const { } /// @return les editeurs d'elements ouverts -QList QETApp::elementEditors() const { +QList QETApp::detectElementEditors() const { QList element_editors; foreach(QWidget *qw, topLevelWidgets()) { if (!qw -> isWindow()) continue; @@ -549,7 +657,7 @@ void QETApp::openProjectFiles(const QStringList &files_list) { // ouvre les fichiers dans l'editeur ainsi choisi foreach(QString file, files_list) { - de_open -> openAndAddDiagram(file); + de_open -> openAndAddProject(file); } } else { // cree un nouvel editeur qui ouvrira les fichiers @@ -646,7 +754,7 @@ void QETApp::initSplashScreen() { if (non_interactive_execution_) return; splash_screen_ = new QSplashScreen(QPixmap(":/ico/splash.png")); splash_screen_ -> show(); - setSplashScreenStep(tr("Chargement...")); + setSplashScreenStep(tr("Chargement...", "splash screen caption")); } /** @@ -700,6 +808,7 @@ void QETApp::initConfiguration() { // police a utiliser pour le rendu de texte diagram_texts_font = qet_settings -> value("diagramfont", "Sans Serif").toString(); + diagram_texts_size = qet_settings -> value("diagramsize", 9).toInt(); // fichiers recents projects_recent_files_ = new RecentFiles("projects"); @@ -710,9 +819,9 @@ void QETApp::initConfiguration() { Construit l'icone dans le systray et son menu */ void QETApp::initSystemTray() { - setSplashScreenStep(tr("Chargement... icne du systray")); + setSplashScreenStep(tr("Chargement... icne du systray", "splash screen caption")); // initialisation des menus de l'icone dans le systray - menu_systray = new QMenu(tr("QElectroTech")); + menu_systray = new QMenu(tr("QElectroTech", "systray menu title")); quitter_qet = new QAction(QIcon(":/ico/exit.png"), tr("&Quitter"), this); reduce_appli = new QAction(QIcon(":/ico/masquer.png"), tr("&Masquer"), this); @@ -740,7 +849,7 @@ void QETApp::initSystemTray() { // initialisation de l'icone du systray qsti = new QSystemTrayIcon(QIcon(":/ico/qet.png"), this); - qsti -> setToolTip(tr("QElectroTech")); + qsti -> setToolTip(tr("QElectroTech", "systray icon tooltip")); connect(qsti, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(systray(QSystemTrayIcon::ActivationReason))); qsti -> setContextMenu(menu_systray); qsti -> show(); @@ -857,7 +966,7 @@ void QETApp::printHelp() { Affiche la version sur la sortie standard */ void QETApp::printVersion() { - std::cout << qPrintable(QET::version) << std::endl; + std::cout << qPrintable(QET::displayedVersion) << std::endl; } /** @@ -916,3 +1025,124 @@ QIcon QETStyle::standardIconImplementation(StandardPixmap standardIcon, const QS QSettings &QETApp::settings() { return(*(instance() -> qet_settings)); } + +/** + @param location adresse virtuelle d'un item (collection, categorie, element, ...) + @param prefer_collections true pour renvoyer la collection lorsque le + chemin correspond aussi bien a une collection qu'a sa categorie racine + @return l'item correspondant a l'adresse virtuelle path, ou 0 si celui-ci n'a pas ete trouve +*/ +ElementsCollectionItem *QETApp::collectionItem(const ElementsLocation &location, bool prefer_collections) { + if (QETProject *target_project = location.project()) { + return(target_project -> embeddedCollection() -> item(location.path(), prefer_collections)); + } else { + QString path(location.path()); + if (path.startsWith("common://")) { + return(common_collection -> item(path, prefer_collections)); + } else if (path.startsWith("custom://")) { + return(custom_collection -> item(path, prefer_collections)); + } + } + return(0); +} + +/** + @param location adresse virtuelle de la categorie a creer + @return la categorie creee, ou 0 en cas d'echec +*/ +ElementsCategory *QETApp::createCategory(const ElementsLocation &location) { + if (QETProject *target_project = location.project()) { + return(target_project -> embeddedCollection() -> createCategory(location.path())); + } else { + QString path(location.path()); + if (path.startsWith("common://")) { + return(common_collection -> createCategory(path)); + } else if (path.startsWith("custom://")) { + return(custom_collection -> createCategory(path)); + } + } + return(0); +} + +/** + @param location adresse virtuelle de l'element a creer + @return l'element cree, ou 0 en cas d'echec +*/ +ElementDefinition *QETApp::createElement(const ElementsLocation &location) { + if (QETProject *target_project = location.project()) { + return(target_project -> embeddedCollection() -> createElement(location.path())); + } else { + QString path(location.path()); + if (path.startsWith("common://")) { + return(common_collection -> createElement(path)); + } else if (path.startsWith("custom://")) { + return(custom_collection -> createElement(path)); + } + } + return(0); +} + +/** + @return la liste des projets avec leurs ids associes +*/ +QMap QETApp::registeredProjects() { + return(registered_projects_); +} + +/** + @param project Projet a enregistrer aupres de l'application + @return true si le projet a pu etre enregistre, false sinon + L'echec de l'enregistrement d'un projet signifie generalement qu'il est deja enregistre. +*/ +bool QETApp::registerProject(QETProject *project) { + // le projet doit sembler valide + if (!project) return(false); + + // si le projet est deja enregistre, renvoie false + if (projectId(project) != -1) return(false); + + // enregistre le projet + registered_projects_.insert(next_project_id ++, project); + return(true); +} + +/** + Annule l'enregistrement du projet project + @param project Projet dont il faut annuler l'enregistrement + @return true si l'annulation a reussi, false sinon + L'echec de cette methode signifie generalement que le projet n'etait pas enregistre. +*/ +bool QETApp::unregisterProject(QETProject *project) { + int project_id = projectId(project); + + // si le projet n'est pas enregistre, renvoie false + if (project_id == -1) return(false); + + // annule l'enregistrement du projet + return(registered_projects_.remove(project_id) == 1); +} + +/** + @param id Id du projet voulu + @return le projet correspond a l'id passe en parametre +*/ +QETProject *QETApp::project(const uint &id) { + if (registered_projects_.contains(id)) { + return(registered_projects_[id]); + } else { + return(0); + } +} + +/** + @param project Projet dont on souhaite recuperer l'id + @return l'id du projet en parametre si celui-ci est enregistre, -1 sinon +*/ +int QETApp::projectId(const QETProject *project) { + foreach(int id, registered_projects_.keys()) { + if (registered_projects_[id] == project) { + return(id); + } + } + return(-1); +} diff --git a/sources/qetapp.h b/sources/qetapp.h index 979ab8171..1cfe11464 100644 --- a/sources/qetapp.h +++ b/sources/qetapp.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -20,9 +20,16 @@ #include "qetsingleapplication.h" #include #include +#include "elementslocation.h" #include "qetarguments.h" class QETDiagramEditor; class QETElementEditor; +class ElementsCollection; +class ElementsCollectionItem; +class FileElementsCollection; +class ElementsCategory; +class ElementDefinition; +class QETProject; class RecentFiles; /** Cette classe represente l'application QElectroTech. @@ -46,17 +53,26 @@ class QETApp : public QETSingleApplication { static void printVersion(); static void printLicense(); + static ElementsCollectionItem *collectionItem(const ElementsLocation &, bool = true); + static ElementsCategory *createCategory(const ElementsLocation &); + static ElementDefinition *createElement(const ElementsLocation &); + static ElementsCollection *commonElementsCollection(); + static ElementsCollection *customElementsCollection(); + static QList availableCollections(); + static QString userName(); static QString commonElementsDir(); static QString customElementsDir(); + static bool registerProject(QETProject *); + static bool unregisterProject(QETProject *); + static QMap registeredProjects(); + static QETProject *project(const uint &); + static int projectId(const QETProject *); static QString configDir(); static QSettings &settings(); static QString languagesPath(); static QString realPath(const QString &); static QString symbolicPath(const QString &); - static QETDiagramEditor *diagramEditorForFile(const QString &); - QList diagramEditors() const; - QList elementEditors() const; static RecentFiles *projectsRecentFiles(); static RecentFiles *elementsRecentFiles(); #ifdef QET_ALLOW_OVERRIDE_CED_OPTION @@ -75,6 +91,11 @@ class QETApp : public QETSingleApplication { static void overrideLangDir(const QString &); static QString lang_dir; ///< Dossier contenant les fichiers de langue static QString diagramTextsFont(); + static int diagramTextsSize(); + static QETDiagramEditor *diagramEditorForFile(const QString &); + static QList diagramEditors(); + static QList elementEditors(); + static QList elementEditors(QETProject *); protected: #ifdef Q_OS_DARWIN @@ -108,7 +129,13 @@ class QETApp : public QETSingleApplication { QSettings *qet_settings; QETArguments qet_arguments_; ///< Analyseur d'arguments bool non_interactive_execution_; ///< booleen indiquant si l'application va se terminer immediatement apres un court traitement + static QString diagram_texts_font; + static int diagram_texts_size; + static FileElementsCollection *common_collection; + static FileElementsCollection *custom_collection; + static QMap registered_projects_; + static uint next_project_id; static RecentFiles *projects_recent_files_; static RecentFiles *elements_recent_files_; @@ -137,6 +164,8 @@ class QETApp : public QETSingleApplication { void cleanup(); private: + QList detectDiagramEditors() const; + QList detectElementEditors() const; QList floatingToolbarsAndDocksForMainWindow(QMainWindow *) const; void parseArguments(); void initSplashScreen(); diff --git a/sources/qetarguments.cpp b/sources/qetarguments.cpp index b8c0fe0a6..772de4041 100644 --- a/sources/qetarguments.cpp +++ b/sources/qetarguments.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/qetarguments.h b/sources/qetarguments.h index ace82d16d..2ff5c46c2 100644 --- a/sources/qetarguments.h +++ b/sources/qetarguments.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/qetdiagrameditor.cpp b/sources/qetdiagrameditor.cpp index 7faba5821..bca14d37d 100644 --- a/sources/qetdiagrameditor.cpp +++ b/sources/qetdiagrameditor.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -23,6 +23,8 @@ #include "aboutqet.h" #include "conductorpropertieswidget.h" #include "configdialog.h" +#include "qetproject.h" +#include "projectview.h" #include "recentfiles.h" /** @@ -30,33 +32,55 @@ @param files Liste de fichiers a ouvrir @param parent le widget parent de la fenetre principale */ -QETDiagramEditor::QETDiagramEditor(const QStringList &files, QWidget *parent) : QMainWindow(parent), open_dialog_dir(QDir::homePath()) { +QETDiagramEditor::QETDiagramEditor(const QStringList &files, QWidget *parent) : + QMainWindow(parent), + open_dialog_dir(QDir::homePath()), + can_update_actions(true) +{ // mise en place de l'interface MDI au centre de l'application setCentralWidget(&workspace); + workspace.setBackground(QBrush(Qt::NoBrush)); + workspace.setObjectName("mdiarea"); + workspace.setStyleSheet( + "QAbstractScrollArea#mdiarea {" + " background-color:white;" + " background-image: url(':/ico/mdiarea_bg.png');" + " background-repeat: no-repeat;" + " background-position: center middle;" + "}" + ); // mise en place du signalmapper - connect(&windowMapper, SIGNAL(mapped(QWidget *)), &workspace, SLOT(setActiveWindow(QWidget *))); + connect(&windowMapper, SIGNAL(mapped(QWidget *)), this, SLOT(activateWidget(QWidget *))); // titre de la fenetre - setWindowTitle(tr("QElectroTech")); + setWindowTitle(tr("QElectroTech", "window title")); // icone de la fenetre setWindowIcon(QIcon(":/ico/qet.png")); // barre de statut de la fenetre - statusBar() -> showMessage(tr("QElectroTech")); + statusBar() -> showMessage(tr("QElectroTech", "status bar message")); // ajout du panel d'Elements en tant que QDockWidget - qdw_pa = new QDockWidget(tr("Panel d'\351l\351ments"), this); + qdw_pa = new QDockWidget(tr("Panel d'\351l\351ments", "dock title"), this); qdw_pa -> setObjectName("elements panel"); - qdw_pa -> setAllowedAreas(Qt::AllDockWidgetAreas); + qdw_pa -> setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); qdw_pa -> setFeatures(QDockWidget::AllDockWidgetFeatures); qdw_pa -> setMinimumWidth(160); qdw_pa -> setWidget(pa = new ElementsPanelWidget(qdw_pa)); + connect(&(pa -> elementsPanel()), SIGNAL(requestForDiagram(Diagram *)), this, SLOT(activateDiagram(Diagram *))); + connect(&(pa -> elementsPanel()), SIGNAL(requestForProject(QETProject *)), this, SLOT(activateProject(QETProject *))); - qdw_undo = new QDockWidget(tr("Annulations")); + connect(pa, SIGNAL(requestForProjectClosing(QETProject *)), this, SLOT(closeProject(QETProject *))); + connect(pa, SIGNAL(requestForProjectPropertiesEdition(QETProject *)), this, SLOT(editProjectProperties(QETProject *))); + connect(pa, SIGNAL(requestForDiagramPropertiesEdition(Diagram *)), this, SLOT(editDiagramProperties(Diagram *))); + connect(pa, SIGNAL(requestForNewDiagram(QETProject *)), this, SLOT(addDiagramToProject(QETProject *))); + connect(pa, SIGNAL(requestForDiagramDeletion(Diagram *)), this, SLOT(removeDiagram(Diagram *))); + + qdw_undo = new QDockWidget(tr("Annulations", "dock title")); qdw_undo -> setObjectName("diagram_undo"); - qdw_undo -> setAllowedAreas(Qt::AllDockWidgetAreas); + qdw_pa -> setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); qdw_undo -> setFeatures(QDockWidget::AllDockWidgetFeatures); qdw_undo -> setMinimumWidth(160); tabifyDockWidget(qdw_pa, qdw_undo); @@ -81,8 +105,8 @@ QETDiagramEditor::QETDiagramEditor(const QStringList &files, QWidget *parent) : setWindowState(Qt::WindowMaximized); // connexions signaux / slots pour une interface sensee - connect(&workspace, SIGNAL(windowActivated(QWidget *)), this, SLOT(slot_updateWindowsMenu())); - connect(&workspace, SIGNAL(windowActivated(QWidget *)), this, SLOT(slot_updateActions())); + connect(&workspace, SIGNAL(subWindowActivated(QMdiSubWindow *)), this, SLOT(slot_updateWindowsMenu())); + connect(&workspace, SIGNAL(subWindowActivated(QMdiSubWindow *)), this, SLOT(slot_updateActions())); connect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(slot_updatePasteAction())); // lecture des parametres @@ -92,16 +116,19 @@ QETDiagramEditor::QETDiagramEditor(const QStringList &files, QWidget *parent) : show(); // si des chemins de fichiers valides sont passes en arguments - int opened_files = 0; - if (files.size()) { + uint opened_projects = 0; + if (files.count()) { // alors on ouvre ces fichiers foreach(QString file, files) { - if (openAndAddDiagram(file)) ++ opened_files; + bool project_opening = openAndAddProject(file, false); + if (project_opening) { + ++ opened_projects; + } } } // si aucun schema n'a ete ouvert jusqu'a maintenant, on ouvre un nouveau schema - if (!opened_files) newDiagram(); + if (!opened_projects) newProject(); } /** @@ -115,18 +142,20 @@ QETDiagramEditor::~QETDiagramEditor() { @param qce Le QCloseEvent correspondant a l'evenement de fermeture */ void QETDiagramEditor::closeEvent(QCloseEvent *qce) { - // quitte directement s'il n'y a aucun schema ouvert + // quitte directement s'il n'y a aucun projet ouvert bool can_quit = true; - if (currentDiagram()) { - // sinon demande la permission de fermer chaque schema - foreach(QWidget *diagram_window, workspace.windowList()) { - if (qobject_cast(diagram_window)) { - workspace.setActiveWindow(diagram_window); - if (!closeDiagram()) { - can_quit = false; - qce -> ignore(); - break; - } + if (openedProjects().count()) { + // s'assure que la fenetre soit visible s'il y a des projets a fermer + if (!isVisible() || isMinimized()) { + if (isMaximized()) showMaximized(); + else showNormal(); + } + // sinon demande la permission de fermer chaque projet + foreach(ProjectView *project, openedProjects()) { + if (!closeProject(project)) { + can_quit = false; + qce -> ignore(); + break; } } } @@ -166,6 +195,7 @@ void QETDiagramEditor::actions() { close_file = new QAction(QIcon(":/ico/fileclose.png"), tr("&Fermer"), this); save_file = new QAction(QIcon(":/ico/save.png"), tr("&Enregistrer"), this); save_file_sous = new QAction(QIcon(":/ico/saveas.png"), tr("Enregistrer sous"), this); + save_all = new QAction(QIcon(":/ico/save_all.png"), tr("&Enregistrer tous les sch\351mas"), this); import_diagram = new QAction(QIcon(":/ico/import.png"), tr("&Importer"), this); export_diagram = new QAction(QIcon(":/ico/export.png"), tr("E&xporter"), this); print = new QAction(QIcon(":/ico/print.png"), tr("Imprimer"), this); @@ -193,21 +223,28 @@ void QETDiagramEditor::actions() { add_row = new QAction(QIcon(":/ico/add_row.png"), tr("Ajouter une ligne"), this); remove_row = new QAction(QIcon(":/ico/remove_row.png"), tr("Enlever une ligne"), this); + prj_edit_prop = new QAction(QIcon(":/ico/info.png"), tr("Propri\351t\351s du projet"), this); + prj_add_diagram = new QAction(QIcon(":/ico/diagram_add.png"),tr("Ajouter un sch\351ma"), this); + prj_del_diagram = new QAction(QIcon(":/ico/diagram_del.png"),tr("Supprimer le sch\351ma"), this); + prj_clean = new QAction( tr("Nettoyer le projet"), this); + zoom_in = new QAction(QIcon(":/ico/viewmag+.png"), tr("Zoom avant"), this); zoom_out = new QAction(QIcon(":/ico/viewmag-.png"), tr("Zoom arri\350re"), this); zoom_fit = new QAction(QIcon(":/ico/viewmagfit.png"), tr("Zoom adapt\351"), this); zoom_reset = new QAction(QIcon(":/ico/viewmag.png"), tr("Pas de zoom"), this); + tabbed_view_mode = new QAction( tr("en utilisant des onglets"), this); + windowed_view_mode= new QAction( tr("en utilisant des fen\352tres"), this); + mode_selection = new QAction(QIcon(":/ico/select.png"), tr("Mode Selection"), this); mode_visualise = new QAction(QIcon(":/ico/move.png"), tr("Mode Visualisation"), this); - fullscreen_on = new QAction(QIcon(":/ico/entrer_fs.png"), tr("Passer en &mode plein \351cran"), this); - fullscreen_off = new QAction(QIcon(":/ico/sortir_fs.png"), tr("Sortir du &mode plein \351cran"), this); + fullscreen = new QAction(this); + slot_updateFullScreenAction(); configure = new QAction(QIcon(":/ico/configure.png"), tr("&Configurer QElectroTech"), this); tile_window = new QAction( tr("&Mosa\357que"), this); cascade_window = new QAction( tr("&Cascade"), this); - arrange_window = new QAction( tr("Arranger les fen\352tres r\351duites"), this); next_window = new QAction( tr("Fen\352tre suivante"), this); prev_window = new QAction( tr("Fen\352tre pr\351c\351dente"), this); @@ -239,121 +276,156 @@ void QETDiagramEditor::actions() { infos_diagram -> setShortcut(QKeySequence(tr("Ctrl+L"))); conductor_default -> setShortcut(QKeySequence(tr("Ctrl+D"))); + prj_add_diagram -> setShortcut(QKeySequence(tr("Ctrl+T"))); + zoom_in -> setShortcut(QKeySequence::ZoomIn); zoom_out -> setShortcut(QKeySequence::ZoomOut); zoom_fit -> setShortcut(QKeySequence(tr("Ctrl+9"))); zoom_reset -> setShortcut(QKeySequence(tr("Ctrl+0"))); - fullscreen_on -> setShortcut(QKeySequence(tr("Ctrl+Shift+F"))); - fullscreen_off -> setShortcut(QKeySequence(tr("Ctrl+Shift+F"))); + fullscreen -> setShortcut(QKeySequence(tr("Ctrl+Shift+F"))); next_window -> setShortcut(QKeySequence::NextChild); prev_window -> setShortcut(QKeySequence::PreviousChild); // affichage dans la barre de statut - new_file -> setStatusTip(tr("Cr\351e un nouveau sch\351ma")); - open_file -> setStatusTip(tr("Ouvre un sch\351ma existant")); - close_file -> setStatusTip(tr("Ferme le sch\351ma courant")); - save_file -> setStatusTip(tr("Enregistre le sch\351ma courant")); - save_file_sous -> setStatusTip(tr("Enregistre le sch\351ma courant avec un autre nom de fichier")); - import_diagram -> setStatusTip(tr("Importe un sch\351ma dans le sch\351ma courant")); - export_diagram -> setStatusTip(tr("Exporte le sch\351ma courant dans un autre format")); - print -> setStatusTip(tr("Imprime le sch\351ma courant")); - quit_editor -> setStatusTip(tr("Ferme l'application QElectroTech")); + new_file -> setStatusTip(tr("Cr\351e un nouveau sch\351ma", "status bar tip")); + open_file -> setStatusTip(tr("Ouvre un sch\351ma existant", "status bar tip")); + close_file -> setStatusTip(tr("Ferme le sch\351ma courant", "status bar tip")); + save_file -> setStatusTip(tr("Enregistre le sch\351ma courant", "status bar tip")); + save_file_sous -> setStatusTip(tr("Enregistre le sch\351ma courant avec un autre nom de fichier", "status bar tip")); + save_all -> setStatusTip(tr("Enregistre tous les sch\351mas du projet courant", "status bar tip")); + import_diagram -> setStatusTip(tr("Importe un sch\351ma dans le sch\351ma courant", "status bar tip")); + export_diagram -> setStatusTip(tr("Exporte le sch\351ma courant dans un autre format", "status bar tip")); + print -> setStatusTip(tr("Imprime le sch\351ma courant", "status bar tip")); + quit_editor -> setStatusTip(tr("Ferme l'application QElectroTech", "status bar tip")); - undo -> setStatusTip(tr("Annule l'action pr\351c\351dente")); - redo -> setStatusTip(tr("Restaure l'action annul\351e")); - cut -> setStatusTip(tr("Transf\350re les \351l\351ments s\351lectionn\351s dans le presse-papier")); - copy -> setStatusTip(tr("Copie les \351l\351ments s\351lectionn\351s dans le presse-papier")); - paste -> setStatusTip(tr("Place les \351l\351ments du presse-papier sur le sch\351ma")); - select_all -> setStatusTip(tr("S\351lectionne tous les \351l\351ments du sch\351ma")); - select_nothing -> setStatusTip(tr("D\351s\351lectionne tous les \351l\351ments du sch\351ma")); - select_invert -> setStatusTip(tr("D\351s\351lectionne les \351l\351ments s\351lectionn\351s et s\351lectionne les \351l\351ments non s\351lectionn\351s")); - delete_selection -> setStatusTip(tr("Enl\350ve les \351l\351ments s\351lectionn\351s du sch\351ma")); - rotate_selection -> setStatusTip(tr("Pivote les \351l\351ments s\351lectionn\351s")); - conductor_prop -> setStatusTip(tr("\311dite les propri\351t\351s du conducteur s\351lectionn\351")); - conductor_reset -> setStatusTip(tr("Recalcule les chemins des conducteurs sans tenir compte des modifications")); - conductor_default -> setStatusTip(tr("Sp\351cifie les propri\351t\351s par d\351faut des conducteurs")); - infos_diagram -> setStatusTip(tr("\311dite les informations affich\351es par le cartouche")); - add_column -> setStatusTip(tr("Ajoute une colonne au sch\351ma")); - remove_column -> setStatusTip(tr("Enl\350ve une colonne au sch\351ma")); - add_row -> setStatusTip(tr("Agrandit le sch\351ma en hauteur")); - remove_row -> setStatusTip(tr("R\351tr\351cit le sch\351ma en hauteur")); + undo -> setStatusTip(tr("Annule l'action pr\351c\351dente", "status bar tip")); + redo -> setStatusTip(tr("Restaure l'action annul\351e", "status bar tip")); + cut -> setStatusTip(tr("Transf\350re les \351l\351ments s\351lectionn\351s dans le presse-papier", "status bar tip")); + copy -> setStatusTip(tr("Copie les \351l\351ments s\351lectionn\351s dans le presse-papier", "status bar tip")); + paste -> setStatusTip(tr("Place les \351l\351ments du presse-papier sur le sch\351ma", "status bar tip")); + select_all -> setStatusTip(tr("S\351lectionne tous les \351l\351ments du sch\351ma", "status bar tip")); + select_nothing -> setStatusTip(tr("D\351s\351lectionne tous les \351l\351ments du sch\351ma", "status bar tip")); + select_invert -> setStatusTip(tr("D\351s\351lectionne les \351l\351ments s\351lectionn\351s et s\351lectionne les \351l\351ments non s\351lectionn\351s", "status bar tip")); + delete_selection -> setStatusTip(tr("Enl\350ve les \351l\351ments s\351lectionn\351s du sch\351ma", "status bar tip")); + rotate_selection -> setStatusTip(tr("Pivote les \351l\351ments s\351lectionn\351s", "status bar tip")); + conductor_prop -> setStatusTip(tr("\311dite les propri\351t\351s du conducteur s\351lectionn\351", "status bar tip")); + conductor_reset -> setStatusTip(tr("Recalcule les chemins des conducteurs sans tenir compte des modifications", "status bar tip")); + conductor_default -> setStatusTip(tr("Sp\351cifie les propri\351t\351s par d\351faut des conducteurs", "status bar tip")); + infos_diagram -> setStatusTip(tr("\311dite les informations affich\351es par le cartouche", "status bar tip")); + add_column -> setStatusTip(tr("Ajoute une colonne au sch\351ma", "status bar tip")); + remove_column -> setStatusTip(tr("Enl\350ve une colonne au sch\351ma", "status bar tip")); + add_row -> setStatusTip(tr("Agrandit le sch\351ma en hauteur", "status bar tip")); + remove_row -> setStatusTip(tr("R\351tr\351cit le sch\351ma en hauteur", "status bar tip")); - zoom_in -> setStatusTip(tr("Agrandit le sch\351ma")); - zoom_out -> setStatusTip(tr("R\351tr\351cit le sch\351ma")); - zoom_fit -> setStatusTip(tr("Adapte la taille du sch\351ma afin qu'il soit enti\350rement visible")); - zoom_reset -> setStatusTip(tr("Restaure le zoom par d\351faut")); + zoom_in -> setStatusTip(tr("Agrandit le sch\351ma", "status bar tip")); + zoom_out -> setStatusTip(tr("R\351tr\351cit le sch\351ma", "status bar tip")); + zoom_fit -> setStatusTip(tr("Adapte la taille du sch\351ma afin qu'il soit enti\350rement visible", "status bar tip")); + zoom_reset -> setStatusTip(tr("Restaure le zoom par d\351faut", "status bar tip")); - mode_selection -> setStatusTip(tr("Permet de s\351lectionner les \351l\351ments")); - mode_visualise -> setStatusTip(tr("Permet de visualiser le sch\351ma sans pouvoir le modifier")); + windowed_view_mode -> setStatusTip(tr("Pr\351sente les diff\351rents projets ouverts dans des sous-fen\352tres", "status bar tip")); + tabbed_view_mode -> setStatusTip(tr("Pr\351sente les diff\351rents projets ouverts des onglets", "status bar tip")); - fullscreen_on -> setStatusTip(tr("Affiche QElectroTech en mode plein \351cran")); - fullscreen_off -> setStatusTip(tr("Affiche QElectroTech en mode fen\352tr\351")); - configure -> setStatusTip(tr("Permet de r\351gler diff\351rents param\350tres de QElectroTech")); + mode_selection -> setStatusTip(tr("Permet de s\351lectionner les \351l\351ments", "status bar tip")); + mode_visualise -> setStatusTip(tr("Permet de visualiser le sch\351ma sans pouvoir le modifier", "status bar tip")); - tile_window -> setStatusTip(tr("Dispose les fen\352tres en mosa\357que")); - cascade_window -> setStatusTip(tr("Dispose les fen\352tres en cascade")); - arrange_window -> setStatusTip(tr("Aligne les fen\352tres r\351duites")); - next_window -> setStatusTip(tr("Active la fen\352tre suivante")); - prev_window -> setStatusTip(tr("Active la fen\352tre pr\351c\351dente")); + configure -> setStatusTip(tr("Permet de r\351gler diff\351rents param\350tres de QElectroTech", "status bar tip")); - about_qet -> setStatusTip(tr("Affiche des informations sur QElectroTech")); - about_qt -> setStatusTip(tr("Affiche des informations sur la biblioth\350que Qt")); + tile_window -> setStatusTip(tr("Dispose les fen\352tres en mosa\357que", "status bar tip")); + cascade_window -> setStatusTip(tr("Dispose les fen\352tres en cascade", "status bar tip")); + next_window -> setStatusTip(tr("Active la fen\352tre suivante", "status bar tip")); + prev_window -> setStatusTip(tr("Active la fen\352tre pr\351c\351dente", "status bar tip")); + + about_qet -> setStatusTip(tr("Affiche des informations sur QElectroTech", "status bar tip")); + about_qt -> setStatusTip(tr("Affiche des informations sur la biblioth\350que Qt", "status bar tip")); // traitements speciaux - add_text -> setCheckable(true); - mode_selection -> setCheckable(true); - mode_visualise -> setCheckable(true); - mode_selection -> setChecked(true); + add_text -> setCheckable(true); + windowed_view_mode -> setCheckable(true); + tabbed_view_mode -> setCheckable(true); + mode_selection -> setCheckable(true); + mode_visualise -> setCheckable(true); + mode_selection -> setChecked(true); grp_visu_sel = new QActionGroup(this); grp_visu_sel -> addAction(mode_selection); grp_visu_sel -> addAction(mode_visualise); grp_visu_sel -> setExclusive(true); + grp_view_mode = new QActionGroup(this); + grp_view_mode -> addAction(windowed_view_mode); + grp_view_mode -> addAction(tabbed_view_mode); + grp_view_mode -> setExclusive(true); + // connexion a des slots - connect(quit_editor, SIGNAL(triggered()), this, SLOT(close()) ); - connect(select_all, SIGNAL(triggered()), this, SLOT(slot_selectAll()) ); - connect(select_nothing, SIGNAL(triggered()), this, SLOT(slot_selectNothing()) ); - connect(select_invert, SIGNAL(triggered()), this, SLOT(slot_selectInvert()) ); - connect(delete_selection, SIGNAL(triggered()), this, SLOT(slot_delete()) ); - connect(rotate_selection, SIGNAL(triggered()), this, SLOT(slot_rotate()) ); - connect(fullscreen_on, SIGNAL(triggered()), this, SLOT(toggleFullScreen()) ); - connect(fullscreen_off, SIGNAL(triggered()), this, SLOT(toggleFullScreen()) ); - connect(configure, SIGNAL(triggered()), this, SLOT(configureQET()) ); - connect(mode_selection, SIGNAL(triggered()), this, SLOT(slot_setSelectionMode()) ); - connect(mode_visualise, SIGNAL(triggered()), this, SLOT(slot_setVisualisationMode())); - connect(about_qet, SIGNAL(triggered()), this, SLOT(aboutQET()) ); - connect(about_qt, SIGNAL(triggered()), qApp, SLOT(aboutQt()) ); - connect(zoom_in, SIGNAL(triggered()), this, SLOT(slot_zoomIn()) ); - connect(zoom_out, SIGNAL(triggered()), this, SLOT(slot_zoomOut()) ); - connect(zoom_fit, SIGNAL(triggered()), this, SLOT(slot_zoomFit()) ); - connect(zoom_reset, SIGNAL(triggered()), this, SLOT(slot_zoomReset()) ); - connect(print, SIGNAL(triggered()), this, SLOT(printDialog()) ); - connect(export_diagram, SIGNAL(triggered()), this, SLOT(exportDialog()) ); - connect(save_file_sous, SIGNAL(triggered()), this, SLOT(saveAsDialog()) ); - connect(save_file, SIGNAL(triggered()), this, SLOT(save()) ); - connect(new_file, SIGNAL(triggered()), this, SLOT(newDiagram()) ); - connect(open_file, SIGNAL(triggered()), this, SLOT(openDiagram()) ); - connect(close_file, SIGNAL(triggered()), this, SLOT(closeDiagram()) ); - connect(cut, SIGNAL(triggered()), this, SLOT(slot_cut()) ); - connect(copy, SIGNAL(triggered()), this, SLOT(slot_copy()) ); - connect(paste, SIGNAL(triggered()), this, SLOT(slot_paste()) ); - connect(tile_window, SIGNAL(triggered()), &workspace, SLOT(tile()) ); - connect(cascade_window, SIGNAL(triggered()), &workspace, SLOT(cascade()) ); - connect(arrange_window, SIGNAL(triggered()), &workspace, SLOT(arrangeIcons()) ); - connect(next_window, SIGNAL(triggered()), &workspace, SLOT(activateNextWindow()) ); - connect(prev_window, SIGNAL(triggered()), &workspace, SLOT(activatePreviousWindow()) ); - connect(conductor_prop, SIGNAL(triggered()), this, SLOT(slot_editConductor()) ); - connect(conductor_reset, SIGNAL(triggered()), this, SLOT(slot_resetConductors()) ); - connect(conductor_default,SIGNAL(triggered()), this, SLOT(slot_editDefaultConductors())); - connect(infos_diagram, SIGNAL(triggered()), this, SLOT(slot_editInfos()) ); - connect(add_text, SIGNAL(triggered()), this, SLOT(slot_addText()) ); - connect(add_column, SIGNAL(triggered()), this, SLOT(slot_addColumn()) ); - connect(remove_column, SIGNAL(triggered()), this, SLOT(slot_removeColumn()) ); - connect(add_row, SIGNAL(triggered()), this, SLOT(slot_addRow()) ); - connect(remove_row, SIGNAL(triggered()), this, SLOT(slot_removeRow()) ); + connect(quit_editor, SIGNAL(triggered()), this, SLOT(close()) ); + connect(select_all, SIGNAL(triggered()), this, SLOT(slot_selectAll()) ); + connect(select_nothing, SIGNAL(triggered()), this, SLOT(slot_selectNothing()) ); + connect(select_invert, SIGNAL(triggered()), this, SLOT(slot_selectInvert()) ); + connect(delete_selection, SIGNAL(triggered()), this, SLOT(slot_delete()) ); + connect(rotate_selection, SIGNAL(triggered()), this, SLOT(slot_rotate()) ); + connect(fullscreen, SIGNAL(triggered()), this, SLOT(toggleFullScreen()) ); + connect(configure, SIGNAL(triggered()), this, SLOT(configureQET()) ); + connect(windowed_view_mode, SIGNAL(triggered()), this, SLOT(setWindowedMode()) ); + connect(tabbed_view_mode, SIGNAL(triggered()), this, SLOT(setTabbedMode()) ); + connect(mode_selection, SIGNAL(triggered()), this, SLOT(slot_setSelectionMode()) ); + connect(mode_visualise, SIGNAL(triggered()), this, SLOT(slot_setVisualisationMode()) ); + connect(about_qet, SIGNAL(triggered()), this, SLOT(aboutQET()) ); + connect(about_qt, SIGNAL(triggered()), qApp, SLOT(aboutQt()) ); + connect(prj_edit_prop, SIGNAL(triggered()), this, SLOT(editCurrentProjectProperties())); + connect(prj_add_diagram, SIGNAL(triggered()), this, SLOT(addDiagramToProject()) ); + connect(prj_del_diagram, SIGNAL(triggered()), this, SLOT(removeDiagramFromProject()) ); + connect(prj_clean, SIGNAL(triggered()), this, SLOT(cleanCurrentProject()) ); + connect(zoom_in, SIGNAL(triggered()), this, SLOT(slot_zoomIn()) ); + connect(zoom_out, SIGNAL(triggered()), this, SLOT(slot_zoomOut()) ); + connect(zoom_fit, SIGNAL(triggered()), this, SLOT(slot_zoomFit()) ); + connect(zoom_reset, SIGNAL(triggered()), this, SLOT(slot_zoomReset()) ); + connect(print, SIGNAL(triggered()), this, SLOT(printDialog()) ); + connect(export_diagram, SIGNAL(triggered()), this, SLOT(exportDialog()) ); + connect(save_file_sous, SIGNAL(triggered()), this, SLOT(saveAsDialog()) ); + connect(save_file, SIGNAL(triggered()), this, SLOT(save()) ); + connect(save_all, SIGNAL(triggered()), this, SLOT(saveAll()) ); + connect(new_file, SIGNAL(triggered()), this, SLOT(newProject()) ); + connect(open_file, SIGNAL(triggered()), this, SLOT(openProject()) ); + connect(close_file, SIGNAL(triggered()), this, SLOT(closeCurrentProject()) ); + connect(cut, SIGNAL(triggered()), this, SLOT(slot_cut()) ); + connect(copy, SIGNAL(triggered()), this, SLOT(slot_copy()) ); + connect(paste, SIGNAL(triggered()), this, SLOT(slot_paste()) ); + connect(tile_window, SIGNAL(triggered()), &workspace, SLOT(tileSubWindows()) ); + connect(cascade_window, SIGNAL(triggered()), &workspace, SLOT(cascadeSubWindows()) ); + connect(next_window, SIGNAL(triggered()), &workspace, SLOT(activateNextSubWindow()) ); + connect(prev_window, SIGNAL(triggered()), &workspace, SLOT(activatePreviousSubWindow()) ); + connect(conductor_prop, SIGNAL(triggered()), this, SLOT(slot_editConductor()) ); + connect(conductor_reset, SIGNAL(triggered()), this, SLOT(slot_resetConductors()) ); + connect(conductor_default, SIGNAL(triggered()), this, SLOT(slot_editDefaultConductors())); + connect(infos_diagram, SIGNAL(triggered()), this, SLOT(editCurrentDiagramProperties())); + connect(add_text, SIGNAL(triggered()), this, SLOT(slot_addText()) ); + connect(add_column, SIGNAL(triggered()), this, SLOT(slot_addColumn()) ); + connect(remove_column, SIGNAL(triggered()), this, SLOT(slot_removeColumn()) ); + connect(add_row, SIGNAL(triggered()), this, SLOT(slot_addRow()) ); + connect(remove_row, SIGNAL(triggered()), this, SLOT(slot_removeRow()) ); +} + +/** + Gere les evenements du l'editeur de schema + Reimplemente ici pour : + * eviter un conflit sur le raccourci clavier "Ctrl+W" (QKeySequence::Close) + * mettre a jour l'action permettant d'entrer en mode plein ecran ou d'en sortir + @param e Evenement +*/ +bool QETDiagramEditor::event(QEvent *e) { + if (e -> type() == QEvent::ShortcutOverride) { + QKeyEvent *shortcut_event = static_cast(e); + if (shortcut_event && shortcut_event -> matches(QKeySequence::Close)) { + close_file -> trigger(); + e -> accept(); + return(true); + } + } else if (e -> type() == QEvent::WindowStateChange) { + slot_updateFullScreenAction(); + } + return(QMainWindow::event(e)); } /** @@ -362,6 +434,7 @@ void QETDiagramEditor::actions() { void QETDiagramEditor::menus() { QMenu *menu_fichier = menuBar() -> addMenu(tr("&Fichier")); QMenu *menu_edition = menuBar() -> addMenu(tr("&\311dition")); + QMenu *menu_project = menuBar() -> addMenu(tr("&Projet")); QMenu *menu_affichage = menuBar() -> addMenu(tr("Afficha&ge")); //QMenu *menu_outils = menuBar() -> addMenu(tr("O&utils")); QMenu *menu_config = menuBar() -> addMenu(tr("&Configuration")); @@ -371,6 +444,7 @@ void QETDiagramEditor::menus() { // tear off feature rulezz... pas ^^ mais bon... menu_fichier -> setTearOffEnabled(true); menu_edition -> setTearOffEnabled(true); + menu_project -> setTearOffEnabled(true); menu_affichage -> setTearOffEnabled(true); //menu_outils -> setTearOffEnabled(true); menu_config -> setTearOffEnabled(true); @@ -384,6 +458,7 @@ void QETDiagramEditor::menus() { connect(QETApp::projectsRecentFiles(), SIGNAL(fileOpeningRequested(const QString &)), this, SLOT(openRecentFile(const QString &))); menu_fichier -> addAction(save_file); menu_fichier -> addAction(save_file_sous); + menu_fichier -> addAction(save_all); menu_fichier -> addAction(close_file); menu_fichier -> addSeparator(); //menu_fichier -> addAction(import_diagram); @@ -418,6 +493,12 @@ void QETDiagramEditor::menus() { menu_edition -> addAction(add_row); menu_edition -> addAction(remove_row); + // menu Projet + menu_project -> addAction(prj_edit_prop); + menu_project -> addAction(prj_add_diagram); + menu_project -> addAction(prj_del_diagram); + menu_project -> addAction(prj_clean); + // menu Configurer > Afficher QMenu *display_toolbars = createPopupMenu(); display_toolbars -> setTearOffEnabled(true); @@ -430,6 +511,12 @@ void QETDiagramEditor::menus() { qdw_undo -> toggleViewAction() -> setStatusTip(tr("Affiche ou non la liste des modifications")); // menu Affichage + QMenu *projects_view_mode = menu_affichage -> addMenu(tr("Afficher les projets")); + projects_view_mode -> setTearOffEnabled(true); + projects_view_mode -> addAction(windowed_view_mode); + projects_view_mode -> addAction(tabbed_view_mode); + + menu_affichage -> addSeparator(); menu_affichage -> addAction(mode_selection); menu_affichage -> addAction(mode_visualise); menu_affichage -> addSeparator(); @@ -440,7 +527,7 @@ void QETDiagramEditor::menus() { // menu Configuration menu_config -> addMenu(display_toolbars); - menu_config -> addAction(fullscreen_on); + menu_config -> addAction(fullscreen); menu_config -> addAction(configure); // menu Fenetres @@ -468,6 +555,7 @@ void QETDiagramEditor::toolbar() { main_bar -> addAction(open_file); main_bar -> addAction(save_file); main_bar -> addAction(save_file_sous); + main_bar -> addAction(save_all); main_bar -> addAction(close_file); main_bar -> addAction(print); main_bar -> addSeparator(); @@ -506,18 +594,18 @@ void QETDiagramEditor::toolbar() { Imprime le schema courant */ void QETDiagramEditor::printDialog() { - DiagramView *sv = currentDiagram(); - if (!sv) return; - sv -> dialogPrint(); + ProjectView *current_project = currentProject(); + if (!current_project) return; + current_project -> printProject(); } /** Gere l'export de schema sous forme d'image */ void QETDiagramEditor::exportDialog() { - DiagramView *sv = currentDiagram(); - if (!sv) return; - sv -> dialogExport(); + ProjectView *current_project = currentProject(); + if (!current_project) return; + current_project -> exportProject(); } /** @@ -525,8 +613,14 @@ void QETDiagramEditor::exportDialog() { @return true si l'enregistrement a reussi, false sinon */ bool QETDiagramEditor::save() { - if (!currentDiagram()) return(false); - return(currentDiagram() -> save()); + if (ProjectView *project_view = currentProject()) { + bool save_file = project_view -> save(); + if (save_file) { + QETApp::projectsRecentFiles() -> fileWasOpened(project_view -> project() -> filePath()); + } + return(save_file); + } + return(false); } /** @@ -534,24 +628,47 @@ bool QETDiagramEditor::save() { @return true si l'enregistrement a reussi, false sinon */ bool QETDiagramEditor::saveAsDialog() { - if (DiagramView *current_diagram_view = currentDiagram()) { - bool save_file = current_diagram_view -> saveAs(); + if (ProjectView *project_view = currentProject()) { + bool save_file = project_view -> saveAs(); if (save_file) { - QETApp::projectsRecentFiles() -> fileWasOpened(current_diagram_view -> file_name); + QETApp::projectsRecentFiles() -> fileWasOpened(project_view -> project() -> filePath()); } return(save_file); - } else { - return(false); } + return(false); } /** - Cette methode cree un nouveau schema. - @return true si tout s'est bien passe ; false si vous executez cette fonction dans un univers non cartesien (en fait y'a pas de return(false) :p) + Methode enregistrant tous les schemas. + @return true si l'enregistrement a reussi, false sinon */ -bool QETDiagramEditor::newDiagram() { - addDiagramView(new DiagramView(this)); - return(true); +bool QETDiagramEditor::saveAll() { + if (ProjectView *project_view = currentProject()) { + bool save_file = project_view -> saveAll(); + if (save_file) { + QETApp::projectsRecentFiles() -> fileWasOpened(project_view -> project() -> filePath()); + } + return(save_file); + } + return(false); +} + +/** + Cree un nouveau projet vide +*/ +bool QETDiagramEditor::newProject() { + // cree un nouveau projet sans schema + QETProject *new_project = new QETProject(0); + + // transmet les proprietes par defaut des nouveaux schemas + new_project -> setDefaultBorderProperties(defaultBorderProperties()); + new_project -> setDefaultConductorProperties(defaultConductorProperties()); + new_project -> setDefaultInsetProperties(defaultInsetProperties()); + + // ajoute un schema au projet + new_project -> addNewDiagram(); + + return(addProject(new_project)); } /** @@ -563,87 +680,305 @@ bool QETDiagramEditor::newDiagram() { */ bool QETDiagramEditor::openRecentFile(const QString &filepath) { if (qApp -> activeWindow() != this) return(false); - return(openAndAddDiagram(filepath)); + return(openAndAddProject(filepath)); } /** Cette fonction demande un nom de fichier a ouvrir a l'utilisateur @return true si l'ouverture a reussi, false sinon */ -bool QETDiagramEditor::openDiagram() { - // demande un nom de fichier a ouvrir a l'utilisateur - QString nom_fichier = QFileDialog::getOpenFileName( +bool QETDiagramEditor::openProject() { + // demande un chemin de fichier a ouvrir a l'utilisateur + QString filepath = QFileDialog::getOpenFileName( this, tr("Ouvrir un fichier"), open_dialog_dir.absolutePath(), tr("Sch\351mas QElectroTech (*.qet);;Fichiers XML (*.xml);;Tous les fichiers (*)") ); + if (filepath.isEmpty()) return(false); - return(openAndAddDiagram(nom_fichier)); + // retient le dossier contenant le dernier projet ouvert + open_dialog_dir = QDir(filepath); + + // ouvre le fichier + return(openAndAddProject(filepath)); } /** - Cette methode ouvre un fichier. - @param nom_fichier Chemin du fichier a ouvrir + Ferme un projet + @param project_view Projet a fermer + @return true si la fermeture du projet a reussi, false sinon + Note : cette methode renvoie true si project est nul +*/ +bool QETDiagramEditor::closeProject(ProjectView *project_view) { + if (project_view) { + activateProject(project_view); + if (QMdiSubWindow *sub_window = subWindowForWidget(project_view)) { + return(sub_window -> close()); + } + } + return(true); +} + +/** + Ferme un projet + @param project projet a fermer + @return true si la fermeture du fichier a reussi, false sinon + Note : cette methode renvoie true si project est nul +*/ +bool QETDiagramEditor::closeProject(QETProject *project) { + if (ProjectView *project_view = findProject(project)) { + return(closeProject(project_view)); + } + return(true); +} + +/** + Ferme le projet courant + @return true si la fermeture du fichier a reussi, false sinon + Note : cette methode renvoie true s'il n'y a pas de projet courant +*/ +bool QETDiagramEditor::closeCurrentProject() { + if (ProjectView *project_view = currentProject()) { + return(closeProject(project_view)); + } + return(true); +} + +/** + Ouvre un projet depuis un fichier et l'ajoute a cet editeur + @param filepath Chemin du projet a ouvrir + @param interactive true pour afficher des messages a l'utilisateur, false sinon @return true si l'ouverture a reussi, false sinon */ -bool QETDiagramEditor::openAndAddDiagram(const QString &nom_fichier) { - if (nom_fichier.isEmpty()) return(false); +bool QETDiagramEditor::openAndAddProject(const QString &filepath, bool interactive) { + if (filepath.isEmpty()) return(false); - open_dialog_dir = QDir(nom_fichier); - // verifie que le fichier n'est pas deja ouvert dans un editeur - if (QETDiagramEditor *diagram_editor = QETApp::diagramEditorForFile(nom_fichier)) { + QFileInfo filepath_info(filepath); + // verifie que le projet n'est pas deja ouvert dans un editeur + QString my_filepath = filepath_info.canonicalFilePath(); + if (QETDiagramEditor *diagram_editor = QETApp::diagramEditorForFile(filepath)) { if (diagram_editor == this) { - if (DiagramView *diagram_view = viewForFile(nom_fichier)) { - workspace.setActiveWindow(diagram_view); + if (ProjectView *project_view = viewForFile(filepath)) { + activateWidget(project_view); show(); activateWindow(); } return(false); } else { // demande a l'autre editeur d'afficher le fichier - return(diagram_editor -> openAndAddDiagram(nom_fichier)); + return(diagram_editor -> openAndAddProject(filepath)); } } - // ouvre le fichier - DiagramView *sv = new DiagramView(this); - int code_erreur; - if (sv -> open(nom_fichier, &code_erreur)) { - addDiagramView(sv); - activateWindow(); - QETApp::projectsRecentFiles() -> fileWasOpened(nom_fichier); - return(true); - } else { - QString message_erreur; - switch(code_erreur) { - case 1: message_erreur = tr("Ce fichier n'existe pas."); break; - case 2: message_erreur = tr("Impossible de lire ce fichier."); break; - case 3: message_erreur = tr("Ce fichier n'est pas un document XML valide."); break; - case 4: message_erreur = tr("Une erreur s'est produite lors de l'ouverture du fichier."); break; + // verifie que le fichier est accessible en lecture + if (!filepath_info.isReadable()) { + if (interactive) { + QMessageBox::critical( + this, + tr("Impossible d'ouvrir le fichier"), + tr("Il semblerait que le fichier que vous essayez d'ouvrir ne " + "soit pas accessible en lecture. Il est donc impossible de " + "l'ouvrir. Veuillez v\351rifier les permissions du fichier.") + ); } - activateWindow(); - QMessageBox::warning(this, tr("Erreur"), message_erreur); - delete sv; return(false); } + + // gere le fait que le fichier puisse etre en lecture seule + if (!filepath_info.isWritable()) { + if (interactive) { + QMessageBox::warning( + this, + tr("Ouverture du projet en lecture seule"), + tr("Il semblerait que le projet que vous essayez d'ouvrir ne " + "soit pas accessible en \351criture. Il sera donc ouvert en " + "lecture seule.") + ); + } + } + + // cree le projet a partir du fichier + QETProject *project = new QETProject(filepath); + if (project -> state() != QETProject::Ok) { + if (interactive) { + QMessageBox::warning( + this, + tr("\311chec de l'ouverture du projet", "message box title"), + QString( + tr( + "Il semblerait que le fichier %1 ne soit pas un fichier" + " projet QElectroTech. Il ne peut donc \352tre ouvert.", + "message box content" + ) + ).arg(filepath) + ); + } + return(false); + } + + // a ce stade, l'ouverture du fichier a reussi + // on l'ajoute a la liste des fichiers recents + QETApp::projectsRecentFiles() -> fileWasOpened(filepath); + // ... et on l'ajoute dans l'application + return(addProject(project)); +} + +/** + Ajoute un projet + @param project projet a ajouter +*/ +bool QETDiagramEditor::addProject(QETProject *project) { + // enregistre le projet + QETApp::registerProject(project); + + // cree un ProjectView pour visualiser le projet + ProjectView *project_view = new ProjectView(project); + addProjectView(project_view); + + // met a jour le panel d'elements + pa -> elementsPanel().projectWasOpened(project); + + return(true); +} + +/** + @return la liste des projets ouverts dans cette fenetre +*/ +QList QETDiagramEditor::openedProjects() const { + QList result; + QList window_list(workspace.subWindowList()); + foreach(QMdiSubWindow *window, window_list) { + if (ProjectView *project_view = qobject_cast(window -> widget())) { + result << project_view; + } + } + return(result); +} + +/** + @return Le projet actuellement edite (= qui a le focus dans l'interface + MDI) ou 0 s'il n'y en a pas +*/ +ProjectView *QETDiagramEditor::currentProject() const { + QMdiSubWindow *current_window = workspace.activeSubWindow(); + if (!current_window) return(0); + + QWidget *current_widget = current_window -> widget(); + if (!current_widget) return(0); + + if (ProjectView *project_view = qobject_cast(current_widget)) { + return(project_view); + } + return(0); +} + +/** + @return Le schema actuellement edite (= l'onglet ouvert dans le projet + courant) ou 0 s'il n'y en a pas +*/ +DiagramView *QETDiagramEditor::currentDiagram() const { + if (ProjectView *project_view = currentProject()) { + return(project_view -> currentDiagram()); + } + return(0); +} + +/** + Cette methode permet de retrouver le projet contenant un schema donne. + @param diagram_view Schema dont il faut retrouver + @return la vue sur le projet contenant ce schema ou 0 s'il n'y en a pas +*/ +ProjectView *QETDiagramEditor::findProject(DiagramView *diagram_view) const { + foreach(ProjectView *project_view, openedProjects()) { + if (project_view -> diagrams().contains(diagram_view)) { + return(project_view); + } + } + return(0); +} + +/** + Cette methode permet de retrouver le projet contenant un schema donne. + @param diagram Schema dont il faut retrouver + @return la vue sur le projet contenant ce schema ou 0 s'il n'y en a pas +*/ +ProjectView *QETDiagramEditor::findProject(Diagram *diagram) const { + foreach(ProjectView *project_view, openedProjects()) { + foreach(DiagramView *diagram_view, project_view -> diagrams()) { + if (diagram_view -> diagram() == diagram) { + return(project_view); + } + } + } + return(0); +} + +/** + @param project Projet dont il faut trouver la vue + @return la vue du projet passe en parametre +*/ +ProjectView *QETDiagramEditor::findProject(QETProject *project) const { + foreach(ProjectView *opened_project, openedProjects()) { + if (opened_project -> project() == project) { + return(opened_project); + } + } + return(0); +} + +/** + @param filepath Chemin de fichier d'un projet + @return le ProjectView correspondant au chemin passe en parametre, ou 0 si + celui-ci n'a pas ete trouve +*/ +ProjectView *QETDiagramEditor::findProject(const QString &filepath) const { + foreach(ProjectView *opened_project, openedProjects()) { + if (QETProject *project = opened_project -> project()) { + if (project -> filePath() == filepath) { + return(opened_project); + } + } + } + return(0); +} + +/** + @param widget Widget a rechercher dans la zone MDI + @return La sous-fenetre accueillant le widget passe en parametre, ou 0 si + celui-ci n'a pas ete trouve. +*/ +QMdiSubWindow *QETDiagramEditor::subWindowForWidget(QWidget *widget) const { + foreach(QMdiSubWindow *sub_window, workspace.subWindowList()) { + if (sub_window -> widget() == widget) { + return(sub_window); + } + } + return(0); +} + +/** + @param widget Widget a activer +*/ +void QETDiagramEditor::activateWidget(QWidget *widget) { + QMdiSubWindow *sub_window = subWindowForWidget(widget); + if (sub_window) { + workspace.setActiveSubWindow(sub_window); + } } /** - Ferme le document courant - @return true si la fermeture du fichier a reussi, false sinon + @param project_view Projet concerne + @param from Index de l'onglet avant le deplacement + @param to Index de l'onglet apres le deplacement */ -bool QETDiagramEditor::closeDiagram() { - DiagramView *sv = currentDiagram(); - if (!sv) return(false); - return(sv -> close()); -} - -/** - @return Le DiagramView qui a le focus dans l'interface MDI -*/ -DiagramView *QETDiagramEditor::currentDiagram() const { - return(qobject_cast(workspace.activeWindow())); +void QETDiagramEditor::diagramOrderChanged(ProjectView *project_view, int from, int to) { + if (!project_view) return; + + QETProject *project = project_view -> project(); + if (!project) return; + + pa -> elementsPanel().diagramOrderChanged(project, from, to); } /** @@ -748,38 +1083,51 @@ void QETDiagramEditor::slot_setVisualisationMode() { gere les actions */ void QETDiagramEditor::slot_updateActions() { - DiagramView *sv = currentDiagram(); - bool opened_document = (sv != 0); + DiagramView *dv = currentDiagram(); + ProjectView *pv = currentProject(); + bool opened_project = pv; + bool opened_diagram = dv; + bool editable_project = (pv && !pv -> project() -> isReadOnly()); + bool editable_diagram = (dv && !dv -> diagram() -> isReadOnly()); // actions ayant juste besoin d'un document ouvert - close_file -> setEnabled(opened_document); - save_file -> setEnabled(opened_document); - save_file_sous -> setEnabled(opened_document); - import_diagram -> setEnabled(opened_document); - export_diagram -> setEnabled(opened_document); - print -> setEnabled(opened_document); - select_all -> setEnabled(opened_document); - select_nothing -> setEnabled(opened_document); - select_invert -> setEnabled(opened_document); - zoom_in -> setEnabled(opened_document); - zoom_out -> setEnabled(opened_document); - zoom_fit -> setEnabled(opened_document); - zoom_reset -> setEnabled(opened_document); - conductor_default-> setEnabled(opened_document); - infos_diagram -> setEnabled(opened_document); - add_text -> setEnabled(opened_document); - add_column -> setEnabled(opened_document); - remove_column -> setEnabled(opened_document); - add_row -> setEnabled(opened_document); - remove_row -> setEnabled(opened_document); + close_file -> setEnabled(opened_project); + save_file -> setEnabled(opened_project && editable_project); + save_file_sous -> setEnabled(opened_project); + save_all -> setEnabled(opened_diagram && editable_diagram); + prj_edit_prop -> setEnabled(editable_project); + prj_add_diagram -> setEnabled(editable_project); + prj_del_diagram -> setEnabled(editable_project); + prj_clean -> setEnabled(editable_project); + import_diagram -> setEnabled(editable_project); + export_diagram -> setEnabled(opened_diagram); + print -> setEnabled(opened_diagram); + select_all -> setEnabled(opened_diagram); + select_nothing -> setEnabled(opened_diagram); + select_invert -> setEnabled(opened_diagram); + zoom_in -> setEnabled(opened_diagram); + zoom_out -> setEnabled(opened_diagram); + zoom_fit -> setEnabled(opened_diagram); + zoom_reset -> setEnabled(opened_diagram); + conductor_default -> setEnabled(editable_diagram); + infos_diagram -> setEnabled(editable_diagram); + add_text -> setEnabled(editable_diagram); + add_column -> setEnabled(editable_diagram); + remove_column -> setEnabled(editable_diagram); + add_row -> setEnabled(editable_diagram); + remove_row -> setEnabled(editable_diagram); // affiche les actions correspondant au diagram view en cours - if (sv) undo_group.setActiveStack(&(sv -> diagram() -> undoStack())); - else { + if (dv) { + if (can_update_actions) { + undo_group.setActiveStack(&(dv -> diagram() -> undoStack())); + } + } else { undo -> setEnabled(false); redo -> setEnabled(false); } + slot_updateFullScreenAction(); slot_updateModeActions(); slot_updatePasteAction(); slot_updateComplexActions(); @@ -791,23 +1139,38 @@ void QETDiagramEditor::slot_updateActions() { */ void QETDiagramEditor::slot_updateComplexActions() { DiagramView *dv = currentDiagram(); - bool opened_document = (dv != 0); + bool editable_diagram = (dv && !dv -> diagram() -> isReadOnly()); // nombre de conducteurs selectionnes - int selected_conductors_count = opened_document ? dv -> diagram() -> selectedConductors().count() : 0; - conductor_prop -> setEnabled(opened_document && selected_conductors_count == 1); - conductor_reset -> setEnabled(opened_document && selected_conductors_count); + int selected_conductors_count = dv ? dv -> diagram() -> selectedConductors().count() : 0; + conductor_prop -> setEnabled(editable_diagram && selected_conductors_count == 1); + conductor_reset -> setEnabled(editable_diagram && selected_conductors_count); // actions ayant aussi besoin d'elements selectionnes - bool selected_elements = opened_document ? (dv -> hasSelectedItems()) : false; - cut -> setEnabled(selected_elements); + bool selected_elements = dv ? (dv -> hasSelectedItems()) : false; + cut -> setEnabled(editable_diagram && selected_elements); copy -> setEnabled(selected_elements); - delete_selection -> setEnabled(selected_elements); - rotate_selection -> setEnabled(selected_elements); + delete_selection -> setEnabled(editable_diagram && selected_elements); + rotate_selection -> setEnabled(editable_diagram && selected_elements); } /** - Gere les actions realtives au mode du schema + Gere l'action permettant de passer en plein ecran ou d'en sortir +*/ +void QETDiagramEditor::slot_updateFullScreenAction() { + if (windowState() & Qt::WindowFullScreen) { + fullscreen -> setText(tr("Sortir du &mode plein \351cran")); + fullscreen -> setIcon(QIcon(":/ico/sortir_fs.png")); + fullscreen -> setStatusTip(tr("Affiche QElectroTech en mode fen\352tr\351", "status bar tip")); + } else { + fullscreen -> setText(tr("Passer en &mode plein \351cran")); + fullscreen -> setIcon(QIcon(":/ico/entrer_fs.png")); + fullscreen -> setStatusTip(tr("Affiche QElectroTech en mode plein \351cran", "status bar tip")); + } +} + +/** + Gere les actions relatives au mode du schema */ void QETDiagramEditor::slot_updateModeActions() { DiagramView *dv = currentDiagram(); @@ -836,45 +1199,59 @@ void QETDiagramEditor::slot_updateModeActions() { Gere les actions ayant besoin du presse-papier */ void QETDiagramEditor::slot_updatePasteAction() { + DiagramView *dv = currentDiagram(); + bool editable_diagram = (dv && !dv -> diagram() -> isReadOnly()); + // pour coller, il faut un schema ouvert et un schema dans le presse-papier - paste -> setEnabled(currentDiagram() && Diagram::clipboardMayContainDiagram()); + paste -> setEnabled(editable_diagram && Diagram::clipboardMayContainDiagram()); } /** - Ajoute un schema dans l'espace de travail - @param dv L'objet DiagramView a ajouter a l'espace de travail + Ajoute un projet dans l'espace de travail + @param project_view Le projet a ajouter dans l'espace de travail */ -void QETDiagramEditor::addDiagramView(DiagramView *dv) { - if (!dv) return; - undo_group.addStack(&(dv -> diagram() -> undoStack())); +void QETDiagramEditor::addProjectView(ProjectView *project_view) { + if (!project_view) return; // on maximise la nouvelle fenetre si la fenetre en cours est inexistante ou bien maximisee - DiagramView *d_v = currentDiagram(); - bool maximise = ((!d_v) || (d_v -> windowState() & Qt::WindowMaximized)); + QWidget *current_window = workspace.activeSubWindow(); + bool maximise = ((!current_window) || (current_window -> windowState() & Qt::WindowMaximized)); // ajoute la fenetre - QWidget *p = workspace.addWindow(dv); - connect(dv -> diagram(), SIGNAL(selectionChanged()), this, SLOT(slot_updateComplexActions())); - connect(dv, SIGNAL(modeChanged()), this, SLOT(slot_updateModeActions())); - connect(dv, SIGNAL(textAdded(bool)), add_text, SLOT(setChecked(bool))); - connect(dv, SIGNAL(destroyed(QObject *)), this, SLOT(slot_updateWindowsMenu())); + QMdiSubWindow *sub_window = workspace.addSubWindow(project_view); + sub_window -> setWindowIcon(project_view -> windowIcon()); + + // lie les schemas du projet a l'editeur : + // quand on change de schemas a l'interieur d'un projet, on met a jour les menus + connect(project_view, SIGNAL(diagramActivated(DiagramView *)), this, SLOT(slot_updateWindowsMenu())); + connect(project_view, SIGNAL(diagramActivated(DiagramView *)), this, SLOT(slot_updateActions())); + foreach(DiagramView *dv, project_view -> diagrams()) { + diagramWasAdded(dv); + } + + // gere la fermeture du projet + connect(project_view, SIGNAL(projectClosed(ProjectView*)), this, SLOT(projectWasClosed(ProjectView *))); + + // gere l'ajout et le retrait de schema du projet + connect(project_view, SIGNAL(diagramAdded(DiagramView *)), this, SLOT(diagramWasAdded(DiagramView *))); + connect(project_view, SIGNAL(diagramAdded(DiagramView *)), this, SLOT(slot_updateActions())); + connect(project_view, SIGNAL(diagramAboutToBeRemoved(DiagramView *)), this, SLOT(diagramIsAboutToBeRemoved(DiagramView *))); + connect(project_view, SIGNAL(diagramRemoved(DiagramView *)), this, SLOT(diagramWasRemoved(DiagramView *))); + connect(project_view, SIGNAL(diagramRemoved(DiagramView *)), this, SLOT(slot_updateActions())); + if (QETProject *project = project_view -> project()) { + connect(project, SIGNAL(diagramAdded (QETProject *, Diagram*)), &(pa -> elementsPanel()), SLOT(diagramWasAdded (QETProject *, Diagram*))); + connect(project, SIGNAL(diagramRemoved(QETProject *, Diagram*)), &(pa -> elementsPanel()), SLOT(diagramWasRemoved(QETProject *, Diagram*))); + + // on met aussi les menus a jour quand un projet passe en lecture seule ou non + connect(project, SIGNAL(readOnlyChanged(QETProject *, bool)), this, SLOT(slot_updateActions())); + } + + // gere les changements de l'ordre des schemas dans le projet + connect(project_view, SIGNAL(diagramOrderChanged(ProjectView *, int, int)), this, SLOT(diagramOrderChanged(ProjectView *, int, int))); // affiche la fenetre - if (maximise) p -> showMaximized(); - else p -> show(); -} - -/** - @return la liste des schemas edites par cet editeur de schemas -*/ -QList QETDiagramEditor::diagramViews() const { - QList diagram_views_list; - foreach (QWidget *window, workspace.windowList()) { - if (DiagramView *diagram_view = qobject_cast(window)) { - diagram_views_list << diagram_view; - } - } - return(diagram_views_list); + if (maximise) project_view -> showMaximized(); + else project_view -> show(); } /** @@ -882,8 +1259,8 @@ QList QETDiagramEditor::diagramViews() const { */ QList QETDiagramEditor::editedFiles() const { QList edited_files_list; - foreach (DiagramView *diagram_view, diagramViews()) { - QString diagram_file(diagram_view -> file_name); + foreach (ProjectView *project_view, openedProjects()) { + QString diagram_file(project_view -> project() -> filePath()); if (!diagram_file.isEmpty()) { edited_files_list << QFileInfo(diagram_file).canonicalFilePath(); } @@ -894,17 +1271,17 @@ QList QETDiagramEditor::editedFiles() const { /** @param filepath Un chemin de fichier Note : si filepath est une chaine vide, cette methode retourne 0. - @return le DiagramView editant le fichier filepath, ou 0 si ce fichier n'est + @return le ProjectView editant le fichier filepath, ou 0 si ce fichier n'est pas edite par cet editeur de schemas. */ -DiagramView *QETDiagramEditor::viewForFile(const QString &filepath) const { +ProjectView *QETDiagramEditor::viewForFile(const QString &filepath) const { if (filepath.isEmpty()) return(0); QString searched_can_file_path = QFileInfo(filepath).canonicalFilePath(); - foreach (DiagramView *diagram_view, diagramViews()) { - QString diagram_can_file_path = QFileInfo(diagram_view -> file_name).canonicalFilePath(); - if (diagram_can_file_path == searched_can_file_path) { - return(diagram_view); + foreach (ProjectView *project_view, openedProjects()) { + QString project_can_file_path = QFileInfo(project_view -> project() -> filePath()).canonicalFilePath(); + if (project_can_file_path == searched_can_file_path) { + return(project_view); } } return(0); @@ -925,7 +1302,6 @@ void QETDiagramEditor::slot_updateWindowsMenu() { windows_menu -> addSeparator(); windows_menu -> addAction(tile_window); windows_menu -> addAction(cascade_window); - windows_menu -> addAction(arrange_window); // actions de deplacement entre les fenetres windows_menu -> addSeparator(); @@ -933,36 +1309,56 @@ void QETDiagramEditor::slot_updateWindowsMenu() { windows_menu -> addAction(prev_window); // liste des fenetres - QList windows = workspace.windowList(); + QList windows = openedProjects(); - tile_window -> setEnabled(!windows.isEmpty()); - cascade_window -> setEnabled(!windows.isEmpty()); - arrange_window -> setEnabled(!windows.isEmpty()); + tile_window -> setEnabled(!windows.isEmpty() && workspace.viewMode() == QMdiArea::SubWindowView); + cascade_window -> setEnabled(!windows.isEmpty() && workspace.viewMode() == QMdiArea::SubWindowView); next_window -> setEnabled(windows.count() > 1); prev_window -> setEnabled(windows.count() > 1); if (!windows.isEmpty()) windows_menu -> addSeparator(); QActionGroup *windows_actions = new QActionGroup(this); - for (int i = 0 ; i < windows.size() ; ++ i) { - DiagramView *dv = qobject_cast(windows.at(i)); - if (!dv) continue; - QString dv_title = dv -> windowTitle().left(dv -> windowTitle().length() - 3); - QAction *action = windows_menu -> addAction(dv_title); + foreach(ProjectView *project_view, windows) { + QString pv_title = project_view -> windowTitle(); + QAction *action = windows_menu -> addAction(pv_title); windows_actions -> addAction(action); - action -> setStatusTip(tr("Active la fen\352tre ") + dv_title); + action -> setStatusTip(QString(tr("Active la fen\352tre %1")).arg(pv_title)); action -> setCheckable(true); - action -> setChecked(dv == currentDiagram()); + action -> setChecked(project_view == currentProject()); connect(action, SIGNAL(triggered()), &windowMapper, SLOT(map())); - windowMapper.setMapping(action, dv); + windowMapper.setMapping(action, project_view); } } /** Edite les informations du schema en cours */ -void QETDiagramEditor::slot_editInfos() { - if (DiagramView *dv = currentDiagram()) { - dv -> dialogEditInfos(); +void QETDiagramEditor::editCurrentDiagramProperties() { + if (ProjectView *project_view = currentProject()) { + activateProject(project_view); + project_view -> editCurrentDiagramProperties(); + } +} + +/** + Edite les proprietes du schema diagram + @param diagram_view schema dont il faut editer les proprietes +*/ +void QETDiagramEditor::editDiagramProperties(DiagramView *diagram_view) { + if (ProjectView *project_view = findProject(diagram_view)) { + activateProject(project_view); + project_view -> editDiagramProperties(diagram_view); + } +} + +/** + Edite les proprietes du schema diagram + @param diagram schema dont il faut editer les proprietes +*/ +void QETDiagramEditor::editDiagramProperties(Diagram *diagram) { + if (ProjectView *project_view = findProject(diagram)) { + activateProject(project_view); + project_view -> editDiagramProperties(diagram); } } @@ -1038,6 +1434,24 @@ void QETDiagramEditor::slot_addText() { } } +/** + Affiche les projets dans des fenetres. +*/ +void QETDiagramEditor::setWindowedMode() { + workspace.setViewMode(QMdiArea::SubWindowView); + windowed_view_mode -> setChecked(true); + slot_updateWindowsMenu(); +} + +/** + Affiche les projets dans des onglets. +*/ +void QETDiagramEditor::setTabbedMode() { + workspace.setViewMode(QMdiArea::TabbedView); + tabbed_view_mode -> setChecked(true); + slot_updateWindowsMenu(); +} + /// Lit les parametres de l'editeur de schemas void QETDiagramEditor::readSettings() { QSettings &settings = QETApp::settings(); @@ -1049,6 +1463,14 @@ void QETDiagramEditor::readSettings() { // etat de la fenetre (barres d'outils, docks...) QVariant state = settings.value("diagrameditor/state"); if (state.isValid()) restoreState(state.toByteArray()); + + // gestion des projets (onglets ou fenetres) + bool tabbed = settings.value("diagrameditor/viewmode", "windowed") == "tabbed"; + if (tabbed) { + setTabbedMode(); + } else { + setWindowedMode(); + } } /// Enregistre les parametres de l'editeur de schemas @@ -1058,6 +1480,189 @@ void QETDiagramEditor::writeSettings() { settings.setValue("diagrameditor/state", saveState()); } +/** + Active le schema passe en parametre + @param diagram Schema a activer +*/ +void QETDiagramEditor::activateDiagram(Diagram *diagram) { + if (QETProject *project = diagram -> project()) { + if (ProjectView *project_view = findProject(project)) { + activateWidget(project_view); + project_view -> showDiagram(diagram); + } + } else { + /// @todo gerer ce cas + } +} + +/** + Active le projet passe en parametre + @param project Projet a activer +*/ +void QETDiagramEditor::activateProject(QETProject *project) { + activateProject(findProject(project)); +} + +/** + Active le projet passe en parametre + @param project_view Projet a activer +*/ +void QETDiagramEditor::activateProject(ProjectView *project_view) { + if (!project_view) return; + activateWidget(project_view); +} + +/** + Gere la fermeture d'une ProjectView + @param project_view ProjectView fermee +*/ +void QETDiagramEditor::projectWasClosed(ProjectView *project_view) { + QETProject *project = project_view -> project(); + if (project) { + pa -> elementsPanel().projectWasClosed(project); + QETApp::unregisterProject(project); + } + project_view -> deleteLater(); + project -> deleteLater(); +} + +/** + Edite les proprietes du projet courant. +*/ +void QETDiagramEditor::editCurrentProjectProperties() { + editProjectProperties(currentProject()); +} + +/** + Edite les proprietes du projet project_view. + @param project_view Vue sur le projet dont il faut editer les proprietes +*/ +void QETDiagramEditor::editProjectProperties(ProjectView *project_view) { + if (!project_view) return; + activateProject(project_view); + project_view -> editProjectProperties(); +} + +/** + Edite les proprietes du projet project. + @param project Projet dont il faut editer les proprietes +*/ +void QETDiagramEditor::editProjectProperties(QETProject *project) { + editProjectProperties(findProject(project)); +} + +/** + Ajoute un nouveau schema au projet courant +*/ +void QETDiagramEditor::addDiagramToProject() { + if (ProjectView *current_project = currentProject()) { + current_project -> addNewDiagram(); + } +} + +/** + Ajoute un nouveau schema a un projet + @param project Projet auquel il faut ajouter un schema +*/ +void QETDiagramEditor::addDiagramToProject(QETProject *project) { + if (!project) return; + + // recupere le ProjectView visualisant ce projet + if (ProjectView *project_view = findProject(project)) { + + // affiche le projet en question + activateProject(project); + + // ajoute un schema au projet + project_view -> addNewDiagram(); + } +} + +/** + Supprime un schema de son projet + @param diagram Schema a supprimer +*/ +void QETDiagramEditor::removeDiagram(Diagram *diagram) { + if (!diagram) return; + + // recupere le projet contenant le schema + if (QETProject *diagram_project = diagram -> project()) { + // recupere la vue sur ce projet + if (ProjectView *project_view = findProject(diagram_project)) { + + // affiche le schema en question + project_view -> showDiagram(diagram); + + // supprime le schema + project_view -> removeDiagram(diagram); + } + } +} + +/** + Nettoie le projet courant +*/ +void QETDiagramEditor::cleanCurrentProject() { + if (ProjectView *current_project = currentProject()) { + int clean_count = current_project -> cleanProject(); + if (clean_count) pa -> reloadAndFilter(); + } +} + +/** + Supprime le schema courant du projet courant +*/ +void QETDiagramEditor::removeDiagramFromProject() { + if (ProjectView *current_project = currentProject()) { + if (DiagramView *current_diagram = current_project -> currentDiagram()) { + can_update_actions = false; + current_project -> removeDiagram(current_diagram); + } + } +} + +/** + Gere l'ajout d'un schema dans un projet + @param dv DiagramView concerne +*/ +void QETDiagramEditor::diagramWasAdded(DiagramView *dv) { + // quand on change qqc a l'interieur d'un schema, on met a jour les menus + undo_group.addStack(&(dv -> diagram() -> undoStack())); + connect(dv -> diagram(), SIGNAL(selectionChanged()), this, SLOT(slot_updateComplexActions())); + connect(dv, SIGNAL(modeChanged()), this, SLOT(slot_updateModeActions())); + connect(dv, SIGNAL(textAdded(bool)), add_text, SLOT(setChecked(bool))); + connect(dv, SIGNAL(titleChanged(DiagramView *, const QString &)), this, SLOT(diagramTitleChanged(DiagramView *))); +} + +/** + Gere le retrait d'un schema dans un projet avant que le retrait ne soit effectif + @param dv DiagramView concerne +*/ +void QETDiagramEditor::diagramIsAboutToBeRemoved(DiagramView *dv) { + undo_group.removeStack(&(dv -> diagram() -> undoStack())); + can_update_actions = false; +} + +/** + Gere le retrait d'un schema dans un projet apres que le retrait soit effectif + @param dv DiagramView concerne +*/ +void QETDiagramEditor::diagramWasRemoved(DiagramView *) { + can_update_actions = true; +} + +/** + Gere le changement de titre d'un schema dans un projet + @param dv DiagramView concerne +*/ +void QETDiagramEditor::diagramTitleChanged(DiagramView *dv) { + if (Diagram *diagram = dv -> diagram()) { + if (QETProject *project = diagram -> project()) { + pa -> elementsPanel().diagramTitleChanged(project, diagram); + } + } +} + /** Permet a l'utilisateur de configurer QET en lancant un dialogue approprie. @see ConfigDialog @@ -1075,22 +1680,8 @@ InsetProperties QETDiagramEditor::defaultInsetProperties() { QSettings &settings = QETApp::settings(); InsetProperties def; - def.title = settings.value("diagrameditor/defaulttitle").toString(); - def.author = settings.value("diagrameditor/defaultauthor").toString(); - def.filename = settings.value("diagrameditor/defaultfilename").toString(); - def.folio = settings.value("diagrameditor/defaultfolio").toString(); - - QString settings_date = settings.value("diagrameditor/defaultdate").toString(); - if (settings_date == "now") { - def.date = QDate::currentDate(); - def.useDate = InsetProperties::CurrentDate; - } else if (settings_date.isEmpty() || settings_date == "null") { - def.date = QDate(); - def.useDate = InsetProperties::UseDateValue; - } else { - def.date = QDate::fromString(settings_date, "yyyyMMdd"); - def.useDate = InsetProperties::UseDateValue; - } + // lit le cartouche par defaut dans la configuration + def.fromSettings(settings, "diagrameditor/default"); return(def); } @@ -1103,15 +1694,22 @@ BorderProperties QETDiagramEditor::defaultBorderProperties() { QSettings &settings = QETApp::settings(); BorderProperties def; - def.columns_count = settings.value("diagrameditor/defaultcols", 17).toInt(); - def.columns_width = qRound(settings.value("diagrameditor/defaultcolsize", 60.0).toDouble()); - def.columns_header_height = 20.0; - def.display_columns = settings.value("diagrameditor/defaultdisplaycols", true).toBool(); - - def.rows_count = settings.value("diagrameditor/defaultrows", 8).toInt(); - def.rows_height = qRound(settings.value("diagrameditor/defaultrowsize", 80.0).toDouble()); - def.rows_header_width = 20.0; - def.display_rows = settings.value("diagrameditor/defaultdisplayrows", true).toBool(); + // lit les dimensions par defaut dans la configuration + def.fromSettings(settings, "diagrameditor/default"); + + return(def); +} + +/** + @return Les proprietes par defaut d'un conducteur +*/ +ConductorProperties QETDiagramEditor::defaultConductorProperties() { + // accede a la configuration de l'application + QSettings &settings = QETApp::settings(); + + ConductorProperties def; + // lit les caracteristiques des conducteurs par defaut dans la configuration + def.fromSettings(settings, "diagrameditor/defaultconductor"); return(def); } diff --git a/sources/qetdiagrameditor.h b/sources/qetdiagrameditor.h index 7a692332d..3a15fbf9c 100644 --- a/sources/qetdiagrameditor.h +++ b/sources/qetdiagrameditor.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -19,7 +19,11 @@ #define QET_DIAGRAM_EDITOR_H #include #include "borderproperties.h" +#include "conductorproperties.h" #include "insetproperties.h" +class QETProject; +class ProjectView; +class Diagram; class DiagramView; class ElementsPanelWidget; class RecentFiles; @@ -44,18 +48,30 @@ class QETDiagramEditor : public QMainWindow { // methodes public: void closeEvent(QCloseEvent *); - void addDiagramView(DiagramView *); - QList diagramViews() const; + QList openedProjects() const; + void addProjectView(ProjectView *); + bool openAndAddProject(const QString &, bool interactive = true); + QList projectViews() const; QList editedFiles() const; - DiagramView *viewForFile(const QString &) const; + ProjectView *viewForFile(const QString &) const; static InsetProperties defaultInsetProperties(); static BorderProperties defaultBorderProperties(); + static ConductorProperties defaultConductorProperties(); protected: void actions(); + virtual bool event(QEvent *); private: + bool addProject(QETProject *); + ProjectView *currentProject() const; DiagramView *currentDiagram() const; + ProjectView *findProject(DiagramView *) const; + ProjectView *findProject(Diagram *) const; + ProjectView *findProject(QETProject *) const; + ProjectView *findProject(const QString &) const; + QMdiSubWindow *subWindowForWidget(QWidget *) const; + void menus(); void toolbar(); @@ -67,12 +83,13 @@ class QETDiagramEditor : public QMainWindow { void exportDialog(); bool saveAsDialog(); bool save(); - bool newDiagram(); - bool openDiagram(); + bool saveAll(); + bool newProject(); + bool openProject(); bool openRecentFile(const QString &); - bool openAndAddDiagram(const QString &); - bool closeDiagram(); - void slot_editInfos(); + bool closeProject(ProjectView *); + bool closeProject(QETProject *); + bool closeCurrentProject(); void slot_cut(); void slot_copy(); void slot_paste(); @@ -88,6 +105,7 @@ class QETDiagramEditor : public QMainWindow { void slot_setSelectionMode(); void slot_setVisualisationMode(); void slot_updateActions(); + void slot_updateFullScreenAction(); void slot_updateModeActions(); void slot_updateComplexActions(); void slot_updatePasteAction(); @@ -100,13 +118,39 @@ class QETDiagramEditor : public QMainWindow { void slot_resetConductors(); void slot_editDefaultConductors(); void slot_addText(); + void setWindowedMode(); + void setTabbedMode(); void readSettings(); void writeSettings(); + void activateDiagram(Diagram *); + void activateProject(QETProject *); + void activateProject(ProjectView *); + void activateWidget(QWidget *); + void diagramOrderChanged(ProjectView *, int, int); + void projectWasClosed(ProjectView *); + void editCurrentProjectProperties(); + void editProjectProperties(ProjectView *); + void editProjectProperties(QETProject *); + void editCurrentDiagramProperties(); + void editDiagramProperties(DiagramView *); + void editDiagramProperties(Diagram *); + void addDiagramToProject(); + void addDiagramToProject(QETProject *); + void removeDiagram(Diagram *); + void removeDiagramFromProject(); + void cleanCurrentProject(); + void diagramWasAdded(DiagramView *); + void diagramIsAboutToBeRemoved(DiagramView *); + void diagramWasRemoved(DiagramView *); + void diagramTitleChanged(DiagramView *); // attributs public: // Actions faisables au travers de menus dans l'application QElectroTech QActionGroup *grp_visu_sel; ///< Groupe d'actions pour les modes (edition et visualisation) + QActionGroup *grp_view_mode; ///< Groupe d'actions pour l'affichage des projets (onglets ou fenetres) + QAction *tabbed_view_mode; ///< Passe les projets en mode onglets + QAction *windowed_view_mode; ///< Passe les projets en mode fenetre QAction *mode_selection; ///< Passe en mode edition QAction *mode_visualise; ///< Passe en mode visualisation QAction *new_file; ///< Cree un nouveau schema @@ -114,6 +158,7 @@ class QETDiagramEditor : public QMainWindow { QAction *close_file; ///< Ferme le fichier QAction *save_file; ///< Enregistre le fichier QAction *save_file_sous; ///< Enregistrer le fichier sous un nom donne + QAction *save_all; ///< Enregistre tous les schemas QAction *import_diagram; ///< Importe un schema existant (non implemente) QAction *export_diagram; ///< Exporte le schema sous forme d'image QAction *print; ///< Imprime le schema @@ -137,6 +182,10 @@ class QETDiagramEditor : public QMainWindow { QAction *remove_column; ///< Enleve une colonne du schema QAction *add_row; ///< Augmente la hauteur du schema QAction *remove_row; ///< Reduit la hauteur du schema + QAction *prj_edit_prop; ///< Edite les proprietes du projet + QAction *prj_add_diagram; ///< Ajoute un schema au projet + QAction *prj_del_diagram; ///< Supprime un schema du projet + QAction *prj_clean; ///< Nettoie un projet QAction *zoom_in; ///< Zoome avant QAction *zoom_out; ///< Zoome arriere QAction *zoom_fit; ///< Ajuste le zoom de facon a voir l'integralite des elements @@ -144,16 +193,14 @@ class QETDiagramEditor : public QMainWindow { QAction *about_qet; ///< Lance le dialogue "A propos de QElectroTech" QAction *about_qt; ///< Lance le dialogue "A propos de Qt" QAction *configure; ///< Lance le dialogue de configuration de QElectroTech - QAction *fullscreen_on; ///< Passe en mode plein ecran - QAction *fullscreen_off; ///< Sort du mode plein ecran + QAction *fullscreen; ///< Passe en mode plein ecran ou en sort QAction *tile_window; ///< Affiche les fenetre MDI en mosaique QAction *cascade_window; ///< Affiche les fenetres MDI en cascade - QAction *arrange_window; ///< Reorganise les fenetres MDI QAction *prev_window; ///< Affiche la fenetre MDI precedente QAction *next_window; ///< Affiche la fenetre MDI suivante private: - QWorkspace workspace; + QMdiArea workspace; QSignalMapper windowMapper; /// Dossier a utiliser pour Fichier > ouvrir QDir open_dialog_dir; @@ -168,5 +215,6 @@ class QETDiagramEditor : public QMainWindow { QToolBar *view_bar; QToolBar *diagram_bar; QUndoGroup undo_group; + bool can_update_actions; }; #endif diff --git a/sources/qetprintpreviewdialog.cpp b/sources/qetprintpreviewdialog.cpp new file mode 100644 index 000000000..dd831161e --- /dev/null +++ b/sources/qetprintpreviewdialog.cpp @@ -0,0 +1,359 @@ +#include "qetprintpreviewdialog.h" +#include "diagramschooser.h" + +/** + Constructeur + @param printer Imprimante a utiliser pour + @param widget Widget parent +*/ +QETPrintPreviewDialog::QETPrintPreviewDialog(QETProject *project, QPrinter *printer, QWidget *widget, Qt::WindowFlags f) : + QDialog(widget, f), + project_(project), + printer_(printer) +{ + setWindowTitle(tr("QElectroTech : Aper\347u avant impression")); + build(); + + connect(preview_, SIGNAL(paintRequested(QPrinter *)), this, SLOT(requestPaint(QPrinter *))); + connect(diagrams_list_, SIGNAL(selectionChanged()), preview_, SLOT(updatePreview())); + connect(diagrams_list_, SIGNAL(selectionChanged()), this, SLOT(checkDiagramsCount())); + + setWindowState(Qt::WindowMaximized); +} + +/** + Destructeur +*/ +QETPrintPreviewDialog::~QETPrintPreviewDialog() { +} + +/** + @return le widget permettant de choisir les schemas a imprimer. +*/ +DiagramsChooser *QETPrintPreviewDialog::diagramsChooser() { + return(diagrams_list_); +} + +/** + @return true si l'option "Adapter le schema a la page" est activee +*/ +bool QETPrintPreviewDialog::fitDiagramsToPages() const { + return(fit_diagram_to_page_ -> isChecked()); +} + +/** + Passe a la premiere page +*/ +void QETPrintPreviewDialog::firstPage() { + preview_ -> setCurrentPage(1); +} + +/** + Passe a la page precedente +*/ +void QETPrintPreviewDialog::previousPage() { + int preview_previous_page = preview_ -> currentPage() - 1; + preview_ -> setCurrentPage(qMax(preview_previous_page, 0)); +} + +/** + Passe a la page suivante +*/ +void QETPrintPreviewDialog::nextPage() { + int preview_next_page = preview_ -> currentPage() + 1; + preview_ -> setCurrentPage(qMin(preview_next_page, preview_ -> numPages())); +} + +/** + Passe a la derniere page +*/ +void QETPrintPreviewDialog::lastPage() { + preview_ -> setCurrentPage(preview_ -> numPages()); +} + +/** + Copnfigure la mise en page +*/ +void QETPrintPreviewDialog::pageSetup() { + QPageSetupDialog page_setup_dialog(printer_, this); + if (page_setup_dialog.exec() == QDialog::Accepted) { + preview_ -> updatePreview(); + updateZoomList(); + } +} + +/** + Utilise ou non toute la page sans teni compte des marges + @param full_page true pour utiliser toute la page, false sinon +*/ +void QETPrintPreviewDialog::useFullPage(bool full_page) { + printer_ -> setFullPage(full_page); + preview_ -> updatePreview(); + updateZoomList(); +} + +/** + Fait tenir ou non chaque schema sur une page + @param fit_diagram true pour adapter chaque schema sur une page, false sinon +*/ +void QETPrintPreviewDialog::fitDiagramToPage(bool /*fit_diagram*/) { + preview_ -> updatePreview(); + updateZoomList(); +} + +/** + Effectue l'action "zoom avant" sur l'apercu avant impression +*/ +void QETPrintPreviewDialog::zoomIn() { + preview_ -> zoomIn(4.0/3.0); + updateZoomList(); +} + +/** + Effectue l'action "zoom arriere" sur l'apercu avant impression +*/ +void QETPrintPreviewDialog::zoomOut() { + preview_ -> zoomOut(4.0/3.0); + updateZoomList(); +} + +/** + Met en place le dialogue +*/ +void QETPrintPreviewDialog::build() { + preview_ = new QPrintPreviewWidget(printer_); + diagrams_label_ = new QLabel(tr("Sch\351mas \340 imprimer\240:")); + diagrams_list_ = new DiagramsChooser(project_); + toggle_diagrams_list_ = new QAction(QIcon(":/ico/diagram.png"), tr("Cacher la liste des sch\351mas"), this); + toggle_print_options_ = new QAction(QIcon(":/ico/configure.png"), tr("Cacher les options d'impression"), this); + adjust_width_ = new QAction(QIcon(":/ico/view_fit_width.png"), tr("Ajuster la largeur"), this); + adjust_page_ = new QAction(QIcon(":/ico/view_fit_window.png"), tr("Ajuster la page"), this); + zoom_out_ = new QAction(QIcon(":/ico/viewmag-.png"), tr("Zoom arri\350re"), this); + zoom_box_ = new QComboBox(this); + zoom_in_ = new QAction(QIcon(":/ico/viewmag+.png"), tr("Zoom avant"), this); + landscape_ = new QAction(QIcon(":/ico/landscape.png"), tr("Paysage"), this); + portrait_ = new QAction(QIcon(":/ico/portrait.png"), tr("Portrait"), this); + first_page_ = new QAction(QIcon(":/ico/2leftarrow.png"), tr("Premi\350re page"), this); + previous_page_ = new QAction(QIcon(":/ico/1leftarrow.png"), tr("Page pr\351c\351dente"), this); + next_page_ = new QAction(QIcon(":/ico/1rightarrow.png"), tr("Page suivante"), this); + last_page_ = new QAction(QIcon(":/ico/2rightarrow.png"), tr("Derni\350re page"), this); + single_page_view_ = new QAction(QIcon(":/ico/single_page.png"), tr("Afficher une seule page"), this); + facing_pages_view_ = new QAction(QIcon(":/ico/two_pages.png"), tr("Afficher deux pages"), this); + all_pages_view_ = new QAction(QIcon(":/ico/all_pages.png"), tr("Afficher un aper\347u de toutes les pages"), this); + page_setup_ = new QAction(QIcon(":/ico/confprint.png"), tr("Mise en page"), this); + + toggle_diagrams_list_ -> setCheckable(true); + toggle_diagrams_list_ -> setChecked(true); + toggle_print_options_ -> setCheckable(true); + toggle_print_options_ -> setChecked(true); + +#ifdef Q_OS_WIN32 + /* + Sous Windows, le QPageSetupDialog utilise le dialogue natif ; ce + dernier ne peut gerer que les imprimantes physiques ("native + printers" ). + cf avertissement : QAbstractPageSetupDialog::QAbstractPageSetupDialog: + Page setup dialog cannot be used on non-native printers + */ + if (!(printer_ -> outputFileName().isEmpty())) { + page_setup_ -> setEnabled(false); + page_setup_ -> setText(tr("Mise en page (non disponible sous Windows pour l'impression PDF/PS)")); + } +#endif + + toolbar_ = new QToolBar(); + toolbar_ -> addAction(toggle_diagrams_list_); + toolbar_ -> addAction(toggle_print_options_); + toolbar_ -> addSeparator(); + toolbar_ -> addAction(adjust_width_); + toolbar_ -> addAction(adjust_page_); + toolbar_ -> addAction(zoom_out_); + toolbar_ -> addWidget(zoom_box_); + toolbar_ -> addAction(zoom_in_); + toolbar_ -> addSeparator(); + toolbar_ -> addAction(landscape_); + toolbar_ -> addAction(portrait_); + toolbar_ -> addSeparator(); + toolbar_ -> addAction(first_page_); + toolbar_ -> addAction(previous_page_); + toolbar_ -> addAction(next_page_); + toolbar_ -> addAction(last_page_); + toolbar_ -> addSeparator(); + toolbar_ -> addAction(single_page_view_); + toolbar_ -> addAction(facing_pages_view_); + toolbar_ -> addAction(all_pages_view_); + toolbar_ -> addSeparator(); + toolbar_ -> addAction(page_setup_); + + print_options_box_= new QGroupBox(tr("Options d'impression")); + use_full_page_ = new QCheckBox(tr("Utiliser toute la feuille")); + use_full_page_label_ = new QLabel(tr( + "Si cette option est coch\351e, les marges de la feuille seront " + "ignor\351es et toute sa surface sera utilis\351e pour l'impression. " + "Cela peut ne pas \352tre support\351 par votre imprimante." + )); + use_full_page_label_ -> setWordWrap(true); + use_full_page_label_ -> setContentsMargins(20, 0, 0, 0); + fit_diagram_to_page_ = new QCheckBox(tr("Adapter le sch\351ma \340 la page")); + fit_diagram_to_page_label_ = new QLabel(tr( + "Si cette option est coch\351e, le sch\351ma sera agrandi ou " + "r\351tr\351ci de fa\347on \340 remplir toute la surface imprimable " + "d'une et une seule page." + )); + fit_diagram_to_page_label_ -> setWordWrap(true); + fit_diagram_to_page_label_ -> setContentsMargins(20, 0, 0, 0); + fit_diagram_to_page_ -> setChecked(true); + + buttons_ = new QDialogButtonBox(); + buttons_ -> addButton(new QPushButton(QIcon(":/ico/print.png"), tr("Imprimer")), QDialogButtonBox::AcceptRole); + buttons_ -> addButton(QDialogButtonBox::Cancel); + + connect(toggle_diagrams_list_, SIGNAL(toggled(bool)), this, SLOT(setDiagramsListVisible(bool))); + connect(toggle_print_options_, SIGNAL(toggled(bool)), this, SLOT(setPrintOptionsVisible(bool))); + connect(adjust_width_, SIGNAL(triggered()), preview_, SLOT(fitToWidth())); + connect(adjust_page_, SIGNAL(triggered()), preview_, SLOT(fitInView())); + connect(zoom_out_, SIGNAL(triggered()), this, SLOT(zoomOut())); + connect(zoom_in_, SIGNAL(triggered()), this, SLOT(zoomIn())); + connect(landscape_, SIGNAL(triggered()), preview_, SLOT(setLandscapeOrientation())); + connect(portrait_, SIGNAL(triggered()), preview_, SLOT(setPortraitOrientation())); + connect(first_page_, SIGNAL(triggered()), this, SLOT(firstPage())); + connect(previous_page_, SIGNAL(triggered()), this, SLOT(previousPage())); + connect(next_page_, SIGNAL(triggered()), this, SLOT(nextPage())); + connect(last_page_, SIGNAL(triggered()), this, SLOT(lastPage())); + connect(single_page_view_, SIGNAL(triggered()), preview_, SLOT(setSinglePageViewMode())); + connect(facing_pages_view_, SIGNAL(triggered()), preview_, SLOT(setFacingPagesViewMode())); + connect(all_pages_view_, SIGNAL(triggered()), preview_, SLOT(setAllPagesViewMode())); + connect(page_setup_, SIGNAL(triggered()), this, SLOT(pageSetup())); + + connect(use_full_page_, SIGNAL(toggled(bool)), this, SLOT(useFullPage(bool))); + connect(fit_diagram_to_page_, SIGNAL(toggled(bool)), this, SLOT(fitDiagramToPage(bool))); + + connect(preview_, SIGNAL(previewChanged()), this, SLOT(updateZoomList())); + connect(zoom_box_, SIGNAL(currentIndexChanged(int)), this, SLOT(updatePreviewZoom())); + + connect(buttons_, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttons_, SIGNAL(rejected()), this, SLOT(reject())); + + hlayout0_ = new QHBoxLayout(); + vlayout0_ = new QVBoxLayout(); + vlayout1_ = new QVBoxLayout(); + vlayout2_ = new QVBoxLayout(); + + vlayout1_ -> addWidget(use_full_page_); + vlayout1_ -> addWidget(use_full_page_label_); + vlayout1_ -> addWidget(fit_diagram_to_page_); + vlayout1_ -> addWidget(fit_diagram_to_page_label_); + print_options_box_ -> setLayout(vlayout1_); + + vlayout2_ -> addWidget(diagrams_label_); + vlayout2_ -> addWidget(diagrams_list_); + + hlayout0_ -> addLayout(vlayout2_); + hlayout0_ -> addWidget(preview_); + + vlayout0_ -> addWidget(toolbar_); + vlayout0_ -> addLayout(hlayout0_); + vlayout0_ -> addWidget(print_options_box_); + vlayout0_ -> addWidget(buttons_); + + setLayout(vlayout0_); + updateZoomList(); +} + +/** + Ce slot prive emet le signal paintRequested avec : + * la liste des schemas a imprimer / selectionnes + * un booleen indiquant s'il faut adapter les schemas aux pages ou non + * l'imprimante a utiliser +*/ +void QETPrintPreviewDialog::requestPaint(QPrinter *printer) { + emit( + paintRequested( + diagrams_list_ -> selectedDiagrams(), + fit_diagram_to_page_ -> isChecked(), + printer + ) + ); +} + +/** + Ce slot prive verifie que le nombre de schemas a imprimer est bien superieur + a 0 et active ou desactive le bouton "Imprimer" en consequence. +*/ +void QETPrintPreviewDialog::checkDiagramsCount() { + int diagrams_count = diagrams_list_ -> selectedDiagrams().count(); + + // desactive le premier bouton de la liste (= le bouton "Imprimer") + QList buttons = buttons_ -> buttons(); + if (buttons.count()) buttons[0] -> setEnabled(diagrams_count); +} + +/** + Ce slot prive affiche ou cache la liste des schemas + @param display true pour affiche la liste des schemas, false pour la cacher +*/ +void QETPrintPreviewDialog::setDiagramsListVisible(bool display) { + diagrams_label_ -> setVisible(display); + diagrams_list_ -> setVisible(display); + + if (display) { + toggle_diagrams_list_ -> setText(tr("Cacher la liste des sch\351mas")); + } else { + toggle_diagrams_list_ -> setText(tr("Afficher la liste des sch\351mas")); + } +} + +/** + Ce slot prive affiche ou cache les options d'impression + @param display true pour affiche les options d'impression, false pour les + cacher +*/ +void QETPrintPreviewDialog::setPrintOptionsVisible(bool display) { + print_options_box_ -> setVisible(display); + + if (display) { + toggle_print_options_ -> setText(tr("Cacher les options d'impression")); + } else { + toggle_print_options_ -> setText(tr("Afficher les options d'impression")); + } +} + +/** + Met a jour la liste des zooms disponibles +*/ +void QETPrintPreviewDialog::updateZoomList() { + // recupere le zooom courant + qreal current_zoom = preview_ -> zoomFactor(); + bool current_zoom_is_not_null = bool(int(current_zoom * 100.0)); + + // liste des zooms par defaut + QList zooms_real; + zooms_real << 0.25 << 0.5 << 0.75 << 1.0 << 1.5 << 2.0 << 4.0 << 8.0; + + // ajout du zoom en cours + if (current_zoom_is_not_null && (!zooms_real.contains(current_zoom))) { + zooms_real << current_zoom; + qSort(zooms_real.begin(), zooms_real.end()); + } + + // remplissage de la liste deroulante + int current_zoom_index = -1; + zoom_box_ -> blockSignals(true); + zoom_box_ -> clear(); + foreach (qreal z, zooms_real) { + zoom_box_ -> addItem(QString(tr("%1 %")).arg(z * 100.0, 0, 'f', 2), z); + if (z == current_zoom) current_zoom_index = zoom_box_ -> count() - 1; + } + zoom_box_ -> setCurrentIndex(current_zoom_index); + zoom_box_ -> blockSignals(false); +} + +/** + Change le zoom de l'apercu en fonctiopn du contenu du zoom selectionne +*/ +void QETPrintPreviewDialog::updatePreviewZoom() { + preview_ -> setZoomFactor( + zoom_box_ -> itemData(zoom_box_ -> currentIndex()).toDouble() + ); + updateZoomList(); +} diff --git a/sources/qetprintpreviewdialog.h b/sources/qetprintpreviewdialog.h new file mode 100644 index 000000000..afacc6970 --- /dev/null +++ b/sources/qetprintpreviewdialog.h @@ -0,0 +1,107 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef QET_PRINT_PREVIEW_DIALOG +#define QET_PRINT_PREVIEW_DIALOG +#include +class Diagram; +class DiagramsChooser; +class QETProject; +/** + Cette classe represente un dialogue permettant d'affiner les options + d'impression d'un schema a l'aide d'un apercu de ce qu'elle donnerait sur + papier. +*/ +class QETPrintPreviewDialog : public QDialog { + Q_OBJECT + + // constructeurs, destructeur + public: + QETPrintPreviewDialog(QETProject *, QPrinter *, QWidget * = 0, Qt::WindowFlags = 0); + virtual ~QETPrintPreviewDialog(); + private: + QETPrintPreviewDialog(const QETPrintPreviewDialog &); + + // methodes + public: + DiagramsChooser *diagramsChooser(); + bool fitDiagramsToPages() const; + + // signaux + signals: + void paintRequested(const QList &, bool, QPrinter *); + + public slots: + void firstPage(); + void previousPage(); + void nextPage(); + void lastPage(); + void pageSetup(); + void useFullPage(bool); + void fitDiagramToPage(bool); + void zoomIn(); + void zoomOut(); + + // attributs + private: + QETProject *project_; + QPrinter *printer_; + QHBoxLayout *hlayout0_; + QVBoxLayout *vlayout0_; + QVBoxLayout *vlayout1_; + QVBoxLayout *vlayout2_; + QToolBar *toolbar_; + QPrintPreviewWidget *preview_; + QLabel *diagrams_label_; + DiagramsChooser *diagrams_list_; + QAction *toggle_diagrams_list_; + QAction *toggle_print_options_; + QAction *adjust_width_; + QAction *adjust_page_; + QAction *zoom_in_; + QComboBox *zoom_box_; + QAction *zoom_out_; + QAction *landscape_; + QAction *portrait_; + QAction *first_page_; + QAction *previous_page_; + QAction *next_page_; + QAction *last_page_; + QAction *all_pages_view_; + QAction *facing_pages_view_; + QAction *single_page_view_; + QAction *page_setup_; + QDialogButtonBox *buttons_; + QGroupBox *print_options_box_; + QCheckBox *use_full_page_; + QLabel *use_full_page_label_; + QCheckBox *fit_diagram_to_page_; + QLabel *fit_diagram_to_page_label_; + + // methodes + private: + void build(); + + private slots: + void requestPaint(QPrinter *); + void checkDiagramsCount(); + void setDiagramsListVisible(bool); + void setPrintOptionsVisible(bool); + void updateZoomList(); + void updatePreviewZoom(); +}; +#endif diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp new file mode 100644 index 000000000..d1d9728ad --- /dev/null +++ b/sources/qetproject.cpp @@ -0,0 +1,979 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#include "qetproject.h" +#include "diagram.h" +#include "elementdefinition.h" +#include "xmlelementscollection.h" +#include "elementscategory.h" +#include "qetapp.h" +#include "qetdiagrameditor.h" +#include "integrationmoveelementshandler.h" +#include "basicmoveelementshandler.h" + +QString QETProject::integration_category_name = "import"; + +/** + Constructeur par defaut - cree un schema contenant une collection + d'elements vide et un schema vide. + @param diagrams Nombre de nouveaux schemas a ajouter a ce nouveau projet + @param parent QObject parent +*/ +QETProject::QETProject(int diagrams, QObject *parent) : + QObject(parent), + read_only_(false) +{ + // 0 a n schema(s) vide(s) + int diagrams_count = qMax(0, diagrams); + for (int i = 0 ; i < diagrams_count ; ++ i) { + addNewDiagram(); + } + + // une collection d'elements vide + collection_ = new XmlElementsCollection(); + collection_ -> setProtocol("embed"); + collection_ -> setProject(this); + connect(collection_, SIGNAL(written()), this, SLOT(componentWritten())); + + // une categorie dediee aux elements integres automatiquement + ensureIntegrationCategoryExists(); +} + +/** + Construit un projet a partir du chemin d'un fichier. + @param path Chemin du fichier + @param parent QObject parent +*/ +QETProject::QETProject(const QString &path, QObject *parent) : + QObject(parent), + read_only_(false) +{ + // ouvre le fichier + QFile project_file(path); + if (!project_file.open(QIODevice::ReadOnly | QIODevice::Text)) { + state_ = FileOpenFailed; + return; + } + setFilePath(path); + + // en extrait le contenu XML + bool xml_parsing = document_root_.setContent(&project_file); + if (!xml_parsing) { + state_ = XmlParsingFailed; + return; + } + + // et construit le projet + readProjectXml(); + + // passe le projet en lecture seule si le fichier l'est + QFileInfo project_file_info(path); + if (!project_file_info.isWritable()) { + setReadOnly(true); + } +} + +/** + Construit un projet a partir d'un element XML representant le projet. + L'element XML fourni est copie et conserve dans la classe. +*/ +QETProject::QETProject(const QDomElement &xml_element, QObject *parent) : + QObject(parent), + read_only_(false) +{ + // copie le contenu XML + document_root_.appendChild(document_root_.importNode(xml_element, true)); + + // et construit le projet + readProjectXml(); +} + +/** + Destructeur +*/ +QETProject::~QETProject() { + // supprime les schemas + // qDebug() << "Suppression du projet" << ((void *)this); + + // supprime la collection + // qDebug() << "Suppression de la collection du projet" << ((void *)this); + delete collection_; + // qDebug() << "Collection du projet" << ((void *)this) << "supprimee"; + + // qDebug() << diagrams_; + foreach (Diagram *diagram, diagrams_) { + diagrams_.removeAll(diagram); + delete diagram; + } + // qDebug() << diagrams_; +} + +/** + Cette methode peut etre utilisee pour tester la bonne ouverture d'un projet + @return l'etat du projet + @see ProjectState +*/ +QETProject::ProjectState QETProject::state() const { + return(state_); +} + +/** + @return la liste des schemas de ce projet +*/ +QList QETProject::diagrams() const { + return(diagrams_); +} + +/** + @return la collection embarquee de ce projet +*/ +ElementsCollection *QETProject::embeddedCollection() const { + return(collection_); +} + +/** + @return le chemin du fichier dans lequel ce projet est enregistre +*/ +QString QETProject::filePath() { + return(file_path_); +} + +/** + Change le chemin du fichier dans lequel ce projet est enregistre + @param filepath Nouveau chemin de fichier +*/ +void QETProject::setFilePath(const QString &filepath) { + file_path_ = filepath; + + // le chemin a change : on reevalue la necessite du mode lecture seule + QFileInfo file_path_info(file_path_); + if (file_path_info.isWritable()) { + setReadOnly(false); + } + + emit(projectFilePathChanged(this, file_path_)); + emit(projectInformationsChanged(this)); +} + +/** + + @return une chaine de caractere du type "Projet titre du projet". + Si le projet n'a pas de titre, le nom du fichier est utilise. + Si le projet n'est pas associe a un fichier, cette methode retourne "Projet + sans titre". + De plus, si le projet est en lecture seule, le tag "[lecture seule]" est + ajoute. +*/ +QString QETProject::pathNameTitle() const { + QString final_title; + + if (!project_title_.isEmpty()) { + final_title = QString( + tr( + "Projet \253\240%1\240\273", + "displayed title for a ProjectView - %1 is the project title" + ) + ).arg(project_title_); + } else if (!file_path_.isEmpty()) { + final_title = QString( + tr( + "Projet %1", + "displayed title for a title-less project - %1 is the file name" + ) + ).arg(QFileInfo(file_path_).completeBaseName()); + } else { + final_title = QString( + tr( + "Projet sans titre", + "displayed title for a project-less, file-less project" + ) + ); + } + + if (isReadOnly()) { + final_title = QString( + tr( + "%1 [lecture seule]", + "displayed title for a read-only project - %1 is a displayable title" + ) + ).arg(final_title); + } + + return(final_title); +} + +/** + @return le titre du projet +*/ +QString QETProject::title() const { + return(project_title_); +} + +/** + @param title le nouveau titre du projet +*/ +void QETProject::setTitle(const QString &title) { + // ne fait rien si le projet est en lecture seule + if (isReadOnly()) return; + + // ne fait rien si le titre du projet n'est pas change par l'appel de cette methode + if (project_title_ == title) return; + + project_title_ = title; + emit(projectTitleChanged(this, project_title_)); + emit(projectInformationsChanged(this)); +} + +/** + @return les dimensions par defaut utilisees lors de la creation d'un + nouveau schema dans ce projet. +*/ +BorderProperties QETProject::defaultBorderProperties() const { + return(default_border_properties_); +} + +/** + Permet de specifier les dimensions par defaut utilisees lors de la creation + d'un nouveau schema dans ce projet. + @param border dimensions d'un schema +*/ +void QETProject::setDefaultBorderProperties(const BorderProperties &border) { + default_border_properties_ = border; +} + +/** + @return le cartouche par defaut utilise lors de la creation d'un + nouveau schema dans ce projet. +*/ +InsetProperties QETProject::defaultInsetProperties() const { + return(default_inset_properties_); +} + +/** + Permet de specifier le cartouche par defaut utilise lors de la creation + d'un nouveau schema dans ce projet. + @param inset Cartouche d'un schema +*/ +void QETProject::setDefaultInsetProperties(const InsetProperties &inset) { + default_inset_properties_ = inset; +} + +/** + @return le type de conducteur par defaut utilise lors de la creation d'un + nouveau schema dans ce projet. +*/ +ConductorProperties QETProject::defaultConductorProperties() const { + return(default_conductor_properties_); +} + +/** + Permet de specifier e type de conducteur par defaut utilise lors de la + creation d'un nouveau schema dans ce projet. +*/ +void QETProject::setDefaultConductorProperties(const ConductorProperties &conductor) { + default_conductor_properties_ = conductor; +} + +/** + @return un document XML representant le projet +*/ +QDomDocument QETProject::toXml() { + // racine du projet + QDomDocument xml_doc; + QDomElement project_root = xml_doc.createElement("project"); + project_root.setAttribute("version", QET::version); + project_root.setAttribute("title", project_title_); + xml_doc.appendChild(project_root); + + // proprietes pour les nouveaux schemas + QDomElement new_diagrams_properties = xml_doc.createElement("newdiagrams"); + writeDefaultPropertiesXml(new_diagrams_properties); + project_root.appendChild(new_diagrams_properties); + + // schemas + + // qDebug() << "Export XML de" << diagrams_.count() << "schemas"; + int order_num = 1; + foreach(Diagram *diagram, diagrams_) { + qDebug() << qPrintable(QString("QETProject::toXml() : exporting diagram \"%1\" [%2]").arg(diagram -> title()).arg(QET::pointerString(diagram))); + QDomNode appended_diagram = project_root.appendChild(diagram -> writeXml(xml_doc)); + appended_diagram.toElement().setAttribute("order", order_num ++); + } + + // collection + project_root.appendChild(collection_ -> writeXml(xml_doc)); + + return(xml_doc); +} + +/** + Ferme le projet +*/ +bool QETProject::close() { + + /// @todo demander son avis a l'utilisateur + /*foreach(Diagram *diagram, diagrams_) { + QTextStream err(stderr); + err << diagram -> writtenXml().toString(4); + }*/ + return(true); +} + +/** + Enregistre le projet vers un fichier. + @see filePath() + @see setFilePath() + @return true si l'enregistrement a reussi, false sinon +*/ +bool QETProject::write() { + // le chemin du fichier doit etre connu + if (file_path_.isEmpty()) { + qDebug() << qPrintable(QString("QETProject::write() : called without a known filepath [%1]").arg(QET::pointerString(this))); + return(false); + } + + // si le projet a ete ouvert en mode lecture seule et que le fichier n'est pas accessible en ecriture, on n'effectue pas l'enregistrement + if (isReadOnly() && !QFileInfo(file_path_).isWritable()) { + qDebug() << qPrintable(QString("QETProject::write() : the file %1 was opened read-only and thus will not be written. [%2]").arg(file_path_).arg(QET::pointerString(this))); + return(true); + } + + // ouvre le fichier en ecriture + QFile file(file_path_); + bool file_opening = file.open(QIODevice::WriteOnly | QIODevice::Text); + if (!file_opening) { + qDebug() << qPrintable(QString("QETProject::write() : unable to open %1 with write access [%2]").arg(file_path_).arg(QET::pointerString(this))); + return(false); + } + + qDebug() << qPrintable(QString("QETProject::write() : writing to file %1 [%2]").arg(file_path_).arg(QET::pointerString(this))); + + // realise l'export en XML du projet dans le document XML interne + document_root_.clear(); + document_root_.appendChild(document_root_.importNode(toXml().documentElement(), true)); + + QTextStream out(&file); + out.setCodec("UTF-8"); + out << document_root_.toString(4); + file.close(); + + return(true); +} + +/** + @return true si le projet est en mode readonly, false sinon +*/ +bool QETProject::isReadOnly() const { + return(read_only_ && read_only_file_path_ == file_path_); +} + +/** + @param bool read_only true pour passer le projet (schemas et collection) + en mode Read Only, false sinon. +*/ +void QETProject::setReadOnly(bool read_only) { + if (read_only_ != read_only) { + // memorise le fichier pour lequel ce projet est en lecture seule + read_only_file_path_ = file_path_; + + // applique le nouveau mode aux schemas + foreach(Diagram *diagram, diagrams()) { + diagram -> setReadOnly(read_only); + } + + read_only_ = read_only; + emit(readOnlyChanged(this, read_only)); + } +} + +/** + @return true si le projet peut etre considere comme vide, c'est-a-dire : + - soit avec une collection embarquee vide + - soit avec uniquement des schemas consideres comme vides + - soit avec un titre de projet +*/ +bool QETProject::isEmpty() const { + // si le projet a un titre, on considere qu'il n'est pas vide + if (!project_title_.isEmpty()) return(false); + + // si la collection du projet n'est pas vide, alors le projet n'est pas vide + if (!collection_ -> isEmpty()) return(false); + + // compte le nombre de schemas non vides + int pertinent_diagrams = 0; + foreach(Diagram *diagram, diagrams_) { + if (!diagram -> isEmpty()) ++ pertinent_diagrams; + } + + return(pertinent_diagrams > 0); +} + +/** + Cree une categorie dediee aux elements integres automatiquement dans le + projet si celle-ci n'existe pas deja. + @return true si tout s'est bien passe, false sinon +*/ +bool QETProject::ensureIntegrationCategoryExists() { + ElementsCategory *root_cat = rootCategory(); + if (!root_cat) return(false); + + if (root_cat -> category(integration_category_name)) return(true); + + ElementsCategory *integration_category = root_cat -> createCategory(integration_category_name); + if (!integration_category) return(false); + + integration_category -> setNames(namesListForIntegrationCategory()); + return(true); +} + +/** + @return la categorie dediee aux elements integres automatiquement dans le + projet ou 0 si celle-ci n'a pu etre creee. + @see ensureIntegrationCategoryExists() +*/ +ElementsCategory *QETProject::integrationCategory() const { + ElementsCategory *root_cat = rootCategory(); + if (!root_cat) return(0); + + return(root_cat -> category(integration_category_name)); +} + +/** + Integre un element dans le projet. + Cette methode delegue son travail a la methode + integrateElement(const QString &, MoveElementsHandler *, QString &) + en lui passant un MoveElementsHandler approprie. + @param elmt_location Emplacement de l'element a integrer + @param error_message Reference vers une chaine de caractere qui contiendra + eventuellement un message d'erreur + @return L'emplacement de l'element apres integration, ou une chaine vide si + l'integration a echoue. +*/ +QString QETProject::integrateElement(const QString &elmt_location, QString &error_msg) { + // handler dedie a l'integration d'element + IntegrationMoveElementsHandler *integ_handler = new IntegrationMoveElementsHandler(0); + QString integ_path = integrateElement(elmt_location, integ_handler, error_msg); + delete integ_handler; + + return(integ_path); +} + +/** + Integre un element dans le projet. + Cette methode prend en parametre l'emplacement d'un element a integrer. + Chaque categorie mentionnee dans le chemin de cet element sera copiee de + maniere non recursive sous la categorie dediee a l'integration si elle + n'existe pas deja. + L'element sera ensuite copiee dans cette copie de la hierarchie d'origine. + En cas de probleme, error_message sera modifiee de facon a contenir un + message decrivant l'erreur rencontree. + @param elmt_path Emplacement de l'element a integrer + @param handler Gestionnaire a utiliser pour gerer les copies d'elements et categories + @param error_message Reference vers une chaine de caractere qui contiendra + eventuellement un message d'erreur + @return L'emplacement de l'element apres integration, ou une chaine vide si + l'integration a echoue. +*/ +QString QETProject::integrateElement(const QString &elmt_path, MoveElementsHandler *handler, QString &error_message) { + // on s'assure que le projet a une categorie dediee aux elements importes automatiquement + if (!ensureIntegrationCategoryExists()) { + error_message = tr("Impossible de cr\351er la cat\351gorie pour l'int\351gration des \351l\351ments"); + return(QString()); + } + + // accede a a categorie d'integration + ElementsCategory *integ_cat = integrationCategory(); + + // accede a l'element a integrer + ElementsCollectionItem *integ_item = QETApp::collectionItem(ElementsLocation::locationFromString(elmt_path)); + ElementDefinition *integ_elmt = integ_item ? integ_item -> toElement() : 0; + if (!integ_item || !integ_elmt) { + error_message = tr("Impossible d'acc\351der \340 l'\351l\351ment a int\351grer"); + return(QString()); + } + + // recopie l'arborescence de l'element de facon non recursive + QList integ_par_cat = integ_elmt -> parentCategories(); + ElementsCategory *target_cat = integ_cat; + foreach(ElementsCategory *par_cat, integ_par_cat) { + if (par_cat -> isRootCategory()) continue; + + if (ElementsCategory *existing_cat = target_cat -> category(par_cat -> pathName())) { + // la categorie cible existe deja : on continue la progression + target_cat = existing_cat; + } else { + // la categorie cible n'existe pas : on la cree par recopie + ElementsCollectionItem *result_cat = par_cat -> copy(target_cat, handler, false); + if (!result_cat || !result_cat -> isCategory()) { + error_message = QString(tr("Un probl\350me s'est produit pendant la copie de la cat\351gorie %1")).arg(par_cat -> location().toString()); + return(QString()); + } + target_cat = result_cat -> toCategory(); + } + } + + // recopie l'element + if (ElementDefinition *existing_elmt = target_cat -> element(integ_item -> pathName())) { + + // l'element existe deja - on demande au handler ce que l'on doit faire + QET::Action todo = handler -> elementAlreadyExists(integ_elmt, existing_elmt); + + if (todo == QET::Ignore) { + // il faut conserver et utiliser l'element deja integre + return(existing_elmt -> location().toString()); + } else if (todo == QET::Erase) { + // il faut ecraser l'element deja integre + BasicMoveElementsHandler *erase_handler = new BasicMoveElementsHandler(); + ElementsLocation result_loc = copyElementWithHandler(integ_elmt, target_cat, erase_handler, error_message); + delete erase_handler; + return(result_loc.toString()); + } else if (todo == QET::Rename) { + // il faut faire cohabiter les deux elements en renommant le nouveau + QString integ_element_name = handler -> nameForRenamingOperation(); + BasicMoveElementsHandler *rename_handler = new BasicMoveElementsHandler(); + rename_handler -> setActionIfItemAlreadyExists(QET::Rename); + rename_handler -> setNameForRenamingOperation(integ_element_name); + ElementsLocation result_loc = copyElementWithHandler(integ_elmt, target_cat, rename_handler, error_message); + delete rename_handler; + return(result_loc.toString()); + } else { + // il faut annuler la pose de l'element + return(QString()); + } + } else { + // integre l'element normalement + ElementsLocation result_loc = copyElementWithHandler(integ_elmt, target_cat, handler, error_message); + return(result_loc.toString()); + } +} + +/** + Permet de savoir si un element est utilise dans un projet + @param location Emplacement d'un element + @return true si l'element location est utilise sur au moins un des schemas + de ce projet, false sinon +*/ +bool QETProject::usesElement(const ElementsLocation &location) { + foreach(Diagram *diagram, diagrams()) { + if (diagram -> usesElement(location)) { + return(true); + } + } + return(false); +} + +/** + Supprime tous les elements inutilises dans le projet + @param handler Gestionnaire d'erreur +*/ +void QETProject::cleanUnusedElements(MoveElementsHandler *handler) { + ElementsCategory *root_cat = rootCategory(); + if (!root_cat) return; + + root_cat -> deleteUnusedElements(handler); +} + +/** + Supprime tous les categories vides (= ne contenant aucun element ou que des + categories vides) dans le projet + @param handler Gestionnaire d'erreur +*/ +void QETProject::cleanEmptyCategories(MoveElementsHandler *handler) { + ElementsCategory *root_cat = rootCategory(); + if (!root_cat) return; + + root_cat -> deleteEmptyCategories(handler); +} + +/** + Gere la reecriture du projet +*/ +void QETProject::componentWritten() { + // reecrit tout le projet + write(); +} + +/** + Ajoute un nouveau schema au projet et emet le signal diagramAdded +*/ +Diagram *QETProject::addNewDiagram() { + // ne fait rien si le projet est en lecture seule + if (isReadOnly()) return(0); + + // cree un nouveau schema + Diagram *diagram = new Diagram(); + + // lui transmet les parametres par defaut + diagram -> border_and_inset.importBorder(defaultBorderProperties()); + diagram -> border_and_inset.importInset(defaultInsetProperties()); + diagram -> defaultConductorProperties = defaultConductorProperties(); + + addDiagram(diagram); + emit(diagramAdded(this, diagram)); + return(diagram); +} + +/** + Enleve un schema du projet et emet le signal diagramRemoved + @param diagram le schema a enlever +*/ +void QETProject::removeDiagram(Diagram *diagram) { + // ne fait rien si le projet est en lecture seule + if (isReadOnly()) return; + + if (!diagram || !diagrams_.contains(diagram)) return; + + if (diagrams_.removeAll(diagram)) { + emit(diagramRemoved(this, diagram)); + delete diagram; + } + + updateDiagramsFolioData(); +} + +/** + Gere le fait que l'ordre des schemas ait change + @param old_index ancien indice du schema deplace + @param new_index nouvel indice du schema deplace + Si l'ancien ou le nouvel index est negatif ou superieur au nombre de schemas + dans le projet, cette methode ne fait rien. + Les index vont de 0 a "nombre de schemas - 1" +*/ +void QETProject::diagramOrderChanged(int old_index, int new_index) { + if (old_index < 0 || new_index < 0) return; + + int diagram_max_index = diagrams_.size() - 1; + if (old_index > diagram_max_index || new_index > diagram_max_index) return; + + diagrams_.move(old_index, new_index); + updateDiagramsFolioData(); +} + +/** + @return un pointeur vers la categorie racine de la collection embarquee, ou + 0 si celle-ci n'est pas accessible. +*/ +ElementsCategory *QETProject::rootCategory() const { + if (!collection_) return(0); + + ElementsCategory *root_cat = collection_ -> rootCategory(); + return(root_cat); +} + +/** + (Re)lit le projet depuis sa description XML +*/ +void QETProject::readProjectXml() { + QDomElement root_elmt = document_root_.documentElement(); + + // la racine du document XML est sensee etre un element "project" + if (root_elmt.tagName() == "project") { + // mode d'ouverture normal + if (root_elmt.hasAttribute("version")) { + bool conv_ok; + qreal diagram_version = root_elmt.attribute("version").toDouble(&conv_ok); + if (conv_ok && QET::version.toDouble() < diagram_version) { + QMessageBox::warning( + 0, + tr("Avertissement", "message box title"), + tr( + "Ce document semble avoir \351t\351 enregistr\351 avec " + "une version ult\351rieure de QElectroTech. Il est " + "possible que l'ouverture de tout ou partie de ce " + "document \351choue.", + "message box content" + ) + ); + } + } + + setTitle(root_elmt.attribute("title")); + } else if (root_elmt.tagName() == "diagram") { + /// @todo gerer l'ouverture de fichiers + } else { + state_ = ProjectParsingFailed; + } + + // charge les proprietes par defaut pour les nouveaux schemas + readDefaultPropertiesXml(); + + // charge la collection embarquee + readElementsCollectionXml(); + + // charge les schemas + readDiagramsXml(); + + state_ = Ok; +} + +/** + Charge les schemas depuis la description XML du projet. + A noter qu'un projet peut parfaitement ne pas avoir de schema. +*/ +void QETProject::readDiagramsXml() { + // map destinee a accueillir les schemas + QMultiMap loaded_diagrams; + + // recherche les schemas dans le projet + QDomNodeList diagram_nodes = document_root_.elementsByTagName("diagram"); + for (uint i = 0 ; i < diagram_nodes.length() ; ++ i) { + if (diagram_nodes.at(i).isElement()) { + QDomElement diagram_xml_element = diagram_nodes.at(i).toElement(); + Diagram *diagram = new Diagram(); + diagram -> setProject(this); + bool diagram_loading = diagram -> fromXml(diagram_xml_element); + if (diagram_loading) { + // recupere l'attribut order du schema + int diagram_order = -1; + if (!QET::attributeIsAnInteger(diagram_xml_element, "order", &diagram_order)) diagram_order = 500000; + loaded_diagrams.insert(diagram_order, diagram); + } else { + delete diagram; + } + } + } + + // ajoute les schemas dans l'ordre indique par les attributs order + foreach(Diagram *diagram, loaded_diagrams.values()) { + addDiagram(diagram); + } +} + +/** + Charge les schemas depuis la description XML du projet +*/ +void QETProject::readElementsCollectionXml() { + // recupere la collection d'elements integreee au projet + QDomNodeList collection_roots = document_root_.elementsByTagName("collection"); + QDomElement collection_root; + if (!collection_roots.isEmpty()) { + // seule la premiere collection trouvee est prise en compte + collection_root = collection_roots.at(0).toElement(); + } + + if (collection_root.isNull()) { + // s'il n'y en a pas, cree une collection vide + collection_ = new XmlElementsCollection(); + } else { + // sinon lit cette collection + collection_ = new XmlElementsCollection(collection_root); + } + collection_ -> setProtocol("embed"); + collection_ -> setProject(this); + connect(collection_, SIGNAL(written()), this, SLOT(componentWritten())); +} + +/** + Charge les proprietes par defaut des nouveaux schemas depuis la description + XML du projet : + * dimensions + * contenu du cartouche + * conducteurs par defaut +*/ +void QETProject::readDefaultPropertiesXml() { + // repere l'element XML decrivant les proprietes des nouveaux schemas + QDomNodeList newdiagrams_nodes = document_root_.elementsByTagName("newdiagrams"); + if (newdiagrams_nodes.isEmpty()) return; + + QDomElement newdiagrams_elmt = newdiagrams_nodes.at(0).toElement(); + + // par defaut, les valeurs sont celles de la configuration QElectroTech + default_border_properties_ = QETDiagramEditor::defaultBorderProperties(); + default_inset_properties_ = QETDiagramEditor::defaultInsetProperties(); + default_conductor_properties_ = QETDiagramEditor::defaultConductorProperties(); + + // lecture des valeurs indiquees dans le projet + QDomElement border_elmt, inset_elmt, conductors_elmt; + + // recherche des elements XML concernant les dimensions, le cartouche et les conducteurs + for (QDomNode child = newdiagrams_elmt.firstChild() ; !child.isNull() ; child = child.nextSibling()) { + QDomElement child_elmt = child.toElement(); + if (child_elmt.isNull()) continue; + if (child_elmt.tagName() == "border") { + border_elmt = child_elmt; + } else if (child_elmt.tagName() == "inset") { + inset_elmt = child_elmt; + } else if (child_elmt.tagName() == "conductors") { + conductors_elmt = child_elmt; + } + } + + // dimensions, cartouche, et conducteurs + if (!border_elmt.isNull()) default_border_properties_.fromXml(border_elmt); + if (!inset_elmt.isNull()) default_inset_properties_.fromXml(inset_elmt); + if (!conductors_elmt.isNull()) default_conductor_properties_.fromXml(conductors_elmt); +} + +/** + Exporte les proprietes par defaut des nouveaux schemas dans l'element XML : + * dimensions + * contenu du cartouche + * conducteurs par defaut + @param xml_element Element XML sous lequel seront exportes les proprietes + par defaut des nouveaux schemas +*/ +void QETProject::writeDefaultPropertiesXml(QDomElement &xml_element) { + QDomDocument xml_document = xml_element.ownerDocument(); + + // exporte les dimensions + QDomElement border_elmt = xml_document.createElement("border"); + default_border_properties_.toXml(border_elmt); + xml_element.appendChild(border_elmt); + + // exporte le contenu du cartouche + QDomElement inset_elmt = xml_document.createElement("inset"); + default_inset_properties_.toXml(inset_elmt); + xml_element.appendChild(inset_elmt); + + // exporte le type de conducteur par defaut + QDomElement conductor_elmt = xml_document.createElement("conductors"); + default_conductor_properties_.toXml(conductor_elmt); + xml_element.appendChild(conductor_elmt); +} + +/** + Cette methode ajoute un schema donne au projet + @param diagram Schema a ajouter +*/ +void QETProject::addDiagram(Diagram *diagram) { + if (!diagram) return; + + // s'assure que le schema connaisse son projet parent + diagram -> setProject(this); + + // si le schema est ecrit, alors il faut reecrire le fichier projet + connect(diagram, SIGNAL(written()), this, SLOT(componentWritten())); + connect( + &(diagram -> border_and_inset), + SIGNAL(needFolioData()), + this, + SLOT(updateDiagramsFolioData()) + ); + + // ajoute le schema au projet + diagrams_ << diagram; + + updateDiagramsFolioData(); +} + +/** + @return La liste des noms a utiliser pour la categorie dediee aux elements + integres automatiquement dans le projet. +*/ +NamesList QETProject::namesListForIntegrationCategory() { + NamesList names; + + names.addName("fr", "\311l\351ments import\351s"); + names.addName("en", "Imported elements"); + names.addName("es", "Elementos importados"); + + return(names); +} + +/** + Cette methode sert a reperer un projet vide, c-a-d un projet identique a ce + que l'on obtient en faisant Fichier > Nouveau. + @return true si la collection d'elements embarquee a ete modifiee. + Concretement, cette methode retourne true si la collection embarquee + contient 0 element et 1 categorie vide qui s'avere etre la categorie dediee + aux elements integres automatiquement dans le projet. +*/ +bool QETProject::embeddedCollectionWasModified() { + ElementsCategory *root_cat = rootCategory(); + if (!root_cat) return(false); + + // la categorie racine doit comporter 0 element et 1 categorie + if (root_cat -> categories().count() != 1) return(true); + if (root_cat -> elements().count() != 0) return(true); + + // la categorie d'integration doit exister + ElementsCategory *integ_cat = integrationCategory(); + if (!integ_cat) return(true); + + // la categorie d'integration doit avoir les noms par defaut + if (integ_cat -> categoryNames() != namesListForIntegrationCategory()) { + return(true); + } + + return(false); +} + +/** + Cette methode sert a reperer un projet vide, c-a-d un projet identique a ce + que l'on obtient en faisant Fichier > Nouveau. + @return true si les schemas de ce projet ont ete modifies + Concretement, il doit y avoir exactement un schema, dont la pile + d'annulation est "clean". +*/ +bool QETProject::diagramsWereModified() { + // il doit y avoir exactement un schema + if (diagrams_.count() != 1) return(true); + + // dont la pile d'annulation est "clean" + return(!(diagrams_[0] -> undoStack().isClean())); +} + +/** + Cette methode sert a reperer un projet vide, c-a-d un projet identique a ce + que l'on obtient en faisant Fichier > Nouveau. + @return true si les schemas, la collection embarquee ou les proprietes de ce + projet ont ete modifies. + Concretement, le projet doit avoir un titre vide et ni ses schemas ni sa + collection embarquee ne doivent avoir ete modifies. + @see diagramsWereModified(), embeddedCollectionWasModified() +*/ +bool QETProject::projectWasModified() { + // il doit avoir un titre vide + if (!title().isEmpty()) return(true); + + // ni ses schemas ni sa collection embarquee ne doivent avoir ete modifies + if (diagramsWereModified()) return(true); + if (embeddedCollectionWasModified()) return(true); + + return(false); +} + +/** + Indique a chaque schema du projet quel est son numero de folio et combien de + folio le projet contient. +*/ +void QETProject::updateDiagramsFolioData() { + int total_folio = diagrams_.count(); + for (int i = 0 ; i < total_folio ; ++ i) { + diagrams_[i] -> border_and_inset.setFolioData(i + 1, total_folio); + } +} + +/** + Copie l'element integ_elmt dans la categorie target_cat en utilisant le + gestionnaire handler ; en cas d'erreur, error_message est rempli. + @return l'emplacement de l'element cree +*/ +ElementsLocation QETProject::copyElementWithHandler( + ElementDefinition *integ_elmt, + ElementsCategory *target_cat, + MoveElementsHandler *handler, + QString &error_message +) { + ElementsCollectionItem *result_item = integ_elmt -> copy(target_cat, handler); + ElementDefinition *result_elmt = result_item ? result_item -> toElement() : 0; + if (!result_item || !result_elmt) { + error_message = QString(tr("Un probl\350me s'est produit pendant la copie de l'\351l\351ment %1")).arg(integ_elmt -> location().toString()); + return(ElementsLocation()); + } + return(result_elmt -> location()); +} diff --git a/sources/qetproject.h b/sources/qetproject.h new file mode 100644 index 000000000..6228209da --- /dev/null +++ b/sources/qetproject.h @@ -0,0 +1,157 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef QET_PROJECT_H +#define QET_PROJECT_H +#include +#include +#include "nameslist.h" +#include "elementslocation.h" +#include "borderproperties.h" +#include "conductorproperties.h" +#include "insetproperties.h" +class Diagram; +class ElementsCollection; +class ElementsCategory; +class ElementDefinition; +class ElementsLocation; +class XmlElementsCollection; +class MoveElementsHandler; +/** + Cette classe represente un projet QET. Typiquement enregistre dans un + fichier, il s'agit d'un document XML integrant des schemas ainsi qu'une + collection d'elements embarques. Ce dernier attribut permet d'exporter des + schema sur d'autres machines sans se soucier de la presence des elements + sur celle-ci. + La classe QETProject gere l'enregistrement partiel. Ainsi, enregistrer les + modifications apportees a un element reenregistrera le fichier mais en + mettant a jour seulement les parties du document XML concerne. +*/ +class QETProject : public QObject { + Q_OBJECT + + // constructeurs, destructeur + public: + QETProject(int = 1, QObject * = 0); + QETProject(const QString &, QObject * = 0); + QETProject(const QDomElement &, QObject * = 0); + virtual ~QETProject(); + + private: + QETProject(const QETProject &); + + // enums + public: + /** + Represente l'etat du projet + */ + enum ProjectState { + Ok = 0, /// Le projet n'est pas en erreur + FileOpenFailed = 1, /// l'ouverture d'un fichier a echoue + XmlParsingFailed = 2, /// l'analyse XML a echoue + ProjectParsingFailed = 3 /// la lecture en tant que projet a echoue + }; + + // methodes + public: + ProjectState state() const; + QList diagrams() const; + ElementsCollection *embeddedCollection() const; + QString filePath(); + void setFilePath(const QString &); + QString pathNameTitle() const; + QString title() const; + void setTitle(const QString &); + BorderProperties defaultBorderProperties() const; + void setDefaultBorderProperties(const BorderProperties &); + InsetProperties defaultInsetProperties() const; + void setDefaultInsetProperties(const InsetProperties &); + ConductorProperties defaultConductorProperties() const; + void setDefaultConductorProperties(const ConductorProperties &); + QDomDocument toXml(); + bool close(); + bool write(); + bool isReadOnly() const; + void setReadOnly(bool); + bool isEmpty() const; + bool ensureIntegrationCategoryExists(); + ElementsCategory *integrationCategory() const; + QString integrateElement(const QString &, QString &); + QString integrateElement(const QString &, MoveElementsHandler *, QString &); + bool usesElement(const ElementsLocation &); + void cleanUnusedElements(MoveElementsHandler *); + void cleanEmptyCategories(MoveElementsHandler *); + bool projectWasModified(); + bool embeddedCollectionWasModified(); + bool diagramsWereModified(); + + public slots: + void componentWritten(); + Diagram *addNewDiagram(); + void removeDiagram(Diagram *); + void diagramOrderChanged(int, int); + + signals: + void projectFilePathChanged(QETProject *, const QString &); + void projectTitleChanged(QETProject *, const QString &); + void projectInformationsChanged(QETProject *); + void diagramAdded(QETProject *, Diagram *); + void diagramRemoved(QETProject *, Diagram *); + void readOnlyChanged(QETProject *, bool); + + private slots: + void updateDiagramsFolioData(); + + private: + ElementsCategory *rootCategory() const; + void readProjectXml(); + void readDiagramsXml(); + void readElementsCollectionXml(); + void readDefaultPropertiesXml(); + void writeDefaultPropertiesXml(QDomElement &); + void addDiagram(Diagram *); + NamesList namesListForIntegrationCategory(); + ElementsLocation copyElementWithHandler(ElementDefinition *, ElementsCategory *, MoveElementsHandler *, QString &); + + // attributs + private: + /// Chemin du fichier dans lequel ce projet est enregistre + QString file_path_; + /// Etat du projet + ProjectState state_; + /// Element XML representant le projet + QDomDocument document_root_; + /// Schemas contenus dans le projet + QList diagrams_; + /// Collection d'elements embarquee + XmlElementsCollection *collection_; + /// Titre du projet + QString project_title_; + /// booleen indiquant si le projet est en ReadOnly ou non + bool read_only_; + /// Chemin du fichier pour lequel ce projet est considere comme etant en lecture seule + QString read_only_file_path_; + /// Nom de la categorie a utiliser pour l'integration automatique des elements + static QString integration_category_name; + /// Dimensions par defaut pour les nouveaux schemas dans ce projet + BorderProperties default_border_properties_; + /// Proprietes par defaut des conducteurs pour les nouveaux schemas dans ce projet + ConductorProperties default_conductor_properties_; + /// Proprietes par defaut du cartouche pour les nouveaux schemas dans ce projet + InsetProperties default_inset_properties_; +}; +#endif diff --git a/sources/qetregexpvalidator.cpp b/sources/qetregexpvalidator.cpp new file mode 100644 index 000000000..6d2ee36e8 --- /dev/null +++ b/sources/qetregexpvalidator.cpp @@ -0,0 +1,33 @@ +#include "qetregexpvalidator.h" + +/** + Constructeur + @param object QObject parent +*/ +QETRegExpValidator::QETRegExpValidator(QObject *parent) : QRegExpValidator(parent) { +} + +/** + Constructeur + @param regexp Expression reguliere a valider + @param object QObject parent +*/ +QETRegExpValidator::QETRegExpValidator(const QRegExp ®exp, QObject *parent) : QRegExpValidator(regexp, parent) { +} + +/** + Destructeur +*/ +QETRegExpValidator::~QETRegExpValidator() { +} + +/** + @see QRegExpValidator::validate + @see validationFailed() + Emet le signal validationFailed si la validation echoue +*/ +QValidator::State QETRegExpValidator::validate(QString &input, int &pos) const { + QValidator::State result = QRegExpValidator::validate(input, pos); + if (result == QValidator::Invalid) emit(validationFailed()); + return(result); +} diff --git a/sources/qetregexpvalidator.h b/sources/qetregexpvalidator.h new file mode 100644 index 000000000..34f2dba4e --- /dev/null +++ b/sources/qetregexpvalidator.h @@ -0,0 +1,43 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef QET_REGEXP_VALIDATOR_H +#define QET_REGEXP_VALIDATOR_H +#include +/** + Cette classe agit comme un QRegExpValidator a ceci pres qu'elle emet un + signal lorsqu'elle ne valide pas une saisie. +*/ +class QETRegExpValidator : public QRegExpValidator { + Q_OBJECT + + // constructeurs, destructeur + public: + QETRegExpValidator(QObject *); + QETRegExpValidator(const QRegExp &, QObject *); + virtual ~QETRegExpValidator(); + private: + QETRegExpValidator(const QETRegExpValidator &); + + // methodes + public: + virtual QValidator::State validate(QString &, int &) const; + + signals: + void validationFailed() const; +}; +#endif diff --git a/sources/qetsingleapplication.cpp b/sources/qetsingleapplication.cpp index 2d9135de1..880e246ae 100644 --- a/sources/qetsingleapplication.cpp +++ b/sources/qetsingleapplication.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/qetsingleapplication.h b/sources/qetsingleapplication.h index 347c8062a..68d635a96 100644 --- a/sources/qetsingleapplication.h +++ b/sources/qetsingleapplication.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/qettabbar.cpp b/sources/qettabbar.cpp new file mode 100644 index 000000000..de109d172 --- /dev/null +++ b/sources/qettabbar.cpp @@ -0,0 +1,323 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#include "qettabbar.h" +#include + +/** + Constructeur + @param parent QWidget parent +*/ +QETTabBar::QETTabBar(QWidget *parent) : + QTabBar(parent), + no_more_tabs_(true), + moved_tab_(-1) +{ +} + +/** + Destructeur +*/ +QETTabBar::~QETTabBar() { +} + +/** + Active l'onglet suivant si possible +*/ +void QETTabBar::activateNextTab() { + int count_ = count(); + if (count_ < 2) return; + + int current_index = currentIndex(); + if (current_index == count_ - 1) { + setCurrentIndex(0); + } else { + setCurrentIndex(current_index + 1); + } +} + +/** + Active l'onglet precedent si possible +*/ +void QETTabBar::activatePreviousTab() { + int count_ = count(); + if (count_ < 2) return; + + int current_index = currentIndex(); + if (!current_index) { + setCurrentIndex(count_ - 1); + } else { + setCurrentIndex(current_index - 1); + } +} + +/** + @param movable true pour que les onglets soient deplacables, false sinon +*/ +void QETTabBar::setTabsMovable(bool movable) { + movable_tabs_ = movable; +} + +/** + @return true si les onglets sont deplacables, false sinon +*/ +bool QETTabBar::tabsMovable() const { + return(movable_tabs_); +} + +/** + @return true si les onglets sont dessines de maniere verticale +*/ +bool QETTabBar::isVertical() const { + int current_shape = shape(); + return( + current_shape == QTabBar::RoundedEast || + current_shape == QTabBar::RoundedWest || + current_shape == QTabBar::TriangularEast || + current_shape == QTabBar::TriangularWest + ); +} + +/** + @return true si les onglets sont dessines de maniere horizontale +*/ +bool QETTabBar::isHorizontal() const { + int current_shape = shape(); + return( + current_shape == QTabBar::RoundedNorth || + current_shape == QTabBar::RoundedSouth || + current_shape == QTabBar::TriangularNorth || + current_shape == QTabBar::TriangularSouth + ); +} + +/** + Gere l'insertion d'un onglet + @param index indice de l'onglet insere +*/ +void QETTabBar::tabInserted(int index) { + QTabBar::tabInserted(index); + if (no_more_tabs_) { + emit(firstTabInserted()); + } + no_more_tabs_ = false; +} + +/** + Gere le retrait d'un onglet + @param index indice de l'onglet enleve +*/ +void QETTabBar::tabRemoved(int index) { + QTabBar::tabRemoved(index); + if (!count()) { + emit(lastTabRemoved()); + no_more_tabs_ = true; + } +} + +/** + Gere les evenements rollette sur cette barre d'onglets + @param event Evenement rollette +*/ +void QETTabBar::wheelEvent(QWheelEvent *event) { + int num_degrees = event -> delta() / 8; + int num_steps = qAbs(num_degrees / 15); + + if (num_degrees <= 0) { + // passe a l'onglet suivant + for (int i = 0 ; i < num_steps ; ++ i) activateNextTab(); + } else { + // passe a l'onglet precedent + for (int i = 0 ; i < num_steps ; ++ i) activatePreviousTab(); + } + event -> accept(); +} + +/** + @param event Objet decrivant l'evenement souris +*/ +void QETTabBar::mousePressEvent(QMouseEvent *event) { + QTabBar::mousePressEvent(event); + if (movable_tabs_) { + if (event -> button() == Qt::LeftButton) { + // retient l'onglet deplace et la position a laquelle le mouvement debute + moved_tab_ = tabForPressedPosition(event -> pos()); + press_point_ = event -> pos(); + } + } +} + +/** + @param event Objet decrivant l'evenement souris +*/ +void QETTabBar::mouseMoveEvent(QMouseEvent *event) { + QTabBar::mouseMoveEvent(event); + + // gere le deplacement d'onglets si celui-ci est active + if (movable_tabs_ && moved_tab_ != -1) { + // repere l'onglet de "destination" + int dest_tab = tabForMovedPosition(event -> pos()); + + // verifie s'il faut deplacer l'onglet puis le deplace + if (mustMoveTab(moved_tab_, dest_tab, event -> pos())) { + moveTab(moved_tab_, dest_tab); + moved_tab_ = dest_tab; + event -> accept(); + } + } +} + +/** + @param event Objet decrivant l'evenement souris +*/ +void QETTabBar::mouseReleaseEvent(QMouseEvent *event) { + QTabBar::mouseReleaseEvent(event); + moved_tab_ = -1; +} + +/** + @param event Objet decrivant l'evenement souris +*/ +void QETTabBar::mouseDoubleClickEvent(QMouseEvent *event) { + QTabBar::mouseDoubleClickEvent(event); + int clicked_tab = tabForPressedPosition(event -> pos()); + emit(tabDoubleClicked(clicked_tab)); +} + +/** + @param src_tab Index de l'onglet de depart + @param dst_tab Index de l'onglet de destination + @param pos Position de la souris dans le cadre du deplacement de l'onglet + @return true s'il faut deplacer l'onglet src_tab a la place de l'onglet + dst_tab. + Cette methode +*/ +bool QETTabBar::mustMoveTab(int src_tab, int dst_tab, const QPoint &pos) const { + // les onglets sources et cibles doivent etre valides et differents + if (src_tab == -1 || dst_tab == -1) return(false); + if (src_tab == dst_tab) return(false); + + /* + A ce stade, le deplacement est possible mais selon la position du + pointeur de la souris, on peut assister a des deplacements prematures + d'onglets, rendant l'interface plus difficilement utilisable. + On s'assure donc que le curseur est assez "loin" pour eviter ces + problemes. + */ + // recupere les rectangles representant les onglets + QRect source_rect = tabRect(src_tab); + QRect target_rect = tabRect(dst_tab); + + if (isHorizontal()) { + if (layoutDirection() == Qt::LeftToRight && source_rect.x() < target_rect.x()) { + source_rect.moveRight(target_rect.right()); + } else { + source_rect.moveLeft(target_rect.left()); + } + } else { + if (source_rect.y() < target_rect.y()) { + source_rect.moveBottom(target_rect.bottom()); + } else { + source_rect.moveTop(target_rect.top()); + } + } + return(posMatchesTabRect(source_rect, pos)); +} + +/** + Deplace un onglet. + @param src_tab Index de l'onglet de depart + @param dst_tab Index de l'onglet de destination + @param pos Position de la souris dans le cadre du deplacement de l'onglet + @return +*/ +void QETTabBar::moveTab(int src_tab, int dst_tab) { + // sauvegarde les caracteristiques de l'onglet deplace + QIcon old_tab_icon = tabIcon(src_tab); + QVariant old_tab_data = tabData(src_tab); + QString old_tab_text = tabText(src_tab); + QColor old_tab_textcolor = tabTextColor(src_tab); + QString old_tab_tooltip = tabToolTip(src_tab); + QString old_tab_whatsthis = tabWhatsThis(src_tab); + + // si la QETTabBar est utilise dans un QTabWidget (ou une classe en + // derivant), elle lui delegue le deplacement de l'onglet + if (QTabWidget *qtabwidget = qobject_cast(parent())) { + QWidget *old_tab_widget = qtabwidget -> widget(src_tab); + qtabwidget -> removeTab(src_tab); + qtabwidget -> insertTab(dst_tab, old_tab_widget, old_tab_text); + qtabwidget -> setCurrentIndex(dst_tab); + } else { + removeTab(src_tab); + insertTab(dst_tab, old_tab_text); + setCurrentIndex(dst_tab); + } + + // remet en place les caracteristiques de l'onglet deplace + setTabIcon (dst_tab, old_tab_icon ); + setTabData (dst_tab, old_tab_data ); + setTabTextColor(dst_tab, old_tab_textcolor); + setTabToolTip (dst_tab, old_tab_tooltip ); + setTabWhatsThis(dst_tab, old_tab_whatsthis); + + // signale le deplacement de l'onglet + emit(tabMoved(src_tab, dst_tab)); +} + +/** + @param pos Position + @return l'index de l'onglet correspondant a la position pos, ou -1 si aucun + onglet ne correspond. +*/ +int QETTabBar::tabForPressedPosition(const QPoint &pos) { + for (int tab_index = 0 ; tab_index < count() ; ++ tab_index) { + if (tabRect(tab_index).contains(pos)) return(tab_index); + } + return(-1); +} + +/** + @param pos Position + @return l'index de l'onglet correspondant a la position pos lors d'un + deplacement d'onglet, ou -1 si aucun onglet ne correspond. Cette methode ne + prend en compte que l'abscisse ou que l'ordonnee de la position en fonction + de l'orientation des onglets. +*/ +int QETTabBar::tabForMovedPosition(const QPoint &pos) { + for (int tab_index = 0 ; tab_index < count() ; ++ tab_index) { + if (posMatchesTabRect(tabRect(tab_index), pos)) return(tab_index); + } + return(-1); +} + +/** + @param rect Un rectangle cense representer un onglet + @param pos Une position + @return true si la position pos correspond a ce rectangle. + Cette methode ne prend en compte que l'abscisse ou que + l'ordonnee de la position en fonction de l'orientation des onglets. +*/ +bool QETTabBar::posMatchesTabRect(const QRect &rect, const QPoint &pos) const { + if (isVertical()) { + // les onglets sont disposes de maniere verticale : on prend en compte l'ordonnee + if (pos.y() >= rect.y() && pos.y() < rect.y() + rect.height()) return(true); + } else { + // les onglets sont disposes de maniere horizontale : on prend en compte l'abscisse + if (pos.x() >= rect.x() && pos.x() < rect.x() + rect.width()) return(true); + } + return(false); +} diff --git a/sources/qettabbar.h b/sources/qettabbar.h new file mode 100644 index 000000000..b1eba92d2 --- /dev/null +++ b/sources/qettabbar.h @@ -0,0 +1,78 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef QET_TAB_BAR_H +#define QET_TAB_BAR_H +#include +#include +/** + Cette classe represente une barre d'onglets. + Elle se distingue d'une QTabBar sur les points suivants : + *elle emet un signal lorsque le dernier onglet est ferme + *elle emet un signal lorsque le premier onglet est insere + *elle permet de passer d'un onglet a l'autre avec la rollette +*/ +class QETTabBar : public QTabBar { + Q_OBJECT + + // constructeurs, destructeur + public: + QETTabBar(QWidget * = 0); + virtual~QETTabBar(); + + private: + QETTabBar(const QETTabBar &); + + // methodes + public: + void activateNextTab(); + void activatePreviousTab(); + void setTabsMovable(bool); + bool tabsMovable() const; + bool isVertical() const; + bool isHorizontal() const; + + protected: + virtual void tabInserted(int); + virtual void tabRemoved(int); + virtual void wheelEvent(QWheelEvent *); + virtual void mousePressEvent(QMouseEvent *); + virtual void mouseMoveEvent(QMouseEvent *); + virtual void mouseReleaseEvent(QMouseEvent *); + virtual void mouseDoubleClickEvent(QMouseEvent *); + + signals: + void lastTabRemoved(); + void firstTabInserted(); + void tabMoved(int, int); + void tabDoubleClicked(int); + + private: + bool mustMoveTab(int, int, const QPoint &) const; + void moveTab(int, int); + int tabForPressedPosition(const QPoint &); + int tabForMovedPosition(const QPoint &); + bool posMatchesTabRect(const QRect &, const QPoint &) const; + + // attributs + private: + bool no_more_tabs_; + bool movable_tabs_; + int moved_tab_; + QPoint press_point_; +}; +#endif diff --git a/sources/qettabwidget.cpp b/sources/qettabwidget.cpp new file mode 100644 index 000000000..6736e9082 --- /dev/null +++ b/sources/qettabwidget.cpp @@ -0,0 +1,74 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ + +#include "qettabwidget.h" +#include +#include +#include "qettabbar.h" + +/** + Constructeur + @param parent QWidget parent +*/ +QETTabWidget::QETTabWidget(QWidget *parent) : + QTabWidget(parent) +{ + tab_bar_ = new QETTabBar(this); + setTabBar(tab_bar_); + + // re-emet les signaux emis par la barre d'onglets + connect(tab_bar_, SIGNAL(lastTabRemoved()), this, SIGNAL(lastTabRemoved())); + connect(tab_bar_, SIGNAL(firstTabInserted()), this, SIGNAL(firstTabInserted())); + connect(tab_bar_, SIGNAL(tabMoved(int, int)), this, SIGNAL(tabMoved(int, int))); + connect(tab_bar_, SIGNAL(tabDoubleClicked(int)), this, SIGNAL(tabDoubleClicked(int))); +} + +/** + Destructeur +*/ +QETTabWidget::~QETTabWidget() { +} + +/** + @param movable true pour que les onglets soient deplacables, false sinon +*/ +void QETTabWidget::setTabsMovable(bool movable) { + tab_bar_ -> setTabsMovable(movable); +} + +/** + @return true si les onglets sont deplacables, false sinon +*/ +bool QETTabWidget::tabsMovable() { + return(tab_bar_ -> tabsMovable()); +} + +/** + Gere les evenements rollette sur cette barre d'onglets + @param event Evenement rollette +*/ +void QETTabWidget::wheelEvent(QWheelEvent *event) { + QTabBar *tab_bar = tabBar(); + // rectangle occupe par la barre d'onglets + QRect tab_bar_region(QPoint(0, 0), QSize(size().width(), tab_bar -> size().height())); + if (tab_bar_region.contains(event -> pos())) { + QCoreApplication::sendEvent(tab_bar, event); + } else { + event -> ignore(); + } +} diff --git a/sources/qettabwidget.h b/sources/qettabwidget.h new file mode 100644 index 000000000..dbd08107d --- /dev/null +++ b/sources/qettabwidget.h @@ -0,0 +1,57 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef QET_TAB_WIDGET_H +#define QET_TAB_WIDGET_H +#include +class QETTabBar; +/** + Cette classe se comporte comme un QTabWidget a ceci pres qu'elle utilise + un QETTAbBar pour gerer ses onglets. + Elle transmet aussi ses signaux lastTabRemoved() et firstTabInserted(). + @see QETTabBar +*/ +class QETTabWidget : public QTabWidget { + Q_OBJECT + + // constructeurs, destructeur + public: + QETTabWidget(QWidget * = 0); + virtual~QETTabWidget(); + + private: + QETTabWidget(const QETTabWidget &); + + // methodes + public: + void setTabsMovable(bool); + bool tabsMovable(); + + protected: + void wheelEvent(QWheelEvent *); + + signals: + void lastTabRemoved(); + void firstTabInserted(); + void tabMoved(int, int); + void tabDoubleClicked(int); + + // attributs + private: + QETTabBar *tab_bar_; +}; +#endif diff --git a/sources/qfilenameedit.cpp b/sources/qfilenameedit.cpp new file mode 100644 index 000000000..c431a872c --- /dev/null +++ b/sources/qfilenameedit.cpp @@ -0,0 +1,83 @@ +#include "qfilenameedit.h" +#include "qetregexpvalidator.h" +#include +#include +#include + +/** + Constructeur + @param parent QWidget parent de ce champ de texte +*/ +QFileNameEdit::QFileNameEdit(QWidget *parent) : QLineEdit(parent) { + init(); +} + +/** + Constructeur + @param contents Contenu initial du champ + @param parent QWidget parent de ce champ de texte +*/ +QFileNameEdit::QFileNameEdit(const QString &contents, QWidget *parent) : QLineEdit(parent) { + init(); + if (!contents.isEmpty() && regexp_.exactMatch(contents)) { + setText(contents); + } +} + +/** + Destructeur +*/ +QFileNameEdit::~QFileNameEdit() { +} + +/** + @return true si le champ de texte est vide, false sinon +*/ +bool QFileNameEdit::isEmpty() { + return(text().isEmpty()); +} + +/** + @return true si le champ de texte n'est pas vide et est valide +*/ +bool QFileNameEdit::isValid() { + return(regexp_.exactMatch(text())); +} + +/** + Construit l'objet +*/ +void QFileNameEdit::init() { + regexp_ = QRegExp("^[0-9a-z_\\-\\.]+$", Qt::CaseSensitive); + validator_ = new QETRegExpValidator(regexp_, this); + setValidator(validator_); + tooltip_text_ = QString( + tr( + "Les caract\350res autoris\351s sont : \n" + " - les chiffres [0-9]\n" + " - les minuscules [a-z]\n" + " - le tiret [-], l'underscore [_] et le point [.]\n", + "tooltip content when editing a filename" + ) + ); + connect(validator_, SIGNAL(validationFailed()), this, SLOT(validationFailed())); +} + +/** + Affiche l'info-bulle informant l'utilisateur des caracteres autorises. +*/ +void QFileNameEdit::displayToolTip() { + QToolTip::showText( + mapToGlobal(QPoint(x() + width(), 0)), + tooltip_text_, + this, + QRect() + ); +} + +/** + Gere le fait que la validation du champ de texte ait echoue. +*/ +void QFileNameEdit::validationFailed() { + displayToolTip(); +} diff --git a/sources/qfilenameedit.h b/sources/qfilenameedit.h new file mode 100644 index 000000000..35307184e --- /dev/null +++ b/sources/qfilenameedit.h @@ -0,0 +1,60 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef Q_FILENAME_EDIT_H +#define Q_FILENAME_EDIT_H +#include +#include +#include +class QETRegExpValidator; +/** + Cette classe represente un champ de texte dedie a la saisie d'un nom de + fichier. Il permet de saisir un nom correspondant a l'expression reguliere + ^[0-9a-z_-\.]+$. Cela permet d'eviter les problemes avec des caracteres + accentues, en majuscules, sortant de la table ASCII ou non-imprimables, ce + qui devrait ameliorer la portabilite des elements crees par l'utilisateur. +*/ +class QFileNameEdit : public QLineEdit { + Q_OBJECT + + // constructeurs, destructeur + public: + QFileNameEdit(QWidget * = 0); + QFileNameEdit(const QString &, QWidget * = 0); + virtual ~QFileNameEdit(); + private: + QFileNameEdit(const QFileNameEdit &); + + // methodes + public: + bool isEmpty(); + bool isValid(); + + private: + void init(); + void displayToolTip(); + + private slots: + void validationFailed(); + + // attributs + private: + QRegExp regexp_; + QETRegExpValidator *validator_; + QString tooltip_text_; +}; +#endif diff --git a/sources/qgimanager.cpp b/sources/qgimanager.cpp index 4bb0383e4..9ec283dc3 100644 --- a/sources/qgimanager.cpp +++ b/sources/qgimanager.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/qgimanager.h b/sources/qgimanager.h index 33ab7bb4c..3e74987ed 100644 --- a/sources/qgimanager.h +++ b/sources/qgimanager.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/recentfiles.cpp b/sources/recentfiles.cpp index df87b6d39..a07d8ebb9 100644 --- a/sources/recentfiles.cpp +++ b/sources/recentfiles.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -62,7 +62,7 @@ void RecentFiles::clear() { } /** - Sauvegarde les fichiers rcents dans la configuration + Sauvegarde les fichiers recents dans la configuration */ void RecentFiles::save() { saveFilesToSettings(); diff --git a/sources/recentfiles.h b/sources/recentfiles.h index cadd9e412..e5ce85f14 100644 --- a/sources/recentfiles.h +++ b/sources/recentfiles.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/terminal.cpp b/sources/terminal.cpp index d33c1bc08..e7a3ed27e 100644 --- a/sources/terminal.cpp +++ b/sources/terminal.cpp @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify @@ -60,7 +60,7 @@ void Terminal::initialise(QPointF pf, QET::Orientation o) { setAcceptsHoverEvents(true); setAcceptedMouseButtons(Qt::LeftButton); hovered = false; - setToolTip(QObject::tr("Borne")); + setToolTip(QObject::tr("Borne", "tooltip")); } /** diff --git a/sources/terminal.h b/sources/terminal.h index a6e145d9c..15914ff43 100644 --- a/sources/terminal.h +++ b/sources/terminal.h @@ -1,5 +1,5 @@ /* - Copyright 2006-2008 Xavier Guerrin + Copyright 2006-2009 Xavier Guerrin This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify diff --git a/sources/xmlelementdefinition.cpp b/sources/xmlelementdefinition.cpp new file mode 100644 index 000000000..930b2a024 --- /dev/null +++ b/sources/xmlelementdefinition.cpp @@ -0,0 +1,220 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#include "xmlelementdefinition.h" +#include "xmlelementscategory.h" +#include "xmlelementscollection.h" +#include "qet.h" +#include "qetproject.h" + +/** + Construit une definition d'element vide +*/ +XmlElementDefinition::XmlElementDefinition(const QString &name, XmlElementsCategory *category, XmlElementsCollection *collection) : + ElementDefinition(category, collection), + parent_category_(category) +{ + name_ = name; + QDomElement new_elmt = xml_element_.createElement("element"); + new_elmt.setAttribute("name", name_); + xml_element_.appendChild(new_elmt); + element_definition_ = xml_element_.createElement("definition"); + new_elmt.appendChild(element_definition_); +} + +/** + Construit une definition d'element a partir de sa representation XML +*/ +XmlElementDefinition::XmlElementDefinition(const QDomElement &xml_element, XmlElementsCategory *category, XmlElementsCollection *collection) : + ElementDefinition(category, collection), + parent_category_(category) +{ + xml_element_.appendChild(xml_element_.importNode(xml_element, true)); + reload(); +} + +/** + Destructeur +*/ +XmlElementDefinition::~XmlElementDefinition() { +} + +/** + @return la definition XML de l'element +*/ +QDomElement XmlElementDefinition::xml() { + return(element_definition_); +} + +/** + Change la definition XML de l'element + @param xml_element Nouvelle definition XML de l'element + @return true si l'operation s'est bien passee, false sinon +*/ +bool XmlElementDefinition::setXml(const QDomElement &xml_element) { + // oublie toute la structure XML + xml_element_.clear(); + + // cree la structure XML contenant le nom de l'element + QDomElement new_elmt = xml_element_.createElement("element"); + new_elmt.setAttribute("name", name_); + xml_element_.appendChild(new_elmt); + + // importe la nouvelle definition XML de l'element + element_definition_ = xml_element_.importNode(xml_element, true).toElement(); + new_elmt.appendChild(element_definition_); + is_null_ = false; + return(true); +} + +bool XmlElementDefinition::write() { + /* + Contrairement a un schema, cet objet ne contient pas deux versions de + l'element (element avant edition et element edite) Cette methode ne + fait donc rien d'autre qu'emettre le signal written(), le travail ayant + deja ete fait par la methode setXml(). + */ + emit(written()); + return(true); +} + +/** + @return true si l'element n'est pas exploitable (definition ou nom non + trouve) +*/ +bool XmlElementDefinition::isNull() const { + return(name_.isEmpty() || is_null_); +} + +/** + @return Le nom de cet element dans l'arborescence +*/ +QString XmlElementDefinition::pathName() const { + return(name_); +} + +/** + @return le chemin virtuel de cet element +*/ +QString XmlElementDefinition::virtualPath() { + // il n'est pas possible d'avoir un chemin virtuel sans appartenir a une collection + if (!hasParentCollection() || name_.isEmpty()) return(QString()); + + if (parent_category_) { + QString tmp(parent_category_ -> virtualPath()); + if (!tmp.isEmpty()) tmp += "/"; + return(tmp + name_); + } else { + return(name_); + } +} + +/** + Recharge le contenu de l'element +*/ +void XmlElementDefinition::reload() { + is_null_ = true; + + // on recupere le nom de l'element + QDomElement doc_elmt = xml_element_.documentElement(); + name_ = doc_elmt.attribute("name"); + if (name_.isEmpty()) return; + + // on recupere la definition de l'element + for (QDomNode node = doc_elmt.firstChild() ; !node.isNull() ; node = node.nextSibling()) { + if (!node.isElement()) continue; + QDomElement current_element = node.toElement(); + if (current_element.tagName() == "definition" && current_element.attribute("type") == "element") { + element_definition_ = current_element; + break; + } + } + + // l'element est nul si aucune definition n'a ete trouvee + is_null_ = (element_definition_.isNull()); +} + +/** + @return true si l'element existe, false sinon +*/ +bool XmlElementDefinition::exists() { + // la seule raison qu'un element aurait de ne pas exister est l'absence + // de nom + return(!name_.isEmpty()); +} + +/** + @return true si la categorie est accessible en lecture, false sinon +*/ +bool XmlElementDefinition::isReadable() { + // une categorie XML n'a aucune raison de ne pas etre accessible en lecture + return(true); +} + +/** + @return true si la categorie est accessible en ecriture, false sinon +*/ +bool XmlElementDefinition::isWritable() { + // une categorie XML peut etre en lecture seule si le projet auquel elle + // appartient l'est + if (QETProject *parent_project = project()) { + return(!parent_project -> isReadOnly()); + } else { + return(true); + } +} + +/** + Supprime l'element + @return true si l'operation s'est bien passee, false sinon +*/ +bool XmlElementDefinition::remove() { + removeContent(); + emit(removed(name_)); + return(true); +} + +/** + @return toujours false, car un element XML n'a pas de chemin de type + fichier +*/ +bool XmlElementDefinition::hasFilePath() { + // une categorie XML n'a pas de chemin de type fichier + return(false); +} + +/** + @return une chaine vide, car un element XML n'a pas de chemin de type + fichier +*/ +QString XmlElementDefinition::filePath() { + // une categorie XML n'a pas de chemin de type fichier + return(QString()); +} + +/** + Ne fait rien, car un element XML n'a pas de chemin de type fichier +*/ +void XmlElementDefinition::setFilePath(const QString &) { + // une categorie XML n'a pas de chemin de type fichier +} + +QDomElement XmlElementDefinition::writeXml(QDomDocument &xml_doc) const { + QDomElement element_elmt = xml_element_.documentElement(); + QDomNode new_node = xml_doc.importNode(element_elmt, true); + return(new_node.toElement()); +} diff --git a/sources/xmlelementdefinition.h b/sources/xmlelementdefinition.h new file mode 100644 index 000000000..af0773f26 --- /dev/null +++ b/sources/xmlelementdefinition.h @@ -0,0 +1,69 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef XML_ELEMENT_DEFINITION +#define XML_ELEMENT_DEFINITION +#include +#include "elementdefinition.h" +class XmlElementsCategory; +class XmlElementsCollection; +/** + Cette classe represente la definition d'un element extraite d'un + document XML (typiquement : un projet QET). +*/ +class XmlElementDefinition : public ElementDefinition { + Q_OBJECT + + public: + XmlElementDefinition(const QString &, XmlElementsCategory * = 0, XmlElementsCollection * = 0); + XmlElementDefinition(const QDomElement &, XmlElementsCategory * = 0, XmlElementsCollection * = 0); + virtual ~XmlElementDefinition(); + + private: + XmlElementDefinition(const XmlElementDefinition &); + + // methodes + public: + virtual QDomElement xml(); + virtual bool setXml(const QDomElement &); + virtual bool write(); + virtual bool isNull() const; + virtual QString pathName() const; + virtual QString virtualPath(); + virtual void reload(); + virtual bool exists(); + virtual bool isReadable(); + virtual bool isWritable(); + virtual bool remove(); + virtual bool hasFilePath(); + virtual QString filePath(); + virtual void setFilePath(const QString &); + virtual QDomElement writeXml(QDomDocument &) const; + + signals: + void written(); + void removed(const QString &); + + // attributs + private: + bool is_null_; + QString name_; + XmlElementsCategory *parent_category_; + QDomDocument xml_element_; + QDomElement element_definition_; +}; +#endif diff --git a/sources/xmlelementscategory.cpp b/sources/xmlelementscategory.cpp new file mode 100644 index 000000000..77087fd50 --- /dev/null +++ b/sources/xmlelementscategory.cpp @@ -0,0 +1,487 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#include "xmlelementscategory.h" +#include "xmlelementscollection.h" +#include "xmlelementdefinition.h" +#include "qetproject.h" + +/** + Cree une categorie XML vide + @param parent Categorie parente + @param collection Collection a laquelle cette categorie appartient +*/ +XmlElementsCategory::XmlElementsCategory(XmlElementsCategory *parent, XmlElementsCollection *collection) : + ElementsCategory(parent, collection), + xml_parent_collection_(collection), + xml_parent_category_(parent) +{ +} + +/** + Cree une categorie XML a partir d'un element XML + @param xml_element Element XML a analyser + @param parent Categorie parente + @param collection Collection a laquelle cette categorie appartient +*/ +XmlElementsCategory::XmlElementsCategory(const QDomElement &xml_element, XmlElementsCategory *parent, XmlElementsCollection *collection) : + ElementsCategory(parent, collection), + xml_parent_collection_(collection), + xml_parent_category_(parent) +{ + loadContent(xml_element); +} + +/** + Destructeur +*/ +XmlElementsCategory::~XmlElementsCategory() { + deleteContent(); +} + +/** + @return le nom de la categorie utilisable dans un chemin (virtuel ou reel) +*/ +QString XmlElementsCategory::pathName() const { + return(name_); +} + +/** + @return le chemin virtuel de la categorie, sans le protocole +*/ +QString XmlElementsCategory::virtualPath() { + // il n'est pas possible d'avoir un chemin virtuel sans appartenir a une collection + if (!hasParentCollection() || name_.isEmpty()) return(QString()); + + if (parent_category_) { + QString tmp(parent_category_ -> virtualPath()); + if (!tmp.isEmpty()) tmp += "/"; + return(tmp + name_); + } else { + return(name_); + } +} + +/** + @return toujours false, car une categorie XML n'a pas de chemin de type + fichier +*/ +bool XmlElementsCategory::hasFilePath() { + // une categorie XML n'a pas de chemin de type fichier + return(false); +} + +/** + @return une chaine vide, car une categorie XML n'a pas de chemin de type + fichier +*/ +QString XmlElementsCategory::filePath() { + // une categorie XML n'a pas de chemin de type fichier + return(QString()); +} + +/** + Ne fait rien, car une categorie XML n'a pas de chemin de type fichier +*/ +void XmlElementsCategory::setFilePath(const QString &) { + // une categorie XML n'a pas de chemin de type fichier +} + +/** + @return la liste des sous-categories de la categorie +*/ +QList XmlElementsCategory::categories() { + QList cat_list; + + QList keys(categories_.keys()); + qSort(keys.begin(), keys.end()); + foreach(QString key, keys) cat_list << categories_[key]; + return(cat_list); +} + +/** + @return la categorie correspondant au chemin virtuel cat_path, ou 0 en cas d'echec + @param cat_path Chemin virtuel de la categorie voulue +*/ +ElementsCategory *XmlElementsCategory::category(const QString &cat_path) { + // recupere les differentes parties du chemin + QString cat_path_(cat_path); + QStringList path_parts = cat_path_.split(QChar('/'), QString::SkipEmptyParts, Qt::CaseInsensitive); + + if (!path_parts.count()) { + return(this); + } else { + // a-t-on la premiere sous-categorie ? + if (!categories_.contains(path_parts.first())) { + return(0); + } + + // on a la premiere sous-categorie + ElementsCategory *first_sub_cat = categories_.value(path_parts.first()); + + if (path_parts.count() == 1) return(first_sub_cat); + + // on demande a la premiere sous-categorie de fournir la categorie finale + path_parts.removeFirst(); + return(first_sub_cat -> category(path_parts.join("/"))); + } +} + +/** + Cree une categorie. La categorie parente doit exister. + @param path chemin d'une categorie a creer sous la forme d'une adresse + virtuelle comme common://cat1/cat2/cat3 + @return la nouvelle categorie demandee, ou 0 en cas d'echec +*/ +ElementsCategory *XmlElementsCategory::createCategory(const QString &path) { + // on ne doit pas etre en lecture seule + if (!isWritable()) return(0); + + // recupere les differentes parties du chemin + QString cat_path_(path); + QStringList path_parts = cat_path_.split(QChar('/'), QString::SkipEmptyParts, Qt::CaseInsensitive); + + if (!path_parts.count()) { + // le chemin est vide, on renvoie 0 + return(0); + } else if (path_parts.count() == 1) { + // il n'y a plus qu'une categorie dans le chemin : il faut la creer ici + + // on verifie si la categorie n'existe pas deja + if (categories_.contains(path_parts[0])) { + return(categories_.value(path_parts[0])); + } + + // on cree un objet + XmlElementsCategory *new_category = new XmlElementsCategory( + this, + xml_parent_collection_ + ); + new_category -> name_ = path_parts[0]; + + // on l'integre dans la liste des sous-categories connues + categories_.insert(path_parts[0], new_category); + connect(new_category, SIGNAL(written()), this, SLOT(componentWritten())); + connect(new_category, SIGNAL(removed(const QString &)), this, SLOT(componentRemoved(const QString &))); + + // on le renvoie + return(new_category); + } else { + // il y a plusieurs categories dans le chemin : + // on delegue le travail a la premiere sous-categorie + + // a-t-on la premiere sous-categorie ? + if (!categories_.contains(path_parts.first())) { + return(0); + } + + // on a la premiere sous-categorie + ElementsCategory *first_sub_cat = categories_.value(path_parts.first()); + + // on demande a la premiere sous-categorie de fournir la categorie finale + path_parts.removeFirst(); + return(first_sub_cat -> category(path_parts.join("/"))); + } +} + +/** + @return la liste des elements de la categorie +*/ +QList XmlElementsCategory::elements() { + QList elmt_list; + + QList keys(elements_.keys()); + qSort(keys.begin(), keys.end()); + foreach(QString key, keys) elmt_list << elements_[key]; + return(elmt_list); +} + +/** + @return l'element correspondant au chemin virtuel elmt_path, ou 0 en cas d'echec + @param cat_path Chemin virtuel de l'element voulu +*/ +ElementDefinition *XmlElementsCategory::element(const QString &elmt_path) { + // recupere les differentes parties du chemin + QString elmt_path_(elmt_path); + QStringList path_parts = elmt_path_.split(QChar('/'), QString::SkipEmptyParts, Qt::CaseInsensitive); + + if (!path_parts.count()) { + // chemin vide + return(0); + } else if (path_parts.count() == 1) { + // seulement le nom de fichier + QString element_filename = path_parts.takeLast(); + if (!elements_.contains(element_filename)) { + return(0); + } else { + return(elements_.value(element_filename)); + } + } else { + // separe le nom de fichier du chemin de la categorie et recupere celle-ci + QString element_filename = path_parts.takeLast(); + ElementsCategory *elmt_cat = category(path_parts.join("/")); + if (!elmt_cat) { + return(0); + } else { + return(elmt_cat -> element(element_filename)); + } + } +} + +/** + Cree un element. La categorie parente doit exister. + @param path chemin d'un element a creer sous la forme d'une adresse + virtuelle comme common://cat1/cat2/cat3/dog.elmt + @return le nouvel element demande, ou 0 en cas d'echec +*/ +ElementDefinition *XmlElementsCategory::createElement(const QString &path) { + // on ne doit pas etre en lecture seule + if (!isWritable()) return(0); + + // recupere les differentes parties du chemin + QString cat_path_(path); + QStringList path_parts = cat_path_.split(QChar('/'), QString::SkipEmptyParts, Qt::CaseInsensitive); + + if (!path_parts.count()) { + // le chemin est vide, on renvoie 0 + return(0); + } else if (path_parts.count() == 1) { + // il n'y a plus que l'element dans le chemin : il faut le creer ici + + // on verifie si l'element n'existe pas deja + if (elements_.contains(path_parts[0])) { + return(elements_.value(path_parts[0])); + } + + // on cree un objet + XmlElementDefinition *new_element = new XmlElementDefinition( + path_parts[0], + this, + xml_parent_collection_ + ); + + // on l'integre dans la liste des elements connus + elements_.insert(path_parts[0], new_element); + connect(new_element, SIGNAL(written()), this, SLOT(componentWritten())); + connect(new_element, SIGNAL(removed(const QString &)), this, SLOT(componentRemoved(const QString &))); + + // on le renvoie + return(new_element); + } else { + // il y a plusieurs categories dans le chemin : + // on delegue le travail a la premiere sous-categorie + + // a-t-on la premiere sous-categorie ? + if (!categories_.contains(path_parts.first())) { + return(0); + } + + // on a la premiere sous-categorie + ElementsCategory *first_sub_cat = categories_.value(path_parts.first()); + + // on demande a la premiere sous-categorie de fournir la categorie finale + path_parts.removeFirst(); + return(first_sub_cat -> createElement(path_parts.join("/"))); + } +} + +/** + @return true si la categorie existe, false sinon +*/ +bool XmlElementsCategory::exists() { + // la seule raison qu'une categorie aurait de ne pas exister est l'absence + // de nom + return(!name_.isEmpty()); +} + +/** + @return true si la categorie est accessible en lecture, false sinon +*/ +bool XmlElementsCategory::isReadable() { + // une categorie XML n'a aucune raison de ne pas etre accessible en lecture + return(true); +} + +/** + @return true si la categorie est accessible en ecriture, false sinon +*/ +bool XmlElementsCategory::isWritable() { + // une categorie XML peut etre en lecture seule si le projet auquel elle + // appartient l'est + if (QETProject *parent_project = project()) { + return(!parent_project -> isReadOnly()); + } else { + return(true); + } +} + +/** + Cette methode ne fait rien. Recharger une categorie XML n'a pas vraiment de + sens. +*/ +void XmlElementsCategory::reload() { +} + +/** + Supprime la categorie et son contenu +*/ +bool XmlElementsCategory::remove() { + removeContent(); + emit(removed(name_)); + write(); + return(true); +} + +/** + Supprime le contenu de la categorie sans supprimer la categorie elle-meme. +*/ +bool XmlElementsCategory::removeContent() { + // suppression des sous-categories + foreach(QString cat_name, categories_.keys()) { + ElementsCategory *cat = categories_.value(cat_name); + if (cat -> remove()) { + categories_.take(cat_name); + delete cat; + } else { + return(false); + } + } + + // suppression des elements + foreach(QString elmt_name, elements_.keys()) { + ElementDefinition *elmt = elements_.value(elmt_name); + if (elmt -> remove()) { + elements_.take(elmt_name); + delete elmt; + } else { + return(false); + } + } + write(); + return(true); +} + +/** + Ecrit la categorie. + Comme il s'agit d'une categorie embarquee, cette methode emet simplement le + signal written pour indiquer qu'il faut enregistrer la collection / le + projet. +*/ +bool XmlElementsCategory::write() { + // indique que la categorie a ete changee + emit(written()); + return(true); +} + +/** + @return un Element XML decrivant la categorie et son contenu + @param xml_doc Document XML a utiliser pour creer l'element XML +*/ +QDomElement XmlElementsCategory::writeXml(QDomDocument &xml_doc) const { + QDomElement category_elmt = xml_doc.createElement("category"); + if (!isRootCategory()) { + category_elmt.setAttribute("name", name_); + category_elmt.appendChild(category_names.toXml(xml_doc)); + } + + foreach(XmlElementsCategory *subcat, categories_) { + category_elmt.appendChild(subcat -> writeXml(xml_doc)); + } + + foreach(XmlElementDefinition *elmt, elements_) { + category_elmt.appendChild(elmt -> writeXml(xml_doc)); + } + + return(category_elmt); +} + +/** + Gere le fait qu'une sous-categorie ou un element ait ete enregistre +*/ +void XmlElementsCategory::componentWritten() { + write(); +} + +/** + Gere le fait qu'une sous-categorie ou un element ait ete supprime + @param item Element ou categorie supprime +*/ +void XmlElementsCategory::componentRemoved(const QString &path) { + if (elements_.contains(path)) { + elements_.remove(path); + write(); + } else if (categories_.contains(path)) { + categories_.remove(path); + write(); + } +} + +/** + Supprime le contenu de la categorie en memoire +*/ +void XmlElementsCategory::deleteContent() { + // suppression des elements + foreach(QString elmt_name, elements_.keys()) { + XmlElementDefinition *elmt = elements_.take(elmt_name); + delete elmt; + } + + // suppression des categories + foreach(QString cat_name, categories_.keys()) { + XmlElementsCategory *cat = categories_.take(cat_name); + delete cat; + } +} + +/** + Charge dans cet objet le contenu de la categorie a partir d'un element XML. + @param xml_element element XML a analyser +*/ +void XmlElementsCategory::loadContent(const QDomElement &xml_element) { + deleteContent(); + name_.clear(); + category_names.clearNames(); + + // charge le nom de la categorie pour son chemin virtuel + name_ = xml_element.attribute("name"); + + // charge les noms affiches de la categorie + category_names.fromXml(xml_element); + + // charge les categories et elements + QDomElement current_element; + for (QDomNode node = xml_element.firstChild() ; !node.isNull() ; node = node.nextSibling()) { + if (!node.isElement()) continue; + current_element = node.toElement(); + + // les sous-categories et elements sans nom sont ignores + if (!current_element.hasAttribute("name")) continue; + + if (current_element.tagName() == "category") { + XmlElementsCategory *new_category = new XmlElementsCategory(current_element, this, xml_parent_collection_); + categories_.insert(current_element.attribute("name"), new_category); + connect(new_category, SIGNAL(written()), this, SLOT(componentWritten())); + connect(new_category, SIGNAL(removed(const QString &)), this, SLOT(componentRemoved(const QString &))); + } else if (current_element.tagName() == "element") { + + XmlElementDefinition *new_element = new XmlElementDefinition(current_element, this, xml_parent_collection_); + elements_.insert(current_element.attribute("name"), new_element); + connect(new_element, SIGNAL(written()), this, SLOT(componentWritten())); + connect(new_element, SIGNAL(removed(const QString &)), this, SLOT(componentRemoved(const QString &))); + } + } +} diff --git a/sources/xmlelementscategory.h b/sources/xmlelementscategory.h new file mode 100644 index 000000000..bdcdcd4a6 --- /dev/null +++ b/sources/xmlelementscategory.h @@ -0,0 +1,95 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef XML_ELEMENTS_CATEGORY +#define XML_ELEMENTS_CATEGORY +#include +#include "elementscategory.h" +class XmlElementsCollection; +class XmlElementDefinition; +/** + Cette classe represente une categorie d'elements issue d'un document XML + (typiquement : un projet QET). +*/ +class XmlElementsCategory : public ElementsCategory { + Q_OBJECT + + // constructeurs, destructeur + public: + XmlElementsCategory(XmlElementsCategory * = 0, XmlElementsCollection * = 0); + XmlElementsCategory(const QDomElement &, XmlElementsCategory * = 0, XmlElementsCollection * = 0); + virtual ~XmlElementsCategory(); + + private: + XmlElementsCategory(const XmlElementsCategory &); + + // methodes + public: + virtual QString pathName() const; + virtual QString virtualPath(); + + virtual QString filePath(); + virtual bool hasFilePath(); + virtual void setFilePath(const QString &); + + virtual QList categories(); + virtual ElementsCategory *category(const QString &); + virtual ElementsCategory *createCategory(const QString &); + + virtual QList elements(); + virtual ElementDefinition *element(const QString &); + virtual ElementDefinition *createElement(const QString &); + + virtual bool exists(); + virtual bool isReadable(); + virtual bool isWritable(); + + virtual void reload(); + virtual bool remove(); + virtual bool removeContent(); + virtual bool write(); + + virtual QDomElement writeXml(QDomDocument &) const; + + public slots: + void componentWritten(); + void componentRemoved(const QString &path); + + signals: + void written(); + void removed(const QString &); + + private: + void deleteContent(); + void loadContent(const QDomElement &); + + // attributs + protected: + /// Collection parente, de type XML + XmlElementsCollection *xml_parent_collection_; + /// Categorie parente, de type XML + XmlElementsCategory *xml_parent_category_; + /// Sous-categories contenues dans cette categorie + QHash categories_; + /// Elements contenus dans cette categorie + QHash elements_; + /// Nom de cette categorie dans l'arborescence + QString name_; + /// Description XML de cette categorie + QDomDocument xml_element_; +}; +#endif diff --git a/sources/xmlelementscollection.cpp b/sources/xmlelementscollection.cpp new file mode 100644 index 000000000..54c318be7 --- /dev/null +++ b/sources/xmlelementscollection.cpp @@ -0,0 +1,135 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#include "xmlelementscollection.h" +#include "xmlelementscategory.h" +#include "qetproject.h" + +/** + Construit une collection vide + @param parent Item parent +*/ +XmlElementsCollection::XmlElementsCollection(ElementsCollectionItem *parent) : + ElementsCollection(parent) +{ + protocol_ = "unknown"; + project_ = 0; + // cree une categorie racine vide + root = new XmlElementsCategory(0, this); + connect(root, SIGNAL(written()), this, SLOT(componentWritten())); +} + +/** + Construit une collection a partir d'un element XML suppose la decrire + @param xml_element Element XML decrivant la collection + @param parent Item parent +*/ +XmlElementsCollection::XmlElementsCollection(const QDomElement &xml_element, ElementsCollectionItem *parent) : + ElementsCollection(parent) +{ + protocol_ = "unknown"; + project_ = 0; + // cree sa categorie racine a partir de l'element XML + root = new XmlElementsCategory(xml_element, 0, this); + connect(root, SIGNAL(written()), this, SLOT(componentWritten())); +} + +/** + Destructeur +*/ +XmlElementsCollection::~XmlElementsCollection() { + deleteContent(); +} + +ElementsCategory *XmlElementsCollection::rootCategory() { + return(root); +} + +/** + @return toujours false ; une collection XML n'est representee nul part sur + le systeme de fichiers. +*/ +bool XmlElementsCollection::hasFilePath() { + return(false); +} + +/** + @return le chemin du repertoire representant cette collection +*/ +QString XmlElementsCollection::filePath() { + return(QString()); +} + +/** + Ne fait rien - une collection XML n'est representee nul part sur le systeme + de fichiers. +*/ +void XmlElementsCollection::setFilePath(const QString &) { +} + +void XmlElementsCollection::reload() { + if (root) root -> reload(); +} + +/** + @return toujours true +*/ +bool XmlElementsCollection::exists() { + return(true); +} + +/** + @return true si la collection est accessible en lecture +*/ +bool XmlElementsCollection::isReadable() { + // une collection XML n'a aucune raison de ne pas etre accessible en lecture + return(true); +} + +bool XmlElementsCollection::isWritable() { + // une collection XML peut etre en lecture seule si le projet auquel elle + // appartient l'est + if (QETProject *parent_project = project()) { + return(!parent_project -> isReadOnly()); + } else { + return(true); + } +} + +bool XmlElementsCollection::write() { + emit(written()); + return(true); +} + +QDomElement XmlElementsCollection::writeXml(QDomDocument &xml_doc) const { + QDomElement collection_elmt = root -> writeXml(xml_doc); + collection_elmt.setTagName("collection"); + xml_doc.appendChild(collection_elmt); + return(collection_elmt); +} + +void XmlElementsCollection::componentWritten() { + write(); +} + +/** + Supprime le contenu en memoire de cette collection +*/ +void XmlElementsCollection::deleteContent() { + delete root; + root = 0; +} diff --git a/sources/xmlelementscollection.h b/sources/xmlelementscollection.h new file mode 100644 index 000000000..4fb15244a --- /dev/null +++ b/sources/xmlelementscollection.h @@ -0,0 +1,66 @@ +/* + Copyright 2006-2009 Xavier Guerrin + 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 . +*/ +#ifndef XML_ELEMENTS_COLLECTION +#define XML_ELEMENTS_COLLECTION +#include +#include "elementscollection.h" +class XmlElementsCategory; +/** + Cette classe represente une collection d'elements accessible via un + document XML (exemple : un projet QET). Typiquement, il s'agit de la + collection embarquee d'un projet QET. +*/ +class XmlElementsCollection : public ElementsCollection { + Q_OBJECT + public: + // constructeurs, destructeur + XmlElementsCollection(ElementsCollectionItem * = 0); + XmlElementsCollection(const QDomElement &, ElementsCollectionItem * = 0); + virtual ~XmlElementsCollection(); + + private: + XmlElementsCollection(const XmlElementsCollection &); + + // methodes + public: + virtual ElementsCategory *rootCategory(); + virtual bool hasFilePath(); + virtual QString filePath(); + virtual void setFilePath(const QString &); + virtual void reload(); + virtual bool exists(); + virtual bool isReadable(); + virtual bool isWritable(); + virtual bool write(); + + virtual QDomElement writeXml(QDomDocument &) const; + + public slots: + void componentWritten(); + + signals: + void written(); + + private: + void deleteContent(); + + // attributs + private: + XmlElementsCategory *root; +}; +#endif