diff --git a/NEWS b/NEWS index f009e94..5b82320 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,14 @@ +v11 - February 2021 +=================== + +* GS 40 compatibility (40.beta) +* Gtk4 port (preferences) +* Toggle animations (background, grid and square area) +* Multi-line text elements +* Start a new line with "Enter" (text tool) +* Add "centered" text alignment +* Convenient marks for rotation and stretch transformations + v10 - October 2020 ================== diff --git a/area.js b/area.js index 93d54ed..f368a76 100644 --- a/area.js +++ b/area.js @@ -1,5 +1,5 @@ /* jslint esversion: 6 */ -/* exported Tools, DrawingArea */ +/* exported Tool, DrawingArea */ /* * Copyright 2019 Abakkk @@ -48,21 +48,22 @@ const pgettext = imports.gettext.domain(Me.metadata['gettext-domain']).pgettext; const MOTION_TIME = 1; // ms, time accuracy for free drawing, max is about 33 ms. The lower it is, the smoother the drawing is. const TEXT_CURSOR_TIME = 600; // ms const ELEMENT_GRABBER_TIME = 80; // ms, default is about 16 ms +const TOGGLE_ANIMATION_DURATION = 300; // ms const GRID_TILES_HORIZONTAL_NUMBER = 30; const COLOR_PICKER_EXTENSION_UUID = 'color-picker@tuberry'; const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_'); -const { Shapes, Transformations } = Elements; +const { Shape, TextAlignment, Transformation } = Elements; const { DisplayStrings } = Menu; const FontGenericFamilies = ['Sans-Serif', 'Serif', 'Monospace', 'Cursive', 'Fantasy']; -const Manipulations = { MOVE: 100, RESIZE: 101, MIRROR: 102 }; -var Tools = Object.assign({ +const Manipulation = { MOVE: 100, RESIZE: 101, MIRROR: 102 }; +var Tool = Object.assign({ getNameOf: function(value) { return Object.keys(this).find(key => this[key] == value); } -}, Shapes, Manipulations); -Object.defineProperty(Tools, 'getNameOf', { enumerable: false }); +}, Shape, Manipulation); +Object.defineProperty(Tool, 'getNameOf', { enumerable: false }); // toJSON provides a string suitable for SVG color attribute whereas // toString provides a string suitable for displaying the color name to the user. @@ -143,14 +144,15 @@ var DrawingArea = new Lang.Class({ this.layerContainer.add_child(this.foreLayer); this.gridLayer = new DrawingLayer(this._repaintGrid.bind(this)); this.gridLayer.hide(); + this.gridLayer.opacity = 0; this.layerContainer.add_child(this.gridLayer); this.elements = []; this.undoneElements = []; this.currentElement = null; - this.currentTool = Shapes.NONE; + this.currentTool = Shape.NONE; this.currentImage = null; - this.currentTextRightAligned = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL; + this.currentTextAlignment = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL ? TextAlignment.RIGHT : TextAlignment.LEFT; let fontName = St.Settings && St.Settings.get().font_name || Convenience.getSettings('org.gnome.desktop.interface').get_string('font-name'); this.currentFont = Pango.FontDescription.from_string(fontName); this.currentFont.unset_fields(Pango.FontMask.SIZE); @@ -176,7 +178,7 @@ var DrawingArea = new Lang.Class({ get menu() { if (!this._menu) - this._menu = new Menu.DrawingMenu(this, this.monitor, Tools, this.areaManagerUtils); + this._menu = new Menu.DrawingMenu(this, this.monitor, Tool, this.areaManagerUtils); return this._menu; }, @@ -249,7 +251,7 @@ var DrawingArea = new Lang.Class({ get hasManipulationTool() { // No Object.values method in GS 3.24. - return Object.keys(Manipulations).map(key => Manipulations[key]).indexOf(this.currentTool) != -1; + return Object.keys(Manipulation).map(key => Manipulation[key]).indexOf(this.currentTool) != -1; }, // Boolean wrapper for switch menu item. @@ -315,25 +317,26 @@ var DrawingArea = new Lang.Class({ for (let i = 0; i < this.elements.length; i++) { cr.save(); - this.elements[i].buildCairo(cr, { showTextRectangle: this.grabbedElement && this.grabbedElement == this.elements[i], - drawTextRectangle: this.grabPoint ? true : false }); + this.elements[i].buildCairo(cr, { showElementBounds: this.grabbedElement && this.grabbedElement == this.elements[i], + drawElementBounds: this.grabPoint ? true : false }); if (this.grabPoint) this._searchElementToGrab(cr, this.elements[i]); if (this.elements[i].fill && !this.elements[i].isStraightLine) { cr.fillPreserve(); - if (this.elements[i].shape == Shapes.NONE || this.elements[i].shape == Shapes.LINE) + if (this.elements[i].shape == Shape.NONE || this.elements[i].shape == Shape.LINE) cr.closePath(); } cr.stroke(); + this.elements[i]._addMarks(cr); cr.restore(); } if (this.currentElement && this.currentElement.eraser) { this.currentElement.buildCairo(cr, { showTextCursor: this.textHasCursor, - showTextRectangle: this.currentElement.shape != Shapes.TEXT || !this.isWriting, + showElementBounds: this.currentElement.shape != Shape.TEXT || !this.isWriting, dummyStroke: this.currentElement.fill && this.currentElement.line.lineWidth == 0 }); cr.stroke(); } @@ -344,7 +347,7 @@ var DrawingArea = new Lang.Class({ return; this.currentElement.buildCairo(cr, { showTextCursor: this.textHasCursor, - showTextRectangle: this.currentElement.shape != Shapes.TEXT || !this.isWriting, + showElementBounds: this.currentElement.shape != Shape.TEXT || !this.isWriting, dummyStroke: this.currentElement.fill && this.currentElement.line.lineWidth == 0 }); cr.stroke(); }, @@ -377,11 +380,11 @@ var DrawingArea = new Lang.Class({ }, _getHasImageBack: function() { - return this.elements.some(element => element.shape == Shapes.IMAGE); + return this.elements.some(element => element.shape == Shape.IMAGE); }, _getHasImageFore: function() { - return this.currentElement && this.currentElement.shape == Shapes.IMAGE || false; + return this.currentElement && this.currentElement.shape == Shape.IMAGE || false; }, _redisplay: function() { @@ -409,7 +412,7 @@ var DrawingArea = new Lang.Class({ let controlPressed = event.has_control_modifier(); let shiftPressed = event.has_shift_modifier(); - if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.isWriting) + if (this.currentElement && this.currentElement.shape == Shape.TEXT && this.isWriting) // finish writing this._stopWriting(); @@ -469,7 +472,7 @@ var DrawingArea = new Lang.Class({ }, _onKeyPressed: function(actor, event) { - if (this.currentElement && this.currentElement.shape == Shapes.LINE && + if (this.currentElement && this.currentElement.shape == Shape.LINE && (event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == Clutter.KEY_KP_Enter || event.get_key_symbol() == Clutter.KEY_Control_L)) { @@ -483,7 +486,7 @@ var DrawingArea = new Lang.Class({ this._redisplay(); return Clutter.EVENT_STOP; } else if (this.currentElement && - (this.currentElement.shape == Shapes.POLYGON || this.currentElement.shape == Shapes.POLYLINE) && + (this.currentElement.shape == Shape.POLYGON || this.currentElement.shape == Shape.POLYLINE) && (event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == Clutter.KEY_KP_Enter)) { this.currentElement.addPoint(); @@ -558,7 +561,7 @@ var DrawingArea = new Lang.Class({ if (!success) return; - if (this.currentTool == Manipulations.MIRROR) { + if (this.currentTool == Manipulation.MIRROR) { this.grabbedElementLocked = !this.grabbedElementLocked; if (this.grabbedElementLocked) { this.updatePointerCursor(); @@ -589,12 +592,12 @@ var DrawingArea = new Lang.Class({ let undoable = !duplicate; - if (this.currentTool == Manipulations.MOVE) - this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.ROTATION : Transformations.TRANSLATION, undoable); - else if (this.currentTool == Manipulations.RESIZE) - this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.STRETCH : Transformations.SCALE_PRESERVE, undoable); - else if (this.currentTool == Manipulations.MIRROR) { - this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.INVERSION : Transformations.REFLECTION, undoable); + if (this.currentTool == Manipulation.MOVE) + this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformation.ROTATION : Transformation.TRANSLATION, undoable); + else if (this.currentTool == Manipulation.RESIZE) + this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformation.STRETCH : Transformation.SCALE_PRESERVE, undoable); + else if (this.currentTool == Manipulation.MIRROR) { + this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformation.INVERSION : Transformation.REFLECTION, undoable); this._redisplay(); } @@ -614,28 +617,28 @@ var DrawingArea = new Lang.Class({ _updateTransforming: function(x, y, controlPressed) { let undoable = this.grabbedElement.lastTransformation.undoable || false; - if (controlPressed && this.grabbedElement.lastTransformation.type == Transformations.TRANSLATION) { + if (controlPressed && this.grabbedElement.lastTransformation.type == Transformation.TRANSLATION) { this.grabbedElement.stopTransformation(); - this.grabbedElement.startTransformation(x, y, Transformations.ROTATION, undoable); - } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.ROTATION) { + this.grabbedElement.startTransformation(x, y, Transformation.ROTATION, undoable); + } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformation.ROTATION) { this.grabbedElement.stopTransformation(); - this.grabbedElement.startTransformation(x, y, Transformations.TRANSLATION, undoable); + this.grabbedElement.startTransformation(x, y, Transformation.TRANSLATION, undoable); } - if (controlPressed && this.grabbedElement.lastTransformation.type == Transformations.SCALE_PRESERVE) { + if (controlPressed && this.grabbedElement.lastTransformation.type == Transformation.SCALE_PRESERVE) { this.grabbedElement.stopTransformation(); - this.grabbedElement.startTransformation(x, y, Transformations.STRETCH, undoable); - } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.STRETCH) { + this.grabbedElement.startTransformation(x, y, Transformation.STRETCH, undoable); + } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformation.STRETCH) { this.grabbedElement.stopTransformation(); - this.grabbedElement.startTransformation(x, y, Transformations.SCALE_PRESERVE, undoable); + this.grabbedElement.startTransformation(x, y, Transformation.SCALE_PRESERVE, undoable); } - if (controlPressed && this.grabbedElement.lastTransformation.type == Transformations.REFLECTION) { + if (controlPressed && this.grabbedElement.lastTransformation.type == Transformation.REFLECTION) { this.grabbedElement.transformations.pop(); - this.grabbedElement.startTransformation(x, y, Transformations.INVERSION, undoable); - } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.INVERSION) { + this.grabbedElement.startTransformation(x, y, Transformation.INVERSION, undoable); + } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformation.INVERSION) { this.grabbedElement.transformations.pop(); - this.grabbedElement.startTransformation(x, y, Transformations.REFLECTION, undoable); + this.grabbedElement.startTransformation(x, y, Transformation.REFLECTION, undoable); } this.grabbedElement.updateTransformation(x, y); @@ -667,7 +670,7 @@ var DrawingArea = new Lang.Class({ this._stopDrawing(); }); - if (this.currentTool == Shapes.TEXT) { + if (this.currentTool == Shape.TEXT) { this.currentElement = new Elements.DrawingElement({ shape: this.currentTool, color: this.currentColor, @@ -675,10 +678,10 @@ var DrawingArea = new Lang.Class({ font: this.currentFont.copy(), // Translators: initial content of the text area text: pgettext("text-area-content", "Text"), - textRightAligned: this.currentTextRightAligned, + textAlignment: this.currentTextAlignment, points: [] }); - } else if (this.currentTool == Shapes.IMAGE) { + } else if (this.currentTool == Shape.IMAGE) { this.currentElement = new Elements.DrawingElement({ shape: this.currentTool, color: this.currentColor, @@ -701,8 +704,8 @@ var DrawingArea = new Lang.Class({ this.currentElement.startDrawing(startX, startY); - if (this.currentTool == Shapes.POLYGON || this.currentTool == Shapes.POLYLINE) { - let icon = Files.Icons[this.currentTool == Shapes.POLYGON ? 'TOOL_POLYGON' : 'TOOL_POLYLINE']; + if (this.currentTool == Shape.POLYGON || this.currentTool == Shape.POLYLINE) { + let icon = Files.Icons[this.currentTool == Shape.POLYGON ? 'TOOL_POLYGON' : 'TOOL_POLYLINE']; // Translators: %s is a key label this.emit('show-osd', icon, _("Press %s to mark vertices") .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true); @@ -725,16 +728,24 @@ var DrawingArea = new Lang.Class({ let controlPressed = event.has_control_modifier(); this._updateDrawing(x, y, controlPressed); - if (this.currentTool == Shapes.NONE) { + if (this.currentTool == Shape.NONE) { let device = event.get_device(); let sequence = event.get_event_sequence(); // Minimum time between two motion events is about 33 ms. // Add intermediate points to make quick free drawings smoother. this.motionTimeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, MOTION_TIME, () => { - let [success, coords] = device.get_coords(sequence); - if (!success) - return GLib.SOURCE_CONTINUE; + let success, coords; + if (device.get_coords) { + [success, coords] = device.get_coords(sequence); + if (!success) + return GLib.SOURCE_CONTINUE; + } else { + // GS 40+, device.get_coords() has been removed + // and seat.query_state() is unusable with null sequences. + let pointer = global.get_pointer(); + coords = { x: pointer[0], y: pointer[1] }; + } let [s, x, y] = this._transformStagePoint(coords.x, coords.y); if (!s) @@ -779,14 +790,14 @@ var DrawingArea = new Lang.Class({ } // skip when a polygon has not at least 3 points - if (this.currentElement && this.currentElement.shape == Shapes.POLYGON && this.currentElement.points.length < 3) + if (this.currentElement && this.currentElement.shape == Shape.POLYGON && this.currentElement.points.length < 3) this.currentElement = null; if (this.currentElement) this.currentElement.stopDrawing(); if (this.currentElement && this.currentElement.points.length >= 2) { - if (this.currentElement.shape == Shapes.TEXT && !this.isWriting) { + if (this.currentElement.shape == Shape.TEXT && !this.isWriting) { this._startWriting(); return; } @@ -806,12 +817,14 @@ var DrawingArea = new Lang.Class({ this.currentElement.cursorPosition = 0; // Translators: %s is a key label this.emit('show-osd', Files.Icons.TOOL_TEXT, _("Press %s\nto start a new line") - .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 1)), "", -1, true); + .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true); this._updateTextCursorTimeout(); this.textHasCursor = true; this._redisplay(); - // Do not hide and do not set opacity to 0 because ibusCandidatePopup need a mapped text entry to init correctly its position. + // Do not hide and do not set opacity to 0 because: + // 1. ibusCandidatePopup need a mapped text entry to init correctly its position. + // 2. 'cursor-changed' signal is no emitted if the text entry is not visible. this.textEntry = new St.Entry({ opacity: 1, x: stageX + x, y: stageY + y }); this.insert_child_below(this.textEntry, null); this.textEntry.grab_key_focus(); @@ -830,17 +843,27 @@ var DrawingArea = new Lang.Class({ this.textEntry.connect('destroy', () => ibusCandidatePopup.disconnect(this.ibusHandler)); } + this.textEntry.clutterText.set_single_line_mode(false); + this.textEntry.clutterText.set_activatable(false); this.textEntry.clutterText.connect('activate', (clutterText) => { this._stopWriting(); }); - this.textEntry.clutterText.connect('text-changed', (clutterText) => { - GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { - this.currentElement.text = clutterText.text; - this.currentElement.cursorPosition = clutterText.cursorPosition; - this._updateTextCursorTimeout(); - this._redisplay(); - }); + let showCursorOnPositionChanged = true; + this.textEntry.clutterText.connect('text-changed', clutterText => { + this.textEntry.y = stageY + y + (this.textEntry.clutterText.get_layout().get_line_count() - 1) * this.currentElement.height; + this.currentElement.text = clutterText.text; + showCursorOnPositionChanged = false; + this._redisplay(); + }); + + this.textEntry.clutterText.connect('cursor-changed', clutterText => { + this.currentElement.cursorPosition = clutterText.cursorPosition; + this._updateTextCursorTimeout(); + let cursorPosition = clutterText.cursorPosition == -1 ? clutterText.text.length : clutterText.cursorPosition; + this.textHasCursor = showCursorOnPositionChanged || GLib.unichar_isspace(clutterText.text.charAt(cursorPosition - 1)); + showCursorOnPositionChanged = true; + this._redisplay(); }); this.textEntry.clutterText.connect('key-press-event', (clutterText, event) => { @@ -848,56 +871,23 @@ var DrawingArea = new Lang.Class({ this.currentElement.text = ""; this._stopWriting(); return Clutter.EVENT_STOP; - } else if (event.has_shift_modifier() && - (event.get_key_symbol() == Clutter.KEY_Return || - event.get_key_symbol() == Clutter.KEY_KP_Enter)) { - let startNewLine = true; - this._stopWriting(startNewLine); - clutterText.text = ""; - return Clutter.EVENT_STOP; - } - - // 'cursor-changed' signal is not emitted if the text entry is not visible. - // So key events related to the cursor must be listened. - if (event.get_key_symbol() == Clutter.KEY_Left || event.get_key_symbol() == Clutter.KEY_Right || - event.get_key_symbol() == Clutter.KEY_Home || event.get_key_symbol() == Clutter.KEY_End) { - GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { - this.currentElement.cursorPosition = clutterText.cursorPosition; - this._updateTextCursorTimeout(); - this.textHasCursor = true; - this._redisplay(); - }); } return Clutter.EVENT_PROPAGATE; }); }, - _stopWriting: function(startNewLine) { + _stopWriting: function() { if (this.currentElement.text.length > 0) this.elements.push(this.currentElement); - if (startNewLine && this.currentElement.points.length == 2) { - this.currentElement.lineIndex = this.currentElement.lineIndex || 0; - // copy object, the original keep existing in this.elements - this.currentElement = Object.create(this.currentElement); - this.currentElement.lineIndex ++; - // define a new 'points' array, the original keep existing in this.elements - this.currentElement.points = [ - [this.currentElement.points[0][0], this.currentElement.points[0][1] + this.currentElement.height], - [this.currentElement.points[1][0], this.currentElement.points[1][1] + this.currentElement.height] - ]; - this.currentElement.text = ""; - this.textEntry.set_y(this.currentElement.y); - } else { - this.currentElement = null; - this._stopTextCursorTimeout(); - this.textEntry.destroy(); - delete this.textEntry; - this.grab_key_focus(); - this.updateActionMode(); - this.updatePointerCursor(); - } + this.currentElement = null; + this._stopTextCursorTimeout(); + this.textEntry.destroy(); + delete this.textEntry; + this.grab_key_focus(); + this.updateActionMode(); + this.updatePointerCursor(); this._redisplay(); }, @@ -910,15 +900,15 @@ var DrawingArea = new Lang.Class({ }, updatePointerCursor: function(controlPressed) { - if (this.currentTool == Manipulations.MIRROR && this.grabbedElementLocked) + if (this.currentTool == Manipulation.MIRROR && this.grabbedElementLocked) this.setPointerCursor('CROSSHAIR'); else if (this.hasManipulationTool) this.setPointerCursor(this.grabbedElement ? 'MOVE_OR_RESIZE_WINDOW' : 'DEFAULT'); - else if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.isWriting) + else if (this.currentElement && this.currentElement.shape == Shape.TEXT && this.isWriting) this.setPointerCursor('IBEAM'); else if (!this.currentElement) - this.setPointerCursor(this.currentTool == Shapes.NONE ? 'POINTING_HAND' : 'CROSSHAIR'); - else if (this.currentElement.shape != Shapes.NONE && controlPressed) + this.setPointerCursor(this.currentTool == Shape.NONE ? 'POINTING_HAND' : 'CROSSHAIR'); + else if (this.currentElement.shape != Shape.NONE && controlPressed) this.setPointerCursor('MOVE_OR_RESIZE_WINDOW'); }, @@ -1007,7 +997,7 @@ var DrawingArea = new Lang.Class({ }, smoothLastElement: function() { - if (this.elements.length > 0 && this.elements[this.elements.length - 1].shape == Shapes.NONE) { + if (this.elements.length > 0 && this.elements[this.elements.length - 1].shape == Shape.NONE) { this.elements[this.elements.length - 1].smoothAll(); this._redisplay(); } @@ -1015,7 +1005,16 @@ var DrawingArea = new Lang.Class({ toggleBackground: function() { this.hasBackground = !this.hasBackground; - this.set_background_color(this.hasBackground ? this.areaBackgroundColor : null); + let backgroundColor = this.hasBackground ? this.areaBackgroundColor : Clutter.Color.get_static(Clutter.StaticColor.TRANSPARENT); + + if (this.ease) { + this.remove_all_transitions(); + this.ease({ backgroundColor, + duration: TOGGLE_ANIMATION_DURATION, + transition: Clutter.AnimationMode.EASE_IN_OUT_QUAD }); + } else { + this.set_background_color(backgroundColor); + } }, get hasGrid() { @@ -1024,19 +1023,43 @@ var DrawingArea = new Lang.Class({ toggleGrid: function() { // The grid layer is repainted when the visibility changes. - this.gridLayer.visible = !this.gridLayer.visible; + if (this.gridLayer.ease) { + this.gridLayer.remove_all_transitions(); + let visible = !this.gridLayer.visible; + this.gridLayer.visible = true; + this.gridLayer.ease({ opacity: visible ? 255 : 0, + duration: TOGGLE_ANIMATION_DURATION, + transition: Clutter.AnimationMode.EASE_IN_OUT_QUAD, + onStopped: () => this.gridLayer.visible = visible }); + } else { + this.gridLayer.visible = !this.gridLayer.visible; + } }, toggleSquareArea: function() { this.isSquareArea = !this.isSquareArea; + let x, y, width, height, onComplete; + if (this.isSquareArea) { - this.layerContainer.set_position((this.monitor.width - this.squareAreaSize) / 2, (this.monitor.height - this.squareAreaSize) / 2); - this.layerContainer.set_size(this.squareAreaSize, this.squareAreaSize); this.layerContainer.add_style_class_name('draw-on-your-screen-square-area'); + [x, y] = [(this.monitor.width - this.squareAreaSize) / 2, (this.monitor.height - this.squareAreaSize) / 2]; + width = height = this.squareAreaSize; + onComplete = () => {}; } else { - this.layerContainer.set_position(0, 0); - this.layerContainer.set_size(this.monitor.width, this.monitor.height); - this.layerContainer.remove_style_class_name('draw-on-your-screen-square-area'); + x = y = 0; + [width, height] = [this.monitor.width, this.monitor.height]; + onComplete = () => this.layerContainer.remove_style_class_name('draw-on-your-screen-square-area'); + } + + if (this.layerContainer.ease) { + this.layerContainer.remove_all_transitions(); + this.layerContainer.ease({ x, y, width, height, onComplete, + duration: TOGGLE_ANIMATION_DURATION, + transition: Clutter.AnimationMode.EASE_OUT_QUAD }); + } else { + this.layerContainer.set_position(x, y); + this.layerContainer.set_size(width, height); + onComplete(); } }, @@ -1055,7 +1078,7 @@ var DrawingArea = new Lang.Class({ selectTool: function(tool) { this.currentTool = tool; - this.emit('show-osd', Files.Icons[`TOOL_${Tools.getNameOf(tool)}`] || null, DisplayStrings.Tool[tool], "", -1, false); + this.emit('show-osd', Files.Icons[`TOOL_${Tool.getNameOf(tool)}`] || null, DisplayStrings.Tool[tool], "", -1, false); this.updatePointerCursor(); }, @@ -1137,13 +1160,13 @@ var DrawingArea = new Lang.Class({ }, switchTextAlignment: function() { - this.currentTextRightAligned = !this.currentTextRightAligned; - if (this.currentElement && this.currentElement.textRightAligned !== undefined) { - this.currentElement.textRightAligned = this.currentTextRightAligned; + this.currentTextAlignment = this.currentTextAlignment == 2 ? 0 : this.currentTextAlignment + 1; + if (this.currentElement && this.currentElement.textAlignment != this.currentTextAlignment) { + this.currentElement.textAlignment = this.currentTextAlignment; this._redisplay(); } - let icon = Files.Icons[this.currentTextRightAligned ? 'RIGHT_ALIGNED' : 'LEFT_ALIGNED']; - this.emit('show-osd', icon, DisplayStrings.getTextAlignment(this.currentTextRightAligned), "", -1, false); + let icon = Files.Icons[this.currentTextAlignment == TextAlignment.RIGHT ? 'RIGHT_ALIGNED' : this.currentTextAlignment == TextAlignment.CENTER ? 'CENTERED' : 'LEFT_ALIGNED']; + this.emit('show-osd', icon, DisplayStrings.TextAlignment[this.currentTextAlignment], "", -1, false); }, switchImageFile: function(reverse) { @@ -1155,7 +1178,7 @@ var DrawingArea = new Lang.Class({ pasteImageFiles: function() { Files.Images.addImagesFromClipboard(lastImage => { this.currentImage = lastImage; - this.currentTool = Shapes.IMAGE; + this.currentTool = Shape.IMAGE; this.updatePointerCursor(); this.emit('show-osd', this.currentImage.gicon, this.currentImage.toString(), "", -1, false); }); @@ -1355,7 +1378,7 @@ var DrawingArea = new Lang.Class({ this._stopAll(); let prefixes = 'xmlns="http://www.w3.org/2000/svg"'; - if (this.elements.some(element => element.shape == Shapes.IMAGE)) + if (this.elements.some(element => element.shape == Shape.IMAGE)) prefixes += ' xmlns:xlink="http://www.w3.org/1999/xlink"'; let content = ``; let backgroundColorString = this.hasBackground ? String(this.areaBackgroundColor) : 'transparent'; diff --git a/elements.js b/elements.js index 5890187..bbbb1d2 100644 --- a/elements.js +++ b/elements.js @@ -1,5 +1,5 @@ /* jslint esversion: 6 */ -/* exported Shapes, Transformations, getAllFontFamilies, DrawingElement */ +/* exported Shape, TextAlignment, Transformation, getAllFontFamilies, DrawingElement */ /* * Copyright 2019 Abakkk @@ -30,8 +30,9 @@ const PangoCairo = imports.gi.PangoCairo; const Me = imports.misc.extensionUtils.getCurrentExtension(); const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_'); -var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6, IMAGE: 7 }; -var Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, STRETCH: 3, REFLECTION: 4, INVERSION: 5, SMOOTH: 100 }; +var Shape = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6, IMAGE: 7 }; +var TextAlignment = { LEFT: 0, CENTER: 1, RIGHT: 2 }; +var Transformation = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, STRETCH: 3, REFLECTION: 4, INVERSION: 5, SMOOTH: 100 }; var getAllFontFamilies = function() { return PangoCairo.font_map_get_default().list_families().map(fontFamily => fontFamily.get_name()).sort((a,b) => a.localeCompare(b)); @@ -63,10 +64,11 @@ const MIN_TRANSLATION_DISTANCE = 1; // px const MIN_ROTATION_ANGLE = Math.PI / 1000; // rad const MIN_DRAWING_SIZE = 3; // px const MIN_INTERMEDIATE_POINT_DISTANCE = 1; // px, the higher it is, the fewer points there will be +const MARK_COLOR = Clutter.Color.get_static(Clutter.StaticColor.BLUE); var DrawingElement = function(params) { - return params.shape == Shapes.TEXT ? new TextElement(params) : - params.shape == Shapes.IMAGE ? new ImageElement(params) : + return params.shape == Shape.TEXT ? new TextElement(params) : + params.shape == Shape.IMAGE ? new ImageElement(params) : new _DrawingElement(params); }; @@ -103,14 +105,19 @@ const _DrawingElement = new Lang.Class({ if (params.transform && params.transform.center) { let angle = (params.transform.angle || 0) + (params.transform.startAngle || 0); if (angle) - this.transformations.push({ type: Transformations.ROTATION, angle: angle }); + this.transformations.push({ type: Transformation.ROTATION, angle: angle }); } - if (params.shape == Shapes.ELLIPSE && params.transform && params.transform.ratio && params.transform.ratio != 1 && params.points.length >= 2) { + if (params.shape == Shape.ELLIPSE && params.transform && params.transform.ratio && params.transform.ratio != 1 && params.points.length >= 2) { let [ratio, p0, p1] = [params.transform.ratio, params.points[0], params.points[1]]; // Add a fake point that will give the right ellipse ratio when building the element. this.points.push([ratio * (p1[0] - p0[0]) + p0[0], ratio * (p1[1] - p0[1]) + p0[1]]); } delete this.transform; + + // v10- + if (this.textRightAligned) + this.textAlignment = TextAlignment.RIGHT; + delete this.textRightAligned; }, // toJSON is called by JSON.stringify @@ -123,7 +130,7 @@ const _DrawingElement = new Lang.Class({ fill: this.fill, fillRule: this.fillRule, eraser: this.eraser, - transformations: this.transformations.filter(transformation => transformation.type != Transformations.SMOOTH) + transformations: this.transformations.filter(transformation => transformation.type != Transformation.SMOOTH) .map(transformation => Object.assign({}, transformation, { undoable: undefined })), points: this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100]) }; @@ -133,18 +140,6 @@ const _DrawingElement = new Lang.Class({ if (this.color) Clutter.cairo_set_source_color(cr, this.color); - if (this.showSymmetryElement) { - let transformation = this.lastTransformation; - setDummyStroke(cr); - if (transformation.type == Transformations.REFLECTION) { - cr.moveTo(transformation.startX, transformation.startY); - cr.lineTo(transformation.endX, transformation.endY); - } else { - cr.arc(transformation.endX, transformation.endY, INVERSION_CIRCLE_RADIUS, 0, 2 * Math.PI); - } - cr.stroke(); - } - if (this.line) { cr.setLineCap(this.line.lineCap); cr.setLineJoin(this.line.lineJoin); @@ -171,21 +166,21 @@ const _DrawingElement = new Lang.Class({ } this.transformations.slice(0).reverse().forEach(transformation => { - if (transformation.type == Transformations.TRANSLATION) { + if (transformation.type == Transformation.TRANSLATION) { cr.translate(transformation.slideX, transformation.slideY); - } else if (transformation.type == Transformations.ROTATION) { + } else if (transformation.type == Transformation.ROTATION) { let center = this._getTransformedCenter(transformation); cr.translate(center[0], center[1]); cr.rotate(transformation.angle); cr.translate(-center[0], -center[1]); - } else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.STRETCH) { + } else if (transformation.type == Transformation.SCALE_PRESERVE || transformation.type == Transformation.STRETCH) { let center = this._getTransformedCenter(transformation); cr.translate(center[0], center[1]); cr.rotate(transformation.angle); cr.scale(transformation.scaleX, transformation.scaleY); cr.rotate(-transformation.angle); cr.translate(-center[0], -center[1]); - } else if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) { + } else if (transformation.type == Transformation.REFLECTION || transformation.type == Transformation.INVERSION) { cr.translate(transformation.slideX, transformation.slideY); cr.rotate(transformation.angle); cr.scale(transformation.scaleX, transformation.scaleY); @@ -199,24 +194,61 @@ const _DrawingElement = new Lang.Class({ cr.identityMatrix(); }, + _addMarks: function(cr) { + if (this.showSymmetryElement) { + setDummyStroke(cr); + Clutter.cairo_set_source_color(cr, MARK_COLOR); + + let transformation = this.lastTransformation; + if (transformation.type == Transformation.REFLECTION) { + cr.moveTo(transformation.startX, transformation.startY); + cr.lineTo(transformation.endX, transformation.endY); + } else { + cr.arc(transformation.endX, transformation.endY, INVERSION_CIRCLE_RADIUS, 0, 2 * Math.PI); + } + cr.stroke(); + } + + if (this.showRotationCenter) { + setDummyStroke(cr); + Clutter.cairo_set_source_color(cr, MARK_COLOR); + + let center = this._getTransformedCenter(this.lastTransformation); + cr.arc(center[0], center[1], INVERSION_CIRCLE_RADIUS, 0, 2 * Math.PI); + cr.stroke(); + } + + if (this.showStretchAxes) { + setDummyStroke(cr); + Clutter.cairo_set_source_color(cr, MARK_COLOR); + + let center = this._getTransformedCenter(this.lastTransformation); + for (let i = 0; i <=1; i++) { + cr.moveTo(center[0] - 1000 * Math.cos(i * Math.PI / 2), center[1] - 1000 * Math.sin(i * Math.PI / 2)); + cr.lineTo(center[0] + 1000 * Math.cos(i * Math.PI / 2), center[1] + 1000 * Math.sin(i * Math.PI / 2)); + } + cr.stroke(); + } + }, + _drawCairo: function(cr, params) { let [points, shape] = [this.points, this.shape]; - if (shape == Shapes.LINE && points.length == 3) { + if (shape == Shape.LINE && points.length == 3) { cr.moveTo(points[0][0], points[0][1]); cr.curveTo(points[0][0], points[0][1], points[1][0], points[1][1], points[2][0], points[2][1]); - } else if (shape == Shapes.LINE && points.length == 4) { + } else if (shape == Shape.LINE && points.length == 4) { cr.moveTo(points[0][0], points[0][1]); cr.curveTo(points[1][0], points[1][1], points[2][0], points[2][1], points[3][0], points[3][1]); - } else if (shape == Shapes.NONE || shape == Shapes.LINE) { + } else if (shape == Shape.NONE || shape == Shape.LINE) { cr.moveTo(points[0][0], points[0][1]); for (let j = 1; j < points.length; j++) { cr.lineTo(points[j][0], points[j][1]); } - } else if (shape == Shapes.ELLIPSE && points.length >= 2) { + } else if (shape == Shape.ELLIPSE && points.length >= 2) { let radius = Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]); let ratio = 1; @@ -232,15 +264,15 @@ const _DrawingElement = new Lang.Class({ } else cr.arc(points[0][0], points[0][1], radius, 0, 2 * Math.PI); - } else if (shape == Shapes.RECTANGLE && points.length == 2) { + } else if (shape == Shape.RECTANGLE && points.length == 2) { cr.rectangle(points[0][0], points[0][1], points[1][0] - points[0][0], points[1][1] - points[0][1]); - } else if ((shape == Shapes.POLYGON || shape == Shapes.POLYLINE) && points.length >= 2) { + } else if ((shape == Shape.POLYGON || shape == Shape.POLYLINE) && points.length >= 2) { cr.moveTo(points[0][0], points[0][1]); for (let j = 1; j < points.length; j++) { cr.lineTo(points[j][0], points[j][1]); } - if (shape == Shapes.POLYGON) + if (shape == Shape.POLYGON) cr.closePath(); } @@ -262,19 +294,19 @@ const _DrawingElement = new Lang.Class({ this.transformations.slice(0).reverse().forEach(transformation => { let center = this._getTransformedCenter(transformation); - if (transformation.type == Transformations.TRANSLATION) { + if (transformation.type == Transformation.TRANSLATION) { transforms.push(['translate', transformation.slideX, transformation.slideY]); - } else if (transformation.type == Transformations.ROTATION) { + } else if (transformation.type == Transformation.ROTATION) { transforms.push(['translate', center[0], center[1]]); transforms.push(['rotate', transformation.angle * RADIAN]); transforms.push(['translate', -center[0], -center[1]]); - } else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.STRETCH) { + } else if (transformation.type == Transformation.SCALE_PRESERVE || transformation.type == Transformation.STRETCH) { transforms.push(['translate', center[0], center[1]]); transforms.push(['rotate', transformation.angle * RADIAN]); transforms.push(['scale', transformation.scaleX, transformation.scaleY]); transforms.push(['rotate', -transformation.angle * RADIAN]); transforms.push(['translate', -center[0], -center[1]]); - } else if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) { + } else if (transformation.type == Transformation.REFLECTION || transformation.type == Transformation.INVERSION) { transforms.push(['translate', transformation.slideX, transformation.slideY]); transforms.push(['rotate', transformation.angle * RADIAN]); transforms.push(['scale', transformation.scaleX, transformation.scaleY]); @@ -342,45 +374,45 @@ const _DrawingElement = new Lang.Class({ attributes += ` stroke-dasharray="${this.dash.array[0]} ${this.dash.array[1]}" stroke-dashoffset="${this.dash.offset}"`; } - if (this.shape == Shapes.LINE && points.length == 4) { + if (this.shape == Shape.LINE && points.length == 4) { row += ``; - } else if (this.shape == Shapes.LINE && points.length == 3) { + } else if (this.shape == Shape.LINE && points.length == 3) { row += ``; - } else if (this.shape == Shapes.LINE) { + } else if (this.shape == Shape.LINE) { row += ``; - } else if (this.shape == Shapes.NONE) { + } else if (this.shape == Shape.NONE) { row += ``; - } else if (this.shape == Shapes.ELLIPSE && points.length == 3) { + } else if (this.shape == Shape.ELLIPSE && points.length == 3) { let ry = Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]); let rx = Math.hypot(points[2][0] - points[0][0], points[2][1] - points[0][1]); row += ``; - } else if (this.shape == Shapes.ELLIPSE && points.length == 2) { + } else if (this.shape == Shape.ELLIPSE && points.length == 2) { let r = Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]); row += ``; - } else if (this.shape == Shapes.RECTANGLE && points.length == 2) { + } else if (this.shape == Shape.RECTANGLE && points.length == 2) { row += ``; - } else if (this.shape == Shapes.POLYGON && points.length >= 3) { + } else if (this.shape == Shape.POLYGON && points.length >= 3) { row += ``; - } else if (this.shape == Shapes.POLYLINE && points.length >= 2) { + } else if (this.shape == Shape.POLYLINE && points.length >= 2) { row += ` { - let lower = attribute.toLowerCase(); - if (this.font[`get_${lower}`]() != Pango[attribute].NORMAL) { - let font = new Pango.FontDescription(); - font[`set_${lower}`](this.font[`get_${lower}`]()); - attributes += ` font-${lower}="${font.to_string()}"`; - } - }); - if (this.font.get_weight() != Pango.Weight.NORMAL) - attributes += ` font-weight="${this.font.get_weight()}"`; - row += `${this.text}`; + // this.font.to_string() is not valid to fill the svg 'font' shorthand property. + // Each property must be filled separately. + ['Stretch', 'Style', 'Variant'].forEach(attribute => { + let lower = attribute.toLowerCase(); + if (this.font[`get_${lower}`]() != Pango[attribute].NORMAL) { + let font = new Pango.FontDescription(); + font[`set_${lower}`](this.font[`get_${lower}`]()); + attributes += ` font-${lower}="${font.to_string()}"`; + } + }); + if (this.font.get_weight() != Pango.Weight.NORMAL) + attributes += ` font-weight="${this.font.get_weight()}"`; + + // It is a fallback for thumbnails. The following layout is not the same than the Cairo one and + // layoutLine.get_pixel_extents does not return the correct value with non-latin fonts. + // An alternative would be to store this.lineWidths in the json. + if (this.textAlignment != TextAlignment.LEFT && !this.lineWidths) { + let clutterText = new Clutter.Text({ text: this.text }); + let layout = clutterText.get_layout(); + let fontSize = height * Pango.SCALE; + this.font.set_absolute_size(fontSize); + layout.set_font_description(this.font); + this.lineWidths = layout.get_lines_readonly().map(layoutLine => layoutLine.get_pixel_extents()[1].width); } + this.text.split(/\r\n|\r|\n/).forEach((text, index) => { + let x = Math.round(this._getLineX(index) * 100) / 100; + let y = Math.round((this.y + this.height * index) * 100) / 100; + row += `\n ${text}`; + }); + return row; }, @@ -827,7 +895,7 @@ const TextElement = new Lang.Class({ _getOriginalCenter: function() { if (!this._originalCenter) { let points = this.points; - this._originalCenter = this.textWidth ? [points[1][0], Math.max(points[0][1], points[1][1]) - this.lineOffset] : + this._originalCenter = points.length == 2 ? [points[1][0], Math.max(points[0][1], points[1][1])] : points.length >= 3 ? getCentroid(points) : getNaiveCenter(points); } @@ -869,10 +937,10 @@ const ImageElement = new Lang.Class({ cr.fill(); cr.restore(); - if (params.showTextRectangle) { + if (params.showElementBounds) { cr.rectangle(x, y, width, height); setDummyStroke(cr); - } else if (params.drawTextRectangle) { + } else if (params.drawElementBounds) { cr.rectangle(x, y, width, height); // Only draw the rectangle to find the element, not to show it. cr.setLineWidth(0); diff --git a/extension.js b/extension.js index 9c20bef..2535a5e 100644 --- a/extension.js +++ b/extension.js @@ -171,7 +171,7 @@ const AreaManager = new Lang.Class({ let loadPersistent = i == Main.layoutManager.primaryIndex && this.persistentOverRestarts; // Some utils for the drawing area menus. let areaManagerUtils = { - getHiddenList: () => this.hiddenList, + getHiddenList: () => this.hiddenList || null, togglePanelAndDockOpacity: this.togglePanelAndDockOpacity.bind(this), openPreferences: this.openPreferences.bind(this) }; @@ -210,17 +210,17 @@ const AreaManager = new Lang.Class({ 'switch-fill' : this.activeArea.switchFill.bind(this.activeArea), 'switch-image-file' : this.activeArea.switchImageFile.bind(this.activeArea, false), 'switch-image-file-reverse' : this.activeArea.switchImageFile.bind(this.activeArea, true), - 'select-none-shape': () => this.activeArea.selectTool(Area.Tools.NONE), - 'select-line-shape': () => this.activeArea.selectTool(Area.Tools.LINE), - 'select-ellipse-shape': () => this.activeArea.selectTool(Area.Tools.ELLIPSE), - 'select-rectangle-shape': () => this.activeArea.selectTool(Area.Tools.RECTANGLE), - 'select-text-shape': () => this.activeArea.selectTool(Area.Tools.TEXT), - 'select-image-shape': () => this.activeArea.selectTool(Area.Tools.IMAGE), - 'select-polygon-shape': () => this.activeArea.selectTool(Area.Tools.POLYGON), - 'select-polyline-shape': () => this.activeArea.selectTool(Area.Tools.POLYLINE), - 'select-move-tool': () => this.activeArea.selectTool(Area.Tools.MOVE), - 'select-resize-tool': () => this.activeArea.selectTool(Area.Tools.RESIZE), - 'select-mirror-tool': () => this.activeArea.selectTool(Area.Tools.MIRROR) + 'select-none-shape': () => this.activeArea.selectTool(Area.Tool.NONE), + 'select-line-shape': () => this.activeArea.selectTool(Area.Tool.LINE), + 'select-ellipse-shape': () => this.activeArea.selectTool(Area.Tool.ELLIPSE), + 'select-rectangle-shape': () => this.activeArea.selectTool(Area.Tool.RECTANGLE), + 'select-text-shape': () => this.activeArea.selectTool(Area.Tool.TEXT), + 'select-image-shape': () => this.activeArea.selectTool(Area.Tool.IMAGE), + 'select-polygon-shape': () => this.activeArea.selectTool(Area.Tool.POLYGON), + 'select-polyline-shape': () => this.activeArea.selectTool(Area.Tool.POLYLINE), + 'select-move-tool': () => this.activeArea.selectTool(Area.Tool.MOVE), + 'select-resize-tool': () => this.activeArea.selectTool(Area.Tool.RESIZE), + 'select-mirror-tool': () => this.activeArea.selectTool(Area.Tool.MIRROR) }; // available when writing diff --git a/files.js b/files.js index 1f31691..56bd809 100644 --- a/files.js +++ b/files.js @@ -41,13 +41,13 @@ const ICON_NAMES = [ 'arc', 'color', 'dashed-line', 'document-export', 'fillrule-evenodd', 'fillrule-nonzero', 'fill', 'full-line', 'linecap', 'linejoin', 'palette', 'smooth', 'stroke', 'tool-ellipse', 'tool-line', 'tool-mirror', 'tool-move', 'tool-none', 'tool-polygon', 'tool-polyline', 'tool-rectangle', 'tool-resize', ]; -const ThemedIconNames = { +const ThemedIconName = { COLOR_PICKER: 'color-select-symbolic', ENTER: 'applications-graphics', LEAVE: 'application-exit', GRAB: 'input-touchpad', UNGRAB: 'touchpad-disabled', OPEN: 'document-open', SAVE: 'document-save', FONT_FAMILY: 'font-x-generic', FONT_STYLE: 'format-text-italic', FONT_WEIGHT:'format-text-bold', - LEFT_ALIGNED: 'format-justify-left', RIGHT_ALIGNED: 'format-justify-right', + LEFT_ALIGNED: 'format-justify-left', CENTERED: 'format-justify-center',RIGHT_ALIGNED: 'format-justify-right', TOOL_IMAGE: 'insert-image', TOOL_TEXT: 'insert-text', }; @@ -74,11 +74,11 @@ ICON_NAMES.forEach(name => { }); }); -Object.keys(ThemedIconNames).forEach(key => { +Object.keys(ThemedIconName).forEach(key => { Object.defineProperty(Icons, key, { get: function() { if (!this[`_${key}`]) - this[`_${key}`] = new Gio.ThemedIcon({ name: `${ThemedIconNames[key]}-symbolic` }); + this[`_${key}`] = new Gio.ThemedIcon({ name: `${ThemedIconName[key]}-symbolic` }); return this[`_${key}`]; } }); diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index 5de5eaf..a36dc63 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: Draw On Your Screen\n" "Report-Msgid-Bugs-To: https://framagit.org/abakkk/DrawOnYourScreen/issues\n" -"POT-Creation-Date: 2020-10-04 22:45+0200\n" +"POT-Creation-Date: 2021-02-17 14:14+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -44,7 +44,9 @@ msgstr "" #. Translators: %s is a key label #, javascript-format -msgid "Press %s\nto start a new line" +msgid "" +"Press %s\n" +"to start a new line" msgstr "" #. Translators: It is displayed in an OSD notification to ask the user to start picking, so it should use the imperative mood. @@ -214,11 +216,13 @@ msgstr "" msgid "%f px" msgstr "" -#. Translators: text alignment -msgid "Right aligned" +msgid "Left aligned" msgstr "" -msgid "Left aligned" +msgid "Centered" +msgstr "" + +msgid "Right aligned" msgstr "" msgctxt "drawing-tool" @@ -685,7 +689,7 @@ msgstr "" msgid "Change linejoin" msgstr "" -msgid "Toggle text alignment" +msgid "Change text alignment" msgstr "" msgid "Add a drawing background" diff --git a/locale/fr/LC_MESSAGES/draw-on-your-screen.mo b/locale/fr/LC_MESSAGES/draw-on-your-screen.mo index 012de3d..1ea4ddf 100644 Binary files a/locale/fr/LC_MESSAGES/draw-on-your-screen.mo and b/locale/fr/LC_MESSAGES/draw-on-your-screen.mo differ diff --git a/locale/fr/LC_MESSAGES/draw-on-your-screen.po b/locale/fr/LC_MESSAGES/draw-on-your-screen.po index ecf8142..ff4b1ba 100644 --- a/locale/fr/LC_MESSAGES/draw-on-your-screen.po +++ b/locale/fr/LC_MESSAGES/draw-on-your-screen.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: Draw On Your Screen 6.1\n" "Report-Msgid-Bugs-To: https://framagit.org/abakkk/DrawOnYourScreen/issues\n" -"POT-Creation-Date: 2020-09-19 15:32+0200\n" -"PO-Revision-Date: 2020-10-21 09:23+0200\n" +"POT-Creation-Date: 2021-02-17 14:14+0100\n" +"PO-Revision-Date: 2021-02-20 01:06+0100\n" "Last-Translator: Abakkk\n" "Language-Team: \n" "Language: fr\n" @@ -226,13 +226,15 @@ msgstr "Biseauté" msgid "%f px" msgstr "%f px" -#. Translators: text alignment -msgid "Right aligned" -msgstr "Aligné à droite" - msgid "Left aligned" msgstr "Aligné à gauche" +msgid "Centered" +msgstr "Centré" + +msgid "Right aligned" +msgstr "Aligné à droite" + msgctxt "drawing-tool" msgid "Free drawing" msgstr "Dessin libre" @@ -277,12 +279,6 @@ msgctxt "drawing-tool" msgid "Mirror" msgstr "Miroir" -msgid "Undo" -msgstr "Retirer" - -msgid "Redo" -msgstr "Rétablir" - msgid "Erase" msgstr "Effacer" @@ -601,8 +597,8 @@ msgstr "Ouvrir le dessin précédent" msgid "Add images from the clipboard" msgstr "Ajouter des images depuis le presse-papiers" -msgid "Redo last brushstroke" -msgstr "Rétablir le dernier tracé" +msgid "Redo" +msgstr "Rétablir" msgid "Save drawing" msgstr "Enregistrer le dessin" @@ -706,8 +702,8 @@ msgstr "Modifier la forme de l’extrémité des lignes" msgid "Change linejoin" msgstr "Modifier la jointure des segments de ligne" -msgid "Toggle text alignment" -msgstr "Alterner l’alignement du texte" +msgid "Change text alignment" +msgstr "Modifier l’alignement du texte" msgid "Add a drawing background" msgstr "Ajouter une couleur de fond au dessin" @@ -725,5 +721,5 @@ msgstr "Cacher le panneau et le dock" msgid "Square drawing area" msgstr "Rendre la zone de dessin carrée" -msgid "Undo last brushstroke" -msgstr "Retirer le dernier tracé" +msgid "Undo" +msgstr "Retirer" diff --git a/menu.js b/menu.js index 909d668..50d1f32 100644 --- a/menu.js +++ b/menu.js @@ -47,6 +47,7 @@ const FONT_FAMILY_STYLE = true; // use 'login-dialog-message-warning' class in order to get GS theme warning color (default: #f57900) const WARNING_COLOR_STYLE_CLASS_NAME = 'login-dialog-message-warning'; const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_'); +const TextAlignmentIcon = { 0: Files.Icons.LEFT_ALIGNED, 1: Files.Icons.CENTERED, 2: Files.Icons.RIGHT_ALIGNED }; const getActor = function(object) { return GS_VERSION < '3.33.0' ? object.actor : object; @@ -71,52 +72,52 @@ var DisplayStrings = { }, get FillRule() { - if (!this._fillRules) + if (!this._FillRule) // Translators: fill-rule SVG attribute - this._fillRules = { 0: _("Nonzero"), 1: _("Evenodd") }; - return this._fillRules; + this._FillRule = { 0: _("Nonzero"), 1: _("Evenodd") }; + return this._FillRule; }, getFontFamily: function(family) { - if (!this._fontGenericFamilies) + if (!this._FontGenericFamily) // Translators: generic font-family SVG attribute - this._fontGenericFamilies = { 'Sans-Serif': pgettext("font-family", "Sans-Serif"), 'Serif': pgettext("font-family", "Serif"), - 'Monospace': pgettext("font-family", "Monospace"), 'Cursive': pgettext("font-family", "Cursive"), - 'Fantasy': pgettext("font-family", "Fantasy") }; - return this._fontGenericFamilies[family] || family; + this._FontGenericFamily = { 'Sans-Serif': pgettext("font-family", "Sans-Serif"), 'Serif': pgettext("font-family", "Serif"), + 'Monospace': pgettext("font-family", "Monospace"), 'Cursive': pgettext("font-family", "Cursive"), + 'Fantasy': pgettext("font-family", "Fantasy") }; + return this._FontGenericFamily[family] || family; }, get FontStyle() { - if (!this._fontStyles) + if (!this._FontStyle) // Translators: font-style SVG attribute - this._fontStyles = { 0: pgettext("font-style", "Normal"), 1: pgettext("font-style", "Oblique"), 2: pgettext("font-style", "Italic") }; - return this._fontStyles; + this._FontStyle = { 0: pgettext("font-style", "Normal"), 1: pgettext("font-style", "Oblique"), 2: pgettext("font-style", "Italic") }; + return this._FontStyle; }, FontStyleMarkup: { 0: 'normal', 1: 'oblique', 2: 'italic' }, get FontWeight() { - if (!this._fontWeights) + if (!this._FontWeight) // Translators: font-weight SVG attribute - this._fontWeights = { 100: pgettext("font-weight", "Thin"), 200: pgettext("font-weight", "Ultra Light"), 300: pgettext("font-weight", "Light"), - 350: pgettext("font-weight", "Semi Light"), 380: pgettext("font-weight", "Book"), 400: pgettext("font-weight", "Normal"), - 500: pgettext("font-weight", "Medium"), 600: pgettext("font-weight", "Semi Bold"), 700: pgettext("font-weight", "Bold"), - 800: pgettext("font-weight", "Ultra Bold"), 900: pgettext("font-weight", "Heavy"), 1000: pgettext("font-weight", "Ultra Heavy") }; - return this._fontWeights; + this._FontWeight = { 100: pgettext("font-weight", "Thin"), 200: pgettext("font-weight", "Ultra Light"), 300: pgettext("font-weight", "Light"), + 350: pgettext("font-weight", "Semi Light"), 380: pgettext("font-weight", "Book"), 400: pgettext("font-weight", "Normal"), + 500: pgettext("font-weight", "Medium"), 600: pgettext("font-weight", "Semi Bold"), 700: pgettext("font-weight", "Bold"), + 800: pgettext("font-weight", "Ultra Bold"), 900: pgettext("font-weight", "Heavy"), 1000: pgettext("font-weight", "Ultra Heavy") }; + return this._FontWeight; }, get LineCap() { - if (!this._lineCaps) + if (!this._LineCap) // Translators: stroke-linecap SVG attribute - this._lineCaps = { 0: pgettext("stroke-linecap", "Butt"), 1: pgettext("stroke-linecap", "Round"), 2: pgettext("stroke-linecap", "Square") }; - return this._lineCaps; + this._LineCap = { 0: pgettext("stroke-linecap", "Butt"), 1: pgettext("stroke-linecap", "Round"), 2: pgettext("stroke-linecap", "Square") }; + return this._LineCap; }, get LineJoin() { - if (!this._lineJoins) + if (!this._LineJoin) // Translators: stroke-linejoin SVG attribute - this._lineJoins = { 0: pgettext("stroke-linejoin", "Miter"), 1: pgettext("stroke-linejoin", "Round"), 2: pgettext("stroke-linejoin", "Bevel") }; - return this._lineJoins; + this._LineJoin = { 0: pgettext("stroke-linejoin", "Miter"), 1: pgettext("stroke-linejoin", "Round"), 2: pgettext("stroke-linejoin", "Bevel") }; + return this._LineJoin; }, getPixels(value) { @@ -124,28 +125,31 @@ var DisplayStrings = { return _("%f px").format(value); }, - getTextAlignment: function(rightAligned) { + get TextAlignment() { // Translators: text alignment - return rightAligned ? _("Right aligned") : _("Left aligned"); + if (!this._TextAlignment) + this._TextAlignment = { 0: _("Left aligned"), 1: _("Centered"), 2: _("Right aligned") }; + + return this._TextAlignment; }, get Tool() { - if (!this._tools) - this._tools = { 0: pgettext("drawing-tool", "Free drawing"), 1: pgettext("drawing-tool", "Line"), 2: pgettext("drawing-tool", "Ellipse"), - 3: pgettext("drawing-tool", "Rectangle"), 4: pgettext("drawing-tool", "Text"), 5: pgettext("drawing-tool", "Polygon"), - 6: pgettext("drawing-tool", "Polyline"), 7: pgettext("drawing-tool", "Image"), - 100: pgettext("drawing-tool", "Move"), 101: pgettext("drawing-tool", "Resize"), 102: pgettext("drawing-tool", "Mirror") }; - return this._tools; + if (!this._Tool) + this._Tool = { 0: pgettext("drawing-tool", "Free drawing"), 1: pgettext("drawing-tool", "Line"), 2: pgettext("drawing-tool", "Ellipse"), + 3: pgettext("drawing-tool", "Rectangle"), 4: pgettext("drawing-tool", "Text"), 5: pgettext("drawing-tool", "Polygon"), + 6: pgettext("drawing-tool", "Polyline"), 7: pgettext("drawing-tool", "Image"), + 100: pgettext("drawing-tool", "Move"), 101: pgettext("drawing-tool", "Resize"), 102: pgettext("drawing-tool", "Mirror") }; + return this._Tool; } }; var DrawingMenu = new Lang.Class({ Name: `${UUID}-DrawingMenu`, - _init: function(area, monitor, drawingTools, areaManagerUtils) { + _init: function(area, monitor, DrawingTool, areaManagerUtils) { this.area = area; this.monitor = monitor; - this.drawingTools = drawingTools; + this.DrawingTool = DrawingTool; this.areaManagerUtils = areaManagerUtils; let side = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL ? St.Side.RIGHT : St.Side.LEFT; @@ -178,7 +182,7 @@ var DrawingMenu = new Lang.Class({ disable: function() { delete this.area; - delete this.drawingTools; + delete this.DrawingTool; delete this.areaManagerUtils; this.menuManager.removeMenu(this.menu); Main.layoutManager.uiGroup.remove_actor(this.menu.actor); @@ -268,7 +272,7 @@ var DrawingMenu = new Lang.Class({ this._addFontFamilySubMenuItem(fontSection, Files.Icons.FONT_FAMILY); this._addSubMenuItem(fontSection, Files.Icons.FONT_WEIGHT, DisplayStrings.FontWeight, this.area, 'currentFontWeight'); this._addSubMenuItem(fontSection, Files.Icons.FONT_STYLE, DisplayStrings.FontStyle, this.area, 'currentFontStyle'); - this._addSwitchItem(fontSection, DisplayStrings.getTextAlignment(true), Files.Icons.LEFT_ALIGNED, Files.Icons.RIGHT_ALIGNED, this.area, 'currentTextRightAligned'); + this._addTextAlignmentSubMenuItem(fontSection); this._addSeparator(fontSection); this.menu.addMenuItem(fontSection); fontSection.itemActivated = () => {}; @@ -311,14 +315,14 @@ var DrawingMenu = new Lang.Class({ this.undoButton.child.reactive = this.area.elements.length > 0; this.redoButton.child.reactive = this.area.undoneElements.length > 0 || (this.area.elements.length && this.area.elements[this.area.elements.length - 1].canUndo); this.eraseButton.child.reactive = this.area.elements.length > 0; - this.smoothButton.child.reactive = this.area.elements.length > 0 && this.area.elements[this.area.elements.length - 1].shape == this.drawingTools.NONE; + this.smoothButton.child.reactive = this.area.elements.length > 0 && this.area.elements[this.area.elements.length - 1].shape == this.DrawingTool.NONE; this.saveButton.child.reactive = this.area.elements.length > 0; this.svgButton.child.reactive = this.area.elements.length > 0; this.saveDrawingSubMenuItem.setSensitive(this.area.elements.length > 0); }, _updateSectionVisibility: function() { - let [isText, isImage] = [this.area.currentTool == this.drawingTools.TEXT, this.area.currentTool == this.drawingTools.IMAGE]; + let [isText, isImage] = [this.area.currentTool == this.DrawingTool.TEXT, this.area.currentTool == this.DrawingTool.IMAGE]; this.lineSection.actor.visible = !isText && !isImage; this.fontSection.actor.visible = isText; this.imageSection.actor.visible = isImage; @@ -421,7 +425,7 @@ var DrawingMenu = new Lang.Class({ let item = new PopupMenu.PopupSubMenuMenuItem('', true); item.update = () => { item.label.set_text(DisplayStrings.Tool[this.area.currentTool]); - let toolName = this.drawingTools.getNameOf(this.area.currentTool); + let toolName = this.DrawingTool.getNameOf(this.area.currentTool); item.icon.set_gicon(Files.Icons[`TOOL_${toolName}`]); }; item.update(); @@ -431,7 +435,7 @@ var DrawingMenu = new Lang.Class({ GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { Object.keys(DisplayStrings.Tool).forEach(key => { let text = DisplayStrings.Tool[key]; - let toolName = this.drawingTools.getNameOf(key); + let toolName = this.DrawingTool.getNameOf(key); let subItemIcon = Files.Icons[`TOOL_${toolName}`]; let subItem = item.menu.addAction(text, () => { this.area.currentTool = Number(key); @@ -443,10 +447,10 @@ var DrawingMenu = new Lang.Class({ getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); // change the display order of tools - if (key == this.drawingTools.POLYGON) - item.menu.moveMenuItem(subItem, Number(this.drawingTools.TEXT)); - else if (key == this.drawingTools.POLYLINE) - item.menu.moveMenuItem(subItem, Number(this.drawingTools.TEXT) + 1); + if (key == this.DrawingTool.POLYGON) + item.menu.moveMenuItem(subItem, Number(this.DrawingTool.TEXT)); + else if (key == this.DrawingTool.POLYLINE) + item.menu.moveMenuItem(subItem, Number(this.DrawingTool.TEXT) + 1); }); return GLib.SOURCE_REMOVE; }); @@ -553,6 +557,28 @@ var DrawingMenu = new Lang.Class({ menu.addMenuItem(item); }, + _addTextAlignmentSubMenuItem: function(menu) { + let item = new PopupMenu.PopupSubMenuMenuItem(DisplayStrings.TextAlignment[this.area.currentTextAlignment], true); + item.icon.set_gicon(TextAlignmentIcon[this.area.currentTextAlignment]); + + item.menu.itemActivated = item.menu.close; + + GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { + Object.keys(TextAlignmentIcon).forEach(key => { + let subItem = item.menu.addAction(DisplayStrings.TextAlignment[key], () => { + item.label.set_text(DisplayStrings.TextAlignment[key]); + this.area.currentTextAlignment = key; + item.icon.set_gicon(TextAlignmentIcon[key]); + }); + + getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); + }); + return GLib.SOURCE_REMOVE; + }); + + menu.addMenuItem(item); + }, + _addImageSubMenuItem: function(menu, images) { let item = new PopupMenu.PopupSubMenuMenuItem('', true); item.update = () => { @@ -654,7 +680,7 @@ var DrawingMenu = new Lang.Class({ let insertCallback = () => { this.area.currentImage = json.image; this.imageItem.update(); - this.area.currentTool = this.drawingTools.IMAGE; + this.area.currentTool = this.DrawingTool.IMAGE; this.toolItem.update(); this._updateSectionVisibility(); }; diff --git a/metadata.json b/metadata.json index 6c49c47..1272b75 100644 --- a/metadata.json +++ b/metadata.json @@ -16,7 +16,8 @@ "3.32", "3.34", "3.36", - "3.38" + "3.38", + "40" ], - "version": 10 + "version": 11 } diff --git a/prefs.js b/prefs.js index 434c9ab..2c93299 100644 --- a/prefs.js +++ b/prefs.js @@ -21,12 +21,12 @@ * along with this program. If not, see . */ -const Atk = imports.gi.Atk; const Gdk = imports.gi.Gdk; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; +const IS_GTK3 = Gtk.get_major_version() == 3; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); @@ -39,27 +39,66 @@ const _ = function(string) { return ""; return gettext(string); }; -const _GTK = imports.gettext.domain('gtk30').gettext; +const _GTK = imports.gettext.domain(IS_GTK3 ? 'gtk30' : 'gtk40').gettext; const MARGIN = 10; -const ROWBOX_MARGIN_PARAMS = { margin_top: MARGIN / 2, margin_bottom: MARGIN / 2, margin_left: MARGIN, margin_right: MARGIN }; +const ROWBOX_MARGIN_PARAMS = { margin_top: MARGIN / 2, margin_bottom: MARGIN / 2, margin_start: MARGIN, margin_end: MARGIN, spacing: 4 }; const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_'); +if (IS_GTK3) { + Gtk.Container.prototype.append = Gtk.Container.prototype.add; + Gtk.Bin.prototype.set_child = Gtk.Container.prototype.add; +} + +const setAccessibleLabel = function(widget, label) { + if (IS_GTK3) + widget.get_accessible().set_name(label); + else + widget.update_property([Gtk.AccessibleProperty.LABEL], [label]); +}; + +const setAccessibleDescription = function(widget, description) { + if (IS_GTK3) + widget.get_accessible().set_description(description); + else + widget.update_property([Gtk.AccessibleProperty.DESCRIPTION], [description]); +}; + +const getChildrenOf = function(widget) { + if (IS_GTK3) { + return widget.get_children(); + } else { + let listModel = widget.observe_children(); + let i = 0; + let children = []; + let child; + while (!!(child = listModel.get_item(i))) { + children.push(child); + i++; + } + return children; + } +}; + function init() { Convenience.initTranslations(); } function buildPrefsWidget() { let topStack = new TopStack(); - let switcher = new Gtk.StackSwitcher({halign: Gtk.Align.CENTER, visible: true, stack: topStack}); + let switcher = new Gtk.StackSwitcher({ halign: Gtk.Align.CENTER, visible: true, stack: topStack }); + GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { - let window = topStack.get_toplevel(); - let headerBar = window.get_titlebar(); - headerBar.custom_title = switcher; - return false; + if (IS_GTK3) + topStack.get_toplevel().get_titlebar().set_custom_title(switcher); + else + topStack.get_root().get_titlebar().set_title_widget(switcher); + + return GLib.SOURCE_REMOVE; }); - topStack.show_all(); + if (IS_GTK3) + topStack.show_all(); return topStack; } @@ -68,7 +107,8 @@ const TopStack = new GObject.Class({ Extends: Gtk.Stack, _init: function(params) { - this.parent({ transition_type: 1, transition_duration: 500, expand: true }); + this.parent({ transition_type: Gtk.StackTransitionType.CROSSFADE, transition_duration: 500, hexpand: true, vexpand: true }); + this.prefsPage = new PrefsPage(); // Translators: "Preferences" page in preferences this.add_titled(this.prefsPage, 'prefs', _("Preferences")); @@ -88,8 +128,8 @@ const AboutPage = new GObject.Class({ _init: function(params) { this.parent({ hscrollbar_policy: Gtk.PolicyType.NEVER }); - let vbox= new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin: MARGIN * 3 }); - this.add(vbox); + let vbox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin_top: 3 * MARGIN, margin_bottom: 3 * MARGIN, margin_start: 3 * MARGIN, margin_end: 3 * MARGIN }); + this.set_child(vbox); // Translators: you are free to translate the extension name, that is displayed in About page, or not let name = " " + _("Draw On You Screen") + ""; @@ -102,21 +142,21 @@ const AboutPage = new GObject.Class({ let licenceLink = "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html"; let licence = "" + _GTK("This program comes with absolutely no warranty.\nSee the %s for details.").format(licenceLink, licenceName) + ""; - let aboutLabel = new Gtk.Label({ wrap: true, justify: 2, use_markup: true, label: + let aboutLabel = new Gtk.Label({ wrap: true, justify: Gtk.Justification.CENTER, use_markup: true, label: name + "\n\n" + version + "\n\n" + description + "\n\n" + link + "\n\n" + licence + "\n" }); - vbox.add(aboutLabel); + vbox.append(aboutLabel); - let creditBox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, margin: 2 * MARGIN }); - let leftBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); - let rightBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); - let leftLabel = new Gtk.Label({ wrap: true, valign: 1, halign: 2, justify: 1, use_markup: true, label: "" + _GTK("Created by") + "" }); - let rightLabel = new Gtk.Label({ wrap: true, valign: 1, halign: 1, justify: 0, use_markup: true, label: "Abakkk" }); - leftBox.pack_start(leftLabel, false, false, 0); - rightBox.pack_start(rightLabel, false, false, 0); - creditBox.pack_start(leftBox, true, true, 5); - creditBox.pack_start(rightBox, true, true, 5); - vbox.add(creditBox); + let creditBox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, margin_top: 2 * MARGIN, margin_bottom: 2 * MARGIN, margin_start: 2 * MARGIN, margin_end: 2 * MARGIN, spacing: 5 }); + let leftBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, hexpand: true }); + let rightBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, hexpand: true }); + leftBox.append(new Gtk.Label({ wrap: true, valign: Gtk.Align.START, halign: Gtk.Align.END, justify: Gtk.Justification.RIGHT, + use_markup: true, label: "" + _GTK("Created by") + "" })); + rightBox.append(new Gtk.Label({ wrap: true, valign: Gtk.Align.START, halign: Gtk.Align.START, justify: Gtk.Justification.LEFT, + use_markup: true, label: "Abakkk" })); + creditBox.append(leftBox); + creditBox.append(rightBox); + vbox.append(creditBox); // Translators: add your name here or keep it empty, it will be displayed in about page, e.g. // msgstr "" @@ -124,12 +164,10 @@ const AboutPage = new GObject.Class({ // "translator2\n" // "translator3" if (_("translator-credits") != "translator-credits" && _("translator-credits") != "") { - leftBox.pack_start(new Gtk.Label(), false, false, 0); - rightBox.pack_start(new Gtk.Label(), false, false, 0); - leftLabel = new Gtk.Label({ wrap: true, valign: 1, halign: 2, justify: 1, use_markup: true, label: "" + _GTK("Translated by") + "" }); - rightLabel = new Gtk.Label({ wrap: true, valign: 1, halign: 1, justify: 0, use_markup: true, label: "" + _("translator-credits") + "" }); - leftBox.pack_start(leftLabel, false, false, 0); - rightBox.pack_start(rightLabel, false, false, 0); + leftBox.append(new Gtk.Label()); + rightBox.append(new Gtk.Label()); + leftBox.append(new Gtk.Label({ wrap: true, valign: Gtk.Align.START, halign: Gtk.Align.END, justify: 1, use_markup: true, label: "" + _GTK("Translated by") + "" })); + rightBox.append(new Gtk.Label({ wrap: true, valign: Gtk.Align.START, halign: Gtk.Align.START, justify: 0, use_markup: true, label: "" + _("translator-credits") + "" })); } } }); @@ -144,44 +182,46 @@ const DrawingPage = new GObject.Class({ this.settings = Convenience.getSettings(Me.metadata['settings-schema'] + '.drawing'); this.schema = this.settings.settings_schema; - let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin: 3 * MARGIN, spacing: 3 * MARGIN }); - this.add(box); + let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin_top: 3 * MARGIN, margin_bottom: 3 * MARGIN, margin_start: 3 * MARGIN, margin_end: 3 * MARGIN, spacing: 3 * MARGIN }); + this.set_child(box); let palettesFrame = new Frame({ label: _("Palettes") }); - box.add(palettesFrame); + box.append(palettesFrame); let palettesFrameBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); - palettesFrame.add(palettesFrameBox); + palettesFrame.set_child(palettesFrameBox); let palettesScrolledWindow = new Gtk.ScrolledWindow({ vscrollbar_policy: Gtk.PolicyType.NEVER }); - palettesFrameBox.add(palettesScrolledWindow); - let palettesViewport = new Gtk.Viewport({ margin_top: MARGIN / 2, margin_bottom: MARGIN / 2 }); - palettesScrolledWindow.add(palettesViewport); + palettesFrameBox.append(palettesScrolledWindow); this.palettesListBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true }); this.palettesListBox.get_style_context().add_class('background'); - this.palettesListBox.get_accessible().set_name(this.schema.get_key('palettes').get_summary()); - this.palettesListBox.get_accessible().set_description(this.schema.get_key('palettes').get_description()); - palettesViewport.add(this.palettesListBox); + setAccessibleLabel(this.palettesListBox, this.schema.get_key('palettes').get_summary()); + setAccessibleDescription(this.palettesListBox, this.schema.get_key('palettes').get_description()); + palettesScrolledWindow.set_child(this.palettesListBox); + palettesScrolledWindow.get_child().set_margin_top(MARGIN / 2); + palettesScrolledWindow.get_child().set_margin_bottom(MARGIN / 2); this.settings.connect('changed::palettes', this._updatePalettes.bind(this)); this._updatePalettes(); let addBox = new Gtk.Box(ROWBOX_MARGIN_PARAMS); - let addButton = Gtk.Button.new_from_icon_name('list-add-symbolic', Gtk.IconSize.BUTTON); + let addButton = IS_GTK3 ? Gtk.Button.new_from_icon_name('list-add-symbolic', Gtk.IconSize.BUTTON) : Gtk.Button.new_from_icon_name('list-add-symbolic'); addButton.set_tooltip_text(_("Add a new palette")); - addBox.pack_start(addButton, true, true, 4); + addButton.set_hexpand(true); + addBox.append(addButton); addButton.connect('clicked', this._addNewPalette.bind(this)); - let importButton = Gtk.Button.new_from_icon_name('document-open-symbolic', Gtk.IconSize.BUTTON); + let importButton = IS_GTK3 ? Gtk.Button.new_from_icon_name('document-open-symbolic', Gtk.IconSize.BUTTON) : Gtk.Button.new_from_icon_name('document-open-symbolic'); importButton.set_tooltip_text(_GTK("Select a File")); - addBox.pack_start(importButton, true, true, 4); + importButton.set_hexpand(true); + addBox.append(importButton); importButton.connect('clicked', this._importPalette.bind(this)); - palettesFrameBox.add(addBox); + palettesFrameBox.append(addBox); let areaFrame = new Frame({ label: _("Area") }); - box.add(areaFrame); + box.append(areaFrame); let areaListBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: MARGIN / 2, margin_bottom: MARGIN / 2 }); areaListBox.get_style_context().add_class('background'); - areaFrame.add(areaListBox); + areaFrame.set_child(areaListBox); let squareAreaRow = new PrefRow({ label: this.schema.get_key('square-area-size').get_summary() }); let squareAreaAutoButton = new Gtk.CheckButton({ label: _("Auto"), @@ -196,7 +236,7 @@ const DrawingPage = new GObject.Class({ squareAreaAutoButton.bind_property('active', squareAreaSizeButton, 'sensitive', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN); squareAreaRow.addWidget(squareAreaAutoButton); squareAreaRow.addWidget(squareAreaSizeButton); - areaListBox.add(squareAreaRow); + areaListBox.insert(squareAreaRow, -1); let backgroundColorRow = new PrefRow({ label: this.schema.get_key('background-color').get_summary() }); let backgroundColorButton = new ColorStringButton({ use_alpha: true, show_editor: true, @@ -204,7 +244,7 @@ const DrawingPage = new GObject.Class({ tooltip_text: this.schema.get_key('background-color').get_description() }); this.settings.bind('background-color', backgroundColorButton, 'color-string', 0); backgroundColorRow.addWidget(backgroundColorButton); - areaListBox.add(backgroundColorRow); + areaListBox.insert(backgroundColorRow, -1); let gridLineRow = new PrefRow({ label: _("Grid overlay line") }); let gridLineAutoButton = new Gtk.CheckButton({ label: _("Auto"), @@ -226,7 +266,7 @@ const DrawingPage = new GObject.Class({ gridLineRow.addWidget(gridLineAutoButton); gridLineRow.addWidget(gridLineWidthButton); gridLineRow.addWidget(gridLineSpacingButton); - areaListBox.add(gridLineRow); + areaListBox.insert(gridLineRow, -1); let gridColorRow = new PrefRow({ label: this.schema.get_key('grid-color').get_summary() }); let gridColorButton = new ColorStringButton({ use_alpha: true, show_editor: true, @@ -234,14 +274,14 @@ const DrawingPage = new GObject.Class({ tooltip_text: this.schema.get_key('grid-color').get_description() }); this.settings.bind('grid-color', gridColorButton, 'color-string', 0); gridColorRow.addWidget(gridColorButton); - areaListBox.add(gridColorRow); + areaListBox.insert(gridColorRow, -1); let toolsFrame = new Frame({ label: _("Tools") }); - box.add(toolsFrame); + box.append(toolsFrame); let toolsListBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: MARGIN / 2, margin_bottom: MARGIN / 2 }); toolsListBox.get_style_context().add_class('background'); - toolsFrame.add(toolsListBox); + toolsFrame.set_child(toolsListBox); let dashArrayRow = new PrefRow({ label: _("Dash array") }); let dashArrayAutoButton = new Gtk.CheckButton({ label: _("Auto"), @@ -263,7 +303,7 @@ const DrawingPage = new GObject.Class({ dashArrayRow.addWidget(dashArrayAutoButton); dashArrayRow.addWidget(dashArrayOnButton); dashArrayRow.addWidget(dashArrayOffButton); - toolsListBox.add(dashArrayRow); + toolsListBox.insert(dashArrayRow, -1); let dashOffsetRow = new PrefRow({ label: this.schema.get_key('dash-offset').get_summary() }); let dashOffsetButton = new PixelSpinButton({ width_chars: 5, digits: 1, step: 0.1, @@ -272,7 +312,7 @@ const DrawingPage = new GObject.Class({ tooltip_text: this.schema.get_key('dash-offset').get_description() }); this.settings.bind('dash-offset', dashOffsetButton, 'value', 0); dashOffsetRow.addWidget(dashOffsetButton); - toolsListBox.add(dashOffsetRow); + toolsListBox.insert(dashOffsetRow, -1); let imageLocationRow = new PrefRow({ label: this.schema.get_key('image-location').get_summary() }); let imageLocationButton = new FileChooserButton({ action: Gtk.FileChooserAction.SELECT_FOLDER, @@ -280,19 +320,19 @@ const DrawingPage = new GObject.Class({ tooltip_text: this.schema.get_key('image-location').get_description() }); this.settings.bind('image-location', imageLocationButton, 'location', 0); imageLocationRow.addWidget(imageLocationButton); - toolsListBox.add(imageLocationRow); + toolsListBox.insert(imageLocationRow, -1); let resetButton = new Gtk.Button({ label: _("Reset settings"), halign: Gtk.Align.CENTER }); resetButton.get_style_context().add_class('destructive-action'); resetButton.connect('clicked', () => this.schema.list_keys().forEach(key => this.settings.reset(key))); - box.add(resetButton); + box.append(resetButton); }, _updatePalettes: function() { this.palettes = this.settings.get_value('palettes').deep_unpack(); - this.palettesListBox.get_children().slice(this.palettes.length) + getChildrenOf(this.palettesListBox).slice(this.palettes.length) .forEach(row => this.palettesListBox.remove(row)); - let paletteBoxes = this.palettesListBox.get_children().map(row => row.get_child()); + let paletteBoxes = getChildrenOf(this.palettesListBox).map(row => row.get_child()); this.palettes.forEach((palette, paletteIndex) => { let [name, colors] = palette; @@ -300,22 +340,24 @@ const DrawingPage = new GObject.Class({ if (paletteBoxes[paletteIndex]) { paletteBox = paletteBoxes[paletteIndex]; - let nameEntry = paletteBox.get_children()[0]; + let nameEntry = getChildrenOf(paletteBox)[0]; if (nameEntry.get_text() !== _(name)) { GObject.signal_handler_block(nameEntry, nameEntry.paletteNameChangedHandler); nameEntry.set_text(_(name)); GObject.signal_handler_unblock(nameEntry, nameEntry.paletteNameChangedHandler); } } else { - let nameEntry = new Gtk.Entry({ text: name, halign: Gtk.Align.START, tooltip_text: _("Rename the palette") }); + let nameEntry = new Gtk.Entry({ text: name, halign: Gtk.Align.START, tooltip_text: _("Rename the palette"), hexpand: true }); nameEntry.paletteNameChangedHandler = nameEntry.connect('changed', this._onPaletteNameChanged.bind(this, paletteIndex)); - let removeButton = Gtk.Button.new_from_icon_name('list-remove-symbolic', Gtk.IconSize.BUTTON); + // Minimum size, for Gtk4 + nameEntry.set_size_request(nameEntry.get_preferred_size()[1].width, -1); + let removeButton = IS_GTK3 ? Gtk.Button.new_from_icon_name('list-remove-symbolic', Gtk.IconSize.BUTTON) : Gtk.Button.new_from_icon_name('list-remove-symbolic'); removeButton.set_tooltip_text(_("Remove the palette")); removeButton.connect('clicked', this._removePalette.bind(this, paletteIndex)); paletteBox = new Gtk.Box(ROWBOX_MARGIN_PARAMS); - paletteBox.pack_start(nameEntry, true, true, 4); - paletteBox.pack_start(new Gtk.Box({ spacing: 4 }), false, false, 4); - paletteBox.pack_start(removeButton, false, false, 4); + paletteBox.append(nameEntry); + paletteBox.append(new Gtk.Box({ spacing: 4 })); + paletteBox.append(removeButton); this.palettesListBox.insert(paletteBox, paletteIndex); paletteBox.get_parent().set_activatable(false); } @@ -323,8 +365,8 @@ const DrawingPage = new GObject.Class({ while (colors.length < 9) colors.push('transparent'); - let colorsBox = paletteBox.get_children()[1]; - let colorButtons = colorsBox.get_children(); + let colorsBox = getChildrenOf(paletteBox)[1]; + let colorButtons = getChildrenOf(colorsBox); colors.forEach((color, colorIndex) => { let [colorString, displayName] = color.split(':'); if (colorButtons[colorIndex]) { @@ -335,11 +377,12 @@ const DrawingPage = new GObject.Class({ use_alpha: true, show_editor: true, halign: Gtk.Align.START, hexpand: false }); colorButton.connect('notify::color-string', this._onPaletteColorChanged.bind(this, paletteIndex, colorIndex)); - colorsBox.add(colorButton); + colorsBox.append(colorButton); } }); - paletteBox.show_all(); + if (IS_GTK3) + paletteBox.show_all(); }); }, @@ -367,17 +410,31 @@ const DrawingPage = new GObject.Class({ }, _importPalette: function() { - let dialog = new Gtk.FileChooserNative({ action: Gtk.FileChooserAction.OPEN, title: _GTK("Select a File") }); + let dialog = new Gtk.FileChooserDialog({ + title: _GTK("Select a File"), + action: Gtk.FileChooserAction.OPEN, + modal: true, + transient_for: IS_GTK3 ? this.get_toplevel() : this.get_root(), + }); + dialog.add_button(_GTK("_Cancel"), Gtk.ResponseType.CANCEL); + dialog.add_button(_GTK("_Open"), Gtk.ResponseType.ACCEPT); + let filter = new Gtk.FileFilter(); filter.set_name("GIMP Palette (*.gpl)"); filter.add_pattern('*.gpl'); dialog.add_filter(filter); - if (dialog.run() == Gtk.ResponseType.ACCEPT) { - let file = dialog.get_file(); - let palettes = GimpPaletteParser.parseFile(file); - palettes.forEach(palette => this.palettes.push(palette)); - this._savePalettes(); - } + + dialog.connect('response', (dialog, response) => { + if (response == Gtk.ResponseType.ACCEPT) { + let file = dialog.get_file(); + let palettes = GimpPaletteParser.parseFile(file); + palettes.forEach(palette => this.palettes.push(palette)); + this._savePalettes(); + } + dialog.destroy(); + }); + + dialog.show(); }, _removePalette: function(paletteIndex) { @@ -397,24 +454,24 @@ const PrefsPage = new GObject.Class({ let schema = settings.settings_schema; let internalShortcutSettings = Convenience.getSettings(Me.metadata['settings-schema'] + '.internal-shortcuts'); - let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin: MARGIN * 3, spacing: 3 * MARGIN }); - this.add(box); + let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin_top: 3 * MARGIN, margin_bottom: 3 * MARGIN, margin_start: 3 * MARGIN, margin_end: 3 * MARGIN, spacing: 3 * MARGIN }); + this.set_child(box); let globalFrame = new Frame({ label: _("Global") }); - box.add(globalFrame); + box.append(globalFrame); let listBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: MARGIN, margin_bottom: MARGIN / 2 }); listBox.get_style_context().add_class('background'); - globalFrame.add(listBox); + globalFrame.set_child(listBox); Shortcuts.GLOBAL_KEYBINDINGS.forEach((settingKeys, index) => { if (index) - listBox.add(new Gtk.Box(ROWBOX_MARGIN_PARAMS)); + listBox.insert(new Gtk.Box(ROWBOX_MARGIN_PARAMS), -1); let globalKeybindingsRow = new Gtk.ListBoxRow({ activatable: false }); let globalKeybindingsWidget = new KeybindingsWidget(settingKeys, settings); - globalKeybindingsRow.add(globalKeybindingsWidget); - listBox.add(globalKeybindingsRow); + globalKeybindingsRow.set_child(globalKeybindingsWidget); + listBox.insert(globalKeybindingsRow, -1); }); let persistentOverTogglesKey = schema.get_key('persistent-over-toggles'); @@ -422,7 +479,7 @@ const PrefsPage = new GObject.Class({ let persistentOverTogglesSwitch = new Gtk.Switch(); settings.bind('persistent-over-toggles', persistentOverTogglesSwitch, 'active', 0); persistentOverTogglesRow.addWidget(persistentOverTogglesSwitch, true); - listBox.add(persistentOverTogglesRow); + listBox.insert(persistentOverTogglesRow, -1); let persistentOverRestartsKey = schema.get_key('persistent-over-restarts'); let persistentOverRestartsRow = new PrefRow({ label: persistentOverRestartsKey.get_summary(), desc: persistentOverRestartsKey.get_description() }); @@ -430,7 +487,7 @@ const PrefsPage = new GObject.Class({ settings.bind('persistent-over-restarts', persistentOverRestartsSwitch, 'active', 0); persistentOverRestartsRow.addWidget(persistentOverRestartsSwitch, true); persistentOverTogglesSwitch.bind_property('active', persistentOverRestartsSwitch, 'sensitive', GObject.BindingFlags.SYNC_CREATE); - listBox.add(persistentOverRestartsRow); + listBox.insert(persistentOverRestartsRow, -1); let desktopKey = schema.get_key('drawing-on-desktop'); let desktopRow = new PrefRow({ label: desktopKey.get_summary(), desc: desktopKey.get_description() }); @@ -438,56 +495,53 @@ const PrefsPage = new GObject.Class({ settings.bind('drawing-on-desktop', desktopSwitch, 'active', 0); desktopRow.addWidget(desktopSwitch, true); persistentOverTogglesSwitch.bind_property('active', desktopSwitch, 'sensitive', GObject.BindingFlags.SYNC_CREATE); - listBox.add(desktopRow); + listBox.insert(desktopRow, -1); let osdKey = schema.get_key('osd-disabled'); let osdRow = new PrefRow({ label: osdKey.get_summary(), desc: osdKey.get_description() }); let osdSwitch = new Gtk.Switch(); settings.bind('osd-disabled', osdSwitch, 'active', 0); osdRow.addWidget(osdSwitch, true); - listBox.add(osdRow); + listBox.insert(osdRow, -1); let indicatorKey = schema.get_key('indicator-disabled'); let indicatorRow = new PrefRow({ label: indicatorKey.get_summary(), desc: indicatorKey.get_description() }); let indicatorSwitch = new Gtk.Switch(); settings.bind('indicator-disabled', indicatorSwitch, 'active', 0); indicatorRow.addWidget(indicatorSwitch, true); - listBox.add(indicatorRow); + listBox.insert(indicatorRow, -1); let internalFrame = new Frame({ label: _("Internal"), desc: _("In drawing mode") }); - box.add(internalFrame); + box.append(internalFrame); listBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: MARGIN, margin_bottom: MARGIN }); listBox.get_style_context().add_class('background'); - internalFrame.add(listBox); + internalFrame.set_child(listBox); Shortcuts.OTHERS.forEach((pairs, index) => { if (index) - listBox.add(new Gtk.Box(ROWBOX_MARGIN_PARAMS)); + listBox.insert(new Gtk.Box(ROWBOX_MARGIN_PARAMS), -1); pairs.forEach(pair => { let [action, shortcut] = pair; - let otherBox = new Gtk.Box({ margin_left: MARGIN, margin_right: MARGIN }); - let otherLabel = new Gtk.Label({ label: action, use_markup: true }); - otherLabel.set_halign(1); - let otherLabel2 = new Gtk.Label({ label: shortcut }); - otherBox.pack_start(otherLabel, true, true, 4); - otherBox.pack_start(otherLabel2, false, false, 4); - listBox.add(otherBox); + let otherBox = new Gtk.Box({ margin_start: MARGIN, margin_end: MARGIN, spacing: 4 }); + otherBox.append(new Gtk.Label({ label: action, use_markup: true, halign: Gtk.Align.START, hexpand: true })); + otherBox.append(new Gtk.Label({ label: shortcut })); + listBox.insert(otherBox, -1); }); }); - listBox.add(new Gtk.Box(ROWBOX_MARGIN_PARAMS)); + listBox.insert(new Gtk.Box(ROWBOX_MARGIN_PARAMS), -1); Shortcuts.INTERNAL_KEYBINDINGS.forEach((settingKeys, index) => { if (index) - listBox.add(new Gtk.Box(ROWBOX_MARGIN_PARAMS)); + listBox.insert(new Gtk.Box(ROWBOX_MARGIN_PARAMS), -1); let internalKeybindingsWidget = new KeybindingsWidget(settingKeys, internalShortcutSettings); - listBox.add(internalKeybindingsWidget); + listBox.insert(internalKeybindingsWidget, -1); }); - listBox.get_children().forEach(row => row.set_activatable(false)); + getChildrenOf(listBox).forEach(row => row.set_activatable(false)); let resetButton = new Gtk.Button({ label: _("Reset settings"), halign: Gtk.Align.CENTER }); resetButton.get_style_context().add_class('destructive-action'); @@ -495,7 +549,7 @@ const PrefsPage = new GObject.Class({ internalShortcutSettings.settings_schema.list_keys().forEach(key => internalShortcutSettings.reset(key)); settings.settings_schema.list_keys().forEach(key => settings.reset(key)); }); - box.add(resetButton); + box.append(resetButton); } }); @@ -504,12 +558,12 @@ const Frame = new GObject.Class({ Extends: Gtk.Frame, _init: function(params) { - let labelWidget = new Gtk.Label({ margin_bottom: MARGIN / 2, use_markup: true, label: `${params.label}` }); - this.parent({ label_yalign: 1.0, label_widget: labelWidget }); + let labelWidget = new Gtk.Label({ use_markup: true, label: `${params.label}` }); + this.parent({ label_widget: labelWidget }); if (params.desc) { labelWidget.set_tooltip_text(params.desc); - this.get_accessible().set_description(params.desc); + setAccessibleDescription(this, params.desc); } } }); @@ -522,43 +576,43 @@ const PrefRow = new GObject.Class({ this.parent({ activatable: false }); let hbox = new Gtk.Box(ROWBOX_MARGIN_PARAMS); - this.add(hbox); + this.set_child(hbox); - let labelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); - hbox.pack_start(labelBox, true, true, 4); + let labelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, hexpand: true }); + hbox.append(labelBox); this.widgetBox = new Gtk.Box({ spacing: 4 }); - hbox.pack_start(this.widgetBox, false, false, 4); + hbox.append(this.widgetBox); - this.label = new Gtk.Label({ use_markup: true, label: params.label, halign: Gtk.Align.START }); - labelBox.pack_start(this.label, true, true, 0); + this.label = new Gtk.Label({ use_markup: true, label: params.label, halign: Gtk.Align.START, vexpand: true }); + labelBox.append(this.label); if (params.desc) { - this.desc = new Gtk.Label({ use_markup: true, label: `${params.desc}`, halign: Gtk.Align.START, wrap: true, xalign: 0 }); + this.desc = new Gtk.Label({ use_markup: true, label: `${params.desc}`, halign: Gtk.Align.START, wrap: true, xalign: 0, vexpand: true }); this.desc.get_style_context().add_class('dim-label'); - labelBox.pack_start(this.desc, true, true, 0); + labelBox.append(this.desc); this.widgetBox.set_valign(Gtk.Align.START); } }, addWidget: function(widget, setRelationship) { - this.widgetBox.add(widget); + this.widgetBox.append(widget); if (widget.name) - widget.get_accessible().set_name(widget.name); + setAccessibleLabel(widget, widget.name); - if (setRelationship) { - this.label.get_accessible().add_relationship(Atk.RelationType.LABEL_FOR, widget.get_accessible()); - widget.get_accessible().add_relationship(Atk.RelationType.LABELLED_BY, this.label.get_accessible()); + if (setRelationship && IS_GTK3) { + this.label.get_accessible().add_relationship(imports.gi.Atk.RelationType.LABEL_FOR, widget.get_accessible()); + widget.get_accessible().add_relationship(imports.gi.Atk.RelationType.LABELLED_BY, this.label.get_accessible()); if (this.desc) { - this.desc.get_accessible().add_relationship(Atk.RelationType.DESCRIPTION_FOR, widget.get_accessible()); - widget.get_accessible().add_relationship(Atk.RelationType.DESCRIBED_BY, this.desc.get_accessible()); + this.desc.get_accessible().add_relationship(imports.gi.Atk.RelationType.DESCRIPTION_FOR, widget.get_accessible()); + widget.get_accessible().add_relationship(imports.gi.Atk.RelationType.DESCRIBED_BY, this.desc.get_accessible()); } } } }); -const PixelSpinButton = new GObject.Class({ +const PixelSpinButton = new GObject.Class(Object.assign({ Name: `${UUID}-PixelSpinButton`, Extends: Gtk.SpinButton, Properties: { @@ -584,17 +638,22 @@ const PixelSpinButton = new GObject.Class({ this.adjustment.set_page_increment(step * 10); }, - // Add 'px' unit. - vfunc_output: function() { - this.text = _("%f px").format(Number(this.value).toFixed(2)); - return true; - }, - + vfunc_constructed() { + this.parent(); + + // No virtual functions with Gtk4 :/. + // Add 'px' unit. + this.connect('output', spinButton => { + this.text = _("%f px").format(Number(spinButton.value).toFixed(2)); + return true; + }); + } +}, IS_GTK3 ? { // Prevent accidental scrolling. vfunc_scroll_event: function(event) { return this.has_focus ? this.parent(event) : Gdk.EVENT_PROPAGATE; } -}); +} : {})); // A color button that can be easily bound with a color string setting. const ColorStringButton = new GObject.Class({ @@ -617,49 +676,75 @@ const ColorStringButton = new GObject.Class({ this.set_rgba(newRgba); }, - // Do nothing if the new color is equivalent to the old color (e.g. "black" and "rgb(0,0,0)"). - vfunc_color_set(args) { - let oldRgba = new Gdk.RGBA(); - oldRgba.parse(this.color_string); + vfunc_constructed() { + this.parent(); - if (!this.rgba.equal(oldRgba)) { - this._color_string = this.rgba.to_string(); - this.notify('color-string'); - } + // No virtual functions with Gtk4 :/. + // Do nothing if the new color is equivalent to the old color (e.g. "black" and "rgb(0,0,0)"). + this.connect('color-set', colorButton => { + let oldRgba = new Gdk.RGBA(); + oldRgba.parse(colorButton.color_string); + + if (!this.rgba.equal(oldRgba)) { + this._color_string = colorButton.rgba.to_string(); + this.notify('color-string'); + } + }); } }); const FileChooserButton = new GObject.Class({ Name: `${UUID}-FileChooserButton`, - Extends: Gtk.FileChooserButton, + Extends: Gtk.Button, Properties: { + 'action': GObject.ParamSpec.enum('action', 'action', 'action', + GObject.ParamFlags.READWRITE, + Gtk.FileChooserAction.$gtype, + Gtk.FileChooserAction.SELECT_FOLDER), + 'location': GObject.ParamSpec.string('location', 'location', 'location', GObject.ParamFlags.READWRITE, '') }, get location() { - return this.get_file().get_path(); + return this._location || ""; }, set location(location) { - if (!location) { - this.unselect_all(); - if (this.get_file()) - this.set_file(Gio.File.new_for_path('aFileThatDoesNotExist')); - return; + if (!this._location || this._location != location) { + this._location = location; + this.label = location ? + Gio.File.new_for_commandline_arg(location).query_info('standard::display-name', Gio.FileQueryInfoFlags.NONE, null).get_display_name() : + _GTK("(None)"); + + this.notify('location'); } - - let file = Gio.File.new_for_commandline_arg(location); - if (file.query_exists(null)) - this.set_file(file); }, - vfunc_file_set: function(args) { - this.notify('location'); + vfunc_clicked: function() { + let dialog = new Gtk.FileChooserDialog({ + title: _(this.name), + action: this.action, + modal: true, + transient_for: IS_GTK3 ? this.get_toplevel() : this.get_root(), + }); + dialog.add_button(_GTK("_Cancel"), Gtk.ResponseType.CANCEL); + dialog.add_button(_GTK("_Select"), Gtk.ResponseType.ACCEPT); + + if (this.location) + dialog.set_file(Gio.File.new_for_commandline_arg(this.location)); + + dialog.connect('response', (dialog, response) => { + if (response == Gtk.ResponseType.ACCEPT) + this.location = dialog.get_file().get_path(); + dialog.destroy(); + }); + + dialog.show(); } }); -// this code comes from Sticky Notes View by Sam Bull, https://extensions.gnome.org/extension/568/notes/ +// From Sticky Notes View by Sam Bull, https://extensions.gnome.org/extension/568/notes/ const KeybindingsWidget = new GObject.Class({ Name: `${UUID}-KeybindingsWidget`, Extends: Gtk.Box, @@ -713,7 +798,7 @@ const KeybindingsWidget = new GObject.Class({ let [success, iterator ] = this._store.get_iter_from_string(iter); - if(!success) { + if (!success) { printerr("Can't change keybinding"); } @@ -745,7 +830,7 @@ const KeybindingsWidget = new GObject.Class({ this._tree_view.columns_autosize(); this._tree_view.set_headers_visible(false); - this.add(this._tree_view); + this.append(this._tree_view); this.keybinding_column = keybinding_column; this.action_column = action_column; @@ -768,9 +853,11 @@ const KeybindingsWidget = new GObject.Class({ this._store.clear(); this._settingKeys.forEach(settingKey => { - let [key, mods] = Gtk.accelerator_parse( - this._settings.get_strv(settingKey)[0] || '' - ); + let success_, key, mods; + if (IS_GTK3) + [key, mods] = Gtk.accelerator_parse(this._settings.get_strv(settingKey)[0] || ''); + else + [success_, key, mods] = Gtk.accelerator_parse(this._settings.get_strv(settingKey)[0] || ''); let iter = this._store.append(); this._store.set(iter, diff --git a/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml b/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml index 1b26430..456b144 100644 --- a/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml +++ b/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml @@ -315,7 +315,7 @@ ["<Primary><Alt>a"] - Toggle text alignment + Change text alignment ["<Primary>b"] diff --git a/shortcuts.js b/shortcuts.js index 2b2d1ac..9b3b5c2 100644 --- a/shortcuts.js +++ b/shortcuts.js @@ -22,6 +22,7 @@ */ const Gtk = imports.gi.Gtk; +const IS_GTK3 = Gtk.get_major_version() == 3; const GS_VERSION = imports.misc.config.PACKAGE_VERSION; const ExtensionUtils = imports.misc.extensionUtils; @@ -32,7 +33,11 @@ const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; const internalShortcutsSchema = Convenience.getSettings(Me.metadata['settings-schema'] + '.internal-shortcuts').settings_schema; const getKeyLabel = function(accel) { - let [keyval, mods] = Gtk.accelerator_parse(accel); + let success_, keyval, mods; + if (IS_GTK3) + [keyval, mods] = Gtk.accelerator_parse(accel); + else + [success_, keyval, mods] = Gtk.accelerator_parse(accel); return Gtk.accelerator_get_label(keyval, mods); };