2020-07-30 06:13:23 -03:00
/* jslint esversion: 6 */
2020-09-13 18:50:09 -03:00
/* exported Icons, Image, Images, Json, Jsons, getDateString, saveSvg */
2020-07-30 06:13:23 -03:00
/ *
* Copyright 2019 Abakkk
*
* This file is part of DrawOnYourScreen , a drawing extension for GNOME Shell .
* https : //framagit.org/abakkk/DrawOnYourScreen
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
* /
2020-08-04 21:00:20 -03:00
const ByteArray = imports . byteArray ;
2020-07-30 06:13:23 -03:00
const Gdk = imports . gi . Gdk ;
const GdkPixbuf = imports . gi . GdkPixbuf ;
const Gio = imports . gi . Gio ;
const GLib = imports . gi . GLib ;
const Lang = imports . lang ;
2020-09-09 17:25:56 -03:00
const St = imports . gi . St ;
2020-07-30 06:13:23 -03:00
const ExtensionUtils = imports . misc . extensionUtils ;
const Me = ExtensionUtils . getCurrentExtension ( ) ;
2020-09-10 23:06:21 -03:00
const EXAMPLE _IMAGE _DIRECTORY = Me . dir . get _child ( 'data' ) . get _child ( 'images' ) ;
const DEFAULT _USER _IMAGE _LOCATION = GLib . build _filenamev ( [ GLib . get _user _data _dir ( ) , Me . metadata [ 'data-dir' ] , 'images' ] ) ;
2020-09-09 17:25:56 -03:00
const Clipboard = St . Clipboard . get _default ( ) ;
const CLIPBOARD _TYPE = St . ClipboardType . CLIPBOARD ;
2020-09-10 10:19:17 -03:00
const ICON _DIR = Me . dir . get _child ( 'data' ) . get _child ( 'icons' ) ;
2020-09-11 20:25:37 -03:00
const ICON _NAMES = [
'arc' , 'color' , 'dashed-line' , 'fillrule-evenodd' , 'fillrule-nonzero' , 'fill' , 'full-line' , 'linecap' , 'linejoin' , 'palette' , 'smooth' , 'stroke' ,
'tool-ellipse' , 'tool-line' , 'tool-move' , 'tool-none' , 'tool-polygon' , 'tool-polyline' , 'tool-rectangle' , 'tool-resize' ,
] ;
const ThemedIconNames = {
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' ,
TOOL _IMAGE : 'insert-image' , TOOL _MIRROR : 'view-mirror' , TOOL _TEXT : 'insert-text' ,
2020-09-10 10:19:17 -03:00
} ;
2020-09-11 20:25:37 -03:00
var Icons = { } ;
2020-09-10 10:19:17 -03:00
ICON _NAMES . forEach ( name => {
Object . defineProperty ( Icons , name . toUpperCase ( ) . replace ( /-/gi , '_' ) , {
get : function ( ) {
if ( ! this [ ` _ ${ name } ` ] ) {
let file = Gio . File . new _for _path ( ICON _DIR . get _child ( ` ${ name } -symbolic.svg ` ) . get _path ( ) ) ;
this [ ` _ ${ name } ` ] = file . query _exists ( null ) ? new Gio . FileIcon ( { file } ) : new Gio . ThemedIcon ( { name : 'error-symbolic' } ) ;
}
return this [ ` _ ${ name } ` ] ;
}
2020-09-11 20:25:37 -03:00
} ) ;
} ) ;
Object . keys ( ThemedIconNames ) . forEach ( key => {
Object . defineProperty ( Icons , key , {
get : function ( ) {
if ( ! this [ ` _ ${ key } ` ] )
this [ ` _ ${ key } ` ] = new Gio . ThemedIcon ( { name : ` ${ ThemedIconNames [ key ] } -symbolic ` } ) ;
return this [ ` _ ${ key } ` ] ;
}
2020-09-10 10:19:17 -03:00
} ) ;
} ) ;
2020-07-30 06:13:23 -03:00
2020-09-12 14:54:10 -03:00
// wrapper around an image file. If not subclassed, it is used with drawing files (.json) and it takes { displayName, contentType, base64, hash } as params.
2020-07-30 06:13:23 -03:00
var Image = new Lang . Class ( {
Name : 'DrawOnYourScreenImage' ,
_init : function ( params ) {
for ( let key in params )
this [ key ] = params [ key ] ;
} ,
toString : function ( ) {
return this . displayName ;
} ,
toJson : function ( ) {
return {
displayName : this . displayName ,
contentType : this . contentType ,
2020-08-04 21:13:56 -03:00
base64 : this . base64 ,
hash : this . hash
2020-07-30 06:13:23 -03:00
} ;
} ,
get bytes ( ) {
2020-09-12 14:54:10 -03:00
if ( ! this . _bytes )
this . _bytes = new GLib . Bytes ( GLib . base64 _decode ( this . base64 ) ) ;
2020-07-30 06:13:23 -03:00
return this . _bytes ;
} ,
get base64 ( ) {
if ( ! this . _base64 )
this . _base64 = GLib . base64 _encode ( this . bytes . get _data ( ) ) ;
return this . _base64 ;
} ,
2020-08-04 21:13:56 -03:00
set base64 ( base64 ) {
this . _base64 = base64 ;
} ,
// hash is not used
2020-07-30 06:13:23 -03:00
get hash ( ) {
if ( ! this . _hash )
this . _hash = this . bytes . hash ( ) ;
return this . _hash ;
} ,
2020-08-04 21:13:56 -03:00
set hash ( hash ) {
this . _hash = hash ;
} ,
2020-07-30 06:13:23 -03:00
get pixbuf ( ) {
if ( ! this . _pixbuf ) {
let stream = Gio . MemoryInputStream . new _from _bytes ( this . bytes ) ;
this . _pixbuf = GdkPixbuf . Pixbuf . new _from _stream ( stream , null ) ;
stream . close ( null ) ;
}
return this . _pixbuf ;
} ,
getPixbufAtScale : function ( width , height ) {
let stream = Gio . MemoryInputStream . new _from _bytes ( this . bytes ) ;
let pixbuf = GdkPixbuf . Pixbuf . new _from _stream _at _scale ( stream , width , height , true , null ) ;
stream . close ( null ) ;
return pixbuf ;
} ,
setCairoSource : function ( cr , x , y , width , height , preserveAspectRatio ) {
let pixbuf = preserveAspectRatio ? this . getPixbufAtScale ( width , height )
: this . pixbuf . scale _simple ( width , height , GdkPixbuf . InterpType . BILINEAR ) ;
Gdk . cairo _set _source _pixbuf ( cr , pixbuf , x , y ) ;
}
} ) ;
2020-09-12 14:54:10 -03:00
// Add a gicon generator to Image. It is used with image files and it takes { file, info } as params.
const ImageWithGicon = new Lang . Class ( {
Name : 'DrawOnYourScreenImageWithGicon' ,
Extends : Image ,
get displayName ( ) {
return this . info . get _display _name ( ) ;
} ,
get contentType ( ) {
return this . info . get _content _type ( ) ;
} ,
get thumbnailFile ( ) {
if ( ! this . _thumbnailFile ) {
if ( this . info . has _attribute ( 'thumbnail::path' ) && this . info . get _attribute _boolean ( 'thumbnail::is-valid' ) ) {
let thumbnailPath = this . info . get _attribute _as _string ( 'thumbnail::path' ) ;
this . _thumbnailFile = Gio . File . new _for _path ( thumbnailPath ) ;
}
}
return this . _thumbnailFile || null ;
} ,
get gicon ( ) {
if ( ! this . _gicon )
this . _gicon = new Gio . FileIcon ( { file : this . thumbnailFile || this . file } ) ;
return this . _gicon ;
} ,
// use only thumbnails in menu (memory)
get thumbnailGicon ( ) {
if ( this . contentType != 'image/svg+xml' && ! this . thumbnailFile )
return null ;
return this . gicon ;
} ,
get bytes ( ) {
if ( ! this . _bytes ) {
try {
// load_bytes available in GLib 2.56+
this . _bytes = this . file . load _bytes ( null ) [ 0 ] ;
} catch ( e ) {
let [ , contents ] = this . file . load _contents ( null ) ;
if ( contents instanceof Uint8Array )
this . _bytes = ByteArray . toGBytes ( contents ) ;
else
this . _bytes = contents . toGBytes ( ) ;
}
}
return this . _bytes ;
}
} ) ;
// Access images with getPrevious, getNext, getSorted or by iterating over it.
2020-09-09 17:25:56 -03:00
var Images = {
2020-09-10 23:06:21 -03:00
_images : [ ] ,
_clipboardImages : [ ] ,
_upToDate : false ,
2020-09-13 18:50:09 -03:00
disable : function ( ) {
this . _images = [ ] ;
this . _clipboardImages = [ ] ;
this . _upToDate = false ;
} ,
2020-09-10 23:06:21 -03:00
_clipboardImagesContains : function ( file ) {
return this . _clipboardImages . some ( image => image . file . equal ( file ) ) ;
} ,
2020-07-30 06:13:23 -03:00
2020-09-12 14:54:10 -03:00
// Firstly iterate over the extension directory that contains Example.svg,
// secondly iterate over the directory that was configured by the user in prefs,
// finally iterate over the images pasted from the clipboard.
[ Symbol . iterator ] : function ( ) {
2020-09-10 23:06:21 -03:00
if ( this . _upToDate )
2020-09-12 14:54:10 -03:00
return this . _images . concat ( this . _clipboardImages ) [ Symbol . iterator ] ( ) ;
2020-09-10 23:06:21 -03:00
2020-09-12 14:54:10 -03:00
this . _upToDate = true ;
let oldImages = this . _images ;
let newImages = this . _images = [ ] ;
let clipboardImagesContains = this . _clipboardImagesContains . bind ( this ) ;
let clipboardIterator = this . _clipboardImages [ Symbol . iterator ] ( ) ;
2020-09-09 17:25:56 -03:00
2020-09-12 14:54:10 -03:00
return {
getExampleEnumerator : function ( ) {
try {
return EXAMPLE _IMAGE _DIRECTORY . enumerate _children ( 'standard::,thumbnail::' , Gio . FileQueryInfoFlags . NONE , null ) ;
} catch ( e ) {
return this . getUserEnumerator ( ) ;
}
} ,
2020-09-09 17:25:56 -03:00
2020-09-12 14:54:10 -03:00
getUserEnumerator : function ( ) {
try {
let userLocation = Me . drawingSettings . get _string ( 'image-location' ) || DEFAULT _USER _IMAGE _LOCATION ;
let userDirectory = Gio . File . new _for _commandline _arg ( userLocation ) ;
return userDirectory . enumerate _children ( 'standard::,thumbnail::' , Gio . FileQueryInfoFlags . NONE , null ) ;
} catch ( e ) {
return null ;
}
} ,
get enumerator ( ) {
if ( this . _enumerator === undefined )
this . _enumerator = this . getExampleEnumerator ( ) ;
else if ( this . _enumerator && this . _enumerator . get _container ( ) . equal ( EXAMPLE _IMAGE _DIRECTORY ) && this . _enumerator . is _closed ( ) )
this . _enumerator = this . getUserEnumerator ( ) ;
else if ( this . _enumerator && this . _enumerator . is _closed ( ) )
this . _enumerator = null ;
return this . _enumerator ;
} ,
next : function ( ) {
if ( ! this . enumerator )
return clipboardIterator . next ( ) ;
2020-09-10 23:06:21 -03:00
2020-09-12 14:54:10 -03:00
let info = this . enumerator . next _file ( null ) ;
if ( ! info ) {
this . enumerator . close ( null ) ;
return this . next ( ) ;
2020-09-10 23:06:21 -03:00
}
2020-09-12 14:54:10 -03:00
let file = this . enumerator . get _child ( info ) ;
if ( info . get _content _type ( ) . indexOf ( 'image' ) == 0 && ! clipboardImagesContains ( file ) ) {
2020-09-13 18:50:09 -03:00
let image = oldImages . find ( oldImage => oldImage . file . equal ( file ) ) || new ImageWithGicon ( { file , info } ) ;
2020-09-12 14:54:10 -03:00
newImages . push ( image ) ;
return { value : image , done : false } ;
} else {
return this . next ( ) ;
}
2020-09-09 17:25:56 -03:00
}
2020-09-12 14:54:10 -03:00
} ;
2020-09-10 23:06:21 -03:00
} ,
2020-09-12 14:54:10 -03:00
getSorted : function ( ) {
return [ ... this ] . sort ( ( a , b ) => a . toString ( ) . localeCompare ( b . toString ( ) ) ) ;
2020-09-10 23:06:21 -03:00
} ,
getNext : function ( currentImage ) {
2020-09-12 14:54:10 -03:00
let images = this . getSorted ( ) ;
let index = currentImage ? images . findIndex ( image => image . file . equal ( currentImage . file ) ) : - 1 ;
2020-09-10 23:06:21 -03:00
return images [ index == images . length - 1 ? 0 : index + 1 ] || null ;
} ,
getPrevious : function ( currentImage ) {
2020-09-12 14:54:10 -03:00
let images = this . getSorted ( ) ;
2020-09-13 18:50:09 -03:00
let index = currentImage ? images . findIndex ( image => image . file . equal ( currentImage . file ) ) : - 1 ;
2020-09-10 23:06:21 -03:00
return images [ index <= 0 ? images . length - 1 : index - 1 ] || null ;
} ,
reset : function ( ) {
this . _upToDate = false ;
2020-09-09 17:25:56 -03:00
} ,
2020-07-30 06:13:23 -03:00
2020-09-09 17:25:56 -03:00
addImagesFromClipboard : function ( callback ) {
Clipboard . get _text ( CLIPBOARD _TYPE , ( clipBoard , text ) => {
if ( ! text )
return ;
let lines = text . split ( '\n' ) ;
if ( lines [ 0 ] == 'x-special/nautilus-clipboard' )
lines = lines . slice ( 2 ) ;
let images = lines . filter ( line => ! ! line )
. map ( line => Gio . File . new _for _commandline _arg ( line ) )
. filter ( file => file . query _exists ( null ) )
2020-09-10 23:06:21 -03:00
. map ( file => [ file , file . query _info ( 'standard::,thumbnail::' , Gio . FileQueryInfoFlags . NONE , null ) ] )
2020-09-09 17:25:56 -03:00
. filter ( pair => pair [ 1 ] . get _content _type ( ) . indexOf ( 'image' ) == 0 )
2020-09-12 14:54:10 -03:00
. map ( pair => new ImageWithGicon ( { file : pair [ 0 ] , info : pair [ 1 ] } ) ) ;
2020-09-09 17:25:56 -03:00
// Prevent duplicated
2020-09-10 23:06:21 -03:00
images . filter ( image => ! this . _clipboardImagesContains ( image . file ) )
. forEach ( image => this . _clipboardImages . push ( image ) ) ;
2020-09-09 17:25:56 -03:00
if ( images . length ) {
2020-09-10 23:06:21 -03:00
this . reset ( ) ;
2020-09-09 17:25:56 -03:00
let lastFile = images [ images . length - 1 ] . file ;
2020-09-12 14:54:10 -03:00
callback ( this . _clipboardImages . find ( image => image . file . equal ( lastFile ) ) ) ;
2020-09-10 23:06:21 -03:00
}
2020-09-09 17:25:56 -03:00
} ) ;
}
2020-07-30 06:13:23 -03:00
} ;
2020-08-04 21:00:20 -03:00
// wrapper around a json file
var Json = new Lang . Class ( {
Name : 'DrawOnYourScreenJson' ,
_init : function ( params ) {
for ( let key in params )
this [ key ] = params [ key ] ;
} ,
2020-09-13 18:50:09 -03:00
get isPersistent ( ) {
return this . name == Me . metadata [ 'persistent-file-name' ] ;
} ,
2020-08-04 21:00:20 -03:00
toString : function ( ) {
return this . displayName || this . name ;
} ,
delete : function ( ) {
this . file . delete ( null ) ;
} ,
get file ( ) {
2020-09-13 18:50:09 -03:00
if ( ! this . _file )
2020-08-04 21:00:20 -03:00
this . _file = Gio . File . new _for _path ( GLib . build _filenamev ( [ GLib . get _user _data _dir ( ) , Me . metadata [ 'data-dir' ] , ` ${ this . name } .json ` ] ) ) ;
2020-09-13 18:50:09 -03:00
return this . _file ;
2020-08-04 21:00:20 -03:00
} ,
set file ( file ) {
this . _file = file ;
} ,
get contents ( ) {
2020-09-13 18:50:09 -03:00
if ( this . _contents === undefined ) {
try {
[ , this . _contents ] = this . file . load _contents ( null ) ;
if ( this . _contents instanceof Uint8Array )
this . _contents = ByteArray . toString ( this . _contents ) ;
} catch ( e ) {
this . _contents = null ;
}
2020-08-04 21:00:20 -03:00
}
2020-09-13 18:50:09 -03:00
return this . _contents ;
2020-08-04 21:00:20 -03:00
} ,
set contents ( contents ) {
2020-09-13 18:50:09 -03:00
if ( this . isPersistent && ( this . contents == contents || ! this . contents && contents == '[]' ) )
return ;
2020-08-04 21:00:20 -03:00
try {
this . file . replace _contents ( contents , null , false , Gio . FileCreateFlags . NONE , null ) ;
} catch ( e ) {
this . file . get _parent ( ) . make _directory _with _parents ( null ) ;
this . file . replace _contents ( contents , null , false , Gio . FileCreateFlags . NONE , null ) ;
}
2020-09-13 18:50:09 -03:00
this . _contents = contents ;
2020-09-13 20:32:37 -03:00
} ,
createGicon : function ( content ) {
let bytes = new GLib . Bytes ( content ) ;
this . gicon = Gio . BytesIcon . new ( bytes ) ;
2020-08-04 21:00:20 -03:00
}
} ) ;
2020-09-13 18:50:09 -03:00
var Jsons = {
_jsons : [ ] ,
_upToDate : false ,
2020-08-04 21:00:20 -03:00
2020-09-13 18:50:09 -03:00
disable : function ( ) {
if ( this . _monitor ) {
this . _monitor . disconnect ( this . _monitorHandler ) ;
this . _monitor . cancel ( ) ;
2020-08-04 21:00:20 -03:00
}
2020-09-13 18:50:09 -03:00
delete this . _monitor ;
delete this . _persistent ;
this . _jsons = [ ] ;
this . _upToDate = false ;
} ,
2020-08-04 21:00:20 -03:00
2020-09-13 18:50:09 -03:00
_updateMonitor : function ( ) {
if ( this . _monitor )
return ;
let directory = Gio . File . new _for _path ( GLib . build _filenamev ( [ GLib . get _user _data _dir ( ) , Me . metadata [ 'data-dir' ] ] ) ) ;
this . _monitor = directory . monitor ( Gio . FileMonitorFlags . NONE , null ) ;
this . _monitorHandler = this . _monitor . connect ( 'changed' , ( monitor , file ) => {
if ( file . get _basename ( ) != ` ${ Me . metadata [ 'persistent-file-name' ] } .json ` && file . get _basename ( ) . indexOf ( '.goutputstream' ) )
this . reset ( ) ;
} ) ;
} ,
2020-08-04 21:00:20 -03:00
2020-09-13 18:50:09 -03:00
[ Symbol . iterator ] : function ( ) {
if ( this . _upToDate )
return this . _jsons [ Symbol . iterator ] ( ) ;
this . _updateMonitor ( ) ;
this . _upToDate = true ;
let newJsons = this . _jsons = [ ] ;
return {
get enumerator ( ) {
if ( this . _enumerator === undefined ) {
try {
let directory = Gio . File . new _for _path ( GLib . build _filenamev ( [ GLib . get _user _data _dir ( ) , Me . metadata [ 'data-dir' ] ] ) ) ;
this . _enumerator = directory . enumerate _children ( 'standard::name,standard::display-name,standard::content-type,time::modified' , Gio . FileQueryInfoFlags . NONE , null ) ;
} catch ( e ) {
this . _enumerator = null ;
}
}
return this . _enumerator ;
} ,
next : function ( ) {
if ( ! this . enumerator || this . enumerator . is _closed ( ) )
return { done : true } ;
let info = this . enumerator . next _file ( null ) ;
if ( ! info ) {
this . enumerator . close ( null ) ;
return this . next ( ) ;
}
let file = this . enumerator . get _child ( info ) ;
if ( info . get _content _type ( ) . indexOf ( 'json' ) != - 1 && info . get _name ( ) != ` ${ Me . metadata [ 'persistent-file-name' ] } .json ` ) {
let json = new Json ( {
file , name : info . get _name ( ) . slice ( 0 , - 5 ) ,
displayName : info . get _display _name ( ) . slice ( 0 , - 5 ) ,
// info.get_modification_date_time: Gio 2.62+
modificationUnixTime : info . get _attribute _uint64 ( 'time::modified' )
} ) ;
newJsons . push ( json ) ;
return { value : json , done : false } ;
} else {
return this . next ( ) ;
}
}
} ;
} ,
getSorted : function ( ) {
return [ ... this ] . sort ( ( a , b ) => b . modificationUnixTime - a . modificationUnixTime ) ;
} ,
getNext : function ( currentJson ) {
let jsons = this . getSorted ( ) ;
let index = currentJson ? jsons . findIndex ( json => json . name == currentJson . name ) : - 1 ;
return jsons [ index == jsons . length - 1 ? 0 : index + 1 ] || null ;
} ,
getPrevious : function ( currentJson ) {
let jsons = this . getSorted ( ) ;
let index = currentJson ? jsons . findIndex ( json => json . name == currentJson . name ) : - 1 ;
return jsons [ index <= 0 ? jsons . length - 1 : index - 1 ] || null ;
} ,
getPersistent : function ( ) {
if ( ! this . _persistent )
this . _persistent = new Json ( { name : Me . metadata [ 'persistent-file-name' ] } ) ;
return this . _persistent ;
} ,
getDated : function ( ) {
return new Json ( { name : getDateString ( ) } ) ;
} ,
getNamed : function ( name ) {
return [ ... this ] . find ( json => json . name == name ) || new Json ( { name } ) ;
} ,
reset : function ( ) {
this . _upToDate = false ;
}
2020-08-04 21:00:20 -03:00
} ;
var getDateString = function ( ) {
let date = GLib . DateTime . new _now _local ( ) ;
return ` ${ date . format ( "%F" ) } ${ date . format ( "%X" ) } ` ;
} ;
2020-09-13 18:50:09 -03:00
var saveSvg = function ( content ) {
let filename = ` ${ Me . metadata [ 'svg-file-name' ] } ${ getDateString ( ) } .svg ` ;
let dir = GLib . get _user _special _dir ( GLib . UserDirectory . DIRECTORY _PICTURES ) ;
let path = GLib . build _filenamev ( [ dir , filename ] ) ;
let file = Gio . File . new _for _path ( path ) ;
if ( file . query _exists ( null ) )
return false ;
try {
2020-09-13 20:32:37 -03:00
return file . replace _contents ( content , null , false , Gio . FileCreateFlags . NONE , null ) [ 0 ] ;
2020-09-13 18:50:09 -03:00
} catch ( e ) {
return false ;
}
} ;