Decoupled History from QML
This commit is contained in:
parent
54363b25bc
commit
2dc9234b22
10 changed files with 239 additions and 191 deletions
|
@ -54,6 +54,11 @@ export class BaseEventEmitter {
|
||||||
* @param {function(BaseEvent)} eventListener - The function to be called back when the event is emitted.
|
* @param {function(BaseEvent)} eventListener - The function to be called back when the event is emitted.
|
||||||
*/
|
*/
|
||||||
on(eventType, eventListener) {
|
on(eventType, eventListener) {
|
||||||
|
if(eventType.includes(" ")) // Listen to several different events with the same listener.
|
||||||
|
for(const type of eventType.split(" "))
|
||||||
|
this.on(type, eventListener)
|
||||||
|
else {
|
||||||
|
console.log("Listening to", eventType)
|
||||||
if(!this.constructor.emits.includes(eventType)) {
|
if(!this.constructor.emits.includes(eventType)) {
|
||||||
const className = this.constructor.name
|
const className = this.constructor.name
|
||||||
const eventTypes = this.constructor.emits.join(", ")
|
const eventTypes = this.constructor.emits.join(", ")
|
||||||
|
@ -62,15 +67,21 @@ export class BaseEventEmitter {
|
||||||
if(!this.#listeners[eventType].has(eventListener))
|
if(!this.#listeners[eventType].has(eventListener))
|
||||||
this.#listeners[eventType].add(eventListener)
|
this.#listeners[eventType].add(eventListener)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remvoes a listener from an event that can be emitted by this object.
|
* Removes a listener from an event that can be emitted by this object.
|
||||||
*
|
*
|
||||||
* @param {string} eventType - Name of the event that was listened to. Throws an error if this object does not emit this kind of event.
|
* @param {string} eventType - Name of the event that was listened to. Throws an error if this object does not emit this kind of event.
|
||||||
* @param {function(BaseEvent)} eventListener - The function previously registered as a listener.
|
* @param {function(BaseEvent)} eventListener - The function previously registered as a listener.
|
||||||
* @returns {boolean} True if the listener was removed, false if it was not found.
|
* @returns {boolean} True if the listener was removed, false if it was not found.
|
||||||
*/
|
*/
|
||||||
off(eventType, eventListener) {
|
off(eventType, eventListener) {
|
||||||
|
if(eventType.includes(" ")) { // Unlisten to several different events with the same listener.
|
||||||
|
let found = false
|
||||||
|
for(const type of eventType.split(" "))
|
||||||
|
found ||= this.off(eventType, eventListener)
|
||||||
|
} else {
|
||||||
if(!this.constructor.emits.includes(eventType)) {
|
if(!this.constructor.emits.includes(eventType)) {
|
||||||
const className = this.constructor.name
|
const className = this.constructor.name
|
||||||
const eventTypes = this.constructor.emits.join(", ")
|
const eventTypes = this.constructor.emits.join(", ")
|
||||||
|
@ -78,6 +89,7 @@ export class BaseEventEmitter {
|
||||||
}
|
}
|
||||||
return this.#listeners[eventType].delete(eventListener)
|
return this.#listeners[eventType].delete(eventListener)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits an event to all of its listeners.
|
* Emits an event to all of its listeners.
|
||||||
|
|
|
@ -67,6 +67,19 @@ function stringReplaceAll(from, to) {
|
||||||
return this.split(from).join(to)
|
return this.split(from).join(to)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of an element of the array at a given index.
|
||||||
|
* Accepts negative indexes.
|
||||||
|
* @this {Array|string}
|
||||||
|
* @param {number} index
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
function arrayAt(index) {
|
||||||
|
if(typeof index !== "number")
|
||||||
|
throw new Error(`${index} is not a number`)
|
||||||
|
return index >= 0 ? this[index] : this[this.length + index]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const polyfills = {
|
const polyfills = {
|
||||||
2017: [
|
2017: [
|
||||||
|
@ -95,8 +108,8 @@ const polyfills = {
|
||||||
[String.prototype, "replaceAll", stringReplaceAll]
|
[String.prototype, "replaceAll", stringReplaceAll]
|
||||||
],
|
],
|
||||||
2022: [
|
2022: [
|
||||||
[Array.prototype, "at", notPolyfilled("Array.prototype.at")],
|
[Array.prototype, "at", arrayAt],
|
||||||
[String.prototype, "at", notPolyfilled("String.prototype.at")],
|
[String.prototype, "at", arrayAt],
|
||||||
[Object, "hasOwn", notPolyfilled("Object.hasOwn")]
|
[Object, "hasOwn", notPolyfilled("Object.hasOwn")]
|
||||||
],
|
],
|
||||||
2023: [
|
2023: [
|
||||||
|
|
|
@ -17,60 +17,151 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Module } from "./common.mjs"
|
import { Module } from "./common.mjs"
|
||||||
import { HistoryInterface, NUMBER, STRING } from "./interface.mjs"
|
import { HelperInterface, HistoryInterface, NUMBER, STRING } from "./interface.mjs"
|
||||||
|
import { BaseEvent } from "../events.mjs"
|
||||||
|
import { Action, Actions } from "../history/index.mjs"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class UpdatedEvent extends BaseEvent {
|
||||||
|
constructor() {
|
||||||
|
super("updated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UndoneEvent extends BaseEvent {
|
||||||
|
constructor(action) {
|
||||||
|
super("undone")
|
||||||
|
this.undid = action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RedoneEvent extends BaseEvent {
|
||||||
|
constructor(action) {
|
||||||
|
super("redone")
|
||||||
|
this.redid = action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class HistoryAPI extends Module {
|
class HistoryAPI extends Module {
|
||||||
|
static emits = ["updated", "undone", "redone"]
|
||||||
|
|
||||||
|
#helper
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super("History", {
|
super("History", {
|
||||||
historyObj: HistoryInterface,
|
helper: HelperInterface,
|
||||||
themeTextColor: STRING,
|
themeTextColor: STRING,
|
||||||
imageDepth: NUMBER,
|
imageDepth: NUMBER,
|
||||||
fontSize: NUMBER
|
fontSize: NUMBER
|
||||||
})
|
})
|
||||||
// History QML object
|
// History QML object
|
||||||
this.history = null
|
/** @type {Action[]} */
|
||||||
|
this.undoStack = []
|
||||||
|
/** @type {Action[]} */
|
||||||
|
this.redoStack = []
|
||||||
|
|
||||||
this.themeTextColor = "#FF0000"
|
this.themeTextColor = "#FF0000"
|
||||||
this.imageDepth = 2
|
this.imageDepth = 2
|
||||||
this.fontSize = 28
|
this.fontSize = 28
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize({ historyObj, themeTextColor, imageDepth, fontSize }) {
|
/**
|
||||||
super.initialize({ historyObj, themeTextColor, imageDepth, fontSize })
|
* @param {HelperInterface} historyObj
|
||||||
this.history = historyObj
|
* @param {string} themeTextColor
|
||||||
|
* @param {number} imageDepth
|
||||||
|
* @param {number} fontSize
|
||||||
|
*/
|
||||||
|
initialize({ helper, themeTextColor, imageDepth, fontSize }) {
|
||||||
|
super.initialize({ helper, themeTextColor, imageDepth, fontSize })
|
||||||
|
this.#helper = helper
|
||||||
this.themeTextColor = themeTextColor
|
this.themeTextColor = themeTextColor
|
||||||
this.imageDepth = imageDepth
|
this.imageDepth = imageDepth
|
||||||
this.fontSize = fontSize
|
this.fontSize = fontSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undoes the Action at the top of the undo stack and pushes it to the top of the redo stack.
|
||||||
|
*/
|
||||||
undo() {
|
undo() {
|
||||||
if(!this.initialized) throw new Error("Attempting undo before initialize!")
|
if(!this.initialized) throw new Error("Attempting undo before initialize!")
|
||||||
this.history.undo()
|
if(this.undoStack.length > 0) {
|
||||||
|
const action = this.undoStack.pop()
|
||||||
|
action.undo()
|
||||||
|
this.redoStack.push(action)
|
||||||
|
this.emit(new UndoneEvent(action))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redoes the Action at the top of the redo stack and pushes it to the top of the undo stack.
|
||||||
|
*/
|
||||||
redo() {
|
redo() {
|
||||||
if(!this.initialized) throw new Error("Attempting redo before initialize!")
|
if(!this.initialized) throw new Error("Attempting redo before initialize!")
|
||||||
this.history.redo()
|
if(this.redoStack.length > 0) {
|
||||||
|
const action = this.redoStack.pop()
|
||||||
|
action.redo()
|
||||||
|
this.undoStack.push(action)
|
||||||
|
this.emit(new RedoneEvent(action))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears both undo and redo stacks completely.
|
||||||
|
*/
|
||||||
clear() {
|
clear() {
|
||||||
if(!this.initialized) throw new Error("Attempting clear before initialize!")
|
if(!this.initialized) throw new Error("Attempting clear before initialize!")
|
||||||
this.history.clear()
|
this.undoStack = []
|
||||||
|
this.redoStack = []
|
||||||
|
this.emit(new UpdatedEvent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an instance of HistoryLib.Action to history.
|
||||||
|
* @param action
|
||||||
|
*/
|
||||||
addToHistory(action) {
|
addToHistory(action) {
|
||||||
if(!this.initialized) throw new Error("Attempting addToHistory before initialize!")
|
if(!this.initialized) throw new Error("Attempting addToHistory before initialize!")
|
||||||
this.history.addToHistory(action)
|
if(action instanceof Action) {
|
||||||
|
console.log("Added new entry to history: " + action.getReadableString())
|
||||||
|
this.undoStack.push(action)
|
||||||
|
if(this.#helper.getSettingBool("reset_redo_stack"))
|
||||||
|
this.redoStack = []
|
||||||
|
this.emit(new UpdatedEvent())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unserialize(...data) {
|
/**
|
||||||
|
* Unserializes both the undo stack and redo stack from serialized content.
|
||||||
|
* @param {[string, any[]][]} undoSt
|
||||||
|
* @param {[string, any[]][]} redoSt
|
||||||
|
*/
|
||||||
|
unserialize(undoSt, redoSt) {
|
||||||
if(!this.initialized) throw new Error("Attempting unserialize before initialize!")
|
if(!this.initialized) throw new Error("Attempting unserialize before initialize!")
|
||||||
this.history.unserialize(...data)
|
this.clear()
|
||||||
|
for(const [name, args] of undoSt)
|
||||||
|
this.undoStack.push(
|
||||||
|
new Actions[name](...args)
|
||||||
|
)
|
||||||
|
for(const [name, args] of redoSt)
|
||||||
|
this.redoStack.push(
|
||||||
|
new Actions[name](...args)
|
||||||
|
)
|
||||||
|
this.emit(new UpdatedEvent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes history into JSON-able content.
|
||||||
|
* @return {[[string, any[]], [string, any[]]]}
|
||||||
|
*/
|
||||||
serialize() {
|
serialize() {
|
||||||
if(!this.initialized) throw new Error("Attempting serialize before initialize!")
|
if(!this.initialized) throw new Error("Attempting serialize before initialize!")
|
||||||
return this.history.serialize()
|
let undoSt = [], redoSt = [];
|
||||||
|
for(const action of this.undoStack)
|
||||||
|
undoSt.push([ action.type(), action.export() ])
|
||||||
|
for(const action of this.redoStack)
|
||||||
|
redoSt.push([ action.type(), action.export() ])
|
||||||
|
return [undoSt, redoSt]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,24 +60,6 @@ export class Interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class SettingsInterface extends Interface {
|
|
||||||
width = NUMBER
|
|
||||||
height = NUMBER
|
|
||||||
xmin = NUMBER
|
|
||||||
ymax = NUMBER
|
|
||||||
xzoom = NUMBER
|
|
||||||
yzoom = NUMBER
|
|
||||||
xaxisstep = STRING
|
|
||||||
yaxisstep = STRING
|
|
||||||
xlabel = STRING
|
|
||||||
ylabel = STRING
|
|
||||||
linewidth = NUMBER
|
|
||||||
textsize = NUMBER
|
|
||||||
logscalex = BOOLEAN
|
|
||||||
showxgrad = BOOLEAN
|
|
||||||
showygrad = BOOLEAN
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CanvasInterface extends Interface {
|
export class CanvasInterface extends Interface {
|
||||||
imageLoaders = OBJECT
|
imageLoaders = OBJECT
|
||||||
/** @type {function(string): CanvasRenderingContext2D} */
|
/** @type {function(string): CanvasRenderingContext2D} */
|
||||||
|
|
|
@ -21,7 +21,7 @@ import Objects from "./objects.mjs"
|
||||||
import History from "./history.mjs"
|
import History from "./history.mjs"
|
||||||
import Canvas from "./canvas.mjs"
|
import Canvas from "./canvas.mjs"
|
||||||
import Settings from "./settings.mjs"
|
import Settings from "./settings.mjs"
|
||||||
import { DialogInterface, RootInterface, SettingsInterface } from "./interface.mjs"
|
import { DialogInterface, RootInterface } from "./interface.mjs"
|
||||||
|
|
||||||
|
|
||||||
class IOAPI extends Module {
|
class IOAPI extends Module {
|
||||||
|
|
|
@ -73,6 +73,10 @@ class SettingsAPI extends Module {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {HelperInterface} helper
|
||||||
|
*/
|
||||||
initialize({ helper }) {
|
initialize({ helper }) {
|
||||||
super.initialize({ helper })
|
super.initialize({ helper })
|
||||||
// Initialize default values.
|
// Initialize default values.
|
||||||
|
|
|
@ -76,18 +76,16 @@ MenuBar {
|
||||||
Action {
|
Action {
|
||||||
text: qsTr("&Undo")
|
text: qsTr("&Undo")
|
||||||
shortcut: StandardKey.Undo
|
shortcut: StandardKey.Undo
|
||||||
onTriggered: history.undo()
|
onTriggered: Modules.History.undo()
|
||||||
icon.name: 'edit-undo'
|
icon.name: 'edit-undo'
|
||||||
icon.color: enabled ? sysPalette.windowText : sysPaletteIn.windowText
|
icon.color: enabled ? sysPalette.windowText : sysPaletteIn.windowText
|
||||||
enabled: history.undoCount > 0
|
|
||||||
}
|
}
|
||||||
Action {
|
Action {
|
||||||
text: qsTr("&Redo")
|
text: qsTr("&Redo")
|
||||||
shortcut: StandardKey.Redo
|
shortcut: StandardKey.Redo
|
||||||
onTriggered: history.redo()
|
onTriggered: Modules.History.redo()
|
||||||
icon.name: 'edit-redo'
|
icon.name: 'edit-redo'
|
||||||
icon.color: enabled ? sysPalette.windowText : sysPaletteIn.windowText
|
icon.color: enabled ? sysPalette.windowText : sysPaletteIn.windowText
|
||||||
enabled: history.redoCount > 0
|
|
||||||
}
|
}
|
||||||
Action {
|
Action {
|
||||||
text: qsTr("&Copy plot")
|
text: qsTr("&Copy plot")
|
||||||
|
|
|
@ -64,10 +64,7 @@ Item {
|
||||||
Clears both undo and redo stacks completly.
|
Clears both undo and redo stacks completly.
|
||||||
*/
|
*/
|
||||||
function clear() {
|
function clear() {
|
||||||
undoCount = 0
|
Modules.History.clear()
|
||||||
redoCount = 0
|
|
||||||
undoStack = []
|
|
||||||
redoStack = []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,18 +73,7 @@ Item {
|
||||||
Serializes history into JSON-able content.
|
Serializes history into JSON-able content.
|
||||||
*/
|
*/
|
||||||
function serialize() {
|
function serialize() {
|
||||||
let undoSt = [], redoSt = [];
|
return Modules.History.serialize()
|
||||||
for(let i = 0; i < undoCount; i++)
|
|
||||||
undoSt.push([
|
|
||||||
undoStack[i].type(),
|
|
||||||
undoStack[i].export()
|
|
||||||
]);
|
|
||||||
for(let i = 0; i < redoCount; i++)
|
|
||||||
redoSt.push([
|
|
||||||
redoStack[i].type(),
|
|
||||||
redoStack[i].export()
|
|
||||||
]);
|
|
||||||
return [undoSt, redoSt]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -95,14 +81,7 @@ Item {
|
||||||
Unserializes both \c undoSt stack and \c redoSt stack from serialized content.
|
Unserializes both \c undoSt stack and \c redoSt stack from serialized content.
|
||||||
*/
|
*/
|
||||||
function unserialize(undoSt, redoSt) {
|
function unserialize(undoSt, redoSt) {
|
||||||
clear();
|
Modules.History.unserialize(undoSt, redoSt)
|
||||||
for(let i = 0; i < undoSt.length; i++)
|
|
||||||
undoStack.push(new JS.HistoryLib.Actions[undoSt[i][0]](...undoSt[i][1]))
|
|
||||||
for(let i = 0; i < redoSt.length; i++)
|
|
||||||
redoStack.push(new JS.HistoryLib.Actions[redoSt[i][0]](...redoSt[i][1]))
|
|
||||||
undoCount = undoSt.length;
|
|
||||||
redoCount = redoSt.length;
|
|
||||||
objectLists.update()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -110,16 +89,7 @@ Item {
|
||||||
Adds an instance of HistoryLib.Action to history.
|
Adds an instance of HistoryLib.Action to history.
|
||||||
*/
|
*/
|
||||||
function addToHistory(action) {
|
function addToHistory(action) {
|
||||||
if(action instanceof JS.HistoryLib.Action) {
|
Modules.History.addToHistory(action)
|
||||||
console.log("Added new entry to history: " + action.getReadableString())
|
|
||||||
undoStack.push(action)
|
|
||||||
undoCount++;
|
|
||||||
if(Helper.getSettingBool("reset_redo_stack")) {
|
|
||||||
redoStack = []
|
|
||||||
redoCount = 0
|
|
||||||
}
|
|
||||||
saved = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -128,16 +98,7 @@ Item {
|
||||||
By default, will update the graph and the object list. This behavior can be disabled by setting the \c updateObjectList to false.
|
By default, will update the graph and the object list. This behavior can be disabled by setting the \c updateObjectList to false.
|
||||||
*/
|
*/
|
||||||
function undo(updateObjectList = true) {
|
function undo(updateObjectList = true) {
|
||||||
if(undoStack.length > 0) {
|
Modules.History.undo()
|
||||||
var action = undoStack.pop()
|
|
||||||
action.undo()
|
|
||||||
if(updateObjectList)
|
|
||||||
objectLists.update()
|
|
||||||
redoStack.push(action)
|
|
||||||
undoCount--;
|
|
||||||
redoCount++;
|
|
||||||
saved = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -146,77 +107,6 @@ Item {
|
||||||
By default, will update the graph and the object list. This behavior can be disabled by setting the \c updateObjectList to false.
|
By default, will update the graph and the object list. This behavior can be disabled by setting the \c updateObjectList to false.
|
||||||
*/
|
*/
|
||||||
function redo(updateObjectList = true) {
|
function redo(updateObjectList = true) {
|
||||||
if(redoStack.length > 0) {
|
Modules.History.redo()
|
||||||
var action = redoStack.pop()
|
|
||||||
action.redo()
|
|
||||||
if(updateObjectList)
|
|
||||||
objectLists.update()
|
|
||||||
undoStack.push(action)
|
|
||||||
undoCount++;
|
|
||||||
redoCount--;
|
|
||||||
saved = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlmethod void History::undoMultipleDefered(int toUndoCount)
|
|
||||||
Undoes several HistoryLib.Action at the top of the undo stack and pushes them to the top of the redo stack.
|
|
||||||
It undoes them deferedly to avoid overwhelming the computer while creating a cool short accelerated summary of all changes.
|
|
||||||
*/
|
|
||||||
function undoMultipleDefered(toUndoCount) {
|
|
||||||
undoTimer.toUndoCount = toUndoCount;
|
|
||||||
undoTimer.start()
|
|
||||||
if(toUndoCount > 0)
|
|
||||||
saved = false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlmethod void History::redoMultipleDefered(int toRedoCount)
|
|
||||||
Redoes several HistoryLib.Action at the top of the redo stack and pushes them to the top of the undo stack.
|
|
||||||
It redoes them deferedly to avoid overwhelming the computer while creating a cool short accelerated summary of all changes.
|
|
||||||
*/
|
|
||||||
function redoMultipleDefered(toRedoCount) {
|
|
||||||
redoTimer.toRedoCount = toRedoCount;
|
|
||||||
redoTimer.start()
|
|
||||||
if(toRedoCount > 0)
|
|
||||||
saved = false
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: undoTimer
|
|
||||||
interval: 5; running: false; repeat: true
|
|
||||||
property int toUndoCount: 0
|
|
||||||
onTriggered: {
|
|
||||||
if(toUndoCount > 0) {
|
|
||||||
historyObj.undo(toUndoCount % 4 == 1) // Only redraw once every 4 changes.
|
|
||||||
toUndoCount--;
|
|
||||||
} else {
|
|
||||||
running = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: redoTimer
|
|
||||||
interval: 5; running: false; repeat: true
|
|
||||||
property int toRedoCount: 0
|
|
||||||
onTriggered: {
|
|
||||||
if(toRedoCount > 0) {
|
|
||||||
historyObj.redo(toRedoCount % 4 == 1) // Only redraw once every 4 changes.
|
|
||||||
toRedoCount--;
|
|
||||||
} else {
|
|
||||||
running = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
Modules.History.initialize({
|
|
||||||
historyObj,
|
|
||||||
themeTextColor: sysPalette.windowText.toString(),
|
|
||||||
imageDepth: Screen.devicePixelRatio,
|
|
||||||
fontSize: 14
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
|
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
|
||||||
|
@ -47,6 +49,18 @@ Item {
|
||||||
*/
|
*/
|
||||||
property bool darkTheme: isDarkTheme()
|
property bool darkTheme: isDarkTheme()
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmlproperty int HistoryBrowser::undoCount
|
||||||
|
Number of actions in the undo stack.
|
||||||
|
*/
|
||||||
|
property int undoCount: 0
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmlproperty int HistoryBrowser::redoCount
|
||||||
|
Number of actions in the redo stack.
|
||||||
|
*/
|
||||||
|
property int redoCount: 0
|
||||||
|
|
||||||
Setting.TextSetting {
|
Setting.TextSetting {
|
||||||
id: filterInput
|
id: filterInput
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
@ -76,19 +90,22 @@ Item {
|
||||||
id: redoColumn
|
id: redoColumn
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
width: actionWidth
|
width: historyBrowser.actionWidth
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: history.redoCount
|
model: historyBrowser.redoCount
|
||||||
|
|
||||||
HistoryItem {
|
HistoryItem {
|
||||||
id: redoButton
|
id: redoButton
|
||||||
width: actionWidth
|
width: historyBrowser.actionWidth
|
||||||
//height: actionHeight
|
//height: actionHeight
|
||||||
isRedo: true
|
isRedo: true
|
||||||
idx: index
|
|
||||||
darkTheme: historyBrowser.darkTheme
|
darkTheme: historyBrowser.darkTheme
|
||||||
hidden: !(filterInput.value == "" || content.includes(filterInput.value))
|
hidden: !(filterInput.value == "" || content.includes(filterInput.value))
|
||||||
|
onClicked: {
|
||||||
|
redoTimer.toRedoCount = Modules.History.redoStack.length-index
|
||||||
|
redoTimer.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,14 +118,14 @@ Item {
|
||||||
transform: Rotation { origin.x: 30; origin.y: 30; angle: 270}
|
transform: Rotation { origin.x: 30; origin.y: 30; angle: 270}
|
||||||
height: 70
|
height: 70
|
||||||
width: 20
|
width: 20
|
||||||
visible: history.redoCount > 0
|
visible: historyBrowser.redoCount > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: nowRect
|
id: nowRect
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: redoColumn.bottom
|
anchors.top: redoColumn.bottom
|
||||||
width: actionWidth
|
width: historyBrowser.actionWidth
|
||||||
height: 40
|
height: 40
|
||||||
color: sysPalette.highlight
|
color: sysPalette.highlight
|
||||||
Text {
|
Text {
|
||||||
|
@ -124,20 +141,24 @@ Item {
|
||||||
id: undoColumn
|
id: undoColumn
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: nowRect.bottom
|
anchors.top: nowRect.bottom
|
||||||
width: actionWidth
|
width: historyBrowser.actionWidth
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: history.undoCount
|
model: historyBrowser.undoCount
|
||||||
|
|
||||||
|
|
||||||
HistoryItem {
|
HistoryItem {
|
||||||
id: undoButton
|
id: undoButton
|
||||||
width: actionWidth
|
width: historyBrowser.actionWidth
|
||||||
//height: actionHeight
|
//height: actionHeight
|
||||||
isRedo: false
|
isRedo: false
|
||||||
idx: index
|
|
||||||
darkTheme: historyBrowser.darkTheme
|
darkTheme: historyBrowser.darkTheme
|
||||||
hidden: !(filterInput.value == "" || content.includes(filterInput.value))
|
hidden: !(filterInput.value == "" || content.includes(filterInput.value))
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
undoTimer.toUndoCount = +index+1
|
||||||
|
undoTimer.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,7 +171,39 @@ Item {
|
||||||
transform: Rotation { origin.x: 30; origin.y: 30; angle: 270}
|
transform: Rotation { origin.x: 30; origin.y: 30; angle: 270}
|
||||||
height: 60
|
height: 60
|
||||||
width: 20
|
width: 20
|
||||||
visible: history.undoCount > 0
|
visible: historyBrowser.undoCount > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: undoTimer
|
||||||
|
interval: 5; running: false; repeat: true
|
||||||
|
property int toUndoCount: 0
|
||||||
|
onTriggered: {
|
||||||
|
if(toUndoCount > 0) {
|
||||||
|
Modules.History.undo()
|
||||||
|
if(toUndoCount % 3 === 1)
|
||||||
|
Modules.Canvas.requestPaint()
|
||||||
|
toUndoCount--;
|
||||||
|
} else {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: redoTimer
|
||||||
|
interval: 5; running: false; repeat: true
|
||||||
|
property int toRedoCount: 0
|
||||||
|
onTriggered: {
|
||||||
|
if(toRedoCount > 0) {
|
||||||
|
Modules.History.redo()
|
||||||
|
if(toRedoCount % 3 === 1)
|
||||||
|
Modules.Canvas.requestPaint()
|
||||||
|
toRedoCount--;
|
||||||
|
} else {
|
||||||
|
running = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,6 +216,18 @@ Item {
|
||||||
let hex = sysPalette.windowText.toString()
|
let hex = sysPalette.windowText.toString()
|
||||||
// We only check the first parameter, as on all normal OSes, text color is grayscale.
|
// We only check the first parameter, as on all normal OSes, text color is grayscale.
|
||||||
return parseInt(hex.substr(1,2), 16) > 128
|
return parseInt(hex.substr(1,2), 16) > 128
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
Modules.History.initialize({
|
||||||
|
helper: Helper,
|
||||||
|
themeTextColor: sysPalette.windowText.toString(),
|
||||||
|
imageDepth: Screen.devicePixelRatio,
|
||||||
|
fontSize: 14
|
||||||
|
})
|
||||||
|
Modules.History.on("updated undone redone", () => {
|
||||||
|
undoCount = Modules.History.undoStack.length
|
||||||
|
redoCount = Modules.History.redoStack.length
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,17 +41,17 @@ Button {
|
||||||
\qmlproperty bool HistoryItem::isRedo
|
\qmlproperty bool HistoryItem::isRedo
|
||||||
true if the action is in the redo stack, false othewise.
|
true if the action is in the redo stack, false othewise.
|
||||||
*/
|
*/
|
||||||
property bool isRedo
|
required property bool isRedo
|
||||||
/*!
|
/*!
|
||||||
\qmlproperty int HistoryItem::idx
|
\qmlproperty int HistoryItem::index
|
||||||
Index of the item within the HistoryBrowser list.
|
Index of the item within the HistoryBrowser list.
|
||||||
*/
|
*/
|
||||||
property int idx
|
required property int index
|
||||||
/*!
|
/*!
|
||||||
\qmlproperty bool HistoryItem::darkTheme
|
\qmlproperty bool HistoryItem::darkTheme
|
||||||
true when the system is running with a dark theme, false otherwise.
|
true when the system is running with a dark theme, false otherwise.
|
||||||
*/
|
*/
|
||||||
property bool darkTheme
|
required property bool darkTheme
|
||||||
/*!
|
/*!
|
||||||
\qmlproperty bool HistoryItem::hidden
|
\qmlproperty bool HistoryItem::hidden
|
||||||
true when the item is filtered out, false otherwise.
|
true when the item is filtered out, false otherwise.
|
||||||
|
@ -61,7 +61,7 @@ Button {
|
||||||
\qmlproperty int HistoryItem::historyAction
|
\qmlproperty int HistoryItem::historyAction
|
||||||
Associated history action.
|
Associated history action.
|
||||||
*/
|
*/
|
||||||
readonly property var historyAction: isRedo ? history.redoStack[idx] : history.undoStack[history.undoCount-idx-1]
|
readonly property var historyAction: isRedo ? Modules.History.redoStack.at(index) : Modules.History.undoStack.at(-index-1)
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmlproperty int HistoryItem::actionHeight
|
\qmlproperty int HistoryItem::actionHeight
|
||||||
|
@ -147,13 +147,6 @@ Button {
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
ToolTip.delay: 200
|
ToolTip.delay: 200
|
||||||
ToolTip.text: content
|
ToolTip.text: content
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
if(isRedo)
|
|
||||||
history.redoMultipleDefered(history.redoCount-idx)
|
|
||||||
else
|
|
||||||
history.undoMultipleDefered(+idx+1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue