Cura.qml 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773
  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. visible: CuraApplication.platformActivity
  165. }
  166. ObjectsList
  167. {
  168. id: objectsList
  169. visible: UM.Preferences.getValue("cura/use_multi_build_plate")
  170. anchors
  171. {
  172. bottom: viewOrientationControls.top
  173. left: toolbar.right
  174. margins: UM.Theme.getSize("default_margin").width
  175. }
  176. }
  177. ViewOrientationControls
  178. {
  179. id: viewOrientationControls
  180. anchors
  181. {
  182. left: parent.left
  183. margins: UM.Theme.getSize("default_margin").width
  184. bottom: parent.bottom
  185. }
  186. }
  187. Loader
  188. {
  189. id: viewPanel
  190. anchors.bottom: viewModeButton.top
  191. anchors.topMargin: UM.Theme.getSize("default_margin").height
  192. anchors.right: viewModeButton.right
  193. property var buttonTarget: Qt.point(viewModeButton.x + Math.round(viewModeButton.width / 2), viewModeButton.y + Math.round(viewModeButton.height / 2))
  194. height: childrenRect.height
  195. width: childrenRect.width
  196. source: UM.ActiveView.valid ? UM.ActiveView.activeViewPanel : ""
  197. }
  198. Cura.ActionPanelWidget
  199. {
  200. anchors.right: parent.right
  201. anchors.bottom: parent.bottom
  202. anchors.rightMargin: UM.Theme.getSize("thick_margin").width
  203. anchors.bottomMargin: UM.Theme.getSize("thick_margin").height
  204. visible: CuraApplication.platformActivity
  205. }
  206. Loader
  207. {
  208. // A stage can control this area. If nothing is set, it will therefore show the 3D view.
  209. id: main
  210. anchors.fill: parent
  211. source: UM.Controller.activeStage != null ? UM.Controller.activeStage.mainComponent : ""
  212. }
  213. Loader
  214. {
  215. // The stage menu is, as the name implies, a menu that is defined by the active stage.
  216. // Note that this menu does not need to be set at all! It's perfectly acceptable to have a stage
  217. // without this menu!
  218. id: stageMenu
  219. anchors
  220. {
  221. left: parent.left
  222. right: parent.right
  223. top: parent.top
  224. }
  225. height: UM.Theme.getSize("stage_menu").height
  226. source: UM.Controller.activeStage != null ? UM.Controller.activeStage.stageMenuComponent : ""
  227. }
  228. UM.MessageStack
  229. {
  230. anchors
  231. {
  232. horizontalCenter: parent.horizontalCenter
  233. top: parent.verticalCenter
  234. bottom: parent.bottom
  235. bottomMargin: UM.Theme.getSize("default_margin").height
  236. }
  237. }
  238. }
  239. SidebarTooltip
  240. {
  241. id: tooltip
  242. }
  243. }
  244. UM.PreferencesDialog
  245. {
  246. id: preferences
  247. Component.onCompleted:
  248. {
  249. //; Remove & re-add the general page as we want to use our own instead of uranium standard.
  250. removePage(0);
  251. insertPage(0, catalog.i18nc("@title:tab","General"), Qt.resolvedUrl("Preferences/GeneralPage.qml"));
  252. removePage(1);
  253. insertPage(1, catalog.i18nc("@title:tab","Settings"), Qt.resolvedUrl("Preferences/SettingVisibilityPage.qml"));
  254. insertPage(2, catalog.i18nc("@title:tab", "Printers"), Qt.resolvedUrl("Preferences/MachinesPage.qml"));
  255. insertPage(3, catalog.i18nc("@title:tab", "Materials"), Qt.resolvedUrl("Preferences/Materials/MaterialsPage.qml"));
  256. insertPage(4, catalog.i18nc("@title:tab", "Profiles"), Qt.resolvedUrl("Preferences/ProfilesPage.qml"));
  257. // Remove plug-ins page because we will use the shiny new plugin browser:
  258. removePage(5);
  259. //Force refresh
  260. setPage(0);
  261. }
  262. onVisibleChanged:
  263. {
  264. // When the dialog closes, switch to the General page.
  265. // This prevents us from having a heavy page like Setting Visiblity active in the background.
  266. setPage(0);
  267. }
  268. }
  269. Connections
  270. {
  271. target: Cura.Actions.preferences
  272. onTriggered: preferences.visible = true
  273. }
  274. Connections
  275. {
  276. target: CuraApplication
  277. onShowPreferencesWindow: preferences.visible = true
  278. }
  279. Connections
  280. {
  281. target: Cura.Actions.addProfile
  282. onTriggered:
  283. {
  284. preferences.show();
  285. preferences.setPage(4);
  286. // Create a new profile after a very short delay so the preference page has time to initiate
  287. createProfileTimer.start();
  288. }
  289. }
  290. Connections
  291. {
  292. target: Cura.Actions.configureMachines
  293. onTriggered:
  294. {
  295. preferences.visible = true;
  296. preferences.setPage(2);
  297. }
  298. }
  299. Connections
  300. {
  301. target: Cura.Actions.manageProfiles
  302. onTriggered:
  303. {
  304. preferences.visible = true;
  305. preferences.setPage(4);
  306. }
  307. }
  308. Connections
  309. {
  310. target: Cura.Actions.manageMaterials
  311. onTriggered:
  312. {
  313. preferences.visible = true;
  314. preferences.setPage(3)
  315. }
  316. }
  317. Connections
  318. {
  319. target: Cura.Actions.configureSettingVisibility
  320. onTriggered:
  321. {
  322. preferences.visible = true;
  323. preferences.setPage(1);
  324. if(source && source.key)
  325. {
  326. preferences.getCurrentItem().scrollToSection(source.key);
  327. }
  328. }
  329. }
  330. Timer
  331. {
  332. id: createProfileTimer
  333. repeat: false
  334. interval: 1
  335. onTriggered: preferences.getCurrentItem().createProfile()
  336. }
  337. // BlurSettings is a way to force the focus away from any of the setting items.
  338. // We need to do this in order to keep the bindings intact.
  339. Connections
  340. {
  341. target: Cura.MachineManager
  342. onBlurSettings:
  343. {
  344. contentItem.forceActiveFocus()
  345. }
  346. }
  347. ContextMenu
  348. {
  349. id: contextMenu
  350. }
  351. onPreClosing:
  352. {
  353. close.accepted = CuraApplication.getIsAllChecksPassed();
  354. if (!close.accepted)
  355. {
  356. CuraApplication.checkAndExitApplication();
  357. }
  358. }
  359. MessageDialog
  360. {
  361. id: exitConfirmationDialog
  362. title: catalog.i18nc("@title:window", "Closing Cura")
  363. text: catalog.i18nc("@label", "Are you sure you want to exit Cura?")
  364. icon: StandardIcon.Question
  365. modality: Qt.ApplicationModal
  366. standardButtons: StandardButton.Yes | StandardButton.No
  367. onYes: CuraApplication.callConfirmExitDialogCallback(true)
  368. onNo: CuraApplication.callConfirmExitDialogCallback(false)
  369. onRejected: CuraApplication.callConfirmExitDialogCallback(false)
  370. onVisibilityChanged:
  371. {
  372. if (!visible)
  373. {
  374. // reset the text to default because other modules may change the message text.
  375. text = catalog.i18nc("@label", "Are you sure you want to exit Cura?");
  376. }
  377. }
  378. }
  379. Connections
  380. {
  381. target: CuraApplication
  382. onShowConfirmExitDialog:
  383. {
  384. exitConfirmationDialog.text = message;
  385. exitConfirmationDialog.open();
  386. }
  387. }
  388. Connections
  389. {
  390. target: Cura.Actions.quit
  391. onTriggered: CuraApplication.checkAndExitApplication();
  392. }
  393. Connections
  394. {
  395. target: Cura.Actions.toggleFullScreen
  396. onTriggered: base.toggleFullscreen();
  397. }
  398. FileDialog
  399. {
  400. id: openDialog;
  401. //: File open dialog title
  402. title: catalog.i18nc("@title:window","Open file(s)")
  403. modality: UM.Application.platform == "linux" ? Qt.NonModal : Qt.WindowModal;
  404. selectMultiple: true
  405. nameFilters: UM.MeshFileHandler.supportedReadFileTypes;
  406. folder: CuraApplication.getDefaultPath("dialog_load_path")
  407. onAccepted:
  408. {
  409. // Because several implementations of the file dialog only update the folder
  410. // when it is explicitly set.
  411. var f = folder;
  412. folder = f;
  413. CuraApplication.setDefaultPath("dialog_load_path", folder);
  414. handleOpenFileUrls(fileUrls);
  415. }
  416. // Yeah... I know... it is a mess to put all those things here.
  417. // There are lots of user interactions in this part of the logic, such as showing a warning dialog here and there,
  418. // etc. This means it will come back and forth from time to time between QML and Python. So, separating the logic
  419. // and view here may require more effort but make things more difficult to understand.
  420. function handleOpenFileUrls(fileUrlList)
  421. {
  422. // look for valid project files
  423. var projectFileUrlList = [];
  424. var hasGcode = false;
  425. var nonGcodeFileList = [];
  426. for (var i in fileUrlList)
  427. {
  428. var endsWithG = /\.g$/;
  429. var endsWithGcode = /\.gcode$/;
  430. if (endsWithG.test(fileUrlList[i]) || endsWithGcode.test(fileUrlList[i]))
  431. {
  432. continue;
  433. }
  434. else if (CuraApplication.checkIsValidProjectFile(fileUrlList[i]))
  435. {
  436. projectFileUrlList.push(fileUrlList[i]);
  437. }
  438. nonGcodeFileList.push(fileUrlList[i]);
  439. }
  440. hasGcode = nonGcodeFileList.length < fileUrlList.length;
  441. // show a warning if selected multiple files together with Gcode
  442. var hasProjectFile = projectFileUrlList.length > 0;
  443. var selectedMultipleFiles = fileUrlList.length > 1;
  444. if (selectedMultipleFiles && hasGcode)
  445. {
  446. infoMultipleFilesWithGcodeDialog.selectedMultipleFiles = selectedMultipleFiles;
  447. infoMultipleFilesWithGcodeDialog.hasProjectFile = hasProjectFile;
  448. infoMultipleFilesWithGcodeDialog.fileUrls = nonGcodeFileList.slice();
  449. infoMultipleFilesWithGcodeDialog.projectFileUrlList = projectFileUrlList.slice();
  450. infoMultipleFilesWithGcodeDialog.open();
  451. }
  452. else
  453. {
  454. handleOpenFiles(selectedMultipleFiles, hasProjectFile, fileUrlList, projectFileUrlList);
  455. }
  456. }
  457. function handleOpenFiles(selectedMultipleFiles, hasProjectFile, fileUrlList, projectFileUrlList)
  458. {
  459. // we only allow opening one project file
  460. if (selectedMultipleFiles && hasProjectFile)
  461. {
  462. openFilesIncludingProjectsDialog.fileUrls = fileUrlList.slice();
  463. openFilesIncludingProjectsDialog.show();
  464. return;
  465. }
  466. if (hasProjectFile)
  467. {
  468. var projectFile = projectFileUrlList[0];
  469. // check preference
  470. var choice = UM.Preferences.getValue("cura/choice_on_open_project");
  471. if (choice == "open_as_project")
  472. {
  473. openFilesIncludingProjectsDialog.loadProjectFile(projectFile);
  474. }
  475. else if (choice == "open_as_model")
  476. {
  477. openFilesIncludingProjectsDialog.loadModelFiles([projectFile].slice());
  478. }
  479. else // always ask
  480. {
  481. // ask whether to open as project or as models
  482. askOpenAsProjectOrModelsDialog.fileUrl = projectFile;
  483. askOpenAsProjectOrModelsDialog.show();
  484. }
  485. }
  486. else
  487. {
  488. openFilesIncludingProjectsDialog.loadModelFiles(fileUrlList.slice());
  489. }
  490. }
  491. }
  492. MessageDialog
  493. {
  494. id: packageInstallDialog
  495. title: catalog.i18nc("@window:title", "Install Package");
  496. standardButtons: StandardButton.Ok
  497. modality: Qt.ApplicationModal
  498. }
  499. MessageDialog
  500. {
  501. id: infoMultipleFilesWithGcodeDialog
  502. title: catalog.i18nc("@title:window", "Open File(s)")
  503. icon: StandardIcon.Information
  504. standardButtons: StandardButton.Ok
  505. 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.")
  506. property var selectedMultipleFiles
  507. property var hasProjectFile
  508. property var fileUrls
  509. property var projectFileUrlList
  510. onAccepted:
  511. {
  512. openDialog.handleOpenFiles(selectedMultipleFiles, hasProjectFile, fileUrls, projectFileUrlList);
  513. }
  514. }
  515. Connections
  516. {
  517. target: Cura.Actions.open
  518. onTriggered: openDialog.open()
  519. }
  520. OpenFilesIncludingProjectsDialog
  521. {
  522. id: openFilesIncludingProjectsDialog
  523. }
  524. AskOpenAsProjectOrModelsDialog
  525. {
  526. id: askOpenAsProjectOrModelsDialog
  527. }
  528. Connections
  529. {
  530. target: CuraApplication
  531. onOpenProjectFile:
  532. {
  533. askOpenAsProjectOrModelsDialog.fileUrl = project_file;
  534. askOpenAsProjectOrModelsDialog.show();
  535. }
  536. }
  537. Connections
  538. {
  539. target: Cura.Actions.showProfileFolder
  540. onTriggered:
  541. {
  542. var path = UM.Resources.getPath(UM.Resources.Preferences, "");
  543. if(Qt.platform.os == "windows") {
  544. path = path.replace(/\\/g,"/");
  545. }
  546. Qt.openUrlExternally(path);
  547. if(Qt.platform.os == "linux") {
  548. Qt.openUrlExternally(UM.Resources.getPath(UM.Resources.Resources, ""));
  549. }
  550. }
  551. }
  552. AddMachineDialog
  553. {
  554. id: addMachineDialog
  555. onMachineAdded:
  556. {
  557. machineActionsWizard.firstRun = addMachineDialog.firstRun
  558. machineActionsWizard.start(id)
  559. }
  560. }
  561. // Dialog to handle first run machine actions
  562. UM.Wizard
  563. {
  564. id: machineActionsWizard;
  565. title: catalog.i18nc("@title:window", "Add Printer")
  566. property var machine;
  567. function start(id)
  568. {
  569. var actions = Cura.MachineActionManager.getFirstStartActions(id)
  570. resetPages() // Remove previous pages
  571. for (var i = 0; i < actions.length; i++)
  572. {
  573. actions[i].displayItem.reset()
  574. machineActionsWizard.appendPage(actions[i].displayItem, catalog.i18nc("@title", actions[i].label));
  575. }
  576. //Only start if there are actions to perform.
  577. if (actions.length > 0)
  578. {
  579. machineActionsWizard.currentPage = 0;
  580. show()
  581. }
  582. }
  583. }
  584. MessageDialog
  585. {
  586. id: messageDialog
  587. modality: Qt.ApplicationModal
  588. onAccepted: CuraApplication.messageBoxClosed(clickedButton)
  589. onApply: CuraApplication.messageBoxClosed(clickedButton)
  590. onDiscard: CuraApplication.messageBoxClosed(clickedButton)
  591. onHelp: CuraApplication.messageBoxClosed(clickedButton)
  592. onNo: CuraApplication.messageBoxClosed(clickedButton)
  593. onRejected: CuraApplication.messageBoxClosed(clickedButton)
  594. onReset: CuraApplication.messageBoxClosed(clickedButton)
  595. onYes: CuraApplication.messageBoxClosed(clickedButton)
  596. }
  597. Connections
  598. {
  599. target: CuraApplication
  600. onShowMessageBox:
  601. {
  602. messageDialog.title = title
  603. messageDialog.text = text
  604. messageDialog.informativeText = informativeText
  605. messageDialog.detailedText = detailedText
  606. messageDialog.standardButtons = buttons
  607. messageDialog.icon = icon
  608. messageDialog.visible = true
  609. }
  610. }
  611. DiscardOrKeepProfileChangesDialog
  612. {
  613. id: discardOrKeepProfileChangesDialog
  614. }
  615. Connections
  616. {
  617. target: CuraApplication
  618. onShowDiscardOrKeepProfileChanges:
  619. {
  620. discardOrKeepProfileChangesDialog.show()
  621. }
  622. }
  623. Connections
  624. {
  625. target: Cura.Actions.addMachine
  626. onTriggered: addMachineDialog.visible = true;
  627. }
  628. AboutDialog
  629. {
  630. id: aboutDialog
  631. }
  632. Connections
  633. {
  634. target: Cura.Actions.about
  635. onTriggered: aboutDialog.visible = true;
  636. }
  637. Connections
  638. {
  639. target: CuraApplication
  640. onRequestAddPrinter:
  641. {
  642. addMachineDialog.visible = true
  643. addMachineDialog.firstRun = false
  644. }
  645. }
  646. Timer
  647. {
  648. id: startupTimer;
  649. interval: 100;
  650. repeat: false;
  651. running: true;
  652. onTriggered:
  653. {
  654. if(!base.visible)
  655. {
  656. base.visible = true;
  657. }
  658. // check later if the user agreement dialog has been closed
  659. if (CuraApplication.needToShowUserAgreement)
  660. {
  661. restart();
  662. }
  663. else if(Cura.MachineManager.activeMachine == null)
  664. {
  665. addMachineDialog.open();
  666. }
  667. }
  668. }
  669. }