From 3438900b2b3c8380fc513a32935e27f8a1ac1f05 Mon Sep 17 00:00:00 2001 From: Yann Lanthony Date: Wed, 1 Aug 2018 23:43:15 +0200 Subject: [PATCH] [ui] add "About" dialog Accessible from main menu Help > About Meshroom, or by pressing "F1". Provides info about Meshroom version + useful links and related Open Source Licenses. * load COPYING.md from Meshroom/AliceVision files (either provided with the standalone package or online) + translate them to HTML for display (added `markdown` package in requirements) * request.js: centralize function to open file urls * provide system info from MeshroomApp (exposed as contextProperty) --- meshroom/ui/app.py | 55 ++++- meshroom/ui/img/GitHub-Mark-Light-32px.png | Bin 0 -> 1571 bytes meshroom/ui/img/meshroom-tagline-vertical.svg | 112 ++++++++++ meshroom/ui/qml/AboutDialog.qml | 200 ++++++++++++++++++ meshroom/ui/qml/Utils/qmldir | 1 + meshroom/ui/qml/Utils/request.js | 11 + meshroom/ui/qml/main.qml | 15 +- requirements.txt | 1 + setup.py | 2 +- 9 files changed, 393 insertions(+), 4 deletions(-) create mode 100644 meshroom/ui/img/GitHub-Mark-Light-32px.png create mode 100644 meshroom/ui/img/meshroom-tagline-vertical.svg create mode 100644 meshroom/ui/qml/AboutDialog.qml create mode 100644 meshroom/ui/qml/Utils/request.js diff --git a/meshroom/ui/app.py b/meshroom/ui/app.py index feb6bf7f..62b669dc 100644 --- a/meshroom/ui/app.py +++ b/meshroom/ui/app.py @@ -1,6 +1,7 @@ +import logging import os -from PySide2.QtCore import Qt +from PySide2.QtCore import Qt, Slot, QJsonValue, Property from PySide2.QtGui import QIcon from PySide2.QtWidgets import QApplication @@ -50,8 +51,58 @@ class MeshroomApp(QApplication): self.engine.rootContext().setContextProperty("_PaletteManager", pm) fpHelper = FilepathHelper(parent=self) self.engine.rootContext().setContextProperty("Filepath", fpHelper) - + self.engine.rootContext().setContextProperty("MeshroomApp", self) # Request any potential computation to stop on exit self.aboutToQuit.connect(r.stopExecution) self.engine.load(os.path.normpath(url)) + + @Slot(str, result=str) + def markdownToHtml(self, md): + """ + Convert markdown to HTML. + + Args: + md (str): the markdown text to convert + + Returns: + str: the resulting HTML string + """ + try: + from markdown import markdown + except ImportError: + logging.warning("Can't import markdown module, returning source markdown text.") + return md + return markdown(md) + + @Property(QJsonValue, constant=True) + def systemInfo(self): + import platform + import sys + return { + 'platform': '{} {}'.format(platform.system(), platform.release()), + 'python': 'Python {}'.format(sys.version.split(" ")[0]) + } + + @Property("QVariantList", constant=True) + def licensesModel(self): + """ + Get info about open-source licenses for the application. + Model provides: + title: the name of the project + localUrl: the local path to COPYING.md + onlineUrl: the remote path to COPYING.md + """ + rootDir = os.environ.get("MESHROOM_INSTALL_DIR", os.getcwd()) + return [ + { + "title": "Meshroom", + "localUrl": os.path.join(rootDir, "COPYING.md"), + "onlineUrl": "https://raw.githubusercontent.com/alicevision/meshroom/develop/COPYING.md" + }, + { + "title": "AliceVision", + "localUrl": os.path.join(rootDir, "aliceVision", "share", "COPYING.md"), + "onlineUrl": "https://raw.githubusercontent.com/alicevision/AliceVision/develop/COPYING.md" + } + ] diff --git a/meshroom/ui/img/GitHub-Mark-Light-32px.png b/meshroom/ui/img/GitHub-Mark-Light-32px.png new file mode 100644 index 0000000000000000000000000000000000000000..628da97c70890c73e59204f5b140c4e67671e92d GIT binary patch literal 1571 zcmaJ>c~BE~6izDPQq)#Nu*KOf(n^(VHY9;fiINM65``pc+9*v(mL$bwfCjbc%v9V{8r9iX|O%>Nr%pLD2qT{mty}c=LVleeamv znz3SOSm@kP8jThvOOq(56Yzh*fz(booe!uZij=BJC6+_lbvQ~B8nA2>kXdv_RDtRY z`5QXWWEySCe6vbTs^#f?J!WC*{1~RgVx!nJTJjQyO{dRANgx|FnymtGbD9%JmCh9^y)##j7{Dcqfn*1ta$rG89pJF6w-S7Z037$rr|y0;1Onp_ zGFJdT6Q!1C0AdVB0WOmpuV=AgAQ550Tn+-mivTtYPJmz*#75#_n9oV%!#rSOfmAfy zki%C~=fTp1{O#BLpJ|0jj#m6#|LRWit-vq3PE1z9ZqyvET4sX$-Icqy7t z<=aq5ff86AuBZBu6EjJsYWM0uejufWFTwPA7Su}0Bm$7KFb!q{Um_8~A{LUG#1l(l zSehUda@kU8LIRg9fkk2tZ;~ss5~R+mM<==F7hLHpxqLB>>PQS%Vc7b~?q!%T5+h8Q z4G=4Nzyi5WZ?^gkasJ{?Xhm`JC#WG6$1K2jb@=9&D3EgD#3UhGh#*21rJjulVXjCF zvp76q62jt0zzMG5C7DlfMgPl%C^3+~wf|}Lq=}jz|MmIcQjh1Ok6NjD$Em^Iv26D> z8tt_TnM9~^Tt8mflRGPOrrX|HtT3gG4LEuuk{g2Rn}QgJIa?gZo))!!=o_l9bvD%A zZ`aHajl8#~u?!4f7F#*b*->A=R2L)6!>saz?h>#wTXT-I(XmQ zx{84skS>k=i~i`(6k4C7;Zpfx%dCPVjPayMf8pugtGM=~s=Id1l#8MZJ1-73wV#Q3 zR3>v3%}jbQs1f_Z0xo;%=LILlA+nTpKI4ha%xWW}uqHrNao~&T4AY6m`P$_n-6h*g zhoX+e4n%~gl_lhe#s+AMb7d{5WzvYTa%6Q~si@@4{;s(0zU|H&P3fE+t{7X`S#Cj@ zC#vd}^4pcBD*77Ny5=j$h8EL2_t$O38$SQiJ6fPjJMimypr~MB2(&P0aI|h}$64<0 z>_~duqNjaT=DM^6+N{&B_lED;F2wrl?!4Lk*2((x!fmrcsw+=cI^qttuZ9C}-m~5E z-ryYVpL%^xR#&(0YI5hz<(}F7-p)?FPcyJO-zVO>%9ZDXJH8pnY;GJYFDQ>vd#j_* zRrd}L(r=!g+1#nQwsO?kpS`Qq8`NxE+Zy{gf7*_7J*U2V_|NpLo{iasj7VCg_V9&| ShohtYzipXxh2)4xTk + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/meshroom/ui/qml/AboutDialog.qml b/meshroom/ui/qml/AboutDialog.qml new file mode 100644 index 00000000..b9c4cde1 --- /dev/null +++ b/meshroom/ui/qml/AboutDialog.qml @@ -0,0 +1,200 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.3 +import Utils 1.0 +import MaterialIcons 2.2 + + +/// Meshroom "About" window +Dialog { + id: root + + x: parent.width / 2 - width / 2 + width: 600 + + // Fade in transition + enter: Transition { + NumberAnimation { property: "opacity"; from: 0.0; to: 1.0 } + } + + modal: true + closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside + padding: 30 + topPadding: 0 // header provides top padding + + header: Pane { + background: Item {} + MaterialToolButton { + text: MaterialIcons.close + anchors.right: parent.right + onClicked: root.close() + } + } + + ColumnLayout { + width: parent.width + spacing: 6 + + // Logo + Version info + Column { + Layout.fillWidth: true + Image { + anchors.horizontalCenter: parent.horizontalCenter + source: "../img/meshroom-tagline-vertical.svg" + sourceSize: Qt.size(222, 180) + } + TextArea { + id: config + width: parent.width + readOnly: true + horizontalAlignment: TextArea.AlignHCenter + selectByKeyboard: true + selectByMouse: true + text: "Version " + Qt.application.version + "\n" + + MeshroomApp.systemInfo["platform"] + " \n" + + MeshroomApp.systemInfo["python"] + } + } + + SystemPalette { id: systemPalette } + + // Links + Row { + spacing: 4 + Layout.alignment: Qt.AlignHCenter + MaterialToolButton { + text: MaterialIcons.public_ + font.pointSize: 21 + palette.buttonText: root.palette.link + ToolTip.text: "AliceVision Website" + onClicked: Qt.openUrlExternally("https://alicevision.github.io") + } + ToolButton { + icon.source: "../img/GitHub-Mark-Light-32px.png" + icon.width: 24 + icon.height: 24 + ToolTip.text: "Meshroom on Github" + ToolTip.visible: hovered + onClicked: Qt.openUrlExternally("https://github.com/alicevision/meshroom") + } + MaterialToolButton { + text: MaterialIcons.bug_report + font.pointSize: 21 + ToolTip.text: "Report a Bug (GitHub account required)" + palette.buttonText: "#F44336" + property string body: "**Configuration**\n" + config.text + onClicked: Qt.openUrlExternally("https://github.com/alicevision/meshroom/issues/new?body="+body) + } + MaterialToolButton { + text: MaterialIcons.forum + font.pointSize: 21 + palette.buttonText: Qt.lighter(systemPalette.buttonText, 1.3) + ToolTip.text: "Public Mailing-List (open discussions, use-cases, problems, best practices...)" + onClicked: Qt.openUrlExternally("https://groups.google.com/forum/#!forum/alicevision") + } + MaterialToolButton { + text: MaterialIcons.mail + font.pointSize: 21 + palette.buttonText: Qt.lighter(systemPalette.buttonText, 1.3) + ToolTip.text: "Private Contact (alicevision-team@googlegroups.com)" + onClicked: Qt.openUrlExternally("mailto:alicevision-team@googlegroups.com") + } + } + + // Copyright + RowLayout { + spacing: 2 + Layout.alignment: Qt.AlignHCenter + Label { + font.family: MaterialIcons.fontFamily + text: MaterialIcons.copyright + font.pointSize: 10 + } + Label { + text: "2018 AliceVision contributors" + } + } + + // Spacer + Rectangle { + width: 50 + height: 1 + color: systemPalette.mid + Layout.alignment: Qt.AlignHCenter + } + + // OpenSource licenses + Label { + text: "Open Source Licenses" + Layout.alignment: Qt.AlignHCenter + } + + ListView { + Layout.fillWidth: true + implicitHeight: childrenRect.height + spacing: 2 + interactive: false + + model: MeshroomApp.licensesModel + + // Exclusive ButtonGroup for licenses entries + ButtonGroup { id: licensesGroup; exclusive: true } + + delegate: ColumnLayout { + width: ListView.view.width + Button { + id: sectionButton + flat: true + text: modelData.title + font.pointSize: 10 + font.bold: true + checkable: true + ButtonGroup.group: licensesGroup + Layout.alignment: Qt.AlignHCenter + } + + Loader { + Layout.fillWidth: true + active: sectionButton.checked + Layout.preferredHeight: active ? 210 : 0 + visible: active + + // Log display + sourceComponent: ScrollView { + + Component.onCompleted: { + // try to load the local file + var url = Filepath.stringToUrl(modelData.localUrl) + // fallback to the online url if file is not found + if(!Filepath.exists(url)) + url = modelData.onlineUrl + Request.get(url, + function(xhr){ + if(xhr.readyState === XMLHttpRequest.DONE) + { + // status is OK + if(xhr.status === 200) + textArea.text = MeshroomApp.markdownToHtml(xhr.responseText) + else + textArea.text = "Could not load license file. Available online at "+ url + "." + } + }) + } + + background: Rectangle { color: palette.base } + + TextArea { + id: textArea + readOnly: true + selectByMouse: true + selectByKeyboard: true + wrapMode: TextArea.WrapAnywhere + textFormat: TextEdit.RichText + onLinkActivated: Qt.openUrlExternally(link) + } + } + } + } + } + } +} diff --git a/meshroom/ui/qml/Utils/qmldir b/meshroom/ui/qml/Utils/qmldir index 2e726c0f..0480ee67 100644 --- a/meshroom/ui/qml/Utils/qmldir +++ b/meshroom/ui/qml/Utils/qmldir @@ -1,5 +1,6 @@ module Utils SortFilterDelegateModel 1.0 SortFilterDelegateModel.qml +Request 1.0 request.js # causes random crash at application exit # singleton Filepath 1.0 Filepath.qml diff --git a/meshroom/ui/qml/Utils/request.js b/meshroom/ui/qml/Utils/request.js new file mode 100644 index 00000000..fba1a599 --- /dev/null +++ b/meshroom/ui/qml/Utils/request.js @@ -0,0 +1,11 @@ +.pragma library + +/** + * Perform 'GET' request on url, and bind 'callback' to onreadystatechange (with XHR objet as parameter). + */ +function get(url, callback) { + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { callback(xhr) } + xhr.open("GET", url) + xhr.send() +} diff --git a/meshroom/ui/qml/main.qml b/meshroom/ui/qml/main.qml index 1c3fe97b..ca6f9546 100755 --- a/meshroom/ui/qml/main.qml +++ b/meshroom/ui/qml/main.qml @@ -133,6 +133,10 @@ ApplicationWindow { } } + AboutDialog { + id: aboutDialog + } + // Check if document has been saved function ensureSaved(callback) { @@ -302,9 +306,18 @@ ApplicationWindow { onTriggered: _window.visibility == ApplicationWindow.FullScreen ? _window.showNormal() : showFullScreen() } } + Menu { + title: "Help" + Action { + text: "About Meshroom" + onTriggered: aboutDialog.open() + // shoud be StandardKey.HelpContents, but for some reason it's not stable + // (may cause crash, requires pressing F1 twice after closing the popup) + shortcut: "F1" + } + } } - Connections { target: _reconstruction diff --git a/requirements.txt b/requirements.txt index 3c3a18a2..37488047 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ psutil enum34;python_version<"3.4" PySide2==5.11.0 +markdown==2.6.11 \ No newline at end of file diff --git a/setup.py b/setup.py index 6bccb33d..1284c5f1 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ elif sys.platform.startswith("darwin"): setup( name="Meshroom", description="Meshroom", - install_requires=['psutil', 'pytest', 'PySide2'], + install_requires=['psutil', 'pytest', 'PySide2', 'markdown'], extras_require={ ':python_version < "3.4"': [ 'enum34',