3694 lines
133 KiB
JavaScript
3694 lines
133 KiB
JavaScript
|
/*
|
|||
|
* ArcMenu - A traditional application menu for GNOME 3
|
|||
|
*
|
|||
|
* ArcMenu Lead Developer and Maintainer
|
|||
|
* Andrew Zaech https://gitlab.com/AndrewZaech
|
|||
|
*
|
|||
|
* ArcMenu Founder, Former Maintainer, and Former Graphic Designer
|
|||
|
* LinxGem33 https://gitlab.com/LinxGem33 - (No Longer Active)
|
|||
|
*
|
|||
|
* 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/>.
|
|||
|
*/
|
|||
|
|
|||
|
const ExtensionUtils = imports.misc.extensionUtils;
|
|||
|
const Me = ExtensionUtils.getCurrentExtension();
|
|||
|
const {Atk, Clutter, Gio, GLib, GMenu, GObject, Gtk, Shell, St} = imports.gi;
|
|||
|
const AccountsService = imports.gi.AccountsService;
|
|||
|
const AppFavorites = imports.ui.appFavorites;
|
|||
|
const BoxPointer = imports.ui.boxpointer;
|
|||
|
const Constants = Me.imports.constants;
|
|||
|
const Dash = imports.ui.dash;
|
|||
|
const DND = imports.ui.dnd;
|
|||
|
const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']);
|
|||
|
const Main = imports.ui.main;
|
|||
|
const PopupMenu = imports.ui.popupMenu;
|
|||
|
const Signals = imports.signals;
|
|||
|
const _SystemActions = imports.misc.systemActions;
|
|||
|
const SystemActions = _SystemActions.getDefault();
|
|||
|
const Util = imports.misc.util;
|
|||
|
const Utils = Me.imports.utils;
|
|||
|
const _ = Gettext.gettext;
|
|||
|
const { loadInterfaceXML } = imports.misc.fileUtils;
|
|||
|
|
|||
|
const ClocksIntegrationIface = loadInterfaceXML('org.gnome.Shell.ClocksIntegration');
|
|||
|
const ClocksProxy = Gio.DBusProxy.makeProxyWrapper(ClocksIntegrationIface);
|
|||
|
|
|||
|
Gio._promisify(Gio._LocalFilePrototype, 'query_info_async', 'query_info_finish');
|
|||
|
Gio._promisify(Gio._LocalFilePrototype, 'set_attributes_async', 'set_attributes_finish');
|
|||
|
|
|||
|
const INDICATOR_ICON_SIZE = 18;
|
|||
|
const USER_AVATAR_SIZE = 28;
|
|||
|
|
|||
|
function activatePowerOption(powerType, arcMenu){
|
|||
|
arcMenu.itemActivated(BoxPointer.PopupAnimation.NONE);
|
|||
|
if(powerType === Constants.PowerType.POWER_OFF)
|
|||
|
SystemActions.activatePowerOff();
|
|||
|
else if(powerType === Constants.PowerType.RESTART)
|
|||
|
SystemActions.activateRestart ? SystemActions.activateRestart() : SystemActions.activatePowerOff();
|
|||
|
else if(powerType === Constants.PowerType.LOCK)
|
|||
|
SystemActions.activateLockScreen();
|
|||
|
else if(powerType === Constants.PowerType.LOGOUT)
|
|||
|
SystemActions.activateLogout();
|
|||
|
else if(powerType === Constants.PowerType.SUSPEND)
|
|||
|
SystemActions.activateSuspend();
|
|||
|
else if(powerType === Constants.PowerType.HYBRID_SLEEP)
|
|||
|
Utils.activateHybridSleep();
|
|||
|
else if(powerType === Constants.PowerType.HIBERNATE)
|
|||
|
Utils.activateHibernate();
|
|||
|
}
|
|||
|
|
|||
|
var ApplicationContextItems = GObject.registerClass({
|
|||
|
Signals: {
|
|||
|
'close-context-menu': { },
|
|||
|
},
|
|||
|
|
|||
|
}, class Arc_Menu_ApplicationContextItems extends St.BoxLayout{
|
|||
|
_init(actor, app, menuLayout){
|
|||
|
super._init({
|
|||
|
vertical: true,
|
|||
|
x_expand: true,
|
|||
|
y_expand: true,
|
|||
|
style_class: 'margin-box',
|
|||
|
});
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this._settings = menuLayout._settings;
|
|||
|
this._menuButton = menuLayout.menuButton;
|
|||
|
this._app = app;
|
|||
|
this.sourceActor = actor;
|
|||
|
this.layout = this._settings.get_enum('menu-layout');
|
|||
|
|
|||
|
this.discreteGpuAvailable = false;
|
|||
|
this._switcherooNotifyId = global.connect('notify::switcheroo-control',
|
|||
|
() => this._updateDiscreteGpuAvailable());
|
|||
|
this._updateDiscreteGpuAvailable();
|
|||
|
}
|
|||
|
|
|||
|
set path(path){
|
|||
|
this._path = path;
|
|||
|
}
|
|||
|
|
|||
|
_updateDiscreteGpuAvailable() {
|
|||
|
this._switcherooProxy = global.get_switcheroo_control();
|
|||
|
if (this._switcherooProxy) {
|
|||
|
let prop = this._switcherooProxy.get_cached_property('HasDualGpu');
|
|||
|
this.discreteGpuAvailable = prop?.unpack() ?? false;
|
|||
|
} else {
|
|||
|
this.discreteGpuAvailable = false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
closeMenus(){
|
|||
|
this.close();
|
|||
|
this._menuLayout.arcMenu.toggle();
|
|||
|
}
|
|||
|
|
|||
|
close(){
|
|||
|
this.emit('close-context-menu');
|
|||
|
}
|
|||
|
|
|||
|
rebuildItems(){
|
|||
|
this.destroy_all_children();
|
|||
|
if(this._app instanceof Shell.App){
|
|||
|
this.appInfo = this._app.get_app_info();
|
|||
|
let actions = this.appInfo.list_actions();
|
|||
|
|
|||
|
let windows = this._app.get_windows().filter(
|
|||
|
w => !w.skip_taskbar
|
|||
|
);
|
|||
|
|
|||
|
if (windows.length > 0){
|
|||
|
let item = new PopupMenu.PopupMenuItem(_("Current Windows:"), {
|
|||
|
reactive: false,
|
|||
|
can_focus: false
|
|||
|
});
|
|||
|
item.actor.add_style_class_name('inactive');
|
|||
|
this.add_child(item);
|
|||
|
|
|||
|
windows.forEach(window => {
|
|||
|
let title = window.title ? window.title
|
|||
|
: this._app.get_name();
|
|||
|
let item = this._appendMenuItem(title);
|
|||
|
item.connect('activate', () => {
|
|||
|
this.closeMenus();
|
|||
|
Main.activateWindow(window);
|
|||
|
});
|
|||
|
});
|
|||
|
this._appendSeparator();
|
|||
|
}
|
|||
|
|
|||
|
if (!this._app.is_window_backed()) {
|
|||
|
if (this._app.can_open_new_window() && !actions.includes('new-window')) {
|
|||
|
let newWindowItem = this._appendMenuItem(_("New Window"));
|
|||
|
newWindowItem.connect('activate', () => {
|
|||
|
this.closeMenus();
|
|||
|
this._app.open_new_window(-1);
|
|||
|
});
|
|||
|
}
|
|||
|
if (this.discreteGpuAvailable && this._app.state == Shell.AppState.STOPPED) {
|
|||
|
const appPrefersNonDefaultGPU = this.appInfo.get_boolean('PrefersNonDefaultGPU');
|
|||
|
const gpuPref = appPrefersNonDefaultGPU
|
|||
|
? Shell.AppLaunchGpu.DEFAULT
|
|||
|
: Shell.AppLaunchGpu.DISCRETE;
|
|||
|
|
|||
|
this._onGpuMenuItem = this._appendMenuItem(appPrefersNonDefaultGPU
|
|||
|
? _('Launch using Integrated Graphics Card')
|
|||
|
: _('Launch using Discrete Graphics Card'));
|
|||
|
|
|||
|
this._onGpuMenuItem.connect('activate', () => {
|
|||
|
this.closeMenus();
|
|||
|
this._app.launch(0, -1, gpuPref);
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
for (let i = 0; i < actions.length; i++) {
|
|||
|
let action = actions[i];
|
|||
|
let item;
|
|||
|
if(action === "empty-trash-inactive"){
|
|||
|
item = new PopupMenu.PopupMenuItem(this.appInfo.get_action_name(action), {reactive:false, can_focus:false});
|
|||
|
item.actor.add_style_class_name('inactive');
|
|||
|
this._appendSeparator();
|
|||
|
this.add_child(item);
|
|||
|
}
|
|||
|
else if(action === "empty-trash"){
|
|||
|
this._appendSeparator();
|
|||
|
item = this._appendMenuItem(this.appInfo.get_action_name(action));
|
|||
|
}
|
|||
|
else{
|
|||
|
item = this._appendMenuItem(this.appInfo.get_action_name(action));
|
|||
|
}
|
|||
|
|
|||
|
item.connect('activate', (emitter, event) => {
|
|||
|
this.closeMenus();
|
|||
|
this._app.launch_action(action, event.get_time(), -1);
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
//If Trash Can, we don't want to add the rest of the entries below.
|
|||
|
if(this.appInfo.get_string('Id') === "ArcMenu_Trash")
|
|||
|
return false;
|
|||
|
|
|||
|
let desktopIcons = Main.extensionManager.lookup("desktop-icons@csoriano");
|
|||
|
let desktopIconsNG = Main.extensionManager.lookup("ding@rastersoft.com");
|
|||
|
if((desktopIcons && desktopIcons.stateObj) || (desktopIconsNG && desktopIconsNG.stateObj)){
|
|||
|
this._appendSeparator();
|
|||
|
let fileDestination = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP);
|
|||
|
let src = Gio.File.new_for_path(this.appInfo.get_filename());
|
|||
|
let dst = Gio.File.new_for_path(GLib.build_filenamev([fileDestination, src.get_basename()]));
|
|||
|
let exists = dst.query_exists(null);
|
|||
|
if(exists) {
|
|||
|
let item = this._appendMenuItem(_("Delete Desktop Shortcut"));
|
|||
|
item.connect('activate', () => {
|
|||
|
if(src && dst){
|
|||
|
try {
|
|||
|
dst.delete(null);
|
|||
|
} catch (e) {
|
|||
|
log(`Failed to delete shortcut: ${e.message}`);
|
|||
|
}
|
|||
|
}
|
|||
|
this.close();
|
|||
|
});
|
|||
|
}
|
|||
|
else {
|
|||
|
let item = this._appendMenuItem(_("Create Desktop Shortcut"));
|
|||
|
item.connect('activate', () => {
|
|||
|
if(src && dst){
|
|||
|
try {
|
|||
|
// copy_async() isn't introspectable :-(
|
|||
|
src.copy(dst, Gio.FileCopyFlags.OVERWRITE, null, null);
|
|||
|
this._markTrusted(dst);
|
|||
|
} catch (e) {
|
|||
|
log(`Failed to copy to desktop: ${e.message}`);
|
|||
|
}
|
|||
|
}
|
|||
|
this.close();
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
let canFavorite = global.settings.is_writable('favorite-apps');
|
|||
|
if (canFavorite) {
|
|||
|
this._appendSeparator();
|
|||
|
let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._app.get_id());
|
|||
|
if (isFavorite) {
|
|||
|
let item = this._appendMenuItem(_("Remove from Favorites"));
|
|||
|
item.connect('activate', () => {
|
|||
|
let favs = AppFavorites.getAppFavorites();
|
|||
|
favs.removeFavorite(this._app.get_id());
|
|||
|
this.close();
|
|||
|
});
|
|||
|
} else {
|
|||
|
let item = this._appendMenuItem(_("Add to Favorites"));
|
|||
|
item.connect('activate', () => {
|
|||
|
let favs = AppFavorites.getAppFavorites();
|
|||
|
favs.addFavorite(this._app.get_id());
|
|||
|
this.close();
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
let pinnedApps = this._settings.get_strv('pinned-app-list');
|
|||
|
let pinnedAppID = [];
|
|||
|
|
|||
|
//filter pinnedApps list by every 3rd entry in list. 3rd entry contains an appID or command
|
|||
|
for(let i = 2; i < pinnedApps.length; i += 3){
|
|||
|
pinnedAppID.push(pinnedApps[i]);
|
|||
|
}
|
|||
|
let isAppPinned = pinnedAppID.find((element) => {
|
|||
|
return element == this._app.get_id();
|
|||
|
});
|
|||
|
|
|||
|
//if app is pinned and menulayout has PinnedApps category, show Unpin from ArcMenu entry
|
|||
|
if(isAppPinned && this._menuLayout.hasPinnedApps) {
|
|||
|
let item = this._appendMenuItem(_("Unpin from ArcMenu"));
|
|||
|
item.connect('activate', ()=>{
|
|||
|
this.close();
|
|||
|
for(let i = 0; i < pinnedApps.length; i += 3){
|
|||
|
if(pinnedApps[i + 2] === this._app.get_id()){
|
|||
|
pinnedApps.splice(i, 3);
|
|||
|
this._settings.set_strv('pinned-app-list', pinnedApps);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
else if(this._menuLayout.hasPinnedApps) {
|
|||
|
let item = this._appendMenuItem(_("Pin to ArcMenu"));
|
|||
|
item.connect('activate', ()=>{
|
|||
|
this.close();
|
|||
|
pinnedApps.push(this.appInfo.get_display_name());
|
|||
|
pinnedApps.push('');
|
|||
|
pinnedApps.push(this._app.get_id());
|
|||
|
this._settings.set_strv('pinned-app-list',pinnedApps);
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
if (Shell.AppSystem.get_default().lookup_app('org.gnome.Software.desktop')) {
|
|||
|
this._appendSeparator();
|
|||
|
let item = this._appendMenuItem(_("Show Details"));
|
|||
|
item.connect('activate', () => {
|
|||
|
let id = this._app.get_id();
|
|||
|
let args = GLib.Variant.new('(ss)', [id, '']);
|
|||
|
Gio.DBus.get(Gio.BusType.SESSION, null, (o, res) => {
|
|||
|
let bus = Gio.DBus.get_finish(res);
|
|||
|
bus.call('org.gnome.Software',
|
|||
|
'/org/gnome/Software',
|
|||
|
'org.gtk.Actions', 'Activate',
|
|||
|
GLib.Variant.new('(sava{sv})',
|
|||
|
['details', [args], null]),
|
|||
|
null, 0, -1, null, null);
|
|||
|
this.closeMenus();
|
|||
|
});
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else if(this._path){
|
|||
|
let newWindowItem = this._appendMenuItem(_("Open Folder Location"));
|
|||
|
newWindowItem.connect('activate', () => {
|
|||
|
let file = Gio.File.new_for_path(this._path);
|
|||
|
let context = global.create_app_launch_context(Clutter.get_current_event().get_time(), -1)
|
|||
|
new Promise((resolve, reject) => {
|
|||
|
Gio.AppInfo.launch_default_for_uri_async(file.get_uri(), context, null, (o, res) => {
|
|||
|
try {
|
|||
|
Gio.AppInfo.launch_default_for_uri_finish(res);
|
|||
|
resolve();
|
|||
|
} catch (e) {
|
|||
|
reject(e);
|
|||
|
}
|
|||
|
});
|
|||
|
});
|
|||
|
this.closeMenus();
|
|||
|
});
|
|||
|
}
|
|||
|
else if(this._menuLayout.hasPinnedApps && this.sourceActor instanceof PinnedAppsMenuItem) {
|
|||
|
let item = this._appendMenuItem(_("Unpin from ArcMenu"));
|
|||
|
item.connect('activate', () => {
|
|||
|
this.close();
|
|||
|
let pinnedApps = this._settings.get_strv('pinned-app-list');
|
|||
|
for(let i = 0; i < pinnedApps.length; i += 3){
|
|||
|
if(pinnedApps[i + 2] === this._app){
|
|||
|
pinnedApps.splice(i, 3);
|
|||
|
this._settings.set_strv('pinned-app-list', pinnedApps);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//_markTrusted function borrowed from
|
|||
|
//https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/tree/master/extensions/apps-menu
|
|||
|
async _markTrusted(file) {
|
|||
|
let modeAttr = Gio.FILE_ATTRIBUTE_UNIX_MODE;
|
|||
|
let trustedAttr = 'metadata::trusted';
|
|||
|
let queryFlags = Gio.FileQueryInfoFlags.NONE;
|
|||
|
let ioPriority = GLib.PRIORITY_DEFAULT;
|
|||
|
|
|||
|
try {
|
|||
|
let info = await file.query_info_async(modeAttr, queryFlags, ioPriority, null);
|
|||
|
|
|||
|
let mode = info.get_attribute_uint32(modeAttr) | 0o100;
|
|||
|
info.set_attribute_uint32(modeAttr, mode);
|
|||
|
info.set_attribute_string(trustedAttr, 'yes');
|
|||
|
await file.set_attributes_async(info, queryFlags, ioPriority, null);
|
|||
|
|
|||
|
// Hack: force nautilus to reload file info
|
|||
|
info = new Gio.FileInfo();
|
|||
|
info.set_attribute_uint64(
|
|||
|
Gio.FILE_ATTRIBUTE_TIME_ACCESS, GLib.get_real_time());
|
|||
|
try {
|
|||
|
await file.set_attributes_async(info, queryFlags, ioPriority, null);
|
|||
|
} catch (e) {
|
|||
|
log(`Failed to update access time: ${e.message}`);
|
|||
|
}
|
|||
|
} catch (e) {
|
|||
|
log(`Failed to mark file as trusted: ${e.message}`);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
_appendSeparator() {
|
|||
|
let separator = new ArcMenuSeparator(Constants.SeparatorStyle.SHORT, Constants.SeparatorAlignment.HORIZONTAL);
|
|||
|
this.add_child(separator);
|
|||
|
}
|
|||
|
|
|||
|
_appendMenuItem(labelText) {
|
|||
|
let item = new ArcMenuPopupBaseMenuItem(this._menuLayout);
|
|||
|
this.label = new St.Label({
|
|||
|
text: _(labelText),
|
|||
|
y_expand: true,
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
item.add_child(this.label);
|
|||
|
this.add_child(item);
|
|||
|
return item;
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var ApplicationContextMenu = class Arc_Menu_ApplicationContextMenu extends PopupMenu.PopupMenu {
|
|||
|
constructor(actor, app, menuLayout){
|
|||
|
super(actor, 0.0, St.Side.TOP);
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this._settings = menuLayout._settings;
|
|||
|
this._menuButton = menuLayout.menuButton;
|
|||
|
this._app = app;
|
|||
|
this.layout = this._settings.get_enum('menu-layout');
|
|||
|
this._boxPointer.setSourceAlignment(.20);
|
|||
|
this._boxPointer._border.queue_repaint();
|
|||
|
this.blockSourceEvents = true;
|
|||
|
Main.uiGroup.add_child(this.actor);
|
|||
|
this._menuLayout.contextMenuManager.addMenu(this);
|
|||
|
this.contextMenuItems = new ApplicationContextItems(actor, app, menuLayout);
|
|||
|
this.contextMenuItems.connect('close-context-menu', () => this.toggle());
|
|||
|
this.contextMenuItems._delegate = this.contextMenuItems;
|
|||
|
this.box.add_child(this.contextMenuItems);
|
|||
|
this.sourceActor = actor;
|
|||
|
this.sourceActor.connect("destroy", ()=> {
|
|||
|
if(this.isOpen)
|
|||
|
this.close();
|
|||
|
Main.uiGroup.remove_child(this.actor);
|
|||
|
this.contextMenuItems.destroy();
|
|||
|
this.destroy();
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
centerBoxPointerPosition(){
|
|||
|
this._boxPointer.setSourceAlignment(.50);
|
|||
|
this._arrowAlignment = .5;
|
|||
|
this._boxPointer._border.queue_repaint();
|
|||
|
}
|
|||
|
|
|||
|
rightBoxPointerPosition(){
|
|||
|
this._arrowSide = St.Side.LEFT;
|
|||
|
this._boxPointer._arrowSide = St.Side.LEFT;
|
|||
|
this._boxPointer._userArrowSide = St.Side.LEFT;
|
|||
|
this._boxPointer.setSourceAlignment(.50);
|
|||
|
this._arrowAlignment = .5;
|
|||
|
this._boxPointer._border.queue_repaint();
|
|||
|
}
|
|||
|
|
|||
|
set path(path){
|
|||
|
this.contextMenuItems.path = path;
|
|||
|
}
|
|||
|
|
|||
|
open(animate){
|
|||
|
if(this._menuLayout.searchResults && this.sourceActor !== this._menuLayout.searchResults.getTopResult())
|
|||
|
this._menuLayout.searchResults.getTopResult()?.remove_style_pseudo_class('active');
|
|||
|
if(this._menuButton.tooltipShowingID){
|
|||
|
GLib.source_remove(this._menuButton.tooltipShowingID);
|
|||
|
this._menuButton.tooltipShowingID = null;
|
|||
|
this._menuButton.tooltipShowing = false;
|
|||
|
}
|
|||
|
if(this.sourceActor.tooltip){
|
|||
|
this.sourceActor.tooltip.hide();
|
|||
|
this._menuButton.tooltipShowing = false;
|
|||
|
}
|
|||
|
|
|||
|
super.open(animate);
|
|||
|
}
|
|||
|
|
|||
|
close(animate){
|
|||
|
super.close(animate);
|
|||
|
if(this.sourceActor instanceof ArcMenuButtonItem)
|
|||
|
this.sourceActor.sync_hover();
|
|||
|
else{
|
|||
|
this.sourceActor.active = false;
|
|||
|
}
|
|||
|
|
|||
|
this.sourceActor.sync_hover();
|
|||
|
this.sourceActor.hovered = this.sourceActor.hover;
|
|||
|
}
|
|||
|
|
|||
|
rebuildItems(){
|
|||
|
let customStyle = this._settings.get_boolean('enable-custom-arc-menu');
|
|||
|
if(customStyle){
|
|||
|
this.actor.style_class = 'arc-menu-boxpointer';
|
|||
|
this.actor.add_style_class_name('arc-menu');
|
|||
|
}
|
|||
|
else{
|
|||
|
this.actor.style_class = 'popup-menu-boxpointer';
|
|||
|
this.actor.add_style_class_name('popup-menu');
|
|||
|
}
|
|||
|
|
|||
|
this.contextMenuItems.rebuildItems();
|
|||
|
}
|
|||
|
|
|||
|
_onKeyPress(actor, event) {
|
|||
|
return Clutter.EVENT_PROPAGATE;
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
var ArcMenuPopupBaseMenuItem = GObject.registerClass({
|
|||
|
Properties: {
|
|||
|
'active': GObject.ParamSpec.boolean('active', 'active', 'active',
|
|||
|
GObject.ParamFlags.READWRITE,
|
|||
|
false),
|
|||
|
'hovered': GObject.ParamSpec.boolean('hovered', 'hovered', 'hovered',
|
|||
|
GObject.ParamFlags.READWRITE,
|
|||
|
false),
|
|||
|
'sensitive': GObject.ParamSpec.boolean('sensitive', 'sensitive', 'sensitive',
|
|||
|
GObject.ParamFlags.READWRITE,
|
|||
|
true),
|
|||
|
},
|
|||
|
Signals: {
|
|||
|
'activate': { param_types: [Clutter.Event.$gtype] },
|
|||
|
},
|
|||
|
|
|||
|
}, class Arc_Menu_PopupBaseMenuItem extends St.BoxLayout{
|
|||
|
_init(menuLayout, params){
|
|||
|
params = imports.misc.params.parse(params, {
|
|||
|
reactive: true,
|
|||
|
activate: true,
|
|||
|
hover: true,
|
|||
|
style_class: null,
|
|||
|
can_focus: true,
|
|||
|
});
|
|||
|
super._init({ style_class: 'popup-menu-item arcmenu-menu-item',
|
|||
|
reactive: params.reactive,
|
|||
|
track_hover: params.reactive,
|
|||
|
can_focus: params.can_focus,
|
|||
|
accessible_role: Atk.Role.MENU_ITEM
|
|||
|
});
|
|||
|
this.set_offscreen_redirect(Clutter.OffscreenRedirect.ON_IDLE);
|
|||
|
this.hasContextMenu = false;
|
|||
|
this._delegate = this;
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this.arcMenu = this._menuLayout.arcMenu;
|
|||
|
this.shouldShow = true;
|
|||
|
this._parent = null;
|
|||
|
this._active = false;
|
|||
|
this._activatable = params.reactive && params.activate;
|
|||
|
this._sensitive = true;
|
|||
|
|
|||
|
this._ornamentLabel = new St.Label({ style_class: 'popup-menu-ornament' });
|
|||
|
this.add_child(this._ornamentLabel);
|
|||
|
|
|||
|
this.x_align = Clutter.ActorAlign.FILL;
|
|||
|
this.x_expand = true;
|
|||
|
|
|||
|
if (!this._activatable)
|
|||
|
this.add_style_class_name('popup-inactive-menu-item');
|
|||
|
|
|||
|
if (params.style_class)
|
|||
|
this.add_style_class_name(params.style_class);
|
|||
|
|
|||
|
if (params.reactive && params.hover)
|
|||
|
this.bind_property('hover', this, 'hovered', GObject.BindingFlags.SYNC_CREATE);
|
|||
|
|
|||
|
if(params.hover)
|
|||
|
this.actor.connect('notify::hover', this._onHover.bind(this));
|
|||
|
|
|||
|
this.arcMenuOpenStateChangeID = this.arcMenu.connect('open-state-changed', (menu, open) =>{
|
|||
|
if(!open)
|
|||
|
this.cancelPopupTimeout();
|
|||
|
});
|
|||
|
|
|||
|
let textureCache = St.TextureCache.get_default();
|
|||
|
let iconThemeChangedId = textureCache.connect('icon-theme-changed', this._updateIcon.bind(this));
|
|||
|
this.connect('destroy', () => {
|
|||
|
textureCache.disconnect(iconThemeChangedId);
|
|||
|
this._onDestroy();
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
_updateIcon() {
|
|||
|
if(!this._iconBin || !this.createIcon)
|
|||
|
return;
|
|||
|
|
|||
|
let icon = this.createIcon();
|
|||
|
this._iconBin.set_child(icon);
|
|||
|
}
|
|||
|
|
|||
|
get actor() {
|
|||
|
return this;
|
|||
|
}
|
|||
|
|
|||
|
get active(){
|
|||
|
return this._active;
|
|||
|
}
|
|||
|
|
|||
|
set active(active) {
|
|||
|
if(this.isDestroyed)
|
|||
|
return;
|
|||
|
let activeChanged = active != this.active;
|
|||
|
if(activeChanged){
|
|||
|
this._active = active;
|
|||
|
if(active){
|
|||
|
if(this._menuLayout.activeMenuItem !== this)
|
|||
|
this._menuLayout.activeMenuItem = this;
|
|||
|
this.remove_style_class_name('selected');
|
|||
|
this.add_style_pseudo_class('active');
|
|||
|
if(this.can_focus)
|
|||
|
this.grab_key_focus();
|
|||
|
}
|
|||
|
else{
|
|||
|
this.remove_style_pseudo_class('active');
|
|||
|
this.remove_style_class_name('selected');
|
|||
|
}
|
|||
|
this.notify('active');
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
set hovered(hover) {
|
|||
|
let hoverChanged = hover != this.hovered;
|
|||
|
if(hoverChanged){
|
|||
|
let isActiveStyle = this.get_style_pseudo_class()?.includes('active')
|
|||
|
if(hover && !isActiveStyle){
|
|||
|
this.add_style_class_name('selected');
|
|||
|
}
|
|||
|
else{
|
|||
|
this.remove_style_class_name('selected');
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
setShouldShow(){
|
|||
|
//If a saved shortcut link is a desktop app, check if currently installed.
|
|||
|
//Do NOT display if application not found.
|
|||
|
if(this._command.endsWith(".desktop") && !Shell.AppSystem.get_default().lookup_app(this._command)){
|
|||
|
this.shouldShow = false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
_onHover() {
|
|||
|
if(this.tooltip === undefined && this.actor.hover && this.label){
|
|||
|
let description = this.description;
|
|||
|
if(this._app)
|
|||
|
description = this._app.get_description();
|
|||
|
Utils.createTooltip(this._menuLayout, this, this.label, description, this._displayType ? this._displayType : -1);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
vfunc_button_press_event(){
|
|||
|
let event = Clutter.get_current_event();
|
|||
|
this.pressed = false;
|
|||
|
if(event.get_button() == 1){
|
|||
|
this._menuLayout._blockActivateEvent = false;
|
|||
|
this.pressed = true;
|
|||
|
if(this.hasContextMenu)
|
|||
|
this.contextMenuTimeOut();
|
|||
|
}
|
|||
|
else if(event.get_button() == 3){
|
|||
|
this.pressed = true;
|
|||
|
}
|
|||
|
this.active = true;
|
|||
|
return Clutter.EVENT_PROPAGATE;
|
|||
|
}
|
|||
|
|
|||
|
vfunc_button_release_event(){
|
|||
|
let event = Clutter.get_current_event();
|
|||
|
if(event.get_button() == 1 && !this._menuLayout._blockActivateEvent && this.pressed){
|
|||
|
this.pressed = false;
|
|||
|
this.activate(event);
|
|||
|
if(!(this instanceof CategoryMenuItem) && !(this instanceof ArcMenuButtonItem))
|
|||
|
this.active = false;
|
|||
|
return Clutter.EVENT_STOP;
|
|||
|
}
|
|||
|
if(event.get_button() == 3 && this.pressed){
|
|||
|
this.pressed = false;
|
|||
|
if(this.hasContextMenu)
|
|||
|
this.popupContextMenu();
|
|||
|
else if(!(this instanceof CategoryMenuItem && !(this instanceof ArcMenuButtonItem))){
|
|||
|
this.active = false;
|
|||
|
this.hovered = true;
|
|||
|
}
|
|||
|
return Clutter.EVENT_STOP;
|
|||
|
}
|
|||
|
return Clutter.EVENT_PROPAGATE;
|
|||
|
}
|
|||
|
|
|||
|
vfunc_key_focus_in() {
|
|||
|
super.vfunc_key_focus_in();
|
|||
|
if(!this.actor.hover)
|
|||
|
this._menuLayout._keyFocusIn(this.actor);
|
|||
|
this.active = true;
|
|||
|
}
|
|||
|
|
|||
|
vfunc_key_focus_out() {
|
|||
|
if(this.contextMenu && this.contextMenu.isOpen){
|
|||
|
return;
|
|||
|
}
|
|||
|
super.vfunc_key_focus_out();
|
|||
|
this.active = false;
|
|||
|
}
|
|||
|
|
|||
|
activate(event) {
|
|||
|
this.emit('activate', event);
|
|||
|
}
|
|||
|
|
|||
|
vfunc_key_press_event(keyEvent) {
|
|||
|
if (!this._activatable)
|
|||
|
return super.vfunc_key_press_event(keyEvent);
|
|||
|
|
|||
|
let state = keyEvent.modifier_state;
|
|||
|
|
|||
|
// if user has a modifier down (except capslock and numlock)
|
|||
|
// then don't handle the key press here
|
|||
|
state &= ~Clutter.ModifierType.LOCK_MASK;
|
|||
|
state &= ~Clutter.ModifierType.MOD2_MASK;
|
|||
|
state &= Clutter.ModifierType.MODIFIER_MASK;
|
|||
|
|
|||
|
if (state)
|
|||
|
return Clutter.EVENT_PROPAGATE;
|
|||
|
|
|||
|
let symbol = keyEvent.keyval;
|
|||
|
if ( symbol == Clutter.KEY_Return || symbol == Clutter.KEY_KP_Enter) {
|
|||
|
this.activate(Clutter.get_current_event());
|
|||
|
return Clutter.EVENT_STOP;
|
|||
|
}
|
|||
|
return Clutter.EVENT_PROPAGATE;
|
|||
|
}
|
|||
|
|
|||
|
vfunc_touch_event(event){
|
|||
|
if(event.type == Clutter.EventType.TOUCH_END && !this._menuLayout._blockActivateEvent && this.pressed){
|
|||
|
this.remove_style_pseudo_class('active');
|
|||
|
this.activate(Clutter.get_current_event());
|
|||
|
this.pressed = false;
|
|||
|
return Clutter.EVENT_STOP;
|
|||
|
}
|
|||
|
else if(event.type == Clutter.EventType.TOUCH_BEGIN && !this._menuLayout.contextMenuManager.activeMenu){
|
|||
|
this.pressed = true;
|
|||
|
this._menuLayout._blockActivateEvent = false;
|
|||
|
if(this.hasContextMenu)
|
|||
|
this.contextMenuTimeOut();
|
|||
|
this.add_style_pseudo_class('active');
|
|||
|
}
|
|||
|
else if(event.type == Clutter.EventType.TOUCH_BEGIN && this._menuLayout.contextMenuManager.activeMenu){
|
|||
|
this.pressed = false;
|
|||
|
this._menuLayout._blockActivateEvent = false;
|
|||
|
this._menuLayout.contextMenuManager.activeMenu.toggle();
|
|||
|
}
|
|||
|
return Clutter.EVENT_PROPAGATE;
|
|||
|
}
|
|||
|
|
|||
|
contextMenuTimeOut(){
|
|||
|
this._popupTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 600, () => {
|
|||
|
this.pressed = false;
|
|||
|
this._popupTimeoutId = null;
|
|||
|
if(this.hasContextMenu && this._menuLayout.arcMenu.isOpen && !this._menuLayout._blockActivateEvent) {
|
|||
|
this.popupContextMenu();
|
|||
|
this._menuLayout.contextMenuManager.ignoreRelease();
|
|||
|
}
|
|||
|
return GLib.SOURCE_REMOVE;
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
cancelPopupTimeout(){
|
|||
|
if(this._popupTimeoutId){
|
|||
|
GLib.source_remove(this._popupTimeoutId);
|
|||
|
this._popupTimeoutId = null;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
_onDestroy(){
|
|||
|
this.isDestroyed = true;
|
|||
|
if(this.arcMenuOpenStateChangeID){
|
|||
|
this.arcMenu.disconnect(this.arcMenuOpenStateChangeID);
|
|||
|
this.arcMenuOpenStateChangeID = null;
|
|||
|
}
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var ArcMenuSeparator = GObject.registerClass(
|
|||
|
class Arc_Menu_Separator extends PopupMenu.PopupBaseMenuItem {
|
|||
|
_init(separatorLength, separatorAlignment, text) {
|
|||
|
super._init({
|
|||
|
style_class: 'popup-separator-menu-item',
|
|||
|
reactive: false,
|
|||
|
can_focus: false,
|
|||
|
});
|
|||
|
this._settings = ExtensionUtils.getSettings(Me.metadata['settings-schema']);
|
|||
|
this.remove_child(this._ornamentLabel);
|
|||
|
this.reactive = true;
|
|||
|
this.label = new St.Label({
|
|||
|
text: text || '',
|
|||
|
style: 'font-weight: bold'
|
|||
|
});
|
|||
|
this.add_child(this.label);
|
|||
|
this.label_actor = this.label;
|
|||
|
|
|||
|
this.label.add_style_pseudo_class = () => { return false; };
|
|||
|
|
|||
|
this.label.connect('notify::text',
|
|||
|
this._syncLabelVisibility.bind(this));
|
|||
|
this._syncLabelVisibility();
|
|||
|
|
|||
|
this._separator = new St.Widget({
|
|||
|
style_class: 'popup-separator-menu-item-separator separator-color-style',
|
|||
|
x_expand: true,
|
|||
|
y_expand: true,
|
|||
|
y_align: Clutter.ActorAlign.CENTER,
|
|||
|
});
|
|||
|
this.add_child(this._separator);
|
|||
|
if(separatorAlignment === Constants.SeparatorAlignment.HORIZONTAL){
|
|||
|
this.style = "padding: 0px 5px;"
|
|||
|
if(separatorLength === Constants.SeparatorStyle.SHORT)
|
|||
|
this._separator.style = "margin: 5px 45px;";
|
|||
|
else if(separatorLength === Constants.SeparatorStyle.MEDIUM)
|
|||
|
this._separator.style = "margin: 5px 15px;";
|
|||
|
else if(separatorLength === Constants.SeparatorStyle.LONG)
|
|||
|
this._separator.style = "margin: 0px 5px;";
|
|||
|
else if(separatorLength === Constants.SeparatorStyle.MAX)
|
|||
|
this._separator.style = "margin: 0px; padding: 0px;";
|
|||
|
else if(separatorLength === Constants.SeparatorStyle.HEADER_LABEL){
|
|||
|
this._separator.style = "margin: 0px 20px 0px 10px;";
|
|||
|
this.style = "padding: 5px 15px;"
|
|||
|
}
|
|||
|
}
|
|||
|
else if(separatorAlignment === Constants.SeparatorAlignment.VERTICAL){
|
|||
|
if(separatorLength === Constants.SeparatorStyle.ALWAYS_SHOW){
|
|||
|
this.style = "padding: 8px 4px;"
|
|||
|
}
|
|||
|
else{
|
|||
|
this._syncVisibility();
|
|||
|
this.vertSeparatorChangedID = this._settings.connect('changed::vert-separator', this._syncVisibility.bind(this));
|
|||
|
this.style = "padding: 0px 4px;"
|
|||
|
}
|
|||
|
|
|||
|
this._separator.style = "margin: 0px; width: 1px; height: -1px;";
|
|||
|
this.remove_child(this.label);
|
|||
|
this.x_expand = this._separator.x_expand = true;
|
|||
|
this.x_align = this._separator.x_align = Clutter.ActorAlign.CENTER;
|
|||
|
this.y_expand = this._separator.y_expand = true;
|
|||
|
this.y_align = this._separator.y_align = Clutter.ActorAlign.FILL;
|
|||
|
}
|
|||
|
|
|||
|
this.connect('destroy', () => {
|
|||
|
if(this.vertSeparatorChangedID){
|
|||
|
this._settings.disconnect(this.vertSeparatorChangedID);
|
|||
|
this.vertSeparatorChangedID = null;
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
_syncLabelVisibility() {
|
|||
|
this.label.visible = this.label.text != '';
|
|||
|
}
|
|||
|
|
|||
|
_syncVisibility() {
|
|||
|
this._separator.visible = this._settings.get_boolean('vert-separator');
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var ActivitiesMenuItem = GObject.registerClass(class Arc_Menu_ActivitiesMenuItem extends ArcMenuPopupBaseMenuItem{
|
|||
|
_init(menuLayout) {
|
|||
|
super._init(menuLayout);
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this._settings = this._menuLayout._settings;
|
|||
|
|
|||
|
this._iconBin = new St.Bin();
|
|||
|
this.add_child(this._iconBin);
|
|||
|
|
|||
|
this._updateIcon();
|
|||
|
|
|||
|
this.label = new St.Label({
|
|||
|
text: _("Activities Overview"),
|
|||
|
y_expand: true,
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
this.add_child(this.label);
|
|||
|
}
|
|||
|
|
|||
|
createIcon(){
|
|||
|
const IconSizeEnum = this._settings.get_enum('quicklinks-item-icon-size');
|
|||
|
const LayoutProps = this._menuLayout.layoutProperties;
|
|||
|
let defaultIconSize = LayoutProps.DefaultQuickLinksIconSize;
|
|||
|
let iconSize = Utils.getIconSize(IconSizeEnum, defaultIconSize);
|
|||
|
|
|||
|
return new St.Icon({
|
|||
|
icon_name: 'view-fullscreen-symbolic',
|
|||
|
style_class: 'popup-menu-icon',
|
|||
|
icon_size: iconSize
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
activate(event) {
|
|||
|
this._menuLayout.arcMenu.toggle();
|
|||
|
Main.overview.show();
|
|||
|
super.activate(event);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var Tooltip = class Arc_Menu_Tooltip{
|
|||
|
constructor(menuLayout, sourceActor, title, description) {
|
|||
|
this._menuButton = menuLayout.menuButton;
|
|||
|
this._settings = this._menuButton._settings;
|
|||
|
this.sourceActor = sourceActor;
|
|||
|
if(this.sourceActor.tooltipLocation)
|
|||
|
this.location = this.sourceActor.tooltipLocation;
|
|||
|
else
|
|||
|
this.location = Constants.TooltipLocation.BOTTOM;
|
|||
|
let descriptionLabel;
|
|||
|
this.actor = new St.BoxLayout({
|
|||
|
vertical: true,
|
|||
|
style_class: 'dash-label tooltip-menu-item',
|
|||
|
opacity: 0
|
|||
|
});
|
|||
|
|
|||
|
if(title){
|
|||
|
this.titleLabel = new St.Label({
|
|||
|
text: _(title),
|
|||
|
style: description ? "font-weight: bold;" : null,
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
this.actor.add_child(this.titleLabel);
|
|||
|
}
|
|||
|
|
|||
|
if(description){
|
|||
|
descriptionLabel = new St.Label({
|
|||
|
text: description,
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
this.actor.add_child(descriptionLabel);
|
|||
|
}
|
|||
|
|
|||
|
global.stage.add_child(this.actor);
|
|||
|
|
|||
|
this.actor.connect('destroy',()=>{
|
|||
|
if(this.destroyID){
|
|||
|
this.sourceActor.disconnect(this.destroyID);
|
|||
|
this.destroyID = null;
|
|||
|
}
|
|||
|
if(this.activeID){
|
|||
|
this.sourceActor.disconnect(this.activeID);
|
|||
|
this.activeID = null;
|
|||
|
}
|
|||
|
|
|||
|
if(this.hoverID){
|
|||
|
this.sourceActor.disconnect(this.hoverID);
|
|||
|
this.hoverID = null;
|
|||
|
}
|
|||
|
if(this.toggleID){
|
|||
|
this._settings.disconnect(this.toggleID);
|
|||
|
this.toggleID = null;
|
|||
|
}
|
|||
|
})
|
|||
|
this.activeID = this.sourceActor.connect('notify::active', ()=> this.setActive(this.sourceActor.active));
|
|||
|
this.destroyID = this.sourceActor.connect('destroy',this.destroy.bind(this));
|
|||
|
this.hoverID = this.sourceActor.connect('notify::hover', this._onHover.bind(this));
|
|||
|
this._useTooltips = ! this._settings.get_boolean('disable-tooltips');
|
|||
|
this.toggleID = this._settings.connect('changed::disable-tooltips', this.disableTooltips.bind(this));
|
|||
|
}
|
|||
|
|
|||
|
setActive(active){
|
|||
|
if(!active)
|
|||
|
this.hide();
|
|||
|
}
|
|||
|
|
|||
|
disableTooltips() {
|
|||
|
this._useTooltips = ! this._settings.get_boolean('disable-tooltips');
|
|||
|
}
|
|||
|
|
|||
|
_onHover() {
|
|||
|
if(this._useTooltips){
|
|||
|
if(this.sourceActor.hover){
|
|||
|
if(this._menuButton.tooltipShowing){
|
|||
|
this.show();
|
|||
|
this._menuButton.activeTooltip = this.actor;
|
|||
|
}
|
|||
|
else{
|
|||
|
this._menuButton.tooltipShowingID = GLib.timeout_add(0, 750, () => {
|
|||
|
this.show();
|
|||
|
this._menuButton.tooltipShowing = true;
|
|||
|
this._menuButton.activeTooltip = this.actor;
|
|||
|
this._menuButton.tooltipShowingID = null;
|
|||
|
return GLib.SOURCE_REMOVE;
|
|||
|
});
|
|||
|
}
|
|||
|
if(this._menuButton.tooltipHidingID){
|
|||
|
GLib.source_remove(this._menuButton.tooltipHidingID);
|
|||
|
this._menuButton.tooltipHidingID = null;
|
|||
|
}
|
|||
|
}
|
|||
|
else {
|
|||
|
this.hide();
|
|||
|
if(this._menuButton.tooltipShowingID){
|
|||
|
GLib.source_remove(this._menuButton.tooltipShowingID);
|
|||
|
this._menuButton.tooltipShowingID = null;
|
|||
|
}
|
|||
|
this._menuButton.tooltipHidingID = GLib.timeout_add(0, 750, () => {
|
|||
|
this._menuButton.tooltipShowing = false;
|
|||
|
this._menuButton.activeTooltip = null;
|
|||
|
this._menuButton.tooltipHidingID = null;
|
|||
|
return GLib.SOURCE_REMOVE;
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
show() {
|
|||
|
if(this._useTooltips){
|
|||
|
this.actor.opacity = 0;
|
|||
|
this.actor.show();
|
|||
|
|
|||
|
let [stageX, stageY] = this.sourceActor.get_transformed_position();
|
|||
|
|
|||
|
let itemWidth = this.sourceActor.allocation.x2 - this.sourceActor.allocation.x1;
|
|||
|
let itemHeight = this.sourceActor.allocation.y2 - this.sourceActor.allocation.y1;
|
|||
|
|
|||
|
let labelWidth = this.actor.get_width();
|
|||
|
let labelHeight = this.actor.get_height();
|
|||
|
|
|||
|
let x, y;
|
|||
|
let gap = 5;
|
|||
|
|
|||
|
switch (this.location) {
|
|||
|
case Constants.TooltipLocation.BOTTOM_CENTERED:
|
|||
|
y = stageY + itemHeight + gap;
|
|||
|
x = stageX + Math.floor((itemWidth - labelWidth) / 2);
|
|||
|
break;
|
|||
|
case Constants.TooltipLocation.TOP_CENTERED:
|
|||
|
y = stageY - labelHeight - gap;
|
|||
|
x = stageX + Math.floor((itemWidth - labelWidth) / 2);
|
|||
|
break;
|
|||
|
case Constants.TooltipLocation.BOTTOM:
|
|||
|
y = stageY + itemHeight + gap;
|
|||
|
x = stageX + gap;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
// keep the label inside the screen
|
|||
|
let monitor = Main.layoutManager.findMonitorForActor(this.sourceActor);
|
|||
|
if (x - monitor.x < gap)
|
|||
|
x += monitor.x - x + gap;
|
|||
|
else if (x + labelWidth > monitor.x + monitor.width - gap)
|
|||
|
x -= x + labelWidth - (monitor.x + monitor.width) + gap;
|
|||
|
else if (y - monitor.y < gap)
|
|||
|
y += monitor.y - y + gap;
|
|||
|
else if (y + labelHeight > monitor.y + monitor.height - gap)
|
|||
|
y -= y + labelHeight - (monitor.y + monitor.height) + gap;
|
|||
|
|
|||
|
this.actor.set_position(x, y);
|
|||
|
this.actor.ease({
|
|||
|
opacity: 255,
|
|||
|
duration: Dash.DASH_ITEM_LABEL_SHOW_TIME,
|
|||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
hide() {
|
|||
|
if(this._useTooltips){
|
|||
|
this.actor.ease({
|
|||
|
opacity: 0,
|
|||
|
duration: Dash.DASH_ITEM_LABEL_HIDE_TIME,
|
|||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|||
|
onComplete: () => this.actor.hide()
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
destroy() {
|
|||
|
if (this._menuButton.tooltipShowingID) {
|
|||
|
GLib.source_remove(this._menuButton.tooltipShowingID);
|
|||
|
this._menuButton.tooltipShowingID = null;
|
|||
|
}
|
|||
|
if (this._menuButton.tooltipHidingID) {
|
|||
|
GLib.source_remove(this._menuButton.tooltipHidingID);
|
|||
|
this._menuButton.tooltipHidingID = null;
|
|||
|
}
|
|||
|
if(this.toggleID>0){
|
|||
|
this._settings.disconnect(this.toggleID);
|
|||
|
this.toggleID = 0;
|
|||
|
}
|
|||
|
if(this.hoverID>0){
|
|||
|
this.sourceActor.disconnect(this.hoverID);
|
|||
|
this.hoverID = 0;
|
|||
|
}
|
|||
|
if(this._menuButton.activeTooltip = this.actor)
|
|||
|
this._menuButton.activeTooltip = null;
|
|||
|
|
|||
|
global.stage.remove_child(this.actor);
|
|||
|
this.actor.destroy();
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
var ArcMenuButtonItem = GObject.registerClass(
|
|||
|
class Arc_Menu_ArcMenuButtonItem extends ArcMenuPopupBaseMenuItem {
|
|||
|
_init(menuLayout, tooltipText, iconName, gicon) {
|
|||
|
super._init(menuLayout);
|
|||
|
this.style_class = 'popup-menu-item arc-menu-button';
|
|||
|
this._settings = this._menuLayout._settings;
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this.remove_child(this._ornamentLabel);
|
|||
|
this.x_expand = false;
|
|||
|
this.x_align = Clutter.ActorAlign.CENTER;
|
|||
|
this.y_expand = false;
|
|||
|
this.y_align = Clutter.ActorAlign.CENTER;
|
|||
|
this.iconName = iconName;
|
|||
|
this.gicon = gicon;
|
|||
|
this.toggleMenuOnClick = true;
|
|||
|
|
|||
|
if(tooltipText){
|
|||
|
this.tooltip = new Tooltip(this._menuLayout, this.actor, tooltipText);
|
|||
|
this.tooltip.location = Constants.TooltipLocation.TOP_CENTERED;
|
|||
|
this.tooltip.hide();
|
|||
|
}
|
|||
|
|
|||
|
if(this.iconName !== null){
|
|||
|
this._iconBin = new St.Bin();
|
|||
|
this.add_child(this._iconBin);
|
|||
|
|
|||
|
this._updateIcon();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
createIcon(overrideIconSize){
|
|||
|
const IconSizeEnum = this._settings.get_enum('button-item-icon-size');
|
|||
|
const LayoutProps = this._menuLayout.layoutProperties;
|
|||
|
let defaultIconSize = LayoutProps.DefaultButtonsIconSize;
|
|||
|
let iconSize = Utils.getIconSize(IconSizeEnum, defaultIconSize);
|
|||
|
|
|||
|
return new St.Icon({
|
|||
|
gicon: this.gicon ? this.gicon : Gio.icon_new_for_string(this.iconName),
|
|||
|
icon_size: overrideIconSize ? overrideIconSize : iconSize,
|
|||
|
x_expand: true,
|
|||
|
x_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
setIconSize(size){
|
|||
|
if(!this._iconBin)
|
|||
|
return;
|
|||
|
this._iconBin.set_child(this.createIcon(size));
|
|||
|
}
|
|||
|
|
|||
|
activate(event){
|
|||
|
if(this.toggleMenuOnClick)
|
|||
|
this._menuLayout.arcMenu.toggle();
|
|||
|
super.activate(event);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
// Settings Button
|
|||
|
var SettingsButton = GObject.registerClass(class Arc_Menu_SettingsButton extends ArcMenuButtonItem {
|
|||
|
_init(menuLayout) {
|
|||
|
super._init(menuLayout, _("Settings"), 'emblem-system-symbolic');
|
|||
|
}
|
|||
|
activate(event) {
|
|||
|
super.activate(event);
|
|||
|
Util.spawnCommandLine('gnome-control-center');
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
// Runner Layout Tweaks Button
|
|||
|
var RunnerTweaksButton = GObject.registerClass(class Arc_Menu_RunnerTweaksButton extends ArcMenuButtonItem {
|
|||
|
_init(menuLayout) {
|
|||
|
super._init(menuLayout, _("Configure Runner"), 'emblem-system-symbolic');
|
|||
|
this.tooltip.location = Constants.TooltipLocation.BOTTOM_CENTERED;
|
|||
|
}
|
|||
|
activate(event) {
|
|||
|
super.activate(event);
|
|||
|
this._menuLayout._settings.set_int('prefs-visible-page', Constants.PrefsVisiblePage.RUNNER_TWEAKS);
|
|||
|
Util.spawnCommandLine(Constants.ArcMenuSettingsCommand);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
//'Insider' layout Pinned Apps hamburger button
|
|||
|
var PinnedAppsButton = GObject.registerClass(class Arc_Menu_PinnedAppsButton extends ArcMenuButtonItem {
|
|||
|
_init(menuLayout) {
|
|||
|
super._init(menuLayout, _("Pinned Apps"), Me.path + Constants.HamburgerIcon.PATH);
|
|||
|
this.toggleMenuOnClick = false;
|
|||
|
}
|
|||
|
activate(event) {
|
|||
|
super.activate(event);
|
|||
|
this._menuLayout.togglePinnedAppsMenu();
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
//'Windows' layout extras hamburger button
|
|||
|
var ExtrasButton = GObject.registerClass(class Arc_Menu_ExtrasButton extends ArcMenuButtonItem {
|
|||
|
_init(menuLayout) {
|
|||
|
super._init(menuLayout, _("Extras"), Me.path + Constants.HamburgerIcon.PATH);
|
|||
|
this.toggleMenuOnClick = false;
|
|||
|
}
|
|||
|
activate(event) {
|
|||
|
super.activate(event);
|
|||
|
this._menuLayout.toggleExtrasMenu();
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
//"Leave" Button with popupmenu that shows lock, power off, restart, etc
|
|||
|
var LeaveButton = GObject.registerClass(class Arc_Menu_LeaveButton extends ArcMenuButtonItem {
|
|||
|
_init(menuLayout) {
|
|||
|
super._init(menuLayout, _("Leave"), 'system-shutdown-symbolic');
|
|||
|
this.toggleMenuOnClick = false;
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this.menuButton = menuLayout.menuButton;
|
|||
|
this._settings = menuLayout._settings;
|
|||
|
this._createLeaveMenu();
|
|||
|
}
|
|||
|
|
|||
|
_createLeaveMenu(){
|
|||
|
this.leaveMenu = new PopupMenu.PopupMenu(this, 0.5 , St.Side.BOTTOM);
|
|||
|
this.leaveMenu.blockSourceEvents = true;
|
|||
|
let section = new PopupMenu.PopupMenuSection();
|
|||
|
this.leaveMenu.addMenuItem(section);
|
|||
|
|
|||
|
let box = new St.BoxLayout({
|
|||
|
vertical: true,
|
|||
|
style_class: 'margin-box'
|
|||
|
});
|
|||
|
box._delegate = box;
|
|||
|
|
|||
|
section.actor.add_child(box);
|
|||
|
|
|||
|
box.add_child(this._menuLayout.createLabelRow(_("Session")));
|
|||
|
|
|||
|
this.lockItem = new PowerMenuItem(this._menuLayout, Constants.PowerType.LOCK);
|
|||
|
box.add_child(this.lockItem);
|
|||
|
|
|||
|
let logOutItem = new PowerMenuItem(this._menuLayout, Constants.PowerType.LOGOUT);
|
|||
|
box.add_child(logOutItem);
|
|||
|
|
|||
|
box.add_child(this._menuLayout.createLabelRow(_("System")));
|
|||
|
|
|||
|
Utils.canHybridSleep((canHybridSleep, needsAuth) => {
|
|||
|
if(canHybridSleep){
|
|||
|
let sleepItem = new PowerMenuItem(this._menuLayout, Constants.PowerType.HYBRID_SLEEP);
|
|||
|
box.insert_child_at_index(sleepItem, 4);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
Utils.canHibernate((canHibernate, needsAuth) => {
|
|||
|
if(canHibernate){
|
|||
|
let hibernateItem = new PowerMenuItem(this._menuLayout, Constants.PowerType.HIBERNATE);
|
|||
|
box.insert_child_at_index(hibernateItem, 5);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
let suspendItem = new PowerMenuItem(this._menuLayout, Constants.PowerType.SUSPEND);
|
|||
|
box.add_child(suspendItem);
|
|||
|
|
|||
|
let restartItem = new PowerMenuItem(this._menuLayout, Constants.PowerType.RESTART);
|
|||
|
box.add_child(restartItem);
|
|||
|
|
|||
|
let powerOffItem = new PowerMenuItem(this._menuLayout, Constants.PowerType.POWER_OFF);
|
|||
|
box.add_child(powerOffItem);
|
|||
|
|
|||
|
this._menuLayout.subMenuManager.addMenu(this.leaveMenu);
|
|||
|
this.leaveMenu.actor.hide();
|
|||
|
Main.uiGroup.add_child(this.leaveMenu.actor);
|
|||
|
this.leaveMenu.connect('open-state-changed', (menu, open) => {
|
|||
|
if(open){
|
|||
|
if(this.menuButton.tooltipShowingID){
|
|||
|
GLib.source_remove(this.menuButton.tooltipShowingID);
|
|||
|
this.menuButton.tooltipShowingID = null;
|
|||
|
this.menuButton.tooltipShowing = false;
|
|||
|
}
|
|||
|
if(this.tooltip){
|
|||
|
this.tooltip.hide();
|
|||
|
this.menuButton.tooltipShowing = false;
|
|||
|
}
|
|||
|
}
|
|||
|
else{
|
|||
|
this.active = false;
|
|||
|
this.sync_hover();
|
|||
|
this.hovered = this.hover;
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
_onDestroy(){
|
|||
|
Main.uiGroup.remove_child(this.leaveMenu.actor);
|
|||
|
this.leaveMenu.destroy();
|
|||
|
}
|
|||
|
|
|||
|
activate(event) {
|
|||
|
super.activate(event);
|
|||
|
let customStyle = this._settings.get_boolean('enable-custom-arc-menu');
|
|||
|
this.leaveMenu.actor.style_class = customStyle ? 'arc-menu-boxpointer': 'popup-menu-boxpointer';
|
|||
|
this.leaveMenu.actor.add_style_class_name( customStyle ? 'arc-menu' : 'popup-menu');
|
|||
|
this.leaveMenu.toggle();
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
//'Unity' layout categories hamburger button
|
|||
|
var CategoriesButton = GObject.registerClass(class Arc_Menu_CategoriesButton extends ArcMenuButtonItem {
|
|||
|
_init(menuLayout) {
|
|||
|
super._init(menuLayout, _("Categories"), Me.path + Constants.HamburgerIcon.PATH);
|
|||
|
this.toggleMenuOnClick = false;
|
|||
|
}
|
|||
|
activate(event) {
|
|||
|
super.activate(event);
|
|||
|
this._menuLayout.toggleCategoriesMenu();
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var PowerButton = GObject.registerClass(class Arc_Menu_PowerButton extends ArcMenuButtonItem {
|
|||
|
_init(menuLayout, powerType) {
|
|||
|
super._init(menuLayout, Constants.PowerOptions[powerType].TITLE, Constants.PowerOptions[powerType].IMAGE);
|
|||
|
this.powerType = powerType;
|
|||
|
}
|
|||
|
activate(event) {
|
|||
|
activatePowerOption(this.powerType, this._menuLayout.arcMenu);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var PowerMenuItem = GObject.registerClass(class Arc_Menu_PowerMenuItem extends ArcMenuPopupBaseMenuItem{
|
|||
|
_init(menuLayout, type) {
|
|||
|
super._init(menuLayout);
|
|||
|
this.powerType = type;
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this._layout = this._menuLayout.layout;
|
|||
|
this._settings = this._menuLayout._settings;
|
|||
|
|
|||
|
this._iconBin = new St.Bin();
|
|||
|
this.add_child(this._iconBin);
|
|||
|
|
|||
|
this._updateIcon();
|
|||
|
|
|||
|
this.label = new St.Label({
|
|||
|
text: _(Constants.PowerOptions[this.powerType].TITLE),
|
|||
|
y_expand: false,
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
|
|||
|
this.add_child(this.label);
|
|||
|
}
|
|||
|
|
|||
|
createIcon(){
|
|||
|
const IconSizeEnum = this._settings.get_enum('quicklinks-item-icon-size');
|
|||
|
const LayoutProps = this._menuLayout.layoutProperties;
|
|||
|
let defaultIconSize = LayoutProps.DefaultQuickLinksIconSize;
|
|||
|
let iconSize = Utils.getIconSize(IconSizeEnum, defaultIconSize);
|
|||
|
|
|||
|
return new St.Icon({
|
|||
|
gicon: Gio.icon_new_for_string(Constants.PowerOptions[this.powerType].IMAGE),
|
|||
|
style_class: 'popup-menu-icon',
|
|||
|
icon_size: iconSize,
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
activate(){
|
|||
|
activatePowerOption(this.powerType, this._menuLayout.arcMenu);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var PlasmaMenuItem = GObject.registerClass(class Arc_Menu_PlasmaMenuItem extends ArcMenuPopupBaseMenuItem{
|
|||
|
_init(menuLayout, title, iconPath) {
|
|||
|
super._init(menuLayout);
|
|||
|
this.remove_child(this._ornamentLabel);
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this.tooltipLocation = Constants.TooltipLocation.BOTTOM_CENTERED;
|
|||
|
this._layout = this._menuLayout.layout;
|
|||
|
this._settings = this._menuLayout._settings;
|
|||
|
this.vertical = true;
|
|||
|
this.name = "arc-menu-plasma-button";
|
|||
|
this.iconPath = iconPath;
|
|||
|
|
|||
|
this._iconBin = new St.Bin();
|
|||
|
this.add_child(this._iconBin);
|
|||
|
|
|||
|
this._updateIcon();
|
|||
|
|
|||
|
this.label = new St.Label({
|
|||
|
text: _(title),
|
|||
|
x_expand: true,
|
|||
|
y_expand: false,
|
|||
|
x_align: Clutter.ActorAlign.CENTER,
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
|
|||
|
this.label.x_align = this.label.y_align = Clutter.ActorAlign.CENTER;
|
|||
|
this.label.y_expand = true;
|
|||
|
|
|||
|
this._iconBin.x_align = this._iconBin.y_align = Clutter.ActorAlign.CENTER;
|
|||
|
this._iconBin.y_expand = true;
|
|||
|
|
|||
|
this.label.get_clutter_text().set_line_wrap(true);
|
|||
|
this.add_child(this.label);
|
|||
|
this.actor.connect('notify::hover', this._onHover.bind(this));
|
|||
|
}
|
|||
|
|
|||
|
createIcon(){
|
|||
|
return new St.Icon({
|
|||
|
gicon: Gio.icon_new_for_string(this.iconPath),
|
|||
|
style_class: 'popup-menu-icon',
|
|||
|
icon_size: Constants.MEDIUM_ICON_SIZE
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
_onHover(){
|
|||
|
if(this.tooltip === undefined && this.actor.hover && this.label){
|
|||
|
let description = null;
|
|||
|
Utils.createTooltip(this._menuLayout, this, this.label, description, Constants.DisplayType.LIST);
|
|||
|
}
|
|||
|
let shouldHover = this._settings.get_boolean('plasma-enable-hover');
|
|||
|
if(shouldHover && this.actor.hover && !this.isActive){
|
|||
|
this.activate(Clutter.get_current_event());
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
set active(active) {
|
|||
|
let activeChanged = active != this.active;
|
|||
|
if(activeChanged){
|
|||
|
this._active = active;
|
|||
|
if(active){
|
|||
|
this.add_style_class_name('selected');
|
|||
|
this._menuLayout.activeMenuItem = this;
|
|||
|
if(this.can_focus)
|
|||
|
this.grab_key_focus();
|
|||
|
}
|
|||
|
else{
|
|||
|
this.remove_style_class_name('selected');
|
|||
|
}
|
|||
|
this.notify('active');
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
setActive(active){
|
|||
|
if(active){
|
|||
|
this.isActive = true;
|
|||
|
this.set_style_pseudo_class("active-item");
|
|||
|
}
|
|||
|
else{
|
|||
|
this.isActive = false;
|
|||
|
this.set_style_pseudo_class(null);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
activate(event){
|
|||
|
this._menuLayout.searchBox.clearWithoutSearchChangeEvent();
|
|||
|
this._menuLayout.clearActiveItem();
|
|||
|
this.setActive(true);
|
|||
|
super.activate(event);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var PlasmaCategoryHeader = GObject.registerClass(class Arc_Menu_PlasmaCategoryHeader extends St.BoxLayout{
|
|||
|
_init(menuLayout) {
|
|||
|
super._init({
|
|||
|
style_class: "popup-menu-item",
|
|||
|
style: 'padding: 0px;',
|
|||
|
reactive: true,
|
|||
|
track_hover:true,
|
|||
|
can_focus: true,
|
|||
|
accessible_role: Atk.Role.MENU_ITEM
|
|||
|
});
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this._layout = this._menuLayout.layout;
|
|||
|
this._settings = this._menuLayout._settings;
|
|||
|
|
|||
|
this.backButton = new ArcMenuPopupBaseMenuItem(this._menuLayout);
|
|||
|
this.backButton.x_expand = false;
|
|||
|
this.backButton.x_align = Clutter.ActorAlign.CENTER;
|
|||
|
this.label = new St.Label({
|
|||
|
text: _("Applications"),
|
|||
|
y_expand: false,
|
|||
|
y_align: Clutter.ActorAlign.CENTER,
|
|||
|
style: 'font-weight: bold'
|
|||
|
});
|
|||
|
|
|||
|
this.backButton.add_child(this.label);
|
|||
|
|
|||
|
this.add_child(this.backButton);
|
|||
|
this.backButton.connect("activate", () => this._menuLayout.displayCategories() );
|
|||
|
|
|||
|
this.categoryLabel = new St.Label({
|
|||
|
text: '',
|
|||
|
y_expand: true,
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
|
|||
|
this.add_child(this.categoryLabel);
|
|||
|
}
|
|||
|
|
|||
|
setActiveCategory(categoryText){
|
|||
|
if(categoryText){
|
|||
|
this.categoryLabel.text = _(categoryText);
|
|||
|
this.categoryLabel.show();
|
|||
|
}
|
|||
|
else
|
|||
|
this.categoryLabel.hide();
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var AllAppsButton = GObject.registerClass(class Arc_Menu_AllAppsButton extends ArcMenuButtonItem{
|
|||
|
_init(menuLayout) {
|
|||
|
super._init(menuLayout, null, 'go-next-symbolic');
|
|||
|
this.setIconSize(Constants.EXTRA_SMALL_ICON_SIZE);
|
|||
|
this.toggleMenuOnClick = false;
|
|||
|
this.style = 'min-height: 28px; padding: 0px 8px;'
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this._layout = this._menuLayout.layout;
|
|||
|
this._settings = this._menuLayout._settings;
|
|||
|
this.x_expand = true;
|
|||
|
this.x_align = Clutter.ActorAlign.END;
|
|||
|
this._label = new St.Label({
|
|||
|
text: _("All Apps"),
|
|||
|
x_expand: true,
|
|||
|
x_align: Clutter.ActorAlign.FILL,
|
|||
|
y_expand: false,
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
|
|||
|
this.insert_child_at_index(this._label, 0);
|
|||
|
}
|
|||
|
|
|||
|
activate(event){
|
|||
|
super.activate(event);
|
|||
|
this._menuLayout.displayAllApps();
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var BackButton = GObject.registerClass(class Arc_Menu_BackButton extends ArcMenuButtonItem{
|
|||
|
_init(menuLayout) {
|
|||
|
super._init(menuLayout, null, 'go-previous-symbolic');
|
|||
|
this.setIconSize(Constants.EXTRA_SMALL_ICON_SIZE);
|
|||
|
this.toggleMenuOnClick = false;
|
|||
|
this.style = 'min-height: 28px; padding: 0px 8px;'
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this._layout = this._menuLayout.layout;
|
|||
|
this._settings = this._menuLayout._settings;
|
|||
|
this.x_expand = true;
|
|||
|
this.x_align = Clutter.ActorAlign.END;
|
|||
|
this._label = new St.Label({
|
|||
|
text: _("Back"),
|
|||
|
x_expand: true,
|
|||
|
x_align: Clutter.ActorAlign.FILL,
|
|||
|
y_expand: false,
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
|
|||
|
this.add_child(this._label);
|
|||
|
}
|
|||
|
|
|||
|
activate(event){
|
|||
|
super.activate(event);
|
|||
|
this._menuLayout.setDefaultMenuView();
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
// Menu item to go back to category view
|
|||
|
var BackMenuItem = GObject.registerClass(class Arc_Menu_BackMenuItem extends ArcMenuPopupBaseMenuItem{
|
|||
|
_init(menuLayout) {
|
|||
|
super._init(menuLayout);
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this._layout = this._menuLayout.layout;
|
|||
|
this._settings = this._menuLayout._settings;
|
|||
|
|
|||
|
this._iconBin = new St.Bin({
|
|||
|
x_expand: false,
|
|||
|
x_align: Clutter.ActorAlign.START,
|
|||
|
});
|
|||
|
this.add_child(this._iconBin);
|
|||
|
|
|||
|
this._updateIcon();
|
|||
|
|
|||
|
let backLabel = new St.Label({
|
|||
|
text: _("Back"),
|
|||
|
x_expand: false,
|
|||
|
x_align: Clutter.ActorAlign.START,
|
|||
|
y_expand: true,
|
|||
|
y_align: Clutter.ActorAlign.CENTER,
|
|||
|
});
|
|||
|
this.add_child(backLabel);
|
|||
|
}
|
|||
|
|
|||
|
createIcon(){
|
|||
|
const IconSizeEnum = this._settings.get_enum('misc-item-icon-size');
|
|||
|
let iconSize = Utils.getIconSize(IconSizeEnum, Constants.MISC_ICON_SIZE);
|
|||
|
|
|||
|
return new St.Icon({
|
|||
|
icon_name: 'go-previous-symbolic',
|
|||
|
icon_size: iconSize,
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
activate(event) {
|
|||
|
if(this._layout === Constants.MenuLayout.ARCMENU){
|
|||
|
//If the current page is inside a category and
|
|||
|
//previous page was the categories page,
|
|||
|
//go back to categories page
|
|||
|
if(this._menuLayout.previousCategoryType === Constants.CategoryType.CATEGORIES_LIST && (this._menuLayout.activeCategoryType <= 4 || this._menuLayout.activeCategoryType instanceof GMenu.TreeDirectory))
|
|||
|
this._menuLayout.displayCategories();
|
|||
|
else
|
|||
|
this._menuLayout.setDefaultMenuView();
|
|||
|
}
|
|||
|
else if(this._layout === Constants.MenuLayout.TOGNEE)
|
|||
|
this._menuLayout.setDefaultMenuView();
|
|||
|
super.activate(event);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
// Menu item to view all apps
|
|||
|
var ViewAllPrograms = GObject.registerClass(class Arc_Menu_ViewAllPrograms extends ArcMenuPopupBaseMenuItem{
|
|||
|
_init(menuLayout) {
|
|||
|
super._init(menuLayout);
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this._settings = this._menuLayout._settings;
|
|||
|
|
|||
|
let backLabel = new St.Label({
|
|||
|
text: _("All Applications"),
|
|||
|
x_expand: false,
|
|||
|
x_align: Clutter.ActorAlign.START,
|
|||
|
y_expand: true,
|
|||
|
y_align: Clutter.ActorAlign.CENTER,
|
|||
|
});
|
|||
|
this.add_child(backLabel);
|
|||
|
|
|||
|
this._iconBin = new St.Bin({
|
|||
|
x_expand: false,
|
|||
|
x_align: Clutter.ActorAlign.START,
|
|||
|
});
|
|||
|
this.add_child(this._iconBin);
|
|||
|
this._updateIcon();
|
|||
|
}
|
|||
|
|
|||
|
createIcon(){
|
|||
|
const IconSizeEnum = this._settings.get_enum('misc-item-icon-size');
|
|||
|
let iconSize = Utils.getIconSize(IconSizeEnum, Constants.MISC_ICON_SIZE);
|
|||
|
|
|||
|
return new St.Icon({
|
|||
|
icon_name: 'go-next-symbolic',
|
|||
|
icon_size: iconSize,
|
|||
|
x_align: Clutter.ActorAlign.START
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
activate(event) {
|
|||
|
let defaultMenuView = this._settings.get_enum('default-menu-view');
|
|||
|
if(defaultMenuView === Constants.DefaultMenuView.PINNED_APPS || defaultMenuView === Constants.DefaultMenuView.FREQUENT_APPS)
|
|||
|
this._menuLayout.displayCategories();
|
|||
|
else
|
|||
|
this._menuLayout.displayAllApps();
|
|||
|
super.activate(event);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var ShortcutMenuItem = GObject.registerClass(class Arc_Menu_ShortcutMenuItem extends ArcMenuPopupBaseMenuItem{
|
|||
|
_init(menuLayout, name, icon, command, displayType, isContainedInCategory) {
|
|||
|
super._init(menuLayout);
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this._settings = this._menuLayout._settings;
|
|||
|
if(this._settings.get_enum('shortcut-icon-type') === Constants.CategoryIconType.FULL_COLOR)
|
|||
|
this.add_style_class_name('regular-icons');
|
|||
|
else
|
|||
|
this.add_style_class_name('symbolic-icons');
|
|||
|
this._command = command;
|
|||
|
this._displayType = displayType;
|
|||
|
this.isContainedInCategory = isContainedInCategory;
|
|||
|
this.iconName = icon;
|
|||
|
|
|||
|
//Check for default commands--------
|
|||
|
if(this._command == "ArcMenu_Software"){
|
|||
|
let softwareManager = Utils.findSoftwareManager();
|
|||
|
this._command = softwareManager ? softwareManager : 'ArcMenu_unfound.desktop';
|
|||
|
}
|
|||
|
else if(this._command === "ArcMenu_Trash"){
|
|||
|
this.trash = new Me.imports.placeDisplay.Trash(this);
|
|||
|
this._command = "ArcMenu_Trash";
|
|||
|
this._app = this.trash.getApp();
|
|||
|
}
|
|||
|
if(!this._app)
|
|||
|
this._app = Shell.AppSystem.get_default().lookup_app(this._command);
|
|||
|
|
|||
|
if(this._app && icon === ''){
|
|||
|
let appIcon = this._app.create_icon_texture(Constants.MEDIUM_ICON_SIZE);
|
|||
|
if(appIcon instanceof St.Icon){
|
|||
|
this.iconName = appIcon.gicon.to_string();
|
|||
|
}
|
|||
|
}
|
|||
|
//-------------------------------------
|
|||
|
|
|||
|
this.hasContextMenu = this._app ? true : false;
|
|||
|
|
|||
|
this._iconBin = new St.Bin();
|
|||
|
this.add_child(this._iconBin);
|
|||
|
|
|||
|
this._updateIcon();
|
|||
|
|
|||
|
this.label = new St.Label({
|
|||
|
text: _(name), y_expand: true,
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
|
|||
|
this.layout = this._settings.get_enum('menu-layout');
|
|||
|
if(this.layout === Constants.MenuLayout.PLASMA && this._settings.get_boolean('apps-show-extra-details') && this._app){
|
|||
|
let labelBox = new St.BoxLayout({
|
|||
|
vertical: true
|
|||
|
});
|
|||
|
let descriptionLabel = new St.Label({
|
|||
|
text: this._app.get_description(),
|
|||
|
y_expand: true,
|
|||
|
y_align: Clutter.ActorAlign.CENTER,
|
|||
|
style: "font-weight: lighter;"
|
|||
|
});
|
|||
|
labelBox.add_child(this.label);
|
|||
|
if(this._app.get_description())
|
|||
|
labelBox.add_child(descriptionLabel);
|
|||
|
this.add_child(labelBox);
|
|||
|
}
|
|||
|
else{
|
|||
|
this.add_child(this.label);
|
|||
|
}
|
|||
|
|
|||
|
if(this._displayType === Constants.DisplayType.GRID)
|
|||
|
Utils.convertToGridLayout(this);
|
|||
|
else if(this._displayType === Constants.DisplayType.BUTTON){
|
|||
|
this.style_class = 'popup-menu-item arc-menu-button';
|
|||
|
this.remove_child(this._ornamentLabel);
|
|||
|
this.remove_child(this.label);
|
|||
|
this.x_expand = false;
|
|||
|
this.x_align = Clutter.ActorAlign.CENTER;
|
|||
|
this.y_expand = false;
|
|||
|
this.y_align = Clutter.ActorAlign.CENTER;
|
|||
|
}
|
|||
|
this.setShouldShow();
|
|||
|
}
|
|||
|
|
|||
|
createIcon(){
|
|||
|
let iconSizeEnum;
|
|||
|
if(this.isContainedInCategory)
|
|||
|
iconSizeEnum = this._settings.get_enum('menu-item-icon-size');
|
|||
|
else
|
|||
|
iconSizeEnum = this._settings.get_enum('quicklinks-item-icon-size');
|
|||
|
const LayoutProps = this._menuLayout.layoutProperties;
|
|||
|
let defaultIconSize = this.isContainedInCategory ? LayoutProps.DefaultApplicationIconSize : LayoutProps.DefaultQuickLinksIconSize;
|
|||
|
let iconSize = Utils.getIconSize(iconSizeEnum, defaultIconSize);
|
|||
|
|
|||
|
if(this._displayType === Constants.DisplayType.BUTTON){
|
|||
|
iconSizeEnum = this._settings.get_enum('button-item-icon-size');
|
|||
|
defaultIconSize = LayoutProps.DefaultButtonsIconSize;
|
|||
|
iconSize = Utils.getIconSize(iconSizeEnum, defaultIconSize);
|
|||
|
}
|
|||
|
else if(this._displayType === Constants.DisplayType.GRID){
|
|||
|
iconSizeEnum = this._settings.get_enum('menu-item-grid-icon-size');
|
|||
|
let defaultIconStyle = LayoutProps.DefaultIconGridStyle;
|
|||
|
iconSize = Utils.getGridIconSize(iconSizeEnum, defaultIconStyle);
|
|||
|
}
|
|||
|
|
|||
|
return new St.Icon({
|
|||
|
icon_name: this.iconName,
|
|||
|
gicon: Gio.icon_new_for_string(this.iconName),
|
|||
|
style_class: 'popup-menu-icon',
|
|||
|
icon_size: iconSize
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
popupContextMenu(){
|
|||
|
if(this._app && this.contextMenu == undefined){
|
|||
|
this.contextMenu = new ApplicationContextMenu(this.actor, this._app, this._menuLayout);
|
|||
|
if(this._displayType === Constants.DisplayType.GRID || this.layout === Constants.MenuLayout.UNITY || this.layout === Constants.MenuLayout.AZ)
|
|||
|
this.contextMenu.centerBoxPointerPosition();
|
|||
|
else if(this.layout === Constants.MenuLayout.MINT || this.layout === Constants.MenuLayout.TOGNEE)
|
|||
|
this.contextMenu.rightBoxPointerPosition();
|
|||
|
if(this._path)
|
|||
|
this.contextMenu.path = this._path;
|
|||
|
}
|
|||
|
if(this.contextMenu !== undefined){
|
|||
|
if(this.tooltip !== undefined)
|
|||
|
this.tooltip.hide();
|
|||
|
if(!this.contextMenu.isOpen){
|
|||
|
this.contextMenu.rebuildItems();
|
|||
|
}
|
|||
|
this.contextMenu.toggle();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
activate(event) {
|
|||
|
if(this._command === "ArcMenu_LogOut")
|
|||
|
activatePowerOption(Constants.PowerType.LOGOUT, this._menuLayout.arcMenu);
|
|||
|
else if(this._command === "ArcMenu_Lock")
|
|||
|
activatePowerOption(Constants.PowerType.LOCK, this._menuLayout.arcMenu);
|
|||
|
else if(this._command === "ArcMenu_PowerOff")
|
|||
|
activatePowerOption(Constants.PowerType.POWER_OFF, this._menuLayout.arcMenu);
|
|||
|
else if(this._command === "ArcMenu_Restart")
|
|||
|
activatePowerOption(Constants.PowerType.RESTART, this._menuLayout.arcMenu);
|
|||
|
else if(this._command === "ArcMenu_Suspend")
|
|||
|
activatePowerOption(Constants.PowerType.SUSPEND, this._menuLayout.arcMenu);
|
|||
|
else if(this._command === "ArcMenu_HybridSleep")
|
|||
|
activatePowerOption(Constants.PowerType.HYBRID_SLEEP, this._menuLayout.arcMenu);
|
|||
|
else if(this._command === "ArcMenu_Hibernate")
|
|||
|
activatePowerOption(Constants.PowerType.HIBERNATE, this._menuLayout.arcMenu);
|
|||
|
|
|||
|
else{
|
|||
|
this._menuLayout.arcMenu.toggle();
|
|||
|
if(this._command === "ArcMenu_ActivitiesOverview")
|
|||
|
Main.overview.show();
|
|||
|
else if(this._command === "ArcMenu_RunCommand")
|
|||
|
Main.openRunDialog();
|
|||
|
else if(this._command === "ArcMenu_ShowAllApplications")
|
|||
|
Main.overview._overview._controls._toggleAppsPage();
|
|||
|
else if(this._app)
|
|||
|
this._app.open_new_window(-1);
|
|||
|
else
|
|||
|
Util.spawnCommandLine(this._command);
|
|||
|
}
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
// Menu item which displays the current user
|
|||
|
var UserMenuItem = GObject.registerClass(class Arc_Menu_UserMenuItem extends ArcMenuPopupBaseMenuItem{
|
|||
|
_init(menuLayout, displayType) {
|
|||
|
super._init(menuLayout);
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this._displayType = displayType;
|
|||
|
this._settings = this._menuLayout._settings;
|
|||
|
|
|||
|
if(this._displayType === Constants.DisplayType.BUTTON){
|
|||
|
this.style_class = 'popup-menu-item arc-menu-button';
|
|||
|
const IconSizeEnum = this._settings.get_enum('button-item-icon-size');
|
|||
|
const LayoutProps = this._menuLayout.layoutProperties;
|
|||
|
let defaultIconSize = LayoutProps.DefaultButtonsIconSize;
|
|||
|
this.iconSize = Utils.getIconSize(IconSizeEnum, defaultIconSize);
|
|||
|
|
|||
|
this.remove_child(this._ornamentLabel);
|
|||
|
this.x_expand = false;
|
|||
|
this.x_align = Clutter.ActorAlign.CENTER;
|
|||
|
this.y_expand = false;
|
|||
|
this.y_align = Clutter.ActorAlign.CENTER;
|
|||
|
}
|
|||
|
else{
|
|||
|
const IconSizeEnum = this._settings.get_enum('misc-item-icon-size');
|
|||
|
this.iconSize = Utils.getIconSize(IconSizeEnum, USER_AVATAR_SIZE);
|
|||
|
}
|
|||
|
|
|||
|
this.userMenuIcon = new UserMenuIcon(menuLayout, this.iconSize, false);
|
|||
|
|
|||
|
this.add_child(this.userMenuIcon.actor);
|
|||
|
this.label = this.userMenuIcon.label;
|
|||
|
if(this._displayType !== Constants.DisplayType.BUTTON)
|
|||
|
this.add_child(this.label);
|
|||
|
}
|
|||
|
|
|||
|
activate(event) {
|
|||
|
Util.spawnCommandLine("gnome-control-center user-accounts");
|
|||
|
this._menuLayout.arcMenu.toggle();
|
|||
|
super.activate(event);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var UserMenuIcon = class Arc_Menu_UserMenuIcon{
|
|||
|
constructor(menuLayout, size, hasTooltip) {
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this.iconSize = size;
|
|||
|
|
|||
|
let username = GLib.get_user_name();
|
|||
|
this._user = AccountsService.UserManager.get_default().get_user(username);
|
|||
|
|
|||
|
this.actor = new St.Bin({
|
|||
|
style_class: 'menu-user-avatar user-icon',
|
|||
|
track_hover: true,
|
|||
|
reactive: true,
|
|||
|
x_align: Clutter.ActorAlign.CENTER,
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
|
|||
|
this.label = new St.Label({
|
|||
|
text: GLib.get_real_name(),
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
|
|||
|
this.actor.style = "width: " + this.iconSize + "px; height: " + this.iconSize + "px;";
|
|||
|
|
|||
|
this._userLoadedId = this._user.connect('notify::is-loaded', this._onUserChanged.bind(this));
|
|||
|
this._userChangedId = this._user.connect('changed', this._onUserChanged.bind(this));
|
|||
|
this.actor.connect('destroy', this._onDestroy.bind(this));
|
|||
|
if(hasTooltip)
|
|||
|
this.actor.connect('notify::hover',this._onHover.bind(this));
|
|||
|
|
|||
|
this._onUserChanged();
|
|||
|
}
|
|||
|
|
|||
|
_onHover() {
|
|||
|
if(this.tooltip === undefined && this.actor.hover){
|
|||
|
this.tooltip = new Tooltip(this._menuLayout, this.actor, GLib.get_real_name());
|
|||
|
this.tooltip.location = Constants.TooltipLocation.BOTTOM_CENTERED;
|
|||
|
this.tooltip._onHover();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
_onUserChanged() {
|
|||
|
if (this._user.is_loaded) {
|
|||
|
this.label.set_text(this._user.get_real_name());
|
|||
|
if(this.tooltip)
|
|||
|
this.tooltip.titleLabel.text = this._user.get_real_name();
|
|||
|
|
|||
|
let iconFile = this._user.get_icon_file();
|
|||
|
if (iconFile && !GLib.file_test(iconFile ,GLib.FileTest.EXISTS))
|
|||
|
iconFile = null;
|
|||
|
|
|||
|
if (iconFile) {
|
|||
|
this.actor.child = null;
|
|||
|
this.actor.add_style_class_name('user-avatar');
|
|||
|
this.actor.style = 'background-image: url("%s");'.format(iconFile) + "width: " + this.iconSize + "px; height: " + this.iconSize + "px;";
|
|||
|
}
|
|||
|
else {
|
|||
|
this.actor.style = "width: " + this.iconSize + "px; height: " + this.iconSize + "px;";
|
|||
|
this.actor.child = new St.Icon({
|
|||
|
icon_name: 'avatar-default-symbolic',
|
|||
|
icon_size: this.iconSize,
|
|||
|
style: "padding: 5px; width: " + this.iconSize + "px; height: " + this.iconSize + "px;"
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
_onDestroy() {
|
|||
|
if (this._userLoadedId) {
|
|||
|
this._user.disconnect(this._userLoadedId);
|
|||
|
this._userLoadedId = null;
|
|||
|
}
|
|||
|
if (this._userChangedId) {
|
|||
|
this._user.disconnect(this._userChangedId);
|
|||
|
this._userChangedId = null;
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
// Menu pinned apps item class
|
|||
|
var PinnedAppsMenuItem = GObject.registerClass({
|
|||
|
Signals: { 'saveSettings': {}, },
|
|||
|
}, class Arc_Menu_PinnedAppsMenuItem extends ArcMenuPopupBaseMenuItem{
|
|||
|
_init(menuLayout, name, icon, command, displayType, isContainedInCategory) {
|
|||
|
super._init(menuLayout);
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this._menuButton = menuLayout.menuButton;
|
|||
|
this._settings = this._menuLayout._settings;
|
|||
|
this._command = command;
|
|||
|
this._iconString = this._iconPath = icon;
|
|||
|
this._name = name;
|
|||
|
this._displayType = displayType;
|
|||
|
this._app = Shell.AppSystem.get_default().lookup_app(this._command);
|
|||
|
this.hasContextMenu = true;
|
|||
|
this.gridLocation = [-1, -1];
|
|||
|
this.isContainedInCategory = isContainedInCategory;
|
|||
|
|
|||
|
//Modifiy the Default Pinned Apps---------------------
|
|||
|
if(this._name == "ArcMenu Settings"){
|
|||
|
this._name = _("ArcMenu Settings");
|
|||
|
}
|
|||
|
else if(this._name == "Terminal"){
|
|||
|
this._name = _("Terminal");
|
|||
|
}
|
|||
|
else if(this._name == "Files"){
|
|||
|
this._name = _("Files");
|
|||
|
}
|
|||
|
if(this._iconPath === "ArcMenu_ArcMenuIcon" || this._iconPath === Me.path + '/media/icons/arc-menu-symbolic.svg'){
|
|||
|
this._iconString = this._iconPath = Me.path + '/media/icons/menu_icons/arc-menu-symbolic.svg';
|
|||
|
}
|
|||
|
//-------------------------------------------------------
|
|||
|
|
|||
|
if(this._app && this._iconPath === ''){
|
|||
|
let appIcon = this._app.create_icon_texture(Constants.MEDIUM_ICON_SIZE);
|
|||
|
if(appIcon instanceof St.Icon){
|
|||
|
this._iconString = appIcon.gicon ? appIcon.gicon.to_string() : appIcon.fallback_icon_name;
|
|||
|
if(!this._iconString)
|
|||
|
this._iconString = "";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
this._iconBin = new St.Bin();
|
|||
|
this.add_child(this._iconBin);
|
|||
|
|
|||
|
this._updateIcon();
|
|||
|
|
|||
|
this.label = new St.Label({
|
|||
|
text: _(this._name),
|
|||
|
y_expand: true,
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
|
|||
|
if(this._displayType === Constants.DisplayType.LIST && this._settings.get_boolean('apps-show-extra-details') && this._app){
|
|||
|
let labelBox = new St.BoxLayout({
|
|||
|
vertical: true
|
|||
|
});
|
|||
|
let descriptionLabel = new St.Label({
|
|||
|
text: this._app.get_description(),
|
|||
|
y_expand: true,
|
|||
|
y_align: Clutter.ActorAlign.CENTER,
|
|||
|
style: "font-weight: lighter;"
|
|||
|
});
|
|||
|
labelBox.add_child(this.label);
|
|||
|
if(this._app.get_description())
|
|||
|
labelBox.add_child(descriptionLabel);
|
|||
|
this.add_child(labelBox);
|
|||
|
}
|
|||
|
else{
|
|||
|
this.add_child(this.label);
|
|||
|
}
|
|||
|
|
|||
|
this._draggable = DND.makeDraggable(this.actor);
|
|||
|
this._draggable._animateDragEnd = (eventTime) => {
|
|||
|
this._draggable._animationInProgress = true;
|
|||
|
this._draggable._onAnimationComplete(this._draggable._dragActor, eventTime);
|
|||
|
};
|
|||
|
this.isDraggableApp = true;
|
|||
|
this._draggable.connect('drag-begin', this._onDragBegin.bind(this));
|
|||
|
this._draggable.connect('drag-end', this._onDragEnd.bind(this));
|
|||
|
|
|||
|
if(this._displayType === Constants.DisplayType.GRID)
|
|||
|
Utils.convertToGridLayout(this);
|
|||
|
|
|||
|
this.setShouldShow();
|
|||
|
}
|
|||
|
|
|||
|
createIcon(){
|
|||
|
let iconSize;
|
|||
|
if(this._displayType === Constants.DisplayType.GRID){
|
|||
|
const IconSizeEnum = this._settings.get_enum('menu-item-grid-icon-size');
|
|||
|
const LayoutProps = this._menuLayout.layoutProperties;
|
|||
|
let defaultIconStyle = LayoutProps.DefaultIconGridStyle;
|
|||
|
iconSize = Utils.getGridIconSize(IconSizeEnum, defaultIconStyle);
|
|||
|
}
|
|||
|
else if(this._displayType === Constants.DisplayType.LIST){
|
|||
|
const IconSizeEnum = this._settings.get_enum('menu-item-icon-size');
|
|||
|
const LayoutProps = this._menuLayout.layoutProperties;
|
|||
|
let defaultIconSize = this.isContainedInCategory ? LayoutProps.DefaultApplicationIconSize : LayoutProps.DefaultPinnedIconSize;
|
|||
|
iconSize = Utils.getIconSize(IconSizeEnum, defaultIconSize);
|
|||
|
}
|
|||
|
|
|||
|
return new St.Icon({
|
|||
|
gicon: Gio.icon_new_for_string(this._iconString),
|
|||
|
icon_size: iconSize
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
popupContextMenu(){
|
|||
|
if(this.contextMenu == undefined){
|
|||
|
let app = this._app ? this._app : this._command;
|
|||
|
this.contextMenu = new ApplicationContextMenu(this.actor, app, this._menuLayout);
|
|||
|
if(this._displayType === Constants.DisplayType.GRID)
|
|||
|
this.contextMenu.centerBoxPointerPosition();
|
|||
|
}
|
|||
|
if(this.tooltip !== undefined)
|
|||
|
this.tooltip.hide();
|
|||
|
if(!this.contextMenu.isOpen)
|
|||
|
this.contextMenu.rebuildItems();
|
|||
|
this.contextMenu.toggle();
|
|||
|
}
|
|||
|
|
|||
|
_onDragBegin() {
|
|||
|
this.isDragging = true;
|
|||
|
if(this._menuButton.tooltipShowingID){
|
|||
|
GLib.source_remove(this._menuButton.tooltipShowingID);
|
|||
|
this._menuButton.tooltipShowingID = null;
|
|||
|
this._menuButton.tooltipShowing = false;
|
|||
|
}
|
|||
|
if(this.tooltip){
|
|||
|
this.tooltip.hide();
|
|||
|
this._menuButton.tooltipShowing = false;
|
|||
|
}
|
|||
|
|
|||
|
if(this.contextMenu && this.contextMenu.isOpen)
|
|||
|
this.contextMenu.toggle();
|
|||
|
|
|||
|
this.cancelPopupTimeout();
|
|||
|
|
|||
|
this._dragMonitor = {
|
|||
|
dragMotion: this._onDragMotion.bind(this)
|
|||
|
};
|
|||
|
DND.addDragMonitor(this._dragMonitor);
|
|||
|
this._parentBox = this.actor.get_parent();
|
|||
|
let p = this._parentBox.get_transformed_position();
|
|||
|
this.posX = p[0];
|
|||
|
this.posY = p[1];
|
|||
|
|
|||
|
this.actor.opacity = 55;
|
|||
|
this.get_allocation_box();
|
|||
|
this.rowHeight = this.height;
|
|||
|
this.rowWidth = this.width;
|
|||
|
}
|
|||
|
|
|||
|
_onDragMotion(dragEvent) {
|
|||
|
let layoutManager = this._parentBox.layout_manager;
|
|||
|
if(layoutManager instanceof Clutter.GridLayout){
|
|||
|
this.xIndex = Math.floor((this._draggable._dragX - this.posX) / (this.rowWidth + layoutManager.column_spacing));
|
|||
|
this.yIndex = Math.floor((this._draggable._dragY - this.posY) / (this.rowHeight + layoutManager.row_spacing));
|
|||
|
|
|||
|
if(this.xIndex === this.gridLocation[0] && this.yIndex === this.gridLocation[1]){
|
|||
|
return DND.DragMotionResult.CONTINUE;
|
|||
|
}
|
|||
|
else{
|
|||
|
this.gridLocation = [this.xIndex, this.yIndex];
|
|||
|
}
|
|||
|
|
|||
|
this._parentBox.remove_child(this);
|
|||
|
let children = this._parentBox.get_children();
|
|||
|
let childrenCount = children.length;
|
|||
|
let columns = layoutManager.gridColumns;
|
|||
|
let rows = Math.floor(childrenCount / columns);
|
|||
|
if(this.yIndex >= rows)
|
|||
|
this.yIndex = rows;
|
|||
|
if(this.yIndex < 0)
|
|||
|
this.yIndex = 0;
|
|||
|
if(this.xIndex >= columns - 1)
|
|||
|
this.xIndex = columns - 1;
|
|||
|
if(this.xIndex < 0)
|
|||
|
this.xIndex = 0;
|
|||
|
|
|||
|
if(((this.xIndex + 1) + (this.yIndex * columns)) > childrenCount)
|
|||
|
this.xIndex = Math.floor(childrenCount % columns);
|
|||
|
|
|||
|
this._parentBox.remove_all_children();
|
|||
|
|
|||
|
let x = 0, y = 0;
|
|||
|
for(let i = 0; i < children.length; i++){
|
|||
|
if(this.xIndex === x && this.yIndex === y)
|
|||
|
[x, y] = this.gridLayoutIter(x, y, columns);
|
|||
|
layoutManager.attach(children[i], x, y, 1, 1);
|
|||
|
[x, y] = this.gridLayoutIter(x, y, columns);
|
|||
|
}
|
|||
|
layoutManager.attach(this, this.xIndex, this.yIndex, 1, 1);
|
|||
|
}
|
|||
|
return DND.DragMotionResult.CONTINUE;
|
|||
|
}
|
|||
|
|
|||
|
_onDragEnd() {
|
|||
|
if (this._dragMonitor) {
|
|||
|
DND.removeDragMonitor(this._dragMonitor);
|
|||
|
this._dragMonitor = null;
|
|||
|
}
|
|||
|
this.actor.opacity = 255;
|
|||
|
let layoutManager = this._parentBox.layout_manager;
|
|||
|
if(layoutManager instanceof Clutter.GridLayout){
|
|||
|
let x = 0, y = 0;
|
|||
|
let columns = layoutManager.gridColumns;
|
|||
|
let orderedList = [];
|
|||
|
let children = this._parentBox.get_children();
|
|||
|
for(let i = 0; i < children.length; i++){
|
|||
|
orderedList.push(this._parentBox.layout_manager.get_child_at(x, y));
|
|||
|
[x, y] = this.gridLayoutIter(x, y, columns);
|
|||
|
}
|
|||
|
this._menuLayout.pinnedAppsArray = orderedList;
|
|||
|
}
|
|||
|
this.emit('saveSettings');
|
|||
|
}
|
|||
|
|
|||
|
getDragActor() {
|
|||
|
let customStyle = this._settings.get_boolean('enable-custom-arc-menu');
|
|||
|
let icon = new St.Icon({
|
|||
|
gicon: Gio.icon_new_for_string(this._iconString),
|
|||
|
style_class: 'popup-menu-icon',
|
|||
|
icon_size: this._iconBin.get_child().icon_size
|
|||
|
});
|
|||
|
let iconColor = this._settings.get_string('menu-foreground-color');
|
|||
|
if(customStyle)
|
|||
|
icon.style = `color: ${iconColor};`;
|
|||
|
return icon;
|
|||
|
}
|
|||
|
|
|||
|
getDragActorSource() {
|
|||
|
return this.actor;
|
|||
|
}
|
|||
|
|
|||
|
gridLayoutIter(x, y, columns){
|
|||
|
x++;
|
|||
|
if(x === columns){
|
|||
|
y++;
|
|||
|
x = 0;
|
|||
|
}
|
|||
|
return [x, y];
|
|||
|
}
|
|||
|
|
|||
|
activate(event) {
|
|||
|
if(this._app)
|
|||
|
this._app.open_new_window(-1);
|
|||
|
else if(this._command === "ArcMenu_ShowAllApplications")
|
|||
|
Main.overview._overview._controls._toggleAppsPage();
|
|||
|
else
|
|||
|
Util.spawnCommandLine(this._command);
|
|||
|
|
|||
|
this._menuLayout.arcMenu.toggle();
|
|||
|
super.activate(event);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var ApplicationMenuItem = GObject.registerClass(class Arc_Menu_ApplicationMenuItem extends ArcMenuPopupBaseMenuItem{
|
|||
|
_init(menuLayout, app, displayType, metaInfo, isContainedInCategory) {
|
|||
|
super._init(menuLayout);
|
|||
|
this._app = app;
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this.metaInfo = metaInfo;
|
|||
|
this._settings = this._menuLayout._settings;
|
|||
|
this.searchType = this._menuLayout.layoutProperties.SearchDisplayType;
|
|||
|
this._displayType = displayType;
|
|||
|
this.hasContextMenu = true;
|
|||
|
this.isSearchResult = this.metaInfo ? true : false;
|
|||
|
this.isContainedInCategory = isContainedInCategory;
|
|||
|
|
|||
|
if(this._app){
|
|||
|
let disableRecentAppsIndicator = this._settings.get_boolean("disable-recently-installed-apps")
|
|||
|
if(!disableRecentAppsIndicator){
|
|||
|
let recentApps = this._settings.get_strv('recently-installed-apps');
|
|||
|
this.isRecentlyInstalled = recentApps.some((appIter) => appIter === this._app.get_id());
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
this._iconBin = new St.Bin({
|
|||
|
x_align: Clutter.ActorAlign.CENTER,
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
this.add_child(this._iconBin);
|
|||
|
|
|||
|
this._updateIcon();
|
|||
|
|
|||
|
this.label = new St.Label({
|
|||
|
text: this._app ? this._app.get_name() : this.metaInfo['name'],
|
|||
|
y_expand: true,
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
this.description = this._app ? this._app.get_description() : this.metaInfo['description'];
|
|||
|
|
|||
|
let searchResultsDescriptionsSetting = this._settings.get_boolean("show-search-result-details");
|
|||
|
let appsShowDescriptionsSetting = this._settings.get_boolean("apps-show-extra-details");
|
|||
|
this.searchResultsDescriptions = searchResultsDescriptionsSetting && this.isSearchResult;
|
|||
|
this.appsShowDescriptions = appsShowDescriptionsSetting && !this.isSearchResult;
|
|||
|
|
|||
|
if(this.description && (this.searchResultsDescriptions || this.appsShowDescriptions) && this._displayType === Constants.DisplayType.LIST){
|
|||
|
let labelBox = new St.BoxLayout({
|
|||
|
vertical: true
|
|||
|
});
|
|||
|
let descriptionText = this.description.split('\n')[0];
|
|||
|
this.descriptionLabel = new St.Label({
|
|||
|
text: descriptionText,
|
|||
|
y_expand: true,
|
|||
|
y_align: Clutter.ActorAlign.CENTER,
|
|||
|
style: "font-weight: lighter;"
|
|||
|
});
|
|||
|
labelBox.add_child(this.label);
|
|||
|
labelBox.add_child(this.descriptionLabel);
|
|||
|
this.add_child(labelBox);
|
|||
|
}
|
|||
|
else{
|
|||
|
this.add_child(this.label);
|
|||
|
}
|
|||
|
|
|||
|
this.label_actor = this.label;
|
|||
|
|
|||
|
if(this.isRecentlyInstalled){
|
|||
|
this._indicator = new St.Label({
|
|||
|
text: _('New'),
|
|||
|
style_class: "arc-menu-menu-item-text-indicator",
|
|||
|
style: "border-radius: 15px; margin: 0px; padding: 0px 10px;",
|
|||
|
x_expand: true,
|
|||
|
x_align: Clutter.ActorAlign.END,
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
this.add_child(this._indicator);
|
|||
|
}
|
|||
|
if(this._displayType === Constants.DisplayType.GRID)
|
|||
|
Utils.convertToGridLayout(this);
|
|||
|
|
|||
|
this.hoverID = this.connect("notify::hover", () => this.removeIndicator());
|
|||
|
this.keyFocusInID = this.connect("key-focus-in", () => this.removeIndicator());
|
|||
|
}
|
|||
|
|
|||
|
createIcon(){
|
|||
|
let iconSize;
|
|||
|
if(this._displayType === Constants.DisplayType.GRID){
|
|||
|
this._iconBin.x_align = Clutter.ActorAlign.CENTER;
|
|||
|
|
|||
|
const IconSizeEnum = this._settings.get_enum('menu-item-grid-icon-size');
|
|||
|
const LayoutProps = this._menuLayout.layoutProperties;
|
|||
|
let defaultIconStyle = LayoutProps.DefaultIconGridStyle;
|
|||
|
iconSize = Utils.getGridIconSize(IconSizeEnum, defaultIconStyle);
|
|||
|
}
|
|||
|
else if(this._displayType === Constants.DisplayType.LIST){
|
|||
|
const IconSizeEnum = this._settings.get_enum('menu-item-icon-size');
|
|||
|
const LayoutProps = this._menuLayout.layoutProperties;
|
|||
|
let defaultIconSize = this.isContainedInCategory || this.isSearchResult ? LayoutProps.DefaultApplicationIconSize : LayoutProps.DefaultPinnedIconSize;
|
|||
|
iconSize = Utils.getIconSize(IconSizeEnum, defaultIconSize);
|
|||
|
}
|
|||
|
return this.metaInfo ? this.metaInfo['createIcon'](iconSize) : this._app.create_icon_texture(iconSize);
|
|||
|
}
|
|||
|
|
|||
|
removeIndicator(){
|
|||
|
if(this.isRecentlyInstalled){
|
|||
|
this.isRecentlyInstalled = false;
|
|||
|
let recentApps = this._settings.get_strv('recently-installed-apps');
|
|||
|
let index = recentApps.indexOf(this._app.get_id());
|
|||
|
if(index > -1){
|
|||
|
recentApps.splice(index, 1);
|
|||
|
}
|
|||
|
this._settings.set_strv('recently-installed-apps', recentApps);
|
|||
|
|
|||
|
this._indicator.hide();
|
|||
|
this._menuLayout.setRecentlyInstalledIndicator();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
popupContextMenu(){
|
|||
|
this.removeIndicator();
|
|||
|
if(this.tooltip)
|
|||
|
this.tooltip.hide();
|
|||
|
if(!this._app && !this._path)
|
|||
|
return;
|
|||
|
|
|||
|
if(this.contextMenu === undefined){
|
|||
|
this.contextMenu = new ApplicationContextMenu(this.actor, this._app, this._menuLayout);
|
|||
|
if(this._path)
|
|||
|
this.contextMenu.path = this._path;
|
|||
|
if(this._displayType === Constants.DisplayType.GRID)
|
|||
|
this.contextMenu.centerBoxPointerPosition();
|
|||
|
}
|
|||
|
if(!this.contextMenu.isOpen)
|
|||
|
this.contextMenu.rebuildItems();
|
|||
|
this.contextMenu.toggle();
|
|||
|
}
|
|||
|
|
|||
|
activateSearchResult(provider, metaInfo, terms, event){
|
|||
|
this._menuLayout.arcMenu.toggle();
|
|||
|
if(provider.activateResult){
|
|||
|
provider.activateResult(metaInfo.id, terms);
|
|||
|
if (metaInfo.clipboardText)
|
|||
|
St.Clipboard.get_default().set_text(St.ClipboardType.CLIPBOARD, metaInfo.clipboardText);
|
|||
|
}
|
|||
|
else{
|
|||
|
if (metaInfo.id.endsWith('.desktop')) {
|
|||
|
let app = Shell.AppSystem.get_default().lookup_app(metaInfo.id);
|
|||
|
if (app.can_open_new_window())
|
|||
|
app.open_new_window(-1);
|
|||
|
else
|
|||
|
app.activate();
|
|||
|
}
|
|||
|
else{
|
|||
|
this._menuLayout.arcMenu.itemActivated(BoxPointer.PopupAnimation.NONE);
|
|||
|
SystemActions.activateAction(metaInfo.id);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
activate(event) {
|
|||
|
this.removeIndicator();
|
|||
|
|
|||
|
if(this.metaInfo){
|
|||
|
this.activateSearchResult(this.provider, this.metaInfo, this.resultsView.terms, event);
|
|||
|
return Clutter.EVENT_STOP;
|
|||
|
}
|
|||
|
else
|
|||
|
this._app.open_new_window(-1);
|
|||
|
|
|||
|
this._menuLayout.arcMenu.toggle();
|
|||
|
super.activate(event);
|
|||
|
}
|
|||
|
|
|||
|
_onDestroy(){
|
|||
|
if(this.hoverID){
|
|||
|
this.disconnect(this.hoverID);
|
|||
|
this.hoverID = null;
|
|||
|
}
|
|||
|
if(this.keyFocusInID){
|
|||
|
this.disconnect(this.keyFocusInID);
|
|||
|
this.keyFocusInID = null;
|
|||
|
}
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
// Menu Category item class
|
|||
|
var CategoryMenuItem = GObject.registerClass(class Arc_Menu_CategoryMenuItem extends ArcMenuPopupBaseMenuItem{
|
|||
|
_init(menuLayout, category, displayType) {
|
|||
|
super._init(menuLayout);
|
|||
|
this.appList = [];
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this._settings = this._menuLayout._settings;
|
|||
|
this._layout = this._settings.get_enum('menu-layout');
|
|||
|
this._category = category;
|
|||
|
this._name = "";
|
|||
|
this._horizontalFlip = this._settings.get_boolean('enable-horizontal-flip');
|
|||
|
this._displayType = displayType;
|
|||
|
this.layoutProps = this._menuLayout.layoutProperties;
|
|||
|
|
|||
|
this._iconBin = new St.Bin();
|
|||
|
this.add_child(this._iconBin);
|
|||
|
|
|||
|
this._updateIcon();
|
|||
|
|
|||
|
this.label = new St.Label({
|
|||
|
text: this._name,
|
|||
|
y_expand: true,
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
this.add_child(this.label);
|
|||
|
|
|||
|
if(this.isRecentlyInstalled)
|
|||
|
this.setRecentlyInstalledIndicator(true);
|
|||
|
|
|||
|
if(this._displayType === Constants.DisplayType.BUTTON){
|
|||
|
this.style_class = 'popup-menu-item arc-menu-button';
|
|||
|
this.remove_child(this._ornamentLabel);
|
|||
|
this.x_expand = false;
|
|||
|
this.x_align = Clutter.ActorAlign.CENTER;
|
|||
|
this.y_expand = false;
|
|||
|
this.y_align = Clutter.ActorAlign.CENTER;
|
|||
|
this.remove_child(this.label);
|
|||
|
}
|
|||
|
|
|||
|
this.label_actor = this.label;
|
|||
|
this._menuLayout._oldX = -1;
|
|||
|
this._menuLayout._oldY = -1;
|
|||
|
this.connect('motion-event', this._onMotionEvent.bind(this));
|
|||
|
this.connect('enter-event', this._onEnterEvent.bind(this));
|
|||
|
this.connect('leave-event', this._onLeaveEvent.bind(this));
|
|||
|
}
|
|||
|
|
|||
|
createIcon(){
|
|||
|
const IconSizeEnum = this._settings.get_enum('menu-item-icon-size');
|
|||
|
|
|||
|
let defaultIconSize = this.layoutProps.DefaultCategoryIconSize;
|
|||
|
let iconSize = Utils.getIconSize(IconSizeEnum, defaultIconSize);
|
|||
|
|
|||
|
if(this._displayType === Constants.DisplayType.BUTTON){
|
|||
|
const IconSizeEnum = this._settings.get_enum('button-item-icon-size');
|
|||
|
let defaultIconSize = this.layoutProps.DefaultButtonsIconSize;
|
|||
|
iconSize = Utils.getIconSize(IconSizeEnum, defaultIconSize);
|
|||
|
}
|
|||
|
|
|||
|
let icon = new St.Icon({
|
|||
|
style_class: 'popup-menu-icon',
|
|||
|
icon_size: iconSize
|
|||
|
});
|
|||
|
|
|||
|
let categoryIconType = this._settings.get_enum('category-icon-type');
|
|||
|
let [name, gicon, iconName, fallbackIconName] = Utils.getCategoryDetails(this._category, categoryIconType);
|
|||
|
this._name = _(name);
|
|||
|
if(gicon)
|
|||
|
icon.gicon = gicon;
|
|||
|
else if(iconName)
|
|||
|
icon.icon_name = iconName;
|
|||
|
else
|
|||
|
icon.fallback_icon_name = fallbackIconName;
|
|||
|
|
|||
|
return icon;
|
|||
|
}
|
|||
|
|
|||
|
setRecentlyInstalledIndicator(shouldShow){
|
|||
|
if(this._displayType === Constants.DisplayType.BUTTON)
|
|||
|
return;
|
|||
|
this.isRecentlyInstalled = shouldShow;
|
|||
|
if(shouldShow){
|
|||
|
this._indicator = new St.Icon({
|
|||
|
icon_name: 'message-indicator-symbolic',
|
|||
|
style_class: 'arc-menu-menu-item-indicator',
|
|||
|
icon_size: INDICATOR_ICON_SIZE,
|
|||
|
x_expand: true,
|
|||
|
y_expand: false,
|
|||
|
x_align: Clutter.ActorAlign.END,
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
this.add_child(this._indicator);
|
|||
|
}
|
|||
|
else if(this._indicator && this.contains(this._indicator))
|
|||
|
this.remove_child(this._indicator);
|
|||
|
}
|
|||
|
|
|||
|
displayAppList(){
|
|||
|
this._menuLayout.searchBox?.clearWithoutSearchChangeEvent();
|
|||
|
this._menuLayout.activeCategory = this._name;
|
|||
|
Utils.activateCategory(this._category, this._menuLayout, this, null);
|
|||
|
}
|
|||
|
|
|||
|
activate(event) {
|
|||
|
this.displayAppList();
|
|||
|
if(this.layoutProps.SupportsCategoryOnHover)
|
|||
|
this._menuLayout.setActiveCategory(this, true);
|
|||
|
super.activate(event);
|
|||
|
}
|
|||
|
|
|||
|
_onEnterEvent(actor, event) {
|
|||
|
if(this._menuLayout.navigatingCategoryLeaveEventID){
|
|||
|
GLib.source_remove(this._menuLayout.navigatingCategoryLeaveEventID);
|
|||
|
this._menuLayout.navigatingCategoryLeaveEventID = null;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
_onLeaveEvent(actor, event) {
|
|||
|
if(!this._menuLayout.navigatingCategoryLeaveEventID){
|
|||
|
this._menuLayout.navigatingCategoryLeaveEventID = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 200, () => {
|
|||
|
this._menuLayout.navigatingCategory = null;
|
|||
|
this._menuLayout.navigatingCategoryLeaveEventID = null;
|
|||
|
return GLib.SOURCE_REMOVE;
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
_onMotionEvent(actor, event) {
|
|||
|
if(this.layoutProps.SupportsCategoryOnHover && this._settings.get_boolean('activate-on-hover')){
|
|||
|
if (!this._menuLayout.navigatingCategory) {
|
|||
|
this._menuLayout.navigatingCategory = this;
|
|||
|
}
|
|||
|
|
|||
|
if (this._isInTriangle(event.get_coords())){
|
|||
|
if(this._menuLayout.activeCategoryType !== this._category && this._menuLayout.navigatingCategory === this)
|
|||
|
this.activate(Clutter.get_current_event());
|
|||
|
return true;
|
|||
|
}
|
|||
|
this._menuLayout.navigatingCategory = this;
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
_isInTriangle([x, y]){
|
|||
|
let [posX, posY] = this._menuLayout.navigatingCategory.get_transformed_position();
|
|||
|
|
|||
|
//the mouse is still in the active category
|
|||
|
if (this._menuLayout.navigatingCategory === this){
|
|||
|
this._menuLayout._oldX = x;
|
|||
|
this._menuLayout._oldY = y;
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
if(!this._menuLayout.navigatingCategory)
|
|||
|
return false;
|
|||
|
|
|||
|
let width = this._menuLayout.navigatingCategory.width;
|
|||
|
let height = this._menuLayout.navigatingCategory.height;
|
|||
|
|
|||
|
let maxX = this._horizontalFlip ? posX : posX + width;
|
|||
|
let maxY = posY + height;
|
|||
|
|
|||
|
let distance = Math.abs(maxX - this._menuLayout._oldX);
|
|||
|
let point1 = [this._menuLayout._oldX, this._menuLayout._oldY]
|
|||
|
let point2 = [maxX, posY - distance];
|
|||
|
let point3 = [maxX, maxY + distance];
|
|||
|
|
|||
|
let area = Utils.areaOfTriangle(point1, point2, point3);
|
|||
|
let a1 = Utils.areaOfTriangle([x, y], point2, point3);
|
|||
|
let a2 = Utils.areaOfTriangle(point1, [x, y], point3);
|
|||
|
let a3 = Utils.areaOfTriangle(point1, point2, [x, y]);
|
|||
|
return area === a1 + a2 + a3;
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var SimpleMenuItem = GObject.registerClass(class Arc_Menu_SimpleMenuItem extends CategoryMenuItem{
|
|||
|
_init(menuLayout, category) {
|
|||
|
super._init(menuLayout, category);
|
|||
|
this.subMenu = new PopupMenu.PopupMenu(this.actor,.5,St.Side.LEFT);
|
|||
|
this.subMenu.connect("open-state-changed", (menu, open) => {
|
|||
|
if(!open){
|
|||
|
let appsScrollBoxAdj = this.applicationsScrollBox.get_vscroll_bar().get_adjustment();
|
|||
|
appsScrollBoxAdj.set_value(0);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
Main.uiGroup.add_child(this.subMenu.actor);
|
|||
|
this.section = new PopupMenu.PopupMenuSection();
|
|||
|
this.subMenu.addMenuItem(this.section);
|
|||
|
|
|||
|
this.applicationsScrollBox = this._menuLayout._createScrollBox({
|
|||
|
x_expand: true,
|
|||
|
y_expand: true,
|
|||
|
y_align: Clutter.ActorAlign.START,
|
|||
|
style_class: 'left-panel ' + (this._menuLayout.disableFadeEffect ? '' : 'small-vfade'),
|
|||
|
overlay_scrollbars: true
|
|||
|
});
|
|||
|
this._menuLayout.subMenuManager.addMenu(this.subMenu);
|
|||
|
this.applicationsBox = new St.BoxLayout({
|
|||
|
vertical: true,
|
|||
|
style_class: 'margin-box'
|
|||
|
});
|
|||
|
this.applicationsScrollBox.style = 'max-height: 25em;';
|
|||
|
this.applicationsBox._delegate = this.applicationsBox;
|
|||
|
this.applicationsScrollBox.add_actor(this.applicationsBox);
|
|||
|
this.section.actor.add_child(this.applicationsScrollBox);
|
|||
|
|
|||
|
if(this.subMenu._keyPressId)
|
|||
|
this.actor.disconnect(this.subMenu._keyPressId);
|
|||
|
this.applicationsScrollBox.connect("key-press-event",(actor, event)=>{
|
|||
|
let symbol = event.get_key_symbol();
|
|||
|
switch (symbol) {
|
|||
|
case Clutter.KEY_Right:
|
|||
|
case Clutter.KEY_Left:
|
|||
|
this.subMenu.toggle();
|
|||
|
this.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
|||
|
case Clutter.KEY_Escape:
|
|||
|
if(this.subMenu.isOpen){
|
|||
|
this.subMenu.toggle();
|
|||
|
this.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
|||
|
}
|
|||
|
return Clutter.EVENT_STOP;
|
|||
|
default:
|
|||
|
return Clutter.EVENT_PROPAGATE;
|
|||
|
}
|
|||
|
});
|
|||
|
this.actor.connect("key-press-event",(actor, event)=>{
|
|||
|
let symbol = event.get_key_symbol();
|
|||
|
switch (symbol) {
|
|||
|
case Clutter.KEY_Escape:
|
|||
|
if(this.subMenu.isOpen){
|
|||
|
this.subMenu.toggle();
|
|||
|
}
|
|||
|
return Clutter.EVENT_STOP;
|
|||
|
case Clutter.KEY_Left:
|
|||
|
case Clutter.KEY_Right:
|
|||
|
case Clutter.KEY_Return:
|
|||
|
if(!this.subMenu.isOpen){
|
|||
|
let navigateFocus = true;
|
|||
|
this.activate(event, navigateFocus);
|
|||
|
this.subMenu.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
|||
|
return Clutter.EVENT_STOP;
|
|||
|
}
|
|||
|
else{
|
|||
|
return Clutter.EVENT_PROPAGATE;
|
|||
|
}
|
|||
|
default:
|
|||
|
return Clutter.EVENT_PROPAGATE;
|
|||
|
}
|
|||
|
});
|
|||
|
this.updateStyle();
|
|||
|
}
|
|||
|
|
|||
|
updateStyle(){
|
|||
|
let customStyle = this._settings.get_boolean('enable-custom-arc-menu');
|
|||
|
|
|||
|
this.subMenu.actor.hide();
|
|||
|
if(customStyle){
|
|||
|
this.subMenu.actor.style_class = 'arc-menu-boxpointer';
|
|||
|
this.subMenu.actor.add_style_class_name('arc-menu');
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
this.subMenu.actor.style_class = 'popup-menu-boxpointer';
|
|||
|
this.subMenu.actor.add_style_class_name('popup-menu');
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
displayAppList(){
|
|||
|
this._menuLayout.activeCategory = this._name;
|
|||
|
}
|
|||
|
|
|||
|
activate(event, navigateFocus = true) {
|
|||
|
this._menuLayout.activeCategory = this._name;
|
|||
|
Utils.activateCategory(this._category, this._menuLayout, this, true);
|
|||
|
this.subMenu.toggle();
|
|||
|
if(navigateFocus)
|
|||
|
this.subMenu.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
|||
|
this._menuLayout.setActiveCategory(this, true);
|
|||
|
}
|
|||
|
|
|||
|
_onMotionEvent(actor, event) {
|
|||
|
if (!this._menuLayout.navigatingCategory) {
|
|||
|
this._menuLayout.navigatingCategory = this;
|
|||
|
}
|
|||
|
|
|||
|
if (this._isInTriangle(event.get_coords())){
|
|||
|
if(this._menuLayout.activeCategory !== this._name && this._menuLayout.navigatingCategory === this){
|
|||
|
let navigateFocus = false;
|
|||
|
this.activate(event, navigateFocus);
|
|||
|
}
|
|||
|
return true;
|
|||
|
}
|
|||
|
this._menuLayout.navigatingCategory = this;
|
|||
|
return true;
|
|||
|
}
|
|||
|
});
|
|||
|
// SubMenu Category item class
|
|||
|
var CategorySubMenuItem = GObject.registerClass(class Arc_Menu_CategorySubMenuItem extends PopupMenu.PopupSubMenuMenuItem{
|
|||
|
_init(menuLayout, category) {
|
|||
|
super._init('', true);
|
|||
|
this.add_style_class_name("arcmenu-menu-item");
|
|||
|
this.add_style_class_name('margin-box');
|
|||
|
this._category = category;
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this._settings = this._menuLayout._settings;
|
|||
|
this._name = "";
|
|||
|
this.isSimpleMenuItem = false;
|
|||
|
this._active = false;
|
|||
|
this.applicationsMap = new Map();
|
|||
|
this.appList = [];
|
|||
|
|
|||
|
let categoryIconType = this._settings.get_enum('category-icon-type');
|
|||
|
let [name, gicon, iconName, fallbackIconName] = Utils.getCategoryDetails(this._category, categoryIconType);
|
|||
|
this._name = _(name);
|
|||
|
if(gicon)
|
|||
|
this.icon.gicon = gicon;
|
|||
|
else if(iconName)
|
|||
|
this.icon.icon_name = iconName;
|
|||
|
else
|
|||
|
this.icon.fallback_icon_name = fallbackIconName;
|
|||
|
|
|||
|
this.label.text = this._name;
|
|||
|
|
|||
|
const IconSizeEnum = this._settings.get_enum('menu-item-icon-size');
|
|||
|
const LayoutProps = this._menuLayout.layoutProperties;
|
|||
|
let defaultIconSize = LayoutProps.DefaultCategoryIconSize;
|
|||
|
let iconSize = Utils.getIconSize(IconSizeEnum, defaultIconSize);
|
|||
|
this.icon.icon_size = iconSize;
|
|||
|
|
|||
|
let panAction = new Clutter.PanAction({ interpolate: false });
|
|||
|
panAction.connect('pan', (action) => {
|
|||
|
this._menuLayout._blockActivateEvent = true;
|
|||
|
this._menuLayout.onPan(action, this.menu.actor);
|
|||
|
});
|
|||
|
panAction.connect('gesture-cancel',(action) => this._menuLayout.onPanEnd(action, this.menu.actor));
|
|||
|
panAction.connect('gesture-end', (action) => this._menuLayout.onPanEnd(action, this.menu.actor));
|
|||
|
this.menu.actor.add_action(panAction);
|
|||
|
|
|||
|
this.menu.actor.style = 'max-height: 250px;';
|
|||
|
this.menu.actor.overlay_scrollbars = true;
|
|||
|
this.menu.actor.style_class = 'popup-sub-menu ' + (this._menuLayout.disableFadeEffect ? '' : 'small-vfade');
|
|||
|
this.menu._needsScrollbar = () => this._needsScrollbar();
|
|||
|
|
|||
|
this.menu.connect('open-state-changed', () => {
|
|||
|
if(!this.menu.isOpen){
|
|||
|
let scrollbar= this.menu.actor.get_vscroll_bar().get_adjustment();
|
|||
|
scrollbar.set_value(0);
|
|||
|
}
|
|||
|
});
|
|||
|
this.menu.box.style_class = 'margin-box';
|
|||
|
}
|
|||
|
|
|||
|
setRecentlyInstalledIndicator(shouldShow){
|
|||
|
this.isRecentlyInstalled = shouldShow;
|
|||
|
if(shouldShow){
|
|||
|
this._indicator = new St.Icon({
|
|||
|
icon_name: 'message-indicator-symbolic',
|
|||
|
style_class: 'arc-menu-menu-item-indicator',
|
|||
|
icon_size: INDICATOR_ICON_SIZE,
|
|||
|
x_expand: true,
|
|||
|
y_expand: false,
|
|||
|
x_align: Clutter.ActorAlign.END,
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
this.actor.add_child(this._indicator);
|
|||
|
}
|
|||
|
else if(this._indicator && this.actor.contains(this._indicator))
|
|||
|
this.actor.remove_child(this._indicator);
|
|||
|
}
|
|||
|
|
|||
|
_needsScrollbar() {
|
|||
|
let topMenu = this.menu;
|
|||
|
let [, topNaturalHeight] = topMenu.actor.get_preferred_height(-1);
|
|||
|
let topThemeNode = topMenu.actor.get_theme_node();
|
|||
|
|
|||
|
let topMaxHeight = topThemeNode.get_max_height();
|
|||
|
let needsScrollbar = topMaxHeight >= 0 && topNaturalHeight >= topMaxHeight;
|
|||
|
if(needsScrollbar)
|
|||
|
this.menu.actor.style = 'min-height:150px; max-height: 250px;';
|
|||
|
else
|
|||
|
this.menu.actor.style = 'max-height: 250px;';
|
|||
|
|
|||
|
this.menu.actor.vscrollbar_policy = St.PolicyType.AUTOMATIC;
|
|||
|
|
|||
|
if (needsScrollbar)
|
|||
|
this.menu.actor.add_style_pseudo_class('scrolled');
|
|||
|
else
|
|||
|
this.menu.actor.remove_style_pseudo_class('scrolled');
|
|||
|
|
|||
|
return needsScrollbar;
|
|||
|
}
|
|||
|
|
|||
|
loadMenu(){
|
|||
|
let children = this.menu.box.get_children();
|
|||
|
for (let i = 0; i < children.length; i++) {
|
|||
|
let item = children[i];
|
|||
|
this.menu.box.remove_child(item);
|
|||
|
}
|
|||
|
let appList = [];
|
|||
|
this.applicationsMap.forEach((value,key,map) => {
|
|||
|
appList.push(key);
|
|||
|
});
|
|||
|
appList.sort((a, b) => {
|
|||
|
return a.get_name().toLowerCase() > b.get_name().toLowerCase();
|
|||
|
});
|
|||
|
for (let i = 0; i < appList.length; i++) {
|
|||
|
let app = appList[i];
|
|||
|
let item = this.applicationsMap.get(app);
|
|||
|
if(item.actor.get_parent()){
|
|||
|
item.actor.get_parent().remove_child(item.actor);
|
|||
|
}
|
|||
|
if (!item.actor.get_parent()) {
|
|||
|
this.menu.box.add_child(item.actor);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
_setOpenState(open){
|
|||
|
if(this.isSimpleMenuItem && open){
|
|||
|
this._menuLayout.activeCategory = this._name;
|
|||
|
Utils.activateCategory(this._category, this._menuLayout, this, true);
|
|||
|
}
|
|||
|
else if(open)
|
|||
|
this.loadMenu();
|
|||
|
|
|||
|
this.setSubmenuShown(open);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
// Place Info class
|
|||
|
var PlaceInfo = class Arc_Menu_PlaceInfo {
|
|||
|
constructor(file, name, icon) {
|
|||
|
this.file = file;
|
|||
|
this.name = name ? name : this._getFileName();
|
|||
|
this.icon = icon ? icon : null;
|
|||
|
this.gicon = icon ? null : this.getIcon();
|
|||
|
}
|
|||
|
|
|||
|
launch(timestamp) {
|
|||
|
let context = global.create_app_launch_context(timestamp, -1);
|
|||
|
new Promise((resolve, reject) => {
|
|||
|
Gio.AppInfo.launch_default_for_uri_async(this.file.get_uri(), context, null, (o, res) => {
|
|||
|
try {
|
|||
|
Gio.AppInfo.launch_default_for_uri_finish(res);
|
|||
|
resolve();
|
|||
|
} catch (e) {
|
|||
|
Main.notifyError(_('Failed to open “%s”').format(this._getFileName()), e.message);
|
|||
|
reject(e);
|
|||
|
}
|
|||
|
});
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
getIcon() {
|
|||
|
try {
|
|||
|
let info = this.file.query_info('standard::symbolic-icon', 0, null);
|
|||
|
return info.get_symbolic_icon();
|
|||
|
|
|||
|
} catch (e) {
|
|||
|
if (e instanceof Gio.IOErrorEnum) {
|
|||
|
if (!this.file.is_native()) {
|
|||
|
return new Gio.ThemedIcon({ name: 'folder-remote-symbolic' });
|
|||
|
} else {
|
|||
|
return new Gio.ThemedIcon({ name: 'folder-symbolic' });
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
_getFileName() {
|
|||
|
try {
|
|||
|
let info = this.file.query_info('standard::display-name', 0, null);
|
|||
|
return info.get_display_name();
|
|||
|
} catch (e) {
|
|||
|
if (e instanceof Gio.IOErrorEnum) {
|
|||
|
return this.file.get_basename();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
destroy(){
|
|||
|
}
|
|||
|
};
|
|||
|
Signals.addSignalMethods(PlaceInfo.prototype);
|
|||
|
|
|||
|
// Menu Place Shortcut item class
|
|||
|
var PlaceMenuItem = GObject.registerClass(class Arc_Menu_PlaceMenuItem extends ArcMenuPopupBaseMenuItem{
|
|||
|
_init(menuLayout, info, displayType, isContainedInCategory) {
|
|||
|
super._init(menuLayout);
|
|||
|
this._menuLayout = menuLayout;
|
|||
|
this._displayType = displayType;
|
|||
|
this._info = info;
|
|||
|
this._settings = menuLayout._settings;
|
|||
|
this.isContainedInCategory = isContainedInCategory;
|
|||
|
this.hasContextMenu = true;
|
|||
|
|
|||
|
this.label = new St.Label({
|
|||
|
text: _(info.name),
|
|||
|
y_expand: true,
|
|||
|
y_align: Clutter.ActorAlign.CENTER
|
|||
|
});
|
|||
|
|
|||
|
this._iconBin = new St.Bin();
|
|||
|
this.add_child(this._iconBin);
|
|||
|
|
|||
|
this._updateIcon();
|
|||
|
|
|||
|
if(this._displayType === Constants.DisplayType.BUTTON){
|
|||
|
this.style_class = 'popup-menu-item arc-menu-button';
|
|||
|
this.remove_child(this._ornamentLabel);
|
|||
|
this.x_expand = this.y_expand = false;
|
|||
|
this.x_align = this.y_align = Clutter.ActorAlign.CENTER;
|
|||
|
}
|
|||
|
else{
|
|||
|
this.add_child(this.label);
|
|||
|
}
|
|||
|
|
|||
|
this._changedId = this._info.connect('changed', this._propertiesChanged.bind(this));
|
|||
|
}
|
|||
|
|
|||
|
createIcon(){
|
|||
|
let iconSizeEnum;
|
|||
|
if(this.isContainedInCategory)
|
|||
|
iconSizeEnum = this._settings.get_enum('menu-item-icon-size');
|
|||
|
else
|
|||
|
iconSizeEnum = this._settings.get_enum('quicklinks-item-icon-size');
|
|||
|
|
|||
|
const LayoutProps = this._menuLayout.layoutProperties;
|
|||
|
let defaultIconSize = this.isContainedInCategory ? LayoutProps.DefaultApplicationIconSize : LayoutProps.DefaultQuickLinksIconSize;
|
|||
|
let iconSize = Utils.getIconSize(iconSizeEnum, defaultIconSize);
|
|||
|
|
|||
|
if(this._displayType === Constants.DisplayType.BUTTON){
|
|||
|
let defaultIconSize = LayoutProps.DefaultButtonsIconSize;
|
|||
|
const IconSizeEnum = this._settings.get_enum('button-item-icon-size');
|
|||
|
iconSize = Utils.getIconSize(IconSizeEnum, defaultIconSize);
|
|||
|
}
|
|||
|
|
|||
|
return new St.Icon({
|
|||
|
gicon: this._info.gicon ? this._info.gicon : this._info.icon,
|
|||
|
icon_size: iconSize,
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
_onDestroy() {
|
|||
|
if (this._changedId) {
|
|||
|
this._info.disconnect(this._changedId);
|
|||
|
this._changedId = null;
|
|||
|
}
|
|||
|
if(this._info)
|
|||
|
this._info.destroy();
|
|||
|
super._onDestroy();
|
|||
|
}
|
|||
|
|
|||
|
popupContextMenu(){
|
|||
|
if(this.tooltip)
|
|||
|
this.tooltip.hide();
|
|||
|
if(!this._app && !this._path)
|
|||
|
return;
|
|||
|
|
|||
|
if(this.contextMenu === undefined){
|
|||
|
this.contextMenu = new ApplicationContextMenu(this.actor, this._app, this._menuLayout);
|
|||
|
if(this._path)
|
|||
|
this.contextMenu.path = this._path;
|
|||
|
if(this._displayType === Constants.DisplayType.GRID)
|
|||
|
this.contextMenu.centerBoxPointerPosition();
|
|||
|
}
|
|||
|
if(!this.contextMenu.isOpen)
|
|||
|
this.contextMenu.rebuildItems();
|
|||
|
this.contextMenu.toggle();
|
|||
|
}
|
|||
|
|
|||
|
activate(event) {
|
|||
|
this._info.launch(event.get_time());
|
|||
|
this._menuLayout.arcMenu.toggle();
|
|||
|
super.activate(event);
|
|||
|
}
|
|||
|
|
|||
|
_propertiesChanged(info) {
|
|||
|
this._info = info;
|
|||
|
this.createIcon();
|
|||
|
if(this.label)
|
|||
|
this.label.text = info.name;
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var SearchBox = GObject.registerClass({
|
|||
|
Signals: {
|
|||
|
'search-changed': { param_types: [GObject.TYPE_STRING] },
|
|||
|
'entry-key-focus-in': { },
|
|||
|
'entry-key-press': { param_types: [Clutter.Event.$gtype] },
|
|||
|
},},
|
|||
|
class Arc_Menu_SearchBox extends St.Entry {
|
|||
|
_init(menuLayout) {
|
|||
|
super._init({
|
|||
|
hint_text: _("Search…"),
|
|||
|
track_hover: true,
|
|||
|
can_focus: true,
|
|||
|
x_expand: true,
|
|||
|
x_align: Clutter.ActorAlign.FILL,
|
|||
|
y_align: Clutter.ActorAlign.START,
|
|||
|
name: "ArcSearchEntry"
|
|||
|
});
|
|||
|
this.searchResults = menuLayout.searchResults;
|
|||
|
this._settings = menuLayout._settings;
|
|||
|
this.triggerSearchChangeEvent = true;
|
|||
|
|
|||
|
const IconSizeEnum = this._settings.get_enum('misc-item-icon-size');
|
|||
|
let iconSize = Utils.getIconSize(IconSizeEnum, Constants.EXTRA_SMALL_ICON_SIZE);
|
|||
|
|
|||
|
this._findIcon = new St.Icon({
|
|||
|
style_class: 'search-entry-icon',
|
|||
|
icon_name: 'edit-find-symbolic',
|
|||
|
icon_size: iconSize
|
|||
|
});
|
|||
|
|
|||
|
this._clearIcon = new St.Icon({
|
|||
|
style_class: 'search-entry-icon',
|
|||
|
icon_name: 'edit-clear-symbolic',
|
|||
|
icon_size: iconSize
|
|||
|
});
|
|||
|
|
|||
|
this.set_primary_icon(this._findIcon);
|
|||
|
|
|||
|
this._text = this.get_clutter_text();
|
|||
|
this._textChangedId = this._text.connect('text-changed', this._onTextChanged.bind(this));
|
|||
|
this._keyPressId = this._text.connect('key-press-event', this._onKeyPress.bind(this));
|
|||
|
this._keyFocusInId = this._text.connect('key-focus-in', this._onKeyFocusIn.bind(this));
|
|||
|
this._keyFocusInId = this._text.connect('key-focus-out', this._onKeyFocusOut.bind(this));
|
|||
|
this._searchIconClickedId = this.connect('secondary-icon-clicked', () => this.clear());
|
|||
|
this.connect('destroy', this._onDestroy.bind(this));
|
|||
|
}
|
|||
|
|
|||
|
updateStyle(removeBorder){
|
|||
|
let style = this.style;
|
|||
|
this.style = style.replace("border-width: 0;", "");
|
|||
|
if(removeBorder)
|
|||
|
this.style += 'border-width: 0;';
|
|||
|
}
|
|||
|
|
|||
|
get entryBox(){
|
|||
|
return this;
|
|||
|
}
|
|||
|
|
|||
|
get actor(){
|
|||
|
return this;
|
|||
|
}
|
|||
|
|
|||
|
getText() {
|
|||
|
return this.get_text();
|
|||
|
}
|
|||
|
|
|||
|
setText(text) {
|
|||
|
this.set_text(text);
|
|||
|
}
|
|||
|
|
|||
|
clearWithoutSearchChangeEvent(){
|
|||
|
this.triggerSearchChangeEvent = false;
|
|||
|
this.set_text('');
|
|||
|
this.triggerSearchChangeEvent = true;
|
|||
|
}
|
|||
|
|
|||
|
hasKeyFocus() {
|
|||
|
return this.contains(global.stage.get_key_focus());
|
|||
|
}
|
|||
|
|
|||
|
clear() {
|
|||
|
this.set_text('');
|
|||
|
}
|
|||
|
|
|||
|
isEmpty() {
|
|||
|
return this.get_text() == '';
|
|||
|
}
|
|||
|
|
|||
|
_onKeyFocusOut(){
|
|||
|
if(!this.isEmpty()){
|
|||
|
this.add_style_pseudo_class('focus');
|
|||
|
return Clutter.EVENT_STOP;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
_onTextChanged() {
|
|||
|
let searchString = this.get_text();
|
|||
|
if(!this.isEmpty()){
|
|||
|
if(!this.hasKeyFocus())
|
|||
|
this.grab_key_focus();
|
|||
|
if (!this.searchResults.getTopResult()?.has_style_pseudo_class("active"))
|
|||
|
this.searchResults.getTopResult()?.add_style_pseudo_class("active")
|
|||
|
this.add_style_pseudo_class('focus');
|
|||
|
this.set_secondary_icon(this._clearIcon);
|
|||
|
}
|
|||
|
else{
|
|||
|
if(!this.hasKeyFocus())
|
|||
|
this.remove_style_pseudo_class('focus');
|
|||
|
this.set_secondary_icon(null);
|
|||
|
}
|
|||
|
|
|||
|
if(this.triggerSearchChangeEvent)
|
|||
|
this.emit('search-changed', searchString);
|
|||
|
}
|
|||
|
|
|||
|
_onKeyPress(actor, event) {
|
|||
|
let symbol = event.get_key_symbol();
|
|||
|
if (symbol == Clutter.KEY_Return ||
|
|||
|
symbol == Clutter.KEY_KP_Enter) {
|
|||
|
if (!this.isEmpty()) {
|
|||
|
if (this.searchResults.getTopResult()) {
|
|||
|
this.searchResults.getTopResult().activate(event);
|
|||
|
}
|
|||
|
}
|
|||
|
return Clutter.EVENT_STOP;
|
|||
|
}
|
|||
|
this.emit('entry-key-press', event);
|
|||
|
return Clutter.EVENT_PROPAGATE;
|
|||
|
}
|
|||
|
|
|||
|
_onKeyFocusIn() {
|
|||
|
this.add_style_pseudo_class('focus');
|
|||
|
this.emit('entry-key-focus-in');
|
|||
|
return Clutter.EVENT_PROPAGATE;
|
|||
|
}
|
|||
|
|
|||
|
_onDestroy() {
|
|||
|
if (this._textChangedId) {
|
|||
|
this._text.disconnect(this._textChangedId);
|
|||
|
this._textChangedId = null;
|
|||
|
}
|
|||
|
if (this._keyPressId) {
|
|||
|
this._text.disconnect(this._keyPressId);
|
|||
|
this._keyPressId = null;
|
|||
|
}
|
|||
|
if (this._keyFocusInId) {
|
|||
|
this._text.disconnect(this._keyFocusInId);
|
|||
|
this._keyFocusInId = null;
|
|||
|
}
|
|||
|
if(this._searchIconClickedId){
|
|||
|
this.disconnect(this._searchIconClickedId);
|
|||
|
this._searchIconClickedId = null;
|
|||
|
}
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
/**
|
|||
|
* This class is responsible for the appearance of the menu button.
|
|||
|
*/
|
|||
|
var MenuButtonWidget = class Arc_Menu_MenuButtonWidget{
|
|||
|
constructor() {
|
|||
|
this.actor = new St.BoxLayout({
|
|||
|
style_class: 'panel-status-menu-box',
|
|||
|
pack_start: false
|
|||
|
});
|
|||
|
this._arrowIcon = PopupMenu.arrowIcon(St.Side.BOTTOM);
|
|||
|
this._arrowIcon.add_style_class_name('arc-menu-arrow');
|
|||
|
|
|||
|
this._icon = new St.Icon({
|
|||
|
icon_name: 'start-here-symbolic',
|
|||
|
style_class: 'arc-menu-icon',
|
|||
|
track_hover:true,
|
|||
|
reactive: true,
|
|||
|
});
|
|||
|
this._label = new St.Label({
|
|||
|
text: _("Applications"),
|
|||
|
y_expand: true,
|
|||
|
style_class: 'arc-menu-text',
|
|||
|
y_align: Clutter.ActorAlign.CENTER,
|
|||
|
});
|
|||
|
|
|||
|
this.actor.add_child(this._icon);
|
|||
|
this.actor.add_child(this._label);
|
|||
|
this.actor.add_child(this._arrowIcon);
|
|||
|
}
|
|||
|
|
|||
|
setActiveStylePseudoClass(enable){
|
|||
|
if(enable){
|
|||
|
this._arrowIcon.add_style_pseudo_class('active');
|
|||
|
this._icon.add_style_pseudo_class('active');
|
|||
|
this._label.add_style_pseudo_class('active');
|
|||
|
}
|
|||
|
else{
|
|||
|
this._arrowIcon.remove_style_pseudo_class('active');
|
|||
|
this._icon.remove_style_pseudo_class('active');
|
|||
|
this._label.remove_style_pseudo_class('active');
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
updateArrowIconSide(side){
|
|||
|
let iconName;
|
|||
|
switch (side) {
|
|||
|
case St.Side.TOP:
|
|||
|
iconName = 'pan-down-symbolic';
|
|||
|
break;
|
|||
|
case St.Side.RIGHT:
|
|||
|
iconName = 'pan-start-symbolic';
|
|||
|
break;
|
|||
|
case St.Side.BOTTOM:
|
|||
|
iconName = 'pan-up-symbolic';
|
|||
|
break;
|
|||
|
case St.Side.LEFT:
|
|||
|
iconName = 'pan-end-symbolic';
|
|||
|
break;
|
|||
|
}
|
|||
|
this._arrowIcon.icon_name = iconName;
|
|||
|
}
|
|||
|
|
|||
|
getPanelLabel() {
|
|||
|
return this._label;
|
|||
|
}
|
|||
|
|
|||
|
getPanelIcon() {
|
|||
|
return this._icon;
|
|||
|
}
|
|||
|
|
|||
|
showArrowIcon() {
|
|||
|
if (!this.actor.contains(this._arrowIcon)) {
|
|||
|
this.actor.add_child(this._arrowIcon);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
hideArrowIcon() {
|
|||
|
if (this.actor.contains(this._arrowIcon)) {
|
|||
|
this.actor.remove_child(this._arrowIcon);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
showPanelIcon() {
|
|||
|
if (!this.actor.contains(this._icon)) {
|
|||
|
this.actor.add_child(this._icon);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
hidePanelIcon() {
|
|||
|
if (this.actor.contains(this._icon)) {
|
|||
|
this.actor.remove_child(this._icon);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
showPanelText() {
|
|||
|
if (!this.actor.contains(this._label)) {
|
|||
|
this.actor.add_child(this._label);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
hidePanelText() {
|
|||
|
this._label.style = '';
|
|||
|
if (this.actor.contains(this._label)) {
|
|||
|
this.actor.remove_child(this._label);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
setPanelTextStyle(style){
|
|||
|
this._label.style = style;
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
var DashMenuButtonWidget = class Arc_Menu_DashMenuButtonWidget{
|
|||
|
constructor(menuButton, settings) {
|
|||
|
this._menuButton = menuButton;
|
|||
|
this._settings = settings;
|
|||
|
this.actor = new St.Button({
|
|||
|
style_class: 'show-apps',
|
|||
|
track_hover: true,
|
|||
|
can_focus: true,
|
|||
|
toggle_mode: false,
|
|||
|
reactive: false
|
|||
|
});
|
|||
|
this.actor._delegate = this;
|
|||
|
this.icon = new imports.ui.iconGrid.BaseIcon(_("ArcMenu"),
|
|||
|
{ setSizeManually: true,
|
|||
|
showLabel: false,
|
|||
|
createIcon: this._createIcon.bind(this) });
|
|||
|
this._icon = new St.Icon({
|
|||
|
icon_name: 'start-here-symbolic',
|
|||
|
style_class: 'arc-menu-icon',
|
|||
|
icon_size: 15,
|
|||
|
track_hover:true,
|
|||
|
reactive: true
|
|||
|
});
|
|||
|
this.icon._delegate = this.actor;
|
|||
|
this._labelText = _("ArcMenu");
|
|||
|
this.label = new St.Label({ style_class: 'dash-label' });
|
|||
|
this.label.hide();
|
|||
|
Main.layoutManager.addChrome(this.label);
|
|||
|
this.label_actor = this.label;
|
|||
|
this.actor.set_child(this.icon);
|
|||
|
|
|||
|
this.child = this.actor;
|
|||
|
}
|
|||
|
showLabel() {
|
|||
|
if (!this._labelText)
|
|||
|
return;
|
|||
|
|
|||
|
this.label.set_text(this._labelText);
|
|||
|
this.label.opacity = 0;
|
|||
|
this.label.show();
|
|||
|
|
|||
|
let [stageX, stageY] = this.actor.get_transformed_position();
|
|||
|
let node = this.label.get_theme_node();
|
|||
|
|
|||
|
let itemWidth = this.actor.allocation.x2 - this.actor.allocation.x1;
|
|||
|
let itemHeight = this.actor.allocation.y2 - this.actor.allocation.y1;
|
|||
|
|
|||
|
let labelWidth = this.label.get_width();
|
|||
|
let labelHeight = this.label.get_height();
|
|||
|
|
|||
|
let x, y, xOffset, yOffset;
|
|||
|
|
|||
|
let position = this._menuButton._panel._settings.get_enum('dock-position');
|
|||
|
this._isHorizontal = ((position == St.Side.TOP) || (position == St.Side.BOTTOM));
|
|||
|
let labelOffset = node.get_length('-x-offset');
|
|||
|
switch (position) {
|
|||
|
case St.Side.LEFT:
|
|||
|
yOffset = Math.floor((itemHeight - labelHeight) / 2);
|
|||
|
y = stageY + yOffset;
|
|||
|
xOffset = labelOffset;
|
|||
|
x = stageX + this.actor.get_width() + xOffset;
|
|||
|
break;
|
|||
|
case St.Side.RIGHT:
|
|||
|
yOffset = Math.floor((itemHeight - labelHeight) / 2);
|
|||
|
y = stageY + yOffset;
|
|||
|
xOffset = labelOffset;
|
|||
|
x = Math.round(stageX) - labelWidth - xOffset;
|
|||
|
break;
|
|||
|
case St.Side.TOP:
|
|||
|
y = stageY + labelOffset + itemHeight;
|
|||
|
xOffset = Math.floor((itemWidth - labelWidth) / 2);
|
|||
|
x = stageX + xOffset;
|
|||
|
break;
|
|||
|
case St.Side.BOTTOM:
|
|||
|
yOffset = labelOffset;
|
|||
|
y = stageY - labelHeight - yOffset;
|
|||
|
xOffset = Math.floor((itemWidth - labelWidth) / 2);
|
|||
|
x = stageX + xOffset;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
// keep the label inside the screen border
|
|||
|
// Only needed fot the x coordinate.
|
|||
|
|
|||
|
// Leave a few pixel gap
|
|||
|
let gap = 5;
|
|||
|
let monitor = Main.layoutManager.findMonitorForActor(this.actor);
|
|||
|
if (x - monitor.x < gap)
|
|||
|
x += monitor.x - x + labelOffset;
|
|||
|
else if (x + labelWidth > monitor.x + monitor.width - gap)
|
|||
|
x -= x + labelWidth - (monitor.x + monitor.width) + gap;
|
|||
|
|
|||
|
this.label.remove_all_transitions();
|
|||
|
this.label.set_position(x, y);
|
|||
|
this.label.ease({
|
|||
|
opacity: 255,
|
|||
|
duration: Dash.DASH_ITEM_LABEL_SHOW_TIME,
|
|||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD
|
|||
|
});
|
|||
|
}
|
|||
|
hideLabel() {
|
|||
|
this.label.ease({
|
|||
|
opacity: 0,
|
|||
|
duration: Dash.DASH_ITEM_LABEL_HIDE_TIME,
|
|||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|||
|
onComplete: () => this.label.hide()
|
|||
|
});
|
|||
|
}
|
|||
|
_createIcon(size) {
|
|||
|
this._icon = new St.Icon({
|
|||
|
icon_name: 'start-here-symbolic',
|
|||
|
style_class: 'arc-menu-icon',
|
|||
|
track_hover:true,
|
|||
|
icon_size: size,
|
|||
|
reactive: true
|
|||
|
});
|
|||
|
let path = this._settings.get_string('custom-menu-button-icon');
|
|||
|
let iconString = Utils.getMenuButtonIcon(this._settings, path);
|
|||
|
this._icon.set_gicon(Gio.icon_new_for_string(iconString));
|
|||
|
|
|||
|
return this._icon;
|
|||
|
}
|
|||
|
getPanelIcon() {
|
|||
|
return this._icon;
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
var WorldClocksSection = GObject.registerClass(class Arc_Menu_WorldClocksSection extends ArcMenuButtonItem {
|
|||
|
_init(menuLayout) {
|
|||
|
super._init(menuLayout, null, null);
|
|||
|
|
|||
|
this.x_expand = true;
|
|||
|
this._clock = new imports.gi.GnomeDesktop.WallClock();
|
|||
|
this._clockNotifyId = 0;
|
|||
|
|
|||
|
this._locations = [];
|
|||
|
|
|||
|
let layout = new Clutter.GridLayout({ orientation: Clutter.Orientation.VERTICAL });
|
|||
|
this._grid = new St.Widget({ style_class: 'world-clocks-grid',
|
|||
|
x_expand: true,
|
|||
|
layout_manager: layout });
|
|||
|
layout.hookup_style(this._grid);
|
|||
|
|
|||
|
this.add_child(this._grid);
|
|||
|
|
|||
|
this._clocksApp = null;
|
|||
|
this._clocksProxy = new ClocksProxy(
|
|||
|
Gio.DBus.session,
|
|||
|
'org.gnome.clocks',
|
|||
|
'/org/gnome/clocks',
|
|||
|
this._onProxyReady.bind(this),
|
|||
|
null /* cancellable */,
|
|||
|
Gio.DBusProxyFlags.DO_NOT_AUTO_START | Gio.DBusProxyFlags.GET_INVALIDATED_PROPERTIES);
|
|||
|
|
|||
|
this._clockSettings = new Gio.Settings({
|
|||
|
schema_id: 'org.gnome.shell.world-clocks',
|
|||
|
});
|
|||
|
this.clocksChangedID = this._clockSettings.connect('changed', this._clocksChanged.bind(this));
|
|||
|
this._clocksChanged();
|
|||
|
|
|||
|
this._appSystem = Shell.AppSystem.get_default();
|
|||
|
this.syncID = this._appSystem.connect('installed-changed',
|
|||
|
this._sync.bind(this));
|
|||
|
this._sync();
|
|||
|
}
|
|||
|
|
|||
|
_onDestroy(){
|
|||
|
if(this.syncID){
|
|||
|
this._appSystem.disconnect(this.syncID);
|
|||
|
this.syncID = null;
|
|||
|
}
|
|||
|
if(this.clocksChangedID){
|
|||
|
this._clockSettings.disconnect(this.clocksChangedID);
|
|||
|
this.clocksChangedID = null;
|
|||
|
}
|
|||
|
if(this.clocksProxyID){
|
|||
|
this._clocksProxy.disconnect(this.clocksProxyID);
|
|||
|
this.clocksProxyID = null;
|
|||
|
}
|
|||
|
if (this._clockNotifyId){
|
|||
|
this._clock.disconnect(this._clockNotifyId);
|
|||
|
this._clockNotifyId = null;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
activate(event) {
|
|||
|
super.activate(event);
|
|||
|
if (this._clocksApp){
|
|||
|
this._clocksApp.activate();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
_sync() {
|
|||
|
this._clocksApp = this._appSystem.lookup_app('org.gnome.clocks.desktop');
|
|||
|
this.visible = this._clocksApp != null;
|
|||
|
}
|
|||
|
|
|||
|
_clocksChanged() {
|
|||
|
this._grid.destroy_all_children();
|
|||
|
this._locations = [];
|
|||
|
|
|||
|
let world = imports.gi.GWeather.Location.get_world();
|
|||
|
let clocks = this._clockSettings.get_value('locations').deep_unpack();
|
|||
|
for (let i = 0; i < clocks.length; i++) {
|
|||
|
let l = world.deserialize(clocks[i]);
|
|||
|
if (l && l.get_timezone() != null)
|
|||
|
this._locations.push({ location: l });
|
|||
|
}
|
|||
|
|
|||
|
this._locations.sort((a, b) => {
|
|||
|
return a.location.get_timezone().get_offset() -
|
|||
|
b.location.get_timezone().get_offset();
|
|||
|
});
|
|||
|
|
|||
|
let layout = this._grid.layout_manager;
|
|||
|
let title = this._locations.length == 0
|
|||
|
? _("Add world clocks…")
|
|||
|
: _("World Clocks");
|
|||
|
let header = new St.Label({ x_align: Clutter.ActorAlign.START,
|
|||
|
text: title });
|
|||
|
header.style = "font-weight: bold;";
|
|||
|
layout.attach(header, 0, 0, 2, 1);
|
|||
|
this.label_actor = header;
|
|||
|
|
|||
|
let localOffset = GLib.DateTime.new_now_local().get_utc_offset();
|
|||
|
|
|||
|
for (let i = 0; i < this._locations.length; i++) {
|
|||
|
let l = this._locations[i].location;
|
|||
|
|
|||
|
let name = l.get_city_name() || l.get_name();
|
|||
|
let label = new St.Label({ text: name,
|
|||
|
x_align: Clutter.ActorAlign.START,
|
|||
|
y_align: Clutter.ActorAlign.CENTER,
|
|||
|
x_expand: true });
|
|||
|
label.style = "font-weight: normal; font-size: 0.9em;";
|
|||
|
let time = new St.Label();
|
|||
|
time.style = "font-feature-settings: \"tnum\"; font-size: 1.2em;";
|
|||
|
let otherOffset = this._getTimeAtLocation(l).get_utc_offset();
|
|||
|
let offset = (otherOffset - localOffset) / GLib.TIME_SPAN_HOUR;
|
|||
|
let fmt = Math.trunc(offset) == offset ? '%s%.0f' : '%s%.1f';
|
|||
|
let prefix = offset >= 0 ? '+' : '-';
|
|||
|
let tz = new St.Label({ text: fmt.format(prefix, Math.abs(offset)),
|
|||
|
x_align: Clutter.ActorAlign.END,
|
|||
|
y_align: Clutter.ActorAlign.CENTER });
|
|||
|
tz.style = "font-feature-settings: \"tnum\"; font-size: 0.9em;";
|
|||
|
if (this._grid.text_direction == Clutter.TextDirection.RTL) {
|
|||
|
layout.attach(tz, 0, i + 1, 1, 1);
|
|||
|
layout.attach(time, 1, i + 1, 1, 1);
|
|||
|
layout.attach(label, 2, i + 1, 1, 1);
|
|||
|
} else {
|
|||
|
layout.attach(label, 0, i + 1, 1, 1);
|
|||
|
layout.attach(time, 1, i + 1, 1, 1);
|
|||
|
layout.attach(tz, 2, i + 1, 1, 1);
|
|||
|
}
|
|||
|
|
|||
|
this._locations[i].actor = time;
|
|||
|
}
|
|||
|
|
|||
|
if (this._grid.get_n_children() > 1) {
|
|||
|
if (!this._clockNotifyId) {
|
|||
|
this._clockNotifyId =
|
|||
|
this._clock.connect('notify::clock', this._updateLabels.bind(this));
|
|||
|
}
|
|||
|
this._updateLabels();
|
|||
|
} else {
|
|||
|
if (this._clockNotifyId)
|
|||
|
this._clock.disconnect(this._clockNotifyId);
|
|||
|
this._clockNotifyId = 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
_getTimeAtLocation(location) {
|
|||
|
let tz = GLib.TimeZone.new(location.get_timezone().get_tzid());
|
|||
|
return GLib.DateTime.new_now(tz);
|
|||
|
}
|
|||
|
|
|||
|
_updateLabels() {
|
|||
|
for (let i = 0; i < this._locations.length; i++) {
|
|||
|
let l = this._locations[i];
|
|||
|
let now = this._getTimeAtLocation(l.location);
|
|||
|
l.actor.text = Util.formatTime(now, { timeOnly: true });
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
_onProxyReady(proxy, error) {
|
|||
|
if (error) {
|
|||
|
log(`Failed to create GNOME Clocks proxy: ${error}`);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
this.clocksProxyID = this._clocksProxy.connect('g-properties-changed',
|
|||
|
this._onClocksPropertiesChanged.bind(this));
|
|||
|
this._onClocksPropertiesChanged();
|
|||
|
}
|
|||
|
|
|||
|
_onClocksPropertiesChanged() {
|
|||
|
if (this._clocksProxy.g_name_owner == null)
|
|||
|
return;
|
|||
|
|
|||
|
this._clockSettings.set_value('locations',
|
|||
|
new GLib.Variant('av', this._clocksProxy.Locations));
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var WeatherSection = GObject.registerClass(class Arc_Menu_WeatherSection extends ArcMenuButtonItem {
|
|||
|
_init(menuLayout) {
|
|||
|
super._init(menuLayout, null, null);
|
|||
|
|
|||
|
this.x_expand = true;
|
|||
|
this.x_align = Clutter.ActorAlign.FILL;
|
|||
|
this._weatherClient = new imports.misc.weather.WeatherClient();
|
|||
|
|
|||
|
let box = new St.BoxLayout({
|
|||
|
vertical: true,
|
|||
|
x_expand: true,
|
|||
|
});
|
|||
|
|
|||
|
this.add_child(box);
|
|||
|
|
|||
|
let titleBox = new St.BoxLayout({ });
|
|||
|
let label = new St.Label({
|
|||
|
x_align: Clutter.ActorAlign.START,
|
|||
|
x_expand: true,
|
|||
|
y_align: Clutter.ActorAlign.END,
|
|||
|
text: _('Weather'),
|
|||
|
})
|
|||
|
label.style = "font-weight: bold; padding-bottom: 5px;";
|
|||
|
titleBox.add_child(label);
|
|||
|
box.add_child(titleBox);
|
|||
|
|
|||
|
this._titleLocation = new St.Label({
|
|||
|
x_align: Clutter.ActorAlign.END,
|
|||
|
y_align: Clutter.ActorAlign.END,
|
|||
|
});
|
|||
|
this._titleLocation.style = "font-weight: bold; padding-bottom: 5px;";
|
|||
|
titleBox.add_child(this._titleLocation);
|
|||
|
|
|||
|
let layout = new Clutter.GridLayout({ orientation: Clutter.Orientation.VERTICAL });
|
|||
|
this._forecastGrid = new St.Widget({
|
|||
|
style_class: 'weather-grid',
|
|||
|
layout_manager: layout,
|
|||
|
});
|
|||
|
layout.hookup_style(this._forecastGrid);
|
|||
|
box.add_child(this._forecastGrid);
|
|||
|
|
|||
|
this.syncID = this._weatherClient.connect('changed', this._sync.bind(this));
|
|||
|
this._sync();
|
|||
|
|
|||
|
}
|
|||
|
_onDestroy(){
|
|||
|
if(this.syncID){
|
|||
|
this._weatherClient.disconnect(this.syncID);
|
|||
|
this.syncID = null;
|
|||
|
}
|
|||
|
this._weatherClient = null;
|
|||
|
}
|
|||
|
vfunc_map() {
|
|||
|
this._weatherClient.update();
|
|||
|
super.vfunc_map();
|
|||
|
}
|
|||
|
|
|||
|
activate(event) {
|
|||
|
super.activate(event);
|
|||
|
this._weatherClient.activateApp();
|
|||
|
}
|
|||
|
|
|||
|
_getInfos() {
|
|||
|
let forecasts = this._weatherClient.info.get_forecast_list();
|
|||
|
|
|||
|
let now = GLib.DateTime.new_now_local();
|
|||
|
let current = GLib.DateTime.new_from_unix_local(0);
|
|||
|
let infos = [];
|
|||
|
for (let i = 0; i < forecasts.length; i++) {
|
|||
|
const [valid, timestamp] = forecasts[i].get_value_update();
|
|||
|
if (!valid || timestamp === 0)
|
|||
|
continue; // 0 means 'never updated'
|
|||
|
|
|||
|
const datetime = GLib.DateTime.new_from_unix_local(timestamp);
|
|||
|
if (now.difference(datetime) > 0)
|
|||
|
continue; // Ignore earlier forecasts
|
|||
|
|
|||
|
if (datetime.difference(current) < GLib.TIME_SPAN_HOUR)
|
|||
|
continue; // Enforce a minimum interval of 1h
|
|||
|
|
|||
|
if (infos.push(forecasts[i]) == 5)
|
|||
|
break; // Use a maximum of five forecasts
|
|||
|
|
|||
|
current = datetime;
|
|||
|
}
|
|||
|
return infos;
|
|||
|
}
|
|||
|
|
|||
|
_addForecasts() {
|
|||
|
let layout = this._forecastGrid.layout_manager;
|
|||
|
|
|||
|
let infos = this._getInfos();
|
|||
|
if (this._forecastGrid.text_direction == Clutter.TextDirection.RTL)
|
|||
|
infos.reverse();
|
|||
|
|
|||
|
let col = 0;
|
|||
|
infos.forEach(fc => {
|
|||
|
const [valid_, timestamp] = fc.get_value_update();
|
|||
|
let timeStr = Util.formatTime(new Date(timestamp * 1000), {
|
|||
|
timeOnly: true
|
|||
|
});
|
|||
|
const [, tempValue] = fc.get_value_temp(imports.gi.GWeather.TemperatureUnit.DEFAULT);
|
|||
|
const tempPrefix = tempValue >= 0 ? ' ' : '';
|
|||
|
|
|||
|
let time = new St.Label({
|
|||
|
text: timeStr,
|
|||
|
x_align: Clutter.ActorAlign.CENTER,
|
|||
|
});
|
|||
|
time.style = "font-size: 0.8em;"
|
|||
|
let icon = new St.Icon({
|
|||
|
style_class: 'weather-forecast-icon',
|
|||
|
icon_name: fc.get_symbolic_icon_name(),
|
|||
|
x_align: Clutter.ActorAlign.CENTER,
|
|||
|
x_expand: true,
|
|||
|
});
|
|||
|
let temp = new St.Label({
|
|||
|
text: '%s%.0f°'.format(tempPrefix, tempValue),
|
|||
|
x_align: Clutter.ActorAlign.CENTER,
|
|||
|
});
|
|||
|
|
|||
|
temp.clutter_text.ellipsize = imports.gi.Pango.EllipsizeMode.NONE;
|
|||
|
time.clutter_text.ellipsize = imports.gi.Pango.EllipsizeMode.NONE;
|
|||
|
|
|||
|
layout.attach(time, col, 0, 1, 1);
|
|||
|
layout.attach(icon, col, 1, 1, 1);
|
|||
|
layout.attach(temp, col, 2, 1, 1);
|
|||
|
col++;
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
_setStatusLabel(text) {
|
|||
|
let layout = this._forecastGrid.layout_manager;
|
|||
|
let label = new St.Label({ text });
|
|||
|
layout.attach(label, 0, 0, 1, 1);
|
|||
|
}
|
|||
|
|
|||
|
_updateForecasts() {
|
|||
|
this._forecastGrid.destroy_all_children();
|
|||
|
|
|||
|
if (!this._weatherClient.hasLocation) {
|
|||
|
this._setStatusLabel(_("Select a location…"));
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
let info = this._weatherClient.info;
|
|||
|
let loc = info.get_location();
|
|||
|
if (loc.get_level() !== imports.gi.GWeather.LocationLevel.CITY && loc.has_coords()) {
|
|||
|
let world = imports.gi.GWeather.Location.get_world();
|
|||
|
loc = world.find_nearest_city(...loc.get_coords());
|
|||
|
}
|
|||
|
this._titleLocation.text = loc.get_name();
|
|||
|
|
|||
|
if (this._weatherClient.loading) {
|
|||
|
this._setStatusLabel(_("Loading…"));
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (info.is_valid()) {
|
|||
|
this._addForecasts();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (info.network_error())
|
|||
|
this._setStatusLabel(_("Go online for weather information"));
|
|||
|
else
|
|||
|
this._setStatusLabel(_("Weather information is currently unavailable"));
|
|||
|
}
|
|||
|
|
|||
|
_sync() {
|
|||
|
this.visible = this._weatherClient.available;
|
|||
|
|
|||
|
if (!this.visible)
|
|||
|
return;
|
|||
|
|
|||
|
this._titleLocation.visible = this._weatherClient.hasLocation;
|
|||
|
|
|||
|
this._updateForecasts();
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
function _isToday(date) {
|
|||
|
let now = new Date();
|
|||
|
return now.getYear() == date.getYear() &&
|
|||
|
now.getMonth() == date.getMonth() &&
|
|||
|
now.getDate() == date.getDate();
|
|||
|
}
|