Compare commits
128 Commits
wip/fmuell
...
gbsneto/fi
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5813850fe1 | ||
![]() |
1ee73ac357 | ||
![]() |
65cbf4aa45 | ||
![]() |
5ca039c1db | ||
![]() |
2294ae0c46 | ||
![]() |
4d2b2a12ea | ||
![]() |
c6d57059ff | ||
![]() |
5f13cf767e | ||
![]() |
93425b0500 | ||
![]() |
a87ab6d0fc | ||
![]() |
1c117c469a | ||
![]() |
8003f8b803 | ||
![]() |
7df93458d7 | ||
![]() |
753618a19f | ||
![]() |
e355756758 | ||
![]() |
62a3b9e6a3 | ||
![]() |
dc79393b27 | ||
![]() |
c334aa2a4c | ||
![]() |
9f61a4f5fd | ||
![]() |
15d0050994 | ||
![]() |
1846f337d8 | ||
![]() |
a9e63039ce | ||
![]() |
7edd5f27d1 | ||
![]() |
9b47195974 | ||
![]() |
4ef8041be0 | ||
![]() |
f0a7395b30 | ||
![]() |
c1a6effea0 | ||
![]() |
f78efc46e7 | ||
![]() |
42ae052da7 | ||
![]() |
fab390826e | ||
![]() |
2a9923628b | ||
![]() |
291aa0b053 | ||
![]() |
83eb75ad7a | ||
![]() |
bb215966e5 | ||
![]() |
545d49c70d | ||
![]() |
ace44af815 | ||
![]() |
699e97559d | ||
![]() |
4aecf4c973 | ||
![]() |
b092c5f37d | ||
![]() |
aca8aec94b | ||
![]() |
9cfb51c106 | ||
![]() |
e2352f5126 | ||
![]() |
40e624444c | ||
![]() |
3cf67b1236 | ||
![]() |
5dedb97fcc | ||
![]() |
4590094605 | ||
![]() |
15e7625c80 | ||
![]() |
cdd2803498 | ||
![]() |
43fb2b38b1 | ||
![]() |
95224bd006 | ||
![]() |
446183adee | ||
![]() |
345a8fe748 | ||
![]() |
d8593c5b4a | ||
![]() |
f89d721c12 | ||
![]() |
af34b7c25e | ||
![]() |
b108aa1ace | ||
![]() |
5dbf09c008 | ||
![]() |
680dc18c6b | ||
![]() |
73413ac6c0 | ||
![]() |
f61c8e5e1d | ||
![]() |
36713db990 | ||
![]() |
6bb7d4002f | ||
![]() |
aa28d487d3 | ||
![]() |
a7d974481c | ||
![]() |
3730314dd5 | ||
![]() |
6b0c8c9fe0 | ||
![]() |
28a56d24ad | ||
![]() |
5e6629e1a7 | ||
![]() |
a00b967df0 | ||
![]() |
193e4ae31e | ||
![]() |
409a27c3b8 | ||
![]() |
ba97e8da7a | ||
![]() |
c47e672eea | ||
![]() |
43a19739ab | ||
![]() |
36f9147b21 | ||
![]() |
8647922df9 | ||
![]() |
df3068d9ca | ||
![]() |
036e67049b | ||
![]() |
f02033acb7 | ||
![]() |
91d73d65c3 | ||
![]() |
81c4c23016 | ||
![]() |
fef2bac8ab | ||
![]() |
c59c5eb893 | ||
![]() |
a977c1388a | ||
![]() |
0f799ae313 | ||
![]() |
23c3f3fdea | ||
![]() |
82c7090e9e | ||
![]() |
0fdfebdb67 | ||
![]() |
208c551787 | ||
![]() |
b10606e884 | ||
![]() |
f4a64f77f2 | ||
![]() |
e92477a752 | ||
![]() |
37e0a73c8f | ||
![]() |
4f76e05058 | ||
![]() |
3134222d27 | ||
![]() |
a9ed128dce | ||
![]() |
c264cc4131 | ||
![]() |
a7943ff934 | ||
![]() |
c353914dd0 | ||
![]() |
91319e3963 | ||
![]() |
dc4ff941bd | ||
![]() |
b597d5faf0 | ||
![]() |
f6da36ad3a | ||
![]() |
5f4e2749a2 | ||
![]() |
69b1fb699f | ||
![]() |
259f90bbf6 | ||
![]() |
8665045326 | ||
![]() |
a7bb8ee639 | ||
![]() |
8f732e4f45 | ||
![]() |
95a31b0c31 | ||
![]() |
36b9f45368 | ||
![]() |
46575804cc | ||
![]() |
86a00b6872 | ||
![]() |
23d6d13d80 | ||
![]() |
09addfc87c | ||
![]() |
a2f27a9409 | ||
![]() |
412003efbf | ||
![]() |
582b3aacf4 | ||
![]() |
1af0b54c1e | ||
![]() |
74ba2e6634 | ||
![]() |
78608a5080 | ||
![]() |
7a86637f8d | ||
![]() |
c2961f2152 | ||
![]() |
6845c6f958 | ||
![]() |
794a056819 | ||
![]() |
69ad75cf48 | ||
![]() |
3db52155dd | ||
![]() |
38805ae662 |
18
.gitlab-ci.yml
Normal file
18
.gitlab-ci.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
stages:
|
||||
- source_check
|
||||
|
||||
variables:
|
||||
JS_LOG: "js-report.txt"
|
||||
|
||||
js_check:
|
||||
image: registry.fedoraproject.org/fedora:latest
|
||||
stage: source_check
|
||||
before_script:
|
||||
- dnf install -y findutils mozjs60-devel
|
||||
script:
|
||||
- find js -name '*.js' -exec js60 -c -s '{}' ';' 2>&1 | tee $JS_LOG
|
||||
- (! grep -q . $JS_LOG)
|
||||
artifacts:
|
||||
paths:
|
||||
- ${JS_LOG}
|
||||
when: on_failure
|
@@ -158,15 +158,15 @@ you to inherit from a type to use it, you can do so:
|
||||
var MyClutterActor = GObject.registerClass(
|
||||
class MyClutterActor extends Clutter.Actor {
|
||||
|
||||
vfunc_get_preferred_width(actor, forHeight) {
|
||||
vfunc_get_preferred_width(forHeight) {
|
||||
return [100, 100];
|
||||
}
|
||||
|
||||
vfunc_get_preferred_height(actor, forWidth) {
|
||||
vfunc_get_preferred_height(forWidth) {
|
||||
return [100, 100];
|
||||
}
|
||||
|
||||
vfunc_paint(actor) {
|
||||
vfunc_paint() {
|
||||
let alloc = this.get_allocation_box();
|
||||
Cogl.set_source_color4ub(255, 0, 0, 255);
|
||||
Cogl.rectangle(alloc.x1, alloc.y1,
|
||||
|
35
NEWS
35
NEWS
@@ -1,3 +1,38 @@
|
||||
3.31.90
|
||||
=======
|
||||
* Fix input methods after ibus restarts [Takao; #295]
|
||||
* Refresh world clocks and weather sections [Florian; #262]
|
||||
* Port to ES6 classes (update your extensions!) [Florian; !361]
|
||||
* networkAgent: Advise users when WPS support is available [Lubomir; !329]
|
||||
* Performance improvements [Carlos; #832, #815]
|
||||
* Fix drag-and-drop with wacom pens [Carlos; #540]
|
||||
* Fix CAPS LOCK indication on wayland [Carlos; #762881]
|
||||
* Show details of non-sensitive notifications on lock screen [Philip; #726]
|
||||
* Refine extension-prefs' error UI [Florian; !193]
|
||||
* Add switch-to-application-n keybindings [Florian; #648000]
|
||||
* Remove top bar translucency [Florian; #408]
|
||||
* Support emojis and keypads in on-screen keyboard [Carlos; #675]
|
||||
* Don't allow popups to re-enable keyboard shortcuts on lock screen
|
||||
(CVE-2019-3820) [Florian, Ray; #851]
|
||||
* Replace app menu [Florian; #624]
|
||||
* Include commonly copied+pasted extension helpers [Florian; !150]
|
||||
* Misc. bug fixes and cleanups [Florian, Daniel, Philip, Sergio, Pascal,
|
||||
Georges, verdre, Carlos, Christopher; #780, #909, !316, !308, !309, #915,
|
||||
!350, !362, !357, !365, !366, !283, !367, #942, !371, !373, !374, !343,
|
||||
!375, !292, !317, !377, !379, !346, !383, #953, !388]
|
||||
|
||||
Contributors:
|
||||
Sergio Costas, Christopher Davis, Bilal Elmoussaoui, Takao Fujiwara,
|
||||
Carlos Garnacho, Niels De Graef, Christian Kellner, Ignat Loskutov,
|
||||
Florian Müllner, Georges Basile Stavracas Neto, Pascal Nowack, Lubomir Rintel,
|
||||
Jakub Steiner, Ray Strode, verdre, Daniel van Vugt, Philip Withnall
|
||||
|
||||
Translators:
|
||||
Daniel Mustieles [es], Carmen Bianca BAKKER [eo], Charles Monzat [fr],
|
||||
Pieter Schalk Schoeman [af], Jordi Mas [ca], Matej Urbančič [sl],
|
||||
Fran Dieguez [gl], Balázs Úr [hu], A S Alam [pa], Fabio Tomat [fur],
|
||||
Aurimas Černius [lt], Piotr Drąg [pl], Marek Cernocky [cs], Ryuta Fujii [ja]
|
||||
|
||||
3.31.4
|
||||
======
|
||||
* Improve icon grid performance [Daniel; #174]
|
||||
|
15740
data/emoji.json
Normal file
15740
data/emoji.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -54,5 +54,6 @@
|
||||
<file>us.json</file>
|
||||
<file>vn.json</file>
|
||||
<file>za.json</file>
|
||||
<file>emoji.json</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
|
@@ -145,6 +145,42 @@
|
||||
<summary>Keybinding that pauses and resumes all running tweens, for debugging purposes</summary>
|
||||
<description></description>
|
||||
</key>
|
||||
<key name="switch-to-application-1" type="as">
|
||||
<default>["<Super>1"]</default>
|
||||
<summary>Switch to application 1</summary>
|
||||
</key>
|
||||
<key name="switch-to-application-2" type="as">
|
||||
<default>["<Super>2"]</default>
|
||||
<summary>Switch to application 2</summary>
|
||||
</key>
|
||||
<key name="switch-to-application-3" type="as">
|
||||
<default>["<Super>3"]</default>
|
||||
<summary>Switch to application 3</summary>
|
||||
</key>
|
||||
<key name="switch-to-application-4" type="as">
|
||||
<default>["<Super>4"]</default>
|
||||
<summary>Switch to application 4</summary>
|
||||
</key>
|
||||
<key name="switch-to-application-5" type="as">
|
||||
<default>["<Super>5"]</default>
|
||||
<summary>Switch to application 5</summary>
|
||||
</key>
|
||||
<key name="switch-to-application-6" type="as">
|
||||
<default>["<Super>6"]</default>
|
||||
<summary>Switch to application 6</summary>
|
||||
</key>
|
||||
<key name="switch-to-application-7" type="as">
|
||||
<default>["<Super>7"]</default>
|
||||
<summary>Switch to application 7</summary>
|
||||
</key>
|
||||
<key name="switch-to-application-8" type="as">
|
||||
<default>["<Super>8"]</default>
|
||||
<summary>Switch to application 8</summary>
|
||||
</key>
|
||||
<key name="switch-to-application-9" type="as">
|
||||
<default>["<Super>9"]</default>
|
||||
<summary>Switch to application 9</summary>
|
||||
</key>
|
||||
</schema>
|
||||
|
||||
<schema id="org.gnome.shell.keyboard" path="/org/gnome/shell/keyboard/"
|
||||
|
@@ -2,26 +2,23 @@
|
||||
// it gets @if ed depending on $variant
|
||||
|
||||
|
||||
$base_color: if($variant =='light', #ffffff, #292929);
|
||||
$bg_color: if($variant =='light', #ededed, #393f3f);
|
||||
$fg_color: if($variant =='light', #2e3436, #eeeeec);
|
||||
$base_color: if($variant == 'light', #ffffff, lighten(desaturate(#241f31, 20%), 2%));
|
||||
$bg_color: if($variant == 'light', #f6f5f4, desaturate(#3d3846, 10%));
|
||||
$fg_color: if($variant == 'light', #2e3436, #eeeeec);
|
||||
|
||||
$selected_fg_color: #ffffff;
|
||||
$selected_bg_color: if($variant == 'light', #4a90d9, darken(#4a90d9,20%));
|
||||
$selected_borders_color: if($variant=='light', darken($selected_bg_color, 30%),
|
||||
darken($selected_bg_color, 20%));
|
||||
$borders_color: if($variant =='light', darken($bg_color,30%), darken($bg_color,12%));
|
||||
$borders_edge: if($variant =='light', white, transparentize($fg_color, 0.9));
|
||||
$link_color: if($variant == 'light', darken($selected_bg_color,10%),
|
||||
lighten($selected_bg_color,20%));
|
||||
$link_visited_color: if($variant == 'light', darken($selected_bg_color,20%),
|
||||
lighten($selected_bg_color,10%));
|
||||
$selected_bg_color: if($variant == 'light', #3584e4, darken(#3584e4, 20%));
|
||||
$selected_borders_color: if($variant== 'light', darken($selected_bg_color, 15%), darken($selected_bg_color, 30%));
|
||||
$borders_color: if($variant == 'light', darken($bg_color, 18%), darken($bg_color, 10%));
|
||||
$borders_edge: if($variant == 'light', transparentize(white, 0.2), transparentize($fg_color, 0.93));
|
||||
$link_color: if($variant == 'light', darken($selected_bg_color, 10%), lighten($selected_bg_color, 20%));
|
||||
$link_visited_color: if($variant == 'light', darken($selected_bg_color, 20%), lighten($selected_bg_color, 10%));
|
||||
$top_hilight: $borders_edge;
|
||||
|
||||
$warning_color: #f57900;
|
||||
$error_color: #cc0000;
|
||||
$success_color: if($variant =='light', #73d216, darken(#73d216,10%));
|
||||
$destructive_color: if($variant =='light', #ef2929, darken(#ef2929,10%));
|
||||
$success_color: if($variant == 'light', #33d17a, darken(#33d17a, 10%));
|
||||
$destructive_color: if($variant == 'light', #e01b24, darken(#e01b24, 10%));
|
||||
|
||||
$osd_fg_color: #eeeeec;
|
||||
$osd_bg_color: #2e3436;
|
||||
|
@@ -729,9 +729,7 @@ StScrollBar {
|
||||
/* TOP BAR */
|
||||
|
||||
#panel {
|
||||
background-color: rgba(0, 0, 0, 0.35);
|
||||
/* transition from solid to transparent */
|
||||
transition-duration: 500ms;
|
||||
background-color: black;
|
||||
font-weight: bold;
|
||||
height: 1.86em;
|
||||
font-feature-settings: "tnum";
|
||||
@@ -748,7 +746,7 @@ StScrollBar {
|
||||
|
||||
.panel-corner {
|
||||
-panel-corner-radius: $panel-corner-radius;
|
||||
-panel-corner-background-color: rgba(0, 0, 0, 0.35);
|
||||
-panel-corner-background-color: black;
|
||||
-panel-corner-border-width: 2px;
|
||||
-panel-corner-border-color: transparent;
|
||||
|
||||
@@ -767,9 +765,7 @@ StScrollBar {
|
||||
-natural-hpadding: 12px;
|
||||
-minimum-hpadding: 6px;
|
||||
font-weight: bold;
|
||||
color: #eee;
|
||||
text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.9);
|
||||
transition-duration: 100ms;
|
||||
color: #ccc;
|
||||
|
||||
.app-menu-icon {
|
||||
-st-icon-style: symbolic;
|
||||
@@ -778,21 +774,8 @@ StScrollBar {
|
||||
//dimensions of the icon are hardcoded
|
||||
}
|
||||
|
||||
.system-status-icon,
|
||||
.app-menu-icon > StIcon,
|
||||
.popup-menu-arrow {
|
||||
icon-shadow: 0px 1px 2px rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: lighten($fg_color, 10%);
|
||||
text-shadow: 0px 1px 6px rgba(0, 0, 0, 1);
|
||||
|
||||
.system-status-icon,
|
||||
.app-menu-icon > StIcon,
|
||||
.popup-menu-arrow {
|
||||
icon-shadow: 0px 1px 6px rgba(0, 0, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
&:active, &:overview, &:focus, &:checked {
|
||||
@@ -801,8 +784,6 @@ StScrollBar {
|
||||
background-color: rgba(0, 0, 0, 0.01);
|
||||
box-shadow: inset 0 -2px 0px lighten($selected_bg_color,5%);
|
||||
color: lighten($fg_color,10%);
|
||||
|
||||
& > .system-status-icon { icon-shadow: black 0 2px 2px; }
|
||||
}
|
||||
|
||||
.system-status-icon { icon-size: 1.09em; padding: 0 5px; }
|
||||
@@ -827,31 +808,6 @@ StScrollBar {
|
||||
.screencast-indicator { color: $warning_color; }
|
||||
|
||||
.remote-access-indicator { color: $warning_color; }
|
||||
|
||||
&.solid {
|
||||
background-color: black;
|
||||
/* transition from transparent to solid */
|
||||
transition-duration: 300ms;
|
||||
|
||||
.panel-corner {
|
||||
-panel-corner-background-color: black;
|
||||
}
|
||||
|
||||
.panel-button {
|
||||
color: #ccc;
|
||||
text-shadow: none;
|
||||
|
||||
&:hover, &:active, &:overview, &:focus, &:checked {
|
||||
color: lighten($fg_color, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
.system-status-icon,
|
||||
.app-menu-icon > StIcon,
|
||||
.popup-menu-arrow {
|
||||
icon-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// calendar popover
|
||||
@@ -922,14 +878,47 @@ StScrollBar {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.world-clocks-grid {
|
||||
.weather-header.location {
|
||||
font-weight: normal;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.world-clocks-grid,
|
||||
.weather-grid {
|
||||
spacing-rows: 0.4em;
|
||||
spacing-columns: 0.8em;
|
||||
}
|
||||
|
||||
.weather-box {
|
||||
spacing: 0.4em;
|
||||
}
|
||||
|
||||
.world-clocks-city {
|
||||
font-weight: bold;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.world-clocks-time {
|
||||
color: darken($fg_color,20%);
|
||||
font-feature-settings: "tnum";
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.world-clocks-timezone {
|
||||
color: darken($fg_color,40%);
|
||||
font-feature-settings: "tnum";
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.weather-forecast-icon {
|
||||
icon-size: 2.18em;
|
||||
}
|
||||
|
||||
.weather-forecast-time {
|
||||
color: darken($fg_color,40%);
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.calendar-month-label {
|
||||
color: darken($fg_color,5%);
|
||||
font-weight: bold;
|
||||
@@ -1107,6 +1096,11 @@ StScrollBar {
|
||||
.label-shadow { color: transparent; }
|
||||
}
|
||||
|
||||
.app-menu,
|
||||
.app-well-menu {
|
||||
max-width: 27.25em;
|
||||
}
|
||||
|
||||
.aggregate-menu {
|
||||
min-width: 21em;
|
||||
.popup-menu-icon { padding: 0 4px; }
|
||||
@@ -1646,6 +1640,15 @@ StScrollBar {
|
||||
|
||||
#keyboard {
|
||||
background-color: transparentize($osd_bg_color, 0.3);
|
||||
|
||||
.page-indicator {
|
||||
padding: 4px 4px;
|
||||
|
||||
.page-indicator-icon {
|
||||
width: 6px;
|
||||
height: 6px
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.key-container {
|
||||
@@ -1655,8 +1658,8 @@ StScrollBar {
|
||||
|
||||
.keyboard-key {
|
||||
background-color: #393f3f;
|
||||
min-height: 2em;
|
||||
min-width: 2em;
|
||||
min-height: 1.2em;
|
||||
min-width: 1.2em;
|
||||
font-size: 16pt;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #464d4d;
|
||||
@@ -1707,6 +1710,20 @@ StScrollBar {
|
||||
-boxpointer-gap: 5px;
|
||||
}
|
||||
|
||||
.emoji-page {
|
||||
.keyboard-key {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-panel {
|
||||
.keyboard-key:latched {
|
||||
border-color: #005684;
|
||||
background-color: #006098;
|
||||
}
|
||||
}
|
||||
|
||||
// IBus Candidate Popup
|
||||
|
||||
.candidate-popup-content {
|
||||
|
@@ -104,29 +104,111 @@ var Application = class {
|
||||
}
|
||||
|
||||
_buildErrorUI(extension, exc) {
|
||||
let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
|
||||
let scroll = new Gtk.ScrolledWindow({
|
||||
hscrollbar_policy: Gtk.PolicyType.NEVER,
|
||||
propagate_natural_height: true
|
||||
});
|
||||
|
||||
let box = new Gtk.Box({
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
spacing: 12,
|
||||
margin: 100,
|
||||
margin_bottom: 60
|
||||
});
|
||||
scroll.add(box);
|
||||
|
||||
let label = new Gtk.Label({
|
||||
label: _("There was an error loading the preferences dialog for %s:").format(extension.metadata.name)
|
||||
label: '<span size="x-large">%s</span>'.format(_("Something’s gone wrong")),
|
||||
use_markup: true
|
||||
});
|
||||
label.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
|
||||
box.add(label);
|
||||
|
||||
label = new Gtk.Label({
|
||||
label: _("We’re very sorry, but there’s been a problem: the settings for this extension can’t be displayed. We recommend that you report the issue to the extension authors."),
|
||||
justify: Gtk.Justification.CENTER,
|
||||
wrap: true
|
||||
});
|
||||
box.add(label);
|
||||
|
||||
let errortext = '';
|
||||
errortext += exc;
|
||||
errortext += '\n\n';
|
||||
errortext += 'Stack trace:\n';
|
||||
let expander = new Expander({
|
||||
label: _("Technical Details"),
|
||||
margin_top: 12
|
||||
});
|
||||
box.add(expander);
|
||||
|
||||
// Indent stack trace.
|
||||
errortext += exc.stack.split('\n').map(line => ' ' + line).join('\n');
|
||||
let errortext = `${exc}\n\nStack trace:\n${
|
||||
// Indent stack trace.
|
||||
exc.stack.split('\n').map(line => ` ${line}`).join('\n')
|
||||
}`;
|
||||
|
||||
let scroll = new Gtk.ScrolledWindow({ vexpand: true });
|
||||
let buffer = new Gtk.TextBuffer({ text: errortext });
|
||||
let textview = new Gtk.TextView({ buffer: buffer });
|
||||
textview.override_font(Pango.font_description_from_string('monospace'));
|
||||
scroll.add(textview);
|
||||
box.add(scroll);
|
||||
let textview = new Gtk.TextView({
|
||||
buffer: buffer,
|
||||
wrap_mode: Gtk.WrapMode.WORD,
|
||||
monospace: true,
|
||||
editable: false,
|
||||
top_margin: 12,
|
||||
bottom_margin: 12,
|
||||
left_margin: 12,
|
||||
right_margin: 12
|
||||
});
|
||||
|
||||
box.show_all();
|
||||
return box;
|
||||
let toolbar = new Gtk.Toolbar();
|
||||
let provider = new Gtk.CssProvider();
|
||||
provider.load_from_data(`* {
|
||||
border: 0 solid @borders;
|
||||
border-top-width: 1px;
|
||||
}`);
|
||||
toolbar.get_style_context().add_provider(
|
||||
provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
);
|
||||
|
||||
let copyButton = new Gtk.ToolButton({
|
||||
icon_name: 'edit-copy-symbolic',
|
||||
tooltip_text: _("Copy Error")
|
||||
});
|
||||
toolbar.add(copyButton);
|
||||
|
||||
copyButton.connect('clicked', w => {
|
||||
let clipboard = Gtk.Clipboard.get_default(w.get_display());
|
||||
let backticks = '```';
|
||||
clipboard.set_text(
|
||||
// markdown for pasting in gitlab issues
|
||||
`The settings of extension ${extension.uuid} had an error:\n${
|
||||
backticks}\n${exc}\n${backticks}\n\nStack trace:\n${
|
||||
backticks}\n${exc.stack}${backticks}\n`, -1
|
||||
);
|
||||
});
|
||||
|
||||
let spacing = new Gtk.SeparatorToolItem({ draw: false });
|
||||
toolbar.add(spacing);
|
||||
toolbar.child_set_property(spacing, "expand", true);
|
||||
|
||||
let urlButton = new Gtk.ToolButton({
|
||||
label: _("Homepage"),
|
||||
tooltip_text: _("Visit extension homepage"),
|
||||
no_show_all: true,
|
||||
visible: extension.metadata.url != null
|
||||
});
|
||||
toolbar.add(urlButton);
|
||||
|
||||
urlButton.connect('clicked', w => {
|
||||
let context = w.get_display().get_app_launch_context();
|
||||
Gio.AppInfo.launch_default_for_uri(extension.metadata.url, context);
|
||||
});
|
||||
|
||||
let expandedBox = new Gtk.Box({
|
||||
orientation: Gtk.Orientation.VERTICAL
|
||||
});
|
||||
expandedBox.add(textview);
|
||||
expandedBox.add(toolbar);
|
||||
|
||||
expander.add(expandedBox);
|
||||
|
||||
scroll.show_all();
|
||||
return scroll;
|
||||
}
|
||||
|
||||
_buildUI(app) {
|
||||
@@ -147,8 +229,12 @@ var Application = class {
|
||||
Gio.SettingsBindFlags.DEFAULT |
|
||||
Gio.SettingsBindFlags.INVERT_BOOLEAN);
|
||||
|
||||
this._mainStack = new Gtk.Stack({
|
||||
transition_type: Gtk.StackTransitionType.CROSSFADE
|
||||
});
|
||||
this._window.add(this._mainStack);
|
||||
|
||||
let scroll = new Gtk.ScrolledWindow({ hscrollbar_policy: Gtk.PolicyType.NEVER });
|
||||
this._window.add(scroll);
|
||||
|
||||
this._extensionSelector = new Gtk.ListBox({ selection_mode: Gtk.SelectionMode.NONE });
|
||||
this._extensionSelector.set_sort_func(this._sortList.bind(this));
|
||||
@@ -156,6 +242,8 @@ var Application = class {
|
||||
|
||||
scroll.add(this._extensionSelector);
|
||||
|
||||
this._mainStack.add_named(scroll, 'listing');
|
||||
this._mainStack.add_named(new EmptyPlaceholder(), 'placeholder');
|
||||
|
||||
this._shellProxy = new GnomeShellProxy(Gio.DBus.session, 'org.gnome.Shell', '/org/gnome/Shell');
|
||||
this._shellProxy.connectSignal('ExtensionStatusChanged', (proxy, senderName, [uuid, state, error]) => {
|
||||
@@ -200,6 +288,11 @@ var Application = class {
|
||||
}
|
||||
|
||||
_extensionsLoaded() {
|
||||
if (this._extensionSelector.get_children().length > 0)
|
||||
this._mainStack.visible_child_name = 'listing';
|
||||
else
|
||||
this._mainStack.visible_child_name = 'placeholder';
|
||||
|
||||
if (this._startupUuid && this._extensionAvailable(this._startupUuid))
|
||||
this._selectExtension(this._startupUuid);
|
||||
this._startupUuid = null;
|
||||
@@ -239,6 +332,171 @@ var Application = class {
|
||||
}
|
||||
};
|
||||
|
||||
var Expander = GObject.registerClass({
|
||||
Properties: {
|
||||
'label': GObject.ParamSpec.string(
|
||||
'label', 'label', 'label',
|
||||
GObject.ParamFlags.READWRITE,
|
||||
null
|
||||
)
|
||||
}
|
||||
}, class Expander extends Gtk.Box {
|
||||
_init(params = {}) {
|
||||
this._labelText = null;
|
||||
|
||||
super._init(Object.assign(params, {
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
spacing: 0
|
||||
}));
|
||||
|
||||
this._frame = new Gtk.Frame({
|
||||
shadow_type: Gtk.ShadowType.IN,
|
||||
hexpand: true
|
||||
});
|
||||
|
||||
let eventBox = new Gtk.EventBox();
|
||||
this._frame.add(eventBox);
|
||||
|
||||
let hbox = new Gtk.Box({
|
||||
spacing: 6,
|
||||
margin: 12
|
||||
});
|
||||
eventBox.add(hbox);
|
||||
|
||||
this._arrow = new Gtk.Image({
|
||||
icon_name: 'pan-end-symbolic'
|
||||
});
|
||||
hbox.add(this._arrow);
|
||||
|
||||
this._label = new Gtk.Label({ label: this._labelText });
|
||||
hbox.add(this._label);
|
||||
|
||||
this._revealer = new Gtk.Revealer();
|
||||
|
||||
this._childBin = new Gtk.Frame({
|
||||
shadow_type: Gtk.ShadowType.IN
|
||||
});
|
||||
this._revealer.add(this._childBin);
|
||||
|
||||
// Directly chain up to parent for internal children
|
||||
super.add(this._frame);
|
||||
super.add(this._revealer);
|
||||
|
||||
let provider = new Gtk.CssProvider();
|
||||
provider.load_from_data('* { border-top-width: 0; }');
|
||||
this._childBin.get_style_context().add_provider(
|
||||
provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
);
|
||||
|
||||
this._gesture = new Gtk.GestureMultiPress({
|
||||
widget: this._frame,
|
||||
button: 0,
|
||||
exclusive: true
|
||||
});
|
||||
this._gesture.connect('released', (gesture, nPress) => {
|
||||
if (nPress == 1)
|
||||
this._revealer.reveal_child = !this._revealer.reveal_child;
|
||||
});
|
||||
this._revealer.connect('notify::reveal-child', () => {
|
||||
if (this._revealer.reveal_child)
|
||||
this._arrow.icon_name = 'pan-down-symbolic';
|
||||
else
|
||||
this._arrow.icon_name = 'pan-end-symbolic';
|
||||
});
|
||||
}
|
||||
|
||||
get label() {
|
||||
return this._labelText;
|
||||
}
|
||||
|
||||
set label(text) {
|
||||
if (this._labelText == text)
|
||||
return;
|
||||
|
||||
if (this._label)
|
||||
this._label.label = text;
|
||||
this._labelText = text;
|
||||
this.notify('label');
|
||||
}
|
||||
|
||||
add(child) {
|
||||
// set expanded child
|
||||
this._childBin.get_children().forEach(c => {
|
||||
this._childBin.remove(c);
|
||||
});
|
||||
|
||||
if (child)
|
||||
this._childBin.add(child);
|
||||
}
|
||||
});
|
||||
|
||||
var EmptyPlaceholder = GObject.registerClass(
|
||||
class EmptyPlaceholder extends Gtk.Box {
|
||||
_init() {
|
||||
super._init({
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
spacing: 6,
|
||||
margin: 32
|
||||
});
|
||||
|
||||
let image = new Gtk.Image({
|
||||
icon_name: 'application-x-addon-symbolic',
|
||||
pixel_size: 96,
|
||||
visible: true,
|
||||
vexpand: true,
|
||||
valign: Gtk.Align.END
|
||||
});
|
||||
image.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
|
||||
this.add(image);
|
||||
|
||||
let label = new Gtk.Label({
|
||||
label: `<b><span size="x-large">${_("No Extensions Installed" )}</span></b>`,
|
||||
use_markup: true,
|
||||
visible: true
|
||||
});
|
||||
label.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
|
||||
this.add(label);
|
||||
|
||||
let appInfo = Gio.DesktopAppInfo.new('org.gnome.Software.desktop');
|
||||
|
||||
let desc = new Gtk.Label({
|
||||
label: _("Extensions can be installed through Software or <a href=\"https://extensions.gnome.org\">extensions.gnome.org</a>."),
|
||||
use_markup: true,
|
||||
wrap: true,
|
||||
justify: Gtk.Justification.CENTER,
|
||||
visible: true,
|
||||
max_width_chars: 50,
|
||||
hexpand: true,
|
||||
vexpand: (appInfo == null),
|
||||
halign: Gtk.Align.CENTER,
|
||||
valign: Gtk.Align.START
|
||||
});
|
||||
this.add(desc);
|
||||
|
||||
if (appInfo) {
|
||||
let button = new Gtk.Button({
|
||||
label: _("Browse in Software"),
|
||||
image: new Gtk.Image({
|
||||
icon_name: "org.gnome.Software-symbolic"
|
||||
}),
|
||||
always_show_image: true,
|
||||
margin_top: 12,
|
||||
visible: true,
|
||||
halign: Gtk.Align.CENTER,
|
||||
valign: Gtk.Align.START,
|
||||
vexpand: true
|
||||
});
|
||||
this.add(button);
|
||||
|
||||
button.connect('clicked', w => {
|
||||
let context = w.get_display().get_app_launch_context();
|
||||
appInfo.launch([], context);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var DescriptionLabel = GObject.registerClass(
|
||||
class DescriptionLabel extends Gtk.Label {
|
||||
vfunc_get_preferred_height_for_width(width) {
|
||||
|
@@ -10,6 +10,7 @@ const Animation = imports.ui.animation;
|
||||
const Batch = imports.gdm.batch;
|
||||
const GdmUtil = imports.gdm.util;
|
||||
const Params = imports.misc.params;
|
||||
const Shell = imports.gi.Shell;
|
||||
const ShellEntry = imports.ui.shellEntry;
|
||||
const Tweener = imports.ui.tweener;
|
||||
const UserWidget = imports.ui.userWidget;
|
||||
@@ -96,7 +97,7 @@ var AuthPrompt = class {
|
||||
x_align: St.Align.START });
|
||||
this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry',
|
||||
can_focus: true });
|
||||
ShellEntry.addContextMenu(this._entry, { isPassword: true });
|
||||
ShellEntry.addContextMenu(this._entry, { isPassword: true, actionMode: Shell.ActionMode.NONE });
|
||||
|
||||
this.actor.add(this._entry,
|
||||
{ expand: true,
|
||||
|
@@ -23,7 +23,6 @@ const Gdm = imports.gi.Gdm;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Pango = imports.gi.Pango;
|
||||
const Shell = imports.gi.Shell;
|
||||
@@ -161,8 +160,8 @@ Signals.addSignalMethods(UserListItem.prototype);
|
||||
var UserList = class {
|
||||
constructor() {
|
||||
this.actor = new St.ScrollView({ style_class: 'login-dialog-user-list-view'});
|
||||
this.actor.set_policy(Gtk.PolicyType.NEVER,
|
||||
Gtk.PolicyType.AUTOMATIC);
|
||||
this.actor.set_policy(St.PolicyType.NEVER,
|
||||
St.PolicyType.AUTOMATIC);
|
||||
|
||||
this._box = new St.BoxLayout({ vertical: true,
|
||||
style_class: 'login-dialog-user-list',
|
||||
@@ -183,7 +182,7 @@ var UserList = class {
|
||||
if (global.stage.get_key_focus() != this.actor)
|
||||
return;
|
||||
|
||||
let focusSet = this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
let focusSet = this.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
||||
if (!focusSet) {
|
||||
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
|
||||
this._moveFocusToItems();
|
||||
@@ -335,7 +334,8 @@ var SessionMenuButton = class {
|
||||
this._button.remove_style_pseudo_class('active');
|
||||
});
|
||||
|
||||
this._manager = new PopupMenu.PopupMenuManager({ actor: this._button });
|
||||
this._manager = new PopupMenu.PopupMenuManager({ actor: this._button },
|
||||
{ actionMode: Shell.ActionMode.NONE });
|
||||
this._manager.addMenu(this._menu);
|
||||
|
||||
this._button.connect('clicked', () => { this._menu.toggle(); });
|
||||
@@ -473,8 +473,8 @@ var LoginDialog = GObject.registerClass({
|
||||
|
||||
this._bannerView = new St.ScrollView({ style_class: 'login-dialog-banner-view',
|
||||
opacity: 0,
|
||||
vscrollbar_policy: Gtk.PolicyType.AUTOMATIC,
|
||||
hscrollbar_policy: Gtk.PolicyType.NEVER });
|
||||
vscrollbar_policy: St.PolicyType.AUTOMATIC,
|
||||
hscrollbar_policy: St.PolicyType.NEVER });
|
||||
this.add_child(this._bannerView);
|
||||
|
||||
let bannerBox = new St.BoxLayout({ vertical: true });
|
||||
|
@@ -78,11 +78,11 @@
|
||||
<file>ui/overview.js</file>
|
||||
<file>ui/overviewControls.js</file>
|
||||
<file>ui/padOsd.js</file>
|
||||
<file>ui/pageIndicators.js</file>
|
||||
<file>ui/panel.js</file>
|
||||
<file>ui/panelMenu.js</file>
|
||||
<file>ui/pointerWatcher.js</file>
|
||||
<file>ui/popupMenu.js</file>
|
||||
<file>ui/remoteMenu.js</file>
|
||||
<file>ui/remoteSearch.js</file>
|
||||
<file>ui/runDialog.js</file>
|
||||
<file>ui/screenShield.js</file>
|
||||
|
@@ -3,6 +3,7 @@
|
||||
// Common utils for the extension system and the extension
|
||||
// preferences tool
|
||||
|
||||
const Gettext = imports.gettext;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
@@ -63,6 +64,66 @@ function getCurrentExtension() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* initTranslations:
|
||||
* @domain: (optional): the gettext domain to use
|
||||
*
|
||||
* Initialize Gettext to load translations from extensionsdir/locale.
|
||||
* If @domain is not provided, it will be taken from metadata['gettext-domain']
|
||||
*/
|
||||
function initTranslations(domain) {
|
||||
let extension = getCurrentExtension();
|
||||
|
||||
if (!extension)
|
||||
throw new Error('initTranslations() can only be called from extensions');
|
||||
|
||||
domain = domain || extension.metadata['gettext-domain'];
|
||||
|
||||
// Expect USER extensions to have a locale/ subfolder, otherwise assume a
|
||||
// SYSTEM extension that has been installed in the same prefix as the shell
|
||||
let localeDir = extension.dir.get_child('locale');
|
||||
if (localeDir.query_exists(null))
|
||||
Gettext.bindtextdomain(domain, localeDir.get_path());
|
||||
else
|
||||
Gettext.bindtextdomain(domain, Config.LOCALEDIR);
|
||||
}
|
||||
|
||||
/**
|
||||
* getSettings:
|
||||
* @schema: (optional): the GSettings schema id
|
||||
*
|
||||
* Builds and returns a GSettings schema for @schema, using schema files
|
||||
* in extensionsdir/schemas. If @schema is omitted, it is taken from
|
||||
* metadata['settings-schema'].
|
||||
*/
|
||||
function getSettings(schema) {
|
||||
let extension = getCurrentExtension();
|
||||
|
||||
if (!extension)
|
||||
throw new Error('getSettings() can only be called from extensions');
|
||||
|
||||
schema = schema || extension.metadata['settings-schema'];
|
||||
|
||||
const GioSSS = Gio.SettingsSchemaSource;
|
||||
|
||||
// Expect USER extensions to have a schemas/ subfolder, otherwise assume a
|
||||
// SYSTEM extension that has been installed in the same prefix as the shell
|
||||
let schemaDir = extension.dir.get_child('schemas');
|
||||
let schemaSource;
|
||||
if (schemaDir.query_exists(null))
|
||||
schemaSource = GioSSS.new_from_directory(schemaDir.get_path(),
|
||||
GioSSS.get_default(),
|
||||
false);
|
||||
else
|
||||
schemaSource = GioSSS.get_default();
|
||||
|
||||
let schemaObj = schemaSource.lookup(schema, true);
|
||||
if (!schemaObj)
|
||||
throw new Error(`Schema ${schema} could not be found for extension ${extension.metadata.uuid}. Please check your installation`);
|
||||
|
||||
return new Gio.Settings({ settings_schema: schemaObj });
|
||||
}
|
||||
|
||||
/**
|
||||
* versionCheck:
|
||||
* @required: an array of versions we're compatible with
|
||||
|
@@ -160,7 +160,7 @@ var ObjectManager = class {
|
||||
try {
|
||||
initable.init_finish(result);
|
||||
} catch(e) {
|
||||
logError(e, 'could not initialize object manager for object ' + params.name);
|
||||
logError(e, 'could not initialize object manager for object ' + this._serviceName);
|
||||
|
||||
this._tryToCompleteLoad();
|
||||
return;
|
||||
@@ -281,8 +281,8 @@ var ObjectManager = class {
|
||||
let object = this._objects[objectPaths];
|
||||
|
||||
let interfaceNames = Object.keys(object);
|
||||
for (let j = 0; i < interfaceNames.length; i++) {
|
||||
let interfaceName = interfaceNames[i];
|
||||
for (let j = 0; j < interfaceNames.length; j++) {
|
||||
let interfaceName = interfaceNames[j];
|
||||
if (object[interfaceName])
|
||||
proxies.push(object(interfaceName));
|
||||
}
|
||||
|
@@ -61,8 +61,8 @@ var WeatherClient = class {
|
||||
this.emit('changed');
|
||||
});
|
||||
|
||||
this._weatherAppMon = new Util.AppSettingsMonitor('org.gnome.Weather.Application.desktop',
|
||||
'org.gnome.Weather.Application');
|
||||
this._weatherAppMon = new Util.AppSettingsMonitor('org.gnome.Weather.desktop',
|
||||
'org.gnome.Weather');
|
||||
this._weatherAppMon.connect('available-changed', () => { this.emit('changed'); });
|
||||
this._weatherAppMon.watchSetting('automatic-location',
|
||||
this._onAutomaticLocationChanged.bind(this));
|
||||
@@ -234,7 +234,7 @@ var WeatherClient = class {
|
||||
if (table != 'gnome' || id != 'geolocation')
|
||||
return;
|
||||
|
||||
let permission = perms['org.gnome.Weather.Application'] || ['NONE'];
|
||||
let permission = perms['org.gnome.Weather'] || ['NONE'];
|
||||
let [accuracy] = permission;
|
||||
this._weatherAuthorized = accuracy != 'NONE';
|
||||
|
||||
|
@@ -65,7 +65,7 @@ let WINDOW_CONFIGS = [
|
||||
{ width: 640, height: 480, alpha: true, maximized: false, count: 10, metric: 'overviewFps10Alpha' }
|
||||
];
|
||||
|
||||
function run() {
|
||||
function *run() {
|
||||
Scripting.defineScriptEvent("overviewShowStart", "Starting to show the overview");
|
||||
Scripting.defineScriptEvent("overviewShowDone", "Overview finished showing");
|
||||
Scripting.defineScriptEvent("afterShowHide", "After a show/hide cycle for the overview");
|
||||
|
@@ -89,7 +89,7 @@ function extractBootTimestamp() {
|
||||
return result;
|
||||
}
|
||||
|
||||
function run() {
|
||||
function *run() {
|
||||
Scripting.defineScriptEvent("desktopShown", "Finished initial animation");
|
||||
Scripting.defineScriptEvent("overviewShowStart", "Starting to show the overview");
|
||||
Scripting.defineScriptEvent("overviewShowDone", "Overview finished showing");
|
||||
|
@@ -4,7 +4,6 @@ const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const Meta = imports.gi.Meta;
|
||||
@@ -20,6 +19,7 @@ const IconGrid = imports.ui.iconGrid;
|
||||
const Main = imports.ui.main;
|
||||
const Overview = imports.ui.overview;
|
||||
const OverviewControls = imports.ui.overviewControls;
|
||||
const PageIndicators = imports.ui.pageIndicators;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Tweener = imports.ui.tweener;
|
||||
const Workspace = imports.ui.workspace;
|
||||
@@ -175,7 +175,7 @@ class BaseAppView {
|
||||
|
||||
_selectAppInternal(id) {
|
||||
if (this._items[id])
|
||||
this._items[id].actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
this._items[id].actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
||||
else
|
||||
log('No such application ' + id);
|
||||
}
|
||||
@@ -246,116 +246,6 @@ class BaseAppView {
|
||||
};
|
||||
Signals.addSignalMethods(BaseAppView.prototype);
|
||||
|
||||
var PageIndicatorsActor = GObject.registerClass(
|
||||
class PageIndicatorsActor extends St.BoxLayout {
|
||||
_init() {
|
||||
super._init({ style_class: 'page-indicators',
|
||||
vertical: true,
|
||||
x_expand: true, y_expand: true,
|
||||
x_align: Clutter.ActorAlign.END,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
reactive: true,
|
||||
clip_to_allocation: true });
|
||||
}
|
||||
|
||||
vfunc_get_preferred_height(forWidth) {
|
||||
// We want to request the natural height of all our children as our
|
||||
// natural height, so we chain up to St.BoxLayout, but we only request 0
|
||||
// as minimum height, since it's not that important if some indicators
|
||||
// are not shown
|
||||
let [, natHeight] = super.vfunc_get_preferred_height(forWidth);
|
||||
return [0, natHeight];
|
||||
}
|
||||
});
|
||||
|
||||
class PageIndicators {
|
||||
constructor() {
|
||||
this.actor = new PageIndicatorsActor();
|
||||
this._nPages = 0;
|
||||
this._currentPage = undefined;
|
||||
|
||||
this.actor.connect('notify::mapped', () => {
|
||||
this.animateIndicators(IconGrid.AnimationDirection.IN);
|
||||
});
|
||||
}
|
||||
|
||||
setNPages(nPages) {
|
||||
if (this._nPages == nPages)
|
||||
return;
|
||||
|
||||
let diff = nPages - this._nPages;
|
||||
if (diff > 0) {
|
||||
for (let i = 0; i < diff; i++) {
|
||||
let pageIndex = this._nPages + i;
|
||||
let indicator = new St.Button({ style_class: 'page-indicator',
|
||||
button_mask: St.ButtonMask.ONE |
|
||||
St.ButtonMask.TWO |
|
||||
St.ButtonMask.THREE,
|
||||
toggle_mode: true,
|
||||
checked: pageIndex == this._currentPage });
|
||||
indicator.child = new St.Widget({ style_class: 'page-indicator-icon' });
|
||||
indicator.connect('clicked', () => {
|
||||
this.emit('page-activated', pageIndex);
|
||||
});
|
||||
this.actor.add_actor(indicator);
|
||||
}
|
||||
} else {
|
||||
let children = this.actor.get_children().splice(diff);
|
||||
for (let i = 0; i < children.length; i++)
|
||||
children[i].destroy();
|
||||
}
|
||||
this._nPages = nPages;
|
||||
this.actor.visible = (this._nPages > 1);
|
||||
}
|
||||
|
||||
setCurrentPage(currentPage) {
|
||||
this._currentPage = currentPage;
|
||||
|
||||
let children = this.actor.get_children();
|
||||
for (let i = 0; i < children.length; i++)
|
||||
children[i].set_checked(i == this._currentPage);
|
||||
}
|
||||
|
||||
animateIndicators(animationDirection) {
|
||||
if (!this.actor.mapped)
|
||||
return;
|
||||
|
||||
let children = this.actor.get_children();
|
||||
if (children.length == 0)
|
||||
return;
|
||||
|
||||
for (let i = 0; i < this._nPages; i++)
|
||||
Tweener.removeTweens(children[i]);
|
||||
|
||||
let offset;
|
||||
if (this.actor.get_text_direction() == Clutter.TextDirection.RTL)
|
||||
offset = -children[0].width;
|
||||
else
|
||||
offset = children[0].width;
|
||||
|
||||
let isAnimationIn = animationDirection == IconGrid.AnimationDirection.IN;
|
||||
let delay = isAnimationIn ? INDICATORS_ANIMATION_DELAY :
|
||||
INDICATORS_ANIMATION_DELAY_OUT;
|
||||
let baseTime = isAnimationIn ? INDICATORS_BASE_TIME : INDICATORS_BASE_TIME_OUT;
|
||||
let totalAnimationTime = baseTime + delay * this._nPages;
|
||||
let maxTime = isAnimationIn ? INDICATORS_ANIMATION_MAX_TIME :
|
||||
INDICATORS_ANIMATION_MAX_TIME_OUT;
|
||||
if (totalAnimationTime > maxTime)
|
||||
delay -= (totalAnimationTime - maxTime) / this._nPages;
|
||||
|
||||
for (let i = 0; i < this._nPages; i++) {
|
||||
children[i].translation_x = isAnimationIn ? offset : 0;
|
||||
Tweener.addTween(children[i],
|
||||
{ translation_x: isAnimationIn ? 0 : offset,
|
||||
time: baseTime + delay * i,
|
||||
transition: 'easeInOutQuad',
|
||||
delay: isAnimationIn ? VIEWS_SWITCH_ANIMATION_DELAY : 0
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(PageIndicators.prototype);
|
||||
|
||||
var AllView = class AllView extends BaseAppView {
|
||||
constructor() {
|
||||
super({ usePagination: true }, null);
|
||||
@@ -370,17 +260,17 @@ var AllView = class AllView extends BaseAppView {
|
||||
x_expand:true, y_expand:true });
|
||||
this.actor.add_actor(this._scrollView);
|
||||
|
||||
this._scrollView.set_policy(Gtk.PolicyType.NEVER,
|
||||
Gtk.PolicyType.EXTERNAL);
|
||||
this._scrollView.set_policy(St.PolicyType.NEVER,
|
||||
St.PolicyType.EXTERNAL);
|
||||
this._adjustment = this._scrollView.vscroll.adjustment;
|
||||
|
||||
this._pageIndicators = new PageIndicators();
|
||||
this._pageIndicators = new PageIndicators.AnimatedPageIndicators();
|
||||
this._pageIndicators.connect('page-activated',
|
||||
(indicators, pageIndex) => {
|
||||
this.goToPage(pageIndex);
|
||||
});
|
||||
this._pageIndicators.actor.connect('scroll-event', this._onScroll.bind(this));
|
||||
this.actor.add_actor(this._pageIndicators.actor);
|
||||
this._pageIndicators.connect('scroll-event', this._onScroll.bind(this));
|
||||
this.actor.add_actor(this._pageIndicators);
|
||||
|
||||
this.folderIcons = [];
|
||||
|
||||
@@ -487,15 +377,21 @@ var AllView = class AllView extends BaseAppView {
|
||||
});
|
||||
}
|
||||
|
||||
getAppInfos() {
|
||||
return this._appInfoList;
|
||||
}
|
||||
|
||||
_loadApps() {
|
||||
let apps = Gio.AppInfo.get_all().filter(appInfo => {
|
||||
this._appInfoList = Shell.AppSystem.get_default().get_installed().filter(appInfo => {
|
||||
try {
|
||||
let id = appInfo.get_id(); // catch invalid file encodings
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
return appInfo.should_show();
|
||||
}).map(app => app.get_id());
|
||||
});
|
||||
|
||||
let apps = this._appInfoList.map(app => app.get_id());
|
||||
|
||||
let appSys = Shell.AppSystem.get_default();
|
||||
|
||||
@@ -1118,7 +1014,7 @@ var FolderView = class FolderView extends BaseAppView {
|
||||
this._grid.x_expand = true;
|
||||
|
||||
this.actor = new St.ScrollView({ overlay_scrollbars: true });
|
||||
this.actor.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
this.actor.set_policy(St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
|
||||
let scrollableContainer = new St.BoxLayout({ vertical: true, reactive: true });
|
||||
scrollableContainer.add_actor(this._grid);
|
||||
this.actor.add_actor(scrollableContainer);
|
||||
@@ -1298,15 +1194,13 @@ var FolderIcon = class FolderIcon {
|
||||
folderApps.forEach(addAppId);
|
||||
|
||||
let folderCategories = this._folder.get_strv('categories');
|
||||
Gio.AppInfo.get_all().forEach(appInfo => {
|
||||
let appInfos = this._parentView.getAppInfos();
|
||||
appInfos.forEach(appInfo => {
|
||||
let appCategories = _getCategories(appInfo);
|
||||
if (!_listsIntersect(folderCategories, appCategories))
|
||||
return;
|
||||
|
||||
try {
|
||||
addAppId(appInfo.get_id()); // catch invalid file encodings
|
||||
} catch(e) {
|
||||
}
|
||||
addAppId(appInfo.get_id());
|
||||
});
|
||||
|
||||
this.actor.visible = this.view.getAllItems().length > 0;
|
||||
@@ -1464,18 +1358,18 @@ var AppFolderPopup = class AppFolderPopup {
|
||||
let isLtr = Clutter.get_default_text_direction() == Clutter.TextDirection.LTR;
|
||||
switch (event.get_key_symbol()) {
|
||||
case Clutter.Down:
|
||||
direction = Gtk.DirectionType.TAB_FORWARD;
|
||||
direction = St.DirectionType.TAB_FORWARD;
|
||||
break;
|
||||
case Clutter.Right:
|
||||
direction = isLtr ? Gtk.DirectionType.TAB_FORWARD :
|
||||
Gtk.DirectionType.TAB_BACKWARD;
|
||||
direction = isLtr ? St.DirectionType.TAB_FORWARD :
|
||||
St.DirectionType.TAB_BACKWARD;
|
||||
break;
|
||||
case Clutter.Up:
|
||||
direction = Gtk.DirectionType.TAB_BACKWARD;
|
||||
direction = St.DirectionType.TAB_BACKWARD;
|
||||
break;
|
||||
case Clutter.Left:
|
||||
direction = isLtr ? Gtk.DirectionType.TAB_BACKWARD :
|
||||
Gtk.DirectionType.TAB_FORWARD;
|
||||
direction = isLtr ? St.DirectionType.TAB_BACKWARD :
|
||||
St.DirectionType.TAB_FORWARD;
|
||||
break;
|
||||
default:
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
@@ -1686,7 +1580,7 @@ var AppIcon = class AppIcon {
|
||||
|
||||
_onKeyboardPopupMenu() {
|
||||
this.popupMenu();
|
||||
this._menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
this._menu.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
||||
}
|
||||
|
||||
getId() {
|
||||
|
@@ -36,13 +36,14 @@ const RENAMED_DESKTOP_IDS = {
|
||||
'gnome-software.desktop': 'org.gnome.Software.desktop',
|
||||
'gnome-terminal.desktop': 'org.gnome.Terminal.desktop',
|
||||
'gnome-tweaks.desktop': 'org.gnome.tweaks.desktop',
|
||||
'gnome-weather.desktop': 'org.gnome.Weather.Application.desktop',
|
||||
'gnome-weather.desktop': 'org.gnome.Weather.desktop',
|
||||
'gnomine.desktop': 'gnome-mines.desktop',
|
||||
'gnotravex.desktop': 'gnome-tetravex.desktop',
|
||||
'gnotski.desktop': 'gnome-klotski.desktop',
|
||||
'gtali.desktop': 'tali.desktop',
|
||||
'iagno.desktop': 'org.gnome.Iagno.desktop',
|
||||
'nautilus.desktop': 'org.gnome.Nautilus.desktop',
|
||||
'org.gnome.Weather.Application.desktop': 'org.gnome.Weather.desktop',
|
||||
'polari.desktop': 'org.gnome.Polari.desktop',
|
||||
'totem.desktop': 'org.gnome.Totem.desktop',
|
||||
'evince.desktop': 'org.gnome.Evince.desktop',
|
||||
@@ -153,7 +154,7 @@ class AppFavorites {
|
||||
}
|
||||
|
||||
_removeFavorite(appId) {
|
||||
if (!appId in this._favorites)
|
||||
if (!(appId in this._favorites))
|
||||
return false;
|
||||
|
||||
let ids = this._getIds().filter(id => id != appId);
|
||||
|
@@ -3,7 +3,6 @@
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const St = imports.gi.St;
|
||||
const Signals = imports.signals;
|
||||
const Shell = imports.gi.Shell;
|
||||
@@ -1083,7 +1082,7 @@ var CalendarMessageList = class CalendarMessageList {
|
||||
overlay_scrollbars: true,
|
||||
x_expand: true, y_expand: true,
|
||||
x_fill: true, y_fill: true });
|
||||
this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
this._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
|
||||
box.add_actor(this._scrollView);
|
||||
|
||||
this._clearButton = new St.Button({ style_class: 'message-list-clear-button button',
|
||||
|
@@ -111,10 +111,10 @@ var NetworkSecretDialog = class extends ModalDialog.ModalDialog {
|
||||
descriptionLabel.clutter_text.line_wrap = true;
|
||||
descriptionLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
||||
|
||||
messageBox.add(descriptionLabel,
|
||||
{ y_fill: true,
|
||||
y_align: St.Align.START,
|
||||
expand: true });
|
||||
contentBox.messageBox.add(descriptionLabel,
|
||||
{ y_fill: true,
|
||||
y_align: St.Align.START,
|
||||
expand: true });
|
||||
}
|
||||
|
||||
this._okButton = { label: _("Connect"),
|
||||
@@ -705,14 +705,14 @@ var NetworkAgent = class {
|
||||
if (hints.indexOf('pin') != -1) {
|
||||
let gsmSetting = connection.get_setting_gsm();
|
||||
title = _("PIN code required");
|
||||
message = _("PIN code is needed for the mobile broadband device");
|
||||
body = _("PIN code is needed for the mobile broadband device");
|
||||
break;
|
||||
}
|
||||
// fall through
|
||||
case 'cdma':
|
||||
case 'bluetooth':
|
||||
title = _("Mobile broadband network password");
|
||||
message = _("A password is required to connect to “%s”.").format(connectionSetting.get_id());
|
||||
body = _("A password is required to connect to “%s”.").format(connectionSetting.get_id());
|
||||
break;
|
||||
default:
|
||||
log('Invalid connection type: ' + connectionType);
|
||||
|
@@ -4,7 +4,6 @@ const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Signals = imports.signals;
|
||||
@@ -821,8 +820,8 @@ var ChatNotificationBanner = class extends MessageTray.NotificationBanner {
|
||||
});
|
||||
|
||||
this._scrollArea = new St.ScrollView({ style_class: 'chat-scrollview vfade',
|
||||
vscrollbar_policy: Gtk.PolicyType.AUTOMATIC,
|
||||
hscrollbar_policy: Gtk.PolicyType.NEVER,
|
||||
vscrollbar_policy: St.PolicyType.AUTOMATIC,
|
||||
hscrollbar_policy: St.PolicyType.NEVER,
|
||||
visible: this.expanded });
|
||||
this._contentArea = new St.BoxLayout({ style_class: 'chat-body',
|
||||
vertical: true });
|
||||
|
@@ -1,7 +1,6 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
@@ -58,7 +57,7 @@ var CtrlAltTabManager = class CtrlAltTabManager {
|
||||
if (item.focusCallback)
|
||||
item.focusCallback(timestamp);
|
||||
else
|
||||
item.root.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
item.root.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
||||
}
|
||||
|
||||
// Sort the items into a consistent order; panel first, tray last,
|
||||
|
@@ -4,7 +4,6 @@ const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GnomeDesktop = imports.gi.GnomeDesktop;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const GWeather = imports.gi.GWeather;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Pango = imports.gi.Pango;
|
||||
@@ -23,6 +22,8 @@ const Calendar = imports.ui.calendar;
|
||||
const Weather = imports.misc.weather;
|
||||
const System = imports.system;
|
||||
|
||||
const MAX_FORECASTS = 5;
|
||||
|
||||
function _isToday(date) {
|
||||
let now = new Date();
|
||||
return now.getYear() == date.getYear() &&
|
||||
@@ -155,18 +156,27 @@ var WorldClocksSection = class WorldClocksSection {
|
||||
let label = new St.Label({ style_class: 'world-clocks-city',
|
||||
text: name,
|
||||
x_align: Clutter.ActorAlign.START,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
x_expand: true });
|
||||
|
||||
let time = new St.Label({ style_class: 'world-clocks-time',
|
||||
x_align: Clutter.ActorAlign.END,
|
||||
x_expand: true });
|
||||
let time = new St.Label({ style_class: 'world-clocks-time' });
|
||||
|
||||
let offset = l.get_timezone().get_offset() / 60.;
|
||||
let fmt = (Math.trunc(offset) == offset) ? '%s%.0f' : '%s%.1f';
|
||||
let prefix = (offset >= 0) ? '+' : '-';
|
||||
let tz = new St.Label({ style_class: 'world-clocks-timezone',
|
||||
text: fmt.format(prefix, Math.abs(offset)),
|
||||
x_align: Clutter.ActorAlign.END,
|
||||
y_align: Clutter.ActorAlign.CENTER });
|
||||
|
||||
if (this._grid.text_direction == Clutter.TextDirection.RTL) {
|
||||
layout.attach(time, 0, i + 1, 1, 1);
|
||||
layout.attach(label, 1, i + 1, 1, 1);
|
||||
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;
|
||||
@@ -217,110 +227,114 @@ var WeatherSection = class WeatherSection {
|
||||
|
||||
this.actor.child = box;
|
||||
|
||||
box.add_child(new St.Label({ style_class: 'weather-header',
|
||||
x_align: Clutter.ActorAlign.START,
|
||||
text: _("Weather") }));
|
||||
let titleBox = new St.BoxLayout();
|
||||
titleBox.add_child(new St.Label({ style_class: 'weather-header',
|
||||
x_align: Clutter.ActorAlign.START,
|
||||
x_expand: true,
|
||||
text: _("Weather") }));
|
||||
box.add_child(titleBox);
|
||||
|
||||
this._conditionsLabel = new St.Label({ style_class: 'weather-conditions',
|
||||
x_align: Clutter.ActorAlign.START });
|
||||
this._conditionsLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
||||
this._conditionsLabel.clutter_text.line_wrap = true;
|
||||
box.add_child(this._conditionsLabel);
|
||||
this._titleLocation = new St.Label({ style_class: 'weather-header location',
|
||||
x_align: Clutter.ActorAlign.END });
|
||||
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._weatherClient.connect('changed', this._sync.bind(this));
|
||||
this._sync();
|
||||
}
|
||||
|
||||
_getSummary(info, capitalize=false) {
|
||||
let options = capitalize ? GWeather.FormatOptions.SENTENCE_CAPITALIZATION
|
||||
: GWeather.FormatOptions.NO_CAPITALIZATION;
|
||||
|
||||
let [ok, phenomenon, qualifier] = info.get_value_conditions();
|
||||
if (ok)
|
||||
return new GWeather.Conditions({ significant: true,
|
||||
phenomenon,
|
||||
qualifier }).to_string_full(options);
|
||||
|
||||
let [, sky] = info.get_value_sky();
|
||||
return GWeather.Sky.to_string_full(sky, options);
|
||||
}
|
||||
|
||||
_sameSummary(info1, info2) {
|
||||
let [ok1, phenom1, qualifier1] = info1.get_value_conditions();
|
||||
let [ok2, phenom2, qualifier2] = info2.get_value_conditions();
|
||||
if (ok1 || ok2)
|
||||
return ok1 == ok2 && phenom1 == phenom2 && qualifier1 == qualifier2;
|
||||
|
||||
let [, sky1] = info1.get_value_sky();
|
||||
let [, sky2] = info2.get_value_sky();
|
||||
return sky1 == sky2;
|
||||
}
|
||||
|
||||
_getSummaryText() {
|
||||
_getInfos() {
|
||||
let info = this._weatherClient.info;
|
||||
let forecasts = info.get_forecast_list();
|
||||
if (forecasts.length == 0) // No forecasts, just current conditions
|
||||
return '%s.'.format(this._getSummary(info, true));
|
||||
|
||||
let current = info;
|
||||
let infos = [info];
|
||||
for (let i = 0; i < forecasts.length; i++) {
|
||||
let [ok, timestamp] = forecasts[i].get_value_update();
|
||||
if (!_isToday(new Date(timestamp * 1000)))
|
||||
let datetime = new Date(timestamp * 1000);
|
||||
if (!_isToday(datetime))
|
||||
continue; // Ignore forecasts from other days
|
||||
|
||||
if (this._sameSummary(current, forecasts[i]))
|
||||
continue; // Ignore consecutive runs of equal summaries
|
||||
[ok, timestamp] = current.get_value_update();
|
||||
let currenttime = new Date(timestamp * 1000);
|
||||
if (currenttime.getHours() == datetime.getHours())
|
||||
continue; // Enforce a minimum interval of 1h
|
||||
|
||||
current = forecasts[i];
|
||||
if (infos.push(current) == 3)
|
||||
break; // Use a maximum of three summaries
|
||||
if (infos.push(current) == MAX_FORECASTS)
|
||||
break; // Use a maximum of five forecasts
|
||||
}
|
||||
|
||||
let fmt;
|
||||
switch(infos.length) {
|
||||
/* Translators: %s is a weather condition like "Clear sky"; see
|
||||
libgweather for the possible condition strings. If at all
|
||||
possible, the sentence should match the grammatical case etc. of
|
||||
the inserted conditions. */
|
||||
case 1: fmt = _("%s all day."); break;
|
||||
|
||||
/* Translators: %s is a weather condition like "Clear sky"; see
|
||||
libgweather for the possible condition strings. If at all
|
||||
possible, the sentence should match the grammatical case etc. of
|
||||
the inserted conditions. */
|
||||
case 2: fmt = _("%s, then %s later."); break;
|
||||
|
||||
/* Translators: %s is a weather condition like "Clear sky"; see
|
||||
libgweather for the possible condition strings. If at all
|
||||
possible, the sentence should match the grammatical case etc. of
|
||||
the inserted conditions. */
|
||||
case 3: fmt = _("%s, then %s, followed by %s later."); break;
|
||||
}
|
||||
let summaries = infos.map((info, i) => {
|
||||
let capitalize = i == 0 && fmt.startsWith('%s');
|
||||
return this._getSummary(info, capitalize);
|
||||
});
|
||||
return String.prototype.format.apply(fmt, summaries);
|
||||
return infos;
|
||||
}
|
||||
|
||||
_getLabelText() {
|
||||
if (!this._weatherClient.hasLocation)
|
||||
return _("Select a location…");
|
||||
_addForecasts() {
|
||||
let layout = this._forecastGrid.layout_manager;
|
||||
|
||||
if (this._weatherClient.loading)
|
||||
return _("Loading…");
|
||||
let infos = this._getInfos();
|
||||
if (this._forecastGrid.text_direction == Clutter.TextDirection.RTL)
|
||||
infos.reverse();
|
||||
|
||||
let col = 0;
|
||||
infos.forEach(fc => {
|
||||
let [ok, timestamp] = fc.get_value_update();
|
||||
let timeStr = Util.formatTime(new Date(timestamp * 1000), {
|
||||
timeOnly: true
|
||||
});
|
||||
|
||||
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({ style_class: 'weather-forecast-temp',
|
||||
text: fc.get_temp_summary(),
|
||||
x_align: Clutter.ActorAlign.CENTER });
|
||||
let time = new St.Label({ style_class: 'weather-forecast-time',
|
||||
text: timeStr,
|
||||
x_align: Clutter.ActorAlign.CENTER });
|
||||
|
||||
layout.attach(icon, col, 0, 1, 1);
|
||||
layout.attach(temp, col, 1, 1, 1);
|
||||
layout.attach(time, 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;
|
||||
if (info.is_valid())
|
||||
return this._getSummaryText() + ' ' +
|
||||
/* Translators: %s is a temperature with unit, e.g. "23℃" */
|
||||
_("Feels like %s.").format(info.get_apparent());
|
||||
this._titleLocation.text = info.get_location().get_name();
|
||||
|
||||
if (this._weatherClient.loading) {
|
||||
this._setStatusLabel(_("Loading…"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.is_valid()) {
|
||||
this._addForecasts();
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.network_error())
|
||||
return _("Go online for weather information");
|
||||
|
||||
return _("Weather information is currently unavailable");
|
||||
this._setStatusLabel(_("Go online for weather information"));
|
||||
else
|
||||
this._setStatusLabel(_("Weather information is currently unavailable"));
|
||||
}
|
||||
|
||||
_sync() {
|
||||
@@ -329,7 +343,9 @@ var WeatherSection = class WeatherSection {
|
||||
if (!this.actor.visible)
|
||||
return;
|
||||
|
||||
this._conditionsLabel.text = this._getLabelText();
|
||||
this._titleLocation.visible = this._weatherClient.hasLocation;
|
||||
|
||||
this._updateForecasts();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -378,13 +394,13 @@ class IndicatorPad extends St.Widget {
|
||||
super._init();
|
||||
}
|
||||
|
||||
vfunc_get_preferred_width(container, forHeight) {
|
||||
vfunc_get_preferred_width(forHeight) {
|
||||
if (this._source.visible)
|
||||
return this._source.get_preferred_width(forHeight);
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
vfunc_get_preferred_height(container, forWidth) {
|
||||
vfunc_get_preferred_height(forWidth) {
|
||||
if (this._source.visible)
|
||||
return this._source.get_preferred_height(forWidth);
|
||||
return [0, 0];
|
||||
@@ -515,7 +531,7 @@ class DateMenuButton extends PanelMenu.Button {
|
||||
this._displaysSection = new St.ScrollView({ style_class: 'datemenu-displays-section vfade',
|
||||
x_expand: true, x_fill: true,
|
||||
overlay_scrollbars: true });
|
||||
this._displaysSection.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
this._displaysSection.set_policy(St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
|
||||
vbox.add_actor(this._displaysSection);
|
||||
|
||||
let displaysBox = new St.BoxLayout({ vertical: true,
|
||||
|
94
js/ui/dnd.js
94
js/ui/dnd.js
@@ -2,7 +2,6 @@
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const St = imports.gi.St;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
@@ -109,6 +108,7 @@ var _Draggable = class _Draggable {
|
||||
this._dragCancellable = true;
|
||||
|
||||
this._eventsGrabbed = false;
|
||||
this._capturedEventId = 0;
|
||||
}
|
||||
|
||||
_onButtonPress(actor, event) {
|
||||
@@ -119,7 +119,7 @@ var _Draggable = class _Draggable {
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
|
||||
this._buttonDown = true;
|
||||
this._grabActor();
|
||||
this._grabActor(event.get_device());
|
||||
|
||||
let [stageX, stageY] = event.get_coords();
|
||||
this._dragStartX = stageX;
|
||||
@@ -136,10 +136,8 @@ var _Draggable = class _Draggable {
|
||||
if (Tweener.getTweenCount(actor))
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
|
||||
this._touchSequence = event.get_event_sequence();
|
||||
|
||||
this._buttonDown = true;
|
||||
this._grabActor();
|
||||
this._grabActor(event.get_device(), event.get_event_sequence());
|
||||
|
||||
let [stageX, stageY] = event.get_coords();
|
||||
this._dragStartX = stageX;
|
||||
@@ -148,19 +146,30 @@ var _Draggable = class _Draggable {
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
_grabDevice(actor) {
|
||||
let manager = Clutter.DeviceManager.get_default();
|
||||
let pointer = manager.get_core_device(Clutter.InputDeviceType.POINTER_DEVICE);
|
||||
|
||||
if (pointer && this._touchSequence)
|
||||
pointer.sequence_grab(this._touchSequence, actor);
|
||||
_grabDevice(actor, pointer, touchSequence) {
|
||||
if (touchSequence)
|
||||
pointer.sequence_grab(touchSequence, actor);
|
||||
else if (pointer)
|
||||
pointer.grab (actor);
|
||||
|
||||
this._grabbedDevice = pointer;
|
||||
this._touchSequence = touchSequence;
|
||||
|
||||
this._capturedEventId = global.stage.connect('captured-event', (actor, event) => {
|
||||
let device = event.get_device();
|
||||
if (device != this._grabbedDevice &&
|
||||
device.get_device_type() != Clutter.InputDeviceType.KEYBOARD_DEVICE)
|
||||
return Clutter.EVENT_STOP;
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
});
|
||||
}
|
||||
|
||||
_ungrabDevice() {
|
||||
if (this._capturedEventId != 0) {
|
||||
global.stage.disconnect(this._capturedEventId);
|
||||
this._capturedEventId = 0;
|
||||
}
|
||||
|
||||
if (this._touchSequence)
|
||||
this._grabbedDevice.sequence_ungrab (this._touchSequence);
|
||||
else
|
||||
@@ -170,8 +179,8 @@ var _Draggable = class _Draggable {
|
||||
this._grabbedDevice = null;
|
||||
}
|
||||
|
||||
_grabActor() {
|
||||
this._grabDevice(this.actor);
|
||||
_grabActor(device, touchSequence) {
|
||||
this._grabDevice(this.actor, device, touchSequence);
|
||||
this._onEventId = this.actor.connect('event',
|
||||
this._onEvent.bind(this));
|
||||
}
|
||||
@@ -185,11 +194,11 @@ var _Draggable = class _Draggable {
|
||||
this._onEventId = null;
|
||||
}
|
||||
|
||||
_grabEvents() {
|
||||
_grabEvents(device, touchSequence) {
|
||||
if (!this._eventsGrabbed) {
|
||||
this._eventsGrabbed = Main.pushModal(_getEventHandlerActor());
|
||||
if (this._eventsGrabbed)
|
||||
this._grabDevice(_getEventHandlerActor());
|
||||
this._grabDevice(_getEventHandlerActor(), device, touchSequence);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,14 +210,36 @@ var _Draggable = class _Draggable {
|
||||
}
|
||||
}
|
||||
|
||||
_eventIsRelease(event) {
|
||||
if (event.type() == Clutter.EventType.BUTTON_RELEASE) {
|
||||
let buttonMask = (Clutter.ModifierType.BUTTON1_MASK |
|
||||
Clutter.ModifierType.BUTTON2_MASK |
|
||||
Clutter.ModifierType.BUTTON3_MASK);
|
||||
/* We only obey the last button release from the device,
|
||||
* other buttons may get pressed/released during the DnD op.
|
||||
*/
|
||||
return (event.get_state() & buttonMask) == 0;
|
||||
} else if (event.type() == Clutter.EventType.TOUCH_END) {
|
||||
/* For touch, we only obey the pointer emulating sequence */
|
||||
return global.display.is_pointer_emulating_sequence(event.get_event_sequence());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_onEvent(actor, event) {
|
||||
let device = event.get_device();
|
||||
|
||||
if (this._grabbedDevice &&
|
||||
device != this._grabbedDevice &&
|
||||
device.get_device_type() != Clutter.InputDeviceType.KEYBOARD_DEVICE)
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
|
||||
// We intercept BUTTON_RELEASE event to know that the button was released in case we
|
||||
// didn't start the drag, to drop the draggable in case the drag was in progress, and
|
||||
// to complete the drag and ensure that whatever happens to be under the pointer does
|
||||
// not get triggered if the drag was cancelled with Esc.
|
||||
if (event.type() == Clutter.EventType.BUTTON_RELEASE ||
|
||||
(event.type() == Clutter.EventType.TOUCH_END &&
|
||||
global.display.is_pointer_emulating_sequence(event.get_event_sequence()))) {
|
||||
if (this._eventIsRelease(event)) {
|
||||
this._buttonDown = false;
|
||||
if (this._dragState == DragState.DRAGGING) {
|
||||
return this._dragActorDropped(event);
|
||||
@@ -268,7 +299,19 @@ var _Draggable = class _Draggable {
|
||||
* This function is useful to call if you've specified manualMode
|
||||
* for the draggable.
|
||||
*/
|
||||
startDrag(stageX, stageY, time, sequence) {
|
||||
startDrag(stageX, stageY, time, sequence, device) {
|
||||
if (device == undefined) {
|
||||
let event = Clutter.get_current_event();
|
||||
|
||||
if (event)
|
||||
device = event.get_device();
|
||||
|
||||
if (device == undefined) {
|
||||
let manager = Clutter.DeviceManager.get_default();
|
||||
device = manager.get_core_device(Clutter.InputDeviceType.POINTER_DEVICE);
|
||||
}
|
||||
}
|
||||
|
||||
currentDraggable = this;
|
||||
this._dragState = DragState.DRAGGING;
|
||||
|
||||
@@ -283,8 +326,7 @@ var _Draggable = class _Draggable {
|
||||
if (this._onEventId)
|
||||
this._ungrabActor();
|
||||
|
||||
this._touchSequence = sequence;
|
||||
this._grabEvents();
|
||||
this._grabEvents(device, sequence);
|
||||
global.display.set_cursor(Meta.Cursor.DND_IN_DRAG);
|
||||
|
||||
this._dragX = this._dragStartX = stageX;
|
||||
@@ -352,7 +394,8 @@ var _Draggable = class _Draggable {
|
||||
this._finishAnimation();
|
||||
|
||||
this._dragActor = null;
|
||||
this._dragState = DragState.CANCELLED;
|
||||
if (this._dragState == DragState.DRAGGING)
|
||||
this._dragState = DragState.CANCELLED;
|
||||
});
|
||||
this._dragOrigOpacity = this._dragActor.opacity;
|
||||
if (this._dragActorOpacity != undefined)
|
||||
@@ -398,10 +441,11 @@ var _Draggable = class _Draggable {
|
||||
let [stageX, stageY] = event.get_coords();
|
||||
|
||||
// See if the user has moved the mouse enough to trigger a drag
|
||||
let threshold = Gtk.Settings.get_default().gtk_dnd_drag_threshold;
|
||||
if ((Math.abs(stageX - this._dragStartX) > threshold ||
|
||||
let threshold = St.Settings.get().drag_threshold;
|
||||
if (!currentDraggable &&
|
||||
(Math.abs(stageX - this._dragStartX) > threshold ||
|
||||
Math.abs(stageY - this._dragStartY) > threshold)) {
|
||||
this.startDrag(stageX, stageY, event.get_time(), this._touchSequence);
|
||||
this.startDrag(stageX, stageY, event.get_time(), this._touchSequence, event.get_device());
|
||||
this._updateDragPosition(event);
|
||||
}
|
||||
|
||||
|
@@ -30,7 +30,7 @@ var EdgeDragAction = GObject.registerClass({
|
||||
return global.display.get_monitor_geometry(monitorIndex);
|
||||
}
|
||||
|
||||
vfunc_gesture_prepare(action, actor) {
|
||||
vfunc_gesture_prepare(actor) {
|
||||
if (this.get_n_current_points() == 0)
|
||||
return false;
|
||||
|
||||
@@ -46,7 +46,7 @@ var EdgeDragAction = GObject.registerClass({
|
||||
(this._side == St.Side.BOTTOM && y > monitorRect.y + monitorRect.height - EDGE_THRESHOLD));
|
||||
}
|
||||
|
||||
vfunc_gesture_progress(action, actor) {
|
||||
vfunc_gesture_progress(actor) {
|
||||
let [startX, startY] = this.get_press_coords(0);
|
||||
let [x, y] = this.get_motion_coords(0);
|
||||
let offsetX = Math.abs (x - startX);
|
||||
@@ -66,7 +66,7 @@ var EdgeDragAction = GObject.registerClass({
|
||||
return true;
|
||||
}
|
||||
|
||||
vfunc_gesture_end(action, actor) {
|
||||
vfunc_gesture_end(actor) {
|
||||
let [startX, startY] = this.get_press_coords(0);
|
||||
let [x, y] = this.get_motion_coords(0);
|
||||
let monitorRect = this._getMonitorRect(startX, startY);
|
||||
|
@@ -22,7 +22,6 @@ const AccountsService = imports.gi.AccountsService;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Pango = imports.gi.Pango;
|
||||
const Polkit = imports.gi.Polkit;
|
||||
const St = imports.gi.St;
|
||||
@@ -320,7 +319,7 @@ var EndSessionDialog = class EndSessionDialog extends ModalDialog.ModalDialog {
|
||||
messageLayout.add(this._batteryWarning);
|
||||
|
||||
this._scrollView = new St.ScrollView({ style_class: 'end-session-dialog-list' });
|
||||
this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
this._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
|
||||
this.contentLayout.add(this._scrollView,
|
||||
{ x_fill: true,
|
||||
y_fill: true });
|
||||
|
@@ -4,7 +4,6 @@ const Config = imports.misc.config;
|
||||
|
||||
imports.gi.versions.Clutter = Config.LIBMUTTER_API_VERSION;
|
||||
imports.gi.versions.Gio = '2.0';
|
||||
imports.gi.versions.Gdk = '3.0';
|
||||
imports.gi.versions.GdkPixbuf = '2.0';
|
||||
imports.gi.versions.Gtk = '3.0';
|
||||
imports.gi.versions.TelepathyGLib = '0.12';
|
||||
|
@@ -1,7 +1,6 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
@@ -187,7 +186,7 @@ var GrabHelper = class GrabHelper {
|
||||
if (params.focus) {
|
||||
params.focus.grab_key_focus();
|
||||
} else if (newFocus && hadFocus) {
|
||||
if (!newFocus.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false))
|
||||
if (!newFocus.navigate_focus(null, St.DirectionType.TAB_FORWARD, false))
|
||||
newFocus.grab_key_focus();
|
||||
}
|
||||
|
||||
|
@@ -236,7 +236,7 @@ var CandidatePopup = class CandidatePopup {
|
||||
|
||||
let indexes = [];
|
||||
let indexLabel;
|
||||
for (let i = 0; indexLabel = lookupTable.get_label(i); ++i)
|
||||
for (let i = 0; (indexLabel = lookupTable.get_label(i)); ++i)
|
||||
indexes.push(indexLabel.get_text());
|
||||
|
||||
Main.keyboard.resetSuggestions();
|
||||
@@ -295,7 +295,7 @@ var CandidatePopup = class CandidatePopup {
|
||||
|
||||
_setTextAttributes(clutterText, ibusAttrList) {
|
||||
let attr;
|
||||
for (let i = 0; attr = ibusAttrList.get(i); ++i)
|
||||
for (let i = 0; (attr = ibusAttrList.get(i)); ++i)
|
||||
if (attr.get_attr_type() == IBus.AttrType.BACKGROUND)
|
||||
clutterText.set_selection(attr.get_start_index(), attr.get_end_index());
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
@@ -670,7 +669,7 @@ var IconGrid = GObject.registerClass({
|
||||
}
|
||||
|
||||
addItem(item, index) {
|
||||
if (!item.icon instanceof BaseIcon)
|
||||
if (!(item.icon instanceof BaseIcon))
|
||||
throw new Error('Only items with a BaseIcon icon property can be added to IconGrid');
|
||||
|
||||
this._items.push(item);
|
||||
@@ -906,10 +905,8 @@ var PaginatedIconGrid = GObject.registerClass({
|
||||
getItemPage(item) {
|
||||
let children = this._getVisibleChildren();
|
||||
let index = children.indexOf(item);
|
||||
if (index == -1) {
|
||||
if (index == -1)
|
||||
throw new Error('Item not found.');
|
||||
return 0;
|
||||
}
|
||||
return Math.floor(index / this._childrenPerPage);
|
||||
}
|
||||
|
||||
@@ -924,10 +921,9 @@ var PaginatedIconGrid = GObject.registerClass({
|
||||
openExtraSpace(sourceItem, side, nRows) {
|
||||
let children = this._getVisibleChildren();
|
||||
let index = children.indexOf(sourceItem.actor);
|
||||
if (index == -1) {
|
||||
if (index == -1)
|
||||
throw new Error('Item not found.');
|
||||
return;
|
||||
}
|
||||
|
||||
let pageIndex = Math.floor(index / this._childrenPerPage);
|
||||
let pageOffset = pageIndex * this._childrenPerPage;
|
||||
|
||||
@@ -959,8 +955,8 @@ var PaginatedIconGrid = GObject.registerClass({
|
||||
this._translatedChildren = [];
|
||||
this.emit('space-opened');
|
||||
} else {
|
||||
this._translateChildren(childrenUp, Gtk.DirectionType.UP, nRowsUp);
|
||||
this._translateChildren(childrenDown, Gtk.DirectionType.DOWN, nRowsDown);
|
||||
this._translateChildren(childrenUp, St.DirectionType.UP, nRowsUp);
|
||||
this._translateChildren(childrenDown, St.DirectionType.DOWN, nRowsDown);
|
||||
this._translatedChildren = childrenUp.concat(childrenDown);
|
||||
}
|
||||
}
|
||||
@@ -970,7 +966,7 @@ var PaginatedIconGrid = GObject.registerClass({
|
||||
if (translationY == 0)
|
||||
return;
|
||||
|
||||
if (direction == Gtk.DirectionType.UP)
|
||||
if (direction == St.DirectionType.UP)
|
||||
translationY *= -1;
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
|
@@ -8,7 +8,7 @@ const KEYBOARD_A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard';
|
||||
const KEY_STICKY_KEYS_ENABLED = 'stickykeys-enable';
|
||||
const KEY_SLOW_KEYS_ENABLED = 'slowkeys-enable';
|
||||
|
||||
var KbdA11yDialog = new GObject.registerClass(
|
||||
var KbdA11yDialog = GObject.registerClass(
|
||||
class KbdA11yDialog extends GObject.Object {
|
||||
_init() {
|
||||
super._init();
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
const Atspi = imports.gi.Atspi;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GObject = imports.gi.GObject;
|
||||
@@ -16,12 +15,15 @@ const IBusManager = imports.misc.ibusManager;
|
||||
const BoxPointer = imports.ui.boxpointer;
|
||||
const Layout = imports.ui.layout;
|
||||
const Main = imports.ui.main;
|
||||
const PageIndicators = imports.ui.pageIndicators;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Tweener = imports.ui.tweener;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
var KEYBOARD_REST_TIME = Layout.KEYBOARD_ANIMATION_TIME * 2 * 1000;
|
||||
var KEY_LONG_PRESS_TIME = 250;
|
||||
var PANEL_SWITCH_ANIMATION_TIME = 0.5;
|
||||
var PANEL_SWITCH_RELATIVE_DISTANCE = 1 / 3; /* A third of the actor width */
|
||||
|
||||
const A11Y_APPLICATIONS_SCHEMA = 'org.gnome.desktop.a11y.applications';
|
||||
const SHOW_KEYBOARD = 'screen-keyboard-enabled';
|
||||
@@ -40,28 +42,66 @@ const defaultKeysPost = [
|
||||
[ [{ label: '⌫', width: 1.5, keyval: Clutter.KEY_BackSpace }],
|
||||
[{ width: 2, keyval: Clutter.KEY_Return, extraClassName: 'enter-key' }],
|
||||
[{ width: 3, level: 1, right: true, extraClassName: 'shift-key-lowercase' }],
|
||||
[{ width: 1.5, action: 'languageMenu', extraClassName: 'layout-key' }, { width: 1.5, action: 'hide', extraClassName: 'hide-key' }] ],
|
||||
[{ label: '☻', action: 'emoji' }, { action: 'languageMenu', extraClassName: 'layout-key' }, { action: 'hide', extraClassName: 'hide-key' }] ],
|
||||
[ [{ label: '⌫', width: 1.5, keyval: Clutter.KEY_BackSpace }],
|
||||
[{ width: 2, keyval: Clutter.KEY_Return, extraClassName: 'enter-key' }],
|
||||
[{ width: 3, level: 0, right: true, extraClassName: 'shift-key-uppercase' }],
|
||||
[{ width: 1.5, action: 'languageMenu', extraClassName: 'layout-key' }, { width: 1.5, action: 'hide', extraClassName: 'hide-key' }] ],
|
||||
[{ label: '☻', action: 'emoji' }, { action: 'languageMenu', extraClassName: 'layout-key' }, { action: 'hide', extraClassName: 'hide-key' }] ],
|
||||
[ [{ label: '⌫', width: 1.5, keyval: Clutter.KEY_BackSpace }],
|
||||
[{ width: 2, keyval: Clutter.KEY_Return, extraClassName: 'enter-key' }],
|
||||
[{ label: '=/<', width: 3, level: 3, right: true }],
|
||||
[{ width: 1.5, action: 'languageMenu', extraClassName: 'layout-key' }, { width: 1.5, action: 'hide', extraClassName: 'hide-key' }] ],
|
||||
[{ label: '☻', action: 'emoji' }, { action: 'languageMenu', extraClassName: 'layout-key' }, { action: 'hide', extraClassName: 'hide-key' }] ],
|
||||
[ [{ label: '⌫', width: 1.5, keyval: Clutter.KEY_BackSpace }],
|
||||
[{ width: 2, keyval: Clutter.KEY_Return, extraClassName: 'enter-key' }],
|
||||
[{ label: '?123', width: 3, level: 2, right: true }],
|
||||
[{ width: 1.5, action: 'languageMenu', extraClassName: 'layout-key' }, { width: 1.5, action: 'hide', extraClassName: 'hide-key' }] ],
|
||||
[{ label: '☻', action: 'emoji' }, { action: 'languageMenu', extraClassName: 'layout-key' }, { action: 'hide', extraClassName: 'hide-key' }] ],
|
||||
];
|
||||
|
||||
var KeyContainer = new GObject.registerClass(
|
||||
var AspectContainer = GObject.registerClass(
|
||||
class AspectContainer extends St.Widget {
|
||||
_init(params) {
|
||||
super._init(params);
|
||||
this._ratio = 1;
|
||||
}
|
||||
|
||||
setRatio(relWidth, relHeight) {
|
||||
this._ratio = relWidth / relHeight;
|
||||
this.queue_relayout();
|
||||
}
|
||||
|
||||
vfunc_allocate(box, flags) {
|
||||
if (box.get_width() > 0 && box.get_height() > 0) {
|
||||
let sizeRatio = box.get_width() / box.get_height();
|
||||
|
||||
if (sizeRatio >= this._ratio) {
|
||||
/* Restrict horizontally */
|
||||
let width = box.get_height() * this._ratio;
|
||||
let diff = box.get_width() - width;
|
||||
|
||||
box.x1 += Math.floor(diff / 2);
|
||||
box.x2 -= Math.ceil(diff / 2);
|
||||
} else {
|
||||
/* Restrict vertically */
|
||||
let height = box.get_width() / this._ratio;
|
||||
let diff = box.get_height() - height;
|
||||
|
||||
box.y1 += Math.floor(diff / 2);
|
||||
box.y2 -= Math.floor(diff / 2);
|
||||
}
|
||||
}
|
||||
|
||||
super.vfunc_allocate(box, flags);
|
||||
}
|
||||
});
|
||||
|
||||
var KeyContainer = GObject.registerClass(
|
||||
class KeyContainer extends St.Widget {
|
||||
_init() {
|
||||
let gridLayout = new Clutter.GridLayout({ orientation: Clutter.Orientation.HORIZONTAL,
|
||||
column_homogeneous: true,
|
||||
row_homogeneous: true });
|
||||
super._init({ layout_manager: gridLayout });
|
||||
super._init({ layout_manager: gridLayout,
|
||||
x_expand: true, y_expand: true });
|
||||
this._gridLayout = gridLayout;
|
||||
this._currentRow = 0;
|
||||
this._currentCol = 0;
|
||||
@@ -98,32 +138,7 @@ class KeyContainer extends St.Widget {
|
||||
this._maxCols = Math.max(this._currentCol, this._maxCols);
|
||||
}
|
||||
|
||||
vfunc_allocate(box, flags) {
|
||||
if (box.get_width() > 0 && box.get_height() > 0 && this._maxCols > 0) {
|
||||
let keyboardRatio = this._maxCols / this._rows.length;
|
||||
let sizeRatio = box.get_width() / box.get_height();
|
||||
|
||||
if (sizeRatio >= keyboardRatio) {
|
||||
/* Restrict horizontally */
|
||||
let width = box.get_height() * keyboardRatio;
|
||||
let diff = box.get_width() - width;
|
||||
|
||||
box.x1 += Math.floor(diff / 2);
|
||||
box.x2 -= Math.ceil(diff / 2);
|
||||
} else {
|
||||
/* Restrict vertically */
|
||||
let height = box.get_width() / keyboardRatio;
|
||||
let diff = box.get_height() - height;
|
||||
|
||||
box.y1 += Math.floor(diff / 2);
|
||||
box.y2 -= Math.floor(diff / 2);
|
||||
}
|
||||
}
|
||||
|
||||
super.vfunc_allocate(box, flags);
|
||||
}
|
||||
|
||||
layoutButtons() {
|
||||
layoutButtons(container) {
|
||||
let nCol = 0, nRow = 0;
|
||||
|
||||
for (let i = 0; i < this._rows.length; i++) {
|
||||
@@ -150,6 +165,9 @@ class KeyContainer extends St.Widget {
|
||||
nRow += KEY_SIZE;
|
||||
nCol = 0;
|
||||
}
|
||||
|
||||
if (container)
|
||||
container.setRatio(this._maxCols, this._rows.length);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -232,7 +250,7 @@ var LanguageSelectionPopup = class extends PopupMenu.PopupMenu {
|
||||
};
|
||||
|
||||
var Key = class Key {
|
||||
constructor(key) {
|
||||
constructor(key, extendedKeys) {
|
||||
this.key = key || "";
|
||||
this.keyButton = this._makeKey(this.key);
|
||||
|
||||
@@ -258,6 +276,8 @@ var Key = class Key {
|
||||
this._boxPointer.destroy();
|
||||
this._boxPointer = null;
|
||||
}
|
||||
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
_ensureExtendedKeysPopup() {
|
||||
@@ -279,11 +299,13 @@ var Key = class Key {
|
||||
}
|
||||
|
||||
_getKeyval(key) {
|
||||
let unicode = String.charCodeAt(key, 0);
|
||||
return Gdk.unicode_to_keyval(unicode);
|
||||
let unicode = key.charCodeAt(0);
|
||||
return Clutter.unicode_to_keysym(unicode);
|
||||
}
|
||||
|
||||
_press(key) {
|
||||
this.emit('activated')
|
||||
|
||||
if (key != this.key || this._extended_keys.length == 0) {
|
||||
this.emit('pressed', this._getKeyval(key), key);
|
||||
}
|
||||
@@ -324,6 +346,16 @@ var Key = class Key {
|
||||
this._longPress = false;
|
||||
}
|
||||
|
||||
cancel() {
|
||||
if (this._pressTimeoutId != 0) {
|
||||
GLib.source_remove(this._pressTimeoutId);
|
||||
this._pressTimeoutId = 0;
|
||||
}
|
||||
this._touchPressed = false;
|
||||
this.keyButton.set_hover(false);
|
||||
this.keyButton.fake_release();
|
||||
}
|
||||
|
||||
_onCapturedEvent(actor, event) {
|
||||
let type = event.type();
|
||||
let press = (type == Clutter.EventType.BUTTON_PRESS || type == Clutter.EventType.TOUCH_BEGIN);
|
||||
@@ -395,13 +427,10 @@ var Key = class Key {
|
||||
|
||||
if (!this._touchPressed &&
|
||||
event.type() == Clutter.EventType.TOUCH_BEGIN) {
|
||||
device.sequence_grab(sequence, actor);
|
||||
this._touchPressed = true;
|
||||
this._press(key);
|
||||
} else if (this._touchPressed &&
|
||||
event.type() == Clutter.EventType.TOUCH_END &&
|
||||
device.sequence_get_grabbed_actor(sequence) == actor) {
|
||||
device.sequence_ungrab(sequence);
|
||||
event.type() == Clutter.EventType.TOUCH_END) {
|
||||
this._touchPressed = false;
|
||||
this._release(key);
|
||||
}
|
||||
@@ -550,10 +579,460 @@ var FocusTracker = class {
|
||||
};
|
||||
Signals.addSignalMethods(FocusTracker.prototype);
|
||||
|
||||
var EmojiPager = class EmojiPager {
|
||||
constructor(sections, nCols, nRows) {
|
||||
this.actor = new St.Widget({ layout_manager: new Clutter.BinLayout(),
|
||||
reactive: true,
|
||||
clip_to_allocation: true });
|
||||
this._sections = sections;
|
||||
this._nCols = nCols;
|
||||
this._nRows = nRows;
|
||||
|
||||
this._pages = [];
|
||||
this._panel = null;
|
||||
this._curPage = null;
|
||||
this._followingPage = null;
|
||||
this._followingPanel = null;
|
||||
this._currentKey = null;
|
||||
this._delta = 0;
|
||||
this._width = null;
|
||||
|
||||
this._initPagingInfo();
|
||||
|
||||
let panAction = new Clutter.PanAction({ interpolate: false });
|
||||
panAction.connect('pan', this._onPan.bind(this));
|
||||
panAction.connect('gesture-begin', this._onPanBegin.bind(this));
|
||||
panAction.connect('gesture-cancel', this._onPanCancel.bind(this));
|
||||
panAction.connect('gesture-end', this._onPanEnd.bind(this));
|
||||
this._panAction = panAction;
|
||||
this.actor.add_action(panAction);
|
||||
}
|
||||
|
||||
get delta() {
|
||||
return this._delta;
|
||||
}
|
||||
|
||||
set delta(value) {
|
||||
if (value > this._width)
|
||||
value = this._width;
|
||||
else if (value < -this._width)
|
||||
value = -this._width;
|
||||
|
||||
this._delta = value;
|
||||
|
||||
if (value == 0)
|
||||
return;
|
||||
|
||||
let relValue = Math.abs(value / this._width);
|
||||
let followingPage = this.getFollowingPage();
|
||||
|
||||
if (this._followingPage != followingPage) {
|
||||
if (this._followingPanel) {
|
||||
this._followingPanel.destroy();
|
||||
this._followingPanel = null;
|
||||
}
|
||||
|
||||
if (followingPage != null) {
|
||||
this._followingPanel = this._generatePanel(followingPage);
|
||||
this._followingPanel.set_pivot_point(0.5, 0.5);
|
||||
this.actor.add_child(this._followingPanel);
|
||||
this.actor.set_child_below_sibling(this._followingPanel, this._panel);
|
||||
}
|
||||
|
||||
this._followingPage = followingPage;
|
||||
}
|
||||
|
||||
this._panel.translation_x = value;
|
||||
this._panel.opacity = 255 * (1 - Math.pow(relValue, 3));
|
||||
|
||||
if (this._followingPanel) {
|
||||
this._followingPanel.scale_x = 0.8 + (0.2 * relValue);
|
||||
this._followingPanel.scale_y = 0.8 + (0.2 * relValue);
|
||||
this._followingPanel.opacity = 255 * relValue;
|
||||
}
|
||||
}
|
||||
|
||||
_prevPage(nPage) {
|
||||
return (nPage + this._pages.length - 1) % this._pages.length;
|
||||
}
|
||||
|
||||
_nextPage(nPage) {
|
||||
return (nPage + 1) % this._pages.length;
|
||||
}
|
||||
|
||||
getFollowingPage() {
|
||||
if (this.delta == 0)
|
||||
return null;
|
||||
|
||||
if ((this.delta < 0 && global.stage.text_direction == Clutter.TextDirection.LTR) ||
|
||||
(this.delta > 0 && global.stage.text_direction == Clutter.TextDirection.RTL))
|
||||
return this._nextPage(this._curPage);
|
||||
else
|
||||
return this._prevPage(this._curPage);
|
||||
}
|
||||
|
||||
_onPan(action) {
|
||||
let [dist, dx, dy] = action.get_motion_delta(0);
|
||||
this.delta = this.delta + dx;
|
||||
|
||||
if (this._currentKey != null) {
|
||||
this._currentKey.cancel();
|
||||
this._currentKey = null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_onPanBegin() {
|
||||
this._width = this.actor.width;
|
||||
return true;
|
||||
}
|
||||
|
||||
_onPanEnd() {
|
||||
if (Math.abs(this._delta) < this.actor.width * PANEL_SWITCH_RELATIVE_DISTANCE) {
|
||||
this._onPanCancel()
|
||||
} else {
|
||||
let value;
|
||||
if (this._delta > 0)
|
||||
value = this._width;
|
||||
else if (this._delta < 0)
|
||||
value = -this._width;
|
||||
|
||||
let relDelta = Math.abs(this._delta - value) / this._width;
|
||||
let time = PANEL_SWITCH_ANIMATION_TIME * Math.abs(relDelta);
|
||||
|
||||
Tweener.removeTweens(this);
|
||||
Tweener.addTween(this,
|
||||
{ delta: value,
|
||||
time: time,
|
||||
transition: 'easeInOutQuad',
|
||||
onComplete() {
|
||||
this.setCurrentPage(this.getFollowingPage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_onPanCancel() {
|
||||
let relDelta = Math.abs(this._delta) / this.actor.width;
|
||||
let time = PANEL_SWITCH_ANIMATION_TIME * Math.abs(relDelta);
|
||||
|
||||
Tweener.removeTweens(this);
|
||||
Tweener.addTween(this,
|
||||
{ delta: 0,
|
||||
time: time,
|
||||
transition: 'easeInOutQuad',
|
||||
});
|
||||
}
|
||||
|
||||
_initPagingInfo() {
|
||||
for (let i = 0; i < this._sections.length; i++) {
|
||||
let section = this._sections[i];
|
||||
let itemsPerPage = this._nCols * this._nRows;
|
||||
let nPages = Math.ceil(section.keys.length / itemsPerPage);
|
||||
let page = -1;
|
||||
let pageKeys;
|
||||
|
||||
for (let j = 0; j < section.keys.length; j++) {
|
||||
if (j % itemsPerPage == 0) {
|
||||
page++;
|
||||
pageKeys = [];
|
||||
this._pages.push({ pageKeys, nPages, page, section: this._sections[i] });
|
||||
}
|
||||
|
||||
pageKeys.push(section.keys[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_lookupSection(section, nPage) {
|
||||
for (let i = 0; i < this._pages.length; i++) {
|
||||
let page = this._pages[i];
|
||||
|
||||
if (page.section == section && page.page == nPage)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
_generatePanel(nPage) {
|
||||
let gridLayout = new Clutter.GridLayout({ orientation: Clutter.Orientation.HORIZONTAL,
|
||||
column_homogeneous: true,
|
||||
row_homogeneous: true });
|
||||
let panel = new St.Widget({ layout_manager: gridLayout,
|
||||
style_class: 'emoji-page',
|
||||
x_expand: true,
|
||||
y_expand: true });
|
||||
|
||||
/* Set an expander actor so all proportions are right despite the panel
|
||||
* not having all rows/cols filled in.
|
||||
*/
|
||||
let expander = new Clutter.Actor();
|
||||
gridLayout.attach(expander, 0, 0, this._nCols, this._nRows);
|
||||
|
||||
let page = this._pages[nPage];
|
||||
let col = 0;
|
||||
let row = 0;
|
||||
|
||||
for (let i = 0; i < page.pageKeys.length; i++) {
|
||||
let modelKey = page.pageKeys[i];
|
||||
let key = new Key(modelKey.label, modelKey.variants);
|
||||
|
||||
key.keyButton.set_button_mask(0);
|
||||
|
||||
key.connect('activated', () => {
|
||||
this._currentKey = key;
|
||||
});
|
||||
key.connect('long-press', () => {
|
||||
this._panAction.cancel();
|
||||
});
|
||||
key.connect('released', (actor, keyval, str) => {
|
||||
if (this._currentKey != key)
|
||||
return;
|
||||
this._currentKey = null;
|
||||
this.emit('emoji', str);
|
||||
});
|
||||
|
||||
gridLayout.attach(key.actor, col, row, 1, 1);
|
||||
|
||||
col++;
|
||||
if (col >= this._nCols) {
|
||||
col = 0;
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
setCurrentPage(nPage) {
|
||||
if (this._curPage == nPage)
|
||||
return;
|
||||
|
||||
this._curPage = nPage;
|
||||
|
||||
if (this._panel) {
|
||||
this._panel.destroy();
|
||||
this._panel = null;
|
||||
}
|
||||
|
||||
/* Reuse followingPage if possible */
|
||||
if (nPage == this._followingPage) {
|
||||
this._panel = this._followingPanel;
|
||||
this._followingPanel = null;
|
||||
}
|
||||
|
||||
if (this._followingPanel)
|
||||
this._followingPanel.destroy();
|
||||
|
||||
this._followingPanel = null;
|
||||
this._followingPage = null;
|
||||
this._delta = 0;
|
||||
|
||||
if (!this._panel) {
|
||||
this._panel = this._generatePanel(nPage);
|
||||
this.actor.add_child(this._panel);
|
||||
}
|
||||
|
||||
let page = this._pages[nPage];
|
||||
this.emit('page-changed', page.section, page.page, page.nPages);
|
||||
}
|
||||
|
||||
setCurrentSection(section, nPage) {
|
||||
for (let i = 0; i < this._pages.length; i++) {
|
||||
let page = this._pages[i];
|
||||
|
||||
if (page.section == section && page.page == nPage) {
|
||||
this.setCurrentPage(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(EmojiPager.prototype);
|
||||
|
||||
var EmojiSelection = class EmojiSelection {
|
||||
constructor() {
|
||||
this._sections = [
|
||||
{ first: 'grinning face', label: '🙂️' },
|
||||
{ first: 'selfie', label: '👍️' },
|
||||
{ first: 'monkey face', label: '🌷️' },
|
||||
{ first: 'grapes', label: '🍴️' },
|
||||
{ first: 'globe showing Europe-Africa', label: '✈️' },
|
||||
{ first: 'jack-o-lantern', label: '🏃️' },
|
||||
{ first: 'muted speaker', label: '🔔️' },
|
||||
{ first: 'ATM sign', label: '❤️' },
|
||||
{ first: 'chequered flag', label: '🚩️' },
|
||||
];
|
||||
|
||||
this._populateSections();
|
||||
|
||||
this.actor = new St.BoxLayout({ style_class: 'emoji-panel',
|
||||
x_expand: true,
|
||||
y_expand: true,
|
||||
vertical: true });
|
||||
this.actor.connect('notify::mapped', () => { this._emojiPager.setCurrentPage(0); });
|
||||
|
||||
this._emojiPager = new EmojiPager(this._sections, 11, 3);
|
||||
this._emojiPager.connect('page-changed', (pager, section, page, nPages) => {
|
||||
this._onPageChanged(section, page, nPages);
|
||||
});
|
||||
this._emojiPager.connect('emoji', (pager, str) => {
|
||||
this.emit('emoji-selected', str);
|
||||
});
|
||||
this.actor.add(this._emojiPager.actor, { expand: true });
|
||||
|
||||
this._pageIndicator = new PageIndicators.PageIndicators(false);
|
||||
this.actor.add(this._pageIndicator, { expand: true, x_fill: false, y_fill: false });
|
||||
this._pageIndicator.setReactive(false);
|
||||
|
||||
let bottomRow = this._createBottomRow();
|
||||
this.actor.add(bottomRow, { x_fill: true, y_fill: false });
|
||||
|
||||
this._emojiPager.setCurrentPage(0);
|
||||
}
|
||||
|
||||
_onPageChanged(section, page, nPages) {
|
||||
this._pageIndicator.setNPages(nPages);
|
||||
this._pageIndicator.setCurrentPage(page);
|
||||
|
||||
for (let i = 0; i < this._sections.length; i++) {
|
||||
let sect = this._sections[i];
|
||||
sect.button.setLatched(section == sect);
|
||||
}
|
||||
}
|
||||
|
||||
_findSection(emoji) {
|
||||
for (let i = 0; i < this._sections.length; i++) {
|
||||
if (this._sections[i].first == emoji)
|
||||
return this._sections[i];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_populateSections() {
|
||||
let file = Gio.File.new_for_uri('resource:///org/gnome/shell/osk-layouts/emoji.json');
|
||||
let [success, contents] = file.load_contents(null);
|
||||
|
||||
if (contents instanceof Uint8Array)
|
||||
contents = imports.byteArray.toString(contents);
|
||||
let emoji = JSON.parse(contents);
|
||||
|
||||
let pages = [];
|
||||
let variants = [];
|
||||
let currentKey = 0;
|
||||
let currentSection = null;
|
||||
|
||||
for (let i = 0; i < emoji.length; i++) {
|
||||
/* Group variants of a same emoji so they appear on the key popover */
|
||||
if (emoji[i].name.startsWith(emoji[currentKey].name)) {
|
||||
variants.push(emoji[i].char);
|
||||
if (i < emoji.length - 1)
|
||||
continue;
|
||||
}
|
||||
|
||||
let newSection = this._findSection(emoji[currentKey].name);
|
||||
if (newSection != null) {
|
||||
currentSection = newSection;
|
||||
currentSection.keys = [];
|
||||
}
|
||||
|
||||
/* Create the key */
|
||||
let label = emoji[currentKey].char + String.fromCharCode(0xFE0F);
|
||||
currentSection.keys.push({ label, variants });
|
||||
currentKey = i;
|
||||
variants = [];
|
||||
}
|
||||
}
|
||||
|
||||
_createBottomRow() {
|
||||
let row = new KeyContainer();
|
||||
let key;
|
||||
|
||||
row.appendRow();
|
||||
|
||||
key = new Key('ABC', []);
|
||||
key.keyButton.add_style_class_name('default-key');
|
||||
key.connect('released', () => { this.emit('toggle'); });
|
||||
row.appendKey(key.actor, 1.5);
|
||||
|
||||
for (let i = 0; i < this._sections.length; i++) {
|
||||
let section = this._sections[i];
|
||||
|
||||
key = new Key(section.label, []);
|
||||
key.connect('released', () => { this._emojiPager.setCurrentSection(section, 0) });
|
||||
row.appendKey(key.actor);
|
||||
|
||||
section.button = key;
|
||||
}
|
||||
|
||||
key = new Key(null, []);
|
||||
key.keyButton.add_style_class_name('default-key');
|
||||
key.keyButton.add_style_class_name('hide-key');
|
||||
key.connect('released', () => {
|
||||
this.emit('hide');
|
||||
});
|
||||
row.appendKey(key.actor);
|
||||
row.layoutButtons();
|
||||
|
||||
return row;
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(EmojiSelection.prototype);
|
||||
|
||||
var Keypad = class Keypad {
|
||||
constructor() {
|
||||
let keys = [
|
||||
{ label: '1', keyval: Clutter.KEY_1, left: 0, top: 0 },
|
||||
{ label: '2', keyval: Clutter.KEY_2, left: 1, top: 0 },
|
||||
{ label: '3', keyval: Clutter.KEY_3, left: 2, top: 0 },
|
||||
{ label: '4', keyval: Clutter.KEY_4, left: 0, top: 1 },
|
||||
{ label: '5', keyval: Clutter.KEY_5, left: 1, top: 1 },
|
||||
{ label: '6', keyval: Clutter.KEY_6, left: 2, top: 1 },
|
||||
{ label: '7', keyval: Clutter.KEY_7, left: 0, top: 2 },
|
||||
{ label: '8', keyval: Clutter.KEY_8, left: 1, top: 2 },
|
||||
{ label: '9', keyval: Clutter.KEY_9, left: 2, top: 2 },
|
||||
{ label: '0', keyval: Clutter.KEY_0, left: 1, top: 3 },
|
||||
{ label: '⌫', keyval: Clutter.KEY_BackSpace, left: 3, top: 0 },
|
||||
{ keyval: Clutter.KEY_Return, extraClassName: 'enter-key', left: 3, top: 1, height: 2 },
|
||||
];
|
||||
|
||||
this.actor = new AspectContainer({ layout_manager: new Clutter.BinLayout(),
|
||||
x_expand: true, y_expand: true });
|
||||
|
||||
let gridLayout = new Clutter.GridLayout({ orientation: Clutter.Orientation.HORIZONTAL,
|
||||
column_homogeneous: true,
|
||||
row_homogeneous: true });
|
||||
this._box = new St.Widget({ layout_manager: gridLayout, x_expand: true, y_expand: true });
|
||||
this.actor.add_child(this._box);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
let cur = keys[i];
|
||||
let key = new Key(cur.label || "", []);
|
||||
|
||||
if (keys[i].extraClassName)
|
||||
key.keyButton.add_style_class_name(cur.extraClassName);
|
||||
|
||||
let w, h;
|
||||
w = cur.width || 1;
|
||||
h = cur.height || 1;
|
||||
gridLayout.attach(key.actor, cur.left, cur.top, w, h);
|
||||
|
||||
key.connect('released', () => {
|
||||
this.emit('keyval', cur.keyval);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(Keypad.prototype);
|
||||
|
||||
var Keyboard = class Keyboard {
|
||||
constructor() {
|
||||
this.actor = null;
|
||||
this._focusInExtendedKeys = false;
|
||||
this._emojiActive = false;
|
||||
|
||||
this._languagePopup = null;
|
||||
this._currentFocusWindow = null;
|
||||
@@ -568,6 +1047,7 @@ var Keyboard = class Keyboard {
|
||||
this._a11yApplicationsSettings.connect('changed', this._syncEnabled.bind(this));
|
||||
this._lastDeviceId = null;
|
||||
this._suggestions = null;
|
||||
this._emojiKeyVisible = true;
|
||||
|
||||
this._focusTracker = new FocusTracker();
|
||||
this._focusTracker.connect('position-changed', this._onFocusPositionChanged.bind(this));
|
||||
@@ -656,6 +1136,10 @@ var Keyboard = class Keyboard {
|
||||
this._keyboardController.disconnect(this._keyboardGroupsChangedId);
|
||||
if (this._keyboardStateId)
|
||||
this._keyboardController.disconnect(this._keyboardStateId);
|
||||
if (this._emojiKeyVisibleId)
|
||||
this._keyboardController.disconnect(this._emojiKeyVisibleId);
|
||||
if (this._keypadVisibleId)
|
||||
this._keyboardController.disconnect(this._keypadVisibleId);
|
||||
if (this._focusNotifyId)
|
||||
global.stage.disconnect(this._focusNotifyId);
|
||||
this._keyboard = null;
|
||||
@@ -679,13 +1163,32 @@ var Keyboard = class Keyboard {
|
||||
this._current_page = null;
|
||||
|
||||
this._suggestions = new Suggestions();
|
||||
this._suggestions.connect('suggestion-clicked', (suggestions, str) => {
|
||||
this._keyboardController.commitString(str);
|
||||
});
|
||||
this.actor.add(this._suggestions.actor,
|
||||
{ x_align: St.Align.MIDDLE,
|
||||
x_fill: false });
|
||||
|
||||
this._aspectContainer = new AspectContainer({ layout_manager: new Clutter.BinLayout() });
|
||||
this.actor.add(this._aspectContainer, { expand: true });
|
||||
|
||||
this._emojiSelection = new EmojiSelection();
|
||||
this._emojiSelection.connect('toggle', this._toggleEmoji.bind(this));
|
||||
this._emojiSelection.connect('hide', (selection) => { this.hide(); });
|
||||
this._emojiSelection.connect('emoji-selected', (selection, emoji) => {
|
||||
this._keyboardController.commitString(emoji);
|
||||
});
|
||||
|
||||
this._aspectContainer.add_child(this._emojiSelection.actor);
|
||||
this._emojiSelection.actor.hide();
|
||||
|
||||
this._keypad = new Keypad();
|
||||
this._keypad.connect('keyval', (keypad, keyval) => {
|
||||
this._keyboardController.keyvalPress(keyval);
|
||||
this._keyboardController.keyvalRelease(keyval);
|
||||
});
|
||||
this._aspectContainer.add_child(this._keypad.actor);
|
||||
this._keypad.actor.hide();
|
||||
this._keypadVisible = false;
|
||||
|
||||
this._ensureKeysForGroup(this._keyboardController.getCurrentGroup());
|
||||
this._setActiveLayer(0);
|
||||
|
||||
@@ -697,6 +1200,8 @@ var Keyboard = class Keyboard {
|
||||
this._keyboardNotifyId = this._keyboardController.connect('active-group', this._onGroupChanged.bind(this));
|
||||
this._keyboardGroupsChangedId = this._keyboardController.connect('groups-changed', this._onKeyboardGroupsChanged.bind(this));
|
||||
this._keyboardStateId = this._keyboardController.connect('panel-state', this._onKeyboardStateChanged.bind(this));
|
||||
this._emojiKeyVisibleId = this._keyboardController.connect('emoji-visible', this._onEmojiKeyVisible.bind(this));
|
||||
this._keypadVisibleId = this._keyboardController.connect('keypad-visible', this._onKeypadVisible.bind(this));
|
||||
this._focusNotifyId = global.stage.connect('notify::key-focus', this._onKeyFocusChanged.bind(this));
|
||||
|
||||
this._relayout();
|
||||
@@ -743,11 +1248,12 @@ var Keyboard = class Keyboard {
|
||||
|
||||
this._loadRows(currentLevel, level, levels.length, layout);
|
||||
layers[level] = layout;
|
||||
this.actor.add(layout, { expand: true });
|
||||
layout.layoutButtons();
|
||||
this._aspectContainer.add_child(layout);
|
||||
layout.layoutButtons(this._aspectContainer);
|
||||
|
||||
layout.hide();
|
||||
}
|
||||
|
||||
return layers;
|
||||
}
|
||||
|
||||
@@ -806,7 +1312,11 @@ var Keyboard = class Keyboard {
|
||||
let switchToLevel = key.level;
|
||||
let action = key.action;
|
||||
|
||||
extraButton = new Key(key.label, []);
|
||||
/* Skip emoji button if necessary */
|
||||
if (!this._emojiKeyVisible && action == 'emoji')
|
||||
continue;
|
||||
|
||||
extraButton = new Key(key.label || '', []);
|
||||
|
||||
extraButton.keyButton.add_style_class_name('default-key');
|
||||
if (key.extraClassName != null)
|
||||
@@ -832,6 +1342,8 @@ var Keyboard = class Keyboard {
|
||||
this.hide();
|
||||
else if (action == 'languageMenu')
|
||||
this._popupLanguageMenu(actor);
|
||||
else if (action == 'emoji')
|
||||
this._toggleEmoji();
|
||||
});
|
||||
|
||||
if (switchToLevel == 0) {
|
||||
@@ -857,14 +1369,31 @@ var Keyboard = class Keyboard {
|
||||
extraButton.setWidth(2);
|
||||
} else if (keyval == Clutter.KEY_Return && numKeys > 9) {
|
||||
extraButton.setWidth(1.5);
|
||||
} else if (!this._emojiKeyVisible && (action == 'hide' || action == 'languageMenu')) {
|
||||
extraButton.setWidth(1.5);
|
||||
}
|
||||
|
||||
layout.appendKey(extraButton.actor, extraButton.keyButton.keyWidth);
|
||||
}
|
||||
}
|
||||
|
||||
_updateCurrentPageVisible() {
|
||||
if (this._current_page)
|
||||
this._current_page.visible = !this._emojiActive && !this._keypadVisible;
|
||||
}
|
||||
|
||||
_setEmojiActive(active) {
|
||||
this._emojiActive = active;
|
||||
this._emojiSelection.actor.visible = this._emojiActive;
|
||||
this._updateCurrentPageVisible();
|
||||
}
|
||||
|
||||
_toggleEmoji() {
|
||||
this._setEmojiActive(!this._emojiActive);
|
||||
}
|
||||
|
||||
_setCurrentLevelLatched(layout, latched) {
|
||||
for (let i = 0; layout.shiftKeys[i]; i++) {
|
||||
for (let i = 0; i < layout.shiftKeys.length; i++) {
|
||||
let key = layout.shiftKeys[i];
|
||||
key.setLatched(latched);
|
||||
}
|
||||
@@ -943,6 +1472,24 @@ var Keyboard = class Keyboard {
|
||||
this._onGroupChanged();
|
||||
}
|
||||
|
||||
_onKeypadVisible(controller, visible) {
|
||||
if (visible == this._keypadVisible)
|
||||
return;
|
||||
|
||||
this._keypadVisible = visible;
|
||||
this._keypad.actor.visible = this._keypadVisible;
|
||||
this._updateCurrentPageVisible();
|
||||
}
|
||||
|
||||
_onEmojiKeyVisible(controller, visible) {
|
||||
if (visible == this._emojiKeyVisible)
|
||||
return;
|
||||
|
||||
this._emojiKeyVisible = visible;
|
||||
/* Rebuild keyboard widgetry to include emoji button */
|
||||
this._onKeyboardGroupsChanged();
|
||||
}
|
||||
|
||||
_onKeyboardStateChanged(controller, state) {
|
||||
let enabled;
|
||||
if (state == Clutter.InputPanelState.OFF)
|
||||
@@ -970,7 +1517,7 @@ var Keyboard = class Keyboard {
|
||||
}
|
||||
|
||||
this._current_page = layers[activeLevel];
|
||||
this._current_page.show();
|
||||
this._updateCurrentPageVisible();
|
||||
}
|
||||
|
||||
shouldTakeEvent(event) {
|
||||
@@ -1020,6 +1567,8 @@ var Keyboard = class Keyboard {
|
||||
this._relayout();
|
||||
Main.layoutManager.showKeyboard();
|
||||
|
||||
this._setEmojiActive(false);
|
||||
|
||||
if (this._delayedAnimFocusWindow) {
|
||||
this._setAnimationWindow(this._delayedAnimFocusWindow);
|
||||
this._delayedAnimFocusWindow = null;
|
||||
@@ -1187,8 +1736,21 @@ var KeyboardController = class {
|
||||
_onContentPurposeHintsChanged(method) {
|
||||
let hints = method.content_hints;
|
||||
let purpose = method.content_purpose;
|
||||
let emojiVisible = false;
|
||||
let keypadVisible = false;
|
||||
|
||||
// XXX: hook numeric/emoji/etc special keyboards
|
||||
if (purpose == Clutter.InputContentPurpose.NORMAL ||
|
||||
purpose == Clutter.InputContentPurpose.ALPHA ||
|
||||
purpose == Clutter.InputContentPurpose.PASSWORD ||
|
||||
purpose == Clutter.InputContentPurpose.TERMINAL)
|
||||
emojiVisible = true;
|
||||
if (purpose == Clutter.InputContentPurpose.DIGITS ||
|
||||
purpose == Clutter.InputContentPurpose.NUMBER ||
|
||||
purpose == Clutter.InputContentPurpose.PHONE)
|
||||
keypadVisible = true;
|
||||
|
||||
this.emit('emoji-visible', emojiVisible)
|
||||
this.emit('keypad-visible', keypadVisible);
|
||||
}
|
||||
|
||||
getGroups() {
|
||||
|
@@ -5,7 +5,6 @@ const Cogl = imports.gi.Cogl;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Pango = imports.gi.Pango;
|
||||
const St = imports.gi.St;
|
||||
@@ -187,7 +186,7 @@ var Notebook = class Notebook {
|
||||
|
||||
// Focus the new tab before unmapping the old one
|
||||
let tabData = this._tabs[index];
|
||||
if (!tabData.scrollView.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false))
|
||||
if (!tabData.scrollView.navigate_focus(null, St.DirectionType.TAB_FORWARD, false))
|
||||
this.actor.grab_key_focus();
|
||||
|
||||
this._unselect();
|
||||
|
@@ -2,9 +2,11 @@
|
||||
|
||||
const Atspi = imports.gi.Atspi;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Cogl = imports.gi.Cogl;
|
||||
const GDesktopEnums = imports.gi.GDesktopEnums;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
const Mainloop = imports.mainloop;
|
||||
@@ -52,6 +54,51 @@ const CROSS_HAIRS_CLIP_KEY = 'cross-hairs-clip';
|
||||
|
||||
let magDBusService = null;
|
||||
|
||||
var MouseSpriteContent = GObject.registerClass({
|
||||
Implements: [ Clutter.Content ],
|
||||
}, class MouseSpriteContent extends GObject.Object {
|
||||
_init() {
|
||||
super._init();
|
||||
this._texture = null;
|
||||
}
|
||||
|
||||
vfunc_get_preferred_size() {
|
||||
if (!this._texture)
|
||||
return [0, 0];
|
||||
|
||||
return [this._texture.get_width(), this._texture.get_height()];
|
||||
}
|
||||
|
||||
vfunc_paint_content(actor, node) {
|
||||
if (!this._texture)
|
||||
return;
|
||||
|
||||
let color = new Cogl.Color();
|
||||
color.init_from_4ub(0, 0, 0, 0);
|
||||
|
||||
let textureNode = new Clutter.TextureNode(this._texture,
|
||||
color,
|
||||
Clutter.ScalingFilter.NEAREST,
|
||||
Clutter.ScalingFilter.NEAREST);
|
||||
textureNode.set_name('MouseSpriteContent');
|
||||
node.add_child(textureNode);
|
||||
|
||||
textureNode.add_rectangle(actor.get_content_box());
|
||||
}
|
||||
|
||||
get texture() {
|
||||
return this._texture;
|
||||
}
|
||||
|
||||
set texture(coglTexture) {
|
||||
if (this._texture == coglTexture)
|
||||
return;
|
||||
|
||||
this._texture = coglTexture;
|
||||
this.invalidate();
|
||||
}
|
||||
});
|
||||
|
||||
var Magnifier = class Magnifier {
|
||||
constructor() {
|
||||
// Magnifier is a manager of ZoomRegions.
|
||||
@@ -59,8 +106,12 @@ var Magnifier = class Magnifier {
|
||||
|
||||
// Create small clutter tree for the magnified mouse.
|
||||
let cursorTracker = Meta.CursorTracker.get_for_display(global.display);
|
||||
this._mouseSprite = new Clutter.Texture();
|
||||
Shell.util_cursor_tracker_to_clutter(cursorTracker, this._mouseSprite);
|
||||
this._cursorTracker = cursorTracker;
|
||||
|
||||
this._mouseSprite = new Clutter.Actor({ request_mode: Clutter.RequestMode.CONTENT_SIZE });
|
||||
this._mouseSprite.content = new MouseSpriteContent();
|
||||
this._updateSpriteTexture();
|
||||
|
||||
this._cursorRoot = new Clutter.Actor();
|
||||
this._cursorRoot.add_actor(this._mouseSprite);
|
||||
|
||||
@@ -76,7 +127,6 @@ var Magnifier = class Magnifier {
|
||||
aZoomRegion.scrollContentsTo(this.xMouse, this.yMouse);
|
||||
|
||||
cursorTracker.connect('cursor-changed', this._updateMouseSprite.bind(this));
|
||||
this._cursorTracker = cursorTracker;
|
||||
|
||||
// Export to dbus.
|
||||
magDBusService = new MagnifierDBus.ShellMagnifier();
|
||||
@@ -436,11 +486,22 @@ var Magnifier = class Magnifier {
|
||||
//// Private methods ////
|
||||
|
||||
_updateMouseSprite() {
|
||||
Shell.util_cursor_tracker_to_clutter(this._cursorTracker, this._mouseSprite);
|
||||
this._updateSpriteTexture();
|
||||
let [xHot, yHot] = this._cursorTracker.get_hot();
|
||||
this._mouseSprite.set_anchor_point(xHot, yHot);
|
||||
}
|
||||
|
||||
_updateSpriteTexture() {
|
||||
let sprite = this._cursorTracker.get_sprite();
|
||||
|
||||
if (sprite) {
|
||||
this._mouseSprite.content.texture = sprite;
|
||||
this._mouseSprite.show();
|
||||
} else {
|
||||
this._mouseSprite.hide();
|
||||
}
|
||||
}
|
||||
|
||||
_settingsInit(zoomRegion) {
|
||||
this._appSettings = new Gio.Settings({ schema_id: APPLICATIONS_SCHEMA });
|
||||
this._settings = new Gio.Settings({ schema_id: MAGNIFIER_SCHEMA });
|
||||
|
@@ -1,10 +1,8 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
@@ -128,9 +126,8 @@ function start() {
|
||||
|
||||
sessionMode = new SessionMode.SessionMode();
|
||||
sessionMode.connect('updated', _sessionUpdated);
|
||||
Gtk.Settings.get_default().connect('notify::gtk-theme-name',
|
||||
_loadDefaultStylesheet);
|
||||
Gtk.IconTheme.get_default().add_resource_path('/org/gnome/shell/theme/icons');
|
||||
|
||||
St.Settings.get().connect('notify::gtk-theme', _loadDefaultStylesheet);
|
||||
_initializeUI();
|
||||
|
||||
shellAccessDialogDBusService = new AccessDialog.AccessDialogDBus();
|
||||
@@ -280,7 +277,7 @@ function _getDefaultStylesheet() {
|
||||
|
||||
// Look for a high-contrast variant first when using GTK+'s HighContrast
|
||||
// theme
|
||||
if (Gtk.Settings.get_default().gtk_theme_name == 'HighContrast')
|
||||
if (St.Settings.get().gtk_theme == 'HighContrast')
|
||||
stylesheet = _getStylesheet(name.replace('.css', '-high-contrast.css'));
|
||||
|
||||
if (stylesheet == null)
|
||||
|
@@ -159,7 +159,7 @@ var URLHighlighter = class URLHighlighter {
|
||||
}
|
||||
};
|
||||
|
||||
var ScaleLayout = new GObject.registerClass(
|
||||
var ScaleLayout = GObject.registerClass(
|
||||
class ScaleLayout extends Clutter.BinLayout {
|
||||
_init(params) {
|
||||
this._container = null;
|
||||
|
@@ -4,7 +4,6 @@ const Clutter = imports.gi.Clutter;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Atk = imports.gi.Atk;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
@@ -71,6 +70,17 @@ var Urgency = {
|
||||
CRITICAL: 3
|
||||
};
|
||||
|
||||
// The privacy of the details of a notification. USER is for notifications which
|
||||
// contain private information to the originating user account (for example,
|
||||
// details of an e-mail they’ve received). SYSTEM is for notifications which
|
||||
// contain information private to the physical system (for example, battery
|
||||
// status) and hence the same for every user. This affects whether the content
|
||||
// of a notification is shown on the lock screen.
|
||||
var PrivacyScope = {
|
||||
USER: 0,
|
||||
SYSTEM: 1,
|
||||
};
|
||||
|
||||
var FocusGrabber = class FocusGrabber {
|
||||
constructor(actor) {
|
||||
this._actor = actor;
|
||||
@@ -87,7 +97,7 @@ var FocusGrabber = class FocusGrabber {
|
||||
|
||||
this._focusActorChangedId = global.stage.connect('notify::key-focus', this._focusActorChanged.bind(this));
|
||||
|
||||
if (!this._actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false))
|
||||
if (!this._actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false))
|
||||
this._actor.grab_key_focus();
|
||||
|
||||
this._focused = true;
|
||||
@@ -340,6 +350,7 @@ var Notification = class Notification {
|
||||
this.resident = false;
|
||||
// 'transient' is a reserved keyword in JS, so we have to use an alternate variable name
|
||||
this.isTransient = false;
|
||||
this.privacyScope = PrivacyScope.USER;
|
||||
this.forFeedback = false;
|
||||
this._acknowledged = false;
|
||||
this.bannerBodyText = null;
|
||||
@@ -436,6 +447,10 @@ var Notification = class Notification {
|
||||
this.forFeedback = forFeedback;
|
||||
}
|
||||
|
||||
setPrivacyScope(privacyScope) {
|
||||
this.privacyScope = privacyScope;
|
||||
}
|
||||
|
||||
playSound() {
|
||||
if (this._soundPlayed)
|
||||
return;
|
||||
@@ -497,9 +512,12 @@ class NotificationBanner extends Calendar.NotificationMessage {
|
||||
});
|
||||
}
|
||||
|
||||
_onDestroy() {
|
||||
super._onDestroy();
|
||||
this.notification.disconnect(this._activatedId);
|
||||
_disconnectNotificationSignals() {
|
||||
super._disconnectNotificationSignals();
|
||||
|
||||
if (this._activatedId)
|
||||
this.notification.disconnect(this._activatedId);
|
||||
this._activatedId = 0;
|
||||
}
|
||||
|
||||
_onUpdated(n, clear) {
|
||||
@@ -719,6 +737,11 @@ var Source = class Source {
|
||||
return new NotificationPolicy();
|
||||
}
|
||||
|
||||
get narrowestPrivacyScope() {
|
||||
return this.notifications.every(n => n.privacyScope == PrivacyScope.SYSTEM) ? PrivacyScope.SYSTEM
|
||||
: PrivacyScope.USER;
|
||||
}
|
||||
|
||||
setTitle(newTitle) {
|
||||
this.title = newTitle;
|
||||
this.emit('title-changed');
|
||||
|
@@ -1,7 +1,6 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Meta = imports.gi.Meta;
|
||||
@@ -197,7 +196,6 @@ var ModalDialog = class {
|
||||
else
|
||||
this._savedKeyFocus = null;
|
||||
Main.popModal(this._group, timestamp);
|
||||
Gdk.Display.get_default().sync();
|
||||
this._hasModal = false;
|
||||
|
||||
if (!this._shellReactive)
|
||||
|
@@ -4,7 +4,6 @@ const Clutter = imports.gi.Clutter;
|
||||
const GdkPixbuf = imports.gi.GdkPixbuf;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Mainloop = imports.mainloop;
|
||||
const St = imports.gi.St;
|
||||
@@ -352,6 +351,10 @@ var FdoNotificationDaemon = class FdoNotificationDaemon {
|
||||
// of the 'transient' hint with hints['transient'] rather than hints.transient
|
||||
notification.setTransient(!!hints['transient']);
|
||||
|
||||
let privacyScope = (hints['x-gnome-privacy-scope'] || 'user');
|
||||
notification.setPrivacyScope(privacyScope == 'system' ? MessageTray.PrivacyScope.SYSTEM
|
||||
: MessageTray.PrivacyScope.USER);
|
||||
|
||||
let sourceGIcon = source.useNotificationIcon ? gicon : null;
|
||||
source.processNotification(notification, sourceGIcon);
|
||||
}
|
||||
|
@@ -2,13 +2,11 @@
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Gdk = imports.gi.Gdk;
|
||||
|
||||
const Background = imports.ui.background;
|
||||
const DND = imports.ui.dnd;
|
||||
@@ -158,7 +156,6 @@ var Overview = class {
|
||||
this._windowSwitchTimestamp = 0;
|
||||
this._lastActiveWorkspaceIndex = -1;
|
||||
this._lastHoveredWindow = null;
|
||||
this._needsFakePointerEvent = false;
|
||||
|
||||
if (this._initCalled)
|
||||
this.init();
|
||||
@@ -306,19 +303,9 @@ var Overview = class {
|
||||
if (this._windowSwitchTimeoutId != 0) {
|
||||
Mainloop.source_remove(this._windowSwitchTimeoutId);
|
||||
this._windowSwitchTimeoutId = 0;
|
||||
this._needsFakePointerEvent = false;
|
||||
}
|
||||
}
|
||||
|
||||
_fakePointerEvent() {
|
||||
let display = Gdk.Display.get_default();
|
||||
let deviceManager = display.get_device_manager();
|
||||
let pointer = deviceManager.get_client_pointer();
|
||||
let [gdkScreen, pointerX, pointerY] = pointer.get_position();
|
||||
|
||||
pointer.warp(gdkScreen, pointerX, pointerY);
|
||||
}
|
||||
|
||||
_onDragMotion(dragEvent) {
|
||||
let targetIsWindow = dragEvent.targetActor &&
|
||||
dragEvent.targetActor._delegate &&
|
||||
@@ -340,7 +327,6 @@ var Overview = class {
|
||||
this._windowSwitchTimeoutId = Mainloop.timeout_add(DND_WINDOW_SWITCH_TIMEOUT,
|
||||
() => {
|
||||
this._windowSwitchTimeoutId = 0;
|
||||
this._needsFakePointerEvent = true;
|
||||
Main.activateWindow(dragEvent.targetActor._delegate.metaWindow,
|
||||
this._windowSwitchTimestamp);
|
||||
this.hide();
|
||||
@@ -387,9 +373,6 @@ var Overview = class {
|
||||
// when it is next shown.
|
||||
this.hide();
|
||||
|
||||
if (!Main.layoutManager.primaryMonitor)
|
||||
return;
|
||||
|
||||
this._coverPane.set_position(0, 0);
|
||||
this._coverPane.set_size(global.screen_width, global.screen_height);
|
||||
|
||||
@@ -646,12 +629,6 @@ var Overview = class {
|
||||
Main.layoutManager.hideOverview();
|
||||
|
||||
this._syncGrab();
|
||||
|
||||
// Fake a pointer event if requested
|
||||
if (this._needsFakePointerEvent) {
|
||||
this._fakePointerEvent();
|
||||
this._needsFakePointerEvent = false;
|
||||
}
|
||||
}
|
||||
|
||||
toggle() {
|
||||
|
142
js/ui/pageIndicators.js
Normal file
142
js/ui/pageIndicators.js
Normal file
@@ -0,0 +1,142 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GObject = imports.gi.GObject;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Tweener = imports.ui.tweener;
|
||||
const { ANIMATION_TIME_OUT, ANIMATION_MAX_DELAY_OUT_FOR_ITEM, AnimationDirection } = imports.ui.iconGrid;
|
||||
|
||||
var INDICATORS_BASE_TIME = 0.25;
|
||||
var INDICATORS_BASE_TIME_OUT = 0.125;
|
||||
var INDICATORS_ANIMATION_DELAY = 0.125;
|
||||
var INDICATORS_ANIMATION_DELAY_OUT = 0.0625;
|
||||
var INDICATORS_ANIMATION_MAX_TIME = 0.75;
|
||||
var SWITCH_TIME = 0.4;
|
||||
var INDICATORS_ANIMATION_MAX_TIME_OUT =
|
||||
Math.min (SWITCH_TIME,
|
||||
ANIMATION_TIME_OUT + ANIMATION_MAX_DELAY_OUT_FOR_ITEM);
|
||||
|
||||
var ANIMATION_DELAY = 0.1;
|
||||
|
||||
var PageIndicators = GObject.registerClass({
|
||||
Signals: { 'page-activated': { param_types: [GObject.TYPE_INT] } }
|
||||
}, class PageIndicators extends St.BoxLayout {
|
||||
_init(vertical = true) {
|
||||
super._init({ style_class: 'page-indicators',
|
||||
vertical,
|
||||
x_expand: true, y_expand: true,
|
||||
x_align: vertical ? Clutter.ActorAlign.END : Clutter.ActorAlign.CENTER,
|
||||
y_align: vertical ? Clutter.ActorAlign.CENTER : Clutter.ActorAlign.END,
|
||||
reactive: true,
|
||||
clip_to_allocation: true });
|
||||
this._nPages = 0;
|
||||
this._currentPage = undefined;
|
||||
this._reactive = true;
|
||||
this._reactive = true;
|
||||
}
|
||||
|
||||
vfunc_get_preferred_height(forWidth) {
|
||||
// We want to request the natural height of all our children as our
|
||||
// natural height, so we chain up to St.BoxLayout, but we only request 0
|
||||
// as minimum height, since it's not that important if some indicators
|
||||
// are not shown
|
||||
let [, natHeight] = super.vfunc_get_preferred_height(forWidth);
|
||||
return [0, natHeight];
|
||||
}
|
||||
|
||||
setReactive(reactive) {
|
||||
let children = this.get_children();
|
||||
for (let i = 0; i < children.length; i++)
|
||||
children[i].reactive = reactive;
|
||||
|
||||
this._reactive = reactive;
|
||||
}
|
||||
|
||||
setNPages(nPages) {
|
||||
if (this._nPages == nPages)
|
||||
return;
|
||||
|
||||
let diff = nPages - this._nPages;
|
||||
if (diff > 0) {
|
||||
for (let i = 0; i < diff; i++) {
|
||||
let pageIndex = this._nPages + i;
|
||||
let indicator = new St.Button({ style_class: 'page-indicator',
|
||||
button_mask: St.ButtonMask.ONE |
|
||||
St.ButtonMask.TWO |
|
||||
St.ButtonMask.THREE,
|
||||
toggle_mode: true,
|
||||
reactive: this._reactive,
|
||||
checked: pageIndex == this._currentPage });
|
||||
indicator.child = new St.Widget({ style_class: 'page-indicator-icon' });
|
||||
indicator.connect('clicked', () => {
|
||||
this.emit('page-activated', pageIndex);
|
||||
});
|
||||
this.add_actor(indicator);
|
||||
}
|
||||
} else {
|
||||
let children = this.get_children().splice(diff);
|
||||
for (let i = 0; i < children.length; i++)
|
||||
children[i].destroy();
|
||||
}
|
||||
this._nPages = nPages;
|
||||
this.visible = (this._nPages > 1);
|
||||
}
|
||||
|
||||
setCurrentPage(currentPage) {
|
||||
this._currentPage = currentPage;
|
||||
|
||||
let children = this.get_children();
|
||||
for (let i = 0; i < children.length; i++)
|
||||
children[i].set_checked(i == this._currentPage);
|
||||
}
|
||||
});
|
||||
|
||||
var AnimatedPageIndicators = GObject.registerClass(
|
||||
class AnimatedPageIndicators extends PageIndicators {
|
||||
_init() {
|
||||
super._init(true);
|
||||
|
||||
this.connect('notify::mapped', () => {
|
||||
this.animateIndicators(AnimationDirection.IN);
|
||||
});
|
||||
}
|
||||
|
||||
animateIndicators(animationDirection) {
|
||||
if (!this.mapped)
|
||||
return;
|
||||
|
||||
let children = this.get_children();
|
||||
if (children.length == 0)
|
||||
return;
|
||||
|
||||
for (let i = 0; i < this._nPages; i++)
|
||||
Tweener.removeTweens(children[i]);
|
||||
|
||||
let offset;
|
||||
if (this.get_text_direction() == Clutter.TextDirection.RTL)
|
||||
offset = -children[0].width;
|
||||
else
|
||||
offset = children[0].width;
|
||||
|
||||
let isAnimationIn = animationDirection == AnimationDirection.IN;
|
||||
let delay = isAnimationIn ? INDICATORS_ANIMATION_DELAY :
|
||||
INDICATORS_ANIMATION_DELAY_OUT;
|
||||
let baseTime = isAnimationIn ? INDICATORS_BASE_TIME : INDICATORS_BASE_TIME_OUT;
|
||||
let totalAnimationTime = baseTime + delay * this._nPages;
|
||||
let maxTime = isAnimationIn ? INDICATORS_ANIMATION_MAX_TIME :
|
||||
INDICATORS_ANIMATION_MAX_TIME_OUT;
|
||||
if (totalAnimationTime > maxTime)
|
||||
delay -= (totalAnimationTime - maxTime) / this._nPages;
|
||||
|
||||
for (let i = 0; i < this._nPages; i++) {
|
||||
children[i].translation_x = isAnimationIn ? offset : 0;
|
||||
Tweener.addTween(children[i], {
|
||||
translation_x: isAnimationIn ? 0 : offset,
|
||||
time: baseTime + delay * i,
|
||||
transition: 'easeInOutQuad',
|
||||
delay: isAnimationIn ? ANIMATION_DELAY : 0
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
256
js/ui/panel.js
256
js/ui/panel.js
@@ -21,7 +21,6 @@ const DND = imports.ui.dnd;
|
||||
const Overview = imports.ui.overview;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const RemoteMenu = imports.ui.remoteMenu;
|
||||
const Main = imports.ui.main;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
@@ -75,6 +74,119 @@ function _unpremultiply(color) {
|
||||
blue: blue, alpha: color.alpha });
|
||||
};
|
||||
|
||||
class AppMenu extends PopupMenu.PopupMenu {
|
||||
constructor(sourceActor) {
|
||||
super(sourceActor, 0.0, St.Side.TOP);
|
||||
|
||||
this.actor.add_style_class_name('app-menu');
|
||||
|
||||
this._app = null;
|
||||
this._appSystem = Shell.AppSystem.get_default();
|
||||
|
||||
this._windowsChangedId = 0;
|
||||
|
||||
this._windowSection = new PopupMenu.PopupMenuSection();
|
||||
this.addMenuItem(this._windowSection);
|
||||
|
||||
this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
|
||||
this._newWindowItem = this.addAction(_("New Window"), () => {
|
||||
this._app.open_new_window(-1);
|
||||
});
|
||||
|
||||
this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
|
||||
this._actionSection = new PopupMenu.PopupMenuSection();
|
||||
this.addMenuItem(this._actionSection);
|
||||
|
||||
this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
|
||||
this._detailsItem = this.addAction(_("Show Details"), () => {
|
||||
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.addAction(_("Quit"), () => {
|
||||
this._app.request_quit();
|
||||
});
|
||||
|
||||
this._appSystem.connect('installed-changed', () => {
|
||||
let sw = this._appSystem.lookup_app('org.gnome.Software.desktop');
|
||||
this._detailsItem.actor.visible = (sw != null);
|
||||
});
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
if (!this._app)
|
||||
return true;
|
||||
return super.isEmpty();
|
||||
}
|
||||
|
||||
setApp(app) {
|
||||
if (this._app == app)
|
||||
return;
|
||||
|
||||
if (this._windowsChangedId)
|
||||
this._app.disconnect(this._windowsChangedId);
|
||||
this._windowsChangedId = 0;
|
||||
|
||||
this._app = app;
|
||||
|
||||
if (app) {
|
||||
this._windowsChangedId = app.connect('windows-changed', () => {
|
||||
this._updateWindowsSection();
|
||||
});
|
||||
}
|
||||
|
||||
this._updateWindowsSection();
|
||||
|
||||
let appInfo = app ? app.app_info : null;
|
||||
let actions = appInfo ? appInfo.list_actions() : [];
|
||||
|
||||
this._actionSection.removeAll();
|
||||
actions.forEach(action => {
|
||||
let label = appInfo.get_action_name(action);
|
||||
this._actionSection.addAction(label, event => {
|
||||
this._app.launch_action(action, event.get_time(), -1);
|
||||
});
|
||||
});
|
||||
|
||||
this._newWindowItem.actor.visible =
|
||||
app && app.can_open_new_window() && !actions.includes('new-window');
|
||||
}
|
||||
|
||||
_updateWindowsSection() {
|
||||
this._windowSection.removeAll();
|
||||
|
||||
if (!this._app)
|
||||
return;
|
||||
|
||||
let windows = this._app.get_windows();
|
||||
windows.forEach(window => {
|
||||
let title = window.title || this._app.get_name();
|
||||
this._windowSection.addAction(title, event => {
|
||||
Main.activateWindow(window, event.get_time());
|
||||
});
|
||||
});
|
||||
|
||||
// Add separator between windows of the current desktop and other windows.
|
||||
let workspaceManager = global.workspace_manager;
|
||||
let activeWorkspace = workspaceManager.get_active_workspace();
|
||||
let pos = windows.findIndex(w => w.get_workspace() != activeWorkspace);
|
||||
if (pos >= 0)
|
||||
this._windowSection.addMenuItem(new PopupMenu.PopupSeparatorMenuItem(), pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AppMenuButton:
|
||||
*
|
||||
@@ -96,8 +208,6 @@ var AppMenuButton = GObject.registerClass({
|
||||
this._menuManager = panel.menuManager;
|
||||
this._gtkSettings = Gtk.Settings.get_default();
|
||||
this._targetApp = null;
|
||||
this._appMenuNotifyId = 0;
|
||||
this._actionGroupNotifyId = 0;
|
||||
this._busyNotifyId = 0;
|
||||
|
||||
let bin = new St.Bin({ name: 'appMenu' });
|
||||
@@ -114,28 +224,36 @@ var AppMenuButton = GObject.registerClass({
|
||||
textureCache.connect('icon-theme-changed',
|
||||
this._onIconThemeChanged.bind(this));
|
||||
|
||||
let iconEffect = new Clutter.DesaturateEffect();
|
||||
this._iconBox = new St.Bin({ style_class: 'app-menu-icon' });
|
||||
this._iconBox.add_effect(iconEffect);
|
||||
this._container.add_actor(this._iconBox);
|
||||
|
||||
this._iconBox.connect('style-changed', () => {
|
||||
let themeNode = this._iconBox.get_theme_node();
|
||||
iconEffect.enabled = themeNode.get_icon_style() == St.IconStyle.SYMBOLIC;
|
||||
});
|
||||
|
||||
this._label = new St.Label({ y_expand: true,
|
||||
y_align: Clutter.ActorAlign.CENTER });
|
||||
this._container.add_actor(this._label);
|
||||
this._arrow = PopupMenu.arrowIcon(St.Side.BOTTOM);
|
||||
this._container.add_actor(this._arrow);
|
||||
|
||||
this._visible = this._gtkSettings.gtk_shell_shows_app_menu &&
|
||||
!Main.overview.visible;
|
||||
this._visible = !Main.overview.visible;
|
||||
if (!this._visible)
|
||||
this.hide();
|
||||
this._overviewHidingId = Main.overview.connect('hiding', this._sync.bind(this));
|
||||
this._overviewShowingId = Main.overview.connect('showing', this._sync.bind(this));
|
||||
this._showsAppMenuId = this._gtkSettings.connect('notify::gtk-shell-shows-app-menu',
|
||||
this._sync.bind(this));
|
||||
|
||||
this._stop = true;
|
||||
|
||||
this._spinner = null;
|
||||
|
||||
let menu = new AppMenu(this);
|
||||
this.setMenu(menu);
|
||||
this._menuManager.addMenu(menu);
|
||||
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
let appSys = Shell.AppSystem.get_default();
|
||||
this._focusAppNotifyId =
|
||||
@@ -282,14 +400,6 @@ var AppMenuButton = GObject.registerClass({
|
||||
let targetApp = this._findTargetApp();
|
||||
|
||||
if (this._targetApp != targetApp) {
|
||||
if (this._appMenuNotifyId) {
|
||||
this._targetApp.disconnect(this._appMenuNotifyId);
|
||||
this._appMenuNotifyId = 0;
|
||||
}
|
||||
if (this._actionGroupNotifyId) {
|
||||
this._targetApp.disconnect(this._actionGroupNotifyId);
|
||||
this._actionGroupNotifyId = 0;
|
||||
}
|
||||
if (this._busyNotifyId) {
|
||||
this._targetApp.disconnect(this._busyNotifyId);
|
||||
this._busyNotifyId = 0;
|
||||
@@ -298,20 +408,13 @@ var AppMenuButton = GObject.registerClass({
|
||||
this._targetApp = targetApp;
|
||||
|
||||
if (this._targetApp) {
|
||||
this._appMenuNotifyId = this._targetApp.connect('notify::menu', this._sync.bind(this));
|
||||
this._actionGroupNotifyId = this._targetApp.connect('notify::action-group', this._sync.bind(this));
|
||||
this._busyNotifyId = this._targetApp.connect('notify::busy', this._sync.bind(this));
|
||||
this._label.set_text(this._targetApp.get_name());
|
||||
this.actor.set_accessible_name(this._targetApp.get_name());
|
||||
}
|
||||
}
|
||||
|
||||
let shellShowsAppMenu = this._gtkSettings.gtk_shell_shows_app_menu;
|
||||
Meta.prefs_set_show_fallback_app_menu(!shellShowsAppMenu);
|
||||
|
||||
let visible = (this._targetApp != null &&
|
||||
shellShowsAppMenu &&
|
||||
!Main.overview.visibleTarget);
|
||||
let visible = (this._targetApp != null && !Main.overview.visibleTarget);
|
||||
if (visible)
|
||||
this.fadeIn();
|
||||
else
|
||||
@@ -328,43 +431,10 @@ var AppMenuButton = GObject.registerClass({
|
||||
this.actor.reactive = (visible && !isBusy);
|
||||
|
||||
this._syncIcon();
|
||||
this._maybeSetMenu();
|
||||
this.menu.setApp(this._targetApp);
|
||||
this.emit('changed');
|
||||
}
|
||||
|
||||
_maybeSetMenu() {
|
||||
let menu;
|
||||
|
||||
if (this._targetApp == null) {
|
||||
menu = null;
|
||||
} else if (this._targetApp.action_group && this._targetApp.menu) {
|
||||
if (this.menu instanceof RemoteMenu.RemoteMenu &&
|
||||
this.menu.actionGroup == this._targetApp.action_group)
|
||||
return;
|
||||
|
||||
menu = new RemoteMenu.RemoteMenu(this.actor, this._targetApp.menu, this._targetApp.action_group);
|
||||
menu.connect('activate', () => {
|
||||
let win = this._targetApp.get_windows()[0];
|
||||
win.check_alive(global.get_current_time());
|
||||
});
|
||||
|
||||
} else {
|
||||
if (this.menu && this.menu.isDummyQuitMenu)
|
||||
return;
|
||||
|
||||
// fallback to older menu
|
||||
menu = new PopupMenu.PopupMenu(this.actor, 0.0, St.Side.TOP, 0);
|
||||
menu.isDummyQuitMenu = true;
|
||||
menu.addAction(_("Quit"), () => {
|
||||
this._targetApp.request_quit();
|
||||
});
|
||||
}
|
||||
|
||||
this.setMenu(menu);
|
||||
if (menu)
|
||||
this._menuManager.addMenu(menu);
|
||||
}
|
||||
|
||||
_onDestroy() {
|
||||
if (this._appStateChangedSignalId > 0) {
|
||||
let appSys = Shell.AppSystem.get_default();
|
||||
@@ -384,10 +454,6 @@ var AppMenuButton = GObject.registerClass({
|
||||
Main.overview.disconnect(this._overviewShowingId);
|
||||
this._overviewShowingId = 0;
|
||||
}
|
||||
if (this._showsAppMenuId > 0) {
|
||||
this._gtkSettings.disconnect(this._showsAppMenuId);
|
||||
this._showsAppMenuId = 0;
|
||||
}
|
||||
if (this._switchWorkspaceNotifyId > 0) {
|
||||
global.window_manager.disconnect(this._switchWorkspaceNotifyId);
|
||||
this._switchWorkspaceNotifyId = 0;
|
||||
@@ -797,11 +863,9 @@ class Panel extends St.Widget {
|
||||
|
||||
Main.overview.connect('showing', () => {
|
||||
this.add_style_pseudo_class('overview');
|
||||
this._updateSolidStyle();
|
||||
});
|
||||
Main.overview.connect('hiding', () => {
|
||||
this.remove_style_pseudo_class('overview');
|
||||
this._updateSolidStyle();
|
||||
});
|
||||
|
||||
Main.layoutManager.panelBox.add(this);
|
||||
@@ -810,32 +874,11 @@ class Panel extends St.Widget {
|
||||
|
||||
Main.sessionMode.connect('updated', this._updatePanel.bind(this));
|
||||
|
||||
this._trackedWindows = new Map();
|
||||
global.window_group.connect('actor-added', this._onWindowActorAdded.bind(this));
|
||||
global.window_group.connect('actor-removed', this._onWindowActorRemoved.bind(this));
|
||||
global.window_manager.connect('switch-workspace', this._updateSolidStyle.bind(this));
|
||||
|
||||
global.display.connect('workareas-changed', () => { this.queue_relayout(); });
|
||||
this._updatePanel();
|
||||
}
|
||||
|
||||
_onWindowActorAdded(container, metaWindowActor) {
|
||||
let signalIds = [];
|
||||
['allocation-changed', 'notify::visible'].forEach(s => {
|
||||
signalIds.push(metaWindowActor.connect(s, this._updateSolidStyle.bind(this)));
|
||||
});
|
||||
this._trackedWindows.set(metaWindowActor, signalIds);
|
||||
}
|
||||
|
||||
_onWindowActorRemoved(container, metaWindowActor) {
|
||||
this._trackedWindows.get(metaWindowActor).forEach(id => {
|
||||
metaWindowActor.disconnect(id);
|
||||
});
|
||||
this._trackedWindows.delete(metaWindowActor);
|
||||
this._updateSolidStyle();
|
||||
}
|
||||
|
||||
vfunc_get_preferred_width(actor, forHeight) {
|
||||
vfunc_get_preferred_width(forHeight) {
|
||||
let primaryMonitor = Main.layoutManager.primaryMonitor;
|
||||
|
||||
if (primaryMonitor)
|
||||
@@ -980,8 +1023,8 @@ class Panel extends St.Widget {
|
||||
}
|
||||
|
||||
_toggleMenu(indicator) {
|
||||
if (!indicator) // menu not supported by current session mode
|
||||
return;
|
||||
if (!indicator || !indicator.container.visible)
|
||||
return; // menu not supported by current session mode
|
||||
|
||||
let menu = indicator.menu;
|
||||
if (!indicator.actor.reactive)
|
||||
@@ -989,7 +1032,7 @@ class Panel extends St.Widget {
|
||||
|
||||
menu.toggle();
|
||||
if (menu.isOpen)
|
||||
menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
menu.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
||||
}
|
||||
|
||||
toggleAppMenu() {
|
||||
@@ -1042,8 +1085,6 @@ class Panel extends St.Widget {
|
||||
else
|
||||
Main.messageTray.bannerAlignment = Clutter.ActorAlign.CENTER;
|
||||
|
||||
this._updateSolidStyle();
|
||||
|
||||
if (this._sessionStyle)
|
||||
this._removeStyleClassName(this._sessionStyle);
|
||||
|
||||
@@ -1060,41 +1101,6 @@ class Panel extends St.Widget {
|
||||
}
|
||||
}
|
||||
|
||||
_updateSolidStyle() {
|
||||
if (this.has_style_pseudo_class('overview') || !Main.sessionMode.hasWindows) {
|
||||
this._removeStyleClassName('solid');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Main.layoutManager.primaryMonitor)
|
||||
return;
|
||||
|
||||
/* Get all the windows in the active workspace that are in the primary monitor and visible */
|
||||
let workspaceManager = global.workspace_manager;
|
||||
let activeWorkspace = workspaceManager.get_active_workspace();
|
||||
let windows = activeWorkspace.list_windows().filter(metaWindow => {
|
||||
return metaWindow.is_on_primary_monitor() &&
|
||||
metaWindow.showing_on_its_workspace() &&
|
||||
!metaWindow.is_hidden() &&
|
||||
metaWindow.get_window_type() != Meta.WindowType.DESKTOP;
|
||||
});
|
||||
|
||||
/* Check if at least one window is near enough to the panel */
|
||||
let [, panelTop] = this.get_transformed_position();
|
||||
let panelBottom = panelTop + this.get_height();
|
||||
let scale = St.ThemeContext.get_for_stage(global.stage).scale_factor;
|
||||
let isNearEnough = windows.some(metaWindow => {
|
||||
let verticalPosition = metaWindow.get_frame_rect().y;
|
||||
return verticalPosition < panelBottom + 5 * scale;
|
||||
});
|
||||
|
||||
if (isNearEnough)
|
||||
this._addStyleClassName('solid');
|
||||
else
|
||||
this._removeStyleClassName('solid');
|
||||
|
||||
}
|
||||
|
||||
_hideIndicators() {
|
||||
for (let role in PANEL_ITEM_IMPLEMENTATIONS) {
|
||||
let indicator = this.statusArea[role];
|
||||
|
@@ -3,7 +3,6 @@
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
@@ -163,7 +162,7 @@ var Button = GObject.registerClass({
|
||||
if (symbol == Clutter.KEY_Left || symbol == Clutter.KEY_Right) {
|
||||
let group = global.focus_manager.get_group(this.actor);
|
||||
if (group) {
|
||||
let direction = (symbol == Clutter.KEY_Left) ? Gtk.DirectionType.LEFT : Gtk.DirectionType.RIGHT;
|
||||
let direction = (symbol == Clutter.KEY_Left) ? St.DirectionType.LEFT : St.DirectionType.RIGHT;
|
||||
group.navigate_focus(this.actor, direction, false);
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
|
@@ -1,7 +1,6 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Shell = imports.gi.Shell;
|
||||
@@ -543,7 +542,7 @@ var PopupMenuBase = class {
|
||||
let sensitive = menuItem.getSensitive();
|
||||
if (!sensitive && this._activeMenuItem == menuItem) {
|
||||
if (!this.actor.navigate_focus(menuItem.actor,
|
||||
Gtk.DirectionType.TAB_FORWARD,
|
||||
St.DirectionType.TAB_FORWARD,
|
||||
true))
|
||||
this.actor.grab_key_focus();
|
||||
} else if (sensitive && this._activeMenuItem == null) {
|
||||
@@ -829,7 +828,7 @@ var PopupMenu = class extends PopupMenuBase {
|
||||
} else if (symbol == navKey) {
|
||||
if (!this.isOpen)
|
||||
this.toggle();
|
||||
this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
this.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
||||
return Clutter.EVENT_STOP;
|
||||
} else
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
@@ -914,8 +913,8 @@ var PopupSubMenu = class extends PopupMenuBase {
|
||||
// with long content, we make it scrollable - the scrollbar will only take
|
||||
// effect if a CSS max-height is set on the top menu.
|
||||
this.actor = new St.ScrollView({ style_class: 'popup-sub-menu',
|
||||
hscrollbar_policy: Gtk.PolicyType.NEVER,
|
||||
vscrollbar_policy: Gtk.PolicyType.NEVER });
|
||||
hscrollbar_policy: St.PolicyType.NEVER,
|
||||
vscrollbar_policy: St.PolicyType.NEVER });
|
||||
|
||||
this.actor.add_actor(this.box);
|
||||
this.actor._delegate = this;
|
||||
@@ -957,7 +956,7 @@ var PopupSubMenu = class extends PopupMenuBase {
|
||||
// when we *don't* need it, so turn off the scrollbar when that's true.
|
||||
// Dynamic changes in whether we need it aren't handled properly.
|
||||
this.actor.vscrollbar_policy =
|
||||
needsScrollbar ? Gtk.PolicyType.AUTOMATIC : Gtk.PolicyType.NEVER;
|
||||
needsScrollbar ? St.PolicyType.AUTOMATIC : St.PolicyType.NEVER;
|
||||
|
||||
if (needsScrollbar)
|
||||
this.actor.add_style_pseudo_class('scrolled');
|
||||
@@ -1150,7 +1149,7 @@ var PopupSubMenuMenuItem = class extends PopupBaseMenuItem {
|
||||
|
||||
if (symbol == Clutter.KEY_Right) {
|
||||
this._setOpenState(true);
|
||||
this.menu.actor.navigate_focus(null, Gtk.DirectionType.DOWN, false);
|
||||
this.menu.actor.navigate_focus(null, St.DirectionType.DOWN, false);
|
||||
return Clutter.EVENT_STOP;
|
||||
} else if (symbol == Clutter.KEY_Left && this._getOpenState()) {
|
||||
this._setOpenState(false);
|
||||
|
@@ -1,199 +0,0 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Atk = imports.gi.Atk;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Shell = imports.gi.Shell;
|
||||
const ShellMenu = imports.gi.ShellMenu;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
|
||||
function stripMnemonics(label) {
|
||||
if (!label)
|
||||
return '';
|
||||
|
||||
// remove all underscores that are not followed by another underscore
|
||||
return label.replace(/_([^_])/, '$1');
|
||||
}
|
||||
|
||||
function _insertItem(menu, trackerItem, position) {
|
||||
let mapper;
|
||||
|
||||
if (trackerItem.get_is_separator())
|
||||
mapper = new RemoteMenuSeparatorItemMapper(trackerItem);
|
||||
else if (trackerItem.get_has_submenu())
|
||||
mapper = new RemoteMenuSubmenuItemMapper(trackerItem);
|
||||
else
|
||||
mapper = new RemoteMenuItemMapper(trackerItem);
|
||||
|
||||
let item = mapper.menuItem;
|
||||
menu.addMenuItem(item, position);
|
||||
}
|
||||
|
||||
function _removeItem(menu, position) {
|
||||
let items = menu._getMenuItems();
|
||||
items[position].destroy();
|
||||
}
|
||||
|
||||
var RemoteMenuSeparatorItemMapper = class {
|
||||
constructor(trackerItem) {
|
||||
this._trackerItem = trackerItem;
|
||||
this.menuItem = new PopupMenu.PopupSeparatorMenuItem();
|
||||
this._trackerItem.connect('notify::label', this._updateLabel.bind(this));
|
||||
this._updateLabel();
|
||||
|
||||
this.menuItem.connect('destroy', () => {
|
||||
trackerItem.run_dispose();
|
||||
});
|
||||
}
|
||||
|
||||
_updateLabel() {
|
||||
this.menuItem.label.text = stripMnemonics(this._trackerItem.label);
|
||||
}
|
||||
};
|
||||
|
||||
var RequestSubMenu = class extends PopupMenu.PopupSubMenuMenuItem {
|
||||
constructor() {
|
||||
super('');
|
||||
this._requestOpen = false;
|
||||
}
|
||||
|
||||
_setOpenState(open) {
|
||||
this.emit('request-open', open);
|
||||
this._requestOpen = open;
|
||||
}
|
||||
|
||||
_getOpenState() {
|
||||
return this._requestOpen;
|
||||
}
|
||||
};
|
||||
|
||||
var RemoteMenuSubmenuItemMapper = class {
|
||||
constructor(trackerItem) {
|
||||
this._trackerItem = trackerItem;
|
||||
this.menuItem = new RequestSubMenu();
|
||||
this._trackerItem.connect('notify::label', this._updateLabel.bind(this));
|
||||
this._updateLabel();
|
||||
|
||||
this._tracker = Shell.MenuTracker.new_for_item_submenu(this._trackerItem,
|
||||
_insertItem.bind(null, this.menuItem.menu),
|
||||
_removeItem.bind(null, this.menuItem.menu));
|
||||
|
||||
this.menuItem.connect('request-open', (menu, open) => {
|
||||
this._trackerItem.request_submenu_shown(open);
|
||||
});
|
||||
|
||||
this._trackerItem.connect('notify::submenu-shown', () => {
|
||||
this.menuItem.setSubmenuShown(this._trackerItem.get_submenu_shown());
|
||||
});
|
||||
|
||||
this.menuItem.connect('destroy', () => {
|
||||
trackerItem.run_dispose();
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._tracker.destroy();
|
||||
}
|
||||
|
||||
_updateLabel() {
|
||||
this.menuItem.label.text = stripMnemonics(this._trackerItem.label);
|
||||
}
|
||||
};
|
||||
|
||||
var RemoteMenuItemMapper = class {
|
||||
constructor(trackerItem) {
|
||||
this._trackerItem = trackerItem;
|
||||
|
||||
this.menuItem = new PopupMenu.PopupBaseMenuItem();
|
||||
this._icon = new St.Icon({ style_class: 'popup-menu-icon' });
|
||||
this.menuItem.actor.add_child(this._icon);
|
||||
|
||||
this._label = new St.Label();
|
||||
this.menuItem.actor.add_child(this._label);
|
||||
this.menuItem.actor.label_actor = this._label;
|
||||
|
||||
this.menuItem.connect('activate', () => {
|
||||
this._trackerItem.activated();
|
||||
});
|
||||
|
||||
this._trackerItem.bind_property('visible', this.menuItem.actor, 'visible', GObject.BindingFlags.SYNC_CREATE);
|
||||
|
||||
this._trackerItem.connect('notify::icon', this._updateIcon.bind(this));
|
||||
this._trackerItem.connect('notify::label', this._updateLabel.bind(this));
|
||||
this._trackerItem.connect('notify::sensitive', this._updateSensitivity.bind(this));
|
||||
this._trackerItem.connect('notify::role', this._updateRole.bind(this));
|
||||
this._trackerItem.connect('notify::toggled', this._updateDecoration.bind(this));
|
||||
|
||||
this._updateIcon();
|
||||
this._updateLabel();
|
||||
this._updateSensitivity();
|
||||
this._updateRole();
|
||||
|
||||
this.menuItem.connect('destroy', () => {
|
||||
trackerItem.run_dispose();
|
||||
});
|
||||
}
|
||||
|
||||
_updateIcon() {
|
||||
this._icon.gicon = this._trackerItem.icon;
|
||||
this._icon.visible = (this._icon.gicon != null);
|
||||
}
|
||||
|
||||
_updateLabel() {
|
||||
this._label.text = stripMnemonics(this._trackerItem.label);
|
||||
}
|
||||
|
||||
_updateSensitivity() {
|
||||
this.menuItem.setSensitive(this._trackerItem.sensitive);
|
||||
}
|
||||
|
||||
_updateDecoration() {
|
||||
let ornamentForRole = {};
|
||||
ornamentForRole[ShellMenu.MenuTrackerItemRole.RADIO] = PopupMenu.Ornament.DOT;
|
||||
ornamentForRole[ShellMenu.MenuTrackerItemRole.CHECK] = PopupMenu.Ornament.CHECK;
|
||||
|
||||
let ornament = PopupMenu.Ornament.NONE;
|
||||
if (this._trackerItem.toggled)
|
||||
ornament = ornamentForRole[this._trackerItem.role];
|
||||
|
||||
this.menuItem.setOrnament(ornament);
|
||||
}
|
||||
|
||||
_updateRole() {
|
||||
let a11yRoles = {};
|
||||
a11yRoles[ShellMenu.MenuTrackerItemRole.NORMAL] = Atk.Role.MENU_ITEM;
|
||||
a11yRoles[ShellMenu.MenuTrackerItemRole.RADIO] = Atk.Role.RADIO_MENU_ITEM;
|
||||
a11yRoles[ShellMenu.MenuTrackerItemRole.CHECK] = Atk.Role.CHECK_MENU_ITEM;
|
||||
|
||||
let a11yRole = a11yRoles[this._trackerItem.role];
|
||||
this.menuItem.actor.accessible_role = a11yRole;
|
||||
|
||||
this._updateDecoration();
|
||||
}
|
||||
};
|
||||
|
||||
var RemoteMenu = class extends PopupMenu.PopupMenu {
|
||||
constructor(sourceActor, model, actionGroup) {
|
||||
super(sourceActor, 0.0, St.Side.TOP);
|
||||
|
||||
this._model = model;
|
||||
this._actionGroup = actionGroup;
|
||||
this._tracker = Shell.MenuTracker.new(this._actionGroup,
|
||||
this._model,
|
||||
null, /* action namespace */
|
||||
_insertItem.bind(null, this),
|
||||
_removeItem.bind(null, this));
|
||||
}
|
||||
|
||||
get actionGroup() {
|
||||
return this._actionGroup;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._tracker.destroy();
|
||||
super.destroy();
|
||||
}
|
||||
};
|
@@ -2,12 +2,12 @@
|
||||
|
||||
const AccountsService = imports.gi.AccountsService;
|
||||
const Cairo = imports.cairo;
|
||||
const Cogl = imports.gi.Cogl;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GnomeDesktop = imports.gi.GnomeDesktop;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
@@ -100,7 +100,7 @@ var NotificationsBox = class {
|
||||
style_class: 'screen-shield-notifications-container' });
|
||||
|
||||
this._scrollView = new St.ScrollView({ x_fill: false, x_align: St.Align.START,
|
||||
hscrollbar_policy: Gtk.PolicyType.NEVER });
|
||||
hscrollbar_policy: St.PolicyType.NEVER });
|
||||
this._notificationBox = new St.BoxLayout({ vertical: true,
|
||||
style_class: 'screen-shield-notifications-container' });
|
||||
this._scrollView.add_actor(this._notificationBox);
|
||||
@@ -202,6 +202,11 @@ var NotificationsBox = class {
|
||||
return [title, null];
|
||||
}
|
||||
|
||||
_shouldShowDetails(source) {
|
||||
return source.policy.detailsInLockScreen ||
|
||||
source.narrowestPrivacyScope == MessageTray.PrivacyScope.SYSTEM;
|
||||
}
|
||||
|
||||
_showSource(source, obj, box) {
|
||||
if (obj.detailed) {
|
||||
[obj.titleLabel, obj.countLabel] = this._makeNotificationDetailedSource(source, box);
|
||||
@@ -215,7 +220,7 @@ var NotificationsBox = class {
|
||||
_sourceAdded(tray, source, initial) {
|
||||
let obj = {
|
||||
visible: source.policy.showInLockScreen,
|
||||
detailed: source.policy.detailsInLockScreen,
|
||||
detailed: this._shouldShowDetails(source),
|
||||
sourceDestroyId: 0,
|
||||
sourceCountChangedId: 0,
|
||||
sourceTitleChangedId: 0,
|
||||
@@ -252,7 +257,7 @@ var NotificationsBox = class {
|
||||
// block scrollbars while animating, if they're not needed now
|
||||
let boxHeight = this._notificationBox.height;
|
||||
if (this._scrollView.height >= boxHeight)
|
||||
this._scrollView.vscrollbar_policy = Gtk.PolicyType.NEVER;
|
||||
this._scrollView.vscrollbar_policy = St.PolicyType.NEVER;
|
||||
|
||||
let widget = obj.sourceBox;
|
||||
let [, natHeight] = widget.get_preferred_height(-1);
|
||||
@@ -262,7 +267,7 @@ var NotificationsBox = class {
|
||||
transition: 'easeOutQuad',
|
||||
time: 0.25,
|
||||
onComplete() {
|
||||
this._scrollView.vscrollbar_policy = Gtk.PolicyType.AUTOMATIC;
|
||||
this._scrollView.vscrollbar_policy = St.PolicyType.AUTOMATIC;
|
||||
widget.set_height(-1);
|
||||
},
|
||||
onCompleteScope: this
|
||||
@@ -279,7 +284,14 @@ var NotificationsBox = class {
|
||||
}
|
||||
|
||||
_countChanged(source, obj) {
|
||||
if (obj.detailed) {
|
||||
// A change in the number of notifications may change whether we show
|
||||
// details.
|
||||
let newDetailed = this._shouldShowDetails(source);
|
||||
let oldDetailed = obj.detailed;
|
||||
|
||||
obj.detailed = newDetailed;
|
||||
|
||||
if (obj.detailed || oldDetailed != newDetailed) {
|
||||
// A new notification was pushed, or a previous notification was destroyed.
|
||||
// Give up, and build the list again.
|
||||
|
||||
@@ -311,10 +323,11 @@ var NotificationsBox = class {
|
||||
}
|
||||
|
||||
_detailedChanged(source, obj) {
|
||||
if (obj.detailed == source.policy.detailsInLockScreen)
|
||||
let newDetailed = this._shouldShowDetails(source);
|
||||
if (obj.detailed == newDetailed)
|
||||
return;
|
||||
|
||||
obj.detailed = source.policy.detailsInLockScreen;
|
||||
obj.detailed = newDetailed;
|
||||
|
||||
obj.sourceBox.destroy_all_children();
|
||||
obj.titleLabel = obj.countLabel = null;
|
||||
@@ -405,7 +418,9 @@ class ScreenShieldArrow extends St.Bin {
|
||||
|
||||
let allocation = this._drawingArea.get_allocation_box();
|
||||
let paintOpacity = this._drawingArea.get_paint_opacity();
|
||||
this._shadowHelper.paint(allocation, paintOpacity);
|
||||
let framebuffer = Cogl.get_draw_framebuffer();
|
||||
|
||||
this._shadowHelper.paint(framebuffer, allocation, paintOpacity);
|
||||
}
|
||||
|
||||
this._drawingArea.paint();
|
||||
@@ -918,7 +933,7 @@ var ScreenShield = class {
|
||||
|
||||
if (this._dialog) {
|
||||
this._dialog.actor.grab_key_focus();
|
||||
this._dialog.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
this._dialog.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
@@ -107,7 +106,7 @@ var ScreenshotService = class {
|
||||
}
|
||||
|
||||
ScreenshotAreaAsync(params, invocation) {
|
||||
let [x, y, width, height, flash, filename, callback] = params;
|
||||
let [x, y, width, height, flash, filename] = params;
|
||||
[x, y, width, height] = this._scaleArea(x, y, width, height);
|
||||
if (!this._checkArea(x, y, width, height)) {
|
||||
invocation.return_error_literal(Gio.IOErrorEnum,
|
||||
|
@@ -4,7 +4,6 @@ const Clutter = imports.gi.Clutter;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Signals = imports.signals;
|
||||
const Shell = imports.gi.Shell;
|
||||
@@ -402,7 +401,7 @@ var SearchResults = class {
|
||||
y_fill: false,
|
||||
overlay_scrollbars: true,
|
||||
style_class: 'search-display vfade' });
|
||||
this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
this._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
|
||||
this._scrollView.add_actor(scrollChild);
|
||||
let action = new Clutter.PanAction({ interpolate: true });
|
||||
action.connect('pan', this._onPan.bind(this));
|
||||
@@ -686,10 +685,10 @@ var SearchResults = class {
|
||||
|
||||
navigateFocus(direction) {
|
||||
let rtl = this.actor.get_text_direction() == Clutter.TextDirection.RTL;
|
||||
if (direction == Gtk.DirectionType.TAB_BACKWARD ||
|
||||
direction == (rtl ? Gtk.DirectionType.RIGHT
|
||||
: Gtk.DirectionType.LEFT) ||
|
||||
direction == Gtk.DirectionType.UP) {
|
||||
if (direction == St.DirectionType.TAB_BACKWARD ||
|
||||
direction == (rtl ? St.DirectionType.RIGHT
|
||||
: St.DirectionType.LEFT) ||
|
||||
direction == St.DirectionType.UP) {
|
||||
this.actor.navigate_focus(null, direction, false);
|
||||
return;
|
||||
}
|
||||
|
@@ -1,13 +1,13 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const BoxPointer = imports.ui.boxpointer;
|
||||
const Main = imports.ui.main;
|
||||
const Params = imports.misc.params;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
var EntryMenu = class extends PopupMenu.PopupMenu {
|
||||
constructor(entry) {
|
||||
@@ -68,7 +68,7 @@ var EntryMenu = class extends PopupMenu.PopupMenu {
|
||||
super.open(animate);
|
||||
this._entry.add_style_pseudo_class('focus');
|
||||
|
||||
let direction = Gtk.DirectionType.TAB_FORWARD;
|
||||
let direction = St.DirectionType.TAB_FORWARD;
|
||||
if (!this.actor.navigate_focus(null, direction, false))
|
||||
this.actor.grab_key_focus();
|
||||
}
|
||||
@@ -146,11 +146,12 @@ function addContextMenu(entry, params) {
|
||||
if (entry.menu)
|
||||
return;
|
||||
|
||||
params = Params.parse (params, { isPassword: false });
|
||||
params = Params.parse (params, { isPassword: false, actionMode: Shell.ActionMode.POPUP });
|
||||
|
||||
entry.menu = new EntryMenu(entry);
|
||||
entry.menu.isPassword = params.isPassword;
|
||||
entry._menuManager = new PopupMenu.PopupMenuManager({ actor: entry });
|
||||
entry._menuManager = new PopupMenu.PopupMenuManager({ actor: entry },
|
||||
{ actionMode: params.actionMode });
|
||||
entry._menuManager.addMenu(entry.menu);
|
||||
|
||||
// Add an event handler to both the entry and its clutter_text; the former
|
||||
|
@@ -4,7 +4,6 @@ const Clutter = imports.gi.Clutter;
|
||||
const Signals = imports.signals;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Pango = imports.gi.Pango;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
@@ -371,8 +370,8 @@ var ShellProcessesDialog = class extends ModalDialog.ModalDialog {
|
||||
this.contentLayout.add(this._content, { x_fill: true, y_fill: false });
|
||||
|
||||
let scrollView = new St.ScrollView({ style_class: 'mount-dialog-app-list'});
|
||||
scrollView.set_policy(Gtk.PolicyType.NEVER,
|
||||
Gtk.PolicyType.AUTOMATIC);
|
||||
scrollView.set_policy(St.PolicyType.NEVER,
|
||||
St.PolicyType.AUTOMATIC);
|
||||
this.contentLayout.add(scrollView,
|
||||
{ x_fill: true,
|
||||
y_fill: true });
|
||||
|
@@ -29,7 +29,6 @@ const KEY_VISUAL_BELL = 'visual-bell';
|
||||
const DESKTOP_INTERFACE_SCHEMA = 'org.gnome.desktop.interface';
|
||||
const KEY_GTK_THEME = 'gtk-theme';
|
||||
const KEY_ICON_THEME = 'icon-theme';
|
||||
const KEY_WM_THEME = 'theme';
|
||||
const KEY_TEXT_SCALING_FACTOR = 'text-scaling-factor';
|
||||
|
||||
const HIGH_CONTRAST_THEME = 'HighContrast';
|
||||
@@ -132,7 +131,6 @@ class ATIndicator extends PanelMenu.Button {
|
||||
|
||||
_buildHCItem() {
|
||||
let interfaceSettings = new Gio.Settings({ schema_id: DESKTOP_INTERFACE_SCHEMA });
|
||||
let wmSettings = new Gio.Settings({ schema_id: WM_SCHEMA });
|
||||
interfaceSettings.connect('changed::' + KEY_GTK_THEME, () => {
|
||||
let value = interfaceSettings.get_string(KEY_GTK_THEME);
|
||||
if (value == HIGH_CONTRAST_THEME) {
|
||||
@@ -149,35 +147,25 @@ class ATIndicator extends PanelMenu.Button {
|
||||
if (value != HIGH_CONTRAST_THEME)
|
||||
iconTheme = value;
|
||||
});
|
||||
wmSettings.connect('changed::' + KEY_WM_THEME, () => {
|
||||
let value = wmSettings.get_string(KEY_WM_THEME);
|
||||
if (value != HIGH_CONTRAST_THEME)
|
||||
wmTheme = value;
|
||||
});
|
||||
|
||||
let gtkTheme = interfaceSettings.get_string(KEY_GTK_THEME);
|
||||
let iconTheme = interfaceSettings.get_string(KEY_ICON_THEME);
|
||||
let wmTheme = wmSettings.get_string(KEY_WM_THEME);
|
||||
let hasHC = (gtkTheme == HIGH_CONTRAST_THEME);
|
||||
let highContrast = this._buildItemExtended(
|
||||
_("High Contrast"),
|
||||
hasHC,
|
||||
interfaceSettings.is_writable(KEY_GTK_THEME) &&
|
||||
interfaceSettings.is_writable(KEY_ICON_THEME) &&
|
||||
wmSettings.is_writable(KEY_WM_THEME),
|
||||
interfaceSettings.is_writable(KEY_ICON_THEME),
|
||||
enabled => {
|
||||
if (enabled) {
|
||||
interfaceSettings.set_string(KEY_GTK_THEME, HIGH_CONTRAST_THEME);
|
||||
interfaceSettings.set_string(KEY_ICON_THEME, HIGH_CONTRAST_THEME);
|
||||
wmSettings.set_string(KEY_WM_THEME, HIGH_CONTRAST_THEME);
|
||||
} else if(!hasHC) {
|
||||
interfaceSettings.set_string(KEY_GTK_THEME, gtkTheme);
|
||||
interfaceSettings.set_string(KEY_ICON_THEME, iconTheme);
|
||||
wmSettings.set_string(KEY_WM_THEME, wmTheme);
|
||||
} else {
|
||||
interfaceSettings.reset(KEY_GTK_THEME);
|
||||
interfaceSettings.reset(KEY_ICON_THEME);
|
||||
wmSettings.reset(KEY_WM_THEME);
|
||||
}
|
||||
});
|
||||
return highContrast;
|
||||
|
@@ -969,7 +969,8 @@ class InputSourceIndicator extends PanelMenu.Button {
|
||||
}
|
||||
|
||||
let item;
|
||||
switch (prop.get_prop_type()) {
|
||||
let type = prop.get_prop_type();
|
||||
switch (type) {
|
||||
case IBus.PropType.MENU:
|
||||
item = new PopupMenu.PopupSubMenuMenuItem(prop.get_label().get_text());
|
||||
this._buildPropSubMenu(item.menu, prop.get_sub_props());
|
||||
|
@@ -3,7 +3,6 @@ const Clutter = imports.gi.Clutter;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Mainloop = imports.mainloop;
|
||||
const NM = imports.gi.NM;
|
||||
const Signals = imports.signals;
|
||||
@@ -846,8 +845,8 @@ var NMWirelessDialog = class extends ModalDialog.ModalDialog {
|
||||
this._scrollView = new St.ScrollView({ style_class: 'nm-dialog-scroll-view' });
|
||||
this._scrollView.set_x_expand(true);
|
||||
this._scrollView.set_y_expand(true);
|
||||
this._scrollView.set_policy(Gtk.PolicyType.NEVER,
|
||||
Gtk.PolicyType.AUTOMATIC);
|
||||
this._scrollView.set_policy(St.PolicyType.NEVER,
|
||||
St.PolicyType.AUTOMATIC);
|
||||
this._scrollView.add_actor(this._itemBox);
|
||||
this._stack.add_child(this._scrollView);
|
||||
|
||||
|
@@ -4,6 +4,7 @@ const AccountsService = imports.gi.AccountsService;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Pango = imports.gi.Pango;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
const GObject = imports.gi.GObject;
|
||||
@@ -163,18 +164,15 @@ var Indicator = class extends PanelMenu.SystemIndicator {
|
||||
}
|
||||
|
||||
_updateSwitchUserSubMenu() {
|
||||
this._switchUserSubMenu.label.text = this._user.get_real_name();
|
||||
let clutterText = this._switchUserSubMenu.label.clutter_text;
|
||||
let realName = this._user.get_real_name();
|
||||
|
||||
// XXX -- for some reason, the ClutterText's width changes
|
||||
// rapidly unless we force a relayout of the actor. Probably
|
||||
// a size cache issue or something. Moving this to be a layout
|
||||
// manager would be a much better idea.
|
||||
clutterText.get_allocation_box();
|
||||
|
||||
let layout = clutterText.get_layout();
|
||||
if (layout.is_ellipsized())
|
||||
// In theory, GNOME allows creating users with names up to 255
|
||||
// characters, but such long names look terribly bad, so limit
|
||||
// to 100 and it should fit the vast majority of screen sizes.
|
||||
if (realName.length > 100)
|
||||
this._switchUserSubMenu.label.text = this._user.get_user_name();
|
||||
else
|
||||
this._switchUserSubMenu.label.text = realName;
|
||||
|
||||
let iconFile = this._user.get_icon_file();
|
||||
if (iconFile && !GLib.file_test(iconFile, GLib.FileTest.EXISTS))
|
||||
@@ -211,6 +209,7 @@ var Indicator = class extends PanelMenu.SystemIndicator {
|
||||
|
||||
this._switchUserSubMenu = new PopupMenu.PopupSubMenuMenuItem('', true);
|
||||
this._switchUserSubMenu.icon.style_class = 'system-switch-user-submenu-icon';
|
||||
this._switchUserSubMenu.label.clutter_text.ellipsize = Pango.EllipsizeMode.END;
|
||||
|
||||
// Since the label of the switch user submenu depends on the width of
|
||||
// the popup menu, and we can't easily connect on allocation-changed
|
||||
|
@@ -334,7 +334,7 @@ var Indicator = class extends PanelMenu.SystemIndicator {
|
||||
|
||||
_onEnrollFailed(obj, device, error) {
|
||||
const title = _("Thunderbolt authorization error");
|
||||
const body = _("Could not authorize the Thunderbolt device: %s".format(error.message));
|
||||
const body = _("Could not authorize the Thunderbolt device: %s").format(error.message);
|
||||
this._notify(title, body);
|
||||
}
|
||||
};
|
||||
|
@@ -3,7 +3,6 @@
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
@@ -365,7 +364,7 @@ var SwitcherList = GObject.registerClass({
|
||||
|
||||
this._scrollView = new St.ScrollView({ style_class: 'hfade',
|
||||
enable_mouse_scrolling: false });
|
||||
this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER);
|
||||
this._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.NEVER);
|
||||
|
||||
this._scrollView.add_actor(this._list);
|
||||
this.add_actor(this._scrollView);
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
@@ -52,7 +51,7 @@ function _wrapTweening(target, tweeningParameters) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!Gtk.Settings.get_default().gtk_enable_animations) {
|
||||
if (!St.Settings.get().enable_animations) {
|
||||
tweeningParameters['time'] = 0.000001;
|
||||
tweeningParameters['delay'] = 0.000001;
|
||||
}
|
||||
|
@@ -7,7 +7,6 @@ const Gdm = imports.gi.Gdm;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GnomeDesktop = imports.gi.GnomeDesktop;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Signals = imports.signals;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
@@ -7,6 +7,7 @@ const AccountsService = imports.gi.AccountsService;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GObject = imports.gi.GObject;
|
||||
const Pango = imports.gi.Pango;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Params = imports.misc.params;
|
||||
@@ -72,6 +73,7 @@ class UserWidgetLabel extends St.Widget {
|
||||
|
||||
this._realNameLabel = new St.Label({ style_class: 'user-widget-label',
|
||||
y_align: Clutter.ActorAlign.CENTER });
|
||||
this._realNameLabel.clutter_text.ellipsize = Pango.EllipsizeMode.END;
|
||||
this.add_child(this._realNameLabel);
|
||||
|
||||
this._userNameLabel = new St.Label({ style_class: 'user-widget-label',
|
||||
|
@@ -3,7 +3,6 @@
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Signals = imports.signals;
|
||||
@@ -34,8 +33,8 @@ var ViewPage = {
|
||||
var FocusTrap = GObject.registerClass(
|
||||
class FocusTrap extends St.Widget {
|
||||
vfunc_navigate_focus(from, direction) {
|
||||
if (direction == Gtk.DirectionType.TAB_FORWARD ||
|
||||
direction == Gtk.DirectionType.TAB_BACKWARD)
|
||||
if (direction == St.DirectionType.TAB_FORWARD ||
|
||||
direction == St.DirectionType.TAB_BACKWARD)
|
||||
return super.vfunc_navigate_focus(from, direction);
|
||||
return false;
|
||||
}
|
||||
@@ -82,7 +81,7 @@ var ShowOverviewAction = GObject.registerClass({
|
||||
});
|
||||
}
|
||||
|
||||
vfunc_gesture_prepare(action, actor) {
|
||||
vfunc_gesture_prepare(actor) {
|
||||
return Main.actionMode == Shell.ActionMode.NORMAL &&
|
||||
this.get_n_current_points() == this.get_n_touch_points();
|
||||
}
|
||||
@@ -116,12 +115,12 @@ var ShowOverviewAction = GObject.registerClass({
|
||||
height: maxY - minY });
|
||||
}
|
||||
|
||||
vfunc_gesture_begin(action, actor) {
|
||||
vfunc_gesture_begin(actor) {
|
||||
this._initialRect = this._getBoundingRect(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
vfunc_gesture_end(action, actor) {
|
||||
vfunc_gesture_end(actor) {
|
||||
let rect = this._getBoundingRect(true);
|
||||
let oldArea = this._initialRect.width * this._initialRect.height;
|
||||
let newArea = rect.width * rect.height;
|
||||
@@ -397,7 +396,7 @@ var ViewSelector = class {
|
||||
|
||||
_a11yFocusPage(page) {
|
||||
this._showAppsButton.checked = page == this._appsPage;
|
||||
page.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
page.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
||||
}
|
||||
|
||||
_onShowAppsButtonToggled() {
|
||||
@@ -426,10 +425,10 @@ var ViewSelector = class {
|
||||
this.startSearch(event);
|
||||
} else if (!this._searchActive && !global.stage.key_focus) {
|
||||
if (symbol == Clutter.Tab || symbol == Clutter.Down) {
|
||||
this._activePage.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
this._activePage.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
||||
return Clutter.EVENT_STOP;
|
||||
} else if (symbol == Clutter.ISO_Left_Tab) {
|
||||
this._activePage.navigate_focus(null, Gtk.DirectionType.TAB_BACKWARD, false);
|
||||
this._activePage.navigate_focus(null, St.DirectionType.TAB_BACKWARD, false);
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
}
|
||||
@@ -557,22 +556,22 @@ var ViewSelector = class {
|
||||
let arrowNext, nextDirection;
|
||||
if (entry.get_text_direction() == Clutter.TextDirection.RTL) {
|
||||
arrowNext = Clutter.Left;
|
||||
nextDirection = Gtk.DirectionType.LEFT;
|
||||
nextDirection = St.DirectionType.LEFT;
|
||||
} else {
|
||||
arrowNext = Clutter.Right;
|
||||
nextDirection = Gtk.DirectionType.RIGHT;
|
||||
nextDirection = St.DirectionType.RIGHT;
|
||||
}
|
||||
|
||||
if (symbol == Clutter.Tab) {
|
||||
this._searchResults.navigateFocus(Gtk.DirectionType.TAB_FORWARD);
|
||||
this._searchResults.navigateFocus(St.DirectionType.TAB_FORWARD);
|
||||
return Clutter.EVENT_STOP;
|
||||
} else if (symbol == Clutter.ISO_Left_Tab) {
|
||||
this._focusTrap.can_focus = false;
|
||||
this._searchResults.navigateFocus(Gtk.DirectionType.TAB_BACKWARD);
|
||||
this._searchResults.navigateFocus(St.DirectionType.TAB_BACKWARD);
|
||||
this._focusTrap.can_focus = true;
|
||||
return Clutter.EVENT_STOP;
|
||||
} else if (symbol == Clutter.Down) {
|
||||
this._searchResults.navigateFocus(Gtk.DirectionType.DOWN);
|
||||
this._searchResults.navigateFocus(St.DirectionType.DOWN);
|
||||
return Clutter.EVENT_STOP;
|
||||
} else if (symbol == arrowNext && this._text.position == -1) {
|
||||
this._searchResults.navigateFocus(nextDirection);
|
||||
|
@@ -12,6 +12,7 @@ const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const AltTab = imports.ui.altTab;
|
||||
const AppFavorites = imports.ui.appFavorites;
|
||||
const Dialog = imports.ui.dialog;
|
||||
const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup;
|
||||
const InhibitShortcutsDialog = imports.ui.inhibitShortcutsDialog;
|
||||
@@ -599,7 +600,7 @@ var AppSwitchAction = GObject.registerClass({
|
||||
});
|
||||
}
|
||||
|
||||
vfunc_gesture_prepare(action, actor) {
|
||||
vfunc_gesture_prepare(actor) {
|
||||
if (Main.actionMode != Shell.ActionMode.NORMAL) {
|
||||
this.cancel();
|
||||
return false;
|
||||
@@ -608,7 +609,7 @@ var AppSwitchAction = GObject.registerClass({
|
||||
return this.get_n_current_points() <= 4;
|
||||
}
|
||||
|
||||
vfunc_gesture_begin(action, actor) {
|
||||
vfunc_gesture_begin(actor) {
|
||||
// in milliseconds
|
||||
const LONG_PRESS_TIMEOUT = 250;
|
||||
|
||||
@@ -632,7 +633,7 @@ var AppSwitchAction = GObject.registerClass({
|
||||
return this.get_n_current_points() <= 4;
|
||||
}
|
||||
|
||||
vfunc_gesture_progress(action, actor) {
|
||||
vfunc_gesture_progress(actor) {
|
||||
const MOTION_THRESHOLD = 30;
|
||||
|
||||
if (this.get_n_current_points() == 3) {
|
||||
@@ -938,6 +939,69 @@ var WindowManager = class {
|
||||
Shell.ActionMode.POPUP,
|
||||
this._toggleCalendar.bind(this));
|
||||
|
||||
this.addKeybinding('switch-to-application-1',
|
||||
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
|
||||
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
|
||||
Shell.ActionMode.NORMAL |
|
||||
Shell.ActionMode.OVERVIEW,
|
||||
this._switchToApplication.bind(this));
|
||||
|
||||
this.addKeybinding('switch-to-application-2',
|
||||
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
|
||||
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
|
||||
Shell.ActionMode.NORMAL |
|
||||
Shell.ActionMode.OVERVIEW,
|
||||
this._switchToApplication.bind(this));
|
||||
|
||||
this.addKeybinding('switch-to-application-3',
|
||||
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
|
||||
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
|
||||
Shell.ActionMode.NORMAL |
|
||||
Shell.ActionMode.OVERVIEW,
|
||||
this._switchToApplication.bind(this));
|
||||
|
||||
this.addKeybinding('switch-to-application-4',
|
||||
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
|
||||
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
|
||||
Shell.ActionMode.NORMAL |
|
||||
Shell.ActionMode.OVERVIEW,
|
||||
this._switchToApplication.bind(this));
|
||||
|
||||
this.addKeybinding('switch-to-application-5',
|
||||
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
|
||||
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
|
||||
Shell.ActionMode.NORMAL |
|
||||
Shell.ActionMode.OVERVIEW,
|
||||
this._switchToApplication.bind(this));
|
||||
|
||||
this.addKeybinding('switch-to-application-6',
|
||||
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
|
||||
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
|
||||
Shell.ActionMode.NORMAL |
|
||||
Shell.ActionMode.OVERVIEW,
|
||||
this._switchToApplication.bind(this));
|
||||
|
||||
this.addKeybinding('switch-to-application-7',
|
||||
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
|
||||
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
|
||||
Shell.ActionMode.NORMAL |
|
||||
Shell.ActionMode.OVERVIEW,
|
||||
this._switchToApplication.bind(this));
|
||||
|
||||
this.addKeybinding('switch-to-application-8',
|
||||
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
|
||||
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
|
||||
Shell.ActionMode.NORMAL |
|
||||
Shell.ActionMode.OVERVIEW,
|
||||
this._switchToApplication.bind(this));
|
||||
|
||||
this.addKeybinding('switch-to-application-9',
|
||||
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
|
||||
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
|
||||
Shell.ActionMode.NORMAL |
|
||||
Shell.ActionMode.OVERVIEW,
|
||||
this._switchToApplication.bind(this));
|
||||
|
||||
global.display.connect('show-resize-popup', this._showResizePopup.bind(this));
|
||||
global.display.connect('show-pad-osd', this._showPadOsd.bind(this));
|
||||
global.display.connect('show-osd', (display, monitorIndex, iconName, label) => {
|
||||
@@ -1007,6 +1071,10 @@ var WindowManager = class {
|
||||
gesture.connect('activated', () => {
|
||||
Main.keyboard.show(Main.layoutManager.bottomIndex);
|
||||
});
|
||||
Main.layoutManager.connect('keyboard-visible-changed', (manager, visible) => {
|
||||
gesture.cancel();
|
||||
gesture.set_enabled(!visible);
|
||||
});
|
||||
global.stage.add_action(gesture);
|
||||
|
||||
gesture = new EdgeDragAction.EdgeDragAction(St.Side.TOP, mode);
|
||||
@@ -2012,6 +2080,14 @@ var WindowManager = class {
|
||||
Main.ctrlAltTabManager.popup(binding.is_reversed(), binding.get_name(), binding.get_mask());
|
||||
}
|
||||
|
||||
_switchToApplication(display, window, binding) {
|
||||
let [,,,target] = binding.get_name().split('-');
|
||||
let apps = AppFavorites.getAppFavorites().getFavorites();
|
||||
let app = apps[target - 1];
|
||||
if (app)
|
||||
app.activate();
|
||||
}
|
||||
|
||||
_toggleAppMenu(display, window, event, binding) {
|
||||
Main.panel.toggleAppMenu();
|
||||
}
|
||||
|
@@ -1,14 +1,11 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*
|
||||
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Meta = imports.gi.Meta;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const BoxPointer = imports.ui.boxpointer;
|
||||
const Main = imports.ui.main;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const RemoteMenu = imports.ui.remoteMenu;
|
||||
|
||||
var WindowMenu = class extends PopupMenu.PopupMenu {
|
||||
constructor(window, sourceActor) {
|
||||
@@ -175,22 +172,6 @@ var WindowMenu = class extends PopupMenu.PopupMenu {
|
||||
}
|
||||
};
|
||||
|
||||
var AppMenu = class extends RemoteMenu.RemoteMenu {
|
||||
constructor(window, sourceActor) {
|
||||
let app = Shell.WindowTracker.get_default().get_window_app(window);
|
||||
|
||||
super(sourceActor, app.menu, app.action_group);
|
||||
|
||||
this.actor.add_style_class_name('fallback-app-menu');
|
||||
let variant = window.get_gtk_theme_variant();
|
||||
if (variant)
|
||||
this.actor.add_style_class_name(variant);
|
||||
|
||||
Main.layoutManager.uiGroup.add_actor(this.actor);
|
||||
this.actor.hide();
|
||||
}
|
||||
};
|
||||
|
||||
var WindowMenuManager = class {
|
||||
constructor() {
|
||||
this._manager = new PopupMenu.PopupMenuManager({ actor: Main.layoutManager.dummyCursor });
|
||||
@@ -203,8 +184,9 @@ var WindowMenuManager = class {
|
||||
}
|
||||
|
||||
showWindowMenuForWindow(window, type, rect) {
|
||||
let menuType = (type == Meta.WindowMenuType.WM) ? WindowMenu : AppMenu;
|
||||
let menu = new menuType(window, this._sourceActor);
|
||||
if (type != Meta.WindowMenuType.WM)
|
||||
throw new Error('Unsupported window menu type');
|
||||
let menu = new WindowMenu(window, this._sourceActor);
|
||||
|
||||
this._manager.addMenu(menu);
|
||||
|
||||
@@ -220,7 +202,7 @@ var WindowMenuManager = class {
|
||||
this._sourceActor.show();
|
||||
|
||||
menu.open(BoxPointer.PopupAnimation.NONE);
|
||||
menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
menu.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
||||
menu.connect('open-state-changed', (menu_, isOpen) => {
|
||||
if (isOpen)
|
||||
return;
|
||||
|
@@ -1,5 +1,6 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Atk = imports.gi.Atk;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GObject = imports.gi.GObject;
|
||||
@@ -7,10 +8,8 @@ const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Pango = imports.gi.Pango;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
const Atk = imports.gi.Atk;
|
||||
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const DND = imports.ui.dnd;
|
||||
const Main = imports.ui.main;
|
||||
@@ -22,7 +21,10 @@ var WINDOW_DND_SIZE = 256;
|
||||
|
||||
var WINDOW_CLONE_MAXIMUM_SCALE = 1.0;
|
||||
|
||||
var CLOSE_BUTTON_FADE_TIME = 0.1;
|
||||
var WINDOW_OVERLAY_IDLE_HIDE_TIMEOUT = 750;
|
||||
var WINDOW_OVERLAY_FADE_TIME = 0.1;
|
||||
|
||||
var WINDOW_REPOSITIONING_DELAY = 750;
|
||||
|
||||
var DRAGGING_WINDOW_OPACITY = 100;
|
||||
|
||||
@@ -289,8 +291,7 @@ var WindowClone = class {
|
||||
this.actor.layout_manager.boundingBox = rect;
|
||||
}
|
||||
|
||||
// Find the actor just below us, respecting reparenting done
|
||||
// by DND code
|
||||
// Find the actor just below us, respecting reparenting done by DND code
|
||||
getActualStackAbove() {
|
||||
if (this._stackAbove == null)
|
||||
return null;
|
||||
@@ -441,7 +442,7 @@ Signals.addSignalMethods(WindowClone.prototype);
|
||||
/**
|
||||
* @windowClone: Corresponding window clone
|
||||
* @parentActor: The actor which will be the parent of all overlay items
|
||||
* such as app icon and window caption
|
||||
* such as the close button and window caption
|
||||
*/
|
||||
var WindowOverlay = class {
|
||||
constructor(windowClone, parentActor) {
|
||||
@@ -451,13 +452,15 @@ var WindowOverlay = class {
|
||||
this._parentActor = parentActor;
|
||||
this._hidden = false;
|
||||
|
||||
this._idleHideOverlayId = 0;
|
||||
|
||||
this.borderSize = 0;
|
||||
this.border = new St.Bin({ style_class: 'window-clone-border' });
|
||||
|
||||
let title = new St.Label({ style_class: 'window-caption',
|
||||
text: this._getCaption() });
|
||||
title.clutter_text.ellipsize = Pango.EllipsizeMode.END;
|
||||
windowClone.actor.label_actor = title;
|
||||
this.title = new St.Label({ style_class: 'window-caption',
|
||||
text: this._getCaption() });
|
||||
this.title.clutter_text.ellipsize = Pango.EllipsizeMode.END;
|
||||
windowClone.actor.label_actor = this.title;
|
||||
|
||||
this._maxTitleWidth = -1;
|
||||
|
||||
@@ -466,22 +469,18 @@ var WindowOverlay = class {
|
||||
this.relayout(false);
|
||||
});
|
||||
|
||||
let button = new St.Button({ style_class: 'window-close' });
|
||||
button.add_actor(new St.Icon({ icon_name: 'window-close-symbolic' }));
|
||||
button._overlap = 0;
|
||||
this.closeButton = new St.Button({ style_class: 'window-close' });
|
||||
this.closeButton.add_actor(new St.Icon({ icon_name: 'window-close-symbolic' }));
|
||||
this.closeButton._overlap = 0;
|
||||
|
||||
this._idleToggleCloseId = 0;
|
||||
button.connect('clicked', () => this._windowClone.deleteAll());
|
||||
this.closeButton.connect('clicked', () => this._windowClone.deleteAll());
|
||||
|
||||
windowClone.actor.connect('destroy', this._onDestroy.bind(this));
|
||||
windowClone.connect('show-chrome', this._onShowChrome.bind(this));
|
||||
windowClone.connect('hide-chrome', this._onHideChrome.bind(this));
|
||||
|
||||
button.hide();
|
||||
title.hide();
|
||||
|
||||
this.title = title;
|
||||
this.closeButton = button;
|
||||
this.title.hide();
|
||||
this.closeButton.hide();
|
||||
|
||||
// Don't block drop targets
|
||||
Shell.util_set_hidden_from_pick(this.title, true);
|
||||
@@ -490,12 +489,14 @@ var WindowOverlay = class {
|
||||
parentActor.add_actor(this.border);
|
||||
parentActor.add_actor(this.title);
|
||||
parentActor.add_actor(this.closeButton);
|
||||
title.connect('style-changed',
|
||||
this._onStyleChanged.bind(this));
|
||||
button.connect('style-changed',
|
||||
this._onStyleChanged.bind(this));
|
||||
this.border.connect('style-changed', this._onStyleChanged.bind(this));
|
||||
// force a style change if we are already on a stage - otherwise
|
||||
this.title.connect('style-changed',
|
||||
this._onStyleChanged.bind(this));
|
||||
this.closeButton.connect('style-changed',
|
||||
this._onStyleChanged.bind(this));
|
||||
this.border.connect('style-changed',
|
||||
this._onStyleChanged.bind(this));
|
||||
|
||||
// Force a style change if we are already on a stage - otherwise
|
||||
// the signal will be emitted normally when we are added
|
||||
if (parentActor.get_stage())
|
||||
this._onStyleChanged();
|
||||
@@ -504,7 +505,7 @@ var WindowOverlay = class {
|
||||
hide() {
|
||||
this._hidden = true;
|
||||
|
||||
this.hideCloseButton();
|
||||
this.hideOverlay();
|
||||
}
|
||||
|
||||
show() {
|
||||
@@ -623,9 +624,9 @@ var WindowOverlay = class {
|
||||
}
|
||||
|
||||
_onDestroy() {
|
||||
if (this._idleToggleCloseId > 0) {
|
||||
Mainloop.source_remove(this._idleToggleCloseId);
|
||||
this._idleToggleCloseId = 0;
|
||||
if (this._idleHideOverlayId > 0) {
|
||||
Mainloop.source_remove(this._idleHideOverlayId);
|
||||
this._idleHideOverlayId = 0;
|
||||
}
|
||||
this._windowClone.metaWindow.disconnect(this._updateCaptionId);
|
||||
this.title.destroy();
|
||||
@@ -645,7 +646,7 @@ var WindowOverlay = class {
|
||||
a.opacity = 0;
|
||||
Tweener.addTween(a,
|
||||
{ opacity: 255,
|
||||
time: CLOSE_BUTTON_FADE_TIME,
|
||||
time: WINDOW_OVERLAY_FADE_TIME,
|
||||
transition: 'easeOutQuad' });
|
||||
});
|
||||
}
|
||||
@@ -655,7 +656,7 @@ var WindowOverlay = class {
|
||||
a.opacity = 255;
|
||||
Tweener.addTween(a,
|
||||
{ opacity: 0,
|
||||
time: CLOSE_BUTTON_FADE_TIME,
|
||||
time: WINDOW_OVERLAY_FADE_TIME,
|
||||
transition: 'easeInQuad' });
|
||||
});
|
||||
}
|
||||
@@ -669,18 +670,18 @@ var WindowOverlay = class {
|
||||
return;
|
||||
|
||||
this._animateVisible();
|
||||
this.emit('show-close-button');
|
||||
this.emit('chrome-visible');
|
||||
}
|
||||
|
||||
_onHideChrome() {
|
||||
if (this._idleToggleCloseId == 0) {
|
||||
this._idleToggleCloseId = Mainloop.timeout_add(750, this._idleToggleCloseButton.bind(this));
|
||||
GLib.Source.set_name_by_id(this._idleToggleCloseId, '[gnome-shell] this._idleToggleCloseButton');
|
||||
if (this._idleHideOverlayId == 0) {
|
||||
this._idleHideOverlayId = Mainloop.timeout_add(WINDOW_OVERLAY_IDLE_HIDE_TIMEOUT, this._idleHideOverlay.bind(this));
|
||||
GLib.Source.set_name_by_id(this._idleHideOverlayId, '[gnome-shell] this._idleHideOverlay');
|
||||
}
|
||||
}
|
||||
|
||||
_idleToggleCloseButton() {
|
||||
this._idleToggleCloseId = 0;
|
||||
_idleHideOverlay() {
|
||||
this._idleHideOverlayId = 0;
|
||||
|
||||
if (!this._windowClone.actor['has-pointer'] &&
|
||||
!this.closeButton['has-pointer'])
|
||||
@@ -689,10 +690,10 @@ var WindowOverlay = class {
|
||||
return GLib.SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
hideCloseButton() {
|
||||
if (this._idleToggleCloseId > 0) {
|
||||
Mainloop.source_remove(this._idleToggleCloseId);
|
||||
this._idleToggleCloseId = 0;
|
||||
hideOverlay() {
|
||||
if (this._idleHideOverlayId > 0) {
|
||||
Mainloop.source_remove(this._idleHideOverlayId);
|
||||
this._idleHideOverlayId = 0;
|
||||
}
|
||||
this.closeButton.hide();
|
||||
this.border.hide();
|
||||
@@ -1134,9 +1135,8 @@ var Workspace = class {
|
||||
this._windows = [];
|
||||
this._windowOverlays = [];
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
if (this._isOverviewWindow(windows[i])) {
|
||||
if (this._isOverviewWindow(windows[i]))
|
||||
this._addWindowClone(windows[i], true);
|
||||
}
|
||||
}
|
||||
|
||||
// Track window changes
|
||||
@@ -1206,12 +1206,7 @@ var Workspace = class {
|
||||
}
|
||||
|
||||
_lookupIndex(metaWindow) {
|
||||
for (let i = 0; i < this._windows.length; i++) {
|
||||
if (this._windows[i].metaWindow == metaWindow) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
return this._windows.findIndex(w => w.metaWindow == metaWindow);
|
||||
}
|
||||
|
||||
containsMetaWindow(metaWindow) {
|
||||
@@ -1302,8 +1297,7 @@ var Workspace = class {
|
||||
for (let i = 0; i < slots.length; i++) {
|
||||
let slot = slots[i];
|
||||
let [x, y, scale, clone] = slot;
|
||||
let metaWindow = clone.metaWindow;
|
||||
let overlay = clone.overlay;
|
||||
|
||||
clone.slotId = i;
|
||||
|
||||
// Positioning a window currently being dragged must be avoided;
|
||||
@@ -1319,10 +1313,10 @@ var Workspace = class {
|
||||
let maxChromeWidth = 2 * Math.min(
|
||||
cloneCenter - area.x,
|
||||
area.x + area.width - cloneCenter);
|
||||
overlay.setMaxChromeWidth(Math.round(maxChromeWidth));
|
||||
clone.overlay.setMaxChromeWidth(Math.round(maxChromeWidth));
|
||||
|
||||
if (overlay && (initialPositioning || !clone.positioned))
|
||||
overlay.hide();
|
||||
if (clone.overlay && (initialPositioning || !clone.positioned))
|
||||
clone.overlay.hide();
|
||||
|
||||
if (!clone.positioned) {
|
||||
// This window appeared after the overview was already up
|
||||
@@ -1335,7 +1329,7 @@ var Workspace = class {
|
||||
}
|
||||
|
||||
if (animate && isOnCurrentWorkspace) {
|
||||
if (!metaWindow.showing_on_its_workspace()) {
|
||||
if (!clone.metaWindow.showing_on_its_workspace()) {
|
||||
/* Hidden windows should fade in and grow
|
||||
* therefore we need to resize them now so they
|
||||
* can be scaled up later */
|
||||
@@ -1354,7 +1348,7 @@ var Workspace = class {
|
||||
});
|
||||
}
|
||||
|
||||
this._animateClone(clone, overlay, x, y, scale);
|
||||
this._animateClone(clone, clone.overlay, x, y, scale);
|
||||
} else {
|
||||
// cancel any active tweens (otherwise they might override our changes)
|
||||
Tweener.removeTweens(clone.actor);
|
||||
@@ -1362,7 +1356,7 @@ var Workspace = class {
|
||||
clone.actor.set_scale(scale, scale);
|
||||
clone.actor.set_opacity(255);
|
||||
clone.overlay.relayout(false);
|
||||
this._showWindowOverlay(clone, overlay);
|
||||
this._showWindowOverlay(clone, clone.overlay);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1477,7 +1471,7 @@ var Workspace = class {
|
||||
this._cursorY = y;
|
||||
|
||||
this._currentLayout = null;
|
||||
this._repositionWindowsId = Mainloop.timeout_add(750,
|
||||
this._repositionWindowsId = Mainloop.timeout_add(WINDOW_REPOSITIONING_DELAY,
|
||||
this._delayedWindowRepositioning.bind(this));
|
||||
GLib.Source.set_name_by_id(this._repositionWindowsId, '[gnome-shell] this._delayedWindowRepositioning');
|
||||
}
|
||||
@@ -1504,7 +1498,7 @@ var Workspace = class {
|
||||
|
||||
// We might have the window in our list already if it was on all workspaces and
|
||||
// now was moved to this workspace
|
||||
if (this._lookupIndex (metaWin) != -1)
|
||||
if (this._lookupIndex(metaWin) != -1)
|
||||
return;
|
||||
|
||||
if (!this._isMyWindow(win))
|
||||
@@ -1536,8 +1530,9 @@ var Workspace = class {
|
||||
|
||||
clone.slot = [x, y, clone.actor.width * scale, clone.actor.height * scale];
|
||||
clone.positioned = true;
|
||||
clone.actor.set_position (x, y);
|
||||
clone.actor.set_scale (scale, scale);
|
||||
|
||||
clone.actor.set_position(x, y);
|
||||
clone.actor.set_scale(scale, scale);
|
||||
clone.overlay.relayout(false);
|
||||
}
|
||||
|
||||
@@ -1855,11 +1850,15 @@ var Workspace = class {
|
||||
|
||||
this.actor.add_actor(clone.actor);
|
||||
|
||||
overlay.connect('show-close-button', () => {
|
||||
overlay.connect('chrome-visible', () => {
|
||||
let focus = global.stage.key_focus;
|
||||
if (focus == null || this.actor.contains(focus))
|
||||
clone.actor.grab_key_focus();
|
||||
this._onShowOverlayClose(overlay);
|
||||
|
||||
this._windowOverlays.forEach(o => {
|
||||
if (o != overlay)
|
||||
o.hideOverlay();
|
||||
});
|
||||
});
|
||||
|
||||
if (this._windows.length == 0)
|
||||
@@ -1875,7 +1874,7 @@ var Workspace = class {
|
||||
|
||||
_removeWindowClone(metaWin) {
|
||||
// find the position of the window in our list
|
||||
let index = this._lookupIndex (metaWin);
|
||||
let index = this._lookupIndex(metaWin);
|
||||
|
||||
if (index == -1)
|
||||
return null;
|
||||
@@ -1884,15 +1883,6 @@ var Workspace = class {
|
||||
return this._windows.splice(index, 1).pop();
|
||||
}
|
||||
|
||||
_onShowOverlayClose(windowOverlay) {
|
||||
for (let i = 0; i < this._windowOverlays.length; i++) {
|
||||
let overlay = this._windowOverlays[i];
|
||||
if (overlay == windowOverlay)
|
||||
continue;
|
||||
overlay.hideCloseButton();
|
||||
}
|
||||
}
|
||||
|
||||
_isBetterLayout(oldLayout, newLayout) {
|
||||
if (oldLayout.scale === undefined)
|
||||
return true;
|
||||
|
@@ -319,12 +319,7 @@ var WorkspaceThumbnail = class {
|
||||
}
|
||||
|
||||
_lookupIndex(metaWindow) {
|
||||
for (let i = 0; i < this._windows.length; i++) {
|
||||
if (this._windows[i].metaWindow == metaWindow) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
return this._windows.findIndex(w => w.metaWindow == metaWindow);
|
||||
}
|
||||
|
||||
syncStacking(stackIndices) {
|
||||
@@ -399,7 +394,7 @@ var WorkspaceThumbnail = class {
|
||||
|
||||
// We might have the window in our list already if it was on all workspaces and
|
||||
// now was moved to this workspace
|
||||
if (this._lookupIndex (metaWin) != -1)
|
||||
if (this._lookupIndex(metaWin) != -1)
|
||||
return;
|
||||
|
||||
if (!this._isMyWindow(win))
|
||||
@@ -412,7 +407,7 @@ var WorkspaceThumbnail = class {
|
||||
while (parent.is_attached_dialog())
|
||||
parent = parent.get_transient_for();
|
||||
|
||||
let idx = this._lookupIndex (parent);
|
||||
let idx = this._lookupIndex(parent);
|
||||
if (idx < 0) {
|
||||
// parent was not created yet, it will take care
|
||||
// of the dialog when created
|
||||
@@ -536,7 +531,7 @@ var WorkspaceThumbnail = class {
|
||||
|
||||
_removeWindowClone(metaWin) {
|
||||
// find the position of the window in our list
|
||||
let index = this._lookupIndex (metaWin);
|
||||
let index = this._lookupIndex(metaWin);
|
||||
|
||||
if (index == -1)
|
||||
return null;
|
||||
@@ -624,6 +619,10 @@ class ThumbnailsBox extends St.Widget {
|
||||
this._indicator = indicator;
|
||||
this.add_actor(indicator);
|
||||
|
||||
// The porthole is the part of the screen we're showing in the thumbnails
|
||||
this._porthole = { width: global.stage.width, height: global.stage.height,
|
||||
x: global.stage.x, y: global.stage.y };
|
||||
|
||||
this._dropWorkspace = -1;
|
||||
this._dropPlaceholderPos = -1;
|
||||
this._dropPlaceholder = new St.Bin({ style_class: 'placeholder' });
|
||||
@@ -675,6 +674,9 @@ class ThumbnailsBox extends St.Widget {
|
||||
this._createThumbnails();
|
||||
});
|
||||
|
||||
global.display.connect('workareas-changed',
|
||||
this._updatePorthole.bind(this));
|
||||
|
||||
this._switchWorkspaceNotifyId = 0;
|
||||
this._nWorkspacesNotifyId = 0;
|
||||
this._syncStackingId = 0;
|
||||
@@ -912,7 +914,6 @@ class ThumbnailsBox extends St.Widget {
|
||||
for (let w = 0; w < this._thumbnails.length; w++)
|
||||
this._thumbnails[w].destroy();
|
||||
this._thumbnails = [];
|
||||
this._porthole = null;
|
||||
}
|
||||
|
||||
_workspacesChanged() {
|
||||
@@ -945,8 +946,6 @@ class ThumbnailsBox extends St.Widget {
|
||||
addThumbnails(start, count) {
|
||||
let workspaceManager = global.workspace_manager;
|
||||
|
||||
if (!this._ensurePorthole())
|
||||
return;
|
||||
for (let k = start; k < start + count; k++) {
|
||||
let metaWorkspace = workspaceManager.get_workspace_by_index(k);
|
||||
let thumbnail = new WorkspaceThumbnail(metaWorkspace);
|
||||
@@ -1126,10 +1125,6 @@ class ThumbnailsBox extends St.Widget {
|
||||
// Note that for getPreferredWidth/Height we cheat a bit and skip propagating
|
||||
// the size request to our children because we know how big they are and know
|
||||
// that the actors aren't depending on the virtual functions being called.
|
||||
|
||||
if (!this._ensurePorthole())
|
||||
return [0, 0];
|
||||
|
||||
let workspaceManager = global.workspace_manager;
|
||||
let themeNode = this.get_theme_node();
|
||||
|
||||
@@ -1143,9 +1138,6 @@ class ThumbnailsBox extends St.Widget {
|
||||
}
|
||||
|
||||
vfunc_get_preferred_width(forHeight) {
|
||||
if (!this._ensurePorthole())
|
||||
return [0, 0];
|
||||
|
||||
let workspaceManager = global.workspace_manager;
|
||||
let themeNode = this.get_theme_node();
|
||||
|
||||
@@ -1165,16 +1157,14 @@ class ThumbnailsBox extends St.Widget {
|
||||
return themeNode.adjust_preferred_width(width, width);
|
||||
}
|
||||
|
||||
// The "porthole" is the portion of the screen that we show in the
|
||||
// workspaces
|
||||
_ensurePorthole() {
|
||||
if (!Main.layoutManager.primaryMonitor || !Main.overview.visible)
|
||||
return false;
|
||||
|
||||
if (!this._porthole)
|
||||
_updatePorthole() {
|
||||
if (!Main.layoutManager.primaryMonitor)
|
||||
this._porthole = { width: global.stage.width, height: global.stage.height,
|
||||
x: global.stage.x, y: global.stage.y };
|
||||
else
|
||||
this._porthole = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
||||
|
||||
return true;
|
||||
this.queue_relayout();
|
||||
}
|
||||
|
||||
vfunc_allocate(box, flags) {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
project('gnome-shell', 'c',
|
||||
version: '3.31.4',
|
||||
version: '3.31.90',
|
||||
meson_version: '>= 0.47.0',
|
||||
license: 'GPLv2+'
|
||||
)
|
||||
@@ -23,7 +23,7 @@ gi_req = '>= 1.49.1'
|
||||
gjs_req = '>= 1.47.0'
|
||||
gtk_req = '>= 3.15.0'
|
||||
json_glib_req = '>= 0.13.2'
|
||||
mutter_req = '>= 3.31.4'
|
||||
mutter_req = '>= 3.31.90'
|
||||
polkit_req = '>= 0.100'
|
||||
schemas_req = '>= 3.27.90'
|
||||
startup_req = '>= 0.11'
|
||||
|
@@ -1,596 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2013 Canonical Limited
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the licence, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Ryan Lortie <desrt@desrt.ca>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gtkmenutracker.h"
|
||||
|
||||
/**
|
||||
* SECTION:gtkmenutracker
|
||||
* @Title: GtkMenuTracker
|
||||
* @Short_description: A helper class for interpreting #GMenuModel
|
||||
*
|
||||
* #GtkMenuTracker is a simple object to ease implementations of #GMenuModel.
|
||||
* Given a #GtkActionObservable (usually a #GActionMuxer) along with a
|
||||
* #GMenuModel, it will tell you which menu items to create and where to place
|
||||
* them. If a menu item is removed, it will tell you the position of the menu
|
||||
* item to remove.
|
||||
*
|
||||
* Using #GtkMenuTracker is fairly simple. The only guarantee you must make
|
||||
* to #GtkMenuTracker is that you must obey all insert signals and track the
|
||||
* position of items that #GtkMenuTracker gives you. That is, #GtkMenuTracker
|
||||
* expects positions of all the latter items to change when it calls your
|
||||
* insertion callback with an early position, as it may ask you to remove
|
||||
* an item with a readjusted position later.
|
||||
*
|
||||
* #GtkMenuTracker will give you a #GtkMenuTrackerItem in your callback. You
|
||||
* must hold onto this object until a remove signal is emitted. This item
|
||||
* represents a single menu item, which can be one of three classes: normal item,
|
||||
* separator, or submenu.
|
||||
*
|
||||
* Certain properties on the #GtkMenuTrackerItem are mutable, and you must
|
||||
* listen for changes in the item. For more details, see the documentation
|
||||
* for #GtkMenuTrackerItem along with https://live.gnome.org/GApplication/GMenuModel.
|
||||
*
|
||||
* The idea of @with_separators is for special cases where menu models may
|
||||
* be tracked in places where separators are not available, like in toplevel
|
||||
* "File", "Edit" menu bars. Ignoring separator items is wrong, as #GtkMenuTracker
|
||||
* expects the position to change, so we must tell #GtkMenuTracker to ignore
|
||||
* separators itself.
|
||||
*/
|
||||
|
||||
typedef struct _GtkMenuTrackerSection GtkMenuTrackerSection;
|
||||
|
||||
struct _GtkMenuTracker
|
||||
{
|
||||
GtkActionObservable *observable;
|
||||
GtkMenuTrackerInsertFunc insert_func;
|
||||
GtkMenuTrackerRemoveFunc remove_func;
|
||||
gpointer user_data;
|
||||
|
||||
GtkMenuTrackerSection *toplevel;
|
||||
};
|
||||
|
||||
struct _GtkMenuTrackerSection
|
||||
{
|
||||
gpointer model; /* may be a GtkMenuTrackerItem or a GMenuModel */
|
||||
GSList *items;
|
||||
gchar *action_namespace;
|
||||
|
||||
guint separator_label : 1;
|
||||
guint with_separators : 1;
|
||||
guint has_separator : 1;
|
||||
guint is_fake : 1;
|
||||
|
||||
gulong handler;
|
||||
};
|
||||
|
||||
static GtkMenuTrackerSection * gtk_menu_tracker_section_new (GtkMenuTracker *tracker,
|
||||
GMenuModel *model,
|
||||
gboolean with_separators,
|
||||
gboolean separator_label,
|
||||
gint offset,
|
||||
const gchar *action_namespace);
|
||||
static void gtk_menu_tracker_section_free (GtkMenuTrackerSection *section);
|
||||
|
||||
static GtkMenuTrackerSection *
|
||||
gtk_menu_tracker_section_find_model (GtkMenuTrackerSection *section,
|
||||
gpointer model,
|
||||
gint *offset)
|
||||
{
|
||||
GSList *item;
|
||||
|
||||
if (section->has_separator)
|
||||
(*offset)++;
|
||||
|
||||
if (section->model == model)
|
||||
return section;
|
||||
|
||||
for (item = section->items; item; item = item->next)
|
||||
{
|
||||
GtkMenuTrackerSection *subsection = item->data;
|
||||
|
||||
if (subsection)
|
||||
{
|
||||
GtkMenuTrackerSection *found_section;
|
||||
|
||||
found_section = gtk_menu_tracker_section_find_model (subsection, model, offset);
|
||||
|
||||
if (found_section)
|
||||
return found_section;
|
||||
}
|
||||
else
|
||||
(*offset)++;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* this is responsible for syncing the showing of a separator for a
|
||||
* single subsection (and its children).
|
||||
*
|
||||
* we only ever show separators if we have _actual_ children (ie: we do
|
||||
* not show a separator if the section contains only empty child
|
||||
* sections). it's difficult to determine this on-the-fly, so we have
|
||||
* this separate function to come back later and figure it out.
|
||||
*
|
||||
* 'section' is that section.
|
||||
*
|
||||
* 'tracker' is passed in so that we can emit callbacks when we decide
|
||||
* to add/remove separators.
|
||||
*
|
||||
* 'offset' is passed in so we know which position to emit in our
|
||||
* callbacks. ie: if we add a separator right at the top of this
|
||||
* section then we would emit it with this offset. deeper inside, we
|
||||
* adjust accordingly.
|
||||
*
|
||||
* could_have_separator is true in two situations:
|
||||
*
|
||||
* - our parent section had with_separators defined and there are items
|
||||
* before us (ie: we should add a separator if we have content in
|
||||
* order to divide us from the items above)
|
||||
*
|
||||
* - if we had a 'label' attribute set for this section
|
||||
*
|
||||
* parent_model and parent_index are passed in so that we can give them
|
||||
* to the insertion callback so that it can see the label (and anything
|
||||
* else that happens to be defined on the section).
|
||||
*
|
||||
* we iterate each item in ourselves. for subsections, we recursively
|
||||
* run ourselves to sync separators. after we are done, we notice if we
|
||||
* have any items in us or if we are completely empty and sync if our
|
||||
* separator is shown or not.
|
||||
*/
|
||||
static gint
|
||||
gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section,
|
||||
GtkMenuTracker *tracker,
|
||||
gint offset,
|
||||
gboolean could_have_separator,
|
||||
GMenuModel *parent_model,
|
||||
gint parent_index)
|
||||
{
|
||||
gboolean should_have_separator;
|
||||
gint n_items = 0;
|
||||
GSList *item;
|
||||
gint i = 0;
|
||||
|
||||
for (item = section->items; item; item = item->next)
|
||||
{
|
||||
GtkMenuTrackerSection *subsection = item->data;
|
||||
|
||||
if (subsection)
|
||||
{
|
||||
gboolean section_could_have_separator;
|
||||
|
||||
section_could_have_separator = (section->with_separators && n_items > 0) || subsection->separator_label;
|
||||
|
||||
/* Only pass the parent_model and parent_index in case they may be used to create the separator. */
|
||||
n_items += gtk_menu_tracker_section_sync_separators (subsection, tracker, offset + n_items,
|
||||
section_could_have_separator,
|
||||
section_could_have_separator ? section->model : NULL,
|
||||
section_could_have_separator ? i : 0);
|
||||
}
|
||||
else
|
||||
n_items++;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
should_have_separator = !section->is_fake && could_have_separator && n_items != 0;
|
||||
|
||||
if (should_have_separator > section->has_separator)
|
||||
{
|
||||
/* Add a separator */
|
||||
GtkMenuTrackerItem *menuitem;
|
||||
|
||||
menuitem = _gtk_menu_tracker_item_new (tracker->observable, parent_model, parent_index, NULL, TRUE);
|
||||
(* tracker->insert_func) (menuitem, offset, tracker->user_data);
|
||||
g_object_unref (menuitem);
|
||||
|
||||
section->has_separator = TRUE;
|
||||
}
|
||||
else if (should_have_separator < section->has_separator)
|
||||
{
|
||||
/* Remove a separator */
|
||||
(* tracker->remove_func) (offset, tracker->user_data);
|
||||
section->has_separator = FALSE;
|
||||
}
|
||||
|
||||
n_items += section->has_separator;
|
||||
|
||||
return n_items;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_visibility_changed (GtkMenuTrackerItem *item,
|
||||
gboolean is_now_visible,
|
||||
gpointer user_data)
|
||||
{
|
||||
GtkMenuTracker *tracker = user_data;
|
||||
GtkMenuTrackerSection *section;
|
||||
gboolean was_visible;
|
||||
gint offset = 0;
|
||||
|
||||
/* remember: the item is our model */
|
||||
section = gtk_menu_tracker_section_find_model (tracker->toplevel, item, &offset);
|
||||
|
||||
was_visible = section->items != NULL;
|
||||
|
||||
if (is_now_visible == was_visible)
|
||||
return;
|
||||
|
||||
if (is_now_visible)
|
||||
{
|
||||
section->items = g_slist_prepend (NULL, NULL);
|
||||
(* tracker->insert_func) (section->model, offset, tracker->user_data);
|
||||
}
|
||||
else
|
||||
{
|
||||
section->items = g_slist_delete_link (section->items, section->items);
|
||||
(* tracker->remove_func) (offset, tracker->user_data);
|
||||
}
|
||||
|
||||
gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0);
|
||||
}
|
||||
|
||||
static gint
|
||||
gtk_menu_tracker_section_measure (GtkMenuTrackerSection *section)
|
||||
{
|
||||
GSList *item;
|
||||
gint n_items;
|
||||
|
||||
if (section == NULL)
|
||||
return 1;
|
||||
|
||||
n_items = 0;
|
||||
|
||||
if (section->has_separator)
|
||||
n_items++;
|
||||
|
||||
for (item = section->items; item; item = item->next)
|
||||
n_items += gtk_menu_tracker_section_measure (item->data);
|
||||
|
||||
return n_items;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_remove_items (GtkMenuTracker *tracker,
|
||||
GSList **change_point,
|
||||
gint offset,
|
||||
gint n_items)
|
||||
{
|
||||
gint i;
|
||||
|
||||
for (i = 0; i < n_items; i++)
|
||||
{
|
||||
GtkMenuTrackerSection *subsection;
|
||||
gint n;
|
||||
|
||||
subsection = (*change_point)->data;
|
||||
*change_point = g_slist_delete_link (*change_point, *change_point);
|
||||
|
||||
n = gtk_menu_tracker_section_measure (subsection);
|
||||
gtk_menu_tracker_section_free (subsection);
|
||||
|
||||
while (n--)
|
||||
(* tracker->remove_func) (offset, tracker->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_add_items (GtkMenuTracker *tracker,
|
||||
GtkMenuTrackerSection *section,
|
||||
GSList **change_point,
|
||||
gint offset,
|
||||
GMenuModel *model,
|
||||
gint position,
|
||||
gint n_items)
|
||||
{
|
||||
while (n_items--)
|
||||
{
|
||||
GMenuModel *submenu;
|
||||
|
||||
submenu = g_menu_model_get_item_link (model, position + n_items, G_MENU_LINK_SECTION);
|
||||
g_assert (submenu != model);
|
||||
if (submenu != NULL)
|
||||
{
|
||||
GtkMenuTrackerSection *subsection;
|
||||
gchar *action_namespace = NULL;
|
||||
gboolean has_label;
|
||||
|
||||
has_label = g_menu_model_get_item_attribute (model, position + n_items,
|
||||
G_MENU_ATTRIBUTE_LABEL, "s", NULL);
|
||||
|
||||
g_menu_model_get_item_attribute (model, position + n_items,
|
||||
G_MENU_ATTRIBUTE_ACTION_NAMESPACE, "s", &action_namespace);
|
||||
|
||||
if (section->action_namespace)
|
||||
{
|
||||
gchar *namespace;
|
||||
|
||||
namespace = g_strjoin (".", section->action_namespace, action_namespace, NULL);
|
||||
subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, has_label, offset, namespace);
|
||||
g_free (namespace);
|
||||
}
|
||||
else
|
||||
subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, has_label, offset, action_namespace);
|
||||
|
||||
*change_point = g_slist_prepend (*change_point, subsection);
|
||||
g_free (action_namespace);
|
||||
g_object_unref (submenu);
|
||||
}
|
||||
else
|
||||
{
|
||||
GtkMenuTrackerItem *item;
|
||||
|
||||
item = _gtk_menu_tracker_item_new (tracker->observable, model, position + n_items,
|
||||
section->action_namespace, FALSE);
|
||||
|
||||
/* In the case that the item may disappear we handle that by
|
||||
* treating the item that we just created as being its own
|
||||
* subsection. This happens as so:
|
||||
*
|
||||
* - the subsection is created without the possibility of
|
||||
* showing a separator
|
||||
*
|
||||
* - the subsection will have either 0 or 1 item in it at all
|
||||
* times: either the shown item or not (in the case it is
|
||||
* hidden)
|
||||
*
|
||||
* - the created item acts as the "model" for this section
|
||||
* and we use its "visiblity-changed" signal in the same
|
||||
* way that we use the "items-changed" signal from a real
|
||||
* GMenuModel
|
||||
*
|
||||
* We almost never use the '->model' stored in the section for
|
||||
* anything other than lookups and for dropped the ref and
|
||||
* disconnecting the signal when we destroy the menu, and we
|
||||
* need to do exactly those things in this case as well.
|
||||
*
|
||||
* The only other thing that '->model' is used for is in the
|
||||
* case that we want to show a separator, but we will never do
|
||||
* that because separators are not shown for this fake section.
|
||||
*/
|
||||
if (_gtk_menu_tracker_item_may_disappear (item))
|
||||
{
|
||||
GtkMenuTrackerSection *fake_section;
|
||||
|
||||
fake_section = g_slice_new0 (GtkMenuTrackerSection);
|
||||
fake_section->is_fake = TRUE;
|
||||
fake_section->model = g_object_ref (item);
|
||||
fake_section->handler = g_signal_connect (item, "visibility-changed",
|
||||
G_CALLBACK (gtk_menu_tracker_item_visibility_changed),
|
||||
tracker);
|
||||
*change_point = g_slist_prepend (*change_point, fake_section);
|
||||
|
||||
if (_gtk_menu_tracker_item_is_visible (item))
|
||||
{
|
||||
(* tracker->insert_func) (item, offset, tracker->user_data);
|
||||
fake_section->items = g_slist_prepend (NULL, NULL);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* In the normal case, we store NULL in the linked list.
|
||||
* The measurement and lookup code count NULL always as
|
||||
* exactly 1: an item that will always be there.
|
||||
*/
|
||||
(* tracker->insert_func) (item, offset, tracker->user_data);
|
||||
*change_point = g_slist_prepend (*change_point, NULL);
|
||||
}
|
||||
|
||||
g_object_unref (item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_model_changed (GMenuModel *model,
|
||||
gint position,
|
||||
gint removed,
|
||||
gint added,
|
||||
gpointer user_data)
|
||||
{
|
||||
GtkMenuTracker *tracker = user_data;
|
||||
GtkMenuTrackerSection *section;
|
||||
GSList **change_point;
|
||||
gint offset = 0;
|
||||
gint i;
|
||||
|
||||
/* First find which section the changed model corresponds to, and the
|
||||
* position of that section within the overall menu.
|
||||
*/
|
||||
section = gtk_menu_tracker_section_find_model (tracker->toplevel, model, &offset);
|
||||
|
||||
/* Next, seek through that section to the change point. This gives us
|
||||
* the correct GSList** to make the change to and also finds the final
|
||||
* offset at which we will make the changes (by measuring the number
|
||||
* of items within each item of the section before the change point).
|
||||
*/
|
||||
change_point = §ion->items;
|
||||
for (i = 0; i < position; i++)
|
||||
{
|
||||
offset += gtk_menu_tracker_section_measure ((*change_point)->data);
|
||||
change_point = &(*change_point)->next;
|
||||
}
|
||||
|
||||
/* We remove items in order and add items in reverse order. This
|
||||
* means that the offset used for all inserts and removes caused by a
|
||||
* single change will be the same.
|
||||
*
|
||||
* This also has a performance advantage: GtkMenuShell stores the
|
||||
* menu items in a linked list. In the case where we are creating a
|
||||
* menu for the first time, adding the items in reverse order means
|
||||
* that we only ever insert at index zero, prepending the list. This
|
||||
* means that we can populate in O(n) time instead of O(n^2) that we
|
||||
* would do by appending.
|
||||
*/
|
||||
gtk_menu_tracker_remove_items (tracker, change_point, offset, removed);
|
||||
gtk_menu_tracker_add_items (tracker, section, change_point, offset, model, position, added);
|
||||
|
||||
/* The offsets for insertion/removal of separators will be all over
|
||||
* the place, however...
|
||||
*/
|
||||
gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_section_free (GtkMenuTrackerSection *section)
|
||||
{
|
||||
if (section == NULL)
|
||||
return;
|
||||
|
||||
g_signal_handler_disconnect (section->model, section->handler);
|
||||
g_slist_free_full (section->items, (GDestroyNotify) gtk_menu_tracker_section_free);
|
||||
g_free (section->action_namespace);
|
||||
g_object_unref (section->model);
|
||||
g_slice_free (GtkMenuTrackerSection, section);
|
||||
}
|
||||
|
||||
static GtkMenuTrackerSection *
|
||||
gtk_menu_tracker_section_new (GtkMenuTracker *tracker,
|
||||
GMenuModel *model,
|
||||
gboolean with_separators,
|
||||
gboolean separator_label,
|
||||
gint offset,
|
||||
const gchar *action_namespace)
|
||||
{
|
||||
GtkMenuTrackerSection *section;
|
||||
|
||||
section = g_slice_new0 (GtkMenuTrackerSection);
|
||||
section->model = g_object_ref (model);
|
||||
section->with_separators = with_separators;
|
||||
section->action_namespace = g_strdup (action_namespace);
|
||||
section->separator_label = separator_label;
|
||||
|
||||
gtk_menu_tracker_add_items (tracker, section, §ion->items, offset, model, 0, g_menu_model_get_n_items (model));
|
||||
section->handler = g_signal_connect (model, "items-changed", G_CALLBACK (gtk_menu_tracker_model_changed), tracker);
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
/*< private >
|
||||
* gtk_menu_tracker_new:
|
||||
* @model: the model to flatten
|
||||
* @with_separators: if the toplevel should have separators (ie: TRUE
|
||||
* for menus, FALSE for menubars)
|
||||
* @action_namespace: the passed-in action namespace
|
||||
* @insert_func: insert callback
|
||||
* @remove_func: remove callback
|
||||
* @user_data user data for callbacks
|
||||
*
|
||||
* Creates a GtkMenuTracker for @model, holding a ref on @model for as
|
||||
* long as the tracker is alive.
|
||||
*
|
||||
* This flattens out the model, merging sections and inserting
|
||||
* separators where appropriate. It monitors for changes and performs
|
||||
* updates on the fly. It also handles action_namespace for subsections
|
||||
* (but you will need to handle it yourself for submenus).
|
||||
*
|
||||
* When the tracker is first created, @insert_func will be called many
|
||||
* times to populate the menu with the initial contents of @model
|
||||
* (unless it is empty), before gtk_menu_tracker_new() returns. For
|
||||
* this reason, the menu that is using the tracker ought to be empty
|
||||
* when it creates the tracker.
|
||||
*
|
||||
* Future changes to @model will result in more calls to @insert_func
|
||||
* and @remove_func.
|
||||
*
|
||||
* The position argument to both functions is the linear 0-based
|
||||
* position in the menu at which the item in question should be inserted
|
||||
* or removed.
|
||||
*
|
||||
* For @insert_func, @model and @item_index are used to get the
|
||||
* information about the menu item to insert. @action_namespace is the
|
||||
* action namespace that actions referred to from that item should place
|
||||
* themselves in. Note that if the item is a submenu and the
|
||||
* "action-namespace" attribute is defined on the item, it will _not_ be
|
||||
* applied to the @action_namespace argument as it is meant for the
|
||||
* items inside of the submenu, not the submenu item itself.
|
||||
*
|
||||
* @is_separator is set to %TRUE in case the item being added is a
|
||||
* separator. @model and @item_index will still be meaningfully set in
|
||||
* this case -- to the section menu item corresponding to the separator.
|
||||
* This is useful if the section specifies a label, for example. If
|
||||
* there is an "action-namespace" attribute on this menu item then it
|
||||
* should be ignored by the consumer because #GtkMenuTracker has already
|
||||
* handled it.
|
||||
*
|
||||
* When using #GtkMenuTracker there is no need to hold onto @model or
|
||||
* monitor it for changes. The model will be unreffed when
|
||||
* gtk_menu_tracker_free() is called.
|
||||
*/
|
||||
GtkMenuTracker *
|
||||
gtk_menu_tracker_new (GtkActionObservable *observable,
|
||||
GMenuModel *model,
|
||||
gboolean with_separators,
|
||||
const gchar *action_namespace,
|
||||
GtkMenuTrackerInsertFunc insert_func,
|
||||
GtkMenuTrackerRemoveFunc remove_func,
|
||||
gpointer user_data)
|
||||
{
|
||||
GtkMenuTracker *tracker;
|
||||
|
||||
tracker = g_slice_new (GtkMenuTracker);
|
||||
tracker->observable = g_object_ref (observable);
|
||||
tracker->insert_func = insert_func;
|
||||
tracker->remove_func = remove_func;
|
||||
tracker->user_data = user_data;
|
||||
|
||||
tracker->toplevel = gtk_menu_tracker_section_new (tracker, model, with_separators, FALSE, 0, action_namespace);
|
||||
gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0);
|
||||
|
||||
return tracker;
|
||||
}
|
||||
|
||||
GtkMenuTracker *
|
||||
gtk_menu_tracker_new_for_item_submenu (GtkMenuTrackerItem *item,
|
||||
GtkMenuTrackerInsertFunc insert_func,
|
||||
GtkMenuTrackerRemoveFunc remove_func,
|
||||
gpointer user_data)
|
||||
{
|
||||
GtkMenuTracker *tracker;
|
||||
GMenuModel *submenu;
|
||||
gchar *namespace;
|
||||
|
||||
submenu = _gtk_menu_tracker_item_get_submenu (item);
|
||||
namespace = _gtk_menu_tracker_item_get_submenu_namespace (item);
|
||||
|
||||
tracker = gtk_menu_tracker_new (_gtk_menu_tracker_item_get_observable (item), submenu,
|
||||
TRUE, namespace, insert_func, remove_func, user_data);
|
||||
|
||||
g_object_unref (submenu);
|
||||
g_free (namespace);
|
||||
|
||||
return tracker;
|
||||
}
|
||||
|
||||
/*< private >
|
||||
* gtk_menu_tracker_free:
|
||||
* @tracker: a #GtkMenuTracker
|
||||
*
|
||||
* Frees the tracker, ...
|
||||
*/
|
||||
void
|
||||
gtk_menu_tracker_free (GtkMenuTracker *tracker)
|
||||
{
|
||||
gtk_menu_tracker_section_free (tracker->toplevel);
|
||||
g_object_unref (tracker->observable);
|
||||
g_slice_free (GtkMenuTracker, tracker);
|
||||
}
|
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2013 Canonical Limited
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the licence, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Ryan Lortie <desrt@desrt.ca>
|
||||
*/
|
||||
|
||||
#ifndef __GTK_MENU_TRACKER_H__
|
||||
#define __GTK_MENU_TRACKER_H__
|
||||
|
||||
#include "gtkmenutrackeritem.h"
|
||||
|
||||
typedef struct _GtkMenuTracker GtkMenuTracker;
|
||||
|
||||
typedef void (* GtkMenuTrackerInsertFunc) (GtkMenuTrackerItem *item,
|
||||
gint position,
|
||||
gpointer user_data);
|
||||
|
||||
typedef void (* GtkMenuTrackerRemoveFunc) (gint position,
|
||||
gpointer user_data);
|
||||
|
||||
|
||||
GtkMenuTracker * gtk_menu_tracker_new (GtkActionObservable *observer,
|
||||
GMenuModel *model,
|
||||
gboolean with_separators,
|
||||
const gchar *action_namespace,
|
||||
GtkMenuTrackerInsertFunc insert_func,
|
||||
GtkMenuTrackerRemoveFunc remove_func,
|
||||
gpointer user_data);
|
||||
|
||||
GtkMenuTracker * gtk_menu_tracker_new_for_item_submenu (GtkMenuTrackerItem *item,
|
||||
GtkMenuTrackerInsertFunc insert_func,
|
||||
GtkMenuTrackerRemoveFunc remove_func,
|
||||
gpointer user_data);
|
||||
|
||||
void gtk_menu_tracker_free (GtkMenuTracker *tracker);
|
||||
|
||||
#endif /* __GTK_MENU_TRACKER_H__ */
|
@@ -1,894 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2013 Canonical Limited
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the licence, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Ryan Lortie <desrt@desrt.ca>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gtkmenutrackeritem.h"
|
||||
#include "gtkactionmuxer.h"
|
||||
|
||||
#include "gtkactionmuxer.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* SECTION:gtkmenutrackeritem
|
||||
* @Title: GtkMenuTrackerItem
|
||||
* @Short_description: Small helper for model menu items
|
||||
*
|
||||
* A #GtkMenuTrackerItem is a small helper class used by #GtkMenuTracker to
|
||||
* represent menu items. It has one of three classes: normal item, separator,
|
||||
* or submenu.
|
||||
*
|
||||
* If an item is one of the non-normal classes (submenu, separator), only the
|
||||
* label of the item needs to be respected. Otherwise, all the properties
|
||||
* of the item contribute to the item's appearance and state.
|
||||
*
|
||||
* Implementing the appearance of the menu item is up to toolkits, and certain
|
||||
* toolkits may choose to ignore certain properties, like icon or accel. The
|
||||
* role of the item determines its accessibility role, along with its
|
||||
* decoration if the GtkMenuTrackerItem::toggled property is true. As an
|
||||
* example, if the item has the role %GTK_MENU_TRACKER_ITEM_ROLE_CHECK and
|
||||
* GtkMenuTrackerItem::toggled is %FALSE, its accessible role should be that of
|
||||
* a check menu item, and no decoration should be drawn. But if
|
||||
* GtkMenuTrackerItem::toggled is %TRUE, a checkmark should be drawn.
|
||||
*
|
||||
* All properties except for the two class-determining properties,
|
||||
* GtkMenuTrackerItem::is-separator and GtkMenuTrackerItem::has-submenu are
|
||||
* allowed to change, so listen to the notify signals to update your item's
|
||||
* appearance. When using a GObject library, this can conveniently be done
|
||||
* with g_object_bind_property() and #GBinding, and this is how this is
|
||||
* implemented in GTK+; the appearance side is implemented in #GtkModelMenuItem.
|
||||
*
|
||||
* When an item is clicked, simply call gtk_menu_tracker_item_activated() in
|
||||
* response. The #GtkMenuTrackerItem will take care of everything related to
|
||||
* activating the item and will itself update the state of all items in
|
||||
* response.
|
||||
*
|
||||
* Submenus are a special case of menu item. When an item is a submenu, you
|
||||
* should create a submenu for it with gtk_menu_tracker_new_item_for_submenu(),
|
||||
* and apply the same menu tracking logic you would for a toplevel menu.
|
||||
* Applications using submenus may want to lazily build their submenus in
|
||||
* response to the user clicking on it, as building a submenu may be expensive.
|
||||
*
|
||||
* Thus, the submenu has two special controls -- the submenu's visibility
|
||||
* should be controlled by the GtkMenuTrackerItem::submenu-shown property,
|
||||
* and if a user clicks on the submenu, do not immediately show the menu,
|
||||
* but call gtk_menu_tracker_item_request_submenu_shown() and wait for the
|
||||
* GtkMenuTrackerItem::submenu-shown property to update. If the user navigates,
|
||||
* the application may want to be notified so it can cancel the expensive
|
||||
* operation that it was using to build the submenu. Thus,
|
||||
* gtk_menu_tracker_item_request_submenu_shown() takes a boolean parameter.
|
||||
* Use %TRUE when the user wants to open the submenu, and %FALSE when the
|
||||
* user wants to close the submenu.
|
||||
*/
|
||||
|
||||
typedef GObjectClass GtkMenuTrackerItemClass;
|
||||
|
||||
struct _GtkMenuTrackerItem
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
GtkActionObservable *observable;
|
||||
gchar *action_namespace;
|
||||
gchar *action_and_target;
|
||||
GMenuItem *item;
|
||||
GtkMenuTrackerItemRole role : 4;
|
||||
guint is_separator : 1;
|
||||
guint can_activate : 1;
|
||||
guint sensitive : 1;
|
||||
guint toggled : 1;
|
||||
guint submenu_shown : 1;
|
||||
guint submenu_requested : 1;
|
||||
guint hidden_when : 2;
|
||||
guint is_visible : 1;
|
||||
};
|
||||
|
||||
#define HIDDEN_NEVER 0
|
||||
#define HIDDEN_WHEN_MISSING 1
|
||||
#define HIDDEN_WHEN_DISABLED 2
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_IS_SEPARATOR,
|
||||
PROP_HAS_SUBMENU,
|
||||
PROP_LABEL,
|
||||
PROP_ICON,
|
||||
PROP_SENSITIVE,
|
||||
PROP_VISIBLE,
|
||||
PROP_ROLE,
|
||||
PROP_TOGGLED,
|
||||
PROP_ACCEL,
|
||||
PROP_SUBMENU_SHOWN,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
static GParamSpec *gtk_menu_tracker_item_pspecs[N_PROPS];
|
||||
static guint gtk_menu_tracker_visibility_changed_signal;
|
||||
|
||||
static void gtk_menu_tracker_item_init_observer_iface (GtkActionObserverInterface *iface);
|
||||
G_DEFINE_TYPE_WITH_CODE (GtkMenuTrackerItem, gtk_menu_tracker_item, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTION_OBSERVER, gtk_menu_tracker_item_init_observer_iface))
|
||||
|
||||
GType
|
||||
gtk_menu_tracker_item_role_get_type (void)
|
||||
{
|
||||
static gsize gtk_menu_tracker_item_role_type;
|
||||
|
||||
if (g_once_init_enter (>k_menu_tracker_item_role_type))
|
||||
{
|
||||
static const GEnumValue values[] = {
|
||||
{ GTK_MENU_TRACKER_ITEM_ROLE_NORMAL, "GTK_MENU_TRACKER_ITEM_ROLE_NORMAL", "normal" },
|
||||
{ GTK_MENU_TRACKER_ITEM_ROLE_CHECK, "GTK_MENU_TRACKER_ITEM_ROLE_CHECK", "check" },
|
||||
{ GTK_MENU_TRACKER_ITEM_ROLE_RADIO, "GTK_MENU_TRACKER_ITEM_ROLE_RADIO", "radio" },
|
||||
{ 0, NULL, NULL }
|
||||
};
|
||||
GType type;
|
||||
|
||||
type = g_enum_register_static ("GtkMenuTrackerItemRole", values);
|
||||
|
||||
g_once_init_leave (>k_menu_tracker_item_role_type, type);
|
||||
}
|
||||
|
||||
return gtk_menu_tracker_item_role_type;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_IS_SEPARATOR:
|
||||
g_value_set_boolean (value, gtk_menu_tracker_item_get_is_separator (self));
|
||||
break;
|
||||
case PROP_HAS_SUBMENU:
|
||||
g_value_set_boolean (value, gtk_menu_tracker_item_get_has_submenu (self));
|
||||
break;
|
||||
case PROP_LABEL:
|
||||
g_value_set_string (value, gtk_menu_tracker_item_get_label (self));
|
||||
break;
|
||||
case PROP_ICON:
|
||||
g_value_set_object (value, gtk_menu_tracker_item_get_icon (self));
|
||||
break;
|
||||
case PROP_SENSITIVE:
|
||||
g_value_set_boolean (value, gtk_menu_tracker_item_get_sensitive (self));
|
||||
break;
|
||||
case PROP_VISIBLE:
|
||||
g_value_set_boolean (value, gtk_menu_tracker_item_get_visible (self));
|
||||
break;
|
||||
case PROP_ROLE:
|
||||
g_value_set_enum (value, gtk_menu_tracker_item_get_role (self));
|
||||
break;
|
||||
case PROP_TOGGLED:
|
||||
g_value_set_boolean (value, gtk_menu_tracker_item_get_toggled (self));
|
||||
break;
|
||||
case PROP_ACCEL:
|
||||
g_value_set_string (value, gtk_menu_tracker_item_get_accel (self));
|
||||
break;
|
||||
case PROP_SUBMENU_SHOWN:
|
||||
g_value_set_boolean (value, gtk_menu_tracker_item_get_submenu_shown (self));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_finalize (GObject *object)
|
||||
{
|
||||
GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (object);
|
||||
|
||||
g_free (self->action_namespace);
|
||||
g_free (self->action_and_target);
|
||||
|
||||
if (self->observable)
|
||||
g_object_unref (self->observable);
|
||||
|
||||
g_object_unref (self->item);
|
||||
|
||||
G_OBJECT_CLASS (gtk_menu_tracker_item_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_init (GtkMenuTrackerItem * self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_class_init (GtkMenuTrackerItemClass *class)
|
||||
{
|
||||
class->get_property = gtk_menu_tracker_item_get_property;
|
||||
class->finalize = gtk_menu_tracker_item_finalize;
|
||||
|
||||
gtk_menu_tracker_item_pspecs[PROP_IS_SEPARATOR] =
|
||||
g_param_spec_boolean ("is-separator", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
gtk_menu_tracker_item_pspecs[PROP_HAS_SUBMENU] =
|
||||
g_param_spec_boolean ("has-submenu", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
gtk_menu_tracker_item_pspecs[PROP_LABEL] =
|
||||
g_param_spec_string ("label", "", "", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
gtk_menu_tracker_item_pspecs[PROP_ICON] =
|
||||
g_param_spec_object ("icon", "", "", G_TYPE_ICON, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
gtk_menu_tracker_item_pspecs[PROP_SENSITIVE] =
|
||||
g_param_spec_boolean ("sensitive", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
gtk_menu_tracker_item_pspecs[PROP_VISIBLE] =
|
||||
g_param_spec_boolean ("visible", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
gtk_menu_tracker_item_pspecs[PROP_ROLE] =
|
||||
g_param_spec_enum ("role", "", "",
|
||||
GTK_TYPE_MENU_TRACKER_ITEM_ROLE, GTK_MENU_TRACKER_ITEM_ROLE_NORMAL,
|
||||
G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
gtk_menu_tracker_item_pspecs[PROP_TOGGLED] =
|
||||
g_param_spec_boolean ("toggled", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
gtk_menu_tracker_item_pspecs[PROP_ACCEL] =
|
||||
g_param_spec_string ("accel", "", "", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
gtk_menu_tracker_item_pspecs[PROP_SUBMENU_SHOWN] =
|
||||
g_param_spec_boolean ("submenu-shown", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
|
||||
|
||||
g_object_class_install_properties (class, N_PROPS, gtk_menu_tracker_item_pspecs);
|
||||
|
||||
gtk_menu_tracker_visibility_changed_signal = g_signal_new ("visibility-changed", GTK_TYPE_MENU_TRACKER_ITEM,
|
||||
G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
|
||||
g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE,
|
||||
1, G_TYPE_BOOLEAN);
|
||||
}
|
||||
|
||||
/* This syncs up the visibility for the hidden-when='' case. We call it
|
||||
* from the action observer functions on changes to the action group and
|
||||
* on initialisation (via the action observer functions that are invoked
|
||||
* at that time).
|
||||
*/
|
||||
static void
|
||||
gtk_menu_tracker_item_update_visibility (GtkMenuTrackerItem *self)
|
||||
{
|
||||
gboolean visible;
|
||||
|
||||
switch (self->hidden_when)
|
||||
{
|
||||
case HIDDEN_NEVER:
|
||||
visible = TRUE;
|
||||
break;
|
||||
|
||||
case HIDDEN_WHEN_MISSING:
|
||||
visible = self->can_activate;
|
||||
break;
|
||||
|
||||
case HIDDEN_WHEN_DISABLED:
|
||||
visible = self->sensitive;
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
if (visible != self->is_visible)
|
||||
{
|
||||
self->is_visible = visible;
|
||||
g_signal_emit (self, gtk_menu_tracker_visibility_changed_signal, 0, visible);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_action_added (GtkActionObserver *observer,
|
||||
GtkActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
const GVariantType *parameter_type,
|
||||
gboolean enabled,
|
||||
GVariant *state)
|
||||
{
|
||||
GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer);
|
||||
GVariant *action_target;
|
||||
|
||||
action_target = g_menu_item_get_attribute_value (self->item, G_MENU_ATTRIBUTE_TARGET, NULL);
|
||||
|
||||
self->can_activate = (action_target == NULL && parameter_type == NULL) ||
|
||||
(action_target != NULL && parameter_type != NULL &&
|
||||
g_variant_is_of_type (action_target, parameter_type));
|
||||
|
||||
if (!self->can_activate)
|
||||
{
|
||||
if (action_target)
|
||||
g_variant_unref (action_target);
|
||||
return;
|
||||
}
|
||||
|
||||
self->sensitive = enabled;
|
||||
|
||||
if (action_target != NULL && state != NULL)
|
||||
{
|
||||
self->toggled = g_variant_equal (state, action_target);
|
||||
self->role = GTK_MENU_TRACKER_ITEM_ROLE_RADIO;
|
||||
}
|
||||
|
||||
else if (state != NULL && g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
|
||||
{
|
||||
self->toggled = g_variant_get_boolean (state);
|
||||
self->role = GTK_MENU_TRACKER_ITEM_ROLE_CHECK;
|
||||
}
|
||||
|
||||
g_object_freeze_notify (G_OBJECT (self));
|
||||
|
||||
if (self->sensitive)
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SENSITIVE]);
|
||||
|
||||
if (self->toggled)
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]);
|
||||
|
||||
if (self->role != GTK_MENU_TRACKER_ITEM_ROLE_NORMAL)
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ROLE]);
|
||||
|
||||
g_object_thaw_notify (G_OBJECT (self));
|
||||
|
||||
if (action_target)
|
||||
g_variant_unref (action_target);
|
||||
|
||||
/* In case of hidden-when='', we want to Wait until after refreshing
|
||||
* all of the properties to emit the signal that will cause the
|
||||
* tracker to expose us (to prevent too much thrashing).
|
||||
*/
|
||||
gtk_menu_tracker_item_update_visibility (self);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_action_enabled_changed (GtkActionObserver *observer,
|
||||
GtkActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
gboolean enabled)
|
||||
{
|
||||
GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer);
|
||||
|
||||
if (!self->can_activate)
|
||||
return;
|
||||
|
||||
if (self->sensitive == enabled)
|
||||
return;
|
||||
|
||||
self->sensitive = enabled;
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SENSITIVE]);
|
||||
|
||||
gtk_menu_tracker_item_update_visibility (self);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_action_state_changed (GtkActionObserver *observer,
|
||||
GtkActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
GVariant *state)
|
||||
{
|
||||
GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer);
|
||||
GVariant *action_target;
|
||||
gboolean was_toggled;
|
||||
|
||||
if (!self->can_activate)
|
||||
return;
|
||||
|
||||
action_target = g_menu_item_get_attribute_value (self->item, G_MENU_ATTRIBUTE_TARGET, NULL);
|
||||
was_toggled = self->toggled;
|
||||
|
||||
if (action_target)
|
||||
{
|
||||
self->toggled = g_variant_equal (state, action_target);
|
||||
g_variant_unref (action_target);
|
||||
}
|
||||
|
||||
else if (g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
|
||||
self->toggled = g_variant_get_boolean (state);
|
||||
|
||||
else
|
||||
self->toggled = FALSE;
|
||||
|
||||
if (self->toggled != was_toggled)
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_action_removed (GtkActionObserver *observer,
|
||||
GtkActionObservable *observable,
|
||||
const gchar *action_name)
|
||||
{
|
||||
GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer);
|
||||
gboolean was_sensitive, was_toggled;
|
||||
GtkMenuTrackerItemRole old_role;
|
||||
|
||||
if (!self->can_activate)
|
||||
return;
|
||||
|
||||
was_sensitive = self->sensitive;
|
||||
was_toggled = self->toggled;
|
||||
old_role = self->role;
|
||||
|
||||
self->can_activate = FALSE;
|
||||
self->sensitive = FALSE;
|
||||
self->toggled = FALSE;
|
||||
self->role = GTK_MENU_TRACKER_ITEM_ROLE_NORMAL;
|
||||
|
||||
/* Backwards from adding: we want to remove ourselves from the menu
|
||||
* -before- thrashing the properties.
|
||||
*/
|
||||
gtk_menu_tracker_item_update_visibility (self);
|
||||
|
||||
g_object_freeze_notify (G_OBJECT (self));
|
||||
|
||||
if (was_sensitive)
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SENSITIVE]);
|
||||
|
||||
if (was_toggled)
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]);
|
||||
|
||||
if (old_role != GTK_MENU_TRACKER_ITEM_ROLE_NORMAL)
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ROLE]);
|
||||
|
||||
g_object_thaw_notify (G_OBJECT (self));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_primary_accel_changed (GtkActionObserver *observer,
|
||||
GtkActionObservable *observable,
|
||||
const gchar *action_name,
|
||||
const gchar *action_and_target)
|
||||
{
|
||||
GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer);
|
||||
|
||||
if (g_str_equal (action_and_target, self->action_and_target))
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ACCEL]);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_init_observer_iface (GtkActionObserverInterface *iface)
|
||||
{
|
||||
iface->action_added = gtk_menu_tracker_item_action_added;
|
||||
iface->action_enabled_changed = gtk_menu_tracker_item_action_enabled_changed;
|
||||
iface->action_state_changed = gtk_menu_tracker_item_action_state_changed;
|
||||
iface->action_removed = gtk_menu_tracker_item_action_removed;
|
||||
iface->primary_accel_changed = gtk_menu_tracker_item_primary_accel_changed;
|
||||
}
|
||||
|
||||
GtkMenuTrackerItem *
|
||||
_gtk_menu_tracker_item_new (GtkActionObservable *observable,
|
||||
GMenuModel *model,
|
||||
gint item_index,
|
||||
const gchar *action_namespace,
|
||||
gboolean is_separator)
|
||||
{
|
||||
GtkMenuTrackerItem *self;
|
||||
const gchar *action_name;
|
||||
const gchar *hidden_when;
|
||||
|
||||
g_return_val_if_fail (GTK_IS_ACTION_OBSERVABLE (observable), NULL);
|
||||
g_return_val_if_fail (G_IS_MENU_MODEL (model), NULL);
|
||||
|
||||
self = g_object_new (GTK_TYPE_MENU_TRACKER_ITEM, NULL);
|
||||
self->item = g_menu_item_new_from_model (model, item_index);
|
||||
self->action_namespace = g_strdup (action_namespace);
|
||||
self->observable = g_object_ref (observable);
|
||||
self->is_separator = is_separator;
|
||||
|
||||
if (!is_separator && g_menu_item_get_attribute (self->item, "hidden-when", "&s", &hidden_when))
|
||||
{
|
||||
if (g_str_equal (hidden_when, "action-disabled"))
|
||||
self->hidden_when = HIDDEN_WHEN_DISABLED;
|
||||
else if (g_str_equal (hidden_when, "action-missing"))
|
||||
self->hidden_when = HIDDEN_WHEN_MISSING;
|
||||
|
||||
/* Ignore other values -- this code may be running in context of a
|
||||
* desktop shell or the like and should not spew criticals due to
|
||||
* application bugs...
|
||||
*
|
||||
* Note: if we just set a hidden-when state, but don't get the
|
||||
* action_name below then our visibility will be FALSE forever.
|
||||
* That's to be expected since the action is missing...
|
||||
*/
|
||||
}
|
||||
|
||||
if (!is_separator && g_menu_item_get_attribute (self->item, "action", "&s", &action_name))
|
||||
{
|
||||
GActionGroup *group = G_ACTION_GROUP (observable);
|
||||
const GVariantType *parameter_type;
|
||||
GVariant *target;
|
||||
gboolean enabled;
|
||||
GVariant *state;
|
||||
gboolean found;
|
||||
|
||||
target = g_menu_item_get_attribute_value (self->item, "target", NULL);
|
||||
|
||||
self->action_and_target = gtk_print_action_and_target (action_namespace, action_name, target);
|
||||
|
||||
if (target)
|
||||
g_variant_unref (target);
|
||||
|
||||
action_name = strrchr (self->action_and_target, '|') + 1;
|
||||
|
||||
state = NULL;
|
||||
|
||||
gtk_action_observable_register_observer (self->observable, action_name, GTK_ACTION_OBSERVER (self));
|
||||
found = g_action_group_query_action (group, action_name, &enabled, ¶meter_type, NULL, NULL, &state);
|
||||
|
||||
if (found)
|
||||
gtk_menu_tracker_item_action_added (GTK_ACTION_OBSERVER (self), observable, NULL, parameter_type, enabled, state);
|
||||
else
|
||||
gtk_menu_tracker_item_action_removed (GTK_ACTION_OBSERVER (self), observable, NULL);
|
||||
|
||||
if (state)
|
||||
g_variant_unref (state);
|
||||
}
|
||||
else
|
||||
self->sensitive = TRUE;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
GtkActionObservable *
|
||||
_gtk_menu_tracker_item_get_observable (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return self->observable;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_menu_tracker_item_get_is_separator:
|
||||
* @self: A #GtkMenuTrackerItem instance
|
||||
*
|
||||
* Returns whether the menu item is a separator. If so, only
|
||||
* certain properties may need to be obeyed. See the documentation
|
||||
* for #GtkMenuTrackerItem.
|
||||
*/
|
||||
gboolean
|
||||
gtk_menu_tracker_item_get_is_separator (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return self->is_separator;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_menu_tracker_item_get_has_submenu:
|
||||
* @self: A #GtkMenuTrackerItem instance
|
||||
*
|
||||
* Returns whether the menu item has a submenu. If so, only
|
||||
* certain properties may need to be obeyed. See the documentation
|
||||
* for #GtkMenuTrackerItem.
|
||||
*/
|
||||
gboolean
|
||||
gtk_menu_tracker_item_get_has_submenu (GtkMenuTrackerItem *self)
|
||||
{
|
||||
GMenuModel *link;
|
||||
|
||||
link = g_menu_item_get_link (self->item, G_MENU_LINK_SUBMENU);
|
||||
|
||||
if (link)
|
||||
{
|
||||
g_object_unref (link);
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
const gchar *
|
||||
gtk_menu_tracker_item_get_label (GtkMenuTrackerItem *self)
|
||||
{
|
||||
const gchar *label = NULL;
|
||||
|
||||
g_menu_item_get_attribute (self->item, G_MENU_ATTRIBUTE_LABEL, "&s", &label);
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_menu_tracker_item_get_icon:
|
||||
*
|
||||
* Returns: (transfer full):
|
||||
*/
|
||||
GIcon *
|
||||
gtk_menu_tracker_item_get_icon (GtkMenuTrackerItem *self)
|
||||
{
|
||||
GVariant *icon_data;
|
||||
GIcon *icon;
|
||||
|
||||
icon_data = g_menu_item_get_attribute_value (self->item, "icon", NULL);
|
||||
|
||||
if (icon_data == NULL)
|
||||
return NULL;
|
||||
|
||||
icon = g_icon_deserialize (icon_data);
|
||||
g_variant_unref (icon_data);
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_menu_tracker_item_get_sensitive (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return self->sensitive;
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_menu_tracker_item_get_visible (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
GtkMenuTrackerItemRole
|
||||
gtk_menu_tracker_item_get_role (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return self->role;
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_menu_tracker_item_get_toggled (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return self->toggled;
|
||||
}
|
||||
|
||||
const gchar *
|
||||
gtk_menu_tracker_item_get_accel (GtkMenuTrackerItem *self)
|
||||
{
|
||||
const gchar *accel;
|
||||
|
||||
if (!self->action_and_target)
|
||||
return NULL;
|
||||
|
||||
if (g_menu_item_get_attribute (self->item, "accel", "&s", &accel))
|
||||
return accel;
|
||||
|
||||
if (!GTK_IS_ACTION_MUXER (self->observable))
|
||||
return NULL;
|
||||
|
||||
return gtk_action_muxer_get_primary_accel (GTK_ACTION_MUXER (self->observable), self->action_and_target);
|
||||
}
|
||||
|
||||
GMenuModel *
|
||||
_gtk_menu_tracker_item_get_submenu (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return g_menu_item_get_link (self->item, "submenu");
|
||||
}
|
||||
|
||||
gchar *
|
||||
_gtk_menu_tracker_item_get_submenu_namespace (GtkMenuTrackerItem *self)
|
||||
{
|
||||
const gchar *namespace;
|
||||
|
||||
if (g_menu_item_get_attribute (self->item, "action-namespace", "&s", &namespace))
|
||||
{
|
||||
if (self->action_namespace)
|
||||
return g_strjoin (".", self->action_namespace, namespace, NULL);
|
||||
else
|
||||
return g_strdup (namespace);
|
||||
}
|
||||
else
|
||||
return g_strdup (self->action_namespace);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_menu_tracker_item_get_should_request_show (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return g_menu_item_get_attribute (self->item, "submenu-action", "&s", NULL);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_menu_tracker_item_get_submenu_shown (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return self->submenu_shown;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_item_set_submenu_shown (GtkMenuTrackerItem *self,
|
||||
gboolean submenu_shown)
|
||||
{
|
||||
if (submenu_shown == self->submenu_shown)
|
||||
return;
|
||||
|
||||
self->submenu_shown = submenu_shown;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_SUBMENU_SHOWN]);
|
||||
}
|
||||
|
||||
void
|
||||
gtk_menu_tracker_item_activated (GtkMenuTrackerItem *self)
|
||||
{
|
||||
const gchar *action_name;
|
||||
GVariant *action_target;
|
||||
|
||||
g_return_if_fail (GTK_IS_MENU_TRACKER_ITEM (self));
|
||||
|
||||
if (!self->can_activate)
|
||||
return;
|
||||
|
||||
action_name = strrchr (self->action_and_target, '|') + 1;
|
||||
action_target = g_menu_item_get_attribute_value (self->item, G_MENU_ATTRIBUTE_TARGET, NULL);
|
||||
|
||||
g_action_group_activate_action (G_ACTION_GROUP (self->observable), action_name, action_target);
|
||||
|
||||
if (action_target)
|
||||
g_variant_unref (action_target);
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GtkMenuTrackerItem *item;
|
||||
gchar *submenu_action;
|
||||
gboolean first_time;
|
||||
} GtkMenuTrackerOpener;
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_opener_update (GtkMenuTrackerOpener *opener)
|
||||
{
|
||||
GActionGroup *group = G_ACTION_GROUP (opener->item->observable);
|
||||
gboolean is_open = TRUE;
|
||||
|
||||
/* We consider the menu as being "open" if the action does not exist
|
||||
* or if there is another problem (no state, wrong state type, etc.).
|
||||
* If the action exists, with the correct state then we consider it
|
||||
* open if we have ever seen this state equal to TRUE.
|
||||
*
|
||||
* In the event that we see the state equal to FALSE, we force it back
|
||||
* to TRUE. We do not signal that the menu was closed because this is
|
||||
* likely to create UI thrashing.
|
||||
*
|
||||
* The only way the menu can have a true-to-false submenu-shown
|
||||
* transition is if the user calls _request_submenu_shown (FALSE).
|
||||
* That is handled in _free() below.
|
||||
*/
|
||||
|
||||
if (g_action_group_has_action (group, opener->submenu_action))
|
||||
{
|
||||
GVariant *state = g_action_group_get_action_state (group, opener->submenu_action);
|
||||
|
||||
if (state)
|
||||
{
|
||||
if (g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN))
|
||||
is_open = g_variant_get_boolean (state);
|
||||
g_variant_unref (state);
|
||||
}
|
||||
}
|
||||
|
||||
/* If it is already open, signal that.
|
||||
*
|
||||
* If it is not open, ask it to open.
|
||||
*/
|
||||
if (is_open)
|
||||
gtk_menu_tracker_item_set_submenu_shown (opener->item, TRUE);
|
||||
|
||||
if (!is_open || opener->first_time)
|
||||
{
|
||||
g_action_group_change_action_state (group, opener->submenu_action, g_variant_new_boolean (TRUE));
|
||||
opener->first_time = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_opener_added (GActionGroup *group,
|
||||
const gchar *action_name,
|
||||
gpointer user_data)
|
||||
{
|
||||
GtkMenuTrackerOpener *opener = user_data;
|
||||
|
||||
if (g_str_equal (action_name, opener->submenu_action))
|
||||
gtk_menu_tracker_opener_update (opener);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_opener_removed (GActionGroup *action_group,
|
||||
const gchar *action_name,
|
||||
gpointer user_data)
|
||||
{
|
||||
GtkMenuTrackerOpener *opener = user_data;
|
||||
|
||||
if (g_str_equal (action_name, opener->submenu_action))
|
||||
gtk_menu_tracker_opener_update (opener);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_opener_changed (GActionGroup *action_group,
|
||||
const gchar *action_name,
|
||||
GVariant *new_state,
|
||||
gpointer user_data)
|
||||
{
|
||||
GtkMenuTrackerOpener *opener = user_data;
|
||||
|
||||
if (g_str_equal (action_name, opener->submenu_action))
|
||||
gtk_menu_tracker_opener_update (opener);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_menu_tracker_opener_free (gpointer data)
|
||||
{
|
||||
GtkMenuTrackerOpener *opener = data;
|
||||
|
||||
g_signal_handlers_disconnect_by_func (opener->item->observable, gtk_menu_tracker_opener_added, opener);
|
||||
g_signal_handlers_disconnect_by_func (opener->item->observable, gtk_menu_tracker_opener_removed, opener);
|
||||
g_signal_handlers_disconnect_by_func (opener->item->observable, gtk_menu_tracker_opener_changed, opener);
|
||||
|
||||
g_action_group_change_action_state (G_ACTION_GROUP (opener->item->observable),
|
||||
opener->submenu_action,
|
||||
g_variant_new_boolean (FALSE));
|
||||
|
||||
gtk_menu_tracker_item_set_submenu_shown (opener->item, FALSE);
|
||||
|
||||
g_free (opener->submenu_action);
|
||||
|
||||
g_slice_free (GtkMenuTrackerOpener, opener);
|
||||
}
|
||||
|
||||
static GtkMenuTrackerOpener *
|
||||
gtk_menu_tracker_opener_new (GtkMenuTrackerItem *item,
|
||||
const gchar *submenu_action)
|
||||
{
|
||||
GtkMenuTrackerOpener *opener;
|
||||
|
||||
opener = g_slice_new (GtkMenuTrackerOpener);
|
||||
opener->first_time = TRUE;
|
||||
opener->item = item;
|
||||
|
||||
if (item->action_namespace)
|
||||
opener->submenu_action = g_strjoin (".", item->action_namespace, submenu_action, NULL);
|
||||
else
|
||||
opener->submenu_action = g_strdup (submenu_action);
|
||||
|
||||
g_signal_connect (item->observable, "action-added", G_CALLBACK (gtk_menu_tracker_opener_added), opener);
|
||||
g_signal_connect (item->observable, "action-removed", G_CALLBACK (gtk_menu_tracker_opener_removed), opener);
|
||||
g_signal_connect (item->observable, "action-state-changed", G_CALLBACK (gtk_menu_tracker_opener_changed), opener);
|
||||
|
||||
gtk_menu_tracker_opener_update (opener);
|
||||
|
||||
return opener;
|
||||
}
|
||||
|
||||
void
|
||||
gtk_menu_tracker_item_request_submenu_shown (GtkMenuTrackerItem *self,
|
||||
gboolean shown)
|
||||
{
|
||||
const gchar *submenu_action;
|
||||
gboolean has_submenu_action;
|
||||
|
||||
if (shown == self->submenu_requested)
|
||||
return;
|
||||
|
||||
has_submenu_action = g_menu_item_get_attribute (self->item, "submenu-action", "&s", &submenu_action);
|
||||
|
||||
self->submenu_requested = shown;
|
||||
|
||||
/* If we have a submenu action, start a submenu opener and wait
|
||||
* for the reply from the client. Otherwise, simply open the
|
||||
* submenu immediately.
|
||||
*/
|
||||
if (has_submenu_action)
|
||||
{
|
||||
if (shown)
|
||||
g_object_set_data_full (G_OBJECT (self), "submenu-opener",
|
||||
gtk_menu_tracker_opener_new (self, submenu_action),
|
||||
gtk_menu_tracker_opener_free);
|
||||
else
|
||||
g_object_set_data (G_OBJECT (self), "submenu-opener", NULL);
|
||||
}
|
||||
else
|
||||
gtk_menu_tracker_item_set_submenu_shown (self, shown);
|
||||
}
|
||||
|
||||
gboolean
|
||||
_gtk_menu_tracker_item_is_visible (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return self->is_visible;
|
||||
}
|
||||
|
||||
gboolean
|
||||
_gtk_menu_tracker_item_may_disappear (GtkMenuTrackerItem *self)
|
||||
{
|
||||
return self->hidden_when != HIDDEN_NEVER;
|
||||
}
|
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2011, 2013 Canonical Limited
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the licence, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Ryan Lortie <desrt@desrt.ca>
|
||||
*/
|
||||
|
||||
#ifndef __GTK_MENU_TRACKER_ITEM_H__
|
||||
#define __GTK_MENU_TRACKER_ITEM_H__
|
||||
|
||||
#include "gtkactionobservable.h"
|
||||
|
||||
#define GTK_TYPE_MENU_TRACKER_ITEM (gtk_menu_tracker_item_get_type ())
|
||||
#define GTK_MENU_TRACKER_ITEM(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
|
||||
GTK_TYPE_MENU_TRACKER_ITEM, GtkMenuTrackerItem))
|
||||
#define GTK_IS_MENU_TRACKER_ITEM(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \
|
||||
GTK_TYPE_MENU_TRACKER_ITEM))
|
||||
|
||||
typedef struct _GtkMenuTrackerItem GtkMenuTrackerItem;
|
||||
|
||||
#define GTK_TYPE_MENU_TRACKER_ITEM_ROLE (gtk_menu_tracker_item_role_get_type ())
|
||||
|
||||
typedef enum {
|
||||
GTK_MENU_TRACKER_ITEM_ROLE_NORMAL,
|
||||
GTK_MENU_TRACKER_ITEM_ROLE_CHECK,
|
||||
GTK_MENU_TRACKER_ITEM_ROLE_RADIO,
|
||||
} GtkMenuTrackerItemRole;
|
||||
|
||||
GType gtk_menu_tracker_item_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GType gtk_menu_tracker_item_role_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GtkMenuTrackerItem * _gtk_menu_tracker_item_new (GtkActionObservable *observable,
|
||||
GMenuModel *model,
|
||||
gint item_index,
|
||||
const gchar *action_namespace,
|
||||
gboolean is_separator);
|
||||
|
||||
GtkActionObservable * _gtk_menu_tracker_item_get_observable (GtkMenuTrackerItem *self);
|
||||
|
||||
gboolean gtk_menu_tracker_item_get_is_separator (GtkMenuTrackerItem *self);
|
||||
|
||||
gboolean gtk_menu_tracker_item_get_has_submenu (GtkMenuTrackerItem *self);
|
||||
|
||||
const gchar * gtk_menu_tracker_item_get_label (GtkMenuTrackerItem *self);
|
||||
|
||||
GIcon * gtk_menu_tracker_item_get_icon (GtkMenuTrackerItem *self);
|
||||
|
||||
gboolean gtk_menu_tracker_item_get_sensitive (GtkMenuTrackerItem *self);
|
||||
|
||||
gboolean gtk_menu_tracker_item_get_visible (GtkMenuTrackerItem *self);
|
||||
|
||||
GtkMenuTrackerItemRole gtk_menu_tracker_item_get_role (GtkMenuTrackerItem *self);
|
||||
|
||||
gboolean gtk_menu_tracker_item_get_toggled (GtkMenuTrackerItem *self);
|
||||
|
||||
const gchar * gtk_menu_tracker_item_get_accel (GtkMenuTrackerItem *self);
|
||||
|
||||
GMenuModel * _gtk_menu_tracker_item_get_submenu (GtkMenuTrackerItem *self);
|
||||
|
||||
gchar * _gtk_menu_tracker_item_get_submenu_namespace (GtkMenuTrackerItem *self);
|
||||
|
||||
gboolean _gtk_menu_tracker_item_may_disappear (GtkMenuTrackerItem *self);
|
||||
|
||||
gboolean _gtk_menu_tracker_item_is_visible (GtkMenuTrackerItem *self);
|
||||
|
||||
gboolean gtk_menu_tracker_item_get_should_request_show (GtkMenuTrackerItem *self);
|
||||
|
||||
void gtk_menu_tracker_item_activated (GtkMenuTrackerItem *self);
|
||||
|
||||
void gtk_menu_tracker_item_request_submenu_shown (GtkMenuTrackerItem *self,
|
||||
gboolean shown);
|
||||
|
||||
gboolean gtk_menu_tracker_item_get_submenu_shown (GtkMenuTrackerItem *self);
|
||||
|
||||
#endif
|
@@ -11,8 +11,6 @@
|
||||
#include <cogl-pango/cogl-pango.h>
|
||||
#include <clutter/clutter.h>
|
||||
#include <clutter/x11/clutter-x11.h>
|
||||
#include <gdk/gdk.h>
|
||||
#include <gdk/gdkx.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <glib/gi18n-lib.h>
|
||||
#include <girepository.h>
|
||||
|
@@ -60,24 +60,17 @@ gnome_shell_deps += recorder_deps
|
||||
tools_cflags = '-DLOCALEDIR="@0@"'.format(localedir)
|
||||
tools_deps = [gio_dep, gjs_dep]
|
||||
|
||||
libshell_menu_gir_sources = [
|
||||
libshell_menu_sources = [
|
||||
'gtkactionmuxer.h',
|
||||
'gtkactionmuxer.c',
|
||||
'gtkactionobservable.h',
|
||||
'gtkactionobservable.c',
|
||||
'gtkactionobserver.h',
|
||||
'gtkactionobserver.c',
|
||||
'gtkmenutrackeritem.c',
|
||||
'gtkmenutrackeritem.h'
|
||||
]
|
||||
|
||||
libshell_menu_no_gir_sources= [
|
||||
'gtkmenutracker.c',
|
||||
'gtkmenutracker.h'
|
||||
'gtkactionobserver.c'
|
||||
]
|
||||
|
||||
libshell_menu = library('gnome-shell-menu',
|
||||
sources: libshell_menu_gir_sources + libshell_menu_no_gir_sources,
|
||||
sources: libshell_menu_sources,
|
||||
dependencies: [gio_dep, clutter_dep],
|
||||
include_directories: conf_inc,
|
||||
build_rpath: mutter_typelibdir,
|
||||
@@ -86,20 +79,6 @@ libshell_menu = library('gnome-shell-menu',
|
||||
install: true
|
||||
)
|
||||
|
||||
libshell_menu_gir = gnome.generate_gir(libshell_menu,
|
||||
sources: libshell_menu_gir_sources,
|
||||
nsversion: '0.1',
|
||||
namespace: 'ShellMenu',
|
||||
identifier_prefix: 'Gtk',
|
||||
symbol_prefix: 'gtk',
|
||||
includes: ['Gio-2.0', libst_gir[0]],
|
||||
dependencies: [mutter_dep],
|
||||
extra_args: ['--quiet'],
|
||||
install_dir_gir: pkgdatadir,
|
||||
install_dir_typelib: pkglibdir,
|
||||
install: true
|
||||
)
|
||||
|
||||
libshell_menu_dep = declare_dependency(link_with: libshell_menu)
|
||||
|
||||
libshell_public_headers = [
|
||||
@@ -148,8 +127,6 @@ libshell_sources = [
|
||||
'shell-invert-lightness-effect.c',
|
||||
'shell-keyring-prompt.c',
|
||||
'shell-keyring-prompt.h',
|
||||
'shell-menu-tracker.c',
|
||||
'shell-menu-tracker.h',
|
||||
'shell-mount-operation.c',
|
||||
'shell-perf-log.c',
|
||||
'shell-polkit-authentication-agent.c',
|
||||
@@ -231,7 +208,6 @@ endif
|
||||
|
||||
libshell_gir_includes += [
|
||||
libgvc_gir[0],
|
||||
libshell_menu_gir[0],
|
||||
libst_gir[0]
|
||||
]
|
||||
|
||||
|
@@ -50,6 +50,7 @@ struct _ShellAppSystemPrivate {
|
||||
GHashTable *running_apps;
|
||||
GHashTable *id_to_app;
|
||||
GHashTable *startup_wm_class_to_id;
|
||||
GList *installed_apps;
|
||||
};
|
||||
|
||||
static void shell_app_system_finalize (GObject *object);
|
||||
@@ -82,12 +83,14 @@ static void
|
||||
scan_startup_wm_class_to_id (ShellAppSystem *self)
|
||||
{
|
||||
ShellAppSystemPrivate *priv = self->priv;
|
||||
GList *apps, *l;
|
||||
GList *l;
|
||||
|
||||
g_hash_table_remove_all (priv->startup_wm_class_to_id);
|
||||
|
||||
apps = g_app_info_get_all ();
|
||||
for (l = apps; l != NULL; l = l->next)
|
||||
g_list_free_full (priv->installed_apps, g_object_unref);
|
||||
priv->installed_apps = g_app_info_get_all ();
|
||||
|
||||
for (l = priv->installed_apps; l != NULL; l = l->next)
|
||||
{
|
||||
GAppInfo *info = l->data;
|
||||
const char *startup_wm_class, *id, *old_id;
|
||||
@@ -105,8 +108,6 @@ scan_startup_wm_class_to_id (ShellAppSystem *self)
|
||||
g_hash_table_insert (priv->startup_wm_class_to_id,
|
||||
g_strdup (startup_wm_class), g_strdup (id));
|
||||
}
|
||||
|
||||
g_list_free_full (apps, g_object_unref);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
@@ -198,6 +199,7 @@ shell_app_system_finalize (GObject *object)
|
||||
g_hash_table_destroy (priv->running_apps);
|
||||
g_hash_table_destroy (priv->id_to_app);
|
||||
g_hash_table_destroy (priv->startup_wm_class_to_id);
|
||||
g_list_free_full (priv->installed_apps, g_object_unref);
|
||||
|
||||
G_OBJECT_CLASS (shell_app_system_parent_class)->finalize (object);
|
||||
}
|
||||
@@ -436,3 +438,21 @@ shell_app_system_search (const char *search_string)
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_app_system_get_installed:
|
||||
* @self: the #ShellAppSystem
|
||||
*
|
||||
* Returns all installed apps, as a list of #GAppInfo
|
||||
*
|
||||
* Returns: (transfer none) (element-type GAppInfo): a list of #GAppInfo
|
||||
* describing all known applications. This memory is owned by the
|
||||
* #ShellAppSystem and should not be freed.
|
||||
**/
|
||||
GList *
|
||||
shell_app_system_get_installed (ShellAppSystem *self)
|
||||
{
|
||||
ShellAppSystemPrivate *priv = self->priv;
|
||||
|
||||
return priv->installed_apps;
|
||||
}
|
||||
|
@@ -27,4 +27,6 @@ ShellApp *shell_app_system_lookup_desktop_wmclass (ShellAppSystem *s
|
||||
GSList *shell_app_system_get_running (ShellAppSystem *self);
|
||||
char ***shell_app_system_search (const char *search_string);
|
||||
|
||||
GList *shell_app_system_get_installed (ShellAppSystem *self);
|
||||
|
||||
#endif /* __SHELL_APP_SYSTEM_H__ */
|
||||
|
@@ -26,12 +26,6 @@
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
MATCH_NONE,
|
||||
MATCH_SUBSTRING, /* Not prefix, substring */
|
||||
MATCH_PREFIX, /* Strict prefix */
|
||||
} ShellAppSearchMatch;
|
||||
|
||||
/* This is mainly a memory usage optimization - the user is going to
|
||||
* be running far fewer of the applications at one time than they have
|
||||
* installed. But it also just helps keep the code more logically
|
||||
@@ -51,7 +45,6 @@ typedef struct {
|
||||
guint window_sort_stale : 1;
|
||||
|
||||
/* See GApplication documentation */
|
||||
GDBusMenuModel *remote_menu;
|
||||
GtkActionMuxer *muxer;
|
||||
char *unique_bus_name;
|
||||
GDBusConnection *session;
|
||||
@@ -98,7 +91,6 @@ enum {
|
||||
PROP_ID,
|
||||
PROP_DBUS_ID,
|
||||
PROP_ACTION_GROUP,
|
||||
PROP_MENU,
|
||||
PROP_APP_INFO
|
||||
};
|
||||
|
||||
@@ -137,10 +129,6 @@ shell_app_get_property (GObject *gobject,
|
||||
if (app->running_state)
|
||||
g_value_set_object (value, app->running_state->muxer);
|
||||
break;
|
||||
case PROP_MENU:
|
||||
if (app->running_state)
|
||||
g_value_set_object (value, app->running_state->remote_menu);
|
||||
break;
|
||||
case PROP_APP_INFO:
|
||||
if (app->info)
|
||||
g_value_set_object (value, app->info);
|
||||
@@ -216,7 +204,7 @@ window_backed_app_get_icon (ShellApp *app,
|
||||
|
||||
if (window == NULL)
|
||||
{
|
||||
actor = clutter_texture_new ();
|
||||
actor = clutter_actor_new ();
|
||||
g_object_set (actor, "opacity", 0, "width", (float) size, "height", (float) size, NULL);
|
||||
return actor;
|
||||
}
|
||||
@@ -608,6 +596,7 @@ gboolean
|
||||
shell_app_can_open_new_window (ShellApp *app)
|
||||
{
|
||||
ShellAppRunningState *state;
|
||||
MetaWindow *window;
|
||||
|
||||
/* Apps that are not running can always open new windows, because
|
||||
activating them would open the first one */
|
||||
@@ -641,11 +630,13 @@ shell_app_can_open_new_window (ShellApp *app)
|
||||
Activate() knows nothing about the other instances, so it will show a
|
||||
new window.
|
||||
*/
|
||||
if (state->remote_menu)
|
||||
|
||||
window = state->windows->data;
|
||||
|
||||
if (state->unique_bus_name != NULL &&
|
||||
meta_window_get_gtk_application_object_path (window) != NULL)
|
||||
{
|
||||
const char *application_id;
|
||||
application_id = meta_window_get_gtk_application_id (state->windows->data);
|
||||
if (application_id != NULL)
|
||||
if (meta_window_get_gtk_application_id (window) != NULL)
|
||||
return FALSE;
|
||||
else
|
||||
return TRUE;
|
||||
@@ -1078,7 +1069,7 @@ _shell_app_add_window (ShellApp *app,
|
||||
g_signal_connect (window, "notify::user-time", G_CALLBACK(shell_app_on_user_time_changed), app);
|
||||
g_signal_connect (window, "notify::skip-taskbar", G_CALLBACK(shell_app_on_skip_taskbar_changed), app);
|
||||
|
||||
shell_app_update_app_menu (app, window);
|
||||
shell_app_update_app_actions (app, window);
|
||||
shell_app_ensure_busy_watch (app);
|
||||
|
||||
if (!meta_window_is_skip_taskbar (window))
|
||||
@@ -1380,8 +1371,8 @@ create_running_state (ShellApp *app)
|
||||
}
|
||||
|
||||
void
|
||||
shell_app_update_app_menu (ShellApp *app,
|
||||
MetaWindow *window)
|
||||
shell_app_update_app_actions (ShellApp *app,
|
||||
MetaWindow *window)
|
||||
{
|
||||
const gchar *unique_bus_name;
|
||||
|
||||
@@ -1397,23 +1388,18 @@ shell_app_update_app_menu (ShellApp *app,
|
||||
|
||||
unique_bus_name = meta_window_get_gtk_unique_bus_name (window);
|
||||
|
||||
if (app->running_state->remote_menu == NULL ||
|
||||
g_strcmp0 (app->running_state->unique_bus_name, unique_bus_name) != 0)
|
||||
if (g_strcmp0 (app->running_state->unique_bus_name, unique_bus_name) != 0)
|
||||
{
|
||||
const gchar *application_object_path;
|
||||
const gchar *app_menu_object_path;
|
||||
GDBusActionGroup *actions;
|
||||
|
||||
application_object_path = meta_window_get_gtk_application_object_path (window);
|
||||
app_menu_object_path = meta_window_get_gtk_app_menu_object_path (window);
|
||||
|
||||
if (application_object_path == NULL || app_menu_object_path == NULL || unique_bus_name == NULL)
|
||||
if (application_object_path == NULL || unique_bus_name == NULL)
|
||||
return;
|
||||
|
||||
g_clear_pointer (&app->running_state->unique_bus_name, g_free);
|
||||
app->running_state->unique_bus_name = g_strdup (unique_bus_name);
|
||||
g_clear_object (&app->running_state->remote_menu);
|
||||
app->running_state->remote_menu = g_dbus_menu_model_get (app->running_state->session, unique_bus_name, app_menu_object_path);
|
||||
actions = g_dbus_action_group_get (app->running_state->session, unique_bus_name, application_object_path);
|
||||
gtk_action_muxer_insert (app->running_state->muxer, "app", G_ACTION_GROUP (actions));
|
||||
g_object_unref (actions);
|
||||
@@ -1443,11 +1429,9 @@ unref_running_state (ShellAppRunningState *state)
|
||||
g_clear_object (&state->cancellable);
|
||||
}
|
||||
|
||||
g_clear_object (&state->remote_menu);
|
||||
g_clear_object (&state->muxer);
|
||||
g_clear_object (&state->session);
|
||||
g_clear_pointer (&state->unique_bus_name, g_free);
|
||||
g_clear_pointer (&state->remote_menu, g_free);
|
||||
|
||||
g_slice_free (ShellAppRunningState, state);
|
||||
}
|
||||
@@ -1575,19 +1559,6 @@ shell_app_class_init(ShellAppClass *klass)
|
||||
"The action group exported by the remote application",
|
||||
G_TYPE_ACTION_GROUP,
|
||||
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
/**
|
||||
* ShellApp:menu:
|
||||
*
|
||||
* The #GMenuProxy associated with this ShellApp, if any. See the
|
||||
* documentation of #GMenuModel for details.
|
||||
*/
|
||||
g_object_class_install_property (gobject_class,
|
||||
PROP_MENU,
|
||||
g_param_spec_object ("menu",
|
||||
"Application Menu",
|
||||
"The primary menu exported by the remote application",
|
||||
G_TYPE_MENU_MODEL,
|
||||
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
/**
|
||||
* ShellApp:app-info:
|
||||
*
|
||||
|
@@ -67,7 +67,7 @@ int shell_app_compare_by_name (ShellApp *app, ShellApp *other);
|
||||
int shell_app_compare (ShellApp *app, ShellApp *other);
|
||||
|
||||
void shell_app_update_window_actions (ShellApp *app, MetaWindow *window);
|
||||
void shell_app_update_app_menu (ShellApp *app, MetaWindow *window);
|
||||
void shell_app_update_app_actions (ShellApp *app, MetaWindow *window);
|
||||
|
||||
gboolean shell_app_get_busy (ShellApp *app);
|
||||
|
||||
|
@@ -85,6 +85,8 @@ struct _ShellGlobal {
|
||||
GSList *leisure_closures;
|
||||
guint leisure_function_id;
|
||||
|
||||
GHashTable *save_ops;
|
||||
|
||||
gboolean has_modal;
|
||||
gboolean frame_timestamps;
|
||||
gboolean frame_finish_timestamp;
|
||||
@@ -311,6 +313,10 @@ shell_global_init (ShellGlobal *global)
|
||||
NULL);
|
||||
|
||||
g_strfreev (search_path);
|
||||
|
||||
global->save_ops = g_hash_table_new_full (g_file_hash,
|
||||
(GEqualFunc) g_file_equal,
|
||||
g_object_unref, g_object_unref);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -330,6 +336,8 @@ shell_global_finalize (GObject *object)
|
||||
g_free (global->imagedir);
|
||||
g_free (global->userdatadir);
|
||||
|
||||
g_hash_table_unref (global->save_ops);
|
||||
|
||||
G_OBJECT_CLASS(shell_global_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
@@ -1374,24 +1382,26 @@ shell_global_create_app_launch_context (ShellGlobal *global,
|
||||
guint32 timestamp,
|
||||
int workspace)
|
||||
{
|
||||
GdkAppLaunchContext *context;
|
||||
MetaWorkspaceManager *workspace_manager = global->workspace_manager;
|
||||
MetaStartupNotification *sn;
|
||||
MetaLaunchContext *context;
|
||||
MetaWorkspace *ws = NULL;
|
||||
|
||||
context = gdk_display_get_app_launch_context (global->gdk_display);
|
||||
sn = meta_display_get_startup_notification (global->meta_display);
|
||||
context = meta_startup_notification_create_launcher (sn);
|
||||
|
||||
if (timestamp == 0)
|
||||
timestamp = shell_global_get_current_time (global);
|
||||
gdk_app_launch_context_set_timestamp (context, timestamp);
|
||||
meta_launch_context_set_timestamp (context, timestamp);
|
||||
|
||||
if (workspace < 0)
|
||||
{
|
||||
MetaWorkspaceManager *workspace_manager = global->workspace_manager;
|
||||
ws = meta_workspace_manager_get_active_workspace (workspace_manager);
|
||||
else
|
||||
ws = meta_workspace_manager_get_workspace_by_index (workspace_manager, workspace);
|
||||
|
||||
workspace =
|
||||
meta_workspace_manager_get_active_workspace_index (workspace_manager);
|
||||
}
|
||||
gdk_app_launch_context_set_desktop (context, workspace);
|
||||
meta_launch_context_set_workspace (context, ws);
|
||||
|
||||
return (GAppLaunchContext *)context;
|
||||
return (GAppLaunchContext *) context;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
@@ -1541,20 +1551,77 @@ shell_global_get_session_mode (ShellGlobal *global)
|
||||
}
|
||||
|
||||
static void
|
||||
save_variant (GFile *dir,
|
||||
const char *property_name,
|
||||
GVariant *variant)
|
||||
delete_variant_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
ShellGlobal *global = user_data;
|
||||
GError *error = NULL;
|
||||
|
||||
if (!g_file_delete_finish (G_FILE (object), result, &error))
|
||||
{
|
||||
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
||||
{
|
||||
g_warning ("Could not delete runtime/persistent state file: %s\n",
|
||||
error->message);
|
||||
}
|
||||
|
||||
g_error_free (error);
|
||||
}
|
||||
|
||||
g_hash_table_remove (global->save_ops, object);
|
||||
}
|
||||
|
||||
static void
|
||||
replace_variant_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
ShellGlobal *global = user_data;
|
||||
GError *error = NULL;
|
||||
|
||||
if (!g_file_replace_contents_finish (G_FILE (object), result, NULL, &error))
|
||||
{
|
||||
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
||||
{
|
||||
g_warning ("Could not replace runtime/persistent state file: %s\n",
|
||||
error->message);
|
||||
}
|
||||
|
||||
g_error_free (error);
|
||||
}
|
||||
|
||||
g_hash_table_remove (global->save_ops, object);
|
||||
}
|
||||
|
||||
static void
|
||||
save_variant (ShellGlobal *global,
|
||||
GFile *dir,
|
||||
const char *property_name,
|
||||
GVariant *variant)
|
||||
{
|
||||
GFile *path = g_file_get_child (dir, property_name);
|
||||
GCancellable *cancellable;
|
||||
|
||||
cancellable = g_hash_table_lookup (global->save_ops, path);
|
||||
g_cancellable_cancel (cancellable);
|
||||
|
||||
cancellable = g_cancellable_new ();
|
||||
g_hash_table_insert (global->save_ops, g_object_ref (path), cancellable);
|
||||
|
||||
if (variant == NULL || g_variant_get_data (variant) == NULL)
|
||||
(void) g_file_delete (path, NULL, NULL);
|
||||
{
|
||||
g_file_delete_async (path, G_PRIORITY_DEFAULT, cancellable,
|
||||
delete_variant_cb, global);
|
||||
}
|
||||
else
|
||||
{
|
||||
gsize size = g_variant_get_size (variant);
|
||||
g_file_replace_contents (path, g_variant_get_data (variant), size,
|
||||
NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION,
|
||||
NULL, NULL, NULL);
|
||||
g_file_replace_contents_async (path,
|
||||
g_variant_get_data (variant),
|
||||
g_variant_get_size (variant),
|
||||
NULL, FALSE,
|
||||
G_FILE_CREATE_REPLACE_DESTINATION,
|
||||
cancellable, replace_variant_cb, global);
|
||||
}
|
||||
|
||||
g_object_unref (path);
|
||||
@@ -1608,7 +1675,7 @@ shell_global_set_runtime_state (ShellGlobal *global,
|
||||
const char *property_name,
|
||||
GVariant *variant)
|
||||
{
|
||||
save_variant (global->runtime_state_path, property_name, variant);
|
||||
save_variant (global, global->runtime_state_path, property_name, variant);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1643,7 +1710,7 @@ shell_global_set_persistent_state (ShellGlobal *global,
|
||||
const char *property_name,
|
||||
GVariant *variant)
|
||||
{
|
||||
save_variant (global->userdatadir_path, property_name, variant);
|
||||
save_variant (global, global->userdatadir_path, property_name, variant);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,166 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* Written by:
|
||||
* Jasper St. Pierre <jstpierre@mecheye.net>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "shell-menu-tracker.h"
|
||||
#include "gtkmenutracker.h"
|
||||
|
||||
/**
|
||||
* SECTION:shell-menu-tracker
|
||||
* @short_description: a simple wrapper around #GtkMenuTracker
|
||||
* to make it bindable.
|
||||
*/
|
||||
|
||||
struct _ShellMenuTracker
|
||||
{
|
||||
guint ref_count;
|
||||
|
||||
GtkMenuTracker *tracker;
|
||||
|
||||
ShellMenuTrackerInsertFunc insert_func;
|
||||
gpointer insert_user_data;
|
||||
GDestroyNotify insert_notify;
|
||||
ShellMenuTrackerRemoveFunc remove_func;
|
||||
gpointer remove_user_data;
|
||||
GDestroyNotify remove_notify;
|
||||
};
|
||||
|
||||
static void
|
||||
shell_menu_tracker_insert_func (GtkMenuTrackerItem *item,
|
||||
gint position,
|
||||
gpointer user_data)
|
||||
{
|
||||
ShellMenuTracker *tracker = (ShellMenuTracker *) user_data;
|
||||
tracker->insert_func (item, position, tracker->insert_user_data);
|
||||
}
|
||||
|
||||
static void
|
||||
shell_menu_tracker_remove_func (gint position,
|
||||
gpointer user_data)
|
||||
{
|
||||
ShellMenuTracker *tracker = (ShellMenuTracker *) user_data;
|
||||
tracker->remove_func (position, tracker->remove_user_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* shell_menu_tracker_new:
|
||||
* @observable:
|
||||
* @model:
|
||||
* @action_namespace: (nullable):
|
||||
* @insert_func:
|
||||
* @insert_user_data:
|
||||
* @insert_notify:
|
||||
* @remove_func:
|
||||
* @remove_user_data:
|
||||
* @remove_notify:
|
||||
*/
|
||||
ShellMenuTracker *
|
||||
shell_menu_tracker_new (GtkActionObservable *observable,
|
||||
GMenuModel *model,
|
||||
const gchar *action_namespace,
|
||||
ShellMenuTrackerInsertFunc insert_func,
|
||||
gpointer insert_user_data,
|
||||
GDestroyNotify insert_notify,
|
||||
ShellMenuTrackerRemoveFunc remove_func,
|
||||
gpointer remove_user_data,
|
||||
GDestroyNotify remove_notify)
|
||||
{
|
||||
ShellMenuTracker *tracker = g_slice_new0 (ShellMenuTracker);
|
||||
|
||||
tracker->ref_count = 1;
|
||||
tracker->insert_func = insert_func;
|
||||
tracker->insert_user_data = insert_user_data;
|
||||
tracker->insert_notify = insert_notify;
|
||||
tracker->remove_func = remove_func;
|
||||
tracker->remove_user_data = remove_user_data;
|
||||
tracker->remove_notify = remove_notify;
|
||||
|
||||
tracker->tracker = gtk_menu_tracker_new (observable,
|
||||
model,
|
||||
TRUE, /* with separators */
|
||||
action_namespace,
|
||||
shell_menu_tracker_insert_func,
|
||||
shell_menu_tracker_remove_func,
|
||||
tracker);
|
||||
|
||||
return tracker;
|
||||
}
|
||||
|
||||
ShellMenuTracker *
|
||||
shell_menu_tracker_new_for_item_submenu (GtkMenuTrackerItem *item,
|
||||
ShellMenuTrackerInsertFunc insert_func,
|
||||
gpointer insert_user_data,
|
||||
GDestroyNotify insert_notify,
|
||||
ShellMenuTrackerRemoveFunc remove_func,
|
||||
gpointer remove_user_data,
|
||||
GDestroyNotify remove_notify)
|
||||
{
|
||||
ShellMenuTracker *tracker = g_slice_new0 (ShellMenuTracker);
|
||||
|
||||
tracker->ref_count = 1;
|
||||
tracker->insert_func = insert_func;
|
||||
tracker->insert_user_data = insert_user_data;
|
||||
tracker->insert_notify = insert_notify;
|
||||
tracker->remove_func = remove_func;
|
||||
tracker->remove_user_data = remove_user_data;
|
||||
tracker->remove_notify = remove_notify;
|
||||
|
||||
tracker->tracker = gtk_menu_tracker_new_for_item_submenu (item,
|
||||
shell_menu_tracker_insert_func,
|
||||
shell_menu_tracker_remove_func,
|
||||
tracker);
|
||||
|
||||
return tracker;
|
||||
}
|
||||
|
||||
ShellMenuTracker *
|
||||
shell_menu_tracker_ref (ShellMenuTracker *tracker)
|
||||
{
|
||||
tracker->ref_count++;
|
||||
return tracker;
|
||||
}
|
||||
|
||||
void
|
||||
shell_menu_tracker_unref (ShellMenuTracker *tracker)
|
||||
{
|
||||
if (tracker->ref_count-- <= 0)
|
||||
{
|
||||
shell_menu_tracker_destroy (tracker);
|
||||
g_slice_free (ShellMenuTracker, tracker);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
shell_menu_tracker_destroy (ShellMenuTracker *tracker)
|
||||
{
|
||||
if (tracker->tracker != NULL)
|
||||
{
|
||||
gtk_menu_tracker_free (tracker->tracker);
|
||||
tracker->tracker = NULL;
|
||||
tracker->insert_notify (tracker->insert_user_data);
|
||||
tracker->remove_notify (tracker->remove_user_data);
|
||||
}
|
||||
}
|
||||
|
||||
G_DEFINE_BOXED_TYPE(ShellMenuTracker,
|
||||
shell_menu_tracker,
|
||||
shell_menu_tracker_ref,
|
||||
shell_menu_tracker_unref)
|
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Red Hat
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* Written by:
|
||||
* Jasper St. Pierre <jstpierre@mecheye.net>
|
||||
*/
|
||||
|
||||
#ifndef __SHELL_MENU_TRACKER_H__
|
||||
#define __SHELL_MENU_TRACKER_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "gtkmenutrackeritem.h"
|
||||
|
||||
typedef struct _ShellMenuTracker ShellMenuTracker;
|
||||
|
||||
GType shell_menu_tracker_get_type (void) G_GNUC_CONST;
|
||||
|
||||
typedef void (* ShellMenuTrackerInsertFunc) (GtkMenuTrackerItem *item,
|
||||
gint position,
|
||||
gpointer user_data);
|
||||
|
||||
typedef void (* ShellMenuTrackerRemoveFunc) (gint position,
|
||||
gpointer user_data);
|
||||
|
||||
ShellMenuTracker * shell_menu_tracker_new (GtkActionObservable *observable,
|
||||
GMenuModel *model,
|
||||
const gchar *action_namespace,
|
||||
ShellMenuTrackerInsertFunc insert_func,
|
||||
gpointer insert_user_data,
|
||||
GDestroyNotify insert_notify,
|
||||
ShellMenuTrackerRemoveFunc remove_func,
|
||||
gpointer remove_user_data,
|
||||
GDestroyNotify remove_notify);
|
||||
ShellMenuTracker * shell_menu_tracker_new_for_item_submenu (GtkMenuTrackerItem *item,
|
||||
ShellMenuTrackerInsertFunc insert_func,
|
||||
gpointer insert_user_data,
|
||||
GDestroyNotify insert_notify,
|
||||
ShellMenuTrackerRemoveFunc remove_func,
|
||||
gpointer remove_user_data,
|
||||
GDestroyNotify remove_notify);
|
||||
|
||||
ShellMenuTracker * shell_menu_tracker_ref (ShellMenuTracker *tracker);
|
||||
void shell_menu_tracker_unref (ShellMenuTracker *tracker);
|
||||
void shell_menu_tracker_destroy (ShellMenuTracker *tracker);
|
||||
|
||||
#endif /* __SHELL_MENU_TRACKER_H__ */
|
@@ -12,7 +12,6 @@
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk/gdk.h>
|
||||
|
||||
#include <cogl/cogl.h>
|
||||
#include <meta/meta-cursor-tracker.h>
|
||||
@@ -52,8 +51,6 @@ struct _ShellRecorder {
|
||||
int stage_width;
|
||||
int stage_height;
|
||||
|
||||
GdkScreen *gdk_screen;
|
||||
|
||||
int pointer_x;
|
||||
int pointer_y;
|
||||
|
||||
@@ -210,8 +207,6 @@ shell_recorder_init (ShellRecorder *recorder)
|
||||
|
||||
shell_recorder_src_register ();
|
||||
|
||||
recorder->gdk_screen = gdk_screen_get_default ();
|
||||
|
||||
recorder->memory_target = get_memory_target();
|
||||
|
||||
recorder->a11y_settings = g_settings_new (A11Y_APPS_SCHEMA);
|
||||
|
@@ -146,7 +146,7 @@ shell_stack_get_preferred_width (ClutterActor *actor,
|
||||
static gboolean
|
||||
shell_stack_navigate_focus (StWidget *widget,
|
||||
ClutterActor *from,
|
||||
GtkDirectionType direction)
|
||||
StDirectionType direction)
|
||||
{
|
||||
ClutterActor *top_actor;
|
||||
|
||||
|
@@ -19,7 +19,6 @@
|
||||
#include <glib/gi18n-lib.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||
#include <gdk/gdkx.h>
|
||||
#include <meta/meta-shaped-texture.h>
|
||||
|
||||
#include <locale.h>
|
||||
@@ -235,14 +234,21 @@ shell_util_translate_time_string (const char *str)
|
||||
const char *locale = g_getenv ("LC_TIME");
|
||||
const char *res;
|
||||
char *sep;
|
||||
locale_t old_loc;
|
||||
locale_t loc = (locale_t) 0;
|
||||
|
||||
if (locale)
|
||||
setlocale (LC_MESSAGES, locale);
|
||||
loc = newlocale (LC_MESSAGES_MASK, locale, (locale_t) 0);
|
||||
|
||||
old_loc = uselocale (loc);
|
||||
|
||||
sep = strchr (str, '\004');
|
||||
res = g_dpgettext (NULL, str, sep ? sep - str + 1 : 0);
|
||||
|
||||
setlocale (LC_MESSAGES, "");
|
||||
uselocale (old_loc);
|
||||
|
||||
if (loc != (locale_t) 0)
|
||||
freelocale (loc);
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -367,24 +373,6 @@ shell_util_create_pixbuf_from_data (const guchar *data,
|
||||
(GdkPixbufDestroyNotify) g_free, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
shell_util_cursor_tracker_to_clutter (MetaCursorTracker *tracker,
|
||||
ClutterTexture *texture)
|
||||
{
|
||||
CoglTexture *sprite;
|
||||
|
||||
sprite = meta_cursor_tracker_get_sprite (tracker);
|
||||
if (sprite)
|
||||
{
|
||||
clutter_actor_show (CLUTTER_ACTOR (texture));
|
||||
clutter_texture_set_cogl_texture (texture, sprite);
|
||||
}
|
||||
else
|
||||
{
|
||||
clutter_actor_hide (CLUTTER_ACTOR (texture));
|
||||
}
|
||||
}
|
||||
|
||||
typedef const gchar *(*ShellGLGetString) (GLenum);
|
||||
|
||||
static const gchar *
|
||||
|
@@ -44,9 +44,6 @@ GdkPixbuf *shell_util_create_pixbuf_from_data (const guchar *data,
|
||||
int height,
|
||||
int rowstride);
|
||||
|
||||
void shell_util_cursor_tracker_to_clutter (MetaCursorTracker *tracker,
|
||||
ClutterTexture *texture);
|
||||
|
||||
gboolean shell_util_need_background_refresh (void);
|
||||
|
||||
ClutterContent * shell_util_get_content_for_window_actor (MetaWindowActor *window_actor,
|
||||
|
@@ -5,10 +5,6 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <gdk/gdk.h>
|
||||
#include <gdk/gdkx.h>
|
||||
#include <meta/display.h>
|
||||
#include <meta/group.h>
|
||||
#include <meta/util.h>
|
||||
@@ -478,7 +474,7 @@ update_focus_app (ShellWindowTracker *self)
|
||||
if (new_focus_app)
|
||||
{
|
||||
shell_app_update_window_actions (new_focus_app, new_focus_win);
|
||||
shell_app_update_app_menu (new_focus_app, new_focus_win);
|
||||
shell_app_update_app_actions (new_focus_app, new_focus_win);
|
||||
}
|
||||
|
||||
set_focus_app (self, new_focus_app);
|
||||
|
@@ -17,6 +17,7 @@ st_headers = [
|
||||
'st-scrollable.h',
|
||||
'st-scroll-bar.h',
|
||||
'st-scroll-view.h',
|
||||
'st-settings.h',
|
||||
'st-shadow.h',
|
||||
'st-texture-cache.h',
|
||||
'st-theme.h',
|
||||
@@ -70,6 +71,7 @@ st_sources = [
|
||||
'st-scrollable.c',
|
||||
'st-scroll-bar.c',
|
||||
'st-scroll-view.c',
|
||||
'st-settings.c',
|
||||
'st-shadow.c',
|
||||
'st-texture-cache.c',
|
||||
'st-theme.c',
|
||||
|
@@ -200,7 +200,7 @@ st_bin_popup_menu (StWidget *widget)
|
||||
static gboolean
|
||||
st_bin_navigate_focus (StWidget *widget,
|
||||
ClutterActor *from,
|
||||
GtkDirectionType direction)
|
||||
StDirectionType direction)
|
||||
{
|
||||
StBinPrivate *priv = st_bin_get_instance_private (ST_BIN (widget));
|
||||
ClutterActor *bin_actor = CLUTTER_ACTOR (widget);
|
||||
|
@@ -241,6 +241,8 @@ st_button_touch_event (ClutterActor *actor,
|
||||
|
||||
if (priv->pressed != 0)
|
||||
return CLUTTER_EVENT_PROPAGATE;
|
||||
if ((priv->button_mask & mask) == 0)
|
||||
return CLUTTER_EVENT_PROPAGATE;
|
||||
|
||||
device = clutter_event_get_device ((ClutterEvent*) event);
|
||||
sequence = clutter_event_get_event_sequence ((ClutterEvent*) event);
|
||||
|
@@ -56,6 +56,7 @@
|
||||
|
||||
#include "st-icon.h"
|
||||
#include "st-label.h"
|
||||
#include "st-settings.h"
|
||||
#include "st-widget.h"
|
||||
#include "st-texture-cache.h"
|
||||
#include "st-clipboard.h"
|
||||
@@ -239,15 +240,15 @@ remove_capslock_feedback (StEntry *entry)
|
||||
}
|
||||
|
||||
static void
|
||||
keymap_state_changed (GdkKeymap *keymap,
|
||||
gpointer user_data)
|
||||
keymap_state_changed (ClutterKeymap *keymap,
|
||||
gpointer user_data)
|
||||
{
|
||||
StEntry *entry = ST_ENTRY (user_data);
|
||||
StEntryPrivate *priv = ST_ENTRY_PRIV (entry);
|
||||
|
||||
if (clutter_text_get_password_char (CLUTTER_TEXT (priv->entry)) != 0)
|
||||
{
|
||||
if (gdk_keymap_get_caps_lock_state (keymap))
|
||||
if (clutter_keymap_get_caps_lock_state (keymap))
|
||||
show_capslock_feedback (entry);
|
||||
else
|
||||
remove_capslock_feedback (entry);
|
||||
@@ -259,11 +260,11 @@ st_entry_dispose (GObject *object)
|
||||
{
|
||||
StEntry *entry = ST_ENTRY (object);
|
||||
StEntryPrivate *priv = ST_ENTRY_PRIV (entry);
|
||||
GdkKeymap *keymap;
|
||||
ClutterKeymap *keymap;
|
||||
|
||||
cogl_clear_object (&priv->text_shadow_material);
|
||||
|
||||
keymap = gdk_keymap_get_for_display (gdk_display_get_default ());
|
||||
keymap = clutter_backend_get_keymap (clutter_get_default_backend ());
|
||||
g_signal_handlers_disconnect_by_func (keymap, keymap_state_changed, entry);
|
||||
|
||||
G_OBJECT_CLASS (st_entry_parent_class)->dispose (object);
|
||||
@@ -330,7 +331,7 @@ st_entry_style_changed (StWidget *self)
|
||||
static gboolean
|
||||
st_entry_navigate_focus (StWidget *widget,
|
||||
ClutterActor *from,
|
||||
GtkDirectionType direction)
|
||||
StDirectionType direction)
|
||||
{
|
||||
StEntryPrivate *priv = ST_ENTRY_PRIV (widget);
|
||||
|
||||
@@ -563,11 +564,11 @@ clutter_text_focus_in_cb (ClutterText *text,
|
||||
ClutterActor *actor)
|
||||
{
|
||||
StEntry *entry = ST_ENTRY (actor);
|
||||
GdkKeymap *keymap;
|
||||
ClutterKeymap *keymap;
|
||||
|
||||
st_entry_update_hint_visibility (entry);
|
||||
|
||||
keymap = gdk_keymap_get_for_display (gdk_display_get_default ());
|
||||
keymap = clutter_backend_get_keymap (clutter_get_default_backend ());
|
||||
keymap_state_changed (keymap, entry);
|
||||
g_signal_connect (keymap, "state-changed",
|
||||
G_CALLBACK (keymap_state_changed), entry);
|
||||
@@ -581,7 +582,7 @@ clutter_text_focus_out_cb (ClutterText *text,
|
||||
ClutterActor *actor)
|
||||
{
|
||||
StEntry *entry = ST_ENTRY (actor);
|
||||
GdkKeymap *keymap;
|
||||
ClutterKeymap *keymap;
|
||||
|
||||
st_widget_remove_style_pseudo_class (ST_WIDGET (actor), "focus");
|
||||
|
||||
@@ -590,7 +591,7 @@ clutter_text_focus_out_cb (ClutterText *text,
|
||||
clutter_text_set_cursor_visible (text, FALSE);
|
||||
remove_capslock_feedback (entry);
|
||||
|
||||
keymap = gdk_keymap_get_for_display (gdk_display_get_default ());
|
||||
keymap = clutter_backend_get_keymap (clutter_get_default_backend ());
|
||||
g_signal_handlers_disconnect_by_func (keymap, keymap_state_changed, entry);
|
||||
}
|
||||
|
||||
@@ -644,29 +645,32 @@ clutter_text_button_press_event (ClutterActor *actor,
|
||||
gpointer user_data)
|
||||
{
|
||||
StEntryPrivate *priv = ST_ENTRY_PRIV (user_data);
|
||||
GtkSettings *settings = gtk_settings_get_default ();
|
||||
gboolean primary_paste_enabled;
|
||||
|
||||
g_object_get (settings,
|
||||
"gtk-enable-primary-paste", &primary_paste_enabled,
|
||||
NULL);
|
||||
|
||||
if (primary_paste_enabled && event->button == 2
|
||||
&& clutter_text_get_editable (CLUTTER_TEXT (priv->entry)))
|
||||
if (event->button == 2 &&
|
||||
clutter_text_get_editable (CLUTTER_TEXT (priv->entry)))
|
||||
{
|
||||
StClipboard *clipboard;
|
||||
StSettings *settings;
|
||||
gboolean primary_paste_enabled;
|
||||
|
||||
clipboard = st_clipboard_get_default ();
|
||||
settings = st_settings_get ();
|
||||
g_object_get (settings, "primary-paste", &primary_paste_enabled, NULL);
|
||||
|
||||
/* By the time the clipboard callback is called,
|
||||
* the rest of the signal handlers will have
|
||||
* run, making the text cursor to be in the correct
|
||||
* place.
|
||||
*/
|
||||
st_clipboard_get_text (clipboard,
|
||||
ST_CLIPBOARD_TYPE_PRIMARY,
|
||||
st_entry_clipboard_callback,
|
||||
user_data);
|
||||
if (primary_paste_enabled)
|
||||
{
|
||||
StClipboard *clipboard;
|
||||
|
||||
clipboard = st_clipboard_get_default ();
|
||||
|
||||
/* By the time the clipboard callback is called,
|
||||
* the rest of the signal handlers will have
|
||||
* run, making the text cursor to be in the correct
|
||||
* place.
|
||||
*/
|
||||
st_clipboard_get_text (clipboard,
|
||||
ST_CLIPBOARD_TYPE_PRIMARY,
|
||||
st_entry_clipboard_callback,
|
||||
user_data);
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
@@ -869,6 +873,7 @@ st_entry_paint (ClutterActor *actor)
|
||||
|
||||
if (priv->text_shadow_material != NULL)
|
||||
_st_paint_shadow_with_opacity (shadow_spec,
|
||||
cogl_get_draw_framebuffer (),
|
||||
priv->text_shadow_material,
|
||||
&allocation,
|
||||
clutter_actor_get_paint_opacity (priv->entry));
|
||||
|
@@ -75,7 +75,7 @@ st_focus_manager_stage_event (ClutterActor *stage,
|
||||
gpointer user_data)
|
||||
{
|
||||
StFocusManager *manager = user_data;
|
||||
GtkDirectionType direction;
|
||||
StDirectionType direction;
|
||||
gboolean wrap_around = FALSE;
|
||||
ClutterActor *focused, *group;
|
||||
|
||||
@@ -85,26 +85,26 @@ st_focus_manager_stage_event (ClutterActor *stage,
|
||||
switch (event->key.keyval)
|
||||
{
|
||||
case CLUTTER_KEY_Up:
|
||||
direction = GTK_DIR_UP;
|
||||
direction = ST_DIR_UP;
|
||||
break;
|
||||
case CLUTTER_KEY_Down:
|
||||
direction = GTK_DIR_DOWN;
|
||||
direction = ST_DIR_DOWN;
|
||||
break;
|
||||
case CLUTTER_KEY_Left:
|
||||
direction = GTK_DIR_LEFT;
|
||||
direction = ST_DIR_LEFT;
|
||||
break;
|
||||
case CLUTTER_KEY_Right:
|
||||
direction = GTK_DIR_RIGHT;
|
||||
direction = ST_DIR_RIGHT;
|
||||
break;
|
||||
case CLUTTER_KEY_Tab:
|
||||
if (event->key.modifier_state & CLUTTER_SHIFT_MASK)
|
||||
direction = GTK_DIR_TAB_BACKWARD;
|
||||
direction = ST_DIR_TAB_BACKWARD;
|
||||
else
|
||||
direction = GTK_DIR_TAB_FORWARD;
|
||||
direction = ST_DIR_TAB_FORWARD;
|
||||
wrap_around = TRUE;
|
||||
break;
|
||||
case CLUTTER_KEY_ISO_Left_Tab:
|
||||
direction = GTK_DIR_TAB_BACKWARD;
|
||||
direction = ST_DIR_TAB_BACKWARD;
|
||||
wrap_around = TRUE;
|
||||
break;
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user