mirror of
https://github.com/overte-org/overte.git
synced 2025-04-12 01:22:59 +02:00
Migrate tool window to overlay/QML
This commit is contained in:
parent
e8adcd9f1f
commit
b6272b7824
37 changed files with 2313 additions and 1694 deletions
|
@ -1502,7 +1502,11 @@ PropertiesTool = function(opts) {
|
|||
var that = {};
|
||||
|
||||
var url = Script.resolvePath('html/entityProperties.html');
|
||||
var webView = new WebWindow('Entity Properties', url, 200, 280, true);
|
||||
var webView = new OverlayWebWindow({
|
||||
title: 'Entity Properties',
|
||||
source: url,
|
||||
toolWindow: true
|
||||
});
|
||||
|
||||
var visible = false;
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ SettingsWindow = function() {
|
|||
this.plankyStack = null;
|
||||
this.webWindow = null;
|
||||
this.init = function(plankyStack) {
|
||||
_this.webWindow = new WebWindow('Planky', Script.resolvePath('../../html/plankySettings.html'), 255, 500, true);
|
||||
_this.webWindow = new OverlayWebWindow('Planky', Script.resolvePath('../../html/plankySettings.html'), 255, 500, true);
|
||||
_this.webWindow.setVisible(false);
|
||||
_this.webWindow.eventBridge.webEventReceived.connect(_this.onWebEventReceived);
|
||||
_this.plankyStack = plankyStack;
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<script src="list.min.js"></script>
|
||||
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
|
||||
<script type="text/javascript" src="eventBridgeLoader.js"></script>
|
||||
<script>
|
||||
var entities = {};
|
||||
var selectedEntities = [];
|
||||
|
@ -15,217 +17,219 @@
|
|||
const MAX_ITEMS = Number.MAX_VALUE; // Used to set the max length of the list of discovered entities.
|
||||
|
||||
function loaded() {
|
||||
entityList = new List('entity-list', { valueNames: ['name', 'type', 'url'], page: MAX_ITEMS});
|
||||
entityList.clear();
|
||||
elEntityTable = document.getElementById("entity-table");
|
||||
elEntityTableBody = document.getElementById("entity-table-body");
|
||||
elRefresh = document.getElementById("refresh");
|
||||
elDelete = document.getElementById("delete");
|
||||
elTeleport = document.getElementById("teleport");
|
||||
elRadius = document.getElementById("radius");
|
||||
elNoEntitiesMessage = document.getElementById("no-entities");
|
||||
elNoEntitiesRadius = document.getElementById("no-entities-radius");
|
||||
|
||||
document.getElementById("entity-name").onclick = function() {
|
||||
setSortColumn('name');
|
||||
};
|
||||
document.getElementById("entity-type").onclick = function() {
|
||||
setSortColumn('type');
|
||||
};
|
||||
document.getElementById("entity-url").onclick = function() {
|
||||
setSortColumn('url');
|
||||
};
|
||||
|
||||
function onRowClicked(clickEvent) {
|
||||
var id = this.dataset.entityId;
|
||||
var selection = [this.dataset.entityId];
|
||||
if (clickEvent.ctrlKey) {
|
||||
selection = selection.concat(selectedEntities);
|
||||
} else if (clickEvent.shiftKey && selectedEntities.length > 0) {
|
||||
var previousItemFound = -1;
|
||||
var clickedItemFound = -1;
|
||||
for (var entity in entityList.visibleItems) {
|
||||
if (clickedItemFound === -1 && this.dataset.entityId == entityList.visibleItems[entity].values().id) {
|
||||
clickedItemFound = entity;
|
||||
} else if(previousItemFound === -1 && selectedEntities[0] == entityList.visibleItems[entity].values().id) {
|
||||
previousItemFound = entity;
|
||||
}
|
||||
}
|
||||
if (previousItemFound !== -1 && clickedItemFound !== -1) {
|
||||
var betweenItems = [];
|
||||
var toItem = Math.max(previousItemFound, clickedItemFound);
|
||||
// skip first and last item in this loop, we add them to selection after the loop
|
||||
for (var i = (Math.min(previousItemFound, clickedItemFound) + 1); i < toItem; i++) {
|
||||
entityList.visibleItems[i].elm.className = 'selected';
|
||||
betweenItems.push(entityList.visibleItems[i].values().id);
|
||||
}
|
||||
if (previousItemFound > clickedItemFound) {
|
||||
// always make sure that we add the items in the right order
|
||||
betweenItems.reverse();
|
||||
}
|
||||
selection = selection.concat(betweenItems, selectedEntities);
|
||||
}
|
||||
}
|
||||
|
||||
selectedEntities = selection;
|
||||
|
||||
this.className = 'selected';
|
||||
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "selectionUpdate",
|
||||
focus: false,
|
||||
entityIds: selection,
|
||||
}));
|
||||
}
|
||||
|
||||
function onRowDoubleClicked() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "selectionUpdate",
|
||||
focus: true,
|
||||
entityIds: [this.dataset.entityId],
|
||||
}));
|
||||
}
|
||||
|
||||
function addEntity(id, name, type, url) {
|
||||
if (entities[id] === undefined) {
|
||||
var urlParts = url.split('/');
|
||||
var filename = urlParts[urlParts.length - 1];
|
||||
|
||||
entityList.add([{ id: id, name: name, type: type, url: filename }], function(items) {
|
||||
var currentElement = items[0].elm;
|
||||
var id = items[0]._values.id;
|
||||
entities[id] = {
|
||||
id: id,
|
||||
name: name,
|
||||
el: currentElement,
|
||||
item: items[0],
|
||||
};
|
||||
currentElement.setAttribute('id', 'entity_' + id);
|
||||
currentElement.setAttribute('title', url);
|
||||
currentElement.dataset.entityId = id;
|
||||
currentElement.onclick = onRowClicked;
|
||||
currentElement.ondblclick = onRowDoubleClicked;
|
||||
});
|
||||
|
||||
if (refreshEntityListTimer) {
|
||||
clearTimeout(refreshEntityListTimer);
|
||||
}
|
||||
refreshEntityListTimer = setTimeout(refreshEntityListObject, 50);
|
||||
} else {
|
||||
var item = entities[id].item;
|
||||
item.values({ name: name, url: url });
|
||||
}
|
||||
}
|
||||
|
||||
function clearEntities() {
|
||||
entities = {};
|
||||
entityList.clear();
|
||||
}
|
||||
|
||||
var elSortOrder = {
|
||||
name: document.querySelector('#entity-name .sort-order'),
|
||||
type: document.querySelector('#entity-type .sort-order'),
|
||||
url: document.querySelector('#entity-url .sort-order'),
|
||||
}
|
||||
function setSortColumn(column) {
|
||||
if (currentSortColumn == column) {
|
||||
currentSortOrder = currentSortOrder == "asc" ? "desc" : "asc";
|
||||
} else {
|
||||
elSortOrder[currentSortColumn].style.display = 'none';
|
||||
elSortOrder[column].style.display = 'inline';
|
||||
currentSortColumn = column;
|
||||
currentSortOrder = "asc";
|
||||
}
|
||||
elSortOrder[column].innerHTML = currentSortOrder == "asc" ? ASCENDING_STRING : DESCENDING_STRING;
|
||||
entityList.sort(currentSortColumn, { order: currentSortOrder });
|
||||
}
|
||||
|
||||
function refreshEntities() {
|
||||
clearEntities();
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'refresh' }));
|
||||
}
|
||||
|
||||
function refreshEntityListObject() {
|
||||
refreshEntityListTimer = null;
|
||||
entityList.sort(currentSortColumn, { order: currentSortOrder });
|
||||
entityList.search(document.getElementById("filter").value);
|
||||
}
|
||||
|
||||
function updateSelectedEntities(selectedEntities) {
|
||||
var notFound = false;
|
||||
for (var id in entities) {
|
||||
entities[id].el.className = '';
|
||||
}
|
||||
for (var i = 0; i < selectedEntities.length; i++) {
|
||||
var id = selectedEntities[i];
|
||||
if (id in entities) {
|
||||
var entity = entities[id];
|
||||
entity.el.className = 'selected';
|
||||
} else {
|
||||
notFound = true;
|
||||
}
|
||||
}
|
||||
return notFound;
|
||||
}
|
||||
|
||||
elRefresh.onclick = function() {
|
||||
refreshEntities();
|
||||
}
|
||||
elTeleport.onclick = function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'teleport' }));
|
||||
}
|
||||
elDelete.onclick = function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
|
||||
refreshEntities();
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", function (keyDownEvent) {
|
||||
if (keyDownEvent.target.nodeName === "INPUT") {
|
||||
return;
|
||||
}
|
||||
var keyCode = keyDownEvent.keyCode;
|
||||
if (keyCode === DELETE) {
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
|
||||
refreshEntities();
|
||||
}
|
||||
}, false);
|
||||
|
||||
elRadius.onchange = function () {
|
||||
elRadius.value = Math.max(elRadius.value, 0);
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elRadius.value }));
|
||||
refreshEntities();
|
||||
elNoEntitiesRadius.firstChild.nodeValue = elRadius.value;
|
||||
}
|
||||
|
||||
if (window.EventBridge !== undefined) {
|
||||
EventBridge.scriptEventReceived.connect(function(data) {
|
||||
data = JSON.parse(data);
|
||||
|
||||
if (data.type === "clearEntityList") {
|
||||
clearEntities();
|
||||
} else if (data.type == "selectionUpdate") {
|
||||
var notFound = updateSelectedEntities(data.selectedIDs);
|
||||
if (notFound) {
|
||||
refreshEntities();
|
||||
}
|
||||
} else if (data.type == "update") {
|
||||
var newEntities = data.entities;
|
||||
if (newEntities.length == 0) {
|
||||
elEntityTable.style.display = "none";
|
||||
elNoEntitiesMessage.style.display = "block";
|
||||
} else {
|
||||
elEntityTable.style.display = "table";
|
||||
elNoEntitiesMessage.style.display = "none";
|
||||
for (var i = 0; i < newEntities.length; i++) {
|
||||
var id = newEntities[i].id;
|
||||
addEntity(id, newEntities[i].name, newEntities[i].type, newEntities[i].url);
|
||||
}
|
||||
updateSelectedEntities(data.selectedIDs);
|
||||
}
|
||||
}
|
||||
});
|
||||
setTimeout(refreshEntities, 1000);
|
||||
}
|
||||
openEventBridge(function() {
|
||||
entityList = new List('entity-list', { valueNames: ['name', 'type', 'url'], page: MAX_ITEMS});
|
||||
entityList.clear();
|
||||
elEntityTable = document.getElementById("entity-table");
|
||||
elEntityTableBody = document.getElementById("entity-table-body");
|
||||
elRefresh = document.getElementById("refresh");
|
||||
elDelete = document.getElementById("delete");
|
||||
elTeleport = document.getElementById("teleport");
|
||||
elRadius = document.getElementById("radius");
|
||||
elNoEntitiesMessage = document.getElementById("no-entities");
|
||||
elNoEntitiesRadius = document.getElementById("no-entities-radius");
|
||||
|
||||
document.getElementById("entity-name").onclick = function() {
|
||||
setSortColumn('name');
|
||||
};
|
||||
document.getElementById("entity-type").onclick = function() {
|
||||
setSortColumn('type');
|
||||
};
|
||||
document.getElementById("entity-url").onclick = function() {
|
||||
setSortColumn('url');
|
||||
};
|
||||
|
||||
function onRowClicked(clickEvent) {
|
||||
var id = this.dataset.entityId;
|
||||
var selection = [this.dataset.entityId];
|
||||
if (clickEvent.ctrlKey) {
|
||||
selection = selection.concat(selectedEntities);
|
||||
} else if (clickEvent.shiftKey && selectedEntities.length > 0) {
|
||||
var previousItemFound = -1;
|
||||
var clickedItemFound = -1;
|
||||
for (var entity in entityList.visibleItems) {
|
||||
if (clickedItemFound === -1 && this.dataset.entityId == entityList.visibleItems[entity].values().id) {
|
||||
clickedItemFound = entity;
|
||||
} else if(previousItemFound === -1 && selectedEntities[0] == entityList.visibleItems[entity].values().id) {
|
||||
previousItemFound = entity;
|
||||
}
|
||||
}
|
||||
if (previousItemFound !== -1 && clickedItemFound !== -1) {
|
||||
var betweenItems = [];
|
||||
var toItem = Math.max(previousItemFound, clickedItemFound);
|
||||
// skip first and last item in this loop, we add them to selection after the loop
|
||||
for (var i = (Math.min(previousItemFound, clickedItemFound) + 1); i < toItem; i++) {
|
||||
entityList.visibleItems[i].elm.className = 'selected';
|
||||
betweenItems.push(entityList.visibleItems[i].values().id);
|
||||
}
|
||||
if (previousItemFound > clickedItemFound) {
|
||||
// always make sure that we add the items in the right order
|
||||
betweenItems.reverse();
|
||||
}
|
||||
selection = selection.concat(betweenItems, selectedEntities);
|
||||
}
|
||||
}
|
||||
|
||||
selectedEntities = selection;
|
||||
|
||||
this.className = 'selected';
|
||||
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "selectionUpdate",
|
||||
focus: false,
|
||||
entityIds: selection,
|
||||
}));
|
||||
}
|
||||
|
||||
function onRowDoubleClicked() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "selectionUpdate",
|
||||
focus: true,
|
||||
entityIds: [this.dataset.entityId],
|
||||
}));
|
||||
}
|
||||
|
||||
function addEntity(id, name, type, url) {
|
||||
if (entities[id] === undefined) {
|
||||
var urlParts = url.split('/');
|
||||
var filename = urlParts[urlParts.length - 1];
|
||||
|
||||
entityList.add([{ id: id, name: name, type: type, url: filename }], function(items) {
|
||||
var currentElement = items[0].elm;
|
||||
var id = items[0]._values.id;
|
||||
entities[id] = {
|
||||
id: id,
|
||||
name: name,
|
||||
el: currentElement,
|
||||
item: items[0],
|
||||
};
|
||||
currentElement.setAttribute('id', 'entity_' + id);
|
||||
currentElement.setAttribute('title', url);
|
||||
currentElement.dataset.entityId = id;
|
||||
currentElement.onclick = onRowClicked;
|
||||
currentElement.ondblclick = onRowDoubleClicked;
|
||||
});
|
||||
|
||||
if (refreshEntityListTimer) {
|
||||
clearTimeout(refreshEntityListTimer);
|
||||
}
|
||||
refreshEntityListTimer = setTimeout(refreshEntityListObject, 50);
|
||||
} else {
|
||||
var item = entities[id].item;
|
||||
item.values({ name: name, url: url });
|
||||
}
|
||||
}
|
||||
|
||||
function clearEntities() {
|
||||
entities = {};
|
||||
entityList.clear();
|
||||
}
|
||||
|
||||
var elSortOrder = {
|
||||
name: document.querySelector('#entity-name .sort-order'),
|
||||
type: document.querySelector('#entity-type .sort-order'),
|
||||
url: document.querySelector('#entity-url .sort-order'),
|
||||
}
|
||||
function setSortColumn(column) {
|
||||
if (currentSortColumn == column) {
|
||||
currentSortOrder = currentSortOrder == "asc" ? "desc" : "asc";
|
||||
} else {
|
||||
elSortOrder[currentSortColumn].style.display = 'none';
|
||||
elSortOrder[column].style.display = 'inline';
|
||||
currentSortColumn = column;
|
||||
currentSortOrder = "asc";
|
||||
}
|
||||
elSortOrder[column].innerHTML = currentSortOrder == "asc" ? ASCENDING_STRING : DESCENDING_STRING;
|
||||
entityList.sort(currentSortColumn, { order: currentSortOrder });
|
||||
}
|
||||
|
||||
function refreshEntities() {
|
||||
clearEntities();
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'refresh' }));
|
||||
}
|
||||
|
||||
function refreshEntityListObject() {
|
||||
refreshEntityListTimer = null;
|
||||
entityList.sort(currentSortColumn, { order: currentSortOrder });
|
||||
entityList.search(document.getElementById("filter").value);
|
||||
}
|
||||
|
||||
function updateSelectedEntities(selectedEntities) {
|
||||
var notFound = false;
|
||||
for (var id in entities) {
|
||||
entities[id].el.className = '';
|
||||
}
|
||||
for (var i = 0; i < selectedEntities.length; i++) {
|
||||
var id = selectedEntities[i];
|
||||
if (id in entities) {
|
||||
var entity = entities[id];
|
||||
entity.el.className = 'selected';
|
||||
} else {
|
||||
notFound = true;
|
||||
}
|
||||
}
|
||||
return notFound;
|
||||
}
|
||||
|
||||
elRefresh.onclick = function() {
|
||||
refreshEntities();
|
||||
}
|
||||
elTeleport.onclick = function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'teleport' }));
|
||||
}
|
||||
elDelete.onclick = function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
|
||||
refreshEntities();
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", function (keyDownEvent) {
|
||||
if (keyDownEvent.target.nodeName === "INPUT") {
|
||||
return;
|
||||
}
|
||||
var keyCode = keyDownEvent.keyCode;
|
||||
if (keyCode === DELETE) {
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
|
||||
refreshEntities();
|
||||
}
|
||||
}, false);
|
||||
|
||||
elRadius.onchange = function () {
|
||||
elRadius.value = Math.max(elRadius.value, 0);
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elRadius.value }));
|
||||
refreshEntities();
|
||||
elNoEntitiesRadius.firstChild.nodeValue = elRadius.value;
|
||||
}
|
||||
|
||||
if (window.EventBridge !== undefined) {
|
||||
EventBridge.scriptEventReceived.connect(function(data) {
|
||||
data = JSON.parse(data);
|
||||
|
||||
if (data.type === "clearEntityList") {
|
||||
clearEntities();
|
||||
} else if (data.type == "selectionUpdate") {
|
||||
var notFound = updateSelectedEntities(data.selectedIDs);
|
||||
if (notFound) {
|
||||
refreshEntities();
|
||||
}
|
||||
} else if (data.type == "update") {
|
||||
var newEntities = data.entities;
|
||||
if (newEntities.length == 0) {
|
||||
elEntityTable.style.display = "none";
|
||||
elNoEntitiesMessage.style.display = "block";
|
||||
} else {
|
||||
elEntityTable.style.display = "table";
|
||||
elNoEntitiesMessage.style.display = "none";
|
||||
for (var i = 0; i < newEntities.length; i++) {
|
||||
var id = newEntities[i].id;
|
||||
addEntity(id, newEntities[i].name, newEntities[i].type, newEntities[i].url);
|
||||
}
|
||||
updateSelectedEntities(data.selectedIDs);
|
||||
}
|
||||
}
|
||||
});
|
||||
setTimeout(refreshEntities, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -8,6 +8,8 @@
|
|||
// void scriptEventReceived(const QString& data);
|
||||
//
|
||||
|
||||
var EventBridge;
|
||||
|
||||
EventBridgeConnectionProxy = function(parent) {
|
||||
this.parent = parent;
|
||||
this.realSignal = this.parent.realBridge.scriptEventReceived
|
||||
|
@ -46,12 +48,10 @@ openEventBridge = function(callback) {
|
|||
socket.onopen = function() {
|
||||
channel = new QWebChannel(socket, function(channel) {
|
||||
console.log("Document url is " + document.URL);
|
||||
for(var key in channel.objects){
|
||||
console.log("registered object: " + key);
|
||||
}
|
||||
var webWindow = channel.objects[document.URL.toLowerCase()];
|
||||
console.log("WebWindow is " + webWindow)
|
||||
eventBridgeProxy = new EventBridgeProxy(webWindow);
|
||||
EventBridge = eventBridgeProxy;
|
||||
if (callback) { callback(eventBridgeProxy); }
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,110 +1,114 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
|
||||
<script type="text/javascript" src="eventBridgeLoader.js"></script>
|
||||
<script>
|
||||
function loaded() {
|
||||
var gridColor = { red: 0, green: 0, blue: 0 };
|
||||
var gridColors = [
|
||||
{ red: 0, green: 0, blue: 0 },
|
||||
{ red: 255, green: 255, blue: 255 },
|
||||
{ red: 255, green: 0, blue: 0 },
|
||||
{ red: 0, green: 255, blue: 0},
|
||||
{ red: 0, green: 0, blue: 255 },
|
||||
];
|
||||
var gridColorIndex = 0;
|
||||
|
||||
elPosY = document.getElementById("horiz-y");
|
||||
elMinorSpacing = document.getElementById("minor-spacing");
|
||||
elMajorSpacing = document.getElementById("major-spacing");
|
||||
elSnapToGrid = document.getElementById("snap-to-grid");
|
||||
elHorizontalGridVisible = document.getElementById("horiz-grid-visible");
|
||||
elMoveToSelection = document.getElementById("move-to-selection");
|
||||
elMoveToAvatar = document.getElementById("move-to-avatar");
|
||||
|
||||
if (window.EventBridge !== undefined) {
|
||||
EventBridge.scriptEventReceived.connect(function(data) {
|
||||
data = JSON.parse(data);
|
||||
|
||||
if (data.origin) {
|
||||
var origin = data.origin;
|
||||
elPosY.value = origin.y.toFixed(2);
|
||||
openEventBridge(function() {
|
||||
var gridColor = { red: 0, green: 0, blue: 0 };
|
||||
var gridColors = [
|
||||
{ red: 0, green: 0, blue: 0 },
|
||||
{ red: 255, green: 255, blue: 255 },
|
||||
{ red: 255, green: 0, blue: 0 },
|
||||
{ red: 0, green: 255, blue: 0},
|
||||
{ red: 0, green: 0, blue: 255 },
|
||||
];
|
||||
var gridColorIndex = 0;
|
||||
|
||||
elPosY = document.getElementById("horiz-y");
|
||||
elMinorSpacing = document.getElementById("minor-spacing");
|
||||
elMajorSpacing = document.getElementById("major-spacing");
|
||||
elSnapToGrid = document.getElementById("snap-to-grid");
|
||||
elHorizontalGridVisible = document.getElementById("horiz-grid-visible");
|
||||
elMoveToSelection = document.getElementById("move-to-selection");
|
||||
elMoveToAvatar = document.getElementById("move-to-avatar");
|
||||
|
||||
if (window.EventBridge !== undefined) {
|
||||
EventBridge.scriptEventReceived.connect(function(data) {
|
||||
data = JSON.parse(data);
|
||||
|
||||
if (data.origin) {
|
||||
var origin = data.origin;
|
||||
elPosY.value = origin.y.toFixed(2);
|
||||
}
|
||||
|
||||
if (data.minorGridWidth !== undefined) {
|
||||
elMinorSpacing.value = data.minorGridWidth;
|
||||
}
|
||||
|
||||
if (data.majorGridEvery !== undefined) {
|
||||
elMajorSpacing.value = data.majorGridEvery;
|
||||
}
|
||||
|
||||
if (data.gridColor) {
|
||||
gridColor = data.gridColor;
|
||||
}
|
||||
|
||||
if (data.snapToGrid !== undefined) {
|
||||
elSnapToGrid.checked = data.snapToGrid == true;
|
||||
}
|
||||
|
||||
if (data.visible !== undefined) {
|
||||
elHorizontalGridVisible.checked = data.visible == true;
|
||||
}
|
||||
});
|
||||
|
||||
function emitUpdate() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "update",
|
||||
origin: {
|
||||
y: elPosY.value,
|
||||
},
|
||||
minorGridWidth: elMinorSpacing.value,
|
||||
majorGridEvery: elMajorSpacing.value,
|
||||
gridColor: gridColor,
|
||||
colorIndex: gridColorIndex,
|
||||
snapToGrid: elSnapToGrid.checked,
|
||||
visible: elHorizontalGridVisible.checked,
|
||||
}));
|
||||
}
|
||||
|
||||
if (data.minorGridWidth !== undefined) {
|
||||
elMinorSpacing.value = data.minorGridWidth;
|
||||
}
|
||||
|
||||
if (data.majorGridEvery !== undefined) {
|
||||
elMajorSpacing.value = data.majorGridEvery;
|
||||
}
|
||||
|
||||
if (data.gridColor) {
|
||||
gridColor = data.gridColor;
|
||||
}
|
||||
|
||||
if (data.snapToGrid !== undefined) {
|
||||
elSnapToGrid.checked = data.snapToGrid == true;
|
||||
}
|
||||
|
||||
if (data.visible !== undefined) {
|
||||
elHorizontalGridVisible.checked = data.visible == true;
|
||||
}
|
||||
});
|
||||
|
||||
function emitUpdate() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "update",
|
||||
origin: {
|
||||
y: elPosY.value,
|
||||
},
|
||||
minorGridWidth: elMinorSpacing.value,
|
||||
majorGridEvery: elMajorSpacing.value,
|
||||
gridColor: gridColor,
|
||||
colorIndex: gridColorIndex,
|
||||
snapToGrid: elSnapToGrid.checked,
|
||||
visible: elHorizontalGridVisible.checked,
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
elPosY.addEventListener("change", emitUpdate);
|
||||
elMinorSpacing.addEventListener("change", emitUpdate);
|
||||
elMajorSpacing.addEventListener("change", emitUpdate);
|
||||
elSnapToGrid.addEventListener("change", emitUpdate);
|
||||
elHorizontalGridVisible.addEventListener("change", emitUpdate);
|
||||
|
||||
elMoveToAvatar.addEventListener("click", function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "action",
|
||||
action: "moveToAvatar",
|
||||
}));
|
||||
|
||||
elPosY.addEventListener("change", emitUpdate);
|
||||
elMinorSpacing.addEventListener("change", emitUpdate);
|
||||
elMajorSpacing.addEventListener("change", emitUpdate);
|
||||
elSnapToGrid.addEventListener("change", emitUpdate);
|
||||
elHorizontalGridVisible.addEventListener("change", emitUpdate);
|
||||
|
||||
elMoveToAvatar.addEventListener("click", function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "action",
|
||||
action: "moveToAvatar",
|
||||
}));
|
||||
});
|
||||
elMoveToSelection.addEventListener("click", function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "action",
|
||||
action: "moveToSelection",
|
||||
}));
|
||||
});
|
||||
|
||||
var gridColorBox = document.getElementById('grid-color');
|
||||
|
||||
for (var i = 0; i < gridColors.length; i++) {
|
||||
var colors = gridColors[i];
|
||||
var box = document.createElement('div');
|
||||
box.setAttribute('class', 'color-box');
|
||||
box.style.background = 'rgb(' + colors.red + ', ' + colors.green + ', ' + colors.blue + ')';
|
||||
document.getElementById("grid-colors").appendChild(box);
|
||||
box.addEventListener("click", function(color, index) {
|
||||
return function() {
|
||||
gridColor = color;
|
||||
gridColorIndex = index;
|
||||
emitUpdate();
|
||||
}
|
||||
}({ red: colors.red, green: colors.green, blue: colors.blue }, i));
|
||||
}
|
||||
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'init' }));
|
||||
});
|
||||
elMoveToSelection.addEventListener("click", function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "action",
|
||||
action: "moveToSelection",
|
||||
}));
|
||||
});
|
||||
|
||||
var gridColorBox = document.getElementById('grid-color');
|
||||
|
||||
for (var i = 0; i < gridColors.length; i++) {
|
||||
var colors = gridColors[i];
|
||||
var box = document.createElement('div');
|
||||
box.setAttribute('class', 'color-box');
|
||||
box.style.background = 'rgb(' + colors.red + ', ' + colors.green + ', ' + colors.blue + ')';
|
||||
document.getElementById("grid-colors").appendChild(box);
|
||||
box.addEventListener("click", function(color, index) {
|
||||
return function() {
|
||||
gridColor = color;
|
||||
gridColorIndex = index;
|
||||
emitUpdate();
|
||||
}
|
||||
}({ red: colors.red, green: colors.green, blue: colors.blue }, i));
|
||||
}
|
||||
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'init' }));
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
|
|
@ -4,7 +4,9 @@ EntityListTool = function(opts) {
|
|||
var that = {};
|
||||
|
||||
var url = ENTITY_LIST_HTML_URL;
|
||||
var webView = new WebWindow('Entities', url, 200, 280, true);
|
||||
var webView = new OverlayWebWindow({
|
||||
title: 'Entities', source: url, toolWindow: true
|
||||
});
|
||||
|
||||
var searchRadius = 100;
|
||||
|
||||
|
|
|
@ -231,7 +231,9 @@ GridTool = function(opts) {
|
|||
var listeners = [];
|
||||
|
||||
var url = GRID_CONTROLS_HTML_URL;
|
||||
var webView = new WebWindow('Grid', url, 200, 280, true);
|
||||
var webView = new OverlayWebWindow({
|
||||
title: 'Grid', source: url, toolWindow: true
|
||||
});
|
||||
|
||||
horizontalGrid.addListener(function(data) {
|
||||
webView.eventBridge.emitScriptEvent(JSON.stringify(data));
|
||||
|
|
|
@ -10,18 +10,22 @@ function findChild(item, name) {
|
|||
return null;
|
||||
}
|
||||
|
||||
function findParent(item, name) {
|
||||
function findParentMatching(item, predicate) {
|
||||
while (item) {
|
||||
if (item.objectName === name) {
|
||||
return item;
|
||||
if (predicate(item)) {
|
||||
break;
|
||||
}
|
||||
item = item.parent;
|
||||
}
|
||||
return null;
|
||||
return item;
|
||||
}
|
||||
|
||||
function getDesktop(item) {
|
||||
return findParent(item, OFFSCREEN_ROOT_OBJECT_NAME);
|
||||
function findParentByName(item, name) {
|
||||
return findParentMatching(item, function(item) {
|
||||
var testName = name;
|
||||
var result = (item.name === testName);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
function findRootMenu(item) {
|
||||
|
@ -29,6 +33,13 @@ function findRootMenu(item) {
|
|||
return item ? item.rootMenu : null;
|
||||
}
|
||||
|
||||
function isDesktop(item) {
|
||||
return item.desktopRoot;
|
||||
}
|
||||
|
||||
function isTopLevelWindow(item) {
|
||||
return item.topLevelWindow;
|
||||
}
|
||||
|
||||
function getTopLevelWindows(item) {
|
||||
var desktop = getDesktop(item);
|
||||
|
@ -40,8 +51,7 @@ function getTopLevelWindows(item) {
|
|||
|
||||
for (var i = 0; i < desktop.children.length; ++i) {
|
||||
var child = desktop.children[i];
|
||||
if ((Global.OFFSCREEN_WINDOW_OBJECT_NAME === child.objectName) ||
|
||||
child[Global.OFFSCREEN_WINDOW_OBJECT_NAME]) {
|
||||
if (isTopLevelWindow(child)) {
|
||||
var windowId = child.toString();
|
||||
currentWindows.push(child)
|
||||
}
|
||||
|
@ -50,9 +60,12 @@ function getTopLevelWindows(item) {
|
|||
}
|
||||
|
||||
|
||||
function getDesktop(item) {
|
||||
return findParentMatching(item, isDesktop);
|
||||
}
|
||||
|
||||
function getDesktopWindow(item) {
|
||||
item = findParent(item, OFFSCREEN_WINDOW_OBJECT_NAME);
|
||||
return item;
|
||||
return findParentMatching(item, isTopLevelWindow)
|
||||
}
|
||||
|
||||
function closeWindow(item) {
|
||||
|
@ -142,7 +155,7 @@ function raiseWindow(item) {
|
|||
|
||||
var desktop = getDesktop(targetWindow);
|
||||
if (!desktop) {
|
||||
//console.warn("Could not find desktop for window " + targetWindow);
|
||||
console.warn("Could not find desktop for window " + targetWindow);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,86 +2,26 @@ import QtQuick 2.3
|
|||
import QtQuick.Controls 1.2
|
||||
import QtWebEngine 1.1
|
||||
|
||||
import "controls"
|
||||
import "windows" as Windows
|
||||
import "controls" as Controls
|
||||
import "styles"
|
||||
|
||||
VrDialog {
|
||||
Windows.Window {
|
||||
id: root
|
||||
HifiConstants { id: hifi }
|
||||
title: "WebWindow"
|
||||
resizable: true
|
||||
enabled: false
|
||||
visible: false
|
||||
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
|
||||
destroyOnCloseButton: false
|
||||
contentImplicitWidth: clientArea.implicitWidth
|
||||
contentImplicitHeight: clientArea.implicitHeight
|
||||
backgroundColor: "#7f000000"
|
||||
property url source: "about:blank"
|
||||
property alias source: webview.url
|
||||
|
||||
signal navigating(string url)
|
||||
function stop() {
|
||||
webview.stop();
|
||||
function raiseWindow() { Desktop.raise(root) }
|
||||
|
||||
Controls.WebView {
|
||||
id: webview
|
||||
url: "about:blank"
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Ensure the JS from the web-engine makes it to our logging
|
||||
webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
|
||||
console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
id: clientArea
|
||||
implicitHeight: 600
|
||||
implicitWidth: 800
|
||||
x: root.clientX
|
||||
y: root.clientY
|
||||
width: root.clientWidth
|
||||
height: root.clientHeight
|
||||
|
||||
WebEngineView {
|
||||
id: webview
|
||||
url: root.source
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
|
||||
property var originalUrl
|
||||
property var lastFixupTime: 0
|
||||
|
||||
onUrlChanged: {
|
||||
var currentUrl = url.toString();
|
||||
var newUrl = urlHandler.fixupUrl(currentUrl).toString();
|
||||
if (newUrl != currentUrl) {
|
||||
var now = new Date().valueOf();
|
||||
if (url === originalUrl && (now - lastFixupTime < 100)) {
|
||||
console.warn("URL fixup loop detected")
|
||||
return;
|
||||
}
|
||||
originalUrl = url
|
||||
lastFixupTime = now
|
||||
url = newUrl;
|
||||
}
|
||||
}
|
||||
|
||||
onLoadingChanged: {
|
||||
// Required to support clicking on "hifi://" links
|
||||
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
|
||||
var url = loadRequest.url.toString();
|
||||
if (urlHandler.canHandleUrl(url)) {
|
||||
if (urlHandler.handleUrl(url)) {
|
||||
webview.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
profile: WebEngineProfile {
|
||||
id: webviewProfile
|
||||
httpUserAgent: "Mozilla/5.0 (HighFidelityInterface)"
|
||||
storageName: "qmlWebEngine"
|
||||
}
|
||||
}
|
||||
} // item
|
||||
} // dialog
|
||||
|
|
|
@ -5,52 +5,32 @@ import QtWebChannel 1.0
|
|||
import QtWebSockets 1.0
|
||||
import "qrc:///qtwebchannel/qwebchannel.js" as WebChannel
|
||||
|
||||
import "Global.js" as Global
|
||||
|
||||
import "windows" as Windows
|
||||
import "controls"
|
||||
import "styles"
|
||||
|
||||
VrDialog {
|
||||
Windows.Window {
|
||||
id: root
|
||||
HifiConstants { id: hifi }
|
||||
title: "QmlWindow"
|
||||
resizable: true
|
||||
enabled: false
|
||||
visible: false
|
||||
focus: true
|
||||
property var channel;
|
||||
|
||||
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
|
||||
destroyOnCloseButton: false
|
||||
contentImplicitWidth: clientArea.implicitWidth
|
||||
contentImplicitHeight: clientArea.implicitHeight
|
||||
property alias source: pageLoader.source
|
||||
|
||||
function raiseWindow() {
|
||||
Global.raiseWindow(root)
|
||||
}
|
||||
function raiseWindow() { Desktop.raise(root) }
|
||||
|
||||
Item {
|
||||
id: clientArea
|
||||
implicitHeight: 600
|
||||
implicitWidth: 800
|
||||
x: root.clientX
|
||||
y: root.clientY
|
||||
width: root.clientWidth
|
||||
height: root.clientHeight
|
||||
Loader {
|
||||
id: pageLoader
|
||||
objectName: "Loader"
|
||||
focus: true
|
||||
clip: true
|
||||
|
||||
Loader {
|
||||
id: pageLoader
|
||||
objectName: "Loader"
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
property var dialog: root
|
||||
property var dialog: root
|
||||
|
||||
Keys.onPressed: {
|
||||
console.log("QmlWindow pageLoader keypress")
|
||||
}
|
||||
Keys.onPressed: {
|
||||
console.log("QmlWindow pageLoader keypress")
|
||||
}
|
||||
} // item
|
||||
}
|
||||
} // dialog
|
||||
|
|
|
@ -7,22 +7,25 @@ import "Global.js" as Global
|
|||
// windows will be childed.
|
||||
Item {
|
||||
id: desktop
|
||||
objectName: Global.OFFSCREEN_ROOT_OBJECT_NAME
|
||||
anchors.fill: parent;
|
||||
onParentChanged: forceActiveFocus();
|
||||
|
||||
// Allows QML/JS to find the desktop through the parent chain
|
||||
property bool desktopRoot: true
|
||||
|
||||
// The VR version of the primary menu
|
||||
property var rootMenu: Menu { objectName: "rootMenu" }
|
||||
|
||||
// List of all top level windows
|
||||
property var windows: [];
|
||||
property var rootMenu: Menu {
|
||||
objectName: "rootMenu"
|
||||
}
|
||||
onChildrenChanged: windows = Global.getTopLevelWindows(desktop);
|
||||
|
||||
onChildrenChanged: {
|
||||
windows = Global.getTopLevelWindows(desktop);
|
||||
}
|
||||
|
||||
onParentChanged: {
|
||||
forceActiveFocus();
|
||||
}
|
||||
// The tool window, one instance
|
||||
property alias toolWindow: toolWindow
|
||||
ToolWindow { id: toolWindow }
|
||||
|
||||
function raise(item) {
|
||||
Global.raiseWindow(item);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
126
interface/resources/qml/ToolWindow.qml
Normal file
126
interface/resources/qml/ToolWindow.qml
Normal file
|
@ -0,0 +1,126 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtWebEngine 1.1
|
||||
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
import "windows" as Windows
|
||||
import "controls" as Controls
|
||||
|
||||
Windows.Window {
|
||||
id: toolWindow
|
||||
resizable: true
|
||||
objectName: "ToolWindow"
|
||||
destroyOnCloseButton: false
|
||||
destroyOnInvisible: false
|
||||
visible: false
|
||||
property string newTabSource
|
||||
property alias tabView: tabView
|
||||
onParentChanged: {
|
||||
x = desktop.width / 2 - width / 2;
|
||||
y = desktop.height / 2 - height / 2;
|
||||
}
|
||||
|
||||
Settings {
|
||||
category: "ToolWindow.Position"
|
||||
property alias x: toolWindow.x
|
||||
property alias y: toolWindow.y
|
||||
}
|
||||
|
||||
property var webTabCreator: Component {
|
||||
Controls.WebView {
|
||||
id: webView
|
||||
property string originalUrl;
|
||||
|
||||
// Both toolWindow.newTabSource and url can change, so we need
|
||||
// to store the original url here, without creating any bindings
|
||||
Component.onCompleted: {
|
||||
originalUrl = toolWindow.newTabSource;
|
||||
url = originalUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TabView {
|
||||
id: tabView; width: 384; height: 640;
|
||||
onCountChanged: {
|
||||
if (0 == count) {
|
||||
toolWindow.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateVisiblity() {
|
||||
var newVisible = false
|
||||
console.log("Checking " + tabView.count + " children")
|
||||
for (var i = 0; i < tabView.count; ++i) {
|
||||
if (tabView.getTab(i).enabled) {
|
||||
console.log("Tab " + i + " enabled");
|
||||
newVisible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
console.log("Setting toolWindow visible: " + newVisible);
|
||||
visible = newVisible
|
||||
}
|
||||
|
||||
function findIndexForUrl(source) {
|
||||
for (var i = 0; i < tabView.count; ++i) {
|
||||
var tab = tabView.getTab(i);
|
||||
if (tab && tab.item && tab.item.originalUrl &&
|
||||
tab.item.originalUrl === source) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function removeTabForUrl(source) {
|
||||
var index = findIndexForUrl(source);
|
||||
if (index < 0) {
|
||||
console.warn("Could not find tab for " + source);
|
||||
return;
|
||||
}
|
||||
tabView.removeTab(index);
|
||||
console.log("Updating visibility based on child tab removed");
|
||||
updateVisiblity();
|
||||
}
|
||||
|
||||
function addWebTab(properties) {
|
||||
if (!properties.source) {
|
||||
console.warn("Attempted to open Web Tool Pane without URl")
|
||||
return;
|
||||
}
|
||||
|
||||
var existingTabIndex = findIndexForUrl(properties.source);
|
||||
if (existingTabIndex >= 0) {
|
||||
console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source)
|
||||
return tabView.getTab(existingTabIndex);
|
||||
}
|
||||
|
||||
var title = properties.title || "Unknown";
|
||||
newTabSource = properties.source;
|
||||
console.log(typeof(properties.source))
|
||||
var newTab = tabView.addTab(title, webTabCreator);
|
||||
newTab.active = true;
|
||||
newTab.enabled = false;
|
||||
|
||||
if (properties.width) {
|
||||
tabView.width = Math.min(Math.max(tabView.width, properties.width),
|
||||
toolWindow.maxSize.x);
|
||||
}
|
||||
|
||||
if (properties.height) {
|
||||
tabView.height = Math.min(Math.max(tabView.height, properties.height),
|
||||
toolWindow.maxSize.y);
|
||||
}
|
||||
|
||||
console.log("Updating visibility based on child tab added");
|
||||
newTab.enabledChanged.connect(function() {
|
||||
console.log("Updating visibility based on child tab enabled change");
|
||||
updateVisiblity();
|
||||
})
|
||||
updateVisiblity();
|
||||
return newTab
|
||||
}
|
||||
}
|
|
@ -3,8 +3,6 @@ import QtQuick.Controls 1.2
|
|||
import "."
|
||||
import "../styles"
|
||||
|
||||
import "../Global.js" as Global
|
||||
|
||||
/*
|
||||
* FIXME Need to create a client property here so that objects can be
|
||||
* placed in it without having to think about positioning within the outer
|
||||
|
@ -48,7 +46,7 @@ DialogBase {
|
|||
if (enabled) {
|
||||
visible = true;
|
||||
if (root.parent) {
|
||||
Global.raiseWindow(root);
|
||||
Desktop.raise(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
50
interface/resources/qml/controls/WebView.qml
Normal file
50
interface/resources/qml/controls/WebView.qml
Normal file
|
@ -0,0 +1,50 @@
|
|||
import QtQuick 2.5
|
||||
import QtWebEngine 1.1
|
||||
|
||||
WebEngineView {
|
||||
id: root
|
||||
property var originalUrl
|
||||
property int lastFixupTime: 0
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log("Connecting JS messaging to Hifi Logging")
|
||||
// Ensure the JS from the web-engine makes it to our logging
|
||||
root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
|
||||
console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
onUrlChanged: {
|
||||
var currentUrl = url.toString();
|
||||
var newUrl = urlHandler.fixupUrl(currentUrl).toString();
|
||||
if (newUrl != currentUrl) {
|
||||
var now = new Date().valueOf();
|
||||
if (url === originalUrl && (now - lastFixupTime < 100)) {
|
||||
console.warn("URL fixup loop detected")
|
||||
return;
|
||||
}
|
||||
originalUrl = url
|
||||
lastFixupTime = now
|
||||
url = newUrl;
|
||||
}
|
||||
}
|
||||
|
||||
onLoadingChanged: {
|
||||
// Required to support clicking on "hifi://" links
|
||||
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
|
||||
var url = loadRequest.url.toString();
|
||||
if (urlHandler.canHandleUrl(url)) {
|
||||
if (urlHandler.handleUrl(url)) {
|
||||
root.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
profile: WebEngineProfile {
|
||||
id: webviewProfile
|
||||
httpUserAgent: "Mozilla/5.0 (HighFidelityInterface)"
|
||||
storageName: "qmlWebEngine"
|
||||
}
|
||||
}
|
232
interface/resources/qml/dialogs/FileDialog.qml
Normal file
232
interface/resources/qml/dialogs/FileDialog.qml
Normal file
|
@ -0,0 +1,232 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Controls 1.4
|
||||
import Qt.labs.folderlistmodel 2.1
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
import ".."
|
||||
import "."
|
||||
|
||||
// Work in progress....
|
||||
DialogBase {
|
||||
id: root
|
||||
Constants { id: vr }
|
||||
property string settingsName: ""
|
||||
|
||||
signal selectedFile(var file);
|
||||
signal canceled();
|
||||
|
||||
function selectCurrentFile() {
|
||||
var row = tableView.currentRow
|
||||
console.log("Selecting row " + row)
|
||||
var fileName = folderModel.get(row, "fileName");
|
||||
if (fileName === "..") {
|
||||
folderModel.folder = folderModel.parentFolder
|
||||
} else if (folderModel.isFolder(row)) {
|
||||
folderModel.folder = folderModel.get(row, "fileURL");
|
||||
} else {
|
||||
selectedFile(folderModel.get(row, "fileURL"));
|
||||
enabled = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
property var folderModel: FolderListModel {
|
||||
id: folderModel
|
||||
showDotAndDotDot: true
|
||||
showDirsFirst: true
|
||||
folder: "file:///C:/";
|
||||
onFolderChanged: tableView.currentRow = 0
|
||||
}
|
||||
|
||||
property var filterModel: ListModel {
|
||||
ListElement {
|
||||
text: "All Files (*.*)"
|
||||
filter: "*"
|
||||
}
|
||||
ListElement {
|
||||
text: "Javascript Files (*.js)"
|
||||
filter: "*.js"
|
||||
}
|
||||
ListElement {
|
||||
text: "QML Files (*.qml)"
|
||||
filter: "*.qml"
|
||||
}
|
||||
}
|
||||
|
||||
Settings {
|
||||
// fixme, combine with a property to allow different saved locations
|
||||
category: "FileOpenLastFolder." + settingsName
|
||||
property alias folder: folderModel.folder
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: currentDirectoryWrapper
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 8
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 8
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 8
|
||||
height: currentDirectory.implicitHeight + 8
|
||||
radius: vr.styles.radius
|
||||
color: vr.controls.colors.inputBackground
|
||||
|
||||
TextEdit {
|
||||
enabled: false
|
||||
id: currentDirectory
|
||||
text: folderModel.folder
|
||||
anchors {
|
||||
fill: parent
|
||||
leftMargin: 8
|
||||
rightMargin: 8
|
||||
topMargin: 4
|
||||
bottomMargin: 4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TableView {
|
||||
id: tableView
|
||||
focus: true
|
||||
model: folderModel
|
||||
anchors.top: currentDirectoryWrapper.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: selectionType.top
|
||||
anchors.margins: 8
|
||||
itemDelegate: Item {
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
font.family: vr.fonts.lightFontName
|
||||
font.pointSize: 12
|
||||
color: tableView.activeFocus && styleData.row === tableView.currentRow ? "yellow" : styleData.textColor
|
||||
elide: styleData.elideMode
|
||||
text: getText();
|
||||
function getText() {
|
||||
switch (styleData.column) {
|
||||
//case 1: return Date.fromLocaleString(locale, styleData.value, "yyyy-MM-dd hh:mm:ss");
|
||||
case 2: return folderModel.get(styleData.row, "fileIsDir") ? "" : formatSize(styleData.value);
|
||||
default: return styleData.value;
|
||||
}
|
||||
}
|
||||
|
||||
function formatSize(size) {
|
||||
var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ];
|
||||
var suffixIndex = 0
|
||||
while ((size / 1024.0) > 1.1) {
|
||||
size /= 1024.0;
|
||||
++suffixIndex;
|
||||
}
|
||||
|
||||
size = Math.round(size*1000)/1000;
|
||||
size = size.toLocaleString()
|
||||
|
||||
return size + " " + suffixes[suffixIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TableViewColumn {
|
||||
role: "fileName"
|
||||
title: "Name"
|
||||
width: 400
|
||||
}
|
||||
TableViewColumn {
|
||||
role: "fileModified"
|
||||
title: "Date Modified"
|
||||
width: 200
|
||||
}
|
||||
TableViewColumn {
|
||||
role: "fileSize"
|
||||
title: "Size"
|
||||
width: 200
|
||||
}
|
||||
|
||||
function selectItem(row) {
|
||||
selectCurrentFile()
|
||||
}
|
||||
|
||||
|
||||
Keys.onReturnPressed: selectCurrentFile();
|
||||
onDoubleClicked: { currentRow = row; selectCurrentFile(); }
|
||||
onCurrentRowChanged: currentSelection.text = model.get(currentRow, "fileName");
|
||||
KeyNavigation.left: cancelButton
|
||||
KeyNavigation.right: selectionType
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.right: selectionType.left
|
||||
anchors.rightMargin: 8
|
||||
anchors.top: selectionType.top
|
||||
anchors.bottom: selectionType.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 8
|
||||
radius: 8
|
||||
color: "#eee"
|
||||
Text {
|
||||
id: currentSelection
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
font.family: vr.fonts.mainFontName
|
||||
font.pointSize: 18
|
||||
}
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: selectionType
|
||||
anchors.bottom: buttonRow.top
|
||||
anchors.bottomMargin: 8
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 8
|
||||
anchors.left: buttonRow.left
|
||||
model: filterModel
|
||||
onCurrentIndexChanged: folderModel.nameFilters = [
|
||||
filterModel.get(currentIndex).filter
|
||||
]
|
||||
|
||||
KeyNavigation.left: tableView
|
||||
KeyNavigation.right: openButton
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttonRow
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 8
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 8
|
||||
layoutDirection: Qt.RightToLeft
|
||||
spacing: 8
|
||||
Button {
|
||||
id: cancelButton
|
||||
text: "Cancel"
|
||||
KeyNavigation.up: selectionType
|
||||
KeyNavigation.left: openButton
|
||||
KeyNavigation.right: tableView.contentItem
|
||||
Keys.onReturnPressed: { canceled(); root.enabled = false }
|
||||
onClicked: { canceled(); root.enabled = false }
|
||||
}
|
||||
Button {
|
||||
id: openButton
|
||||
text: "Open"
|
||||
enabled: tableView.currentRow != -1 && !folderModel.get(tableView.currentRow, "fileIsDir")
|
||||
KeyNavigation.up: selectionType
|
||||
KeyNavigation.left: selectionType
|
||||
KeyNavigation.right: cancelButton
|
||||
onClicked: selectCurrentFile();
|
||||
Keys.onReturnPressed: selectedFile(folderModel.get(tableView.currentRow, "fileURL"))
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key === Qt.Key_Backspace && folderModel.parentFolder && folderModel.parentFolder != "") {
|
||||
console.log("Navigating to " + folderModel.parentFolder)
|
||||
folderModel.folder = folderModel.parentFolder
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
311
interface/resources/qml/test/Stubs.qml
Normal file
311
interface/resources/qml/test/Stubs.qml
Normal file
|
@ -0,0 +1,311 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
// Stubs for the global service objects set by Interface.cpp when creating the UI
|
||||
// This is useful for testing inside Qt creator where these services don't actually exist.
|
||||
Item {
|
||||
|
||||
Item {
|
||||
objectName: "urlHandler"
|
||||
function fixupUrl(url) { return url; }
|
||||
function canHandleUrl(url) { return false; }
|
||||
function handleUrl(url) { return true; }
|
||||
}
|
||||
|
||||
Item {
|
||||
objectName: "Account"
|
||||
function isLoggedIn() { return true; }
|
||||
function getUsername() { return "Jherico"; }
|
||||
}
|
||||
|
||||
Item {
|
||||
objectName: "ScriptDiscoveryService"
|
||||
property var scriptsModelFilter: scriptsModel
|
||||
signal scriptCountChanged()
|
||||
property var _runningScripts:[
|
||||
{ name: "wireFrameTest.js", url: "foo/wireframetest.js", path: "foo/wireframetest.js", local: true },
|
||||
{ name: "edit.js", url: "foo/edit.js", path: "foo/edit.js", local: false },
|
||||
{ name: "listAllScripts.js", url: "foo/listAllScripts.js", path: "foo/listAllScripts.js", local: false },
|
||||
{ name: "users.js", url: "foo/users.js", path: "foo/users.js", local: false },
|
||||
]
|
||||
|
||||
function getRunning() {
|
||||
return _runningScripts;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
id: menuHelper
|
||||
objectName: "MenuHelper"
|
||||
|
||||
Component {
|
||||
id: modelMaker
|
||||
ListModel { }
|
||||
}
|
||||
|
||||
function toModel(menu, parent) {
|
||||
if (!parent) { parent = menuHelper }
|
||||
var result = modelMaker.createObject(parent);
|
||||
if (menu.type !== MenuItemType.Menu) {
|
||||
console.warn("Not a menu: " + menu);
|
||||
return result;
|
||||
}
|
||||
|
||||
var items = menu.items;
|
||||
for (var i = 0; i < items.length; ++i) {
|
||||
var item = items[i];
|
||||
switch (item.type) {
|
||||
case 2:
|
||||
result.append({"name": item.title, "item": item})
|
||||
break;
|
||||
case 1:
|
||||
result.append({"name": item.text, "item": item})
|
||||
break;
|
||||
case 0:
|
||||
result.append({"name": "", "item": item})
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
objectName: "Desktop"
|
||||
|
||||
property string _OFFSCREEN_ROOT_OBJECT_NAME: "desktopRoot";
|
||||
property string _OFFSCREEN_DIALOG_OBJECT_NAME: "topLevelWindow";
|
||||
|
||||
|
||||
function findChild(item, name) {
|
||||
for (var i = 0; i < item.children.length; ++i) {
|
||||
if (item.children[i].objectName === name) {
|
||||
return item.children[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function findParent(item, name) {
|
||||
while (item) {
|
||||
if (item.objectName === name) {
|
||||
return item;
|
||||
}
|
||||
item = item.parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function findDialog(item) {
|
||||
item = findParent(item, _OFFSCREEN_DIALOG_OBJECT_NAME);
|
||||
return item;
|
||||
}
|
||||
|
||||
function closeDialog(item) {
|
||||
item = findDialog(item);
|
||||
if (item) {
|
||||
item.enabled = false
|
||||
} else {
|
||||
console.warn("Could not find top level dialog")
|
||||
}
|
||||
}
|
||||
|
||||
function getDesktop(item) {
|
||||
while (item) {
|
||||
if (item.desktopRoot) {
|
||||
break;
|
||||
}
|
||||
item = item.parent;
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
function raise(item) {
|
||||
var desktop = getDesktop(item);
|
||||
if (desktop) {
|
||||
desktop.raise(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: root
|
||||
objectName: "rootMenu"
|
||||
|
||||
Menu {
|
||||
title: "Audio"
|
||||
}
|
||||
|
||||
Menu {
|
||||
title: "Avatar"
|
||||
}
|
||||
|
||||
Menu {
|
||||
title: "Display"
|
||||
ExclusiveGroup { id: displayMode }
|
||||
Menu {
|
||||
title: "More Stuff"
|
||||
|
||||
Menu { title: "Empty" }
|
||||
|
||||
MenuItem {
|
||||
text: "Do Nothing"
|
||||
onTriggered: console.log("Nothing")
|
||||
}
|
||||
}
|
||||
MenuItem {
|
||||
text: "Oculus"
|
||||
exclusiveGroup: displayMode
|
||||
checkable: true
|
||||
}
|
||||
MenuItem {
|
||||
text: "OpenVR"
|
||||
exclusiveGroup: displayMode
|
||||
checkable: true
|
||||
}
|
||||
MenuItem {
|
||||
text: "OSVR"
|
||||
exclusiveGroup: displayMode
|
||||
checkable: true
|
||||
}
|
||||
MenuItem {
|
||||
text: "2D Screen"
|
||||
exclusiveGroup: displayMode
|
||||
checkable: true
|
||||
checked: true
|
||||
}
|
||||
MenuItem {
|
||||
text: "3D Screen (Active)"
|
||||
exclusiveGroup: displayMode
|
||||
checkable: true
|
||||
}
|
||||
MenuItem {
|
||||
text: "3D Screen (Passive)"
|
||||
exclusiveGroup: displayMode
|
||||
checkable: true
|
||||
}
|
||||
}
|
||||
|
||||
Menu {
|
||||
title: "View"
|
||||
Menu {
|
||||
title: "Camera Mode"
|
||||
ExclusiveGroup { id: cameraMode }
|
||||
MenuItem {
|
||||
exclusiveGroup: cameraMode
|
||||
text: "First Person";
|
||||
onTriggered: console.log(text + " checked " + checked)
|
||||
checkable: true
|
||||
checked: true
|
||||
}
|
||||
MenuItem {
|
||||
exclusiveGroup: cameraMode
|
||||
text: "Third Person";
|
||||
onTriggered: console.log(text)
|
||||
checkable: true
|
||||
}
|
||||
MenuItem {
|
||||
exclusiveGroup: cameraMode
|
||||
text: "Independent Mode";
|
||||
onTriggered: console.log(text)
|
||||
checkable: true
|
||||
}
|
||||
MenuItem {
|
||||
exclusiveGroup: cameraMode
|
||||
text: "Entity Mode";
|
||||
onTriggered: console.log(text)
|
||||
enabled: false
|
||||
checkable: true
|
||||
}
|
||||
MenuItem {
|
||||
exclusiveGroup: cameraMode
|
||||
text: "Fullscreen Mirror";
|
||||
onTriggered: console.log(text)
|
||||
checkable: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Menu {
|
||||
title: "Edit"
|
||||
|
||||
MenuItem {
|
||||
text: "Undo"
|
||||
shortcut: "Ctrl+Z"
|
||||
enabled: false
|
||||
onTriggered: console.log(text)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "Redo"
|
||||
shortcut: "Ctrl+Shift+Z"
|
||||
enabled: false
|
||||
onTriggered: console.log(text)
|
||||
}
|
||||
|
||||
MenuSeparator { }
|
||||
|
||||
MenuItem {
|
||||
text: "Cut"
|
||||
shortcut: "Ctrl+X"
|
||||
onTriggered: console.log(text)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "Copy"
|
||||
shortcut: "Ctrl+C"
|
||||
onTriggered: console.log(text)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "Paste"
|
||||
shortcut: "Ctrl+V"
|
||||
visible: false
|
||||
onTriggered: console.log("Paste")
|
||||
}
|
||||
}
|
||||
|
||||
Menu {
|
||||
title: "Navigate"
|
||||
}
|
||||
|
||||
Menu {
|
||||
title: "Market"
|
||||
}
|
||||
|
||||
Menu {
|
||||
title: "Settings"
|
||||
}
|
||||
|
||||
Menu {
|
||||
title: "Developer"
|
||||
}
|
||||
|
||||
Menu {
|
||||
title: "Quit"
|
||||
}
|
||||
|
||||
Menu {
|
||||
title: "File"
|
||||
|
||||
Action {
|
||||
id: login
|
||||
text: "Login"
|
||||
}
|
||||
|
||||
Action {
|
||||
id: quit
|
||||
text: "Quit"
|
||||
shortcut: "Ctrl+Q"
|
||||
onTriggered: Qt.quit();
|
||||
}
|
||||
|
||||
MenuItem { action: quit }
|
||||
MenuItem { action: login }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -4,90 +4,133 @@ import "."
|
|||
import "../controls"
|
||||
|
||||
Frame {
|
||||
id: root
|
||||
anchors { margins: -16; topMargin: parent.closable ? -32 : -16; }
|
||||
id: frame
|
||||
// The frame fills the parent, which should be the size of the content.
|
||||
// The frame decorations use negative anchor margins to extend beyond
|
||||
anchors.fill: parent
|
||||
// Size of the controls
|
||||
readonly property real iconSize: 24;
|
||||
|
||||
// Convenience accessor for the window
|
||||
property alias window: frame.parent
|
||||
// FIXME needed?
|
||||
property alias decoration: decoration
|
||||
|
||||
MouseArea {
|
||||
id: controlsArea
|
||||
anchors.fill: desktopControls
|
||||
hoverEnabled: true
|
||||
drag.target: root.parent
|
||||
propagateComposedEvents: true
|
||||
onClicked: {
|
||||
root.raise()
|
||||
mouse.accepted = false;
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: sizeDrag
|
||||
enabled: root.parent.resizable
|
||||
property int startX
|
||||
property int startY
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
width: 16
|
||||
height: 16
|
||||
z: 1000
|
||||
hoverEnabled: true
|
||||
onPressed: {
|
||||
startX = mouseX
|
||||
startY = mouseY
|
||||
}
|
||||
onPositionChanged: {
|
||||
if (pressed) {
|
||||
root.deltaSize((mouseX - startX), (mouseY - startY))
|
||||
startX = mouseX
|
||||
startY = mouseY
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
anchors { margins: -4 }
|
||||
visible: !decoration.visible
|
||||
anchors.fill: parent;
|
||||
color: "#7f7f7f7f";
|
||||
radius: 3;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: desktopControls
|
||||
// FIXME this doesn't work as expected
|
||||
visible: root.parent.showFrame
|
||||
id: decoration
|
||||
anchors { margins: -iconSize; topMargin: -iconSize * (window.closable ? 2 : 1); }
|
||||
visible: window.activator.containsMouse
|
||||
anchors.fill: parent;
|
||||
color: "#7f7f7f7f";
|
||||
radius: 3;
|
||||
|
||||
// Allow dragging of the window
|
||||
MouseArea {
|
||||
id: dragMouseArea
|
||||
anchors.fill: parent
|
||||
drag {
|
||||
target: window
|
||||
// minimumX: (decoration.width - window.width) * -1
|
||||
// minimumY: 0
|
||||
// maximumX: (window.parent.width - window.width) - 2 * (decoration.width - window.width)
|
||||
// maximumY: (window.parent.height - window.height) - 2 * (decoration.height - window.height)
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: controlsRow
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: 4
|
||||
anchors.topMargin: 4
|
||||
spacing: 4
|
||||
anchors.rightMargin: iconSize
|
||||
anchors.topMargin: iconSize / 2
|
||||
spacing: iconSize / 4
|
||||
FontAwesome {
|
||||
visible: false
|
||||
text: "\uf08d"
|
||||
style: Text.Outline; styleColor: "white"
|
||||
size: 16
|
||||
rotation: !root.parent ? 90 : root.parent.pinned ? 0 : 90
|
||||
color: root.pinned ? "red" : "black"
|
||||
size: frame.iconSize
|
||||
rotation: !frame.parent ? 90 : frame.parent.pinned ? 0 : 90
|
||||
color: frame.pinned ? "red" : "black"
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
propagateComposedEvents: true
|
||||
onClicked: { root.pin(); mouse.accepted = false; }
|
||||
onClicked: { frame.pin(); mouse.accepted = false; }
|
||||
}
|
||||
}
|
||||
|
||||
FontAwesome {
|
||||
visible: root.parent.closable
|
||||
visible: window.closable
|
||||
text: closeClickArea.containsMouse ? "\uf057" : "\uf05c"
|
||||
style: Text.Outline;
|
||||
styleColor: "white"
|
||||
color: closeClickArea.containsMouse ? "red" : "black"
|
||||
size: 16
|
||||
size: frame.iconSize
|
||||
MouseArea {
|
||||
id: closeClickArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: root.close();
|
||||
onClicked: frame.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allow sizing of the window
|
||||
// FIXME works in native QML, doesn't work in Interface
|
||||
MouseArea {
|
||||
id: sizeDrag
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
|
||||
anchors {
|
||||
right: decoration.right;
|
||||
bottom: decoration.bottom
|
||||
bottomMargin: iconSize * 2
|
||||
}
|
||||
property vector2d pressOrigin
|
||||
property vector2d sizeOrigin
|
||||
property bool hid: false
|
||||
onPressed: {
|
||||
console.log("Pressed on size")
|
||||
pressOrigin = Qt.vector2d(mouseX, mouseY)
|
||||
sizeOrigin = Qt.vector2d(window.content.width, window.content.height)
|
||||
hid = false;
|
||||
}
|
||||
onReleased: {
|
||||
if (hid) {
|
||||
window.content.visible = true
|
||||
hid = false;
|
||||
}
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
if (pressed) {
|
||||
if (window.content.visible) {
|
||||
window.content.visible = false;
|
||||
hid = true;
|
||||
}
|
||||
var delta = Qt.vector2d(mouseX, mouseY).minus(pressOrigin);
|
||||
frame.deltaSize(delta.x, delta.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FontAwesome {
|
||||
visible: window.resizable
|
||||
rotation: -45
|
||||
anchors { centerIn: sizeDrag }
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: "\uf07d"
|
||||
size: iconSize / 3 * 2
|
||||
style: Text.Outline; styleColor: "white"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,78 +6,75 @@ import "../styles"
|
|||
|
||||
FocusScope {
|
||||
id: window
|
||||
objectName: "topLevelWindow"
|
||||
HifiConstants { id: hifi }
|
||||
implicitHeight: frame.height
|
||||
implicitWidth: frame.width
|
||||
// The Window size is the size of the content, while the frame
|
||||
// decorations can extend outside it. Windows should generally not be
|
||||
// given explicit height / width, but rather be allowed to conform to
|
||||
// their content
|
||||
implicitHeight: content.height
|
||||
implicitWidth: content.width
|
||||
|
||||
property bool topLevelWindow: true
|
||||
property string title
|
||||
property bool showFrame: true
|
||||
// Should the window include a close control?
|
||||
property bool closable: true
|
||||
property bool destroyOnInvisible: false
|
||||
// Should hitting the close button hide or destroy the window?
|
||||
property bool destroyOnCloseButton: true
|
||||
property bool pinned: false
|
||||
// Should hiding the window destroy it or just hide it?
|
||||
property bool destroyOnInvisible: false
|
||||
// FIXME support for pinned / unpinned pending full design
|
||||
// property bool pinnable: false
|
||||
// property bool pinned: false
|
||||
property bool resizable: false
|
||||
property real minX: 320
|
||||
property real minY: 240;
|
||||
property vector2d minSize: Qt.vector2d(100, 100)
|
||||
property vector2d maxSize: Qt.vector2d(1280, 720)
|
||||
|
||||
// The content to place inside the window, determined by the client
|
||||
default property var content
|
||||
property var frame: DefaultFrame { anchors.fill: content }
|
||||
property var blur: FastBlur { anchors.fill: content; source: content; visible: false; radius: 0}
|
||||
//property var hoverDetector: MouseArea { anchors.fill: frame; hoverEnabled: true; propagateComposedEvents: true; }
|
||||
//property bool mouseInWindow: hoverDetector.containsMouse
|
||||
children: [ frame, content, blur ]
|
||||
signal windowDestroyed();
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
property vector2d minPosition: Qt.vector2d(0, 0);
|
||||
property vector2d maxPosition: Qt.vector2d(100, 100);
|
||||
|
||||
function clamp(value, min, max) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
onContentChanged: {
|
||||
if (content) {
|
||||
content.anchors.fill = window
|
||||
}
|
||||
|
||||
function updateParentRect() {
|
||||
// if (!frame) { return; }
|
||||
// console.log(window.parent.width);
|
||||
// console.log(frame.width);
|
||||
// minPosition = Qt.vector2d(-frame.anchors.leftMargin, -frame.anchors.topMargin);
|
||||
// maxPosition = Qt.vector2d(
|
||||
// Math.max(minPosition.x, Desktop.width - frame.width + minPosition.x),
|
||||
// Math.max(minPosition.y, Desktop.height - frame.height + minPosition.y))
|
||||
// console.log(maxPosition);
|
||||
}
|
||||
|
||||
function keepOnScreen() {
|
||||
//window.x = clamp(x, minPosition.x, maxPosition.x);
|
||||
//window.y = clamp(y, minPosition.y, maxPosition.y);
|
||||
}
|
||||
|
||||
onMinPositionChanged: keepOnScreen();
|
||||
onMaxPositionChanged: keepOnScreen();
|
||||
}
|
||||
|
||||
// Default to a standard frame. Can be overriden to provide custom
|
||||
// frame styles, like a full desktop frame to simulate a modal window
|
||||
property var frame: DefaultFrame {
|
||||
z: -1
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
// This mouse area serves to raise the window. To function, it must live
|
||||
// in the window and have a higher Z-order than the content, but follow
|
||||
// the position and size of frame decoration
|
||||
property var activator: MouseArea {
|
||||
width: frame.decoration.width
|
||||
height: frame.decoration.height
|
||||
x: frame.decoration.anchors.margins
|
||||
y: frame.decoration.anchors.topMargin
|
||||
propagateComposedEvents: true
|
||||
hoverEnabled: true
|
||||
onPressed: { window.raise(); mouse.accepted = false; }
|
||||
// Debugging visualization
|
||||
// Rectangle { anchors.fill:parent; color: "#7f00ff00" }
|
||||
}
|
||||
|
||||
children: [ frame, content, activator ]
|
||||
signal windowDestroyed();
|
||||
|
||||
Component.onCompleted: {
|
||||
d.updateParentRect();
|
||||
fadeTargetProperty = visible ? 1.0 : 0.0
|
||||
raise();
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
content.destroy();
|
||||
console.log("Destroyed " + window);
|
||||
windowDestroyed();
|
||||
}
|
||||
|
||||
onParentChanged: {
|
||||
d.updateParentRect();
|
||||
raise();
|
||||
}
|
||||
|
||||
onFrameChanged: d.updateParentRect();
|
||||
onWidthChanged: d.updateParentRect();
|
||||
onHeightChanged: d.updateParentRect();
|
||||
onXChanged: d.keepOnScreen();
|
||||
onYChanged: d.keepOnScreen();
|
||||
onParentChanged: raise();
|
||||
|
||||
Connections {
|
||||
target: frame
|
||||
|
@ -85,15 +82,15 @@ FocusScope {
|
|||
onClose: window.close();
|
||||
onPin: window.pin();
|
||||
onDeltaSize: {
|
||||
console.log("deltaSize")
|
||||
content.width = Math.max(content.width + dx, minX)
|
||||
content.height = Math.max(content.height + dy, minY)
|
||||
var newSize = Qt.vector2d(content.width + dx, content.height + dy);
|
||||
newSize = clampVector(newSize, minSize, maxSize);
|
||||
window.width = newSize.x
|
||||
window.height = newSize.y
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function raise() {
|
||||
if (enabled && parent) {
|
||||
if (visible && parent) {
|
||||
Desktop.raise(window)
|
||||
if (!focus) {
|
||||
focus = true;
|
||||
|
@ -102,48 +99,58 @@ FocusScope {
|
|||
}
|
||||
|
||||
function pin() {
|
||||
pinned = ! pinned
|
||||
// pinned = ! pinned
|
||||
}
|
||||
|
||||
// our close function performs the same way as the OffscreenUI class:
|
||||
// don't do anything but manipulate the enabled flag and let the other
|
||||
// don't do anything but manipulate the targetVisible flag and let the other
|
||||
// mechanisms decide if the window should be destroyed after the close
|
||||
// animation completes
|
||||
function close() {
|
||||
console.log("Closing " + window)
|
||||
if (destroyOnCloseButton) {
|
||||
destroyOnInvisible = true
|
||||
}
|
||||
enabled = false;
|
||||
visible = false;
|
||||
}
|
||||
|
||||
onEnabledChanged: {
|
||||
if (!enabled) {
|
||||
if (blur) {
|
||||
blur.visible = true;
|
||||
}
|
||||
if (content) {
|
||||
content.visible = false;
|
||||
}
|
||||
}
|
||||
//
|
||||
// Enable window visibility transitions
|
||||
//
|
||||
|
||||
opacity = enabled ? 1.0 : 0.0
|
||||
// If the dialog is initially invisible, setting opacity doesn't
|
||||
// trigger making it visible.
|
||||
if (enabled) {
|
||||
visible = true;
|
||||
raise();
|
||||
// The target property to animate, usually scale or opacity
|
||||
property alias fadeTargetProperty: window.opacity
|
||||
// always start the property at 0 to enable fade in on creation
|
||||
opacity: 0
|
||||
|
||||
// Some dialogs should be destroyed when they become
|
||||
// invisible, so handle that
|
||||
onVisibleChanged: {
|
||||
// If someone directly set the visibility to false
|
||||
// toggle it back on and use the targetVisible flag to transition
|
||||
// via fading.
|
||||
if ((!visible && fadeTargetProperty != 0.0) || (visible && fadeTargetProperty == 0.0)) {
|
||||
var target = visible;
|
||||
visible = !visible;
|
||||
fadeTargetProperty = target ? 1.0 : 0.0;
|
||||
return;
|
||||
}
|
||||
if (!visible && destroyOnInvisible) {
|
||||
console.log("Destroying " + window);
|
||||
destroy();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The offscreen UI will enable an object, rather than manipulating it's
|
||||
// visibility, so that we can do animations in both directions. Because
|
||||
// visibility and enabled are boolean flags, they cannot be animated. So when
|
||||
// enabled is change, we modify a property that can be animated, like scale or
|
||||
// opacity, and then when the target animation value is reached, we can
|
||||
// modify the visibility
|
||||
// visibility is a boolean flags, it cannot be animated. So when
|
||||
// targetVisible is changed, we modify a property that can be animated,
|
||||
// like scale or opacity, and then when the target animation value is reached,
|
||||
// we can modify the visibility
|
||||
|
||||
// The actual animator
|
||||
Behavior on opacity {
|
||||
Behavior on fadeTargetProperty {
|
||||
NumberAnimation {
|
||||
duration: hifi.effects.fadeInDuration
|
||||
easing.type: Easing.OutCubic
|
||||
|
@ -151,31 +158,31 @@ FocusScope {
|
|||
}
|
||||
|
||||
// Once we're transparent, disable the dialog's visibility
|
||||
onOpacityChanged: {
|
||||
visible = (opacity != 0.0);
|
||||
if (opacity == 1.0) {
|
||||
content.visible = true;
|
||||
blur.visible = false;
|
||||
}
|
||||
onFadeTargetPropertyChanged: {
|
||||
visible = (fadeTargetProperty != 0.0);
|
||||
}
|
||||
|
||||
// Some dialogs should be destroyed when they become
|
||||
// invisible, so handle that
|
||||
onVisibleChanged: {
|
||||
if (!visible && destroyOnInvisible) {
|
||||
console.log("Destroying " + window);
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
switch(event.key) {
|
||||
case Qt.Key_W:
|
||||
if (event.modifiers == Qt.ControlModifier) {
|
||||
if (event.modifiers === Qt.ControlModifier) {
|
||||
event.accepted = true
|
||||
enabled = false
|
||||
visible = false
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function clamp(value, min, max) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
function clampVector(value, min, max) {
|
||||
return Qt.vector2d(
|
||||
clamp(value.x, min.x, max.x),
|
||||
clamp(value.y, min.y, max.y))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -395,7 +395,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
QApplication(argc, argv),
|
||||
_dependencyManagerIsSetup(setupEssentials(argc, argv)),
|
||||
_window(new MainWindow(desktop())),
|
||||
_toolWindow(NULL),
|
||||
_undoStackScriptingInterface(&_undoStack),
|
||||
_frameCount(0),
|
||||
_fps(60.0f),
|
||||
|
@ -678,10 +677,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
_renderEngine->addTask(make_shared<RenderDeferredTask>());
|
||||
_renderEngine->registerScene(_main3DScene);
|
||||
|
||||
_toolWindow = new ToolWindow();
|
||||
_toolWindow->setWindowFlags((_toolWindow->windowFlags() | Qt::WindowStaysOnTopHint) & ~Qt::WindowMinimizeButtonHint);
|
||||
_toolWindow->setWindowTitle("Tools");
|
||||
|
||||
_offscreenContext->makeCurrent();
|
||||
|
||||
// Tell our entity edit sender about our known jurisdictions
|
||||
|
@ -4751,7 +4746,7 @@ void Application::showFriendsWindow() {
|
|||
auto webWindowClass = _window->findChildren<WebWindowClass>(FRIENDS_WINDOW_OBJECT_NAME);
|
||||
if (webWindowClass.empty()) {
|
||||
auto friendsWindow = new WebWindowClass(FRIENDS_WINDOW_TITLE, FRIENDS_WINDOW_URL, FRIENDS_WINDOW_WIDTH,
|
||||
FRIENDS_WINDOW_HEIGHT, false);
|
||||
FRIENDS_WINDOW_HEIGHT);
|
||||
friendsWindow->setParent(_window);
|
||||
friendsWindow->setObjectName(FRIENDS_WINDOW_OBJECT_NAME);
|
||||
connect(friendsWindow, &WebWindowClass::closed, &WebWindowClass::deleteLater);
|
||||
|
|
|
@ -62,7 +62,6 @@
|
|||
#include "ui/OverlayConductor.h"
|
||||
#include "ui/overlays/Overlays.h"
|
||||
#include "ui/SnapshotShareDialog.h"
|
||||
#include "ui/ToolWindow.h"
|
||||
#include "UndoStackScriptingInterface.h"
|
||||
|
||||
class OffscreenGLCanvas;
|
||||
|
@ -164,8 +163,6 @@ public:
|
|||
|
||||
NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; }
|
||||
|
||||
ToolWindow* getToolWindow() { return _toolWindow ; }
|
||||
|
||||
virtual controller::ScriptingInterface* getControllerScriptingInterface() { return _controllerScriptingInterface; }
|
||||
virtual void registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine);
|
||||
|
||||
|
@ -400,8 +397,6 @@ private:
|
|||
|
||||
MainWindow* _window;
|
||||
|
||||
ToolWindow* _toolWindow;
|
||||
|
||||
QUndoStack _undoStack;
|
||||
UndoStackScriptingInterface _undoStackScriptingInterface;
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QApplication>
|
||||
#include <QMainWindow>
|
||||
|
@ -35,48 +36,25 @@ void ScriptEventBridge::emitScriptEvent(const QString& data) {
|
|||
emit scriptEventReceived(data);
|
||||
}
|
||||
|
||||
WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height, bool isToolWindow)
|
||||
: QObject(NULL),
|
||||
_eventBridge(new ScriptEventBridge(this)),
|
||||
_isToolWindow(isToolWindow) {
|
||||
WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height)
|
||||
: QObject(NULL), _eventBridge(new ScriptEventBridge(this)) {
|
||||
auto dialogWidget = new QDialog(qApp->getWindow(), Qt::Window);
|
||||
dialogWidget->setWindowTitle(title);
|
||||
dialogWidget->resize(width, height);
|
||||
dialogWidget->installEventFilter(this);
|
||||
connect(dialogWidget, &QDialog::finished, this, &WebWindowClass::hasClosed);
|
||||
|
||||
if (_isToolWindow) {
|
||||
ToolWindow* toolWindow = qApp->getToolWindow();
|
||||
auto layout = new QVBoxLayout(dialogWidget);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
dialogWidget->setLayout(layout);
|
||||
|
||||
auto dockWidget = new QDockWidget(title, toolWindow);
|
||||
dockWidget->setFeatures(QDockWidget::DockWidgetMovable);
|
||||
connect(dockWidget, &QDockWidget::visibilityChanged, this, &WebWindowClass::visibilityChanged);
|
||||
_webView = new QWebView(dialogWidget);
|
||||
|
||||
_webView = new QWebView(dockWidget);
|
||||
addEventBridgeToWindowObject();
|
||||
layout->addWidget(_webView);
|
||||
|
||||
dockWidget->setWidget(_webView);
|
||||
addEventBridgeToWindowObject();
|
||||
|
||||
auto titleWidget = new QWidget(dockWidget);
|
||||
dockWidget->setTitleBarWidget(titleWidget);
|
||||
|
||||
toolWindow->addDockWidget(Qt::TopDockWidgetArea, dockWidget, Qt::Horizontal);
|
||||
|
||||
_windowWidget = dockWidget;
|
||||
} else {
|
||||
auto dialogWidget = new QDialog(qApp->getWindow(), Qt::Window);
|
||||
dialogWidget->setWindowTitle(title);
|
||||
dialogWidget->resize(width, height);
|
||||
dialogWidget->installEventFilter(this);
|
||||
connect(dialogWidget, &QDialog::finished, this, &WebWindowClass::hasClosed);
|
||||
|
||||
auto layout = new QVBoxLayout(dialogWidget);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
dialogWidget->setLayout(layout);
|
||||
|
||||
_webView = new QWebView(dialogWidget);
|
||||
|
||||
layout->addWidget(_webView);
|
||||
|
||||
addEventBridgeToWindowObject();
|
||||
|
||||
_windowWidget = dialogWidget;
|
||||
}
|
||||
_windowWidget = dialogWidget;
|
||||
|
||||
auto style = QStyleFactory::create("fusion");
|
||||
if (style) {
|
||||
|
@ -121,13 +99,8 @@ void WebWindowClass::addEventBridgeToWindowObject() {
|
|||
|
||||
void WebWindowClass::setVisible(bool visible) {
|
||||
if (visible) {
|
||||
if (_isToolWindow) {
|
||||
QMetaObject::invokeMethod(
|
||||
qApp->getToolWindow(), "setVisible", Qt::AutoConnection, Q_ARG(bool, visible));
|
||||
} else {
|
||||
QMetaObject::invokeMethod(_windowWidget, "showNormal", Qt::AutoConnection);
|
||||
QMetaObject::invokeMethod(_windowWidget, "raise", Qt::AutoConnection);
|
||||
}
|
||||
QMetaObject::invokeMethod(_windowWidget, "showNormal", Qt::AutoConnection);
|
||||
QMetaObject::invokeMethod(_windowWidget, "raise", Qt::AutoConnection);
|
||||
}
|
||||
QMetaObject::invokeMethod(_windowWidget, "setVisible", Qt::AutoConnection, Q_ARG(bool, visible));
|
||||
}
|
||||
|
@ -182,13 +155,18 @@ void WebWindowClass::raise() {
|
|||
QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
|
||||
WebWindowClass* retVal;
|
||||
QString file = context->argument(0).toString();
|
||||
if (context->argument(4).toBool()) {
|
||||
qWarning() << "ToolWindow views with WebWindow are no longer supported. Use OverlayWebWindow instead";
|
||||
return QScriptValue();
|
||||
} else {
|
||||
qWarning() << "WebWindow views are deprecated. Use OverlayWebWindow instead";
|
||||
}
|
||||
QMetaObject::invokeMethod(DependencyManager::get<WindowScriptingInterface>().data(), "doCreateWebWindow", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(WebWindowClass*, retVal),
|
||||
Q_ARG(const QString&, file),
|
||||
Q_ARG(QString, context->argument(1).toString()),
|
||||
Q_ARG(int, context->argument(2).toInteger()),
|
||||
Q_ARG(int, context->argument(3).toInteger()),
|
||||
Q_ARG(bool, context->argument(4).toBool()));
|
||||
Q_ARG(int, context->argument(3).toInteger()));
|
||||
|
||||
connect(engine, &QScriptEngine::destroyed, retVal, &WebWindowClass::deleteLater);
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ class WebWindowClass : public QObject {
|
|||
Q_PROPERTY(QSizeF size READ getSize WRITE setSize);
|
||||
|
||||
public:
|
||||
WebWindowClass(const QString& title, const QString& url, int width, int height, bool isToolWindow = false);
|
||||
WebWindowClass(const QString& title, const QString& url, int width, int height);
|
||||
~WebWindowClass();
|
||||
|
||||
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
|
||||
|
@ -75,7 +75,6 @@ private:
|
|||
QWidget* _windowWidget;
|
||||
QWebView* _webView;
|
||||
ScriptEventBridge* _eventBridge;
|
||||
bool _isToolWindow;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "Menu.h"
|
||||
#include "OffscreenUi.h"
|
||||
#include "ui/ModelsBrowser.h"
|
||||
#include "WebWindowClass.h"
|
||||
|
||||
#include "WindowScriptingInterface.h"
|
||||
|
||||
|
@ -37,8 +38,8 @@ WindowScriptingInterface::WindowScriptingInterface() :
|
|||
connect(qApp, &Application::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused);
|
||||
}
|
||||
|
||||
WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height, bool isToolWindow) {
|
||||
return new WebWindowClass(title, url, width, height, isToolWindow);
|
||||
WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height) {
|
||||
return new WebWindowClass(title, url, width, height);
|
||||
}
|
||||
|
||||
QScriptValue WindowScriptingInterface::hasFocus() {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#include <QComboBox>
|
||||
#include <QLineEdit>
|
||||
|
||||
#include "WebWindowClass.h"
|
||||
class WebWindowClass;
|
||||
|
||||
class WindowScriptingInterface : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
|
@ -82,8 +82,8 @@ private slots:
|
|||
void nonBlockingFormAccepted() { _nonBlockingFormActive = false; _formResult = QDialog::Accepted; emit nonBlockingFormClosed(); }
|
||||
void nonBlockingFormRejected() { _nonBlockingFormActive = false; _formResult = QDialog::Rejected; emit nonBlockingFormClosed(); }
|
||||
|
||||
WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height, bool isToolWindow);
|
||||
|
||||
WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height);
|
||||
|
||||
private:
|
||||
QString jsRegExp2QtRegExp(QString string);
|
||||
QDialog* createForm(const QString& title, QScriptValue form);
|
||||
|
|
|
@ -148,8 +148,7 @@ void DialogsManager::lodTools() {
|
|||
}
|
||||
|
||||
void DialogsManager::toggleToolWindow() {
|
||||
QMainWindow* toolWindow = qApp->getToolWindow();
|
||||
toolWindow->setVisible(!toolWindow->isVisible());
|
||||
DependencyManager::get<OffscreenUi>()->toggleToolWindow();
|
||||
}
|
||||
|
||||
void DialogsManager::hmdTools(bool showTools) {
|
||||
|
|
|
@ -79,9 +79,6 @@ HMDToolsDialog::HMDToolsDialog(QWidget* parent) :
|
|||
// what screens we're allowed on
|
||||
watchWindow(windowHandle());
|
||||
auto dialogsManager = DependencyManager::get<DialogsManager>();
|
||||
if (qApp->getToolWindow()) {
|
||||
watchWindow(qApp->getToolWindow()->windowHandle());
|
||||
}
|
||||
if (dialogsManager->getBandwidthDialog()) {
|
||||
watchWindow(dialogsManager->getBandwidthDialog()->windowHandle());
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "Snapshot.h"
|
||||
#include "UserActivityLogger.h"
|
||||
#include "UIUtil.h"
|
||||
#include "scripting/WebWindowClass.h"
|
||||
|
||||
|
||||
const int PREFERENCES_HEIGHT_PADDING = 20;
|
||||
|
@ -135,7 +136,7 @@ void PreferencesDialog::openFullAvatarModelBrowser() {
|
|||
const auto WIDTH = 900;
|
||||
const auto HEIGHT = 700;
|
||||
if (!_marketplaceWindow) {
|
||||
_marketplaceWindow = new WebWindowClass("Marketplace", MARKETPLACE_URL, WIDTH, HEIGHT, false);
|
||||
_marketplaceWindow = new WebWindowClass("Marketplace", MARKETPLACE_URL, WIDTH, HEIGHT);
|
||||
}
|
||||
_marketplaceWindow->setVisible(true);
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#include <QDialog>
|
||||
#include <QString>
|
||||
|
||||
#include "scripting/WebWindowClass.h"
|
||||
class WebWindowClass;
|
||||
|
||||
class PreferencesDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
@ -41,7 +41,7 @@ private:
|
|||
|
||||
QString _displayNameString;
|
||||
|
||||
WebWindowClass* _marketplaceWindow = NULL;
|
||||
WebWindowClass* _marketplaceWindow { nullptr };
|
||||
|
||||
private slots:
|
||||
void accept();
|
||||
|
|
|
@ -1,145 +0,0 @@
|
|||
//
|
||||
// ToolWindow.cpp
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Ryan Huffman on 11/13/14.
|
||||
// Copyright 2014 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 "Application.h"
|
||||
#include "MainWindow.h"
|
||||
#include "ToolWindow.h"
|
||||
#include "UIUtil.h"
|
||||
|
||||
const int DEFAULT_WIDTH = 300;
|
||||
|
||||
ToolWindow::ToolWindow(QWidget* parent) :
|
||||
QMainWindow(parent),
|
||||
_selfHidden(false),
|
||||
_hasShown(false),
|
||||
_lastGeometry() {
|
||||
|
||||
setTabPosition(Qt::TopDockWidgetArea, QTabWidget::TabPosition::North);
|
||||
|
||||
# ifndef Q_OS_LINUX
|
||||
setDockOptions(QMainWindow::ForceTabbedDocks);
|
||||
# endif
|
||||
qApp->installEventFilter(this);
|
||||
}
|
||||
|
||||
bool ToolWindow::event(QEvent* event) {
|
||||
QEvent::Type type = event->type();
|
||||
if (type == QEvent::Show) {
|
||||
|
||||
if (!_hasShown) {
|
||||
_hasShown = true;
|
||||
|
||||
QMainWindow* mainWindow = qApp->getWindow();
|
||||
QRect mainGeometry = mainWindow->geometry();
|
||||
|
||||
int titleBarHeight = UIUtil::getWindowTitleBarHeight(this);
|
||||
int topMargin = titleBarHeight;
|
||||
|
||||
_lastGeometry = QRect(mainGeometry.topLeft().x(), mainGeometry.topLeft().y() + topMargin,
|
||||
DEFAULT_WIDTH, mainGeometry.height() - topMargin);
|
||||
}
|
||||
|
||||
setGeometry(_lastGeometry);
|
||||
|
||||
return true;
|
||||
} else if (type == QEvent::Hide) {
|
||||
_lastGeometry = geometry();
|
||||
return true;
|
||||
}
|
||||
|
||||
return QMainWindow::event(event);
|
||||
}
|
||||
|
||||
bool ToolWindow::eventFilter(QObject* sender, QEvent* event) {
|
||||
# ifndef Q_OS_LINUX
|
||||
switch (event->type()) {
|
||||
case QEvent::WindowStateChange:
|
||||
if (qApp->getWindow()->isMinimized()) {
|
||||
// If we are already visible, we are self-hiding
|
||||
_selfHidden = isVisible();
|
||||
setVisible(false);
|
||||
} else {
|
||||
if (_selfHidden) {
|
||||
setVisible(true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case QEvent::ApplicationDeactivate:
|
||||
_selfHidden = isVisible();
|
||||
setVisible(false);
|
||||
break;
|
||||
case QEvent::ApplicationActivate:
|
||||
if (_selfHidden) {
|
||||
setVisible(true);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
# endif
|
||||
return false;
|
||||
}
|
||||
|
||||
void ToolWindow::onChildVisibilityUpdated(bool visible) {
|
||||
if (!_selfHidden && visible) {
|
||||
setVisible(true);
|
||||
} else {
|
||||
bool hasVisible = false;
|
||||
QList<QDockWidget*> dockWidgets = findChildren<QDockWidget*>();
|
||||
for (int i = 0; i < dockWidgets.count(); i++) {
|
||||
if (dockWidgets[i]->isVisible()) {
|
||||
hasVisible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If a child was hidden and we don't have any children still visible, hide ourself.
|
||||
if (!hasVisible) {
|
||||
setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ToolWindow::addDockWidget(Qt::DockWidgetArea area, QDockWidget* dockWidget) {
|
||||
QList<QDockWidget*> dockWidgets = findChildren<QDockWidget*>();
|
||||
|
||||
QMainWindow::addDockWidget(area, dockWidget);
|
||||
|
||||
// We want to force tabbing, so retabify all of our widgets.
|
||||
QDockWidget* lastDockWidget = dockWidget;
|
||||
|
||||
foreach (QDockWidget* nextDockWidget, dockWidgets) {
|
||||
tabifyDockWidget(lastDockWidget, nextDockWidget);
|
||||
lastDockWidget = nextDockWidget;
|
||||
}
|
||||
|
||||
connect(dockWidget, &QDockWidget::visibilityChanged, this, &ToolWindow::onChildVisibilityUpdated);
|
||||
}
|
||||
|
||||
void ToolWindow::addDockWidget(Qt::DockWidgetArea area, QDockWidget* dockWidget, Qt::Orientation orientation) {
|
||||
QList<QDockWidget*> dockWidgets = findChildren<QDockWidget*>();
|
||||
|
||||
QMainWindow::addDockWidget(area, dockWidget, orientation);
|
||||
|
||||
QDockWidget* lastDockWidget = dockWidget;
|
||||
|
||||
foreach(QDockWidget* nextDockWidget, dockWidgets) {
|
||||
tabifyDockWidget(lastDockWidget, nextDockWidget);
|
||||
lastDockWidget = nextDockWidget;
|
||||
}
|
||||
|
||||
connect(dockWidget, &QDockWidget::visibilityChanged, this, &ToolWindow::onChildVisibilityUpdated);
|
||||
}
|
||||
|
||||
void ToolWindow::removeDockWidget(QDockWidget* dockWidget) {
|
||||
QMainWindow::removeDockWidget(dockWidget);
|
||||
|
||||
disconnect(dockWidget, &QDockWidget::visibilityChanged, this, &ToolWindow::onChildVisibilityUpdated);
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
//
|
||||
// ToolWindow.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Ryan Huffman on 11/13/14.
|
||||
// Copyright 2014 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_ToolWindow_h
|
||||
#define hifi_ToolWindow_h
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <QEvent>
|
||||
#include <QMainWindow>
|
||||
#include <QRect>
|
||||
#include <QWidget>
|
||||
|
||||
class ToolWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ToolWindow(QWidget* parent = NULL);
|
||||
|
||||
virtual bool event(QEvent* event);
|
||||
virtual void addDockWidget(Qt::DockWidgetArea area, QDockWidget* dockWidget);
|
||||
virtual void addDockWidget(Qt::DockWidgetArea area, QDockWidget* dockWidget, Qt::Orientation orientation);
|
||||
virtual void removeDockWidget(QDockWidget* dockWidget);
|
||||
|
||||
virtual bool eventFilter(QObject* sender, QEvent* event);
|
||||
|
||||
public slots:
|
||||
void onChildVisibilityUpdated(bool visible);
|
||||
|
||||
|
||||
private:
|
||||
// Indicates whether this window was hidden by itself (because the main window lost focus).
|
||||
bool _selfHidden;
|
||||
bool _hasShown;
|
||||
QRect _lastGeometry;
|
||||
};
|
||||
|
||||
#endif // hifi_ToolWindow_h
|
|
@ -340,12 +340,25 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
|||
_qmlComponent = new QQmlComponent(_qmlEngine);
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::resize(const QSize& newSize) {
|
||||
void OffscreenQmlSurface::resize(const QSize& newSize_) {
|
||||
|
||||
if (!_renderer || !_renderer->_quickWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float MAX_OFFSCREEN_DIMENSION = 4096;
|
||||
QSize newSize = newSize_;
|
||||
|
||||
if (newSize.width() > MAX_OFFSCREEN_DIMENSION || newSize.height() > MAX_OFFSCREEN_DIMENSION) {
|
||||
float scale = std::min(
|
||||
((float)newSize.width() / MAX_OFFSCREEN_DIMENSION),
|
||||
((float)newSize.height() / MAX_OFFSCREEN_DIMENSION));
|
||||
newSize = QSize(
|
||||
std::max(static_cast<int>(scale * newSize.width()), 10),
|
||||
std::max(static_cast<int>(scale * newSize.height()), 10));
|
||||
}
|
||||
|
||||
|
||||
|
||||
QSize currentSize = _renderer->_quickWindow->geometry().size();
|
||||
if (newSize == currentSize) {
|
||||
|
|
|
@ -21,10 +21,6 @@
|
|||
#include "MessageDialog.h"
|
||||
|
||||
// Needs to match the constants in resources/qml/Global.js
|
||||
static const QString OFFSCREEN_ROOT_OBJECT_NAME = "desktopRoot";
|
||||
static const QString OFFSCREEN_WINDOW_OBJECT_NAME = "topLevelWindow";
|
||||
static QQuickItem* _desktop { nullptr };
|
||||
|
||||
class OffscreenFlags : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool navigationFocused READ isNavigationFocused WRITE setNavigationFocused NOTIFY navigationFocusedChanged)
|
||||
|
@ -252,6 +248,48 @@ void OffscreenUi::createDesktop() {
|
|||
_desktop = dynamic_cast<QQuickItem*>(load("Root.qml"));
|
||||
Q_ASSERT(_desktop);
|
||||
getRootContext()->setContextProperty("Desktop", _desktop);
|
||||
_toolWindow = _desktop->findChild<QQuickItem*>("ToolWindow");
|
||||
}
|
||||
|
||||
void OffscreenUi::toggleToolWindow() {
|
||||
_toolWindow->setEnabled(!_toolWindow->isEnabled());
|
||||
}
|
||||
|
||||
QQuickItem* OffscreenUi::getDesktop() {
|
||||
return _desktop;
|
||||
}
|
||||
|
||||
QQuickItem* OffscreenUi::getToolWindow() {
|
||||
return _toolWindow;
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(std::function<void()>);
|
||||
static auto VoidLambdaType = qRegisterMetaType<std::function<void()>>();
|
||||
Q_DECLARE_METATYPE(std::function<QVariant()>);
|
||||
static auto VariantLambdaType = qRegisterMetaType<std::function<QVariant()>>();
|
||||
|
||||
|
||||
void OffscreenUi::executeOnUiThread(std::function<void()> function) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "executeOnUiThread", Qt::QueuedConnection,
|
||||
Q_ARG(std::function<void()>, function));
|
||||
return;
|
||||
}
|
||||
|
||||
function();
|
||||
}
|
||||
|
||||
QVariant OffscreenUi::returnFromUiThread(std::function<QVariant()> function) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QVariant result;
|
||||
QMetaObject::invokeMethod(this, "returnFromUiThread", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QVariant, result),
|
||||
Q_ARG(std::function<QVariant()>, function));
|
||||
return result;
|
||||
}
|
||||
|
||||
return function();
|
||||
}
|
||||
|
||||
|
||||
#include "OffscreenUi.moc"
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#ifndef hifi_OffscreenUi_h
|
||||
#define hifi_OffscreenUi_h
|
||||
|
||||
#include <QtCore/QVariant>
|
||||
#include <gl/OffscreenQmlSurface.h>
|
||||
|
||||
#include <QMessageBox>
|
||||
|
@ -33,6 +34,12 @@ public:
|
|||
bool navigationFocused();
|
||||
void setNavigationFocused(bool focused);
|
||||
|
||||
QQuickItem* getDesktop();
|
||||
QQuickItem* getToolWindow();
|
||||
|
||||
Q_INVOKABLE void executeOnUiThread(std::function<void()> function);
|
||||
Q_INVOKABLE QVariant returnFromUiThread(std::function<QVariant()> function);
|
||||
|
||||
// Messagebox replacement functions
|
||||
using ButtonCallback = std::function<void(QMessageBox::StandardButton)>;
|
||||
static ButtonCallback NO_OP_CALLBACK;
|
||||
|
@ -70,6 +77,11 @@ public:
|
|||
QMessageBox::StandardButtons buttons = QMessageBox::Ok);
|
||||
|
||||
static void error(const QString& text); // Interim dialog in new style
|
||||
|
||||
void toggleToolWindow();
|
||||
private:
|
||||
QQuickItem* _desktop { nullptr };
|
||||
QQuickItem* _toolWindow { nullptr };
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -31,25 +31,15 @@ static const char* const URL_PROPERTY = "source";
|
|||
// Method called by Qt scripts to create a new web window in the overlay
|
||||
QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
|
||||
return QmlWindowClass::internalConstructor("QmlWebWindow.qml", context, engine,
|
||||
[&](QQmlContext* context, QObject* object) { return new QmlWebWindowClass(object); });
|
||||
[&](QObject* object) { return new QmlWebWindowClass(object); });
|
||||
}
|
||||
|
||||
QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) {
|
||||
QObject::connect(_qmlWindow, SIGNAL(navigating(QString)), this, SLOT(handleNavigation(QString)));
|
||||
}
|
||||
|
||||
void QmlWebWindowClass::handleNavigation(const QString& url) {
|
||||
bool handled = false;
|
||||
static auto handler = dynamic_cast<AbstractUriHandler*>(qApp);
|
||||
if (handler) {
|
||||
if (handler->canAcceptURL(url)) {
|
||||
handled = handler->acceptURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
QMetaObject::invokeMethod(_qmlWindow, "stop", Qt::AutoConnection);
|
||||
}
|
||||
// FIXME remove.
|
||||
void QmlWebWindowClass::handleNavigation(const QString& url) {
|
||||
}
|
||||
|
||||
QString QmlWebWindowClass::getURL() const {
|
||||
|
|
|
@ -35,6 +35,7 @@ static const char* const TITLE_PROPERTY = "title";
|
|||
static const char* const WIDTH_PROPERTY = "width";
|
||||
static const char* const HEIGHT_PROPERTY = "height";
|
||||
static const char* const VISIBILE_PROPERTY = "visible";
|
||||
static const char* const TOOLWINDOW_PROPERTY = "toolWindow";
|
||||
|
||||
void QmlScriptEventBridge::emitWebEvent(const QString& data) {
|
||||
QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data));
|
||||
|
@ -86,13 +87,14 @@ void QmlWindowClass::setupServer() {
|
|||
|
||||
QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
|
||||
QScriptContext* context, QScriptEngine* engine,
|
||||
std::function<QmlWindowClass*(QQmlContext*, QObject*)> function)
|
||||
std::function<QmlWindowClass*(QObject*)> builder)
|
||||
{
|
||||
const auto argumentCount = context->argumentCount();
|
||||
QString url;
|
||||
QString title;
|
||||
int width = -1, height = -1;
|
||||
bool visible = true;
|
||||
bool toolWindow = false;
|
||||
if (argumentCount > 1) {
|
||||
|
||||
if (!context->argument(0).isUndefined()) {
|
||||
|
@ -107,6 +109,9 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
|
|||
if (context->argument(3).isNumber()) {
|
||||
height = context->argument(3).toInt32();
|
||||
}
|
||||
if (context->argument(4).isBool()) {
|
||||
toolWindow = context->argument(4).toBool();
|
||||
}
|
||||
} else {
|
||||
auto argumentObject = context->argument(0);
|
||||
qDebug() << argumentObject.toString();
|
||||
|
@ -125,6 +130,9 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
|
|||
if (argumentObject.property(VISIBILE_PROPERTY).isBool()) {
|
||||
visible = argumentObject.property(VISIBILE_PROPERTY).toBool();
|
||||
}
|
||||
if (argumentObject.property(TOOLWINDOW_PROPERTY).isBool()) {
|
||||
toolWindow = argumentObject.property(TOOLWINDOW_PROPERTY).toBool();
|
||||
}
|
||||
}
|
||||
|
||||
if (!url.startsWith("http") && !url.startsWith("file://") && !url.startsWith("about:")) {
|
||||
|
@ -137,17 +145,44 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
|
|||
}
|
||||
|
||||
QmlWindowClass* retVal{ nullptr };
|
||||
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
qDebug() << "Clearing component cache";
|
||||
offscreenUi->getRootContext()->engine()->clearComponentCache();
|
||||
|
||||
// Build the event bridge and wrapper on the main thread
|
||||
QMetaObject::invokeMethod(offscreenUi.data(), "load", Qt::BlockingQueuedConnection,
|
||||
Q_ARG(const QString&, qmlSource),
|
||||
Q_ARG(std::function<void(QQmlContext*, QObject*)>, [&](QQmlContext* context, QObject* object) {
|
||||
if (toolWindow) {
|
||||
auto toolWindow = offscreenUi->getToolWindow();
|
||||
QVariantMap properties;
|
||||
properties.insert(TITLE_PROPERTY, title);
|
||||
properties.insert(SOURCE_PROPERTY, url);
|
||||
if (width != -1 && height != -1) {
|
||||
properties.insert(WIDTH_PROPERTY, width);
|
||||
properties.insert(HEIGHT_PROPERTY, height);
|
||||
}
|
||||
|
||||
// Build the event bridge and wrapper on the main thread
|
||||
QVariant newTabVar;
|
||||
bool invokeResult = QMetaObject::invokeMethod(toolWindow, "addWebTab", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QVariant, newTabVar),
|
||||
Q_ARG(QVariant, QVariant::fromValue(properties)));
|
||||
|
||||
QQuickItem* newTab = qvariant_cast<QQuickItem*>(newTabVar);
|
||||
if (!invokeResult || !newTab) {
|
||||
return QScriptValue();
|
||||
}
|
||||
|
||||
offscreenUi->returnFromUiThread([&] {
|
||||
setupServer();
|
||||
retVal = function(context, object);
|
||||
retVal = builder(newTab);
|
||||
retVal->_toolWindow = true;
|
||||
offscreenUi->getRootContext()->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership);
|
||||
registerObject(url.toLower(), retVal);
|
||||
return QVariant();
|
||||
});
|
||||
} else {
|
||||
// Build the event bridge and wrapper on the main thread
|
||||
QMetaObject::invokeMethod(offscreenUi.data(), "load", Qt::BlockingQueuedConnection,
|
||||
Q_ARG(const QString&, qmlSource),
|
||||
Q_ARG(std::function<void(QQmlContext*, QObject*)>, [&](QQmlContext* context, QObject* object) {
|
||||
setupServer();
|
||||
retVal = builder(object);
|
||||
context->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership);
|
||||
registerObject(url.toLower(), retVal);
|
||||
if (!title.isEmpty()) {
|
||||
|
@ -158,9 +193,12 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
|
|||
}
|
||||
object->setProperty(SOURCE_PROPERTY, url);
|
||||
if (visible) {
|
||||
object->setProperty("enabled", true);
|
||||
object->setProperty("visible", true);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
retVal->_source = url;
|
||||
connect(engine, &QScriptEngine::destroyed, retVal, &QmlWindowClass::deleteLater);
|
||||
return engine->newQObject(retVal);
|
||||
}
|
||||
|
@ -168,7 +206,7 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
|
|||
|
||||
// Method called by Qt scripts to create a new web window in the overlay
|
||||
QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
|
||||
return internalConstructor("QmlWindow.qml", context, engine, [&](QQmlContext* context, QObject* object){
|
||||
return internalConstructor("QmlWindow.qml", context, engine, [&](QObject* object){
|
||||
return new QmlWindowClass(object);
|
||||
});
|
||||
}
|
||||
|
@ -181,6 +219,21 @@ QmlWindowClass::QmlWindowClass(QObject* qmlWindow)
|
|||
Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow));
|
||||
}
|
||||
|
||||
QmlWindowClass::~QmlWindowClass() {
|
||||
if (_qmlWindow) {
|
||||
if (_toolWindow) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
auto toolWindow = offscreenUi->getToolWindow();
|
||||
auto invokeResult = QMetaObject::invokeMethod(toolWindow, "removeTabForUrl", Qt::QueuedConnection,
|
||||
Q_ARG(QVariant, _source));
|
||||
Q_ASSERT(invokeResult);
|
||||
} else {
|
||||
_qmlWindow->deleteLater();
|
||||
}
|
||||
_qmlWindow = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void QmlWindowClass::registerObject(const QString& name, QObject* object) {
|
||||
webChannel.registerObject(name, object);
|
||||
}
|
||||
|
@ -189,74 +242,85 @@ void QmlWindowClass::deregisterObject(QObject* object) {
|
|||
webChannel.deregisterObject(object);
|
||||
}
|
||||
|
||||
void QmlWindowClass::setVisible(bool visible) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setVisible", Qt::AutoConnection, Q_ARG(bool, visible));
|
||||
return;
|
||||
}
|
||||
|
||||
auto qmlWindow = asQuickItem();
|
||||
if (qmlWindow->isEnabled() != visible) {
|
||||
qmlWindow->setEnabled(visible);
|
||||
emit visibilityChanged(visible);
|
||||
}
|
||||
}
|
||||
|
||||
QQuickItem* QmlWindowClass::asQuickItem() const {
|
||||
if (_toolWindow) {
|
||||
return DependencyManager::get<OffscreenUi>()->getToolWindow();
|
||||
}
|
||||
return dynamic_cast<QQuickItem*>(_qmlWindow);
|
||||
}
|
||||
|
||||
bool QmlWindowClass::isVisible() const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
bool result;
|
||||
QMetaObject::invokeMethod(const_cast<QmlWindowClass*>(this), "isVisible", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result));
|
||||
return result;
|
||||
void QmlWindowClass::setVisible(bool visible) {
|
||||
// For tool window tabs we special case visiblility as enable / disable of the tab, not the window
|
||||
// The tool window itself has special logic based on whether any tabs are enabled
|
||||
if (_toolWindow) {
|
||||
auto targetTab = dynamic_cast<QQuickItem*>(_qmlWindow);
|
||||
DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
|
||||
targetTab->setEnabled(visible);
|
||||
//emit visibilityChanged(visible);
|
||||
});
|
||||
} else {
|
||||
QQuickItem* targetWindow = asQuickItem();
|
||||
DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
|
||||
targetWindow->setVisible(visible);
|
||||
//emit visibilityChanged(visible);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return asQuickItem()->isEnabled();
|
||||
bool QmlWindowClass::isVisible() const {
|
||||
// The tool window itself has special logic based on whether any tabs are enabled
|
||||
if (_toolWindow) {
|
||||
auto targetTab = dynamic_cast<QQuickItem*>(_qmlWindow);
|
||||
return DependencyManager::get<OffscreenUi>()->returnFromUiThread([&] {
|
||||
return QVariant::fromValue(targetTab->isEnabled());
|
||||
}).toBool();
|
||||
} else {
|
||||
QQuickItem* targetWindow = asQuickItem();
|
||||
return DependencyManager::get<OffscreenUi>()->returnFromUiThread([&] {
|
||||
return QVariant::fromValue(targetWindow->isVisible());
|
||||
}).toBool();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
glm::vec2 QmlWindowClass::getPosition() const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
glm::vec2 result;
|
||||
QMetaObject::invokeMethod(const_cast<QmlWindowClass*>(this), "getPosition", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result));
|
||||
return result;
|
||||
}
|
||||
|
||||
return glm::vec2(asQuickItem()->x(), asQuickItem()->y());
|
||||
QQuickItem* targetWindow = asQuickItem();
|
||||
QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant {
|
||||
return targetWindow->position();
|
||||
});
|
||||
return toGlm(result.toPointF());
|
||||
}
|
||||
|
||||
|
||||
void QmlWindowClass::setPosition(const glm::vec2& position) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setPosition", Qt::QueuedConnection, Q_ARG(glm::vec2, position));
|
||||
return;
|
||||
}
|
||||
|
||||
asQuickItem()->setPosition(QPointF(position.x, position.y));
|
||||
QQuickItem* targetWindow = asQuickItem();
|
||||
DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
|
||||
targetWindow->setPosition(QPointF(position.x, position.y));
|
||||
});
|
||||
}
|
||||
|
||||
void QmlWindowClass::setPosition(int x, int y) {
|
||||
setPosition(glm::vec2(x, y));
|
||||
}
|
||||
|
||||
// FIXME move to GLM helpers
|
||||
glm::vec2 toGlm(const QSizeF& size) {
|
||||
return glm::vec2(size.width(), size.height());
|
||||
}
|
||||
|
||||
glm::vec2 QmlWindowClass::getSize() const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
glm::vec2 result;
|
||||
QMetaObject::invokeMethod(const_cast<QmlWindowClass*>(this), "getSize", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result));
|
||||
return result;
|
||||
}
|
||||
|
||||
return glm::vec2(asQuickItem()->width(), asQuickItem()->height());
|
||||
QQuickItem* targetWindow = asQuickItem();
|
||||
QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant {
|
||||
return QSizeF(targetWindow->width(), targetWindow->height());
|
||||
});
|
||||
return toGlm(result.toSizeF());
|
||||
}
|
||||
|
||||
void QmlWindowClass::setSize(const glm::vec2& size) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setSize", Qt::QueuedConnection, Q_ARG(glm::vec2, size));
|
||||
}
|
||||
|
||||
asQuickItem()->setSize(QSizeF(size.x, size.y));
|
||||
QQuickItem* targetWindow = asQuickItem();
|
||||
DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
|
||||
targetWindow->setSize(QSizeF(size.x, size.y));
|
||||
});
|
||||
}
|
||||
|
||||
void QmlWindowClass::setSize(int width, int height) {
|
||||
|
@ -264,27 +328,28 @@ void QmlWindowClass::setSize(int width, int height) {
|
|||
}
|
||||
|
||||
void QmlWindowClass::setTitle(const QString& title) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setTitle", Qt::QueuedConnection, Q_ARG(QString, title));
|
||||
}
|
||||
|
||||
_qmlWindow->setProperty(TITLE_PROPERTY, title);
|
||||
QQuickItem* targetWindow = asQuickItem();
|
||||
DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
|
||||
targetWindow->setProperty(TITLE_PROPERTY, title);
|
||||
});
|
||||
}
|
||||
|
||||
void QmlWindowClass::close() {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection);
|
||||
}
|
||||
_qmlWindow->setProperty("destroyOnInvisible", true);
|
||||
_qmlWindow->setProperty("visible", false);
|
||||
_qmlWindow->deleteLater();
|
||||
DependencyManager::get<OffscreenUi>()->executeOnUiThread([this] {
|
||||
if (_qmlWindow) {
|
||||
_qmlWindow->setProperty("destroyOnInvisible", true);
|
||||
_qmlWindow->setProperty("visible", false);
|
||||
_qmlWindow->deleteLater();
|
||||
_qmlWindow = nullptr;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void QmlWindowClass::hasClosed() {
|
||||
}
|
||||
|
||||
void QmlWindowClass::raise() {
|
||||
QMetaObject::invokeMethod(_qmlWindow, "raiseWindow", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(asQuickItem(), "raiseWindow", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
#include "QmlWindowClass.moc"
|
||||
|
|
|
@ -51,6 +51,7 @@ class QmlWindowClass : public QObject {
|
|||
public:
|
||||
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
|
||||
QmlWindowClass(QObject* qmlWindow);
|
||||
~QmlWindowClass();
|
||||
|
||||
public slots:
|
||||
bool isVisible() const;
|
||||
|
@ -84,7 +85,7 @@ protected slots:
|
|||
protected:
|
||||
static QScriptValue internalConstructor(const QString& qmlSource,
|
||||
QScriptContext* context, QScriptEngine* engine,
|
||||
std::function<QmlWindowClass*(QQmlContext*, QObject*)> function);
|
||||
std::function<QmlWindowClass*(QObject*)> function);
|
||||
static void setupServer();
|
||||
static void registerObject(const QString& name, QObject* object);
|
||||
static void deregisterObject(QObject* object);
|
||||
|
@ -95,9 +96,10 @@ protected:
|
|||
|
||||
// FIXME needs to be initialized in the ctor once we have support
|
||||
// for tool window panes in QML
|
||||
const bool _isToolWindow { false };
|
||||
bool _toolWindow { false };
|
||||
const int _windowId;
|
||||
QObject* const _qmlWindow;
|
||||
QObject* _qmlWindow;
|
||||
QString _source;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue