Cura.qml 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. // Copyright (c) 2018 Ultimaker B.V.
  2. // Cura is released under the terms of the LGPLv3 or higher.
  3. import QtQuick 2.7
  4. import QtQuick.Controls 1.4
  5. import QtQuick.Controls.Styles 1.4
  6. import QtQuick.Layouts 1.1
  7. import QtQuick.Dialogs 1.2
  8. import UM 1.3 as UM
  9. import Cura 1.1 as Cura
  10. import "Dialogs"
  11. import "Menus"
  12. import "MainWindow"
  13. UM.MainWindow
  14. {
  15. id: base
  16. // Cura application window title
  17. title: catalog.i18nc("@title:window", "Ultimaker Cura")
  18. backgroundColor: UM.Theme.getColor("viewport_background")
  19. UM.I18nCatalog
  20. {
  21. id: catalog
  22. name: "cura"
  23. }
  24. function showTooltip(item, position, text)
  25. {
  26. tooltip.text = text;
  27. position = item.mapToItem(backgroundItem, position.x - UM.Theme.getSize("default_arrow").width, position.y);
  28. tooltip.show(position);
  29. }
  30. function hideTooltip()
  31. {
  32. tooltip.hide();
  33. }
  34. Component.onCompleted:
  35. {
  36. CuraApplication.setMinimumWindowSize(UM.Theme.getSize("window_minimum_size"))
  37. // Workaround silly issues with QML Action's shortcut property.
  38. //
  39. // Currently, there is no way to define shortcuts as "Application Shortcut".
  40. // This means that all Actions are "Window Shortcuts". The code for this
  41. // implements a rather naive check that just checks if any of the action's parents
  42. // are a window. Since the "Actions" object is a singleton it has no parent by
  43. // default. If we set its parent to something contained in this window, the
  44. // shortcut will activate properly because one of its parents is a window.
  45. //
  46. // This has been fixed for QtQuick Controls 2 since the Shortcut item has a context property.
  47. Cura.Actions.parent = backgroundItem
  48. CuraApplication.purgeWindows()
  49. }
  50. Item
  51. {
  52. id: backgroundItem
  53. anchors.fill: parent
  54. signal hasMesh(string name) //this signal sends the filebase name so it can be used for the JobSpecs.qml
  55. function getMeshName(path)
  56. {
  57. //takes the path the complete path of the meshname and returns only the filebase
  58. var fileName = path.slice(path.lastIndexOf("/") + 1)
  59. var fileBase = fileName.slice(0, fileName.indexOf("."))
  60. return fileBase
  61. }
  62. //DeleteSelection on the keypress backspace event
  63. Keys.onPressed:
  64. {
  65. if (event.key == Qt.Key_Backspace)
  66. {
  67. Cura.Actions.deleteSelection.trigger()
  68. }
  69. }
  70. ApplicationMenu
  71. {
  72. id: applicationMenu
  73. window: base
  74. }
  75. MainWindowHeader
  76. {
  77. id: mainWindowHeader
  78. anchors
  79. {
  80. left: parent.left
  81. right: parent.right
  82. top: applicationMenu.bottom
  83. }
  84. }
  85. Item
  86. {
  87. id: contentItem
  88. anchors
  89. {
  90. top: mainWindowHeader.bottom
  91. bottom: parent.bottom
  92. left: parent.left
  93. right: parent.right
  94. }
  95. Keys.forwardTo: applicationMenu
  96. DropArea
  97. {
  98. // The drop area is here to handle files being dropped onto Cura.
  99. anchors.fill: parent
  100. onDropped:
  101. {
  102. if (drop.urls.length > 0)
  103. {
  104. var nonPackages = [];
  105. for (var i = 0; i < drop.urls.length; i++)
  106. {
  107. var filename = drop.urls[i];
  108. if (filename.endsWith(".curapackage"))
  109. {
  110. // Try to install plugin & close.
  111. CuraApplication.getPackageManager().installPackageViaDragAndDrop(filename);
  112. packageInstallDialog.text = catalog.i18nc("@label", "This package will be installed after restarting.");
  113. packageInstallDialog.icon = StandardIcon.Information;
  114. packageInstallDialog.open();
  115. }
  116. else
  117. {
  118. nonPackages.push(filename);
  119. }
  120. }
  121. openDialog.handleOpenFileUrls(nonPackages);
  122. }
  123. }
  124. }
  125. Rectangle
  126. {
  127. anchors
  128. {
  129. left: parent.left
  130. right: parent.right
  131. top: parent.top
  132. }
  133. visible: stageMenu.source != ""
  134. height: Math.round(UM.Theme.getSize("stage_menu").height / 2)
  135. color: UM.Theme.getColor("main_window_header_background")
  136. }
  137. Connections
  138. {
  139. target: stageMenu.item
  140. onShowTooltip: base.showTooltip(item, location, text)
  141. onHideTooltip: base.hideTooltip()
  142. }
  143. JobSpecs
  144. {
  145. id: jobSpecs
  146. anchors
  147. {
  148. bottom: parent.bottom
  149. bottomMargin: UM.Theme.getSize("default_margin").height
  150. }
  151. }
  152. Toolbar
  153. {
  154. // The toolbar is the left bar that is populated by all the tools (which are dynamicly populated by
  155. // plugins)
  156. id: toolbar
  157. property int mouseX: base.mouseX
  158. property int mouseY: base.mouseY
  159. anchors
  160. {
  161. verticalCenter: parent.verticalCenter
  162. left: parent.left
  163. }
  164. }
  165. ObjectsList
  166. {
  167. id: objectsList
  168. visible: UM.Preferences.getValue("cura/use_multi_build_plate")
  169. anchors
  170. {
  171. bottom: parent.bottom
  172. left: parent.left
  173. }
  174. }
  175. ViewOrientationControls
  176. {
  177. id: viewOrientationControls
  178. anchors
  179. {
  180. left: parent.left
  181. margins: UM.Theme.getSize("default_margin").width
  182. bottom: parent.bottom
  183. }
  184. }
  185. Loader
  186. {
  187. id: viewPanel
  188. anchors.bottom: viewModeButton.top
  189. anchors.topMargin: UM.Theme.getSize("default_margin").height
  190. anchors.right: viewModeButton.right
  191. property var buttonTarget: Qt.point(viewModeButton.x + Math.round(viewModeButton.width / 2), viewModeButton.y + Math.round(viewModeButton.height / 2))
  192. height: childrenRect.height
  193. width: childrenRect.width
  194. source: UM.ActiveView.valid ? UM.ActiveView.activeViewPanel : ""
  195. }
  196. Cura.ActionPanelWidget
  197. {
  198. anchors.right: parent.right
  199. anchors.bottom: parent.bottom
  200. width: UM.Theme.getSize("action_panel_widget").width
  201. anchors.rightMargin: UM.Theme.getSize("thick_margin").width
  202. anchors.bottomMargin: UM.Theme.getSize("thick_margin").height
  203. onShowTooltip:
  204. {
  205. base.showTooltip(item, location, text)
  206. }
  207. onHideTooltip:
  208. {
  209. base.hideTooltip()
  210. }
  211. }
  212. Loader
  213. {
  214. // A stage can control this area. If nothing is set, it will therefore show the 3D view.
  215. id: main
  216. anchors.fill: parent
  217. source: UM.Controller.activeStage != null ? UM.Controller.activeStage.mainComponent : ""
  218. }
  219. Loader
  220. {
  221. // The stage menu is, as the name implies, a menu that is defined by the active stage.
  222. // Note that this menu does not need to be set at all! It's perfectly acceptable to have a stage
  223. // without this menu!
  224. id: stageMenu
  225. anchors
  226. {
  227. left: parent.left
  228. right: parent.right
  229. top: parent.top
  230. }
  231. height: UM.Theme.getSize("stage_menu").height
  232. source: UM.Controller.activeStage != null ? UM.Controller.activeStage.stageMenuComponent : ""
  233. }
  234. UM.MessageStack
  235. {
  236. anchors
  237. {
  238. horizontalCenter: parent.horizontalCenter
  239. top: parent.verticalCenter
  240. bottom: parent.bottom
  241. bottomMargin: UM.Theme.getSize("default_margin").height
  242. }
  243. }
  244. }
  245. SidebarTooltip
  246. {
  247. id: tooltip
  248. }
  249. }
  250. UM.PreferencesDialog
  251. {
  252. id: preferences
  253. Component.onCompleted:
  254. {
  255. //; Remove & re-add the general page as we want to use our own instead of uranium standard.
  256. removePage(0);
  257. insertPage(0, catalog.i18nc("@title:tab","General"), Qt.resolvedUrl("Preferences/GeneralPage.qml"));
  258. removePage(1);
  259. insertPage(1, catalog.i18nc("@title:tab","Settings"), Qt.resolvedUrl("Preferences/SettingVisibilityPage.qml"));
  260. insertPage(2, catalog.i18nc("@title:tab", "Printers"), Qt.resolvedUrl("Preferences/MachinesPage.qml"));
  261. insertPage(3, catalog.i18nc("@title:tab", "Materials"), Qt.resolvedUrl("Preferences/Materials/MaterialsPage.qml"));
  262. insertPage(4, catalog.i18nc("@title:tab", "Profiles"), Qt.resolvedUrl("Preferences/ProfilesPage.qml"));
  263. // Remove plug-ins page because we will use the shiny new plugin browser:
  264. removePage(5);
  265. //Force refresh
  266. setPage(0);
  267. }
  268. onVisibleChanged:
  269. {
  270. // When the dialog closes, switch to the General page.
  271. // This prevents us from having a heavy page like Setting Visiblity active in the background.
  272. setPage(0);
  273. }
  274. }
  275. Connections
  276. {
  277. target: Cura.Actions.preferences
  278. onTriggered: preferences.visible = true
  279. }
  280. Connections
  281. {
  282. target: CuraApplication
  283. onShowPreferencesWindow: preferences.visible = true
  284. }
  285. Connections
  286. {
  287. target: Cura.Actions.addProfile
  288. onTriggered:
  289. {
  290. preferences.show();
  291. preferences.setPage(4);
  292. // Create a new profile after a very short delay so the preference page has time to initiate
  293. createProfileTimer.start();
  294. }
  295. }
  296. Connections
  297. {
  298. target: Cura.Actions.configureMachines
  299. onTriggered:
  300. {
  301. preferences.visible = true;
  302. preferences.setPage(2);
  303. }
  304. }
  305. Connections
  306. {
  307. target: Cura.Actions.manageProfiles
  308. onTriggered:
  309. {
  310. preferences.visible = true;
  311. preferences.setPage(4);
  312. }
  313. }
  314. Connections
  315. {
  316. target: Cura.Actions.manageMaterials
  317. onTriggered:
  318. {
  319. preferences.visible = true;
  320. preferences.setPage(3)
  321. }
  322. }
  323. Connections
  324. {
  325. target: Cura.Actions.configureSettingVisibility
  326. onTriggered:
  327. {
  328. preferences.visible = true;
  329. preferences.setPage(1);
  330. if(source && source.key)
  331. {
  332. preferences.getCurrentItem().scrollToSection(source.key);
  333. }
  334. }
  335. }
  336. Timer
  337. {
  338. id: createProfileTimer
  339. repeat: false
  340. interval: 1
  341. onTriggered: preferences.getCurrentItem().createProfile()
  342. }
  343. // BlurSettings is a way to force the focus away from any of the setting items.
  344. // We need to do this in order to keep the bindings intact.
  345. Connections
  346. {
  347. target: Cura.MachineManager
  348. onBlurSettings:
  349. {
  350. contentItem.forceActiveFocus()
  351. }
  352. }
  353. ContextMenu
  354. {
  355. id: contextMenu
  356. }
  357. onPreClosing:
  358. {
  359. close.accepted = CuraApplication.getIsAllChecksPassed();
  360. if (!close.accepted)
  361. {
  362. CuraApplication.checkAndExitApplication();
  363. }
  364. }
  365. MessageDialog
  366. {
  367. id: exitConfirmationDialog
  368. title: catalog.i18nc("@title:window", "Closing Cura")
  369. text: catalog.i18nc("@label", "Are you sure you want to exit Cura?")
  370. icon: StandardIcon.Question
  371. modality: Qt.ApplicationModal
  372. standardButtons: StandardButton.Yes | StandardButton.No
  373. onYes: CuraApplication.callConfirmExitDialogCallback(true)
  374. onNo: CuraApplication.callConfirmExitDialogCallback(false)
  375. onRejected: CuraApplication.callConfirmExitDialogCallback(false)
  376. onVisibilityChanged:
  377. {
  378. if (!visible)
  379. {
  380. // reset the text to default because other modules may change the message text.
  381. text = catalog.i18nc("@label", "Are you sure you want to exit Cura?");
  382. }
  383. }
  384. }
  385. Connections
  386. {
  387. target: CuraApplication
  388. onShowConfirmExitDialog:
  389. {
  390. exitConfirmationDialog.text = message;
  391. exitConfirmationDialog.open();
  392. }
  393. }
  394. Connections
  395. {
  396. target: Cura.Actions.quit
  397. onTriggered: CuraApplication.checkAndExitApplication();
  398. }
  399. Connections
  400. {
  401. target: Cura.Actions.toggleFullScreen
  402. onTriggered: base.toggleFullscreen();
  403. }
  404. FileDialog
  405. {
  406. id: openDialog;
  407. //: File open dialog title
  408. title: catalog.i18nc("@title:window","Open file(s)")
  409. modality: UM.Application.platform == "linux" ? Qt.NonModal : Qt.WindowModal;
  410. selectMultiple: true
  411. nameFilters: UM.MeshFileHandler.supportedReadFileTypes;
  412. folder: CuraApplication.getDefaultPath("dialog_load_path")
  413. onAccepted:
  414. {
  415. // Because several implementations of the file dialog only update the folder
  416. // when it is explicitly set.
  417. var f = folder;
  418. folder = f;
  419. CuraApplication.setDefaultPath("dialog_load_path", folder);
  420. handleOpenFileUrls(fileUrls);
  421. }
  422. // Yeah... I know... it is a mess to put all those things here.
  423. // There are lots of user interactions in this part of the logic, such as showing a warning dialog here and there,
  424. // etc. This means it will come back and forth from time to time between QML and Python. So, separating the logic
  425. // and view here may require more effort but make things more difficult to understand.
  426. function handleOpenFileUrls(fileUrlList)
  427. {
  428. // look for valid project files
  429. var projectFileUrlList = [];
  430. var hasGcode = false;
  431. var nonGcodeFileList = [];
  432. for (var i in fileUrlList)
  433. {
  434. var endsWithG = /\.g$/;
  435. var endsWithGcode = /\.gcode$/;
  436. if (endsWithG.test(fileUrlList[i]) || endsWithGcode.test(fileUrlList[i]))
  437. {
  438. continue;
  439. }
  440. else if (CuraApplication.checkIsValidProjectFile(fileUrlList[i]))
  441. {
  442. projectFileUrlList.push(fileUrlList[i]);
  443. }
  444. nonGcodeFileList.push(fileUrlList[i]);
  445. }
  446. hasGcode = nonGcodeFileList.length < fileUrlList.length;
  447. // show a warning if selected multiple files together with Gcode
  448. var hasProjectFile = projectFileUrlList.length > 0;
  449. var selectedMultipleFiles = fileUrlList.length > 1;
  450. if (selectedMultipleFiles && hasGcode)
  451. {
  452. infoMultipleFilesWithGcodeDialog.selectedMultipleFiles = selectedMultipleFiles;
  453. infoMultipleFilesWithGcodeDialog.hasProjectFile = hasProjectFile;
  454. infoMultipleFilesWithGcodeDialog.fileUrls = nonGcodeFileList.slice();
  455. infoMultipleFilesWithGcodeDialog.projectFileUrlList = projectFileUrlList.slice();
  456. infoMultipleFilesWithGcodeDialog.open();
  457. }
  458. else
  459. {
  460. handleOpenFiles(selectedMultipleFiles, hasProjectFile, fileUrlList, projectFileUrlList);
  461. }
  462. }
  463. function handleOpenFiles(selectedMultipleFiles, hasProjectFile, fileUrlList, projectFileUrlList)
  464. {
  465. // we only allow opening one project file
  466. if (selectedMultipleFiles && hasProjectFile)
  467. {
  468. openFilesIncludingProjectsDialog.fileUrls = fileUrlList.slice();
  469. openFilesIncludingProjectsDialog.show();
  470. return;
  471. }
  472. if (hasProjectFile)
  473. {
  474. var projectFile = projectFileUrlList[0];
  475. // check preference
  476. var choice = UM.Preferences.getValue("cura/choice_on_open_project");
  477. if (choice == "open_as_project")
  478. {
  479. openFilesIncludingProjectsDialog.loadProjectFile(projectFile);
  480. }
  481. else if (choice == "open_as_model")
  482. {
  483. openFilesIncludingProjectsDialog.loadModelFiles([projectFile].slice());
  484. }
  485. else // always ask
  486. {
  487. // ask whether to open as project or as models
  488. askOpenAsProjectOrModelsDialog.fileUrl = projectFile;
  489. askOpenAsProjectOrModelsDialog.show();
  490. }
  491. }
  492. else
  493. {
  494. openFilesIncludingProjectsDialog.loadModelFiles(fileUrlList.slice());
  495. }
  496. }
  497. }
  498. MessageDialog
  499. {
  500. id: packageInstallDialog
  501. title: catalog.i18nc("@window:title", "Install Package");
  502. standardButtons: StandardButton.Ok
  503. modality: Qt.ApplicationModal
  504. }
  505. MessageDialog
  506. {
  507. id: infoMultipleFilesWithGcodeDialog
  508. title: catalog.i18nc("@title:window", "Open File(s)")
  509. icon: StandardIcon.Information
  510. standardButtons: StandardButton.Ok
  511. text: catalog.i18nc("@text:window", "We have found one or more G-Code files within the files you have selected. You can only open one G-Code file at a time. If you want to open a G-Code file, please just select only one.")
  512. property var selectedMultipleFiles
  513. property var hasProjectFile
  514. property var fileUrls
  515. property var projectFileUrlList
  516. onAccepted:
  517. {
  518. openDialog.handleOpenFiles(selectedMultipleFiles, hasProjectFile, fileUrls, projectFileUrlList);
  519. }
  520. }
  521. Connections
  522. {
  523. target: Cura.Actions.open
  524. onTriggered: openDialog.open()
  525. }
  526. OpenFilesIncludingProjectsDialog
  527. {
  528. id: openFilesIncludingProjectsDialog
  529. }
  530. AskOpenAsProjectOrModelsDialog
  531. {
  532. id: askOpenAsProjectOrModelsDialog
  533. }
  534. Connections
  535. {
  536. target: CuraApplication
  537. onOpenProjectFile:
  538. {
  539. askOpenAsProjectOrModelsDialog.fileUrl = project_file;
  540. askOpenAsProjectOrModelsDialog.show();
  541. }
  542. }
  543. Connections
  544. {
  545. target: Cura.Actions.showProfileFolder
  546. onTriggered:
  547. {
  548. var path = UM.Resources.getPath(UM.Resources.Preferences, "");
  549. if(Qt.platform.os == "windows") {
  550. path = path.replace(/\\/g,"/");
  551. }
  552. Qt.openUrlExternally(path);
  553. if(Qt.platform.os == "linux") {
  554. Qt.openUrlExternally(UM.Resources.getPath(UM.Resources.Resources, ""));
  555. }
  556. }
  557. }
  558. AddMachineDialog
  559. {
  560. id: addMachineDialog
  561. onMachineAdded:
  562. {
  563. machineActionsWizard.firstRun = addMachineDialog.firstRun
  564. machineActionsWizard.start(id)
  565. }
  566. }
  567. // Dialog to handle first run machine actions
  568. UM.Wizard
  569. {
  570. id: machineActionsWizard;
  571. title: catalog.i18nc("@title:window", "Add Printer")
  572. property var machine;
  573. function start(id)
  574. {
  575. var actions = Cura.MachineActionManager.getFirstStartActions(id)
  576. resetPages() // Remove previous pages
  577. for (var i = 0; i < actions.length; i++)
  578. {
  579. actions[i].displayItem.reset()
  580. machineActionsWizard.appendPage(actions[i].displayItem, catalog.i18nc("@title", actions[i].label));
  581. }
  582. //Only start if there are actions to perform.
  583. if (actions.length > 0)
  584. {
  585. machineActionsWizard.currentPage = 0;
  586. show()
  587. }
  588. }
  589. }
  590. MessageDialog
  591. {
  592. id: messageDialog
  593. modality: Qt.ApplicationModal
  594. onAccepted: CuraApplication.messageBoxClosed(clickedButton)
  595. onApply: CuraApplication.messageBoxClosed(clickedButton)
  596. onDiscard: CuraApplication.messageBoxClosed(clickedButton)
  597. onHelp: CuraApplication.messageBoxClosed(clickedButton)
  598. onNo: CuraApplication.messageBoxClosed(clickedButton)
  599. onRejected: CuraApplication.messageBoxClosed(clickedButton)
  600. onReset: CuraApplication.messageBoxClosed(clickedButton)
  601. onYes: CuraApplication.messageBoxClosed(clickedButton)
  602. }
  603. Connections
  604. {
  605. target: CuraApplication
  606. onShowMessageBox:
  607. {
  608. messageDialog.title = title
  609. messageDialog.text = text
  610. messageDialog.informativeText = informativeText
  611. messageDialog.detailedText = detailedText
  612. messageDialog.standardButtons = buttons
  613. messageDialog.icon = icon
  614. messageDialog.visible = true
  615. }
  616. }
  617. DiscardOrKeepProfileChangesDialog
  618. {
  619. id: discardOrKeepProfileChangesDialog
  620. }
  621. Connections
  622. {
  623. target: CuraApplication
  624. onShowDiscardOrKeepProfileChanges:
  625. {
  626. discardOrKeepProfileChangesDialog.show()
  627. }
  628. }
  629. Connections
  630. {
  631. target: Cura.Actions.addMachine
  632. onTriggered: addMachineDialog.visible = true;
  633. }
  634. AboutDialog
  635. {
  636. id: aboutDialog
  637. }
  638. Connections
  639. {
  640. target: Cura.Actions.about
  641. onTriggered: aboutDialog.visible = true;
  642. }
  643. Connections
  644. {
  645. target: CuraApplication
  646. onRequestAddPrinter:
  647. {
  648. addMachineDialog.visible = true
  649. addMachineDialog.firstRun = false
  650. }
  651. }
  652. Timer
  653. {
  654. id: startupTimer;
  655. interval: 100;
  656. repeat: false;
  657. running: true;
  658. onTriggered:
  659. {
  660. if(!base.visible)
  661. {
  662. base.visible = true;
  663. }
  664. // check later if the user agreement dialog has been closed
  665. if (CuraApplication.needToShowUserAgreement)
  666. {
  667. restart();
  668. }
  669. else if(Cura.MachineManager.activeMachine == null)
  670. {
  671. addMachineDialog.open();
  672. }
  673. }
  674. }
  675. }