diff -Nru cinnamon-3.2.7/configure.ac cinnamon-3.2.8/configure.ac --- cinnamon-3.2.7/configure.ac 2016-12-22 11:45:34.000000000 +0000 +++ cinnamon-3.2.8/configure.ac 2017-01-07 11:20:40.000000000 +0000 @@ -1,5 +1,5 @@ AC_PREREQ(2.63) -AC_INIT([cinnamon],[3.2.7],[https://github.com/linuxmint/Cinnamon/issues],[cinnamon]) +AC_INIT([cinnamon],[3.2.8],[https://github.com/linuxmint/Cinnamon/issues],[cinnamon]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_SRCDIR([src/cinnamon-global.c]) diff -Nru cinnamon-3.2.7/debian/changelog cinnamon-3.2.8/debian/changelog --- cinnamon-3.2.7/debian/changelog 2016-12-23 18:21:59.000000000 +0000 +++ cinnamon-3.2.8/debian/changelog 2017-01-15 12:36:29.000000000 +0000 @@ -1,8 +1,25 @@ -cinnamon (3.2.7-1~yakkety0) yakkety; urgency=medium +cinnamon (3.2.8-1~yakkety0) yakkety; urgency=medium * Package version bump to avoid release clash on Launchpad. - -- embrosyn Fri, 23 Dec 2016 19:21:52 +0100 + -- embrosyn Sun, 15 Jan 2017 13:36:29 +0100 + +cinnamon (3.2.8) yakkety; urgency=medium + + [ Michael Webster ] + * menu applet: Only construct one context menu for recent files, and re-use it. + * cs_screensaver.py: Re-use the Gtk.Socket always - it can hold both Gtk.Plugs and normal Gtk.Widgets, just treat it like a normal bin/container. This seems to assist on ref-cleanup and child reaping when switching screensavers. (observed on Arch, reported on Arch and Fedora) + * menu applet: Don't reconstruct recent files, just re-order, add, remove as required. + * menu applet: Fix a couple of minor leaks (actors created but never added to a container - being GInitiallyUnowned, they never get their floating ref sunk, and so can't be unreffed. Fix a prototype mis-match, and just rename GenericButton to NoRecentDocsButton, since that's the only user of it. + * menu applet: Some fixes for previous commits, don't be so destructive when refreshing applications. + * Revert "cs_screensaver.py: Re-use the Gtk.Socket always - it can hold both Gtk.Plugs" + * Fix tuples for python 3.6 + * tooltips.js: add a hide timer to check if a tooltip should hide itself, but for various reasons never got to act on a leave-event (due to vagaries in event reporting). + * recent applet: Rework to be non-destructive when updating the recent list (this brings a lot of the recent changes to the menu applet over to this one.) + * recents, applets: Couple warning cleanups + * network applet: Remove a useless log warning. At some point more than a year ago we started getting these (possibly a change to network-manager), they have no value whatsoever, but fill up the log. + + -- Clement Lefebvre Sat, 07 Jan 2017 11:19:30 +0000 cinnamon (3.2.7) yakkety; urgency=medium diff -Nru cinnamon-3.2.7/files/usr/bin/cinnamon-desktop-editor cinnamon-3.2.8/files/usr/bin/cinnamon-desktop-editor --- cinnamon-3.2.7/files/usr/bin/cinnamon-desktop-editor 2016-12-22 11:45:34.000000000 +0000 +++ cinnamon-3.2.8/files/usr/bin/cinnamon-desktop-editor 2017-01-07 11:20:40.000000000 +0000 @@ -7,4 +7,4 @@ import os import sys -os.execvp("/usr/share/cinnamon/cinnamon-desktop-editor/cinnamon-desktop-editor.py", ("",) + tuple(sys.argv[1:])) +os.execvp("/usr/share/cinnamon/cinnamon-desktop-editor/cinnamon-desktop-editor.py", (" ",) + tuple(sys.argv[1:])) diff -Nru cinnamon-3.2.7/files/usr/bin/cinnamon-json-makepot cinnamon-3.2.8/files/usr/bin/cinnamon-json-makepot --- cinnamon-3.2.7/files/usr/bin/cinnamon-json-makepot 2016-12-22 11:45:34.000000000 +0000 +++ cinnamon-3.2.8/files/usr/bin/cinnamon-json-makepot 2017-01-07 11:20:40.000000000 +0000 @@ -8,4 +8,4 @@ import os import sys -os.execvp("/usr/share/cinnamon/cinnamon-json-makepot/cinnamon-json-makepot.py", ("",) + tuple(sys.argv[1:])) +os.execvp("/usr/share/cinnamon/cinnamon-json-makepot/cinnamon-json-makepot.py", (" ",) + tuple(sys.argv[1:])) diff -Nru cinnamon-3.2.7/files/usr/bin/cinnamon-looking-glass cinnamon-3.2.8/files/usr/bin/cinnamon-looking-glass --- cinnamon-3.2.7/files/usr/bin/cinnamon-looking-glass 2016-12-22 11:45:34.000000000 +0000 +++ cinnamon-3.2.8/files/usr/bin/cinnamon-looking-glass 2017-01-07 11:20:40.000000000 +0000 @@ -8,4 +8,4 @@ import os import sys -os.execvp("/usr/share/cinnamon/cinnamon-looking-glass/cinnamon-looking-glass.py", ("",) + tuple(sys.argv[1:])) +os.execvp("/usr/share/cinnamon/cinnamon-looking-glass/cinnamon-looking-glass.py", (" ",) + tuple(sys.argv[1:])) diff -Nru cinnamon-3.2.7/files/usr/bin/cinnamon-settings cinnamon-3.2.8/files/usr/bin/cinnamon-settings --- cinnamon-3.2.7/files/usr/bin/cinnamon-settings 2016-12-22 11:45:34.000000000 +0000 +++ cinnamon-3.2.8/files/usr/bin/cinnamon-settings 2017-01-07 11:20:40.000000000 +0000 @@ -11,16 +11,16 @@ if len(sys.argv) > 1: module = sys.argv[1] if module in ("applets", "desklets", "extensions") and len(sys.argv) > 2 and sys.argv[2][0:5] != "panel": - os.execvp("/usr/share/cinnamon/cinnamon-settings/xlet-settings.py", ("", module[0:-1]) + tuple(sys.argv[2:])) + os.execvp("/usr/share/cinnamon/cinnamon-settings/xlet-settings.py", (" ", module[0:-1]) + tuple(sys.argv[2:])) if os.path.exists("/usr/share/cinnamon/cinnamon-settings/modules/cs_%s.py" % module): - os.execvp("/usr/share/cinnamon/cinnamon-settings/cinnamon-settings.py", ("",) + tuple(sys.argv[1:])) + os.execvp("/usr/share/cinnamon/cinnamon-settings/cinnamon-settings.py", (" ",) + tuple(sys.argv[1:])) elif os.path.exists("/usr/bin/cinnamon-control-center"): - os.execvp("/usr/share/cinnamon/cinnamon-settings/cinnamon-settings.py", ("",) + tuple(sys.argv[1:])) + os.execvp("/usr/share/cinnamon/cinnamon-settings/cinnamon-settings.py", (" ",) + tuple(sys.argv[1:])) elif os.path.exists("/usr/bin/gnome-control-center"): print ("Unknown module %s, calling gnome-control-center" % module) - os.execvp("gnome-control-center", ("",) + tuple(sys.argv[1:])) + os.execvp("gnome-control-center", (" ",) + tuple(sys.argv[1:])) else: print ("Unknown module %s" % module) - os.execvp("/usr/share/cinnamon/cinnamon-settings/cinnamon-settings.py", ("",) + tuple(sys.argv[1:])) + os.execvp("/usr/share/cinnamon/cinnamon-settings/cinnamon-settings.py", (" ",) + tuple(sys.argv[1:])) else: - os.execvp("/usr/share/cinnamon/cinnamon-settings/cinnamon-settings.py", ("",) + tuple(sys.argv[1:])) + os.execvp("/usr/share/cinnamon/cinnamon-settings/cinnamon-settings.py", (" ",) + tuple(sys.argv[1:])) diff -Nru cinnamon-3.2.7/files/usr/bin/cinnamon-slideshow cinnamon-3.2.8/files/usr/bin/cinnamon-slideshow --- cinnamon-3.2.7/files/usr/bin/cinnamon-slideshow 2016-12-22 11:45:34.000000000 +0000 +++ cinnamon-3.2.8/files/usr/bin/cinnamon-slideshow 2017-01-07 11:20:40.000000000 +0000 @@ -5,4 +5,4 @@ import os -os.execvp("/usr/share/cinnamon/cinnamon-slideshow/cinnamon-slideshow.py", ("",)) +os.execvp("/usr/share/cinnamon/cinnamon-slideshow/cinnamon-slideshow.py", (" ",)) diff -Nru cinnamon-3.2.7/files/usr/bin/xlet-settings cinnamon-3.2.8/files/usr/bin/xlet-settings --- cinnamon-3.2.7/files/usr/bin/xlet-settings 2016-12-22 11:45:34.000000000 +0000 +++ cinnamon-3.2.8/files/usr/bin/xlet-settings 2017-01-07 11:20:40.000000000 +0000 @@ -5,4 +5,4 @@ if len(sys.argv) < 3: print("usage:\n xlet-settings \nor\n xlet-settings ") else: - os.execvp("/usr/share/cinnamon/cinnamon-settings/xlet-settings.py", ("",) + tuple(sys.argv[1:])) + os.execvp("/usr/share/cinnamon/cinnamon-settings/xlet-settings.py", (" ",) + tuple(sys.argv[1:])) diff -Nru cinnamon-3.2.7/files/usr/share/cinnamon/applets/menu@cinnamon.org/applet.js cinnamon-3.2.8/files/usr/share/cinnamon/applets/menu@cinnamon.org/applet.js --- cinnamon-3.2.7/files/usr/share/cinnamon/applets/menu@cinnamon.org/applet.js 2016-12-22 11:45:34.000000000 +0000 +++ cinnamon-3.2.8/files/usr/share/cinnamon/applets/menu@cinnamon.org/applet.js 2017-01-07 11:20:40.000000000 +0000 @@ -29,7 +29,6 @@ const MAX_FAV_ICON_SIZE = 32; const CATEGORY_ICON_SIZE = 22; const APPLICATION_ICON_SIZE = 22; -const MAX_RECENT_FILES = 20; const INITIAL_BUTTON_LOAD = 30; const MAX_BUTTON_WIDTH = "max-width: 20em;"; @@ -184,7 +183,7 @@ } GenericApplicationButton.prototype = { - __proto__: PopupMenu.PopupSubMenuMenuItem.prototype, + __proto__: PopupMenu.PopupBaseMenuItem.prototype, _init: function(appsMenuButton, app, withMenu) { this.app = app; @@ -284,6 +283,20 @@ get _contextIsOpen() { return this.menu.isOpen; + }, + + destroy: function() { + this.label.destroy(); + + if (this.icon) { + this.icon.destroy(); + } + + if (this.withMenu) { + this.menu.destroy(); + } + + PopupMenu.PopupBaseMenuItem.prototype.destroy.call(this); } } @@ -593,7 +606,7 @@ } RecentButton.prototype = { - __proto__: PopupMenu.PopupSubMenuMenuItem.prototype, + __proto__: PopupMenu.PopupBaseMenuItem.prototype, _init: function(appsMenuButton, file, showIcon) { PopupMenu.PopupBaseMenuItem.prototype._init.call(this, {hover: false}); @@ -615,10 +628,6 @@ if (showIcon) this.icon.realize(); this.label.realize(); - - this.menu = new PopupMenu.PopupSubMenu(this.actor); - this.menu.actor.set_style_class_name('menu-context-menu'); - this.menu.connect('open-state-changed', Lang.bind(this, this._subMenuOpenStateChanged)); }, _onButtonReleaseEvent: function (actor, event) { @@ -637,7 +646,9 @@ }, activateContextMenus: function(event) { - if (!this.menu.isOpen) + let menu = this.appsMenuButton.recentContextMenu; + + if (menu != null && menu.isOpen) this.appsMenuButton.closeContextMenus(this, true); this.toggleMenu(); }, @@ -651,16 +662,34 @@ }, toggleMenu: function() { - if (!this.menu.isOpen){ - let children = this.menu.box.get_children(); + if (this.appsMenuButton.recentContextMenu == null) { + let menu = new PopupMenu.PopupSubMenu(this.actor); + menu.actor.set_style_class_name('menu-context-menu'); + menu.connect('open-state-changed', Lang.bind(this, this._subMenuOpenStateChanged)); + this.appsMenuButton.recentContextMenu = menu; + } + + let menu = this.appsMenuButton.recentContextMenu; + + if (!menu.isOpen) { + let parent = menu.actor.get_parent(); + if (parent != null) { + parent.remove_child(menu.actor); + } + + menu.sourceActor = this.actor; + this.actor.get_parent().insert_child_above(menu.actor, this.actor); + + let children = menu.box.get_children(); for (var i in children) { - this.menu.box.remove_actor(children[i]); + menu.box.remove_actor(children[i]); } + let menuItem; menuItem = new PopupMenu.PopupMenuItem(_("Open with"), { reactive: false }); menuItem.actor.style = "font-weight: bold"; - this.menu.addMenuItem(menuItem); + menu.addMenuItem(menuItem); let file = Gio.File.new_for_uri(this.uri); @@ -675,7 +704,7 @@ this.toggleMenu(); this.appsMenuButton.menu.close(); })); - this.menu.addMenuItem(menuItem); + menu.addMenuItem(menuItem); } let infos = Gio.AppInfo.get_all_for_type(this.mimeType) @@ -699,7 +728,7 @@ this.toggleMenu(); this.appsMenuButton.menu.close(); })); - this.menu.addMenuItem(menuItem); + menu.addMenuItem(menuItem); } if (GLib.find_program_in_path ("nemo-open-with") != null) { @@ -711,16 +740,16 @@ this.toggleMenu(); this.appsMenuButton.menu.close(); })); - this.menu.addMenuItem(menuItem); + menu.addMenuItem(menuItem); } } - this.menu.toggle(); + this.appsMenuButton.recentContextMenu.toggle(); }, - _subMenuOpenStateChanged: function() { - if (this.menu.isOpen) { + _subMenuOpenStateChanged: function(recentContextMenu) { + if (recentContextMenu.isOpen) { this.appsMenuButton._activeContextMenuParent = this; - this.appsMenuButton._scrollToButton(this.menu); + this.appsMenuButton._scrollToButton(recentContextMenu); } else { this.appsMenuButton._activeContextMenuItem = null; this.appsMenuButton._activeContextMenuParent = null; @@ -728,15 +757,25 @@ }, get _contextIsOpen() { - return this.menu.isOpen; - } + return this.appsMenuButton.recentContextMenu != null && this.appsMenuButton.recentContextMenu.isOpen; + }, + + destroy: function() { + this.file = null; + this.appsMenuButton = null; + this.label.destroy(); + if (this.icon) + this.icon.destroy(); + + PopupMenu.PopupBaseMenuItem.prototype.destroy.call(this); + }, }; -function GenericButton(label, icon, reactive, callback) { +function NoRecentDocsButton(label, icon, reactive, callback) { this._init(label, icon, reactive, callback); } -GenericButton.prototype = { +NoRecentDocsButton.prototype = { __proto__: PopupMenu.PopupBaseMenuItem.prototype, _init: function(label, icon, reactive, callback) { @@ -759,8 +798,6 @@ this.actor.reactive = reactive; this.callback = callback; - - this.menu = new PopupMenu.PopupSubMenu(this.actor); }, _onButtonReleaseEvent: function (actor, event) { @@ -787,8 +824,6 @@ this.icon = new St.Icon({ icon_name: 'edit-clear', icon_type: St.IconType.SYMBOLIC, icon_size: APPLICATION_ICON_SIZE }); this.addActor(this.icon); this.addActor(this.label); - - this.menu = new PopupMenu.PopupSubMenu(this.actor); }, _onButtonReleaseEvent: function (actor, event) { @@ -1189,6 +1224,7 @@ this._favoritesButtons = new Array(); this._placesButtons = new Array(); this._transientButtons = new Array(); + this.recentButton = null; this._recentButtons = new Array(); this._categoryButtons = new Array(); this._searchProviderButtons = new Array(); @@ -1222,6 +1258,9 @@ this.settings.bind("search-filesystem", "searchFilesystem"); this.refreshing = false; // used as a flag to know if we're currently refreshing (so we don't do it more than once concurrently) + this.recentContextMenu = null; + this.appsContextMenu = null; + // We shouldn't need to call refreshAll() here... since we get a "icon-theme-changed" signal when CSD starts. // The reason we do is in case the Cinnamon icon theme is the same as the one specificed in GTK itself (in .config) // In that particular case we get no signal at all. @@ -1337,7 +1376,7 @@ this.menu = new Applet.AppletPopupMenu(this, orientation); this.menuManager.addMenu(this.menu); - this.menu.actor.add_style_class_name('menu-background'); + this.menu.setCustomStyleClass('menu-background'); this.menu.connect('open-state-changed', Lang.bind(this, this._onOpenStateChanged)); this._display(); @@ -2110,12 +2149,16 @@ this._placesButtons[i].actor.destroy(); } + this._placesButtons = []; + for (let i = 0; i < this._categoryButtons.length; i++) { if (this._categoryButtons[i] instanceof PlaceCategoryButton) { - this._categoryButtons[i].actor.destroy(); + this._categoryButtons[i].destroy(); + this._categoryButtons.splice(i, 1); + this.placesButton = null; + break; } } - this._placesButtons = new Array(); // Now generate Places category and places buttons and add to the list if (this.showPlaces) { @@ -2189,105 +2232,215 @@ }, _refreshRecent : function() { - for (let i = 0; i < this._recentButtons.length; i ++) { - this._recentButtons[i].actor.destroy(); - } - for (let i = 0; i < this._categoryButtons.length; i++) { - if (this._categoryButtons[i] instanceof RecentCategoryButton) { - this._categoryButtons[i].actor.destroy(); - } - } - this._recentButtons = new Array(); - - // Now generate recent category and recent files buttons and add to the list if (this.privacy_settings.get_boolean(REMEMBER_RECENT_KEY)) { - this.recentButton = new RecentCategoryButton(null, this.showCategoryIcons); - this._addEnterEvent(this.recentButton, Lang.bind(this, function() { - if (!this.searchActive) { - this.recentButton.isHovered = true; + if (this.recentButton == null) { + this.recentButton = new RecentCategoryButton(null, this.showCategoryIcons); + this._addEnterEvent(this.recentButton, Lang.bind(this, function() { + if (!this.searchActive) { + this.recentButton.isHovered = true; + + Mainloop.idle_add_full(Mainloop.PRIORITY_DEFAULT, Lang.bind(this, function() { + if (this.recentButton.isHovered) { + this._clearPrevCatSelection(this.recentButton.actor); + this.recentButton.actor.style_class = "menu-category-button-selected"; + this.closeContextMenus(null, false); + this._displayButtons(null, null, -1); + } else { + this.recentButton.actor.style_class = "menu-category-button"; + } + })) - Mainloop.idle_add_full(Mainloop.PRIORITY_DEFAULT, Lang.bind(this, function() { - if (this.recentButton.isHovered) { - this._clearPrevCatSelection(this.recentButton.actor); - this.recentButton.actor.style_class = "menu-category-button-selected"; - this.closeContextMenus(null, false); - this._displayButtons(null, null, -1); - } else { - this.recentButton.actor.style_class = "menu-category-button"; + this.makeVectorBox(this.recentButton.actor); + } + })); + this.recentButton.actor.connect('leave-event', Lang.bind(this, function () { + + if (this._previousTreeSelectedActor === null) { + this._previousTreeSelectedActor = this.recentButton.actor; + } else { + let prevIdx = this.catBoxIter.getVisibleIndex(this._previousTreeSelectedActor); + let nextIdx = this.catBoxIter.getVisibleIndex(this.recentButton.actor); + + if (Math.abs(prevIdx - nextIdx) <= 1) { + this._previousTreeSelectedActor = this.recentButton.actor; } - })) + } - this.makeVectorBox(this.recentButton.actor); - } - })); - this.recentButton.actor.connect('leave-event', Lang.bind(this, function () { + this.recentButton.isHovered = false; + })); - if (this._previousTreeSelectedActor === null) { - this._previousTreeSelectedActor = this.recentButton.actor; - } else { - let prevIdx = this.catBoxIter.getVisibleIndex(this._previousTreeSelectedActor); - let nextIdx = this.catBoxIter.getVisibleIndex(this.recentButton.actor); + this._categoryButtons.push(this.recentButton); + } - if (Math.abs(prevIdx - nextIdx) <= 1) { - this._previousTreeSelectedActor = this.recentButton.actor; - } - } + /* Make sure the recent category is at the bottom (can happen when refreshing places + * or apps, since we don't destroy the recent category button each time we refresh recents, + * as it happens a lot) */ + + let parent = this.recentButton.actor.get_parent(); + + if (parent != null) { + parent.remove_child(this.recentButton.actor); + } - this.recentButton.isHovered = false; - })); this.categoriesBox.add_actor(this.recentButton.actor); + this._categoryButtons.splice(this._categoryButtons.indexOf(this.recentButton), 1); this._categoryButtons.push(this.recentButton); + let new_recents = []; + if (this.RecentManager._infosByTimestamp.length > 0) { - for (let id = 0; id < MAX_RECENT_FILES && id < this.RecentManager._infosByTimestamp.length; id++) { - let button = new RecentButton(this, this.RecentManager._infosByTimestamp[id], this.showApplicationIcons); + let id = 0; + while (id < this.RecentManager._infosByTimestamp.length) { + let uri = this.RecentManager._infosByTimestamp[id].uri; + + let new_button = null; + + new_button = this._recentButtons.find(button => ((button instanceof RecentButton) && + (button.uri) && (button.uri == uri))); + + if (new_button == undefined) { + let button = new RecentButton(this, this.RecentManager._infosByTimestamp[id], this.showApplicationIcons); + this._addEnterEvent(button, Lang.bind(this, function() { + this._clearPrevSelection(button.actor); + button.actor.style_class = "menu-application-button-selected"; + this.selectedAppTitle.set_text(""); + let selectedAppUri = button.uriDecoded; + let fileIndex = selectedAppUri.indexOf("file:///"); + if (fileIndex !== -1) + selectedAppUri = selectedAppUri.substr(fileIndex + 7); + this.selectedAppDescription.set_text(selectedAppUri); + })); + + button.actor.connect('leave-event', Lang.bind(this, function() { + button.actor.style_class = "menu-application-button"; + this._previousSelectedActor = button.actor; + this.selectedAppTitle.set_text(""); + this.selectedAppDescription.set_text(""); + })); + + new_button = button + } + + new_recents.push(new_button); + + id++; + } + + let recent_clear_button = null; + + recent_clear_button = this._recentButtons.find(button => (button instanceof RecentClearButton)); + + if (recent_clear_button == undefined) { + let button = new RecentClearButton(this); this._addEnterEvent(button, Lang.bind(this, function() { this._clearPrevSelection(button.actor); button.actor.style_class = "menu-application-button-selected"; - this.selectedAppTitle.set_text(""); - let selectedAppUri = button.uriDecoded; - let fileIndex = selectedAppUri.indexOf("file:///"); - if (fileIndex !== -1) - selectedAppUri = selectedAppUri.substr(fileIndex + 7); - this.selectedAppDescription.set_text(selectedAppUri); - - let file = Gio.file_new_for_uri(button.uri); - if (!file.query_exists(null)) - this.selectedAppTitle.set_text(_("This file is no longer available")); })); button.actor.connect('leave-event', Lang.bind(this, function() { button.actor.style_class = "menu-application-button"; this._previousSelectedActor = button.actor; - this.selectedAppTitle.set_text(""); - this.selectedAppDescription.set_text(""); })); - let file = Gio.file_new_for_uri(button.uri); - if (file.query_exists(null)) { - this._recentButtons.push(button); - this.applicationsBox.add_actor(button.actor); - this.applicationsBox.add_actor(button.menu.actor); - } + + recent_clear_button = button; } - let button = new RecentClearButton(this); - this._addEnterEvent(button, Lang.bind(this, function() { - this._clearPrevSelection(button.actor); - button.actor.style_class = "menu-application-button-selected"; - })); - button.actor.connect('leave-event', Lang.bind(this, function() { - button.actor.style_class = "menu-application-button"; - this._previousSelectedActor = button.actor; - })); - this._recentButtons.push(button); - this.applicationsBox.add_actor(button.actor); + new_recents.push(recent_clear_button); + this.noRecentDocuments = false; } else { - let button = new GenericButton(_("No recent documents"), null, false, null); - this._recentButtons.push(button); - this.applicationsBox.add_actor(button.actor); + let new_button = null; + + for (let existing_button in this._recentButtons) { + let button = this._recentButtons[existing_button]; + + if (button instanceof NoRecentDocsButton) { + new_button = button; + break; + } + } + + if (new_button == null) { + new_button = new NoRecentDocsButton(_("No recent documents"), null, false, null); + } + this.noRecentDocuments = true; + new_recents.push(new_button); } + let to_remove = []; + + /* Remove no-longer-valid items */ + for (let i = 0; i < this._recentButtons.length; i++) { + let button = this._recentButtons[i]; + + if (button instanceof NoRecentDocsButton && !this.noRecentDocuments) { + to_remove.push(button); + } else if (button instanceof RecentButton) { + if (new_recents.indexOf(button) == -1) { + to_remove.push(button); + } + } + } + + if (to_remove.length > 0) { + for (let i in to_remove) { + to_remove[i].destroy(); + this._recentButtons.splice(this._recentButtons.indexOf(to_remove[i]), 1); + } + } + + to_remove = []; + + /* Now, add new actors, shuffle existing actors */ + + let placeholder = null; + + /* Find the first occurrence of a RecentButton, if it exists */ + let children = this.applicationsBox.get_children(); + for (let i = children.length - 1; i > 0; i--) { + if ((children[i]._delegate instanceof RecentButton) || + (children[i]._delegate instanceof RecentClearButton) || + (i == children.length - 1)) { + placeholder = children[i - 1]; + break; + } + } + + children = null; + + for (let i = 0; i < new_recents.length; i++) { + let actor = new_recents[i].actor; + + let parent = actor.get_parent(); + if (parent != null) { + parent.remove_child(actor); + } + + if (placeholder != actor) { + this.applicationsBox.insert_child_above(actor, placeholder); + } else { + this.applicationsBox.add_child(actor); + } + + placeholder = actor; + } + + this._recentButtons = new_recents; + } else { + for (let i = 0; i < this._recentButtons.length; i ++) { + this._recentButtons[i].destroy(); + } + + this._recentButtons = []; + + for (let i = 0; i < this._categoryButtons.length; i++) { + if (this._categoryButtons[i] instanceof RecentCategoryButton) { + this._categoryButtons[i].destroy(); + this._categoryButtons.splice(i, 1); + this.recentButton = null; + break; + } + } } this._setCategoriesButtonActive(!this.searchActive); @@ -2297,13 +2450,25 @@ }, _refreshApps : function() { - this.applicationsBox.destroy_all_children(); + /* iterate in reverse, so multiple splices will not upset + * the remaining elements */ + for (let i = this._categoryButtons.length - 1; i > -1; i--) { + if (this._categoryButtons[i] instanceof CategoryButton) { + this._categoryButtons[i].destroy(); + this._categoryButtons.splice(i, 1); + } + } + + this._applicationsButtons.forEach(Lang.bind(this, function(button) { + button.destroy(); + })) + this._applicationsButtons = new Array(); + // this.applicationsBox.destroy_all_children(); + this._transientButtons = new Array(); this._applicationsButtonFromApp = new Object(); this._applicationsBoxWidth = 0; - //Remove all categories - this.categoriesBox.destroy_all_children(); this._allAppsCategoryButton = new CategoryButton(null); this._addEnterEvent(this._allAppsCategoryButton, Lang.bind(this, function() { @@ -2327,7 +2492,9 @@ this._previousSelectedActor = this._allAppsCategoryButton.actor; this._allAppsCategoryButton.isHovered = false; })); + this.categoriesBox.add_actor(this._allAppsCategoryButton.actor); + this._categoryButtons.push(this._allAppsCategoryButton); let trees = [appsys.get_tree()]; @@ -2407,6 +2574,8 @@ } categoryButton.isHovered = false; })); + + this._categoryButtons.push(categoryButton); this.categoriesBox.add_actor(categoryButton.actor); } } @@ -2780,12 +2949,12 @@ } } - for (var recent in this._recentButtons){ - if (recent != excluded && this._recentButtons[recent].menu.isOpen){ + if (excluded != this._activeContextMenuItem) { + if (this.recentContextMenu && this.recentContextMenu.isOpen) { if (animate) - this._recentButtons[recent].toggleMenu(); + this.recentContextMenu.sourceActor._delegate.toggleMenu(); else - this._recentButtons[recent].closeMenu(); + this.recentContextMenu.sourceActor._delegate.closeMenu(); } } }, diff -Nru cinnamon-3.2.7/files/usr/share/cinnamon/applets/network@cinnamon.org/applet.js cinnamon-3.2.8/files/usr/share/cinnamon/applets/network@cinnamon.org/applet.js --- cinnamon-3.2.7/files/usr/share/cinnamon/applets/network@cinnamon.org/applet.js 2016-12-22 11:45:34.000000000 +0000 +++ cinnamon-3.2.8/files/usr/share/cinnamon/applets/network@cinnamon.org/applet.js 2017-01-07 11:20:40.000000000 +0000 @@ -1352,7 +1352,6 @@ let res = this._findExistingNetwork(accessPoint); if (res == null) { - log('Removing an access point that was never added'); return; } diff -Nru cinnamon-3.2.7/files/usr/share/cinnamon/applets/recent@cinnamon.org/applet.js cinnamon-3.2.8/files/usr/share/cinnamon/applets/recent@cinnamon.org/applet.js --- cinnamon-3.2.7/files/usr/share/cinnamon/applets/recent@cinnamon.org/applet.js 2016-12-22 11:45:34.000000000 +0000 +++ cinnamon-3.2.8/files/usr/share/cinnamon/applets/recent@cinnamon.org/applet.js 2017-01-07 11:20:40.000000000 +0000 @@ -17,12 +17,17 @@ MyPopupMenuItem.prototype = { __proto__: PopupMenu.PopupBaseMenuItem.prototype, - _init: function(icon, text, params) - { + + _init: function(icon, text, uri, params) { PopupMenu.PopupBaseMenuItem.prototype._init.call(this, params); this.box = new St.BoxLayout({ style_class: 'popup-combobox-item' }); this.icon = icon; - this.box.add(this.icon); + this.uri = uri; + + if (this.icon) { + this.box.add(this.icon); + } + this.label = new St.Label({ text: text }); this.box.add(this.label); this.addActor(this.box); @@ -45,81 +50,176 @@ this.menuManager = new PopupMenu.PopupMenuManager(this); this.menu = new Applet.AppletPopupMenu(this, orientation); - this.menuManager.addMenu(this.menu); - + this.menuManager.addMenu(this.menu); + + this.recentsScrollBox = new St.ScrollView({ x_fill: true, y_fill: false, y_align: St.Align.START }); + this.recentsScrollBox.set_auto_scrolling(true); + + this.recentsBox = new St.BoxLayout({ vertical:true }); + this.recentsScrollBox.add_actor(this.recentsBox); + this.recentsScrollBox.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); + this.menu.actor.add_actor(this.recentsScrollBox); + this.RecentManager = new DocInfo.DocManager(); this.privacy_settings = new Gio.Settings( {schema_id: PRIVACY_SCHEMA} ); + this._recentButtons = []; this._display(); - this.RecentManager.connect('changed', Lang.bind(this, this._redisplay)); - this.privacy_settings.connect("changed::" + REMEMBER_RECENT_KEY, Lang.bind(this, this._redisplay)); + this.recent_id = this.RecentManager.connect('changed', Lang.bind(this, this._refreshRecents)); + this.settings_id = this.privacy_settings.connect("changed::" + REMEMBER_RECENT_KEY, Lang.bind(this, this._refreshRecents)); } catch (e) { global.logError(e); } }, - - on_applet_clicked: function(event) { - this.menu.toggle(); + + on_applet_removed_from_panel: function () { + this.RecentManager.disconnect(this.recent_id); + this.privacy_settings.disconnect(this.settings_id); }, - - _display: function() { - if (!this.privacy_settings.get_boolean(REMEMBER_RECENT_KEY)) { - let item = new PopupMenu.PopupMenuItem(_("Recent file tracking is currently disabled.")); - item.actor.reactive = false; - this.menu.addMenuItem(item); - - let icon = new St.Icon({ icon_name: 'ok', icon_type: St.IconType.FULLCOLOR, icon_size: 16 }); - item = new MyPopupMenuItem(icon, _("Click here to enable it"), {}); - item.connect("activate", Lang.bind(this, function () { - this.privacy_settings.set_boolean(REMEMBER_RECENT_KEY, true); - })) - this.menu.addMenuItem(item); - return; - } - for (let id = 0; id < 15 && id < this.RecentManager._infosByTimestamp.length; id++) { - let icon = this.RecentManager._infosByTimestamp[id].createIcon(22); - let menuItem = new MyPopupMenuItem(icon, this.RecentManager._infosByTimestamp[id].name, {}); - this.menu.addMenuItem(menuItem); - menuItem.connect('activate', Lang.bind(this, this._launchFile, this.RecentManager._infosByTimestamp[id])); - } - if (this.RecentManager._infosByTimestamp.length > 0) { - this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); - let icon = new St.Icon({ icon_name: 'edit-clear', icon_type: St.IconType.SYMBOLIC, icon_size: 22 }); - let menuItem = new MyPopupMenuItem(icon, _("Clear list"), {}); - this.menu.addMenuItem(menuItem); - menuItem.connect('activate', Lang.bind(this, this._clearAll)); - } else { - this.menu.addMenuItem(new PopupMenu.PopupMenuItem(_("No recent documents"))); - } + on_applet_clicked: function(event) { + this.menu.toggle(); }, - - _redisplay: function() { - this.menu.removeAll(); - this._display(); + + _display: function() { + this._refreshRecents(); }, _launchFile: function(a, b, c, docinfo) { docinfo.launch(); }, - + _clearAll: function() { let GtkRecent = new Gtk.RecentManager(); GtkRecent.purge_items(); }, - + destroy: function() { this.RecentManager.disconnectAll(); this.actor._delegate = null; this.menu.destroy(); this.actor.destroy(); + this._recentButtons = null; this.emit('destroy'); + }, + + _refreshRecents: function() { + if (this.privacy_settings.get_boolean(REMEMBER_RECENT_KEY)) { + let new_recents = []; + let have_recents = false; + + if (this.RecentManager._infosByTimestamp.length > 0) { + let id = 0; + while (id < this.RecentManager._infosByTimestamp.length) { + let uri = this.RecentManager._infosByTimestamp[id].uri; + + let new_button = null; + + new_button = this._recentButtons.find(button => ((button.uri) && (button.uri == uri))); + + if (new_button == undefined) { + let icon = this.RecentManager._infosByTimestamp[id].createIcon(22); + let menuItem = new MyPopupMenuItem(icon, this.RecentManager._infosByTimestamp[id].name, uri, {}); + this.menu.addMenuItem(menuItem); + menuItem.connect('activate', Lang.bind(this, this._launchFile, this.RecentManager._infosByTimestamp[id])); + new_button = menuItem; + } + + new_recents.push(new_button); + + id++; + } + + let recent_clear_button = null; + + recent_clear_button = this._recentButtons.find(button => ((button.uri) && (button.uri == "clear"))); + + if (recent_clear_button == undefined) { + let icon = new St.Icon({ icon_name: 'edit-clear', icon_type: St.IconType.SYMBOLIC, icon_size: 22 }); + let menuItem = new MyPopupMenuItem(icon, _("Clear list"), "clear", {}); + menuItem.connect('activate', Lang.bind(this, this._clearAll)); + + recent_clear_button = menuItem; + } + + have_recents = true; + new_recents.push(recent_clear_button); + } else { + let no_recents_button = null; + + no_recents_button = this._recentButtons.find(button => ((button.uri) && (button.uri == "no-recents"))); + + if (no_recents_button == undefined) { + let menuItem = new MyPopupMenuItem(null, _("No recent documents"), "no-recents", {}); + + no_recents_button = menuItem; + } + + new_recents.push(no_recents_button); + } + + let to_remove = []; + + /* Remove no-longer-valid items */ + for (let i = 0; i < this._recentButtons.length; i++) { + let button = this._recentButtons[i]; + + if (button.uri == "no-recents" && have_recents) { + to_remove.push(button); + } else { + if (new_recents.indexOf(button) == -1) { + to_remove.push(button); + } + } + } + + if (to_remove.length > 0) { + for (let i in to_remove) { + to_remove[i].destroy(); + this._recentButtons.splice(this._recentButtons.indexOf(to_remove[i]), 1); + } + } + + to_remove = []; + + /* Now, add new actors, shuffle existing actors */ + + let placeholder = this.recentsBox.get_first_child(); + + for (let i = 0; i < new_recents.length; i++) { + let actor = new_recents[i].actor; + + let parent = actor.get_parent(); + if (parent != null) { + parent.remove_child(actor); + } + + if (actor != placeholder) { + this.recentsBox.insert_child_above(actor, placeholder); + } else { + this.recentsBox.add_child(actor); + } + + placeholder = actor; + } + + this._recentButtons = new_recents; + + this.actor.show(); + } else { + for (let i = 0; i < this._recentButtons.length; i ++) { + this._recentButtons[i].destroy(); + } + + this._recentButtons = []; + this.actor.hide(); + } } }; function main(metadata, orientation, panel_height, instance_id) { let myApplet = new MyApplet(orientation, panel_height, instance_id); - return myApplet; + return myApplet; } diff -Nru cinnamon-3.2.7/files/usr/share/cinnamon/applets/window-list@cinnamon.org/applet.js cinnamon-3.2.8/files/usr/share/cinnamon/applets/window-list@cinnamon.org/applet.js --- cinnamon-3.2.7/files/usr/share/cinnamon/applets/window-list@cinnamon.org/applet.js 2016-12-22 11:45:34.000000000 +0000 +++ cinnamon-3.2.8/files/usr/share/cinnamon/applets/window-list@cinnamon.org/applet.js 2017-01-07 11:20:40.000000000 +0000 @@ -124,7 +124,7 @@ if (this._applet._tooltipShowing) this.show(); else if (!this._showTimer) - this._showTimer = Mainloop.timeout_add(300, Lang.bind(this, this._onTimerComplete)); + this._showTimer = Mainloop.timeout_add(300, Lang.bind(this, this._onShowTimerComplete)); this.mousePosition = event.get_coords(); }, diff -Nru cinnamon-3.2.7/js/misc/docInfo.js cinnamon-3.2.8/js/misc/docInfo.js --- cinnamon-3.2.7/js/misc/docInfo.js 2016-12-22 11:45:34.000000000 +0000 +++ cinnamon-3.2.8/js/misc/docInfo.js 2017-01-07 11:20:40.000000000 +0000 @@ -146,11 +146,17 @@ let docs = this._docSystem.get_all(); this._infosByTimestamp = []; this._infosByUri = {}; - for (let i = 0; i < docs.length && i < MAX_RECENT_FILES; i++) { + + let valid_count = 0; + let i = 0; + + while (i < docs.length && valid_count < MAX_RECENT_FILES) { let recentInfo = docs[i]; let docInfo = new DocInfo(recentInfo); this._infosByTimestamp.push(docInfo); this._infosByUri[docInfo.uri] = docInfo; + valid_count++; + i++; } }, diff -Nru cinnamon-3.2.7/js/ui/tooltips.js cinnamon-3.2.8/js/ui/tooltips.js --- cinnamon-3.2.7/js/ui/tooltips.js 2016-12-22 11:45:34.000000000 +0000 +++ cinnamon-3.2.8/js/ui/tooltips.js 2017-01-07 11:20:40.000000000 +0000 @@ -68,7 +68,7 @@ // An allocation change could mean that the actor has moved, // so hide, but wait until after the allocation cycle. Mainloop.idle_add(Lang.bind(this, function() { - this.hide(); + this._hide(); })); }); @@ -84,24 +84,49 @@ this._showTimer = null; } + if (this._hideTimer) { + Mainloop.source_remove(this._hideTimer); + this._hideTimer = null; + } + if (!this.visible) { - this._showTimer = Mainloop.timeout_add(300, Lang.bind(this, this._onTimerComplete)); + this._showTimer = Mainloop.timeout_add(300, Lang.bind(this, this._onShowTimerComplete)); this.mousePosition = event.get_coords(); + } else { + this._hideTimer = Mainloop.timeout_add(500, Lang.bind(this, this._onHideTimerComplete)); } }, _onEnterEvent: function(actor, event) { if (!this._showTimer) { - this._showTimer = Mainloop.timeout_add(300, Lang.bind(this, this._onTimerComplete)); + this._showTimer = Mainloop.timeout_add(300, Lang.bind(this, this._onShowTimerComplete)); this.mousePosition = event.get_coords(); } }, - _onTimerComplete: function() { + _onShowTimerComplete: function() { this._showTimer = null; - if (!this.preventShow) + if (!this.preventShow) { this.show(); + } + + return false; + }, + + _onHideTimerComplete: function() { + this._hideTimer = null; + + let [abs_x, abs_y, mods] = global.get_pointer(); + let box = this.item.get_allocation_box(); + + let [success, x, y] = this.item.get_parent().transform_stage_point(abs_x, abs_y); + + if ((x < box.x1) || (x > box.x2) || + (y < box.y1) || (y > box.y2)) { + log("__________________________________hide complete!"); + this._hide(); + } return false; }, @@ -111,6 +136,12 @@ Mainloop.source_remove(this._showTimer); this._showTimer = null; } + + if (this._hideTimer) { + Mainloop.source_remove(this._hideTimer); + this._hideTimer = null; + } + this.hide(); }, @@ -124,6 +155,12 @@ Mainloop.source_remove(this._showTimer); this._showTimer = null; } + + if (this._hideTimer) { + Mainloop.source_remove(this._hideTimer); + this._hideTimer = null; + } + this.signals.disconnectAllSignals(); this._destroy(); }