mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-13 10:53:22 +02:00
Merge branch 'exportToTestRail' of github.com:NissimHadar/hifi into exportToTestRail
This commit is contained in:
commit
78cf29619e
62 changed files with 1840 additions and 411 deletions
|
@ -22,7 +22,8 @@
|
|||
android:icon="@drawable/ic_launcher"
|
||||
android:launchMode="singleTop"
|
||||
android:roundIcon="@drawable/ic_launcher">
|
||||
<activity android:name="io.highfidelity.hifiinterface.PermissionChecker">
|
||||
<activity android:name="io.highfidelity.hifiinterface.PermissionChecker"
|
||||
android:theme="@style/Theme.AppCompat.Translucent.NoActionBar.Launcher">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
|
|
|
@ -162,7 +162,19 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCrea
|
|||
jvmArgs.version = JNI_VERSION_1_6; // choose your JNI version
|
||||
jvmArgs.name = NULL; // you might want to give the java thread a name
|
||||
jvmArgs.group = NULL; // you might want to assign the java thread to a ThreadGroup
|
||||
jvm->AttachCurrentThread(reinterpret_cast<JNIEnv **>(&myNewEnv), &jvmArgs);
|
||||
|
||||
int attachedHere = 0; // know if detaching at the end is necessary
|
||||
jint res = jvm->GetEnv((void**)&myNewEnv, JNI_VERSION_1_6); // checks if current env needs attaching or it is already attached
|
||||
if (JNI_OK != res) {
|
||||
qDebug() << "[JCRASH] GetEnv env not attached yet, attaching now..";
|
||||
res = jvm->AttachCurrentThread(reinterpret_cast<JNIEnv **>(&myNewEnv), &jvmArgs);
|
||||
if (JNI_OK != res) {
|
||||
qDebug() << "[JCRASH] Failed to AttachCurrentThread, ErrorCode = " << res;
|
||||
return;
|
||||
} else {
|
||||
attachedHere = 1;
|
||||
}
|
||||
}
|
||||
|
||||
QAndroidJniObject string = QAndroidJniObject::fromString(a);
|
||||
jboolean jBackToScene = (jboolean) backToScene;
|
||||
|
@ -175,7 +187,9 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCrea
|
|||
myNewEnv->CallObjectMethod(hashmap, mapClassPut, QAndroidJniObject::fromString("url").object<jstring>(), jArg.object<jstring>());
|
||||
}
|
||||
__interfaceActivity.callMethod<void>("openAndroidActivity", "(Ljava/lang/String;ZLjava/util/HashMap;)V", string.object<jstring>(), jBackToScene, hashmap);
|
||||
jvm->DetachCurrentThread();
|
||||
if (attachedHere) {
|
||||
jvm->DetachCurrentThread();
|
||||
}
|
||||
});
|
||||
|
||||
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::hapticFeedbackRequested, [](int duration) {
|
||||
|
|
|
@ -8,6 +8,7 @@ import android.content.Intent;
|
|||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
@ -135,4 +136,13 @@ public class PermissionChecker extends Activity {
|
|||
launchActivityWithPermissions();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
View decorView = getWindow().getDecorView();
|
||||
// Hide the status bar.
|
||||
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
|
||||
decorView.setSystemUiVisibility(uiOptions);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,6 +66,7 @@ public class HomeFragment extends Fragment {
|
|||
GridLayoutManager gridLayoutMgr = new GridLayoutManager(getContext(), numberOfColumns);
|
||||
mDomainsView.setLayoutManager(gridLayoutMgr);
|
||||
mDomainAdapter = new DomainAdapter(getContext(), HifiUtils.getInstance().protocolVersionSignature(), nativeGetLastLocation());
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
mDomainAdapter.setClickListener((view, position, domain) -> {
|
||||
new Handler(getActivity().getMainLooper()).postDelayed(() -> {
|
||||
if (mListener != null) {
|
||||
|
|
|
@ -60,6 +60,8 @@ import android.view.View;
|
|||
import android.view.WindowManager.LayoutParams;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
|
||||
import org.qtproject.qt5.android.QtNative;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class QtActivity extends Activity {
|
||||
public String APPLICATION_PARAMETERS = null;
|
||||
|
@ -103,7 +105,7 @@ public class QtActivity extends Activity {
|
|||
}
|
||||
|
||||
public boolean super_dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
|
||||
return super_dispatchPopulateAccessibilityEvent(event);
|
||||
return super.dispatchPopulateAccessibilityEvent(event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -362,7 +364,25 @@ public class QtActivity extends Activity {
|
|||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
QtApplication.invokeDelegate();
|
||||
/*
|
||||
cduarte https://highfidelity.manuscript.com/f/cases/16712/App-freezes-on-opening-randomly
|
||||
After Qt upgrade to 5.11 we had a black screen crash after closing the application with
|
||||
the hardware button "Back" and trying to start the app again. It could only be fixed after
|
||||
totally closing the app swiping it in the list of running apps.
|
||||
This problem did not happen with the previous Qt version.
|
||||
After analysing changes we came up with this case and change:
|
||||
https://codereview.qt-project.org/#/c/218882/
|
||||
In summary they've moved libs loading to the same thread as main() and as a matter of correctness
|
||||
in the onDestroy method in QtActivityDelegate, they exit that thread with `QtNative.m_qtThread.exit();`
|
||||
That exit call is the main reason of this problem.
|
||||
|
||||
In this fix we just replace the `QtApplication.invokeDelegate();` call that may end using the
|
||||
entire onDestroy method including that thread exit line for other three lines that purposely
|
||||
terminate qt (borrowed from QtActivityDelegate::onDestroy as well).
|
||||
*/
|
||||
QtNative.terminateQt();
|
||||
QtNative.setActivity(null, null);
|
||||
System.exit(0);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
|
11
android/app/src/main/res/drawable/launch_screen.xml
Normal file
11
android/app/src/main/res/drawable/launch_screen.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
|
||||
<item android:drawable="@android:color/black"/>
|
||||
<item android:drawable="@drawable/hifi_logo_splash"
|
||||
android:gravity="center_horizontal"
|
||||
android:top="225dp"
|
||||
android:height="242dp"
|
||||
android:width="242dp">
|
||||
</item>
|
||||
</layer-list>
|
|
@ -7,9 +7,17 @@
|
|||
android:layout_height="match_parent"
|
||||
android:background="@android:color/black">
|
||||
<ImageView
|
||||
android:id="@+id/hifi_logo"
|
||||
android:layout_width="242dp"
|
||||
android:layout_height="242dp"
|
||||
android:src="@drawable/hifi_logo_splash"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="225dp"/>
|
||||
<ProgressBar
|
||||
style="@style/Widget.AppCompat.ProgressBar"
|
||||
android:theme="@style/HifiCircularProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/hifi_logo"
|
||||
android:layout_centerHorizontal="true"/>
|
||||
</RelativeLayout>
|
||||
|
|
|
@ -17,4 +17,5 @@
|
|||
<color name="colorLoginError">#FF7171</color>
|
||||
<color name="black_060">#99000000</color>
|
||||
<color name="statusbar_color">#292929</color>
|
||||
<color name="hifiLogoColor">#23B2E7</color>
|
||||
</resources>
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
<style name="Theme.AppCompat.Translucent.NoActionBar.Launcher" parent="Theme.AppCompat.Translucent.NoActionBar">
|
||||
<item name="android:windowBackground">@drawable/launch_screen</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
|
||||
|
@ -62,4 +65,7 @@
|
|||
<item name="android:background">@color/white_opaque</item>
|
||||
</style>
|
||||
|
||||
<style name="HifiCircularProgress" parent="Theme.AppCompat.Light">
|
||||
<item name="colorAccent">@color/hifiLogoColor</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
|
@ -1223,7 +1223,7 @@
|
|||
"name": "max_avatar_height",
|
||||
"type": "double",
|
||||
"label": "Maximum Avatar Height (meters)",
|
||||
"help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.",
|
||||
"help": "Limits the height of avatars in your domain. Cannot be greater than 1755.",
|
||||
"placeholder": 5.2,
|
||||
"default": 5.2
|
||||
},
|
||||
|
|
|
@ -58,7 +58,11 @@ $(document).ready(function(){
|
|||
}
|
||||
|
||||
Settings.handlePostSettings = function(formJSON) {
|
||||
|
||||
|
||||
if (!verifyAvatarHeights()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if we've set the basic http password
|
||||
if (formJSON["security"]) {
|
||||
|
||||
|
@ -207,7 +211,7 @@ $(document).ready(function(){
|
|||
swal({
|
||||
title: '',
|
||||
type: 'error',
|
||||
text: "There was a problem retreiving domain information from High Fidelity API.",
|
||||
text: "There was a problem retrieving domain information from High Fidelity API.",
|
||||
confirmButtonText: 'Try again',
|
||||
showCancelButton: true,
|
||||
closeOnConfirm: false
|
||||
|
@ -288,7 +292,7 @@ $(document).ready(function(){
|
|||
swal({
|
||||
title: 'Create new domain ID',
|
||||
type: 'input',
|
||||
text: 'Enter a label this machine.</br></br>This will help you identify which domain ID belongs to which machine.</br></br>',
|
||||
text: 'Enter a label for this machine.</br></br>This will help you identify which domain ID belongs to which machine.</br></br>',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: "Create",
|
||||
closeOnConfirm: false,
|
||||
|
@ -669,7 +673,7 @@ $(document).ready(function(){
|
|||
var spinner = createDomainSpinner();
|
||||
$('#' + Settings.PLACES_TABLE_ID).after($(spinner));
|
||||
|
||||
var errorEl = createDomainLoadingError("There was an error retreiving your places.");
|
||||
var errorEl = createDomainLoadingError("There was an error retrieving your places.");
|
||||
$("#" + Settings.PLACES_TABLE_ID).after(errorEl);
|
||||
|
||||
// do we have a domain ID?
|
||||
|
@ -1091,4 +1095,43 @@ $(document).ready(function(){
|
|||
|
||||
$('#settings_backup .panel-body').html(html);
|
||||
}
|
||||
|
||||
function verifyAvatarHeights() {
|
||||
var errorString = '';
|
||||
var minAllowedHeight = 0.009;
|
||||
var maxAllowedHeight = 1755;
|
||||
var alertCss = { backgroundColor: '#ffa0a0' };
|
||||
var minHeightElement = $('input[name="avatars.min_avatar_height"]');
|
||||
var maxHeightElement = $('input[name="avatars.max_avatar_height"]');
|
||||
|
||||
var minHeight = Number(minHeightElement.val());
|
||||
var maxHeight = Number(maxHeightElement.val());
|
||||
|
||||
if (maxHeight < minHeight) {
|
||||
errorString = 'Maximum avatar height must not be less than minimum avatar height<br>';
|
||||
minHeightElement.css(alertCss);
|
||||
maxHeightElement.css(alertCss);
|
||||
};
|
||||
if (minHeight < minAllowedHeight) {
|
||||
errorString += 'Minimum avatar height must not be less than ' + minAllowedHeight + '<br>';
|
||||
minHeightElement.css(alertCss);
|
||||
}
|
||||
if (maxHeight > maxAllowedHeight) {
|
||||
errorString += 'Maximum avatar height must not be greater than ' + maxAllowedHeight + '<br>';
|
||||
maxHeightElement.css(alertCss);
|
||||
}
|
||||
|
||||
if (errorString.length > 0) {
|
||||
swal({
|
||||
type: 'error',
|
||||
title: '',
|
||||
text: errorString,
|
||||
html: true
|
||||
});
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1916,14 +1916,16 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
|
||||
// don't handle if we don't have a matching node
|
||||
if (!matchingNode) {
|
||||
return false;
|
||||
connection->respond(HTTPConnection::StatusCode404, "Resource not found.");
|
||||
return true;
|
||||
}
|
||||
|
||||
auto nodeData = static_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
|
||||
|
||||
// don't handle if we don't have node data for this node
|
||||
if (!nodeData) {
|
||||
return false;
|
||||
connection->respond(HTTPConnection::StatusCode404, "Resource not found.");
|
||||
return true;
|
||||
}
|
||||
|
||||
SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID());
|
||||
|
@ -1944,7 +1946,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
}
|
||||
|
||||
// request not handled
|
||||
return false;
|
||||
connection->respond(HTTPConnection::StatusCode404, "Resource not found.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// check if this is a request for our domain ID
|
||||
|
|
|
@ -26,6 +26,7 @@ ScrollingWindow {
|
|||
y: 100
|
||||
|
||||
Component.onCompleted: {
|
||||
focus = true
|
||||
shown = true
|
||||
addressBar.text = webview.url
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ Column {
|
|||
'protocol=' + encodeURIComponent(Window.protocolSignature())
|
||||
];
|
||||
endpoint: '/api/v1/user_stories?' + options.join('&');
|
||||
itemsPerPage: 3;
|
||||
itemsPerPage: 4;
|
||||
processPage: function (data) {
|
||||
return data.user_stories.map(makeModelData);
|
||||
};
|
||||
|
@ -106,7 +106,6 @@ Column {
|
|||
highlightMoveDuration: -1;
|
||||
highlightMoveVelocity: -1;
|
||||
currentIndex: -1;
|
||||
onAtXEndChanged: { if (scroll.atXEnd && !scroll.atXBeginning) { suggestions.getNextPage(); } }
|
||||
|
||||
spacing: 12;
|
||||
width: parent.width;
|
||||
|
|
|
@ -61,7 +61,7 @@ Rectangle {
|
|||
'username';
|
||||
}
|
||||
sortAscending: connectionsTable.sortIndicatorOrder === Qt.AscendingOrder;
|
||||
itemsPerPage: 9;
|
||||
itemsPerPage: 10;
|
||||
listView: connectionsTable;
|
||||
processPage: function (data) {
|
||||
return data.users.map(function (user) {
|
||||
|
@ -786,14 +786,6 @@ Rectangle {
|
|||
}
|
||||
|
||||
model: connectionsUserModel;
|
||||
Connections {
|
||||
target: connectionsTable.flickableItem;
|
||||
onAtYEndChanged: {
|
||||
if (connectionsTable.flickableItem.atYEnd && !connectionsTable.flickableItem.atYBeginning) {
|
||||
connectionsUserModel.getNextPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This Rectangle refers to each Row in the connectionsTable.
|
||||
rowDelegate: Rectangle {
|
||||
|
|
|
@ -125,6 +125,7 @@ Rectangle {
|
|||
id: wearablesCombobox
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
comboBox.textRole: "text"
|
||||
|
||||
model: ListModel {
|
||||
function findIndexById(id) {
|
||||
|
|
|
@ -398,7 +398,7 @@ Item {
|
|||
http: root.http;
|
||||
listModelName: root.listModelName;
|
||||
endpoint: "/api/v1/users?filter=connections";
|
||||
itemsPerPage: 8;
|
||||
itemsPerPage: 9;
|
||||
listView: connectionsList;
|
||||
processPage: function (data) {
|
||||
return data.users;
|
||||
|
@ -520,7 +520,6 @@ Item {
|
|||
visible: !connectionsLoading.visible;
|
||||
clip: true;
|
||||
model: connectionsModel;
|
||||
onAtYEndChanged: if (connectionsList.atYEnd && !connectionsList.atYBeginning) { connectionsModel.getNextPage(); }
|
||||
snapMode: ListView.SnapToItem;
|
||||
// Anchors
|
||||
anchors.fill: parent;
|
||||
|
|
|
@ -335,7 +335,8 @@ Item {
|
|||
upgradeUrl: root.upgradeUrl,
|
||||
itemHref: root.itemHref,
|
||||
itemType: root.itemType,
|
||||
isInstalled: root.isInstalled
|
||||
isInstalled: root.isInstalled,
|
||||
wornEntityID: root.wornEntityID
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -551,8 +551,9 @@ Rectangle {
|
|||
|
||||
HifiModels.PSFListModel {
|
||||
id: purchasesModel;
|
||||
itemsPerPage: 6;
|
||||
itemsPerPage: 7;
|
||||
listModelName: 'purchases';
|
||||
listView: purchasesContentsList;
|
||||
getPage: function () {
|
||||
console.debug('getPage', purchasesModel.listModelName, root.isShowingMyItems, filterBar.primaryFilter_filterName, purchasesModel.currentPageToRetrieve, purchasesModel.itemsPerPage);
|
||||
Commerce.inventory(
|
||||
|
@ -706,6 +707,12 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
} else if (msg.method === "updateItemClicked") {
|
||||
// These three cases are very similar to the conditionals below, under
|
||||
// "if msg.method === "giftAsset". They differ in their popup's wording
|
||||
// and the actions to take when continuing.
|
||||
// I could see an argument for DRYing up this code, but I think the
|
||||
// actions are different enough now and potentially moving forward such that I'm
|
||||
// OK with "somewhat repeating myself".
|
||||
if (msg.itemType === "app" && msg.isInstalled) {
|
||||
lightboxPopup.titleText = "Uninstall App";
|
||||
lightboxPopup.bodyText = "The app that you are trying to update is installed.<br><br>" +
|
||||
|
@ -720,6 +727,35 @@ Rectangle {
|
|||
sendToScript(msg);
|
||||
};
|
||||
lightboxPopup.visible = true;
|
||||
} else if (msg.itemType === "wearable" && msg.wornEntityID !== '') {
|
||||
lightboxPopup.titleText = "Remove Wearable";
|
||||
lightboxPopup.bodyText = "You are currently wearing the wearable that you are trying to update.<br><br>" +
|
||||
"If you proceed, this wearable will be removed.";
|
||||
lightboxPopup.button1text = "CANCEL";
|
||||
lightboxPopup.button1method = function() {
|
||||
lightboxPopup.visible = false;
|
||||
}
|
||||
lightboxPopup.button2text = "CONFIRM";
|
||||
lightboxPopup.button2method = function() {
|
||||
Entities.deleteEntity(msg.wornEntityID);
|
||||
purchasesModel.setProperty(index, 'wornEntityID', '');
|
||||
sendToScript(msg);
|
||||
};
|
||||
lightboxPopup.visible = true;
|
||||
} else if (msg.itemType === "avatar" && MyAvatar.skeletonModelURL === msg.itemHref) {
|
||||
lightboxPopup.titleText = "Change Avatar to Default";
|
||||
lightboxPopup.bodyText = "You are currently wearing the avatar that you are trying to update.<br><br>" +
|
||||
"If you proceed, your avatar will be changed to the default avatar.";
|
||||
lightboxPopup.button1text = "CANCEL";
|
||||
lightboxPopup.button1method = function() {
|
||||
lightboxPopup.visible = false;
|
||||
}
|
||||
lightboxPopup.button2text = "CONFIRM";
|
||||
lightboxPopup.button2method = function() {
|
||||
MyAvatar.useFullAvatarURL('');
|
||||
sendToScript(msg);
|
||||
};
|
||||
lightboxPopup.visible = true;
|
||||
} else {
|
||||
sendToScript(msg);
|
||||
}
|
||||
|
@ -781,14 +817,6 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onAtYEndChanged: {
|
||||
if (purchasesContentsList.atYEnd && !purchasesContentsList.atYBeginning) {
|
||||
console.log("User scrolled to the bottom of 'Purchases'.");
|
||||
purchasesModel.getNextPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
|
|
@ -212,6 +212,7 @@ Item {
|
|||
HifiModels.PSFListModel {
|
||||
id: transactionHistoryModel;
|
||||
listModelName: "transaction history"; // For debugging. Alternatively, we could specify endpoint for that purpose, even though it's not used directly.
|
||||
listView: transactionHistory;
|
||||
itemsPerPage: 6;
|
||||
getPage: function () {
|
||||
console.debug('getPage', transactionHistoryModel.listModelName, transactionHistoryModel.currentPageToRetrieve);
|
||||
|
@ -346,12 +347,6 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
onAtYEndChanged: {
|
||||
if (transactionHistory.atYEnd && !transactionHistory.atYBeginning) {
|
||||
console.log("User scrolled to the bottom of 'Recent Activity'.");
|
||||
transactionHistoryModel.getNextPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
|
|
|
@ -33,7 +33,6 @@ ListModel {
|
|||
|
||||
// QML fires the following changed handlers even when first instantiating the Item. So we need a guard against firing them too early.
|
||||
property bool initialized: false;
|
||||
Component.onCompleted: initialized = true;
|
||||
onEndpointChanged: if (initialized) { getFirstPage('delayClear'); }
|
||||
onSortKeyChanged: if (initialized) { getFirstPage('delayClear'); }
|
||||
onSearchFilterChanged: if (initialized) { getFirstPage('delayClear'); }
|
||||
|
@ -60,7 +59,37 @@ ListModel {
|
|||
// Override to return one property of data, and/or to transform the elements. Must return an array of model elements.
|
||||
property var processPage: function (data) { return data; }
|
||||
|
||||
property var listView; // Optional. For debugging.
|
||||
property var listView; // Optional. For debugging, or for having the scroll handler automatically call getNextPage.
|
||||
property var flickable: listView && (listView.flickableItem || listView);
|
||||
// 2: get two pages before you need it (i.e. one full page before you reach the end).
|
||||
// 1: equivalent to paging when reaching end (and not before).
|
||||
// 0: don't getNextPage on scroll at all here. The application code will do it.
|
||||
property real pageAhead: 2.0;
|
||||
function needsEarlyYFetch() {
|
||||
return flickable
|
||||
&& !flickable.atYBeginning
|
||||
&& (flickable.contentY - flickable.originY) >= (flickable.contentHeight - (pageAhead * flickable.height));
|
||||
}
|
||||
function needsEarlyXFetch() {
|
||||
return flickable
|
||||
&& !flickable.atXBeginning
|
||||
&& (flickable.contentX - flickable.originX) >= (flickable.contentWidth - (pageAhead * flickable.width));
|
||||
}
|
||||
function getNextPageIfHorizontalScroll() {
|
||||
if (needsEarlyXFetch()) { getNextPage(); }
|
||||
}
|
||||
function getNextPageIfVerticalScroll() {
|
||||
if (needsEarlyYFetch()) { getNextPage(); }
|
||||
}
|
||||
Component.onCompleted: {
|
||||
initialized = true;
|
||||
if (flickable && pageAhead > 0.0) {
|
||||
// Pun: Scrollers are usually one direction or another, such that only one of the following will actually fire.
|
||||
flickable.contentXChanged.connect(getNextPageIfHorizontalScroll);
|
||||
flickable.contentYChanged.connect(getNextPageIfVerticalScroll);
|
||||
}
|
||||
}
|
||||
|
||||
property int totalPages: 0;
|
||||
property int totalEntries: 0;
|
||||
// Check consistency and call processPage.
|
||||
|
|
|
@ -299,7 +299,7 @@ Item {
|
|||
anchors.fill: stackView
|
||||
id: controllerPrefereneces
|
||||
objectName: "TabletControllerPreferences"
|
||||
showCategories: ["VR Movement", "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"]
|
||||
showCategories: [( (HMD.active) ? "VR Movement" : "Movement"), "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"]
|
||||
categoryProperties: {
|
||||
"VR Movement" : {
|
||||
"User real-world height (meters)" : { "anchors.right" : "undefined" },
|
||||
|
|
|
@ -3650,6 +3650,10 @@ bool Application::event(QEvent* event) {
|
|||
|
||||
bool Application::eventFilter(QObject* object, QEvent* event) {
|
||||
|
||||
if (_aboutToQuit) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::Leave) {
|
||||
getApplicationCompositor().handleLeaveEvent();
|
||||
}
|
||||
|
@ -4839,6 +4843,13 @@ void Application::loadSettings() {
|
|||
}
|
||||
|
||||
isFirstPerson = (qApp->isHMDMode());
|
||||
|
||||
// Flying should be disabled by default in HMD mode on first run, and it
|
||||
// should be enabled by default in desktop mode.
|
||||
|
||||
auto myAvatar = getMyAvatar();
|
||||
myAvatar->setFlyingEnabled(!isFirstPerson);
|
||||
|
||||
} else {
|
||||
// if this is not the first run, the camera will be initialized differently depending on user settings
|
||||
|
||||
|
|
|
@ -144,9 +144,19 @@ void AvatarBookmarks::removeBookmark(const QString& bookmarkName) {
|
|||
emit bookmarkDeleted(bookmarkName);
|
||||
}
|
||||
|
||||
bool isWearableEntity(const EntityItemPointer& entity) {
|
||||
return entity->isVisible() && entity->getParentJointIndex() != INVALID_JOINT_INDEX &&
|
||||
(entity->getParentID() == DependencyManager::get<NodeList>()->getSessionUUID() || entity->getParentID() == DependencyManager::get<AvatarManager>()->getMyAvatar()->getSelfID());
|
||||
}
|
||||
|
||||
void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) {
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
myAvatar->removeAvatarEntities();
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
myAvatar->removeAvatarEntities([&](const QUuid& entityID) {
|
||||
auto entity = entityTree->findEntityByID(entityID);
|
||||
return entity && isWearableEntity(entity);
|
||||
});
|
||||
|
||||
addAvatarEntities(avatarEntities);
|
||||
}
|
||||
|
@ -163,7 +173,12 @@ void AvatarBookmarks::loadBookmark(const QString& bookmarkName) {
|
|||
QVariantMap bookmark = bookmarkEntry.value().toMap();
|
||||
if (!bookmark.empty()) {
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
myAvatar->removeAvatarEntities();
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
myAvatar->removeAvatarEntities([&](const QUuid& entityID) {
|
||||
auto entity = entityTree->findEntityByID(entityID);
|
||||
return entity && isWearableEntity(entity);
|
||||
});
|
||||
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
|
||||
myAvatar->useFullAvatarURL(avatarUrl);
|
||||
qCDebug(interfaceapp) << "Avatar On " << avatarUrl;
|
||||
|
@ -233,6 +248,27 @@ QVariantMap AvatarBookmarks::getAvatarDataToBookmark() {
|
|||
bookmark.insert(ENTRY_AVATAR_URL, avatarUrl);
|
||||
bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale);
|
||||
bookmark.insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant());
|
||||
bookmark.insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant());
|
||||
|
||||
QScriptEngine scriptEngine;
|
||||
QVariantList wearableEntities;
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
auto avatarEntities = myAvatar->getAvatarEntityData();
|
||||
for (auto entityID : avatarEntities.keys()) {
|
||||
auto entity = entityTree->findEntityByID(entityID);
|
||||
if (!entity || !isWearableEntity(entity)) {
|
||||
continue;
|
||||
}
|
||||
QVariantMap avatarEntityData;
|
||||
EncodeBitstreamParams params;
|
||||
auto desiredProperties = entity->getEntityProperties(params);
|
||||
desiredProperties += PROP_LOCAL_POSITION;
|
||||
desiredProperties += PROP_LOCAL_ROTATION;
|
||||
EntityItemProperties entityProperties = entity->getProperties(desiredProperties);
|
||||
QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties);
|
||||
avatarEntityData["properties"] = scriptProperties.toVariant();
|
||||
wearableEntities.append(QVariant(avatarEntityData));
|
||||
}
|
||||
bookmark.insert(ENTRY_AVATAR_ENTITIES, wearableEntities);
|
||||
return bookmark;
|
||||
}
|
||||
|
|
|
@ -275,6 +275,13 @@ Menu::Menu() {
|
|||
QString("hifi/tablet/TabletGraphicsPreferences.qml"), "GraphicsPreferencesDialog");
|
||||
});
|
||||
|
||||
// Settings > Attachments...
|
||||
action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::Attachments);
|
||||
connect(action, &QAction::triggered, [] {
|
||||
qApp->showDialog(QString("hifi/dialogs/AttachmentsDialog.qml"),
|
||||
QString("hifi/tablet/TabletAttachmentsDialog.qml"), "AttachmentsDialog");
|
||||
});
|
||||
|
||||
// Settings > Developer Menu
|
||||
addCheckableActionToQMenuAndActionHash(settingsMenu, "Developer Menu", 0, false, this, SLOT(toggleDeveloperMenus()));
|
||||
|
||||
|
|
|
@ -1281,7 +1281,8 @@ void MyAvatar::loadData() {
|
|||
settings.remove("avatarEntityData");
|
||||
}
|
||||
setAvatarEntityDataChanged(true);
|
||||
setFlyingEnabled(settings.value("enabledFlying").toBool());
|
||||
Setting::Handle<bool> firstRunVal { Settings::firstRun, true };
|
||||
setFlyingEnabled(firstRunVal.get() ? getFlyingEnabled() : settings.value("enabledFlying").toBool());
|
||||
setDisplayName(settings.value("displayName").toString());
|
||||
setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString());
|
||||
setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool());
|
||||
|
@ -1607,14 +1608,16 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
emit skeletonModelURLChanged();
|
||||
}
|
||||
|
||||
void MyAvatar::removeAvatarEntities() {
|
||||
void MyAvatar::removeAvatarEntities(const std::function<bool(const QUuid& entityID)>& condition) {
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
if (entityTree) {
|
||||
entityTree->withWriteLock([&] {
|
||||
AvatarEntityMap avatarEntities = getAvatarEntityData();
|
||||
for (auto entityID : avatarEntities.keys()) {
|
||||
entityTree->deleteEntity(entityID, true, true);
|
||||
if (!condition || condition(entityID)) {
|
||||
entityTree->deleteEntity(entityID, true, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include "MyCharacterController.h"
|
||||
#include "RingBufferHistory.h"
|
||||
#include <ThreadSafeValueCache.h>
|
||||
#include <EntityItem.h>
|
||||
|
||||
class AvatarActionHold;
|
||||
class ModelItemID;
|
||||
|
@ -926,7 +927,7 @@ public:
|
|||
* @returns {object[]}
|
||||
*/
|
||||
Q_INVOKABLE QVariantList getAvatarEntitiesVariant();
|
||||
void removeAvatarEntities();
|
||||
void removeAvatarEntities(const std::function<bool(const QUuid& entityID)>& condition = {});
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.isFlying
|
||||
|
|
|
@ -262,6 +262,9 @@ int main(int argc, const char* argv[]) {
|
|||
// Extend argv to enable WebGL rendering
|
||||
std::vector<const char*> argvExtended(&argv[0], &argv[argc]);
|
||||
argvExtended.push_back("--ignore-gpu-blacklist");
|
||||
#ifdef Q_OS_ANDROID
|
||||
argvExtended.push_back("--suppress-settings-reset");
|
||||
#endif
|
||||
int argcExtended = (int)argvExtended.size();
|
||||
|
||||
PROFILE_SYNC_END(startup, "main startup", "");
|
||||
|
|
|
@ -56,6 +56,7 @@ Audio::Audio() : _devices(_contextIsHMD) {
|
|||
connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume);
|
||||
connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged);
|
||||
enableNoiseReduction(enableNoiseReductionSetting.get());
|
||||
onContextChanged();
|
||||
}
|
||||
|
||||
bool Audio::startRecording(const QString& filepath) {
|
||||
|
|
|
@ -172,11 +172,13 @@ void LoginDialog::openUrl(const QString& url) const {
|
|||
offscreenUi->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) {
|
||||
newObject->setProperty("url", url);
|
||||
});
|
||||
LoginDialog::hide();
|
||||
} else {
|
||||
if (!hmd->getShouldShowTablet() && !qApp->isHMDMode()) {
|
||||
offscreenUi->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) {
|
||||
newObject->setProperty("url", url);
|
||||
});
|
||||
LoginDialog::hide();
|
||||
} else {
|
||||
tablet->gotoWebScreen(url);
|
||||
}
|
||||
|
|
|
@ -266,20 +266,15 @@ void setupPreferences() {
|
|||
preferences->addPreference(new SliderPreference(FACE_TRACKING, "Eye Deflection", getter, setter));
|
||||
}
|
||||
|
||||
static const QString MOVEMENT{ "VR Movement" };
|
||||
static const QString MOVEMENT{ "Movement" };
|
||||
{
|
||||
|
||||
static const QString movementsControlChannel = QStringLiteral("Hifi-Advanced-Movement-Disabler");
|
||||
auto getter = [=]()->bool { return myAvatar->useAdvancedMovementControls(); };
|
||||
auto setter = [=](bool value) { myAvatar->setUseAdvancedMovementControls(value); };
|
||||
preferences->addPreference(new CheckPreference(MOVEMENT,
|
||||
QStringLiteral("Advanced movement for hand controllers"),
|
||||
getter, setter));
|
||||
}
|
||||
{
|
||||
auto getter = [=]()->bool { return myAvatar->getFlyingEnabled(); };
|
||||
auto setter = [=](bool value) { myAvatar->setFlyingEnabled(value); };
|
||||
preferences->addPreference(new CheckPreference(MOVEMENT, "Flying & jumping", getter, setter));
|
||||
QStringLiteral("Advanced movement for hand controllers"),
|
||||
getter, setter));
|
||||
}
|
||||
{
|
||||
auto getter = [=]()->int { return myAvatar->getSnapTurn() ? 0 : 1; };
|
||||
|
@ -307,6 +302,47 @@ void setupPreferences() {
|
|||
preferences->addPreference(preference);
|
||||
}
|
||||
|
||||
static const QString VR_MOVEMENT{ "VR Movement" };
|
||||
{
|
||||
|
||||
static const QString movementsControlChannel = QStringLiteral("Hifi-Advanced-Movement-Disabler");
|
||||
auto getter = [=]()->bool { return myAvatar->useAdvancedMovementControls(); };
|
||||
auto setter = [=](bool value) { myAvatar->setUseAdvancedMovementControls(value); };
|
||||
preferences->addPreference(new CheckPreference(VR_MOVEMENT,
|
||||
QStringLiteral("Advanced movement for hand controllers"),
|
||||
getter, setter));
|
||||
}
|
||||
{
|
||||
auto getter = [=]()->bool { return myAvatar->getFlyingEnabled(); };
|
||||
auto setter = [=](bool value) { myAvatar->setFlyingEnabled(value); };
|
||||
preferences->addPreference(new CheckPreference(VR_MOVEMENT, "Flying & jumping", getter, setter));
|
||||
}
|
||||
{
|
||||
auto getter = [=]()->int { return myAvatar->getSnapTurn() ? 0 : 1; };
|
||||
auto setter = [=](int value) { myAvatar->setSnapTurn(value == 0); };
|
||||
auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Snap turn / Smooth turn", getter, setter);
|
||||
QStringList items;
|
||||
items << "Snap turn" << "Smooth turn";
|
||||
preference->setItems(items);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = [=]()->float { return myAvatar->getUserHeight(); };
|
||||
auto setter = [=](float value) { myAvatar->setUserHeight(value); };
|
||||
auto preference = new SpinnerPreference(VR_MOVEMENT, "User real-world height (meters)", getter, setter);
|
||||
preference->setMin(1.0f);
|
||||
preference->setMax(2.2f);
|
||||
preference->setDecimals(3);
|
||||
preference->setStep(0.001f);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto preference = new ButtonPreference(VR_MOVEMENT, "RESET SENSORS", [] {
|
||||
qApp->resetSensors();
|
||||
});
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
|
||||
static const QString AVATAR_CAMERA{ "Mouse Sensitivity" };
|
||||
{
|
||||
auto getter = [=]()->float { return myAvatar->getPitchSpeed(); };
|
||||
|
|
|
@ -27,6 +27,12 @@ ModelOverlay::ModelOverlay()
|
|||
{
|
||||
_model->setLoadingPriority(_loadPriority);
|
||||
_isLoaded = false;
|
||||
|
||||
// Don't show overlay until textures have loaded
|
||||
_visible = false;
|
||||
|
||||
render::ScenePointer scene = qApp->getMain3DScene();
|
||||
_model->setVisibleInScene(false, scene);
|
||||
}
|
||||
|
||||
ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) :
|
||||
|
@ -101,10 +107,11 @@ void ModelOverlay::update(float deltatime) {
|
|||
emit DependencyManager::get<scriptable::ModelProviderFactory>()->modelAddedToScene(getID(), NestableType::Overlay, _model);
|
||||
}
|
||||
bool metaDirty = false;
|
||||
if (_visibleDirty) {
|
||||
if (_visibleDirty && _texturesLoaded) {
|
||||
_visibleDirty = false;
|
||||
// don't show overlays in mirrors or spectator-cam unless _isVisibleInSecondaryCamera is true
|
||||
uint8_t modelRenderTagMask = (_isVisibleInSecondaryCamera ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_MAIN_VIEW);
|
||||
|
||||
_model->setTagMask(modelRenderTagMask, scene);
|
||||
_model->setVisibleInScene(getVisible(), scene);
|
||||
metaDirty = true;
|
||||
|
@ -134,6 +141,8 @@ void ModelOverlay::update(float deltatime) {
|
|||
if (!_modelTextures.isEmpty()) {
|
||||
_model->setTextures(_modelTextures);
|
||||
}
|
||||
|
||||
_model->setVisibleInScene(getVisible(), scene);
|
||||
_model->updateRenderItems();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,11 +138,8 @@ void Web3DOverlay::destroyWebSurface() {
|
|||
// Fix for crash in QtWebEngineCore when rapidly switching domains
|
||||
// Call stop on the QWebEngineView before destroying OffscreenQMLSurface.
|
||||
if (rootItem) {
|
||||
QObject* obj = rootItem->findChild<QObject*>("webEngineView");
|
||||
if (obj) {
|
||||
// stop loading
|
||||
QMetaObject::invokeMethod(obj, "stop");
|
||||
}
|
||||
// stop loading
|
||||
QMetaObject::invokeMethod(rootItem, "stop");
|
||||
}
|
||||
|
||||
_webSurface->pause();
|
||||
|
@ -152,6 +149,11 @@ void Web3DOverlay::destroyWebSurface() {
|
|||
|
||||
// If the web surface was fetched out of the cache, release it back into the cache
|
||||
if (_cachedWebSurface) {
|
||||
// If it's going back into the cache make sure to explicitly set the URL to a blank page
|
||||
// in order to stop any resource consumption or audio related to the page.
|
||||
if (rootItem) {
|
||||
rootItem->setProperty("url", "about:blank");
|
||||
}
|
||||
auto offscreenCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
|
||||
// FIXME prevents crash on shutdown, but we shoudln't have to do this check
|
||||
if (offscreenCache) {
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
set(TARGET_NAME audio-client)
|
||||
setup_hifi_library(Network Multimedia)
|
||||
if (ANDROID)
|
||||
set(PLATFORM_QT_COMPONENTS AndroidExtras)
|
||||
endif ()
|
||||
setup_hifi_library(Network Multimedia ${PLATFORM_QT_COMPONENTS})
|
||||
link_hifi_libraries(audio plugins)
|
||||
include_hifi_library_headers(shared)
|
||||
include_hifi_library_headers(networking)
|
||||
|
|
|
@ -52,6 +52,10 @@
|
|||
#include "AudioLogging.h"
|
||||
#include "AudioHelpers.h"
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
#include <QtAndroidExtras/QAndroidJniObject>
|
||||
#endif
|
||||
|
||||
const int AudioClient::MIN_BUFFER_FRAMES = 1;
|
||||
|
||||
const int AudioClient::MAX_BUFFER_FRAMES = 20;
|
||||
|
@ -60,7 +64,7 @@ static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 100;
|
|||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
static const int CHECK_INPUT_READS_MSECS = 2000;
|
||||
static const int MIN_READS_TO_CONSIDER_INPUT_ALIVE = 100;
|
||||
static const int MIN_READS_TO_CONSIDER_INPUT_ALIVE = 10;
|
||||
#endif
|
||||
|
||||
static const auto DEFAULT_POSITION_GETTER = []{ return Vectors::ZERO; };
|
||||
|
@ -235,7 +239,7 @@ AudioClient::AudioClient() :
|
|||
|
||||
// start a thread to detect any device changes
|
||||
_checkDevicesTimer = new QTimer(this);
|
||||
connect(_checkDevicesTimer, &QTimer::timeout, [this] {
|
||||
connect(_checkDevicesTimer, &QTimer::timeout, this, [this] {
|
||||
QtConcurrent::run(QThreadPool::globalInstance(), [this] { checkDevices(); });
|
||||
});
|
||||
const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000;
|
||||
|
@ -243,7 +247,7 @@ AudioClient::AudioClient() :
|
|||
|
||||
// start a thread to detect peak value changes
|
||||
_checkPeakValuesTimer = new QTimer(this);
|
||||
connect(_checkPeakValuesTimer, &QTimer::timeout, [this] {
|
||||
connect(_checkPeakValuesTimer, &QTimer::timeout, this, [this] {
|
||||
QtConcurrent::run(QThreadPool::globalInstance(), [this] { checkPeakValues(); });
|
||||
});
|
||||
const unsigned long PEAK_VALUES_CHECK_INTERVAL_MSECS = 50;
|
||||
|
@ -482,6 +486,15 @@ bool nativeFormatForAudioDevice(const QAudioDeviceInfo& audioDevice,
|
|||
audioFormat.setSampleType(QAudioFormat::SignedInt);
|
||||
audioFormat.setByteOrder(QAudioFormat::LittleEndian);
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
// Using the HW sample rate (AUDIO_INPUT_FLAG_FAST) in some samsung phones causes a low volume at input stream
|
||||
// Changing the sample rate forces a resampling that (in samsung) amplifies +18 dB
|
||||
QAndroidJniObject brand = QAndroidJniObject::getStaticObjectField<jstring>("android/os/Build", "BRAND");
|
||||
if (audioDevice == QAudioDeviceInfo::defaultInputDevice() && brand.toString().contains("samsung", Qt::CaseInsensitive)) {
|
||||
audioFormat.setSampleRate(24000);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!audioDevice.isFormatSupported(audioFormat)) {
|
||||
qCWarning(audioclient) << "The native format is" << audioFormat << "but isFormatSupported() failed.";
|
||||
return false;
|
||||
|
@ -635,9 +648,7 @@ void AudioClient::start() {
|
|||
qCDebug(audioclient) << "The closest format available is" << outputDeviceInfo.nearestFormat(_desiredOutputFormat);
|
||||
}
|
||||
#if defined(Q_OS_ANDROID)
|
||||
connect(&_checkInputTimer, &QTimer::timeout, [this] {
|
||||
checkInputTimeout();
|
||||
});
|
||||
connect(&_checkInputTimer, &QTimer::timeout, this, &AudioClient::checkInputTimeout);
|
||||
_checkInputTimer.start(CHECK_INPUT_READS_MSECS);
|
||||
#endif
|
||||
}
|
||||
|
@ -651,6 +662,7 @@ void AudioClient::stop() {
|
|||
switchOutputToAudioDevice(QAudioDeviceInfo(), true);
|
||||
#if defined(Q_OS_ANDROID)
|
||||
_checkInputTimer.stop();
|
||||
disconnect(&_checkInputTimer, &QTimer::timeout, 0, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -1558,9 +1570,7 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo inputDeviceInf
|
|||
#if defined(Q_OS_ANDROID)
|
||||
if (_audioInput) {
|
||||
_shouldRestartInputSetup = true;
|
||||
connect(_audioInput, &QAudioInput::stateChanged, [this](QAudio::State state) {
|
||||
audioInputStateChanged(state);
|
||||
});
|
||||
connect(_audioInput, &QAudioInput::stateChanged, this, &AudioClient::audioInputStateChanged);
|
||||
}
|
||||
#endif
|
||||
_inputDevice = _audioInput->start();
|
||||
|
@ -1764,7 +1774,7 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo outputDeviceI
|
|||
_outputScratchBuffer = new int16_t[_outputPeriod];
|
||||
|
||||
// size local output mix buffer based on resampled network frame size
|
||||
int networkPeriod = _localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_STEREO);
|
||||
int networkPeriod = _localToOutputResampler ? _localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_STEREO) : AudioConstants::NETWORK_FRAME_SAMPLES_STEREO;
|
||||
_localOutputMixBuffer = new float[networkPeriod];
|
||||
|
||||
// local period should be at least twice the output period,
|
||||
|
|
|
@ -48,6 +48,13 @@ void HTTPManager::incomingConnection(qintptr socketDescriptor) {
|
|||
}
|
||||
|
||||
bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) {
|
||||
// Reject paths with embedded NULs
|
||||
if (url.path().contains(QChar(0x00))) {
|
||||
connection->respond(HTTPConnection::StatusCode400, "Embedded NULs not allowed in requests");
|
||||
qCWarning(embeddedwebserver) << "Received a request with embedded NULs";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!skipSubHandler && requestHandledByRequestHandler(connection, url)) {
|
||||
// this request was handled by our request handler object
|
||||
// so we don't need to attempt to do so in the document root
|
||||
|
@ -57,17 +64,27 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url,
|
|||
if (!_documentRoot.isEmpty()) {
|
||||
// check to see if there is a file to serve from the document root for this path
|
||||
QString subPath = url.path();
|
||||
|
||||
|
||||
// remove any slash at the beginning of the path
|
||||
if (subPath.startsWith('/')) {
|
||||
subPath.remove(0, 1);
|
||||
}
|
||||
|
||||
|
||||
QString absoluteDocumentRoot { QFileInfo(_documentRoot).absolutePath() };
|
||||
QString filePath;
|
||||
|
||||
if (QFileInfo(_documentRoot + subPath).isFile()) {
|
||||
filePath = _documentRoot + subPath;
|
||||
} else if (subPath.size() > 0 && !subPath.endsWith('/')) {
|
||||
QFileInfo pathFileInfo { _documentRoot + subPath };
|
||||
QString absoluteFilePath { pathFileInfo.absoluteFilePath() };
|
||||
|
||||
// The absolute path for this file isn't under the document root
|
||||
if (absoluteFilePath.indexOf(absoluteDocumentRoot) != 0) {
|
||||
qCWarning(embeddedwebserver) << absoluteFilePath << "is outside the document root";
|
||||
connection->respond(HTTPConnection::StatusCode400, "Requested path outside document root");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pathFileInfo.isFile()) {
|
||||
filePath = absoluteFilePath;
|
||||
} else if (subPath.size() > 0 && !subPath.endsWith('/') && pathFileInfo.isDir()) {
|
||||
// this could be a directory with a trailing slash
|
||||
// send a redirect to the path with a slash so we can
|
||||
QString redirectLocation = '/' + subPath + '/';
|
||||
|
@ -80,6 +97,7 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url,
|
|||
redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8());
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode302, "", HTTPConnection::DefaultContentType, redirectHeader);
|
||||
return true;
|
||||
}
|
||||
|
||||
// if the last thing is a trailing slash then we want to look for index file
|
||||
|
@ -87,8 +105,8 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url,
|
|||
QStringList possibleIndexFiles = QStringList() << "index.html" << "index.shtml";
|
||||
|
||||
foreach (const QString& possibleIndexFilename, possibleIndexFiles) {
|
||||
if (QFileInfo(_documentRoot + subPath + possibleIndexFilename).exists()) {
|
||||
filePath = _documentRoot + subPath + possibleIndexFilename;
|
||||
if (QFileInfo(absoluteFilePath + possibleIndexFilename).exists()) {
|
||||
filePath = absoluteFilePath + possibleIndexFilename;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -185,7 +185,7 @@ private:
|
|||
#if defined(Q_OS_ANDROID)
|
||||
Setting::Handle<bool> _ignoreRadiusEnabled { "IgnoreRadiusEnabled", false };
|
||||
#else
|
||||
Setting::Handle<bool> _ignoreRadiusEnabled { "IgnoreRadiusEnabled", true };
|
||||
Setting::Handle<bool> _ignoreRadiusEnabled { "IgnoreRadiusEnabled", false }; // False, until such time as it is made to work better.
|
||||
#endif
|
||||
|
||||
#if (PR_BUILD || DEV_BUILD)
|
||||
|
|
|
@ -229,7 +229,7 @@ qint64 Socket::writeDatagram(const QByteArray& datagram, const HifiSockAddr& soc
|
|||
|
||||
if (bytesWritten < 0) {
|
||||
// when saturating a link this isn't an uncommon message - suppress it so it doesn't bomb the debug
|
||||
HIFI_FCDEBUG(networking(), "Socket::writeDatagram" << _udpSocket.error() << "-" << qPrintable(_udpSocket.errorString()) );
|
||||
HIFI_FCDEBUG(networking(), "Socket::writeDatagram" << _udpSocket.error());
|
||||
}
|
||||
|
||||
return bytesWritten;
|
||||
|
@ -513,7 +513,7 @@ std::vector<HifiSockAddr> Socket::getConnectionSockAddrs() {
|
|||
}
|
||||
|
||||
void Socket::handleSocketError(QAbstractSocket::SocketError socketError) {
|
||||
HIFI_FCDEBUG(networking(), "udt::Socket error - " << socketError << _udpSocket.errorString());
|
||||
HIFI_FCDEBUG(networking(), "udt::Socket error - " << socketError);
|
||||
}
|
||||
|
||||
void Socket::handleStateChanged(QAbstractSocket::SocketState socketState) {
|
||||
|
|
187
scripts/modules/appUi.js
Normal file
187
scripts/modules/appUi.js
Normal file
|
@ -0,0 +1,187 @@
|
|||
"use strict";
|
||||
/*global Tablet, Script*/
|
||||
//
|
||||
// libraries/appUi.js
|
||||
//
|
||||
// Created by Howard Stearns on 3/20/18.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
function AppUi(properties) {
|
||||
/* Example development order:
|
||||
1. var AppUi = Script.require('appUi');
|
||||
2. Put appname-i.svg, appname-a.svg in graphicsDirectory (where non-default graphicsDirectory can be added in #3).
|
||||
3. ui = new AppUi({buttonName: "APPNAME", home: "qml-or-html-path"});
|
||||
(And if converting an existing app,
|
||||
define var tablet = ui.tablet, button = ui.button; as needed.
|
||||
remove button.clicked.[dis]connect and tablet.remove(button).)
|
||||
4. Define onOpened and onClosed behavior in #3, if any.
|
||||
(And if converting an existing app, remove screenChanged.[dis]connect.)
|
||||
5. Define onMessage and sendMessage in #3, if any. onMessage is wired/unwired on open/close. If you
|
||||
want a handler to be "always on", connect it yourself at script startup.
|
||||
(And if converting an existing app, remove code that [un]wires that message handling such as
|
||||
fromQml/sendToQml or webEventReceived/emitScriptEvent.)
|
||||
6. (If converting an existing app, cleanup stuff that is no longer necessary, like references to button, tablet,
|
||||
and use isOpen, open(), and close() as needed.)
|
||||
7. lint!
|
||||
*/
|
||||
var that = this;
|
||||
function defaultButton(name, suffix) {
|
||||
var base = that[name] || (that.buttonPrefix + suffix);
|
||||
that[name] = (base.indexOf('/') >= 0) ? base : (that.graphicsDirectory + base); //poor man's merge
|
||||
}
|
||||
|
||||
// Defaults:
|
||||
that.tabletName = "com.highfidelity.interface.tablet.system";
|
||||
that.inject = "";
|
||||
that.graphicsDirectory = "icons/tablet-icons/"; // Where to look for button svgs. See below.
|
||||
that.checkIsOpen = function checkIsOpen(type, tabletUrl) { // Are we active? Value used to set isOpen.
|
||||
return (type === that.type) && that.currentUrl && (tabletUrl.indexOf(that.currentUrl) >= 0); // Actual url may have prefix or suffix.
|
||||
};
|
||||
that.setCurrentData = function setCurrentData(url) {
|
||||
that.currentUrl = url;
|
||||
that.type = /.qml$/.test(url) ? 'QML' : 'Web';
|
||||
}
|
||||
that.open = function open(optionalUrl) { // How to open the app.
|
||||
var url = optionalUrl || that.home;
|
||||
that.setCurrentData(url);
|
||||
if (that.isQML()) {
|
||||
that.tablet.loadQMLSource(url);
|
||||
} else {
|
||||
that.tablet.gotoWebScreen(url, that.inject);
|
||||
}
|
||||
};
|
||||
that.close = function close() { // How to close the app.
|
||||
that.currentUrl = "";
|
||||
// for toolbar-mode: go back to home screen, this will close the window.
|
||||
that.tablet.gotoHomeScreen();
|
||||
};
|
||||
that.buttonActive = function buttonActive(isActive) { // How to make the button active (white).
|
||||
that.button.editProperties({isActive: isActive});
|
||||
};
|
||||
that.messagesWaiting = function messagesWaiting(isWaiting) { // How to indicate a message light on button.
|
||||
// Note that waitingButton doesn't have to exist unless someone explicitly calls this with isWaiting true.
|
||||
that.button.editProperties({
|
||||
icon: isWaiting ? that.normalMessagesButton : that.normalButton,
|
||||
activeIcon: isWaiting ? that.activeMessagesButton : that.activeButton
|
||||
});
|
||||
};
|
||||
that.isQML = function isQML() { // We set type property in onClick.
|
||||
return that.type === 'QML';
|
||||
};
|
||||
that.eventSignal = function eventSignal() { // What signal to hook onMessage to.
|
||||
return that.isQML() ? that.tablet.fromQml : that.tablet.webEventReceived;
|
||||
};
|
||||
|
||||
// Overwrite with the given properties:
|
||||
Object.keys(properties).forEach(function (key) { that[key] = properties[key]; });
|
||||
|
||||
// Properties:
|
||||
that.tablet = Tablet.getTablet(that.tabletName);
|
||||
// Must be after we gather properties.
|
||||
that.buttonPrefix = that.buttonPrefix || that.buttonName.toLowerCase() + "-";
|
||||
defaultButton('normalButton', 'i.svg');
|
||||
defaultButton('activeButton', 'a.svg');
|
||||
defaultButton('normalMessagesButton', 'i-msg.svg');
|
||||
defaultButton('activeMessagesButton', 'a-msg.svg');
|
||||
that.button = that.tablet.addButton({
|
||||
icon: that.normalButton,
|
||||
activeIcon: that.activeButton,
|
||||
text: that.buttonName,
|
||||
sortOrder: that.sortOrder
|
||||
});
|
||||
that.ignore = function ignore() { };
|
||||
|
||||
// Handlers
|
||||
that.onScreenChanged = function onScreenChanged(type, url) {
|
||||
// Set isOpen, wireEventBridge, set buttonActive as appropriate,
|
||||
// and finally call onOpened() or onClosed() IFF defined.
|
||||
console.debug(that.buttonName, 'onScreenChanged', type, url, that.isOpen);
|
||||
if (that.checkIsOpen(type, url)) {
|
||||
if (!that.isOpen) {
|
||||
that.wireEventBridge(true);
|
||||
that.buttonActive(true);
|
||||
if (that.onOpened) {
|
||||
that.onOpened();
|
||||
}
|
||||
that.isOpen = true;
|
||||
}
|
||||
|
||||
} else { // Not us. Should we do something for type Home, Menu, and particularly Closed (meaning tablet hidden?
|
||||
if (that.isOpen) {
|
||||
that.wireEventBridge(false);
|
||||
that.buttonActive(false);
|
||||
if (that.onClosed) {
|
||||
that.onClosed();
|
||||
}
|
||||
that.isOpen = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
that.hasEventBridge = false;
|
||||
// HTML event bridge uses strings, not objects. Here we abstract over that.
|
||||
// (Although injected javascript still has to use JSON.stringify/JSON.parse.)
|
||||
that.sendToHtml = function (messageObject) { that.tablet.emitScriptEvent(JSON.stringify(messageObject)); };
|
||||
that.fromHtml = function (messageString) { that.onMessage(JSON.parse(messageString)); };
|
||||
that.wireEventBridge = function wireEventBridge(on) {
|
||||
// Uniquivocally sets that.sendMessage(messageObject) to do the right thing.
|
||||
// Sets hasEventBridge and wires onMessage to eventSignal as appropriate, IFF onMessage defined.
|
||||
var handler, isQml = that.isQML();
|
||||
// Outbound (always, regardless of whether there is an inbound handler).
|
||||
if (on) {
|
||||
that.sendMessage = isQml ? that.tablet.sendToQml : that.sendToHtml;
|
||||
} else {
|
||||
that.sendMessage = that.ignore;
|
||||
}
|
||||
|
||||
if (!that.onMessage) { return; }
|
||||
|
||||
// Inbound
|
||||
handler = isQml ? that.onMessage : that.fromHtml;
|
||||
if (on) {
|
||||
if (!that.hasEventBridge) {
|
||||
console.debug(that.buttonName, 'connecting', that.eventSignal());
|
||||
that.eventSignal().connect(handler);
|
||||
that.hasEventBridge = true;
|
||||
}
|
||||
} else {
|
||||
if (that.hasEventBridge) {
|
||||
console.debug(that.buttonName, 'disconnecting', that.eventSignal());
|
||||
that.eventSignal().disconnect(handler);
|
||||
that.hasEventBridge = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
that.isOpen = false;
|
||||
// To facilitate incremental development, only wire onClicked to do something when "home" is defined in properties.
|
||||
that.onClicked = that.home
|
||||
? function onClicked() {
|
||||
// Call open() or close(), and reset type based on current home property.
|
||||
if (that.isOpen) {
|
||||
that.close();
|
||||
} else {
|
||||
that.open();
|
||||
}
|
||||
} : that.ignore;
|
||||
that.onScriptEnding = function onScriptEnding() {
|
||||
// Close if necessary, clean up any remaining handlers, and remove the button.
|
||||
if (that.isOpen) {
|
||||
that.close();
|
||||
}
|
||||
that.tablet.screenChanged.disconnect(that.onScreenChanged);
|
||||
if (that.button) {
|
||||
if (that.onClicked) {
|
||||
that.button.clicked.disconnect(that.onClicked);
|
||||
}
|
||||
that.tablet.removeButton(that.button);
|
||||
}
|
||||
};
|
||||
// Set up the handlers.
|
||||
that.tablet.screenChanged.connect(that.onScreenChanged);
|
||||
that.button.clicked.connect(that.onClicked);
|
||||
Script.scriptEnding.connect(that.onScriptEnding);
|
||||
}
|
||||
module.exports = AppUi;
|
|
@ -29,13 +29,25 @@ function executeLater(callback) {
|
|||
Script.setTimeout(callback, 300);
|
||||
}
|
||||
|
||||
function getMyAvatarWearables() {
|
||||
var wearablesArray = MyAvatar.getAvatarEntitiesVariant();
|
||||
var INVALID_JOINT_INDEX = -1
|
||||
function isWearable(avatarEntity) {
|
||||
return avatarEntity.properties.visible === true && avatarEntity.properties.parentJointIndex !== INVALID_JOINT_INDEX &&
|
||||
(avatarEntity.properties.parentID === MyAvatar.sessionUUID || avatarEntity.properties.parentID === MyAvatar.SELF_ID);
|
||||
}
|
||||
|
||||
for(var i = 0; i < wearablesArray.length; ++i) {
|
||||
var wearable = wearablesArray[i];
|
||||
var localRotation = wearable.properties.localRotation;
|
||||
wearable.properties.localRotationAngles = Quat.safeEulerAngles(localRotation)
|
||||
function getMyAvatarWearables() {
|
||||
var entitiesArray = MyAvatar.getAvatarEntitiesVariant();
|
||||
var wearablesArray = [];
|
||||
|
||||
for (var i = 0; i < entitiesArray.length; ++i) {
|
||||
var entity = entitiesArray[i];
|
||||
if (!isWearable(entity)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var localRotation = entity.properties.localRotation;
|
||||
entity.properties.localRotationAngles = Quat.safeEulerAngles(localRotation)
|
||||
wearablesArray.push(entity);
|
||||
}
|
||||
|
||||
return wearablesArray;
|
||||
|
@ -159,13 +171,12 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
|
||||
for(var bookmarkName in message.data.bookmarks) {
|
||||
var bookmark = message.data.bookmarks[bookmarkName];
|
||||
if (!bookmark.avatarEntites) { // ensure avatarEntites always exist
|
||||
bookmark.avatarEntites = [];
|
||||
}
|
||||
|
||||
bookmark.avatarEntites.forEach(function(avatarEntity) {
|
||||
avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation)
|
||||
})
|
||||
if (bookmark.avatarEntites) {
|
||||
bookmark.avatarEntites.forEach(function(avatarEntity) {
|
||||
avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
sendToQml(message)
|
||||
|
@ -444,10 +455,6 @@ startup();
|
|||
|
||||
var isWired = false;
|
||||
function off() {
|
||||
if (isWired) { // It is not ok to disconnect these twice, hence guard.
|
||||
isWired = false;
|
||||
}
|
||||
|
||||
if(adjustWearables.opened) {
|
||||
adjustWearables.setOpened(false);
|
||||
ensureWearableSelected(null);
|
||||
|
@ -457,16 +464,20 @@ function off() {
|
|||
Messages.unsubscribe('Hifi-Object-Manipulation');
|
||||
}
|
||||
|
||||
AvatarBookmarks.bookmarkLoaded.disconnect(onBookmarkLoaded);
|
||||
AvatarBookmarks.bookmarkDeleted.disconnect(onBookmarkDeleted);
|
||||
AvatarBookmarks.bookmarkAdded.disconnect(onBookmarkAdded);
|
||||
if (isWired) { // It is not ok to disconnect these twice, hence guard.
|
||||
isWired = false;
|
||||
|
||||
MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged);
|
||||
MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged);
|
||||
MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged);
|
||||
MyAvatar.newCollisionSoundURL.disconnect(onNewCollisionSoundUrl);
|
||||
MyAvatar.animGraphUrlChanged.disconnect(onAnimGraphUrlChanged);
|
||||
MyAvatar.targetScaleChanged.disconnect(onTargetScaleChanged);
|
||||
AvatarBookmarks.bookmarkLoaded.disconnect(onBookmarkLoaded);
|
||||
AvatarBookmarks.bookmarkDeleted.disconnect(onBookmarkDeleted);
|
||||
AvatarBookmarks.bookmarkAdded.disconnect(onBookmarkAdded);
|
||||
|
||||
MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged);
|
||||
MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged);
|
||||
MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged);
|
||||
MyAvatar.newCollisionSoundURL.disconnect(onNewCollisionSoundUrl);
|
||||
MyAvatar.animGraphUrlChanged.disconnect(onAnimGraphUrlChanged);
|
||||
MyAvatar.targetScaleChanged.disconnect(onTargetScaleChanged);
|
||||
}
|
||||
}
|
||||
|
||||
function on() {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
/* global Script, Controller, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, makeRunningValues,
|
||||
Messages, makeDispatcherModuleParameters, HMD, getGrabPointSphereOffset, COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
|
||||
COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_ON_VALUE,
|
||||
getEnabledModuleByName, PICK_MAX_DISTANCE, isInEditMode, Picks, makeLaserParams
|
||||
getEnabledModuleByName, PICK_MAX_DISTANCE, isInEditMode, Picks, makeLaserParams, Entities
|
||||
*/
|
||||
|
||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||
|
@ -19,7 +19,7 @@ Script.include("/~/system/libraries/utils.js");
|
|||
|
||||
(function () {
|
||||
var MARGIN = 25;
|
||||
|
||||
var TABLET_MATERIAL_ENTITY_NAME = 'Tablet-Material-Entity';
|
||||
function InEditMode(hand) {
|
||||
this.hand = hand;
|
||||
this.triggerClicked = false;
|
||||
|
@ -53,7 +53,7 @@ Script.include("/~/system/libraries/utils.js");
|
|||
return (HMD.tabletScreenID && objectID === HMD.tabletScreenID)
|
||||
|| (HMD.homeButtonID && objectID === HMD.homeButtonID);
|
||||
};
|
||||
|
||||
|
||||
this.calculateNewReticlePosition = function(intersection) {
|
||||
var dims = Controller.getViewportDimensions();
|
||||
this.reticleMaxX = dims.x - MARGIN;
|
||||
|
@ -75,10 +75,12 @@ Script.include("/~/system/libraries/utils.js");
|
|||
}
|
||||
}
|
||||
if (this.selectedTarget.type === Picks.INTERSECTED_ENTITY) {
|
||||
Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({
|
||||
method: "selectEntity",
|
||||
entityID: this.selectedTarget.objectID
|
||||
}));
|
||||
if (!this.isTabletMaterialEntity(this.selectedTarget.objectID)) {
|
||||
Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({
|
||||
method: "selectEntity",
|
||||
entityID: this.selectedTarget.objectID
|
||||
}));
|
||||
}
|
||||
} else if (this.selectedTarget.type === Picks.INTERSECTED_OVERLAY) {
|
||||
Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({
|
||||
method: "selectOverlay",
|
||||
|
@ -88,10 +90,16 @@ Script.include("/~/system/libraries/utils.js");
|
|||
|
||||
this.triggerClicked = true;
|
||||
}
|
||||
|
||||
|
||||
this.sendPointingAtData(controllerData);
|
||||
};
|
||||
|
||||
|
||||
|
||||
this.isTabletMaterialEntity = function(entityID) {
|
||||
return ((entityID === HMD.homeButtonHighlightMaterialID) ||
|
||||
(entityID === HMD.homeButtonUnhighlightMaterialID));
|
||||
};
|
||||
|
||||
this.sendPointingAtData = function(controllerData) {
|
||||
var rayPick = controllerData.rayPicks[this.hand];
|
||||
var hudRayPick = controllerData.hudRayPicks[this.hand];
|
||||
|
|
|
@ -77,6 +77,8 @@ VIVE_CONTROLLER_CONFIGURATION_LEFT = {
|
|||
dimensions: viveNaturalDimensions,
|
||||
|
||||
parts: {
|
||||
// DISABLED FOR NOW
|
||||
/*
|
||||
tips: {
|
||||
type: "static",
|
||||
modelURL: viveTipsModelURL,
|
||||
|
@ -103,6 +105,7 @@ VIVE_CONTROLLER_CONFIGURATION_LEFT = {
|
|||
}
|
||||
}
|
||||
},
|
||||
*/
|
||||
|
||||
// The touchpad type draws a dot indicating the current touch/thumb position
|
||||
// and swaps in textures based on the thumb position.
|
||||
|
@ -215,6 +218,8 @@ VIVE_CONTROLLER_CONFIGURATION_RIGHT = {
|
|||
},
|
||||
|
||||
parts: {
|
||||
// DISABLED FOR NOW
|
||||
/*
|
||||
tips: {
|
||||
type: "static",
|
||||
modelURL: viveTipsModelURL,
|
||||
|
@ -242,6 +247,7 @@ VIVE_CONTROLLER_CONFIGURATION_RIGHT = {
|
|||
}
|
||||
}
|
||||
},
|
||||
*/
|
||||
|
||||
// The touchpad type draws a dot indicating the current touch/thumb position
|
||||
// and swaps in textures based on the thumb position.
|
||||
|
|
|
@ -1922,7 +1922,11 @@ function applyEntityProperties(data) {
|
|||
Entities.deleteEntity(entityID);
|
||||
}
|
||||
|
||||
selectionManager.setSelections(selectedEntityIDs);
|
||||
// We might be getting an undo while edit.js is disabled. If that is the case, don't set
|
||||
// our selections, causing the edit widgets to display.
|
||||
if (isActive) {
|
||||
selectionManager.setSelections(selectedEntityIDs);
|
||||
}
|
||||
}
|
||||
|
||||
// For currently selected entities, push a command to the UndoStack that uses the current entity properties for the
|
||||
|
|
|
@ -30,6 +30,8 @@ var INCHES_TO_METERS = 1 / 39.3701;
|
|||
var NO_HANDS = -1;
|
||||
var DELAY_FOR_30HZ = 33; // milliseconds
|
||||
|
||||
var TABLET_MATERIAL_ENTITY_NAME = 'Tablet-Material-Entity';
|
||||
|
||||
|
||||
// will need to be recaclulated if dimensions of fbx model change.
|
||||
var TABLET_NATURAL_DIMENSIONS = {x: 32.083, y: 48.553, z: 2.269};
|
||||
|
@ -79,6 +81,19 @@ function calcSpawnInfo(hand, landscape) {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
cleanUpOldMaterialEntities = function() {
|
||||
var avatarEntityData = MyAvatar.getAvatarEntityData();
|
||||
for (var entityID in avatarEntityData) {
|
||||
var entityName = Entities.getEntityProperties(entityID, ["name"]).name;
|
||||
|
||||
if (entityName === TABLET_MATERIAL_ENTITY_NAME && entityID !== HMD.homeButtonHighlightMaterialID &&
|
||||
entityID !== HMD.homeButtonUnhighlightMaterialID) {
|
||||
Entities.deleteEntity(entityID);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* WebTablet
|
||||
* @param url [string] url of content to show on the tablet.
|
||||
|
@ -134,6 +149,7 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) {
|
|||
}
|
||||
|
||||
this.cleanUpOldTablets();
|
||||
cleanUpOldMaterialEntities();
|
||||
|
||||
this.tabletEntityID = Overlays.addOverlay("model", tabletProperties);
|
||||
|
||||
|
@ -180,6 +196,7 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) {
|
|||
|
||||
this.homeButtonUnhighlightMaterial = Entities.addEntity({
|
||||
type: "Material",
|
||||
name: TABLET_MATERIAL_ENTITY_NAME,
|
||||
materialURL: "materialData",
|
||||
localPosition: { x: 0.0, y: 0.0, z: 0.0 },
|
||||
priority: HIGH_PRIORITY,
|
||||
|
@ -199,6 +216,7 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) {
|
|||
|
||||
this.homeButtonHighlightMaterial = Entities.addEntity({
|
||||
type: "Material",
|
||||
name: TABLET_MATERIAL_ENTITY_NAME,
|
||||
materialURL: "materialData",
|
||||
localPosition: { x: 0.0, y: 0.0, z: 0.0 },
|
||||
priority: LOW_PRIORITY,
|
||||
|
|
|
@ -111,6 +111,11 @@ EntityListTool = function(shouldUseEditTabletApp) {
|
|||
return value !== undefined ? value : "";
|
||||
}
|
||||
|
||||
function filterEntity(entityID) {
|
||||
return ((entityID === HMD.homeButtonHighlightMaterialID) ||
|
||||
(entityID === HMD.homeButtonUnhighlightMaterialID));
|
||||
}
|
||||
|
||||
that.sendUpdate = function() {
|
||||
var entities = [];
|
||||
|
||||
|
@ -121,6 +126,10 @@ EntityListTool = function(shouldUseEditTabletApp) {
|
|||
ids = Entities.findEntities(MyAvatar.position, searchRadius);
|
||||
}
|
||||
|
||||
ids = ids.filter(function(id) {
|
||||
return !filterEntity(id);
|
||||
});
|
||||
|
||||
var cameraPosition = Camera.position;
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
var id = ids[i];
|
||||
|
|
|
@ -263,7 +263,7 @@ SelectionManager = (function() {
|
|||
|
||||
that.worldDimensions = properties.boundingBox.dimensions;
|
||||
that.worldPosition = properties.boundingBox.center;
|
||||
that.worldRotation = properties.boundingBox.rotation;
|
||||
that.worldRotation = Quat.IDENTITY;
|
||||
|
||||
that.entityType = properties.type;
|
||||
|
||||
|
@ -271,10 +271,6 @@ SelectionManager = (function() {
|
|||
SelectionDisplay.setSpaceMode(SPACE_LOCAL);
|
||||
}
|
||||
} else {
|
||||
that.localRotation = null;
|
||||
that.localDimensions = null;
|
||||
that.localPosition = null;
|
||||
|
||||
properties = Entities.getEntityProperties(that.selections[0]);
|
||||
|
||||
that.entityType = properties.type;
|
||||
|
@ -293,6 +289,7 @@ SelectionManager = (function() {
|
|||
tfl.z = Math.max(bb.tfl.z, tfl.z);
|
||||
}
|
||||
|
||||
that.localRotation = null;
|
||||
that.localDimensions = null;
|
||||
that.localPosition = null;
|
||||
that.worldDimensions = {
|
||||
|
@ -300,6 +297,7 @@ SelectionManager = (function() {
|
|||
y: tfl.y - brn.y,
|
||||
z: tfl.z - brn.z
|
||||
};
|
||||
that.worldRotation = Quat.IDENTITY;
|
||||
that.worldPosition = {
|
||||
x: brn.x + (that.worldDimensions.x / 2),
|
||||
y: brn.y + (that.worldDimensions.y / 2),
|
||||
|
@ -381,6 +379,8 @@ SelectionDisplay = (function() {
|
|||
|
||||
var CTRL_KEY_CODE = 16777249;
|
||||
|
||||
var RAIL_AXIS_LENGTH = 10000;
|
||||
|
||||
var TRANSLATE_DIRECTION = {
|
||||
X: 0,
|
||||
Y: 1,
|
||||
|
@ -616,6 +616,40 @@ SelectionDisplay = (function() {
|
|||
dashed: false
|
||||
});
|
||||
|
||||
var xRailOverlay = Overlays.addOverlay("line3d", {
|
||||
visible: false,
|
||||
start: Vec3.ZERO,
|
||||
end: Vec3.ZERO,
|
||||
color: {
|
||||
red: 255,
|
||||
green: 0,
|
||||
blue: 0
|
||||
},
|
||||
ignoreRayIntersection: true // always ignore this
|
||||
});
|
||||
var yRailOverlay = Overlays.addOverlay("line3d", {
|
||||
visible: false,
|
||||
start: Vec3.ZERO,
|
||||
end: Vec3.ZERO,
|
||||
color: {
|
||||
red: 0,
|
||||
green: 255,
|
||||
blue: 0
|
||||
},
|
||||
ignoreRayIntersection: true // always ignore this
|
||||
});
|
||||
var zRailOverlay = Overlays.addOverlay("line3d", {
|
||||
visible: false,
|
||||
start: Vec3.ZERO,
|
||||
end: Vec3.ZERO,
|
||||
color: {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 255
|
||||
},
|
||||
ignoreRayIntersection: true // always ignore this
|
||||
});
|
||||
|
||||
var allOverlays = [
|
||||
handleTranslateXCone,
|
||||
handleTranslateXCylinder,
|
||||
|
@ -656,7 +690,11 @@ SelectionDisplay = (function() {
|
|||
handleScaleFLEdge,
|
||||
handleCloner,
|
||||
selectionBox,
|
||||
iconSelectionBox
|
||||
iconSelectionBox,
|
||||
xRailOverlay,
|
||||
yRailOverlay,
|
||||
zRailOverlay
|
||||
|
||||
];
|
||||
var maximumHandleInAllOverlays = handleCloner;
|
||||
|
||||
|
@ -873,11 +911,13 @@ SelectionDisplay = (function() {
|
|||
};
|
||||
|
||||
// FUNCTION: MOUSE MOVE EVENT
|
||||
var lastMouseEvent = null;
|
||||
that.mouseMoveEvent = function(event) {
|
||||
var wantDebug = false;
|
||||
if (wantDebug) {
|
||||
print("=============== eST::MouseMoveEvent BEG =======================");
|
||||
}
|
||||
lastMouseEvent = event;
|
||||
if (activeTool) {
|
||||
if (wantDebug) {
|
||||
print(" Trigger ActiveTool(" + activeTool.mode + ")'s onMove");
|
||||
|
@ -999,19 +1039,35 @@ SelectionDisplay = (function() {
|
|||
};
|
||||
|
||||
// Control key remains active only while key is held down
|
||||
that.keyReleaseEvent = function(key) {
|
||||
if (key.key === CTRL_KEY_CODE) {
|
||||
that.keyReleaseEvent = function(event) {
|
||||
if (event.key === CTRL_KEY_CODE) {
|
||||
ctrlPressed = false;
|
||||
that.updateActiveRotateRing();
|
||||
}
|
||||
if (activeTool && lastMouseEvent !== null) {
|
||||
lastMouseEvent.isShifted = event.isShifted;
|
||||
lastMouseEvent.isMeta = event.isMeta;
|
||||
lastMouseEvent.isControl = event.isControl;
|
||||
lastMouseEvent.isAlt = event.isAlt;
|
||||
activeTool.onMove(lastMouseEvent);
|
||||
SelectionManager._update();
|
||||
}
|
||||
};
|
||||
|
||||
// Triggers notification on specific key driven events
|
||||
that.keyPressEvent = function(key) {
|
||||
if (key.key === CTRL_KEY_CODE) {
|
||||
that.keyPressEvent = function(event) {
|
||||
if (event.key === CTRL_KEY_CODE) {
|
||||
ctrlPressed = true;
|
||||
that.updateActiveRotateRing();
|
||||
}
|
||||
if (activeTool && lastMouseEvent !== null) {
|
||||
lastMouseEvent.isShifted = event.isShifted;
|
||||
lastMouseEvent.isMeta = event.isMeta;
|
||||
lastMouseEvent.isControl = event.isControl;
|
||||
lastMouseEvent.isAlt = event.isAlt;
|
||||
activeTool.onMove(lastMouseEvent);
|
||||
SelectionManager._update();
|
||||
}
|
||||
};
|
||||
|
||||
// NOTE: mousePressEvent and mouseMoveEvent from the main script should call us., so we don't hook these:
|
||||
|
@ -1705,6 +1761,14 @@ SelectionDisplay = (function() {
|
|||
},
|
||||
onEnd: function(event, reason) {
|
||||
pushCommandForSelections(duplicatedEntityIDs);
|
||||
if (isConstrained) {
|
||||
Overlays.editOverlay(xRailOverlay, {
|
||||
visible: false
|
||||
});
|
||||
Overlays.editOverlay(zRailOverlay, {
|
||||
visible: false
|
||||
});
|
||||
}
|
||||
},
|
||||
elevation: function(origin, intersection) {
|
||||
return (origin.y - intersection.y) / Vec3.distance(origin, intersection);
|
||||
|
@ -1768,10 +1832,46 @@ SelectionDisplay = (function() {
|
|||
vector.x = 0;
|
||||
}
|
||||
if (!isConstrained) {
|
||||
var xStart = Vec3.sum(startPosition, {
|
||||
x: -RAIL_AXIS_LENGTH,
|
||||
y: 0,
|
||||
z: 0
|
||||
});
|
||||
var xEnd = Vec3.sum(startPosition, {
|
||||
x: RAIL_AXIS_LENGTH,
|
||||
y: 0,
|
||||
z: 0
|
||||
});
|
||||
var zStart = Vec3.sum(startPosition, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: -RAIL_AXIS_LENGTH
|
||||
});
|
||||
var zEnd = Vec3.sum(startPosition, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: RAIL_AXIS_LENGTH
|
||||
});
|
||||
Overlays.editOverlay(xRailOverlay, {
|
||||
start: xStart,
|
||||
end: xEnd,
|
||||
visible: true
|
||||
});
|
||||
Overlays.editOverlay(zRailOverlay, {
|
||||
start: zStart,
|
||||
end: zEnd,
|
||||
visible: true
|
||||
});
|
||||
isConstrained = true;
|
||||
}
|
||||
} else {
|
||||
if (isConstrained) {
|
||||
Overlays.editOverlay(xRailOverlay, {
|
||||
visible: false
|
||||
});
|
||||
Overlays.editOverlay(zRailOverlay, {
|
||||
visible: false
|
||||
});
|
||||
isConstrained = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,14 +12,15 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
(function () { // BEGIN LOCAL_SCOPE
|
||||
|
||||
var request = Script.require('request').request;
|
||||
var request = Script.require('request').request;
|
||||
var AppUi = Script.require('appUi');
|
||||
|
||||
var populateNearbyUserList, color, textures, removeOverlays,
|
||||
controllerComputePickRay, onTabletButtonClicked, onTabletScreenChanged,
|
||||
controllerComputePickRay, off,
|
||||
receiveMessage, avatarDisconnected, clearLocalQMLDataAndClosePAL,
|
||||
tablet, CHANNEL, getConnectionData, findableByChanged,
|
||||
CHANNEL, getConnectionData, findableByChanged,
|
||||
avatarAdded, avatarRemoved, avatarSessionChanged; // forward references;
|
||||
|
||||
// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed
|
||||
|
@ -40,6 +41,7 @@ var HOVER_TEXTURES = {
|
|||
var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6};
|
||||
var SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29};
|
||||
var HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now
|
||||
var METAVERSE_BASE = Account.metaverseServerURL;
|
||||
|
||||
Script.include("/~/system/libraries/controllers.js");
|
||||
|
||||
|
@ -221,7 +223,7 @@ function convertDbToLinear(decibels) {
|
|||
return Math.pow(2, decibels / 10.0);
|
||||
}
|
||||
function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml.
|
||||
var data;
|
||||
var data, connectionUserName, friendUserName;
|
||||
switch (message.method) {
|
||||
case 'selected':
|
||||
selectedIds = message.params;
|
||||
|
@ -281,7 +283,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
}
|
||||
getConnectionData(false);
|
||||
});
|
||||
break
|
||||
break;
|
||||
|
||||
case 'removeFriend':
|
||||
friendUserName = message.params;
|
||||
|
@ -296,7 +298,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
}
|
||||
getConnectionData(friendUserName);
|
||||
});
|
||||
break
|
||||
break;
|
||||
case 'addFriend':
|
||||
friendUserName = message.params;
|
||||
print("Adding " + friendUserName + " to friends.");
|
||||
|
@ -307,24 +309,23 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
body: {
|
||||
username: friendUserName,
|
||||
}
|
||||
}, function (error, response) {
|
||||
if (error || (response.status !== 'success')) {
|
||||
print("Error: unable to friend " + friendUserName, error || response.status);
|
||||
return;
|
||||
}
|
||||
getConnectionData(friendUserName);
|
||||
}, function (error, response) {
|
||||
if (error || (response.status !== 'success')) {
|
||||
print("Error: unable to friend " + friendUserName, error || response.status);
|
||||
return;
|
||||
}
|
||||
);
|
||||
getConnectionData(friendUserName);
|
||||
});
|
||||
break;
|
||||
case 'http.request':
|
||||
break; // Handled by request-service.
|
||||
break; // Handled by request-service.
|
||||
default:
|
||||
print('Unrecognized message from Pal.qml:', JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
function sendToQml(message) {
|
||||
tablet.sendToQml(message);
|
||||
ui.sendMessage(message);
|
||||
}
|
||||
function updateUser(data) {
|
||||
print('PAL update:', JSON.stringify(data));
|
||||
|
@ -334,7 +335,6 @@ function updateUser(data) {
|
|||
// User management services
|
||||
//
|
||||
// These are prototype versions that will be changed when the back end changes.
|
||||
var METAVERSE_BASE = Account.metaverseServerURL;
|
||||
|
||||
function requestJSON(url, callback) { // callback(data) if successfull. Logs otherwise.
|
||||
request({
|
||||
|
@ -362,7 +362,7 @@ function getProfilePicture(username, callback) { // callback(url) if successfull
|
|||
});
|
||||
}
|
||||
function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successfull. (Logs otherwise)
|
||||
url = METAVERSE_BASE + '/api/v1/users?per_page=400&'
|
||||
var url = METAVERSE_BASE + '/api/v1/users?per_page=400&';
|
||||
if (domain) {
|
||||
url += 'status=' + domain.slice(1, -1); // without curly braces
|
||||
} else {
|
||||
|
@ -373,7 +373,7 @@ function getAvailableConnections(domain, callback) { // callback([{usename, loca
|
|||
});
|
||||
}
|
||||
function getInfoAboutUser(specificUsername, callback) {
|
||||
url = METAVERSE_BASE + '/api/v1/users?filter=connections'
|
||||
var url = METAVERSE_BASE + '/api/v1/users?filter=connections';
|
||||
requestJSON(url, function (connectionsData) {
|
||||
for (user in connectionsData.users) {
|
||||
if (connectionsData.users[user].username === specificUsername) {
|
||||
|
@ -705,77 +705,41 @@ triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Cont
|
|||
triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand));
|
||||
triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand));
|
||||
|
||||
var ui;
|
||||
// Most apps can have people toggle the tablet closed and open again, and the app should remain "open" even while
|
||||
// the tablet is not shown. However, for the pal, we explicitly close the app and return the tablet to it's
|
||||
// home screen (so that the avatar highlighting goes away).
|
||||
function tabletVisibilityChanged() {
|
||||
if (!tablet.tabletShown && onPalScreen) {
|
||||
ContextOverlay.enabled = true;
|
||||
tablet.gotoHomeScreen();
|
||||
}
|
||||
}
|
||||
|
||||
var wasOnPalScreen = false;
|
||||
var onPalScreen = false;
|
||||
var PAL_QML_SOURCE = "hifi/Pal.qml";
|
||||
function onTabletButtonClicked() {
|
||||
if (!tablet) {
|
||||
print("Warning in onTabletButtonClicked(): 'tablet' undefined!");
|
||||
return;
|
||||
}
|
||||
if (onPalScreen) {
|
||||
// In Toolbar Mode, `gotoHomeScreen` will close the app window.
|
||||
tablet.gotoHomeScreen();
|
||||
} else {
|
||||
tablet.loadQMLSource(PAL_QML_SOURCE);
|
||||
}
|
||||
}
|
||||
var hasEventBridge = false;
|
||||
function wireEventBridge(on) {
|
||||
if (on) {
|
||||
if (!hasEventBridge) {
|
||||
tablet.fromQml.connect(fromQml);
|
||||
hasEventBridge = true;
|
||||
}
|
||||
} else {
|
||||
if (hasEventBridge) {
|
||||
tablet.fromQml.disconnect(fromQml);
|
||||
hasEventBridge = false;
|
||||
}
|
||||
if (!ui.tablet.tabletShown && ui.isOpen) {
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
|
||||
var UPDATE_INTERVAL_MS = 100;
|
||||
var updateInterval;
|
||||
function createUpdateInterval() {
|
||||
return Script.setInterval(function () {
|
||||
updateOverlays();
|
||||
}, UPDATE_INTERVAL_MS);
|
||||
}
|
||||
|
||||
function onTabletScreenChanged(type, url) {
|
||||
wasOnPalScreen = onPalScreen;
|
||||
onPalScreen = (type === "QML" && url === PAL_QML_SOURCE);
|
||||
wireEventBridge(onPalScreen);
|
||||
// for toolbar mode: change button to active when window is first openend, false otherwise.
|
||||
button.editProperties({isActive: onPalScreen});
|
||||
var previousContextOverlay = ContextOverlay.enabled;
|
||||
var previousRequestsDomainListData = Users.requestsDomainListData;
|
||||
function on() {
|
||||
|
||||
if (onPalScreen) {
|
||||
isWired = true;
|
||||
previousContextOverlay = ContextOverlay.enabled;
|
||||
previousRequestsDomainListData = Users.requestsDomainListData
|
||||
ContextOverlay.enabled = false;
|
||||
Users.requestsDomainListData = true;
|
||||
|
||||
ContextOverlay.enabled = false;
|
||||
Users.requestsDomainListData = true;
|
||||
|
||||
tablet.tabletShownChanged.connect(tabletVisibilityChanged);
|
||||
updateInterval = createUpdateInterval();
|
||||
Controller.mousePressEvent.connect(handleMouseEvent);
|
||||
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
|
||||
Users.usernameFromIDReply.connect(usernameFromIDReply);
|
||||
triggerMapping.enable();
|
||||
triggerPressMapping.enable();
|
||||
populateNearbyUserList();
|
||||
} else {
|
||||
off();
|
||||
if (wasOnPalScreen) {
|
||||
ContextOverlay.enabled = true;
|
||||
}
|
||||
}
|
||||
ui.tablet.tabletShownChanged.connect(tabletVisibilityChanged);
|
||||
updateInterval = createUpdateInterval();
|
||||
Controller.mousePressEvent.connect(handleMouseEvent);
|
||||
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
|
||||
Users.usernameFromIDReply.connect(usernameFromIDReply);
|
||||
triggerMapping.enable();
|
||||
triggerPressMapping.enable();
|
||||
populateNearbyUserList();
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -789,8 +753,8 @@ function receiveMessage(channel, messageString, senderID) {
|
|||
var message = JSON.parse(messageString);
|
||||
switch (message.method) {
|
||||
case 'select':
|
||||
if (!onPalScreen) {
|
||||
tablet.loadQMLSource(PAL_QML_SOURCE);
|
||||
if (!ui.isOpen) {
|
||||
ui.open();
|
||||
Script.setTimeout(function () { sendToQml(message); }, 1000);
|
||||
} else {
|
||||
sendToQml(message); // Accepts objects, not just strings.
|
||||
|
@ -826,9 +790,8 @@ function avatarDisconnected(nodeID) {
|
|||
|
||||
function clearLocalQMLDataAndClosePAL() {
|
||||
sendToQml({ method: 'clearLocalQMLData' });
|
||||
if (onPalScreen) {
|
||||
ContextOverlay.enabled = true;
|
||||
tablet.gotoHomeScreen();
|
||||
if (ui.isOpen) {
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -844,20 +807,15 @@ function avatarSessionChanged(avatarID) {
|
|||
sendToQml({ method: 'palIsStale', params: [avatarID, 'avatarSessionChanged'] });
|
||||
}
|
||||
|
||||
|
||||
var button;
|
||||
var buttonName = "PEOPLE";
|
||||
var tablet = null;
|
||||
function startup() {
|
||||
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
button = tablet.addButton({
|
||||
text: buttonName,
|
||||
icon: "icons/tablet-icons/people-i.svg",
|
||||
activeIcon: "icons/tablet-icons/people-a.svg",
|
||||
sortOrder: 7
|
||||
ui = new AppUi({
|
||||
buttonName: "PEOPLE",
|
||||
sortOrder: 7,
|
||||
home: "hifi/Pal.qml",
|
||||
onOpened: on,
|
||||
onClosed: off,
|
||||
onMessage: fromQml
|
||||
});
|
||||
button.clicked.connect(onTabletButtonClicked);
|
||||
tablet.screenChanged.connect(onTabletScreenChanged);
|
||||
Window.domainChanged.connect(clearLocalQMLDataAndClosePAL);
|
||||
Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL);
|
||||
Messages.subscribe(CHANNEL);
|
||||
|
@ -869,35 +827,25 @@ function startup() {
|
|||
}
|
||||
startup();
|
||||
|
||||
|
||||
var isWired = false;
|
||||
function off() {
|
||||
if (isWired) {
|
||||
if (ui.isOpen) { // i.e., only when connected
|
||||
if (updateInterval) {
|
||||
Script.clearInterval(updateInterval);
|
||||
}
|
||||
Controller.mousePressEvent.disconnect(handleMouseEvent);
|
||||
Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
|
||||
tablet.tabletShownChanged.disconnect(tabletVisibilityChanged);
|
||||
ui.tablet.tabletShownChanged.disconnect(tabletVisibilityChanged);
|
||||
Users.usernameFromIDReply.disconnect(usernameFromIDReply);
|
||||
ContextOverlay.enabled = true
|
||||
triggerMapping.disable();
|
||||
triggerPressMapping.disable();
|
||||
Users.requestsDomainListData = false;
|
||||
|
||||
isWired = false;
|
||||
}
|
||||
|
||||
removeOverlays();
|
||||
ContextOverlay.enabled = previousContextOverlay;
|
||||
Users.requestsDomainListData = previousRequestsDomainListData;
|
||||
}
|
||||
|
||||
function shutdown() {
|
||||
if (onPalScreen) {
|
||||
tablet.gotoHomeScreen();
|
||||
}
|
||||
button.clicked.disconnect(onTabletButtonClicked);
|
||||
tablet.removeButton(button);
|
||||
tablet.screenChanged.disconnect(onTabletScreenChanged);
|
||||
Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL);
|
||||
Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL);
|
||||
Messages.subscribe(CHANNEL);
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
//
|
||||
|
||||
/* global Script, HMD, WebTablet, UIWebTablet, UserActivityLogger, Settings, Entities, Messages, Tablet, Overlays,
|
||||
MyAvatar, Menu, AvatarInputs, Vec3 */
|
||||
MyAvatar, Menu, AvatarInputs, Vec3, cleanUpOldMaterialEntities */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
var tabletRezzed = false;
|
||||
|
@ -31,6 +31,14 @@
|
|||
|
||||
Script.include("../libraries/WebTablet.js");
|
||||
|
||||
function cleanupMaterialEntities() {
|
||||
if (Window.isPhysicsEnabled()) {
|
||||
cleanUpOldMaterialEntities();
|
||||
return;
|
||||
}
|
||||
Script.setTimeout(cleanupMaterialEntities, 100);
|
||||
}
|
||||
|
||||
function checkTablet() {
|
||||
if (gTablet === null) {
|
||||
gTablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
|
@ -327,4 +335,5 @@
|
|||
HMD.homeButtonHighlightMaterialID = null;
|
||||
HMD.homeButtonUnhighlightMaterialID = null;
|
||||
});
|
||||
Script.setTimeout(cleanupMaterialEntities, 100);
|
||||
}()); // END LOCAL_SCOPE
|
||||
|
|
|
@ -405,7 +405,7 @@ LogWindow.prototype = {
|
|||
}
|
||||
};
|
||||
|
||||
function goHomeClicked() {
|
||||
function visitSandboxClicked() {
|
||||
if (interfacePath) {
|
||||
startInterface('hifi://localhost');
|
||||
} else {
|
||||
|
@ -439,8 +439,8 @@ var labels = {
|
|||
}
|
||||
},
|
||||
goHome: {
|
||||
label: 'Go Home',
|
||||
click: goHomeClicked,
|
||||
label: 'Visit Sandbox',
|
||||
click: visitSandboxClicked,
|
||||
enabled: false
|
||||
},
|
||||
quit: {
|
||||
|
|
|
@ -5,7 +5,7 @@ project(${TARGET_NAME})
|
|||
SET (CMAKE_AUTOUIC ON)
|
||||
SET (CMAKE_AUTOMOC ON)
|
||||
|
||||
setup_hifi_project (Core Widgets Network)
|
||||
setup_hifi_project (Core Widgets Network Xml)
|
||||
link_hifi_libraries ()
|
||||
|
||||
# FIX: Qt was built with -reduce-relocations
|
||||
|
@ -18,7 +18,7 @@ include_directories (${CMAKE_CURRENT_SOURCE_DIR})
|
|||
include_directories (${Qt5Core_INCLUDE_DIRS})
|
||||
include_directories (${Qt5Widgets_INCLUDE_DIRS})
|
||||
|
||||
set (QT_LIBRARIES Qt5::Core Qt5::Widgets)
|
||||
set (QT_LIBRARIES Qt5::Core Qt5::Widgets QT::Gui Qt5::Xml)
|
||||
|
||||
if (WIN32)
|
||||
# Do not show Console
|
||||
|
|
|
@ -24,7 +24,7 @@ extern AutoTester* autoTester;
|
|||
#include <math.h>
|
||||
|
||||
Test::Test() {
|
||||
mismatchWindow.setModal(true);
|
||||
_mismatchWindow.setModal(true);
|
||||
|
||||
if (autoTester) {
|
||||
autoTester->setUserText("highfidelity");
|
||||
|
@ -34,35 +34,35 @@ Test::Test() {
|
|||
|
||||
bool Test::createTestResultsFolderPath(const QString& directory) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER + "--" + now.toString(DATETIME_FORMAT);
|
||||
QDir testResultsFolder(testResultsFolderPath);
|
||||
_testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER + "--" + now.toString(DATETIME_FORMAT);
|
||||
QDir testResultsFolder(_testResultsFolderPath);
|
||||
|
||||
// Create a new test results folder
|
||||
return QDir().mkdir(testResultsFolderPath);
|
||||
return QDir().mkdir(_testResultsFolderPath);
|
||||
}
|
||||
|
||||
void Test::zipAndDeleteTestResultsFolder() {
|
||||
QString zippedResultsFileName { testResultsFolderPath + ".zip" };
|
||||
QString zippedResultsFileName { _testResultsFolderPath + ".zip" };
|
||||
QFileInfo fileInfo(zippedResultsFileName);
|
||||
if (!fileInfo.exists()) {
|
||||
QFile::remove(zippedResultsFileName);
|
||||
}
|
||||
|
||||
QDir testResultsFolder(testResultsFolderPath);
|
||||
QDir testResultsFolder(_testResultsFolderPath);
|
||||
if (!testResultsFolder.isEmpty()) {
|
||||
JlCompress::compressDir(testResultsFolderPath + ".zip", testResultsFolderPath);
|
||||
JlCompress::compressDir(_testResultsFolderPath + ".zip", _testResultsFolderPath);
|
||||
}
|
||||
|
||||
testResultsFolder.removeRecursively();
|
||||
|
||||
//In all cases, for the next evaluation
|
||||
testResultsFolderPath = "";
|
||||
index = 1;
|
||||
_testResultsFolderPath = "";
|
||||
_index = 1;
|
||||
}
|
||||
|
||||
bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) {
|
||||
progressBar->setMinimum(0);
|
||||
progressBar->setMaximum(expectedImagesFullFilenames.length() - 1);
|
||||
progressBar->setMaximum(_expectedImagesFullFilenames.length() - 1);
|
||||
progressBar->setValue(0);
|
||||
progressBar->setVisible(true);
|
||||
|
||||
|
@ -70,10 +70,10 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar)
|
|||
// Quit loop if user has aborted due to a failed test.
|
||||
bool success{ true };
|
||||
bool keepOn{ true };
|
||||
for (int i = 0; keepOn && i < expectedImagesFullFilenames.length(); ++i) {
|
||||
for (int i = 0; keepOn && i < _expectedImagesFullFilenames.length(); ++i) {
|
||||
// First check that images are the same size
|
||||
QImage resultImage(resultImagesFullFilenames[i]);
|
||||
QImage expectedImage(expectedImagesFullFilenames[i]);
|
||||
QImage resultImage(_resultImagesFullFilenames[i]);
|
||||
QImage expectedImage(_expectedImagesFullFilenames[i]);
|
||||
|
||||
double similarityIndex; // in [-1.0 .. 1.0], where 1.0 means images are identical
|
||||
|
||||
|
@ -82,30 +82,30 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar)
|
|||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Images are not the same size");
|
||||
similarityIndex = -100.0;
|
||||
} else {
|
||||
similarityIndex = imageComparer.compareImages(resultImage, expectedImage);
|
||||
similarityIndex = _imageComparer.compareImages(resultImage, expectedImage);
|
||||
}
|
||||
|
||||
if (similarityIndex < THRESHOLD) {
|
||||
TestFailure testFailure = TestFailure{
|
||||
(float)similarityIndex,
|
||||
expectedImagesFullFilenames[i].left(expectedImagesFullFilenames[i].lastIndexOf("/") + 1), // path to the test (including trailing /)
|
||||
QFileInfo(expectedImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of expected image
|
||||
QFileInfo(resultImagesFullFilenames[i].toStdString().c_str()).fileName() // filename of result image
|
||||
_expectedImagesFullFilenames[i].left(_expectedImagesFullFilenames[i].lastIndexOf("/") + 1), // path to the test (including trailing /)
|
||||
QFileInfo(_expectedImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of expected image
|
||||
QFileInfo(_resultImagesFullFilenames[i].toStdString().c_str()).fileName() // filename of result image
|
||||
};
|
||||
|
||||
mismatchWindow.setTestFailure(testFailure);
|
||||
_mismatchWindow.setTestFailure(testFailure);
|
||||
|
||||
if (!isInteractiveMode) {
|
||||
appendTestResultsToFile(testResultsFolderPath, testFailure, mismatchWindow.getComparisonImage());
|
||||
appendTestResultsToFile(_testResultsFolderPath, testFailure, _mismatchWindow.getComparisonImage());
|
||||
success = false;
|
||||
} else {
|
||||
mismatchWindow.exec();
|
||||
_mismatchWindow.exec();
|
||||
|
||||
switch (mismatchWindow.getUserResponse()) {
|
||||
switch (_mismatchWindow.getUserResponse()) {
|
||||
case USER_RESPONSE_PASS:
|
||||
break;
|
||||
case USE_RESPONSE_FAIL:
|
||||
appendTestResultsToFile(testResultsFolderPath, testFailure, mismatchWindow.getComparisonImage());
|
||||
appendTestResultsToFile(_testResultsFolderPath, testFailure, _mismatchWindow.getComparisonImage());
|
||||
success = false;
|
||||
break;
|
||||
case USER_RESPONSE_ABORT:
|
||||
|
@ -126,20 +126,20 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar)
|
|||
return success;
|
||||
}
|
||||
|
||||
void Test::appendTestResultsToFile(const QString& testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage) {
|
||||
if (!QDir().exists(testResultsFolderPath)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Folder " + testResultsFolderPath + " not found");
|
||||
void Test::appendTestResultsToFile(const QString& _testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage) {
|
||||
if (!QDir().exists(_testResultsFolderPath)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Folder " + _testResultsFolderPath + " not found");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
QString err = QString::number(testFailure._error).left(6);
|
||||
|
||||
QString failureFolderPath { testResultsFolderPath + "/" + err + "-Failure_" + QString::number(index) + "--" + testFailure._actualImageFilename.left(testFailure._actualImageFilename.length() - 4) };
|
||||
QString failureFolderPath { _testResultsFolderPath + "/" + err + "-Failure_" + QString::number(_index) + "--" + testFailure._actualImageFilename.left(testFailure._actualImageFilename.length() - 4) };
|
||||
if (!QDir().mkdir(failureFolderPath)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create folder " + failureFolderPath);
|
||||
exit(-1);
|
||||
}
|
||||
++index;
|
||||
++_index;
|
||||
|
||||
QFile descriptionFile(failureFolderPath + "/" + TEST_RESULTS_FILENAME);
|
||||
if (!descriptionFile.open(QIODevice::ReadWrite)) {
|
||||
|
@ -152,7 +152,7 @@ void Test::appendTestResultsToFile(const QString& testResultsFolderPath, TestFai
|
|||
stream << "Test failed in folder " << testFailure._pathname.left(testFailure._pathname.length() - 1) << endl; // remove trailing '/'
|
||||
stream << "Expected image was " << testFailure._expectedImageFilename << endl;
|
||||
stream << "Actual image was " << testFailure._actualImageFilename << endl;
|
||||
stream << "Similarity index was " << testFailure._error << endl;
|
||||
stream << "Similarity _index was " << testFailure._error << endl;
|
||||
|
||||
descriptionFile.close();
|
||||
|
||||
|
@ -180,26 +180,26 @@ void Test::appendTestResultsToFile(const QString& testResultsFolderPath, TestFai
|
|||
void Test::startTestsEvaluation(const QString& testFolder, const QString& branchFromCommandLine, const QString& userFromCommandLine) {
|
||||
if (testFolder.isNull()) {
|
||||
// Get list of JPEG images in folder, sorted by name
|
||||
QString previousSelection = snapshotDirectory;
|
||||
QString previousSelection = _snapshotDirectory;
|
||||
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
|
||||
if (!parent.isNull() && parent.right(1) != "/") {
|
||||
parent += "/";
|
||||
}
|
||||
snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", parent,
|
||||
_snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", parent,
|
||||
QFileDialog::ShowDirsOnly);
|
||||
|
||||
// If user cancelled then restore previous selection and return
|
||||
if (snapshotDirectory == "") {
|
||||
snapshotDirectory = previousSelection;
|
||||
if (_snapshotDirectory == "") {
|
||||
_snapshotDirectory = previousSelection;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
snapshotDirectory = testFolder;
|
||||
exitWhenComplete = true;
|
||||
_snapshotDirectory = testFolder;
|
||||
_exitWhenComplete = true;
|
||||
}
|
||||
|
||||
// Quit if test results folder could not be created
|
||||
if (!createTestResultsFolderPath(snapshotDirectory)) {
|
||||
if (!createTestResultsFolderPath(_snapshotDirectory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -207,20 +207,20 @@ void Test::startTestsEvaluation(const QString& testFolder, const QString& branch
|
|||
// The expected images are represented as a URL to enable download from GitHub
|
||||
// Images that are in the wrong format are ignored.
|
||||
|
||||
QStringList sortedTestResultsFilenames = createListOfAll_imagesInDirectory("png", snapshotDirectory);
|
||||
QStringList sortedTestResultsFilenames = createListOfAll_imagesInDirectory("png", _snapshotDirectory);
|
||||
QStringList expectedImagesURLs;
|
||||
|
||||
resultImagesFullFilenames.clear();
|
||||
expectedImagesFilenames.clear();
|
||||
expectedImagesFullFilenames.clear();
|
||||
_resultImagesFullFilenames.clear();
|
||||
_expectedImagesFilenames.clear();
|
||||
_expectedImagesFullFilenames.clear();
|
||||
|
||||
QString branch = (branchFromCommandLine.isNull()) ? autoTester->getSelectedBranch() : branchFromCommandLine;
|
||||
QString user = (userFromCommandLine.isNull()) ? autoTester->getSelectedUser() : userFromCommandLine;
|
||||
|
||||
foreach(QString currentFilename, sortedTestResultsFilenames) {
|
||||
QString fullCurrentFilename = snapshotDirectory + "/" + currentFilename;
|
||||
QString fullCurrentFilename = _snapshotDirectory + "/" + currentFilename;
|
||||
if (isInSnapshotFilenameFormat("png", currentFilename)) {
|
||||
resultImagesFullFilenames << fullCurrentFilename;
|
||||
_resultImagesFullFilenames << fullCurrentFilename;
|
||||
|
||||
QString expectedImagePartialSourceDirectory = getExpectedImagePartialSourceDirectory(currentFilename);
|
||||
|
||||
|
@ -237,12 +237,12 @@ void Test::startTestsEvaluation(const QString& testFolder, const QString& branch
|
|||
// The image retrieved from GitHub needs a unique name
|
||||
QString expectedImageFilename = currentFilename.replace("/", "_").replace(".png", "_EI.png");
|
||||
|
||||
expectedImagesFilenames << expectedImageFilename;
|
||||
expectedImagesFullFilenames << snapshotDirectory + "/" + expectedImageFilename;
|
||||
_expectedImagesFilenames << expectedImageFilename;
|
||||
_expectedImagesFullFilenames << _snapshotDirectory + "/" + expectedImageFilename;
|
||||
}
|
||||
}
|
||||
|
||||
autoTester->downloadImages(expectedImagesURLs, snapshotDirectory, expectedImagesFilenames);
|
||||
autoTester->downloadImages(expectedImagesURLs, _snapshotDirectory, _expectedImagesFilenames);
|
||||
}
|
||||
|
||||
void Test::finishTestsEvaluation(bool isRunningFromCommandline, bool interactiveMode, QProgressBar* progressBar) {
|
||||
|
@ -258,7 +258,7 @@ void Test::finishTestsEvaluation(bool isRunningFromCommandline, bool interactive
|
|||
|
||||
zipAndDeleteTestResultsFolder();
|
||||
|
||||
if (exitWhenComplete) {
|
||||
if (_exitWhenComplete) {
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
@ -310,46 +310,46 @@ void Test::includeTest(QTextStream& textStream, const QString& testPathname) {
|
|||
// This script will run all text.js scripts in every applicable sub-folder
|
||||
void Test::createRecursiveScript() {
|
||||
// Select folder to start recursing from
|
||||
QString previousSelection = testDirectory;
|
||||
QString previousSelection = _testDirectory;
|
||||
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
|
||||
if (!parent.isNull() && parent.right(1) != "/") {
|
||||
parent += "/";
|
||||
}
|
||||
|
||||
testDirectory =
|
||||
_testDirectory =
|
||||
QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", parent,
|
||||
QFileDialog::ShowDirsOnly);
|
||||
|
||||
// If user cancelled then restore previous selection and return
|
||||
if (testDirectory == "") {
|
||||
testDirectory = previousSelection;
|
||||
if (_testDirectory == "") {
|
||||
_testDirectory = previousSelection;
|
||||
return;
|
||||
}
|
||||
|
||||
createRecursiveScript(testDirectory, true);
|
||||
createRecursiveScript(_testDirectory, true);
|
||||
}
|
||||
|
||||
// This method creates a `testRecursive.js` script in every sub-folder.
|
||||
void Test::createAllRecursiveScripts() {
|
||||
// Select folder to start recursing from
|
||||
QString previousSelection = testsRootDirectory;
|
||||
QString previousSelection = _testsRootDirectory;
|
||||
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
|
||||
if (!parent.isNull() && parent.right(1) != "/") {
|
||||
parent += "/";
|
||||
}
|
||||
|
||||
testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the recursive scripts",
|
||||
_testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the recursive scripts",
|
||||
parent, QFileDialog::ShowDirsOnly);
|
||||
|
||||
// If user cancelled then restore previous selection and return
|
||||
if (testsRootDirectory == "") {
|
||||
testsRootDirectory = previousSelection;
|
||||
if (_testsRootDirectory == "") {
|
||||
_testsRootDirectory = previousSelection;
|
||||
return;
|
||||
}
|
||||
|
||||
createRecursiveScript(testsRootDirectory, false);
|
||||
createRecursiveScript(_testsRootDirectory, false);
|
||||
|
||||
QDirIterator it(testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
|
||||
QDirIterator it(_testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
|
||||
while (it.hasNext()) {
|
||||
QString directory = it.next();
|
||||
|
||||
|
@ -477,42 +477,42 @@ void Test::createRecursiveScript(const QString& topLevelDirectory, bool interact
|
|||
void Test::createTests() {
|
||||
// Rename files sequentially, as ExpectedResult_00000.jpeg, ExpectedResult_00001.jpg and so on
|
||||
// Any existing expected result images will be deleted
|
||||
QString previousSelection = snapshotDirectory;
|
||||
QString previousSelection = _snapshotDirectory;
|
||||
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
|
||||
if (!parent.isNull() && parent.right(1) != "/") {
|
||||
parent += "/";
|
||||
}
|
||||
|
||||
snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", parent,
|
||||
_snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", parent,
|
||||
QFileDialog::ShowDirsOnly);
|
||||
|
||||
// If user cancelled then restore previous selection and return
|
||||
if (snapshotDirectory == "") {
|
||||
snapshotDirectory = previousSelection;
|
||||
if (_snapshotDirectory == "") {
|
||||
_snapshotDirectory = previousSelection;
|
||||
return;
|
||||
}
|
||||
|
||||
previousSelection = testsRootDirectory;
|
||||
previousSelection = _testsRootDirectory;
|
||||
parent = previousSelection.left(previousSelection.lastIndexOf('/'));
|
||||
if (!parent.isNull() && parent.right(1) != "/") {
|
||||
parent += "/";
|
||||
}
|
||||
|
||||
testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select test root folder", parent,
|
||||
_testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select test root folder", parent,
|
||||
QFileDialog::ShowDirsOnly);
|
||||
|
||||
// If user cancelled then restore previous selection and return
|
||||
if (testsRootDirectory == "") {
|
||||
testsRootDirectory = previousSelection;
|
||||
if (_testsRootDirectory == "") {
|
||||
_testsRootDirectory = previousSelection;
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList sortedImageFilenames = createListOfAll_imagesInDirectory("png", snapshotDirectory);
|
||||
QStringList sortedImageFilenames = createListOfAll_imagesInDirectory("png", _snapshotDirectory);
|
||||
|
||||
int i = 1;
|
||||
const int maxImages = pow(10, NUM_DIGITS);
|
||||
foreach (QString currentFilename, sortedImageFilenames) {
|
||||
QString fullCurrentFilename = snapshotDirectory + "/" + currentFilename;
|
||||
QString fullCurrentFilename = _snapshotDirectory + "/" + currentFilename;
|
||||
if (isInSnapshotFilenameFormat("png", currentFilename)) {
|
||||
if (i >= maxImages) {
|
||||
QMessageBox::critical(0, "Error", "More than " + QString::number(maxImages) + " images not supported");
|
||||
|
@ -522,17 +522,17 @@ void Test::createTests() {
|
|||
// Path to test is extracted from the file name
|
||||
// Example:
|
||||
// filename is tests.engine.interaction.pointer.laser.distanceScaleEnd.00000.jpg
|
||||
// path is <testDirectory>/engine/interaction/pointer/laser/distanceScaleEnd
|
||||
// path is <_testDirectory>/engine/interaction/pointer/laser/distanceScaleEnd
|
||||
//
|
||||
// Note: we don't use the first part and the last 2 parts of the filename at this stage
|
||||
//
|
||||
QStringList pathParts = currentFilename.split(".");
|
||||
QString fullNewFileName = testsRootDirectory;
|
||||
QString fullNewFileName = _testsRootDirectory;
|
||||
for (int j = 1; j < pathParts.size() - 2; ++j) {
|
||||
fullNewFileName += "/" + pathParts[j];
|
||||
}
|
||||
|
||||
// The image index is the penultimate component of the path parts (the last being the file extension)
|
||||
// The image _index is the penultimate component of the path parts (the last being the file extension)
|
||||
QString newFilename = "ExpectedImage_" + pathParts[pathParts.size() - 2].rightJustified(5, '0') + ".png";
|
||||
fullNewFileName += "/" + newFilename;
|
||||
|
||||
|
@ -621,51 +621,51 @@ ExtractedText Test::getTestScriptLines(QString testFileName) {
|
|||
// The folder selected must contain a script named "test.js", the file produced is named "test.md"
|
||||
void Test::createMDFile() {
|
||||
// Folder selection
|
||||
QString previousSelection = testDirectory;
|
||||
QString previousSelection = _testDirectory;
|
||||
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
|
||||
if (!parent.isNull() && parent.right(1) != "/") {
|
||||
parent += "/";
|
||||
}
|
||||
|
||||
testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test", parent,
|
||||
_testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test", parent,
|
||||
QFileDialog::ShowDirsOnly);
|
||||
|
||||
// If user cancelled then restore previous selection and return
|
||||
if (testDirectory == "") {
|
||||
testDirectory = previousSelection;
|
||||
if (_testDirectory == "") {
|
||||
_testDirectory = previousSelection;
|
||||
return;
|
||||
}
|
||||
|
||||
createMDFile(testDirectory);
|
||||
createMDFile(_testDirectory);
|
||||
|
||||
QMessageBox::information(0, "Success", "MD file has been created");
|
||||
}
|
||||
|
||||
void Test::createAllMDFiles() {
|
||||
// Select folder to start recursing from
|
||||
QString previousSelection = testsRootDirectory;
|
||||
QString previousSelection = _testsRootDirectory;
|
||||
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
|
||||
if (!parent.isNull() && parent.right(1) != "/") {
|
||||
parent += "/";
|
||||
}
|
||||
|
||||
testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the MD files", parent,
|
||||
_testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the MD files", parent,
|
||||
QFileDialog::ShowDirsOnly);
|
||||
|
||||
// If user cancelled then restore previous selection and return
|
||||
if (testsRootDirectory == "") {
|
||||
testsRootDirectory = previousSelection;
|
||||
if (_testsRootDirectory == "") {
|
||||
_testsRootDirectory = previousSelection;
|
||||
return;
|
||||
}
|
||||
|
||||
// First test if top-level folder has a test.js file
|
||||
const QString testPathname{ testsRootDirectory + "/" + TEST_FILENAME };
|
||||
const QString testPathname{ _testsRootDirectory + "/" + TEST_FILENAME };
|
||||
QFileInfo fileInfo(testPathname);
|
||||
if (fileInfo.exists()) {
|
||||
createMDFile(testsRootDirectory);
|
||||
createMDFile(_testsRootDirectory);
|
||||
}
|
||||
|
||||
QDirIterator it(testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
|
||||
QDirIterator it(_testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
|
||||
while (it.hasNext()) {
|
||||
QString directory = it.next();
|
||||
|
||||
|
@ -685,9 +685,9 @@ void Test::createAllMDFiles() {
|
|||
QMessageBox::information(0, "Success", "MD files have been created");
|
||||
}
|
||||
|
||||
void Test::createMDFile(const QString& testDirectory) {
|
||||
void Test::createMDFile(const QString& _testDirectory) {
|
||||
// Verify folder contains test.js file
|
||||
QString testFileName(testDirectory + "/" + TEST_FILENAME);
|
||||
QString testFileName(_testDirectory + "/" + TEST_FILENAME);
|
||||
QFileInfo testFileInfo(testFileName);
|
||||
if (!testFileInfo.exists()) {
|
||||
QMessageBox::critical(0, "Error", "Could not find file: " + TEST_FILENAME);
|
||||
|
@ -696,7 +696,7 @@ void Test::createMDFile(const QString& testDirectory) {
|
|||
|
||||
ExtractedText testScriptLines = getTestScriptLines(testFileName);
|
||||
|
||||
QString mdFilename(testDirectory + "/" + "test.md");
|
||||
QString mdFilename(_testDirectory + "/" + "test.md");
|
||||
QFile mdFile(mdFilename);
|
||||
if (!mdFile.open(QIODevice::WriteOnly)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename);
|
||||
|
@ -710,7 +710,7 @@ void Test::createMDFile(const QString& testDirectory) {
|
|||
stream << "# " << testName << "\n";
|
||||
|
||||
// Find the relevant part of the path to the test (i.e. from "tests" down
|
||||
QString partialPath = extractPathFromTestsDown(testDirectory);
|
||||
QString partialPath = extractPathFromTestsDown(_testDirectory);
|
||||
|
||||
stream << "## Run this script URL: [Manual](./test.js?raw=true) [Auto](./testAuto.js?raw=true)(from menu/Edit/Open and Run scripts from URL...)." << "\n\n";
|
||||
|
||||
|
@ -734,23 +734,23 @@ void Test::createMDFile(const QString& testDirectory) {
|
|||
}
|
||||
|
||||
void Test::createTestsOutline() {
|
||||
QString previousSelection = testDirectory;
|
||||
QString previousSelection = _testDirectory;
|
||||
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
|
||||
if (!parent.isNull() && parent.right(1) != "/") {
|
||||
parent += "/";
|
||||
}
|
||||
|
||||
testDirectory =
|
||||
_testDirectory =
|
||||
QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", parent, QFileDialog::ShowDirsOnly);
|
||||
|
||||
// If user cancelled then restore previous selection and return
|
||||
if (testDirectory == "") {
|
||||
testDirectory = previousSelection;
|
||||
if (_testDirectory == "") {
|
||||
_testDirectory = previousSelection;
|
||||
return;
|
||||
}
|
||||
|
||||
const QString testsOutlineFilename { "testsOutline.md" };
|
||||
QString mdFilename(testDirectory + "/" + testsOutlineFilename);
|
||||
QString mdFilename(_testDirectory + "/" + testsOutlineFilename);
|
||||
QFile mdFile(mdFilename);
|
||||
if (!mdFile.open(QIODevice::WriteOnly)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename);
|
||||
|
@ -764,10 +764,10 @@ void Test::createTestsOutline() {
|
|||
stream << "Directories with an appended (*) have an automatic test\n\n";
|
||||
|
||||
// We need to know our current depth, as this isn't given by QDirIterator
|
||||
int rootDepth { testDirectory.count('/') };
|
||||
int rootDepth { _testDirectory.count('/') };
|
||||
|
||||
// Each test is shown as the folder name linking to the matching GitHub URL, and the path to the associated test.md file
|
||||
QDirIterator it(testDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
|
||||
QDirIterator it(_testDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
|
||||
while (it.hasNext()) {
|
||||
QString directory = it.next();
|
||||
|
||||
|
@ -821,12 +821,45 @@ void Test::createTestsOutline() {
|
|||
QMessageBox::information(0, "Success", "Test outline file " + testsOutlineFilename + " has been created");
|
||||
}
|
||||
|
||||
void Test::createTestRailTestSuite() {
|
||||
QString previousSelection = _testDirectory;
|
||||
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
|
||||
if (!parent.isNull() && parent.right(1) != "/") {
|
||||
parent += "/";
|
||||
}
|
||||
|
||||
_testDirectory =
|
||||
QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", parent, QFileDialog::ShowDirsOnly);
|
||||
|
||||
// If user cancelled then restore previous selection and return
|
||||
if (_testDirectory == "") {
|
||||
_testDirectory = previousSelection;
|
||||
return;
|
||||
}
|
||||
|
||||
QString outputDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select a folder to store generated files in",
|
||||
parent, QFileDialog::ShowDirsOnly);
|
||||
|
||||
// If user cancelled then return
|
||||
if (outputDirectory == "") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_testRailCreateMode == PYTHON) {
|
||||
_testRailInterface.createTestSuitePython(_testDirectory, outputDirectory, autoTester->getSelectedUser(),
|
||||
autoTester->getSelectedBranch());
|
||||
} else {
|
||||
_testRailInterface.createTestSuiteXML(_testDirectory, outputDirectory, autoTester->getSelectedUser(),
|
||||
autoTester->getSelectedBranch());
|
||||
}
|
||||
}
|
||||
|
||||
QStringList Test::createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory) {
|
||||
imageDirectory = QDir(pathToImageDirectory);
|
||||
_imageDirectory = QDir(pathToImageDirectory);
|
||||
QStringList nameFilters;
|
||||
nameFilters << "*." + imageFormat;
|
||||
|
||||
return imageDirectory.entryList(nameFilters, QDir::Files, QDir::Name);
|
||||
return _imageDirectory.entryList(nameFilters, QDir::Files, QDir::Name);
|
||||
}
|
||||
|
||||
// Snapshots are files in the following format:
|
||||
|
@ -889,3 +922,7 @@ QString Test::getExpectedImagePartialSourceDirectory(const QString& filename) {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Test::setTestRailCreateMode(TestRailCreateMode testRailCreateMode) {
|
||||
_testRailCreateMode = testRailCreateMode;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include "ImageComparer.h"
|
||||
#include "ui/MismatchWindow.h"
|
||||
#include "TestRailInterface.h"
|
||||
|
||||
class Step {
|
||||
public:
|
||||
|
@ -33,6 +34,11 @@ public:
|
|||
StepList stepList;
|
||||
};
|
||||
|
||||
enum TestRailCreateMode {
|
||||
PYTHON,
|
||||
XML
|
||||
};
|
||||
|
||||
class Test {
|
||||
public:
|
||||
Test();
|
||||
|
@ -51,6 +57,8 @@ public:
|
|||
|
||||
void createTestsOutline();
|
||||
|
||||
void createTestRailTestSuite();
|
||||
|
||||
bool compareImageLists(bool isInteractiveMode, QProgressBar* progressBar);
|
||||
|
||||
QStringList createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory);
|
||||
|
@ -64,11 +72,15 @@ public:
|
|||
bool createTestResultsFolderPath(const QString& directory);
|
||||
void zipAndDeleteTestResultsFolder();
|
||||
|
||||
bool isAValidDirectory(const QString& pathname);
|
||||
static bool isAValidDirectory(const QString& pathname);
|
||||
QString extractPathFromTestsDown(const QString& fullPath);
|
||||
QString getExpectedImageDestinationDirectory(const QString& filename);
|
||||
QString getExpectedImagePartialSourceDirectory(const QString& filename);
|
||||
|
||||
ExtractedText getTestScriptLines(QString testFileName);
|
||||
|
||||
void setTestRailCreateMode(TestRailCreateMode testRailCreateMode);
|
||||
|
||||
private:
|
||||
const QString TEST_FILENAME { "test.js" };
|
||||
const QString TEST_RESULTS_FOLDER { "TestResults" };
|
||||
|
@ -76,14 +88,14 @@ private:
|
|||
|
||||
const double THRESHOLD{ 0.96 };
|
||||
|
||||
QDir imageDirectory;
|
||||
QDir _imageDirectory;
|
||||
|
||||
MismatchWindow mismatchWindow;
|
||||
MismatchWindow _mismatchWindow;
|
||||
|
||||
ImageComparer imageComparer;
|
||||
ImageComparer _imageComparer;
|
||||
|
||||
QString testResultsFolderPath;
|
||||
int index { 1 };
|
||||
QString _testResultsFolderPath;
|
||||
int _index { 1 };
|
||||
|
||||
// Expected images are in the format ExpectedImage_dddd.jpg (d == decimal digit)
|
||||
const int NUM_DIGITS { 5 };
|
||||
|
@ -93,28 +105,30 @@ private:
|
|||
// The first is the directory containing the test we are working with
|
||||
// The second is the root directory of all tests
|
||||
// The third contains the snapshots taken for test runs that need to be evaluated
|
||||
QString testDirectory;
|
||||
QString testsRootDirectory;
|
||||
QString snapshotDirectory;
|
||||
QString _testDirectory;
|
||||
QString _testsRootDirectory;
|
||||
QString _snapshotDirectory;
|
||||
|
||||
QStringList expectedImagesFilenames;
|
||||
QStringList expectedImagesFullFilenames;
|
||||
QStringList resultImagesFullFilenames;
|
||||
QStringList _expectedImagesFilenames;
|
||||
QStringList _expectedImagesFullFilenames;
|
||||
QStringList _resultImagesFullFilenames;
|
||||
|
||||
// Used for accessing GitHub
|
||||
const QString GIT_HUB_REPOSITORY{ "hifi_tests" };
|
||||
|
||||
const QString DATETIME_FORMAT{ "yyyy-MM-dd_hh-mm-ss" };
|
||||
|
||||
ExtractedText getTestScriptLines(QString testFileName);
|
||||
|
||||
// NOTE: these need to match the appropriate var's in autoTester.js
|
||||
// var advanceKey = "n";
|
||||
// var pathSeparator = ".";
|
||||
const QString ADVANCE_KEY{ "n" };
|
||||
const QString PATH_SEPARATOR{ "." };
|
||||
|
||||
bool exitWhenComplete{ false };
|
||||
bool _exitWhenComplete{ false };
|
||||
|
||||
TestRailInterface _testRailInterface;
|
||||
|
||||
TestRailCreateMode _testRailCreateMode { PYTHON };
|
||||
};
|
||||
|
||||
#endif // hifi_test_h
|
376
tools/auto-tester/src/TestRailInterface.cpp
Normal file
376
tools/auto-tester/src/TestRailInterface.cpp
Normal file
|
@ -0,0 +1,376 @@
|
|||
//
|
||||
// TestRailInterface.cpp
|
||||
//
|
||||
// Created by Nissim Hadar on 6 Jul 2018.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "TestRailInterface.h"
|
||||
#include "Test.h"
|
||||
|
||||
#include "ui/TestRailSelectorWindow.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
#include <QMessageBox>
|
||||
#include <QTextStream>
|
||||
|
||||
TestRailInterface::TestRailInterface() {
|
||||
_testRailSelectorWindow.setModal(true);
|
||||
|
||||
_testRailSelectorWindow.setURL("https://highfidelity.testrail.net/");
|
||||
_testRailSelectorWindow.setUser("@highfidelity.io");
|
||||
|
||||
// 24 is the HighFidelity Interface project id in TestRail
|
||||
_testRailSelectorWindow.setProject(24);
|
||||
}
|
||||
|
||||
void TestRailInterface::createTestRailDotPyScript(const QString& outputDirectory) {
|
||||
// Create the testrail.py script
|
||||
// This is the file linked to from http://docs.gurock.com/testrail-api2/bindings-python
|
||||
QFile file(outputDirectory + "/testrail.py");
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||
"Could not create \'testrail.py\'");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
QTextStream stream(&file);
|
||||
|
||||
stream << "#\n";
|
||||
stream << "# TestRail API binding for Python 3.x (API v2, available since \n";
|
||||
stream << "# TestRail 3.0)\n";
|
||||
stream << "#\n";
|
||||
stream << "# Learn more:\n";
|
||||
stream << "#\n";
|
||||
stream << "# http://docs.gurock.com/testrail-api2/start\n";
|
||||
stream << "# http://docs.gurock.com/testrail-api2/accessing\n";
|
||||
stream << "#\n";
|
||||
stream << "# Copyright Gurock Software GmbH. See license.md for details.\n";
|
||||
stream << "#\n";
|
||||
stream << "\n";
|
||||
stream << "import urllib.request, urllib.error\n";
|
||||
stream << "import json, base64\n";
|
||||
stream << "\n";
|
||||
stream << "class APIClient:\n";
|
||||
stream << "\tdef __init__(self, base_url):\n";
|
||||
stream << "\t\tself.user = ''\n";
|
||||
stream << "\t\tself.password = ''\n";
|
||||
stream << "\t\tif not base_url.endswith('/'):\n";
|
||||
stream << "\t\t\tbase_url += '/'\n";
|
||||
stream << "\t\tself.__url = base_url + 'index.php?/api/v2/'\n";
|
||||
stream << "\n";
|
||||
stream << "\t#\n";
|
||||
stream << "\t# Send Get\n";
|
||||
stream << "\t#\n";
|
||||
stream << "\t# Issues a GET request (read) against the API and returns the result\n";
|
||||
stream << "\t# (as Python dict).\n";
|
||||
stream << "\t#\n";
|
||||
stream << "\t# Arguments:\n";
|
||||
stream << "\t#\n";
|
||||
stream << "\t# uri The API method to call including parameters\n";
|
||||
stream << "\t# (e.g. get_case/1)\n";
|
||||
stream << "\t#\n";
|
||||
stream << "\tdef send_get(self, uri):\n";
|
||||
stream << "\t\treturn self.__send_request('GET', uri, None)\n";
|
||||
stream << "\n";
|
||||
stream << "\t#\n";
|
||||
stream << "\t# Send POST\n";
|
||||
stream << "\t#\n";
|
||||
stream << "\t# Issues a POST request (write) against the API and returns the result\n";
|
||||
stream << "\t# (as Python dict).\n";
|
||||
stream << "\t#\n";
|
||||
stream << "\t# Arguments:\n";
|
||||
stream << "\t#\n";
|
||||
stream << "\t# uri The API method to call including parameters\n";
|
||||
stream << "\t# (e.g. add_case/1)\n";
|
||||
stream << "\t# data The data to submit as part of the request (as\n";
|
||||
stream << "\t# Python dict, strings must be UTF-8 encoded)\n";
|
||||
stream << "\t#\n";
|
||||
stream << "\tdef send_post(self, uri, data):\n";
|
||||
stream << "\t\treturn self.__send_request('POST', uri, data)\n";
|
||||
stream << "\n";
|
||||
stream << "\tdef __send_request(self, method, uri, data):\n";
|
||||
stream << "\t\turl = self.__url + uri\n";
|
||||
stream << "\t\trequest = urllib.request.Request(url)\n";
|
||||
stream << "\t\tif (method == 'POST'):\n";
|
||||
stream << "\t\t\trequest.data = bytes(json.dumps(data), 'utf-8')\n";
|
||||
stream << "\t\tauth = str(\n";
|
||||
stream << "\t\t\tbase64.b64encode(\n";
|
||||
stream << "\t\t\t\tbytes('%s:%s' % (self.user, self.password), 'utf-8')\n";
|
||||
stream << "\t\t\t),\n";
|
||||
stream << "\t\t\t'ascii'\n";
|
||||
stream << "\t\t).strip()\n";
|
||||
stream << "\t\trequest.add_header('Authorization', 'Basic %s' % auth)\n";
|
||||
stream << "\t\trequest.add_header('Content-Type', 'application/json')\n";
|
||||
stream << "\n";
|
||||
stream << "\t\te = None\n";
|
||||
stream << "\t\ttry:\n";
|
||||
stream << "\t\t\tresponse = urllib.request.urlopen(request).read()\n";
|
||||
stream << "\t\texcept urllib.error.HTTPError as ex:\n";
|
||||
stream << "\t\t\tresponse = ex.read()\n";
|
||||
stream << "\t\t\te = ex\n";
|
||||
stream << "\n";
|
||||
stream << "\t\tif response:\n";
|
||||
stream << "\t\t\tresult = json.loads(response.decode())\n";
|
||||
stream << "\t\telse:\n";
|
||||
stream << "\t\t\tresult = {}\n";
|
||||
stream << "\n";
|
||||
stream << "\t\tif e != None:\n";
|
||||
stream << "\t\t\tif result and 'error' in result:\n";
|
||||
stream << "\t\t\t\terror = \'\"\' + result[\'error\'] + \'\"\'\n";
|
||||
stream << "\t\t\telse:\n";
|
||||
stream << "\t\t\t\terror = \'No additional error message received\'\n";
|
||||
stream << "\t\t\traise APIError(\'TestRail API returned HTTP %s (%s)\' % \n";
|
||||
stream << "\t\t\t\t(e.code, error))\n";
|
||||
stream << "\n";
|
||||
stream << "\t\treturn result\n";
|
||||
stream << "\n";
|
||||
stream << "class APIError(Exception):\n";
|
||||
stream << "\tpass\n";
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
void TestRailInterface::requestDataFromUser() {
|
||||
_testRailSelectorWindow.exec();
|
||||
|
||||
if (_testRailSelectorWindow.getUserCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_url = _testRailSelectorWindow.getURL();
|
||||
_user = _testRailSelectorWindow.getUser();
|
||||
_password = _testRailSelectorWindow.getPassword();
|
||||
}
|
||||
|
||||
void TestRailInterface::createAddSectionsPythonScript(const QString& outputDirectory) {
|
||||
QFile file(outputDirectory + "/addSections.py");
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||
"Could not create \'addSections.py\'");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
QTextStream stream(&file);
|
||||
|
||||
// Code to access TestRail
|
||||
stream << "from testrail import *\n";
|
||||
stream << "client = APIClient(\'" << _url.toStdString().c_str() << "\')\n";
|
||||
stream << "client.user = \'" << _user << "\'\n";
|
||||
stream << "client.password = \'" << _password << "\'\n\n";
|
||||
|
||||
// top-level section
|
||||
stream << "data = { \'name\': \'"
|
||||
<< "Test Suite - " << QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm") << "\'}\n";
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
void TestRailInterface::createTestSuitePython(const QString& testDirectory,
|
||||
const QString& outputDirectory,
|
||||
const QString& user,
|
||||
const QString& branch) {
|
||||
|
||||
createTestRailDotPyScript(outputDirectory);
|
||||
requestDataFromUser();
|
||||
createAddSectionsPythonScript(outputDirectory);
|
||||
}
|
||||
|
||||
void TestRailInterface::createTestSuiteXML(const QString& testDirectory,
|
||||
const QString& outputDirectory,
|
||||
const QString& user,
|
||||
const QString& branch) {
|
||||
|
||||
QDomProcessingInstruction instruction = _document.createProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");
|
||||
_document.appendChild(instruction);
|
||||
|
||||
// We create a single section, within sections
|
||||
QDomElement root = _document.createElement("sections");
|
||||
_document.appendChild(root);
|
||||
|
||||
QDomElement topLevelSection = _document.createElement("section");
|
||||
|
||||
QDomElement suiteName = _document.createElement("name");
|
||||
suiteName.appendChild(_document.createTextNode("Test Suite - " + QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm")));
|
||||
topLevelSection.appendChild(suiteName);
|
||||
|
||||
QDomElement secondLevelSections = _document.createElement("sections");
|
||||
topLevelSection.appendChild(processDirectory(testDirectory, user, branch, secondLevelSections));
|
||||
|
||||
topLevelSection.appendChild(secondLevelSections);
|
||||
root.appendChild(topLevelSection);
|
||||
|
||||
// Write to file
|
||||
const QString testRailsFilename{ outputDirectory + "/TestRailSuite.xml" };
|
||||
QFile file(testRailsFilename);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Could not create XML file");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
QTextStream stream(&file);
|
||||
stream << _document.toString();
|
||||
|
||||
file.close();
|
||||
|
||||
QMessageBox::information(0, "Success", "TestRail XML file has been created");
|
||||
}
|
||||
|
||||
QDomElement TestRailInterface::processDirectory(const QString& directory, const QString& user, const QString& branch, const QDomElement& element) {
|
||||
QDomElement result = element;
|
||||
|
||||
// Loop over all entries in directory
|
||||
QDirIterator it(directory.toStdString().c_str());
|
||||
while (it.hasNext()) {
|
||||
QString nextDirectory = it.next();
|
||||
|
||||
// The object name appears after the last slash (we are assured there is at least 1).
|
||||
QString objectName = nextDirectory.right(nextDirectory.length() - nextDirectory.lastIndexOf("/") - 1);
|
||||
|
||||
// Only process directories
|
||||
if (Test::isAValidDirectory(nextDirectory)) {
|
||||
// Ignore the utils and preformance directories
|
||||
if (nextDirectory.right(QString("utils").length()) == "utils" || nextDirectory.right(QString("performance").length()) == "performance") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create a section and process it
|
||||
|
||||
QDomElement sectionElement = _document.createElement("section");
|
||||
|
||||
QDomElement sectionElementName = _document.createElement("name");
|
||||
sectionElementName.appendChild(_document.createTextNode(objectName));
|
||||
sectionElement.appendChild(sectionElementName);
|
||||
|
||||
QDomElement testsElement = _document.createElement("sections");
|
||||
sectionElement.appendChild(processDirectory(nextDirectory, user, branch, testsElement));
|
||||
|
||||
result.appendChild(sectionElement);
|
||||
} else if (objectName == "test.js" || objectName == "testStory.js") {
|
||||
QDomElement sectionElement = _document.createElement("section");
|
||||
QDomElement sectionElementName = _document.createElement("name");
|
||||
sectionElementName.appendChild(_document.createTextNode("all"));
|
||||
sectionElement.appendChild(sectionElementName);
|
||||
sectionElement.appendChild(processTest(nextDirectory, objectName, user, branch, _document.createElement("cases")));
|
||||
result.appendChild(sectionElement);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QDomElement TestRailInterface::processTest(const QString& fullDirectory, const QString& test, const QString& user, const QString& branch, const QDomElement& element) {
|
||||
QDomElement result = element;
|
||||
|
||||
QDomElement caseElement = _document.createElement("case");
|
||||
|
||||
caseElement.appendChild(_document.createElement("id"));
|
||||
|
||||
// The name of the test is derived from the full path.
|
||||
// The first term is the first word after "tests"
|
||||
// The last word is the penultimate word
|
||||
QStringList words = fullDirectory.split('/');
|
||||
int i = 0;
|
||||
while (words[i] != "tests") {
|
||||
++i;
|
||||
if (i >= words.length() - 1) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Folder \"tests\" not found in " + fullDirectory);
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
++i;
|
||||
QString title{ words[i] };
|
||||
for (++i; i < words.length() - 1; ++i) {
|
||||
title += " / " + words[i];
|
||||
}
|
||||
|
||||
QDomElement titleElement = _document.createElement("title");
|
||||
titleElement.appendChild(_document.createTextNode(title));
|
||||
caseElement.appendChild(titleElement);
|
||||
|
||||
QDomElement templateElement = _document.createElement("template");
|
||||
templateElement.appendChild(_document.createTextNode("Test Case (Steps)"));
|
||||
caseElement.appendChild(templateElement);
|
||||
|
||||
QDomElement typeElement = _document.createElement("type");
|
||||
typeElement.appendChild(_document.createTextNode("3 - Regression"));
|
||||
caseElement.appendChild(typeElement);
|
||||
|
||||
QDomElement priorityElement = _document.createElement("priority");
|
||||
priorityElement.appendChild(_document.createTextNode("Medium"));
|
||||
caseElement.appendChild(priorityElement);
|
||||
|
||||
QDomElement estimateElementName = _document.createElement("estimate");
|
||||
estimateElementName.appendChild(_document.createTextNode("60"));
|
||||
caseElement.appendChild(estimateElementName);
|
||||
|
||||
caseElement.appendChild(_document.createElement("references"));
|
||||
|
||||
QDomElement customElement = _document.createElement("custom");
|
||||
|
||||
QDomElement tester_countElement = _document.createElement("tester_count");
|
||||
tester_countElement.appendChild(_document.createTextNode("1"));
|
||||
customElement.appendChild(tester_countElement);
|
||||
|
||||
QDomElement domain_bot_loadElement = _document.createElement("domain_bot_load");
|
||||
QDomElement domain_bot_loadElementId = _document.createElement("id");
|
||||
domain_bot_loadElementId.appendChild(_document.createTextNode("1"));
|
||||
domain_bot_loadElement.appendChild(domain_bot_loadElementId);
|
||||
QDomElement domain_bot_loadElementValue = _document.createElement("value");
|
||||
domain_bot_loadElementValue.appendChild(_document.createTextNode(" Without Bots (hifiqa-rc / hifi-qa-stable / hifiqa-master)"));
|
||||
domain_bot_loadElement.appendChild(domain_bot_loadElementValue);
|
||||
customElement.appendChild(domain_bot_loadElement);
|
||||
|
||||
QDomElement automation_typeElement = _document.createElement("automation_type");
|
||||
QDomElement automation_typeElementId = _document.createElement("id");
|
||||
automation_typeElementId.appendChild(_document.createTextNode("0"));
|
||||
automation_typeElement.appendChild(automation_typeElementId);
|
||||
QDomElement automation_typeElementValue = _document.createElement("value");
|
||||
automation_typeElementValue.appendChild(_document.createTextNode("None"));
|
||||
automation_typeElement.appendChild(automation_typeElementValue);
|
||||
customElement.appendChild(automation_typeElement);
|
||||
|
||||
QDomElement added_to_releaseElement = _document.createElement("added_to_release");
|
||||
QDomElement added_to_releaseElementId = _document.createElement("id");
|
||||
added_to_releaseElementId.appendChild(_document.createTextNode("4"));
|
||||
added_to_releaseElement.appendChild(added_to_releaseElementId);
|
||||
QDomElement added_to_releaseElementValue = _document.createElement("value");
|
||||
added_to_releaseElementValue.appendChild(_document.createTextNode(branch));
|
||||
added_to_releaseElement.appendChild(added_to_releaseElementValue);
|
||||
customElement.appendChild(added_to_releaseElement);
|
||||
|
||||
QDomElement precondsElement = _document.createElement("preconds");
|
||||
precondsElement.appendChild(_document.createTextNode("Tester is in an empty region of a domain in which they have edit rights\n\n*Note: Press 'n' to advance test script"));
|
||||
customElement.appendChild(precondsElement);
|
||||
|
||||
QString testMDName = QString("https://github.com/") + user + "/hifi_tests/blob/" + branch + "/tests/content/entity/light/point/create/test.md";
|
||||
|
||||
QDomElement steps_seperatedElement = _document.createElement("steps_separated");
|
||||
QDomElement stepElement = _document.createElement("step");
|
||||
QDomElement stepIndexElement = _document.createElement("index");
|
||||
stepIndexElement.appendChild(_document.createTextNode("1"));
|
||||
stepElement.appendChild(stepIndexElement);
|
||||
QDomElement stepContentElement = _document.createElement("content");
|
||||
stepContentElement.appendChild(_document.createTextNode(QString("Execute instructions in [THIS TEST](") + testMDName + ")"));
|
||||
stepElement.appendChild(stepContentElement);
|
||||
QDomElement stepExpectedElement = _document.createElement("expected");
|
||||
stepExpectedElement.appendChild(_document.createTextNode("Refer to the expected result in the linked description."));
|
||||
stepElement.appendChild(stepExpectedElement);
|
||||
steps_seperatedElement.appendChild(stepElement);
|
||||
customElement.appendChild(steps_seperatedElement);
|
||||
|
||||
QDomElement notesElement = _document.createElement("notes");
|
||||
notesElement.appendChild(_document.createTextNode(testMDName));
|
||||
customElement.appendChild(notesElement);
|
||||
|
||||
caseElement.appendChild(customElement);
|
||||
|
||||
result.appendChild(caseElement);
|
||||
|
||||
return result;
|
||||
}
|
54
tools/auto-tester/src/TestRailInterface.h
Normal file
54
tools/auto-tester/src/TestRailInterface.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// TestRailInterface.h
|
||||
//
|
||||
// Created by Nissim Hadar on 6 Jul 2018.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_test_testrail_interface_h
|
||||
#define hifi_test_testrail_interface_h
|
||||
|
||||
#include "ui/TestRailSelectorWindow.h"
|
||||
#include <QDirIterator>
|
||||
#include <QtXml/QDomDocument>
|
||||
#include <QString>
|
||||
|
||||
class TestRailInterface {
|
||||
public:
|
||||
TestRailInterface();
|
||||
|
||||
void createTestSuiteXML(const QString& testDirectory,
|
||||
const QString& outputDirectory,
|
||||
const QString& user,
|
||||
const QString& branch);
|
||||
|
||||
void createTestSuitePython(const QString& testDirectory,
|
||||
const QString& outputDirectory,
|
||||
const QString& user,
|
||||
const QString& branch);
|
||||
|
||||
QDomElement processDirectory(const QString& directory,
|
||||
const QString& user,
|
||||
const QString& branch,
|
||||
const QDomElement& element);
|
||||
|
||||
QDomElement processTest(const QString& fullDirectory, const QString& test, const QString& user, const QString& branch, const QDomElement& element);
|
||||
|
||||
void createTestRailDotPyScript(const QString& outputDirectory);
|
||||
void requestDataFromUser();
|
||||
void createAddSectionsPythonScript(const QString& outputDirectory);
|
||||
|
||||
private:
|
||||
QDomDocument _document;
|
||||
|
||||
TestRailSelectorWindow _testRailSelectorWindow;
|
||||
|
||||
QString _url;
|
||||
QString _user;
|
||||
QString _password;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -16,56 +16,60 @@
|
|||
#endif
|
||||
|
||||
AutoTester::AutoTester(QWidget *parent) : QMainWindow(parent) {
|
||||
ui.setupUi(this);
|
||||
ui.checkBoxInteractiveMode->setChecked(true);
|
||||
ui.progressBar->setVisible(false);
|
||||
_ui.setupUi(this);
|
||||
_ui.checkBoxInteractiveMode->setChecked(true);
|
||||
_ui.progressBar->setVisible(false);
|
||||
|
||||
signalMapper = new QSignalMapper();
|
||||
_signalMapper = new QSignalMapper();
|
||||
|
||||
connect(ui.actionClose, &QAction::triggered, this, &AutoTester::on_closeButton_clicked);
|
||||
connect(ui.actionAbout, &QAction::triggered, this, &AutoTester::about);
|
||||
connect(_ui.actionClose, &QAction::triggered, this, &AutoTester::on_closeButton_clicked);
|
||||
connect(_ui.actionAbout, &QAction::triggered, this, &AutoTester::about);
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
ui.hideTaskbarButton->setVisible(false);
|
||||
ui.showTaskbarButton->setVisible(false);
|
||||
_ui.hideTaskbarButton->setVisible(false);
|
||||
_ui.showTaskbarButton->setVisible(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AutoTester::setup() {
|
||||
test = new Test();
|
||||
_test = new Test();
|
||||
}
|
||||
|
||||
void AutoTester::runFromCommandLine(const QString& testFolder, const QString& branch, const QString& user) {
|
||||
isRunningFromCommandline = true;
|
||||
test->startTestsEvaluation(testFolder, branch, user);
|
||||
_isRunningFromCommandline = true;
|
||||
_test->startTestsEvaluation(testFolder, branch, user);
|
||||
}
|
||||
|
||||
void AutoTester::on_evaluateTestsButton_clicked() {
|
||||
test->startTestsEvaluation();
|
||||
_test->startTestsEvaluation();
|
||||
}
|
||||
|
||||
void AutoTester::on_createRecursiveScriptButton_clicked() {
|
||||
test->createRecursiveScript();
|
||||
_test->createRecursiveScript();
|
||||
}
|
||||
|
||||
void AutoTester::on_createAllRecursiveScriptsButton_clicked() {
|
||||
test->createAllRecursiveScripts();
|
||||
_test->createAllRecursiveScripts();
|
||||
}
|
||||
|
||||
void AutoTester::on_createTestsButton_clicked() {
|
||||
test->createTests();
|
||||
_test->createTests();
|
||||
}
|
||||
|
||||
void AutoTester::on_createMDFileButton_clicked() {
|
||||
test->createMDFile();
|
||||
_test->createMDFile();
|
||||
}
|
||||
|
||||
void AutoTester::on_createAllMDFilesButton_clicked() {
|
||||
test->createAllMDFiles();
|
||||
_test->createAllMDFiles();
|
||||
}
|
||||
|
||||
void AutoTester::on_createTestsOutlineButton_clicked() {
|
||||
test->createTestsOutline();
|
||||
_test->createTestsOutline();
|
||||
}
|
||||
|
||||
void AutoTester::on_createTestRailTestSuiteButton_clicked() {
|
||||
_test->createTestRailTestSuite();
|
||||
}
|
||||
|
||||
// To toggle between show and hide
|
||||
|
@ -96,11 +100,19 @@ void AutoTester::on_closeButton_clicked() {
|
|||
exit(0);
|
||||
}
|
||||
|
||||
void AutoTester::downloadImage(const QUrl& url) {
|
||||
downloaders.emplace_back(new Downloader(url, this));
|
||||
connect(downloaders[_index], SIGNAL (downloaded()), signalMapper, SLOT (map()));
|
||||
void AutoTester::on_createPythonScriptRadioButton_clicked() {
|
||||
_test->setTestRailCreateMode(PYTHON);
|
||||
}
|
||||
|
||||
signalMapper->setMapping(downloaders[_index], _index);
|
||||
void AutoTester::on_createXMLScriptRadioButton_clicked() {
|
||||
_test->setTestRailCreateMode(XML);
|
||||
}
|
||||
|
||||
void AutoTester::downloadImage(const QUrl& url) {
|
||||
_downloaders.emplace_back(new Downloader(url, this));
|
||||
connect(_downloaders[_index], SIGNAL (downloaded()), _signalMapper, SLOT (map()));
|
||||
|
||||
_signalMapper->setMapping(_downloaders[_index], _index);
|
||||
|
||||
++_index;
|
||||
}
|
||||
|
@ -113,39 +125,39 @@ void AutoTester::downloadImages(const QStringList& URLs, const QString& director
|
|||
_numberOfImagesDownloaded = 0;
|
||||
_index = 0;
|
||||
|
||||
ui.progressBar->setMinimum(0);
|
||||
ui.progressBar->setMaximum(_numberOfImagesToDownload - 1);
|
||||
ui.progressBar->setValue(0);
|
||||
ui.progressBar->setVisible(true);
|
||||
_ui.progressBar->setMinimum(0);
|
||||
_ui.progressBar->setMaximum(_numberOfImagesToDownload - 1);
|
||||
_ui.progressBar->setValue(0);
|
||||
_ui.progressBar->setVisible(true);
|
||||
|
||||
downloaders.clear();
|
||||
_downloaders.clear();
|
||||
for (int i = 0; i < _numberOfImagesToDownload; ++i) {
|
||||
QUrl imageURL(URLs[i]);
|
||||
downloadImage(imageURL);
|
||||
}
|
||||
|
||||
connect(signalMapper, SIGNAL (mapped(int)), this, SLOT (saveImage(int)));
|
||||
connect(_signalMapper, SIGNAL (mapped(int)), this, SLOT (saveImage(int)));
|
||||
}
|
||||
|
||||
void AutoTester::saveImage(int index) {
|
||||
try {
|
||||
QFile file(_directoryName + "/" + _filenames[index]);
|
||||
file.open(QIODevice::WriteOnly);
|
||||
file.write(downloaders[index]->downloadedData());
|
||||
file.write(_downloaders[index]->downloadedData());
|
||||
file.close();
|
||||
} catch (...) {
|
||||
QMessageBox::information(0, "Test Aborted", "Failed to save image: " + _filenames[index]);
|
||||
ui.progressBar->setVisible(false);
|
||||
_ui.progressBar->setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
++_numberOfImagesDownloaded;
|
||||
|
||||
if (_numberOfImagesDownloaded == _numberOfImagesToDownload) {
|
||||
disconnect(signalMapper, SIGNAL (mapped(int)), this, SLOT (saveImage(int)));
|
||||
test->finishTestsEvaluation(isRunningFromCommandline, ui.checkBoxInteractiveMode->isChecked(), ui.progressBar);
|
||||
disconnect(_signalMapper, SIGNAL (mapped(int)), this, SLOT (saveImage(int)));
|
||||
_test->finishTestsEvaluation(_isRunningFromCommandline, _ui.checkBoxInteractiveMode->isChecked(), _ui.progressBar);
|
||||
} else {
|
||||
ui.progressBar->setValue(_numberOfImagesDownloaded);
|
||||
_ui.progressBar->setValue(_numberOfImagesDownloaded);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,18 +166,18 @@ void AutoTester::about() {
|
|||
}
|
||||
|
||||
void AutoTester::setUserText(const QString& user) {
|
||||
ui.userTextEdit->setText(user);
|
||||
_ui.userTextEdit->setText(user);
|
||||
}
|
||||
|
||||
QString AutoTester::getSelectedUser()
|
||||
{
|
||||
return ui.userTextEdit->toPlainText();
|
||||
return _ui.userTextEdit->toPlainText();
|
||||
}
|
||||
|
||||
void AutoTester::setBranchText(const QString& branch) {
|
||||
ui.branchTextEdit->setText(branch);
|
||||
_ui.branchTextEdit->setText(branch);
|
||||
}
|
||||
|
||||
QString AutoTester::getSelectedBranch() {
|
||||
return ui.branchTextEdit->toPlainText();
|
||||
return _ui.branchTextEdit->toPlainText();
|
||||
}
|
||||
|
|
|
@ -45,10 +45,14 @@ private slots:
|
|||
void on_createMDFileButton_clicked();
|
||||
void on_createAllMDFilesButton_clicked();
|
||||
void on_createTestsOutlineButton_clicked();
|
||||
void on_createTestRailTestSuiteButton_clicked();
|
||||
|
||||
void on_hideTaskbarButton_clicked();
|
||||
void on_showTaskbarButton_clicked();
|
||||
|
||||
void on_createPythonScriptRadioButton_clicked();
|
||||
void on_createXMLScriptRadioButton_clicked();
|
||||
|
||||
void on_closeButton_clicked();
|
||||
|
||||
void saveImage(int index);
|
||||
|
@ -56,23 +60,23 @@ private slots:
|
|||
void about();
|
||||
|
||||
private:
|
||||
Ui::AutoTesterClass ui;
|
||||
Test* test;
|
||||
Ui::AutoTesterClass _ui;
|
||||
Test* _test;
|
||||
|
||||
std::vector<Downloader*> downloaders;
|
||||
std::vector<Downloader*> _downloaders;
|
||||
|
||||
// local storage for parameters - folder to store downloaded files in, and a list of their names
|
||||
QString _directoryName;
|
||||
QStringList _filenames;
|
||||
|
||||
// Used to enable passing a parameter to slots
|
||||
QSignalMapper* signalMapper;
|
||||
QSignalMapper* _signalMapper;
|
||||
|
||||
int _numberOfImagesToDownload { 0 };
|
||||
int _numberOfImagesDownloaded { 0 };
|
||||
int _index { 0 };
|
||||
|
||||
bool isRunningFromCommandline { false };
|
||||
bool _isRunningFromCommandline { false };
|
||||
};
|
||||
|
||||
#endif // hifi_AutoTester_h
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>612</width>
|
||||
<height>537</height>
|
||||
<width>645</width>
|
||||
<height>570</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -18,7 +18,7 @@
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>380</x>
|
||||
<y>430</y>
|
||||
<y>450</y>
|
||||
<width>101</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
|
@ -44,7 +44,7 @@
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>430</x>
|
||||
<y>270</y>
|
||||
<y>320</y>
|
||||
<width>101</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
|
@ -57,7 +57,7 @@
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>330</x>
|
||||
<y>110</y>
|
||||
<y>170</y>
|
||||
<width>220</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
|
@ -70,7 +70,7 @@
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>320</x>
|
||||
<y>280</y>
|
||||
<y>330</y>
|
||||
<width>131</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
|
@ -86,7 +86,7 @@
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>320</x>
|
||||
<y>330</y>
|
||||
<y>380</y>
|
||||
<width>255</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
|
@ -99,7 +99,7 @@
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>330</x>
|
||||
<y>170</y>
|
||||
<y>230</y>
|
||||
<width>220</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
|
@ -229,13 +229,55 @@
|
|||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="createTestRailTestSuiteButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>409</x>
|
||||
<y>100</y>
|
||||
<width>141</width>
|
||||
<height>40</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Create TestRail Test Suite</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QRadioButton" name="createPythonScriptRadioButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>310</x>
|
||||
<y>100</y>
|
||||
<width>95</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Python</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QRadioButton" name="createXMLScriptRadioButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>310</x>
|
||||
<y>120</y>
|
||||
<width>95</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>XML</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menuBar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>612</width>
|
||||
<width>645</width>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
|
|
|
@ -66,14 +66,14 @@ void MismatchWindow::setTestFailure(TestFailure testFailure) {
|
|||
QPixmap expectedPixmap = QPixmap(testFailure._pathname + testFailure._expectedImageFilename);
|
||||
QPixmap actualPixmap = QPixmap(testFailure._pathname + testFailure._actualImageFilename);
|
||||
|
||||
diffPixmap = computeDiffPixmap(
|
||||
_diffPixmap = computeDiffPixmap(
|
||||
QImage(testFailure._pathname + testFailure._expectedImageFilename),
|
||||
QImage(testFailure._pathname + testFailure._actualImageFilename)
|
||||
);
|
||||
|
||||
expectedImage->setPixmap(expectedPixmap);
|
||||
resultImage->setPixmap(actualPixmap);
|
||||
diffImage->setPixmap(diffPixmap);
|
||||
diffImage->setPixmap(_diffPixmap);
|
||||
}
|
||||
|
||||
void MismatchWindow::on_passTestButton_clicked() {
|
||||
|
@ -92,5 +92,5 @@ void MismatchWindow::on_abortTestsButton_clicked() {
|
|||
}
|
||||
|
||||
QPixmap MismatchWindow::getComparisonImage() {
|
||||
return diffPixmap;
|
||||
return _diffPixmap;
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ private slots:
|
|||
private:
|
||||
UserResponse _userResponse{ USER_RESPONSE_INVALID };
|
||||
|
||||
QPixmap diffPixmap;
|
||||
QPixmap _diffPixmap;
|
||||
};
|
||||
|
||||
|
||||
|
|
62
tools/auto-tester/src/ui/TestRailSelectorWindow.cpp
Normal file
62
tools/auto-tester/src/ui/TestRailSelectorWindow.cpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// TestRailSelectorWindow.cpp
|
||||
//
|
||||
// Created by Nissim Hadar on 26 Jul 2017.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "TestRailSelectorWindow.h"
|
||||
|
||||
#include <QtCore/QFileInfo>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
TestRailSelectorWindow::TestRailSelectorWindow(QWidget *parent) {
|
||||
setupUi(this);
|
||||
|
||||
projectLineEdit->setValidator(new QIntValidator(1, 999, this));
|
||||
}
|
||||
|
||||
void TestRailSelectorWindow::on_OKButton_clicked() {
|
||||
userCancelled = false;
|
||||
close();
|
||||
}
|
||||
|
||||
void TestRailSelectorWindow::on_cancelButton_clicked() {
|
||||
userCancelled = true;
|
||||
close();
|
||||
}
|
||||
|
||||
bool TestRailSelectorWindow::getUserCancelled() {
|
||||
return userCancelled;
|
||||
}
|
||||
|
||||
void TestRailSelectorWindow::setURL(const QString& user) {
|
||||
URLTextEdit->setText(user);
|
||||
}
|
||||
|
||||
QString TestRailSelectorWindow::getURL() {
|
||||
return URLTextEdit->toPlainText();
|
||||
}
|
||||
|
||||
void TestRailSelectorWindow::setUser(const QString& user) {
|
||||
userTextEdit->setText(user);
|
||||
}
|
||||
|
||||
QString TestRailSelectorWindow::getUser() {
|
||||
return userTextEdit->toPlainText();
|
||||
}
|
||||
|
||||
QString TestRailSelectorWindow::getPassword() {
|
||||
return passwordLineEdit->text();
|
||||
}
|
||||
|
||||
void TestRailSelectorWindow::setProject(const int project) {
|
||||
projectLineEdit->setText(QString::number(project));
|
||||
}
|
||||
|
||||
int TestRailSelectorWindow::getProject() {
|
||||
return projectLineEdit->text().toInt();
|
||||
}
|
41
tools/auto-tester/src/ui/TestRailSelectorWindow.h
Normal file
41
tools/auto-tester/src/ui/TestRailSelectorWindow.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// TestRailSelectorWindow.h
|
||||
//
|
||||
// Created by Nissim Hadar on 26 Jul 2017.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#ifndef hifi_TestRailSelectorWindow_h
|
||||
#define hifi_TestRailSelectorWindow_h
|
||||
|
||||
#include "ui_TestRailSelectorWindow.h"
|
||||
|
||||
class TestRailSelectorWindow : public QDialog, public Ui::TestRailSelectorWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TestRailSelectorWindow(QWidget* parent = Q_NULLPTR);
|
||||
|
||||
bool getUserCancelled();
|
||||
|
||||
void setURL(const QString& user);
|
||||
QString getURL();
|
||||
|
||||
void setUser(const QString& user);
|
||||
QString getUser();
|
||||
|
||||
QString getPassword();
|
||||
|
||||
void setProject(const int project);
|
||||
int getProject();
|
||||
|
||||
bool userCancelled{ false };
|
||||
|
||||
private slots:
|
||||
void on_OKButton_clicked();
|
||||
void on_cancelButton_clicked();
|
||||
};
|
||||
|
||||
#endif
|
182
tools/auto-tester/src/ui/TestRailSelectorWindow.ui
Normal file
182
tools/auto-tester/src/ui/TestRailSelectorWindow.ui
Normal file
|
@ -0,0 +1,182 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TestRailSelectorWindow</class>
|
||||
<widget class="QDialog" name="TestRailSelectorWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>489</width>
|
||||
<height>312</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MismatchWindow</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="errorLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>850</y>
|
||||
<width>500</width>
|
||||
<height>28</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>similarity</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>70</x>
|
||||
<y>115</y>
|
||||
<width>121</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TestRail Password</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>70</x>
|
||||
<y>25</y>
|
||||
<width>121</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TestRail URL</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTextEdit" name="URLTextEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>200</x>
|
||||
<y>25</y>
|
||||
<width>231</width>
|
||||
<height>24</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="OKButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>120</x>
|
||||
<y>240</y>
|
||||
<width>93</width>
|
||||
<height>28</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>OK</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="cancelButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>280</x>
|
||||
<y>240</y>
|
||||
<width>93</width>
|
||||
<height>28</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="passwordLineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>200</x>
|
||||
<y>115</y>
|
||||
<width>231</width>
|
||||
<height>24</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTextEdit" name="userTextEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>200</x>
|
||||
<y>70</y>
|
||||
<width>231</width>
|
||||
<height>24</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>70</x>
|
||||
<y>70</y>
|
||||
<width>121</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TestRail User</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="projectLineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>200</x>
|
||||
<y>160</y>
|
||||
<width>231</width>
|
||||
<height>24</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Normal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>70</x>
|
||||
<y>160</y>
|
||||
<width>121</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TestRail Project</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
Loading…
Reference in a new issue