Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions glue/crumble/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,16 @@ button (now labelled "Hide Import/Export") again.
- `escape`: Unselect. Set current selection to the empty set.
- `delete`: Delete gates at current selection.
- `backspace`: Delete gates at current selection.
- `ctrl+delete`: Delete current circuit layer.
- `ctrl+delete` or `cmd+delete`: Delete current circuit layer.
- `ctrl+backspace`: Delete current circuit layer.
- `ctrl+insert`: Insert empty layer at current circuit layer, pushing current circuit layer ahead in time.
- `ctrl+z`: Undo
- `ctrl+y`: Redo
- `ctrl+shift+z`: Redo
- `ctrl+c`: Copy selection to clipboard (or entire layer if nothing selected).
- `ctrl+v`: Past clipboard contents at current selection (or entire layer if nothing selected).
- `ctrl+x`: Cut selection to clipboard (or entire layer if nothing selected).
- `cmd+enter`: Insert empty layer at current circuit layer, pushing current circuit layer ahead in time.
- `ctrl+z` or `cmd+z`: Undo
- `ctrl+y` or `cmd+y`: Redo
- `ctrl+shift+z` or `cmd+shift+z`: Redo
- `ctrl+c` or `cmd+c`: Copy selection to clipboard (or entire layer if nothing selected).
- `ctrl+v` or `cmd+v`: Paste clipboard contents at current selection (or entire layer if nothing selected).
- `ctrl+x` or `cmd+x`: Cut selection to clipboard (or entire layer if nothing selected).
- `f`: Reverse direction of selected two qubit gates (e.g. exchange the controls and targets of a CNOT).
- `g`: Reverse order of circuit layers, from the current layer to the next empty layer.
- `home`: Jump to the first layer of the circuit.
Expand Down
8 changes: 4 additions & 4 deletions glue/crumble/crumble.html
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@
<button id="btnClearSelectedMarkers">Clear Selected Marks (space)</button>
</div>
<div class="btn-group">
<button id="btnRedo">Redo (ctrl+Y)</button>
<button id="btnUndo">Undo (ctrl+Z)</button>
<button id="btnRedo">Redo (ctrl/cmd+Y)</button>
<button id="btnUndo">Undo (ctrl/cmd+Z)</button>
</div>
<div class="btn-group">
<button id="btnNextLayer">Next Layer (e)</button>
Expand All @@ -134,8 +134,8 @@
<button id="btnClearTimelineFocus">Clear Timeline Focus</button>
</div>
<div class="btn-group">
<button id="btnInsertLayer">Insert Layer (ctrl+insert)</button>
<button id="btnDeleteLayer">Delete Layer (ctrl+delete)</button>
<button id="btnInsertLayer">Insert Layer (ctrl+insert or cmd+enter)</button>
<button id="btnDeleteLayer">Delete Layer (ctrl/cmd+delete)</button>
</div>
</div>
<textarea id="txtDefaultCircuit" style="display: none">[[[DEFAULT_CIRCUIT_CONTENT_LITERAL]]]</textarea>
Expand Down
4 changes: 2 additions & 2 deletions glue/crumble/keyboard/toolbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ let DEF_ROW = [1, 2, 2, 2, 2, 0, 2, 2, 2, -1, -1, -1];
* @returns {undefined|!{row: !int, strength: !number}}
*/
function getFocusedRow(ev) {
if (ev.ctrlKey) {
if (ev.ctrlKey || ev.metaKey) {
return undefined;
}
let hasX = +ev.chord.has('x');
Expand All @@ -36,7 +36,7 @@ function getFocusedRow(ev) {
* @returns {undefined|!{col: !int, strength: !number}}
*/
function getFocusedCol(ev) {
if (ev.ctrlKey) {
if (ev.ctrlKey || ev.metaKey) {
return undefined;
}
let best = undefined;
Expand Down
159 changes: 145 additions & 14 deletions glue/crumble/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ editorState.canvas.addEventListener('mouseup', ev => {
editorState.mouseDownY = undefined;
editorState.curMouseX = ev.offsetX + OFFSET_X;
editorState.curMouseY = ev.offsetY + OFFSET_Y;
editorState.changeFocus(highlightedArea, ev.shiftKey, ev.ctrlKey);
editorState.changeFocus(highlightedArea, ev.shiftKey, ev.ctrlKey || ev.metaKey);
if (ev.buttons === 1) {
isInScrubber = false;
}
Expand All @@ -222,17 +222,8 @@ function makeChordHandlers() {
res.set('ctrl+shift+z', preview => { if (!preview) editorState.redo() });
res.set('ctrl+c', async preview => { await copyToClipboard(); });
res.set('ctrl+v', pasteFromClipboard);
res.set('ctrl+x', async preview => {
await copyToClipboard();
if (editorState.focusedSet.size === 0) {
let c = editorState.copyOfCurCircuit();
c.layers[editorState.curLayer].id_ops.clear();
c.layers[editorState.curLayer].markers.length = 0;
editorState.commit_or_preview(c, preview);
} else {
editorState.deleteAtFocus(preview);
}
});
res.set('ctrl+x', cutToClipboard);

res.set('l', preview => {
if (!preview) {
editorState.timelineSet = new Map(editorState.focusedSet.entries());
Expand Down Expand Up @@ -360,6 +351,8 @@ function makeChordHandlers() {
}

let fallbackEmulatedClipboard = undefined;
let pendingMetaPaste = false;
let pendingMetaPasteTimeout = undefined;
async function copyToClipboard() {
let c = editorState.copyOfCurCircuit();
c.layers = [c.layers[editorState.curLayer]]
Expand Down Expand Up @@ -442,12 +435,136 @@ async function pasteFromClipboard(preview) {
editorState.commit_or_preview(newCircuit, preview);
}

/**
* @param {!string} text
* @param {!boolean} preview
*/
function pasteTextFromClipboardEvent(text, preview) {
let pastedCircuit = Circuit.fromStimCircuit(text);
if (pastedCircuit.layers.length !== 1) {
throw new Error(text);
}
let newCircuit = editorState.copyOfCurCircuit();
if (editorState.focusedSet.size > 0) {
let [x, y] = minXY(editorState.focusedSet.values());
pastedCircuit = pastedCircuit.shifted(x, y);
}

// Include new coordinates.
let usedCoords = [];
for (let q = 0; q < pastedCircuit.qubitCoordData.length; q += 2) {
usedCoords.push([pastedCircuit.qubitCoordData[q], pastedCircuit.qubitCoordData[q + 1]]);
}
newCircuit = newCircuit.withCoordsIncluded(usedCoords);
let c2q = newCircuit.coordToQubitMap();

// Remove existing content at paste location.
for (let key of editorState.focusedSet.keys()) {
let q = c2q.get(key);
if (q !== undefined) {
newCircuit.layers[editorState.curLayer].id_pop_at(q);
}
}

// Add content to paste location.
for (let op of pastedCircuit.layers[0].iter_gates_and_markers()) {
let newTargets = [];
for (let q of op.id_targets) {
let x = pastedCircuit.qubitCoordData[2*q];
let y = pastedCircuit.qubitCoordData[2*q+1];
newTargets.push(c2q.get(`${x},${y}`));
}
newCircuit.layers[editorState.curLayer].put(new Operation(
op.gate,
op.tag,
op.args,
new Uint32Array(newTargets),
));
}

editorState.commit_or_preview(newCircuit, preview);
}

function clearPendingMetaPaste() {
pendingMetaPaste = false;
if (pendingMetaPasteTimeout !== undefined) {
clearTimeout(pendingMetaPasteTimeout);
pendingMetaPasteTimeout = undefined;
}
}

async function cutToClipboard(preview) {
await copyToClipboard();
if (editorState.focusedSet.size === 0) {
let c = editorState.copyOfCurCircuit();
c.layers[editorState.curLayer].id_ops.clear();
c.layers[editorState.curLayer].markers.length = 0;
editorState.commit_or_preview(c, preview);
} else {
editorState.deleteAtFocus(preview);
}
}

const CHORD_HANDLERS = makeChordHandlers();
/**
* @param {!KeyboardEvent} ev
*/
function handleKeyboardEvent(ev) {
async function handleKeyboardEvent(ev) {
if (ev.type === 'keydown' && ev.metaKey) {
if (ev.repeat) {
ev.preventDefault();
editorState.chorder.handleFocusChanged();
return;
}

let key = ev.key.toLowerCase();

if (key === 'z' && !ev.shiftKey) {
ev.preventDefault();
editorState.chorder.handleFocusChanged();
editorState.undo();
return;
}
if ((key === 'z' && ev.shiftKey) || key === 'y') {
ev.preventDefault();
editorState.chorder.handleFocusChanged();
editorState.redo();
return;
}
if (key === 'c') {
ev.preventDefault();
editorState.chorder.handleFocusChanged();
await copyToClipboard();
return;
}
if (key === 'v') {
editorState.chorder.handleFocusChanged();
pendingMetaPaste = true;
pendingMetaPasteTimeout = setTimeout(clearPendingMetaPaste, 1000);
return;
}
if (key === 'x') {
ev.preventDefault();
editorState.chorder.handleFocusChanged();
await cutToClipboard(false);
return;
}
if (key === 'backspace' || key === 'delete') {
ev.preventDefault();
editorState.chorder.handleFocusChanged();
editorState.deleteCurLayer(false);
return;
}
if (key === 'enter') {
ev.preventDefault();
editorState.chorder.handleFocusChanged();
editorState.insertLayer(false);
return;
}
}

editorState.chorder.handleKeyEvent(ev);

if (ev.type === 'keydown') {
if (ev.key.toLowerCase() === 'q') {
let d = ev.shiftKey ? 5 : 1;
Expand Down Expand Up @@ -511,6 +628,20 @@ function handleKeyboardEvent(ev) {
}
}

document.addEventListener('paste', ev => {
if (!pendingMetaPaste) {
return;
}
clearPendingMetaPaste();

let text = ev.clipboardData.getData('text/plain');
if (text === '') {
return;
}

ev.preventDefault();
pasteTextFromClipboardEvent(text, false);
});
document.addEventListener('keydown', handleKeyboardEvent);
document.addEventListener('keyup', handleKeyboardEvent);

Expand All @@ -532,7 +663,7 @@ window.addEventListener('blur', () => {
for (let anchor of document.getElementById('examples-div').querySelectorAll('a')) {
anchor.onclick = ev => {
// Don't stop the user from e.g. opening the example in a new tab using ctrl+click.
if (ev.shiftKey || ev.ctrlKey || ev.altKey || ev.button !== 0) {
if (ev.shiftKey || ev.ctrlKey || ev.metaKey || ev.altKey || ev.button !== 0) {
return undefined;
}
let circuitText = anchor.href.split('#circuit=')[1];
Expand Down
Loading