@@ -12,6 +12,7 @@ const Gio = imports.gi.Gio;
1212const Gettext = imports . gettext ;
1313const Pango = imports . gi . Pango ;
1414const Tooltips = imports . ui . tooltips ;
15+ const PopupMenu = imports . ui . popupMenu ;
1516
1617const UUID = "quicklinks@NotSirius-A" ;
1718const DESKLET_ROOT = imports . ui . deskletManager . deskletMeta [ UUID ] . path ;
@@ -57,8 +58,6 @@ MyDesklet.prototype = {
5758 this . settings . bindProperty ( Settings . BindingDirection . IN , "link-border-color" , "linkBorderColor" , this . on_setting_changed ) ;
5859 this . settings . bindProperty ( Settings . BindingDirection . IN , "icon-size" , "iconSize" , this . on_setting_changed ) ;
5960 this . settings . bindProperty ( Settings . BindingDirection . IN , "font" , "fontRaw" , this . on_setting_changed ) ;
60- this . settings . bindProperty ( Settings . BindingDirection . IN , "font-bold" , "fontBold" , this . on_setting_changed ) ;
61- this . settings . bindProperty ( Settings . BindingDirection . IN , "font-italic" , "fontItalic" , this . on_setting_changed ) ;
6261 this . settings . bindProperty ( Settings . BindingDirection . IN , "scale-size" , "scaleSize" , this . on_setting_changed ) ;
6362 this . settings . bindProperty ( Settings . BindingDirection . IN , "font-enabled" , "fontEnabled" , this . on_setting_changed ) ;
6463 this . settings . bindProperty ( Settings . BindingDirection . IN , "text-color" , "customTextColor" , this . on_setting_changed ) ;
@@ -82,42 +81,96 @@ MyDesklet.prototype = {
8281 * Starts desklet, imports links, renders UI
8382 */
8483 runDesklet : function ( ) {
84+ this . initializeData ( ) ;
8585
86- this . lastClick = {
87- "link_index" : null ,
88- "time" : Infinity ,
89- } ;
90-
91- // MAKE SURE to create a deepcopy of linkList, otherwise you could modify actual desklet settings using this variable
92- let links_list_deepcopy = JSON . parse ( JSON . stringify ( this . linksList ) ) ;
93- this . links = this . getLinks ( links_list_deepcopy ) ;
94-
95- this . font = this . parseFont ( this . fontRaw ) ;
9686 // Render desklet gui
9787 this . renderGUI ( ) ;
9888
89+ this . populateContextMenu ( ) ;
90+
9991 // Render system desklet decorations
10092 this . renderDecorations ( ) ;
10193 } ,
10294
103- /**
104- * Parse raw font string, TODO improve parsing, detect bold/italic etc.
105- * @param {string } fontString - Font descriptor
106- * @returns {{"family": string, "size": Number} } Font descriptor object
107- */
108- parseFont : function ( fontString ) {
109- fontString = fontString . trim ( ) ;
110- const font_split = fontString . split ( " " ) ;
111-
112- const font_size = parseInt ( font_split . pop ( ) ) ;
113- let font_family = font_split . join ( " " ) ;
114-
115- return {
116- "family" : font_family ,
117- "size" : font_size
95+ initializeData : function ( ) {
96+ this . lastClick = {
97+ "link_index" : null ,
98+ "time" : Infinity ,
11899 } ;
100+
101+ // MAKE SURE to create a deepcopy of linkList, otherwise you could modify actual desklet settings using this variable
102+ this . links_list_deepcopy = JSON . parse ( JSON . stringify ( this . linksList ) ) ;
103+ this . links = this . getLinks ( this . links_list_deepcopy ) ;
104+
105+ this . font = this . parseFontStringToCSS ( this . fontRaw ) ;
119106 } ,
120107
108+ /**
109+ * Parse raw font string.
110+ * @param {string } font_string - Font descriptor string
111+ * @returns {{"font-family": string, "font-size": Number, "font-weight": Number, "font-style": string, "font-stretch": string} } Font descriptor object
112+ */
113+ parseFontStringToCSS : function ( font_string ) {
114+ // Some fonts don't work, so a fallback font is a good idea
115+ const fallback_font_str = "Ubuntu Regular 16" ;
116+
117+ // String are passed by reference here
118+ // make sure to copy the string to avoid triggering settings callback on change
119+ const font_string_copy = font_string . slice ( ) . trim ( ) ;
120+
121+ let css_font ;
122+ try {
123+ const my_font_description = Pango . font_description_from_string ( font_string_copy ) ;
124+ css_font = this . _PangoFontDescriptionToCSS ( my_font_description ) ;
125+ } catch ( e ) {
126+ Main . notifyError (
127+ _ ( "Sorry, this font is not supported, please select a different one." )
128+ + _ ( " Font: `" ) + font_string_copy + _ ( "` Error: " )
129+ + e . toString ( )
130+ ) ;
131+
132+ const fallback_font_description = Pango . font_description_from_string ( fallback_font_str ) ;
133+ css_font = this . _PangoFontDescriptionToCSS ( fallback_font_description ) ;
134+ } finally {
135+ return css_font ;
136+ }
137+
138+ } ,
139+
140+
141+ /**
142+ * Process Pango.FontDescription and return valid CSS values
143+ * @param {Pango.FontDescription } font_description - Font descriptor
144+ * @returns {{"font-family": string, "font-size": Number, "font-weight": Number, "font-style": string, "font-stretch": string} } Font descriptor object
145+ */
146+ _PangoFontDescriptionToCSS : function ( font_description ) {
147+ const PangoStyle_to_CSS_map = {
148+ [ Pango . Style . NORMAL ] : "normal" ,
149+ [ Pango . Style . OBLIQUE ] : "oblique" ,
150+ [ Pango . Style . ITALIC ] : "italic" ,
151+ } ;
152+
153+ // font-stretch CSS property seems to be ignored by the CSS renderer
154+ const PangoStretch_to_CSS_map = {
155+ [ Pango . Stretch . ULTRA_CONDENSED ] : "ultra-condensed" ,
156+ [ Pango . Stretch . EXTRA_CONDENSED ] : "extra-condensed" ,
157+ [ Pango . Stretch . CONDENSED ] : "condensed" ,
158+ [ Pango . Stretch . NORMAL ] : "normal" ,
159+ [ Pango . Stretch . SEMI_EXPANDED ] : "semi-expanded" ,
160+ [ Pango . Stretch . EXPANDED ] : "expanded" ,
161+ [ Pango . Stretch . EXTRA_EXPANDED ] : "extra-expanded" ,
162+ [ Pango . Stretch . ULTRA_EXPANDED ] : "ultra-expanded" ,
163+ } ;
164+
165+ return {
166+ "font-family" : font_description . get_family ( ) ,
167+ "font-size" : Math . floor ( font_description . get_size ( ) / Pango . SCALE ) ,
168+ "font-weight" : font_description . get_weight ( ) ,
169+ "font-style" : PangoStyle_to_CSS_map [ font_description . get_style ( ) ] ,
170+ "font-stretch" : PangoStretch_to_CSS_map [ font_description . get_stretch ( ) ]
171+ } ;
172+ } ,
173+
121174
122175 /**
123176 * Process raw links and output links that are ready to be rendered onto the screen
@@ -161,16 +214,17 @@ MyDesklet.prototype = {
161214 const desktop_settings = new Gio . Settings ( { schema_id : "org.cinnamon.desktop.interface" } ) ;
162215 this . text_scale = desktop_settings . get_double ( "text-scaling-factor" ) ;
163216
164- this . font [ "size" ] = this . font [ "size" ] * this . text_scale ;
217+ this . font [ "font- size" ] = this . font [ "font- size" ] * this . text_scale ;
165218
166- // For some reason the border is inside the element not outside like normal CSS?? So border needs to be taken into account here
167- const default_link_width = 80 + this . linkBorderWidth * 12 ;
219+ const default_link_width = 80 ;
168220
169221 // Calculate new sizes based on scale and layout
170222 // Multipliers are based on experimentation with what looks good
171223 const width_scale = ( this . deskletLayout === "tile" ) ? 0.7 : 1
172224 this . scale = this . scaleSize * global . ui_scale * width_scale ;
173- this . link_width = default_link_width * this . scale * this . text_scale ;
225+
226+ // For some reason the border is inside the element not outside like normal CSS?? So border needs to be taken into account here
227+ this . link_width = default_link_width * this . scale * this . text_scale + this . linkBorderWidth * 2 ;
174228
175229 const spacing_scale = ( this . deskletLayout === "tile" ) ? 2 : 1 ;
176230 const link_row_spacing = this . rowSpacing * this . scale * spacing_scale ;
@@ -222,12 +276,13 @@ MyDesklet.prototype = {
222276 link_el . set_width ( Math . round ( this . link_width ) ) ;
223277
224278
225- link_el . style = "font-family: '" + this . font [ "family" ] + "';"
226- + "font-size: " + this . font [ "size" ] + "px;"
279+ link_el . style = "font-family: '" + this . font [ "font- family" ] + "';"
280+ + "font-size: " + this . font [ "font- size" ] + "px;"
227281 + "color:" + this . customTextColor + ";"
228- + "font-weight:" + ( this . fontBold ? "bold" : "normal" ) + ";"
229- + "font-style:" + ( this . fontItalic ? "italic" : "normal" ) + ";"
230- + "text-shadow:" + ( this . textShadow ? "1px 1px 6px " + this . textShadowColor : "none" ) + ";"
282+ + "font-weight:" + this . font [ "font-weight" ] + ";"
283+ + "font-style:" + this . font [ "font-style" ] + ";"
284+ + "font-stretch:" + this . font [ "font-stretch" ] + ";"
285+ + "text-shadow:" + ( this . textShadow ? "2px 2px 4px " + this . textShadowColor : "none" ) + ";"
231286 + "background-color:" + ( this . isTransparentBg ? "unset" : this . customBgColor ) + ";"
232287 + "text-align:" + this . textAlign + ";"
233288 + "border: solid " + this . linkBorderWidth + "px " + this . linkBorderColor + ";" ;
@@ -265,7 +320,7 @@ MyDesklet.prototype = {
265320 if ( this . deskletLayout === "tile" ) {
266321 label_width = Math . round ( this . link_width * 0.92 ) ;
267322 } else {
268- label_width = Math . round ( this . link_width - global . ui_scale * ( this . iconSize * 1.1 + this . font [ "size" ] * 1.15 + 5 ) )
323+ label_width = Math . round ( this . link_width - global . ui_scale * ( this . iconSize * 1.1 + this . font [ "font- size" ] * 1.15 + 5 ) - this . linkBorderWidth * 1.1 )
269324 label_width = Math . max ( label_width , 0 ) ;
270325 }
271326 link_label . set_width ( label_width ) ;
@@ -369,6 +424,75 @@ MyDesklet.prototype = {
369424
370425 } ,
371426
427+ /**
428+ * Add options to context menu
429+ */
430+ populateContextMenu : function ( ) {
431+ let menuItem = new PopupMenu . PopupMenuItem ( _ ( "Add new link" ) ) ;
432+ this . _menu . addMenuItem ( menuItem ) ;
433+ menuItem . connect ( "activate" , Lang . bind ( this , Lang . bind ( this , ( ) => {
434+ this . handleAddEditDialog ( ) ;
435+ } ) ) ) ;
436+ } ,
437+
438+ handleAddEditDialog ( ) {
439+ try {
440+
441+ // Column ids here must match columns ids in the settings
442+ const columns = [
443+ { "id" : "is-visible" , "title" : "Display" , "type" : "boolean" , "default" : true } ,
444+ { "id" : "name" , "title" : "Name" , "type" : "string" , "default" : "Name" } ,
445+ { "id" : "icon-name" , "title" : "Icon" , "type" : "icon" , "default" : "folder" } ,
446+ { "id" : "command" , "title" : "Command" , "type" : "string" , "default" : "nemo /" } ,
447+ { "id" : "shell" , "title" : "Shell" , "type" : "boolean" , "default" : true , "align" : 0 }
448+ ] ;
449+
450+ const [ success_ , command_argv ] = GLib . shell_parse_argv (
451+ `python3 ${ DESKLET_ROOT } /open_add_edit_dialog_gtk.py '${ JSON . stringify ( columns ) . replaceAll ( "'" , "" ) } '`
452+ ) ;
453+
454+
455+ const proc = Gio . Subprocess . new (
456+ command_argv ,
457+ Gio . SubprocessFlags . STDOUT_PIPE
458+ ) ;
459+
460+ // Python script prints the output of the dialog when user accepts it, code here is retrieving this output from the stdout pipe
461+ proc . wait_async ( null , ( ) => {
462+ proc . communicate_utf8_async ( null , null , ( proc , result ) => {
463+
464+ const [ is_ok , dialog_output , _ ] = proc . communicate_utf8_finish ( result ) ;
465+ let dialog_json ;
466+ try {
467+ dialog_json = JSON . parse ( dialog_output ) ;
468+ } catch ( e ) {
469+ global . logError ( e ) ;
470+ return ;
471+ }
472+
473+ // Add a new link when communication was correct and response doesn't have error:true
474+ if ( is_ok && ( ! dialog_json . error || dialog_json . error === undefined ) ) {
475+
476+ try {
477+ let new_settings_list = this . links_list_deepcopy . concat ( JSON . parse ( dialog_output ) ) ;
478+ this . settings . setValue ( "links-list" , new_settings_list ) ;
479+ this . initializeData ( ) ;
480+ this . renderGUI ( ) ;
481+ } catch ( e ) {
482+ global . logError ( e ) ;
483+ return ;
484+ }
485+
486+ }
487+ } ) ;
488+ } ) ;
489+
490+ } catch ( e ) {
491+ global . logError ( e ) ;
492+ }
493+ } ,
494+
495+
372496 /**
373497 * Render desklet decorations
374498 */
@@ -384,15 +508,18 @@ MyDesklet.prototype = {
384508 * This function should be used as a callback when settings change
385509 */
386510 on_setting_changed : function ( ) {
387- this . runDesklet ( ) ;
511+ this . initializeData ( ) ;
512+ this . renderGUI ( ) ;
513+ this . renderDecorations ( ) ;
388514 } ,
389515
390516
391517 /**
392518 * This function should be used as a callback user clicks a button in the settings
393519 */
394520 on_reset_links_callback : function ( ) {
395- this . runDesklet ( ) ;
521+ this . initializeData ( ) ;
522+ this . renderGUI ( ) ;
396523 }
397524
398525}
0 commit comments