Compare commits

..

103 Commits

Author SHA1 Message Date
a26b0b60d1 Fix srcdir != builddir build of St.gir
We need to use st_cflags which has -I$(srcdir) not ST_CFLAGS
when building St.gir.
2009-10-07 19:32:00 -04:00
60dbb19c2d Reference Meta-2.28.gir
Now that Mutter has been bumped to 2.28.0, we need to reference
Meta-2.28.gir not Meta-2.27.gir.
2009-10-07 19:27:17 -04:00
3703a86354 Add run-test.sh to CLEANFILES
Fix distcheck by adding run-test.sh to CLEANFILES
2009-10-07 19:18:43 -04:00
992b43f914 Bump version to 2.28.0 2009-10-07 19:07:05 -04:00
ff39edd1ee Deal with unknown flags from ClutterEvent.get_state()
When we get a ClutterModifierType from Clutter, it might contain
bits not in the enumeration. See bug 59771 for a similar problem
with GdkModifierType.

Add a wrapper Shell.get_event_state() around clutter_event_get_state()
to mask these bits out and only return approved bits.

https://bugzilla.gnome.org/show_bug.cgi?id=597735
2009-10-07 17:22:37 -04:00
a81a16801d Fix overview to show on the correct monitor
When tweening the overview in, tween it to appear on the correct
position of the primary monitor, not at (0,0); position
the 'backOver' actor to cover all monitors properly.

Reported by Rui Matos

https://bugzilla.gnome.org/show_bug.cgi?id=597721
2009-10-07 17:14:56 -04:00
4c6f770dea Fix positioning of the Calendar on multihead
Parens were in the wrong place, resulting in the calendar not
being properly centered on the primary monitor.

https://bugzilla.gnome.org/show_bug.cgi?id=597078
2009-10-07 14:52:49 -04:00
c0b01c0210 Match on menu category during application search
Being able to display all applications in a category based on the search
string with a category name is generaly useful.

Prepare all the applications that match a search term based on their
category name up-front.

Remove unused this._appCategories and a call to non-existing
itemInfo.get_categories()
2009-10-07 14:28:49 -04:00
400326e549 Add docs and (transfer none) to st_widget_get_theme_node()
st_widget_get_theme_node() was missing the (transfer none)
GObject Introspection causing crashes. Add that and document the
function.
2009-10-07 14:26:14 -04:00
d7af6d40e3 [AppSwitcher] Use thumbnails instead of a window menu, and other UI changes
https://bugzilla.gnome.org/show_bug.cgi?id=590563
2009-10-07 14:00:43 -04:00
90ebcd32e3 Fix exception when showing menu for not-running app
Need to check windows.length before checking first window.

https://bugzilla.gnome.org/show_bug.cgi?id=597466
2009-10-06 17:19:36 -04:00
53d0581377 Don't process the first click on Activities button for a timeout period after the hot corner was triggered
This avoids opening and closing the overview when the hot corner is triggered
and the activities button is clicked.
2009-10-06 16:58:30 -04:00
439daf828f [AppSwitcher] add a workaround for a gjs problem with GdkModifierType
gdk_display_get_pointer() sometimes returns values for the mask that
aren't part of the GdkModifierType enumeration, which gjs doesn't like
(bug 597292). Work around that by adding a C wrapper that strips out
the extra flags.

https://bugzilla.gnome.org/show_bug.cgi?id=597559
2009-10-06 16:07:37 -04:00
d120d03de9 Workaround being allocated 0x0
Because of a chain of bugs we could end up getting allocated 0x0,
and this would cause the WellGrid allocation code to be unhappy.

See http://bugzilla.openedhand.com/show_bug.cgi?id=1831

Simply treat 0 as "unlimited" i.e. equivalent to -1.

https://bugzilla.gnome.org/show_bug.cgi?id=597586
2009-10-06 15:12:20 -04:00
53fbabe2ca Use GtkIMContext instead of ClutterIMContext
Add StIMText, which is a drop-in replacement for ClutterIMText but
uses GtkIMContext instead of ClutterIMContext.

StIMText doesn't have preedit support (would need ClutterText
changes), so isn't going to be useful for complicated input methods,
but is good enough to get dead keys and similar working.

entry.js: Simple test case of StEntry
gnome-shell.modules: Remove clutter-imcontext module

https://bugzilla.gnome.org/show_bug.cgi?id=597471
2009-10-06 14:48:32 -04:00
4bdd40911f Disconnect from window user time notifications, only sort when mapped
Before we were badly leaking AppIcons by not disconnecting from the
window signal handlers when our actor got destroyed.  This caused
us to repeatedly re-sort the windows for each AppIcon that
had ever been displayed with obvious bad consequences.

Besides simply chaining the signals to the lifetime of the AppIcon
actor, we also only do the sorting if we're mapped.  This decreases
the amount of work to do in the not-mapped case.

https://bugzilla.gnome.org/show_bug.cgi?id=597120
2009-10-06 12:49:43 -04:00
ff4ac0d02e [AppIcon] Improve shell_draw_box_pointer()
Add a new enum type for the pointer direction, rather than abusing
ClutterGravity, and implement the missing directions.

https://bugzilla.gnome.org/show_bug.cgi?id=597498
2009-10-06 09:53:47 -04:00
45dd342cc0 [AppIcon] redo constructor to take a params object, add "size" param
Add a "size" parameter to allow changing the AppIcon size, and then
simplify the constructor by taking an object with parameters like
gobject-introspection constructors do, rather than taking a large
number of miscellaneous arguments.

https://bugzilla.gnome.org/show_bug.cgi?id=597498
2009-10-06 09:53:42 -04:00
e5efecd2bd [AppIcon] compute the sorted window list even if not doing the menu
https://bugzilla.gnome.org/show_bug.cgi?id=597498
2009-10-05 22:35:05 -04:00
3bf88b8988 Run dialog: Make error message translatable
Allow the error message to be translated.

https://bugzilla.gnome.org/show_bug.cgi?id=597422
2009-10-05 23:53:50 +02:00
4ddc1118bb Work around libcroco < 0.6.2 parsing bug for 'rgba'
To work around a problem where libcroco < 0.6.2 can't handle
functions starting with 'r' or 'u', preconvert 'rgba' to 'RGBA'
when parsing stylesheets and then check for rgba()
case-insensitively.

(libcroco is uniformly case-sensitive, though the CSS spec requires
that ASCII should be handled case-insensitively.)

https://bugzilla.gnome.org/show_bug.cgi?id=597054
2009-10-05 16:25:38 -04:00
fd1e7b2a0f Places: Add padding between "Connect to.." and devices section
Add padding after "Connect to.." to make the list look consistent when devices
are connected.

https://bugzilla.gnome.org/show_bug.cgi?id=597423
2009-10-05 22:24:04 +02:00
aed1e67add Check that no new term was introduced before doing a subsearch
We combine search terms with OR, so if a new search term is introduced,
the search results are no longer a subset of the previous search results.
2009-10-05 13:50:58 -04:00
dc99e8ffcd [AppSwitcher] Change separator color
https://bugzilla.gnome.org/show_bug.cgi?id=597362
2009-10-05 13:13:21 -04:00
6a8b50cb00 Don't create multiple copies of the (+) button
Currently we recreate it every time Main.overview.show() is called,
 so destroy it to avoid having multiple copies floating around.

https://bugzilla.gnome.org/show_bug.cgi?id=597309
2009-10-05 18:54:41 +02:00
edb50d5dc7 String formatting: Fix warning
Fix to not trigger a warning like:

JS ERROR: !!!   WARNING: 'anonymous function does not always return a value'

by adding a return ""; at the end of the anonymous function.

https://bugzilla.gnome.org/show_bug.cgi?id=595661
2009-10-05 17:59:33 +02:00
2f6c951997 Avoid doing expensive work when not mapped
For some unknown reason we were connecting to app-added and
app-removed on ShellAppMonitor in the AppDisplay class, which
never made any use of the data.  Simply don't connect to those
signals for now.  In the future we should have AppDisplay
be using the AppIcon class which will more correctly handle
dynamic changes.

In the AppWell, avoid doing the full relayout until we're
actually mapped.

https://bugzilla.gnome.org/show_bug.cgi?id=597169
2009-10-05 11:32:33 -04:00
64cd51667d Add String formatting
Add String formatting by extending the String object with a
format method.

Now we can do stuff like "Text: %s, %d".format(somevar, 5)

This is required for proper translation of some strings.

https://bugzilla.gnome.org/show_bug.cgi?id=595661
2009-10-04 23:37:33 +02:00
caa08f27fa Updated German translation 2009-10-04 18:52:10 +02:00
26015ef16d Select next window from the current app on alt+tab
This slightly changes the behaviour of the alt+tab window, this way:
when using alt-tab on a workspace that contains two or more windows from
the same window, the application selected when hitting alt+tab is the
currently selected application, but the highlighted window is the next
one.

Intended goal is to make it easier to cycle around windows of the same
application while not having to cycle through all the applications first.

https://bugzilla.gnome.org/show_bug.cgi?id=590563
2009-10-04 12:45:23 -04:00
4798ad5107 Consider workspace when sorting windows in menu.
By sorting the windows in the current workspace first, we ensure that
when using alt+tab to switch windows, we will pick a window on the
active workspace as the default focused window for the application if
the application has at least one window on the current workspace (that
is, if it is on the left of the app switcher separator).

This makes the behaviour of alt+tab more predictable for the user, as
an user will expect alt+tab to switch to the window he/she can see right
now rather than the one on the workspace he just left (presumably to do
something else on the workspace he's currently on).

https://bugzilla.gnome.org/show_bug.cgi?id=590563
2009-10-04 12:45:19 -04:00
9b05304c2d Use better fitting color for non ARGB tray icons
Currently we use 0xefefefff as a background color for non ARGB tray icons,
which looks out of place (i.e does not fit the panel's background gradient).

Change it to 0x0b0b0bff to fix that.

https://bugzilla.gnome.org/show_bug.cgi?id=597148
2009-10-04 10:02:37 +02:00
795feca393 Updated Arabic translation 2009-10-03 19:20:25 +02:00
31663dcd83 Updated Norwegian bokmål translation. 2009-10-03 10:48:19 +02:00
2f2df61093 Cache applications for a menu
Rather than recomputing this each time someone asks, have a
cache of the apps for a given menu.

https://bugzilla.gnome.org/show_bug.cgi?id=597167
2009-10-02 19:30:42 -04:00
3b8d53060d Immediately pop up menu on right click
We were actually showing a menu on button 3 before, but only
through a chain of coincidences.  This patch explicitly supports
it and makes sure we show it immediately rather than after
a timeout. Pass the activating button in so that we only pop
down on that button.

https://bugzilla.gnome.org/show_bug.cgi?id=596371
2009-10-02 19:28:25 -04:00
c5ce405859 Ignore releases of buttons other than the activating button
Before we hardcoded popdowns to only button 1 before. But we need
to actually pop down on the release of the activating button.
(Once the button is released, if the we don't pop-down the menu,
then subsequently we let the user use any button.)

https://bugzilla.gnome.org/show_bug.cgi?id=596371
2009-10-02 19:28:25 -04:00
b3a5fc72fb Add Add/Remove from favorites menu, unify lists more
Also have inactive applications pop up a menu.

Add/Remove from favorites is now in the menu.

Concatenate the favorites/not-favorites instead of having a gap only
if you happened to have a not-divisible-by-4 number of favorites.

https://bugzilla.gnome.org/show_bug.cgi?id=596371
2009-10-02 19:28:25 -04:00
8a2cc11cc0 Fix rundialog not closing for internal commands
Signed-off-by: Adel Gadllah <adel.gadllah@gmail.com>

https://bugzilla.gnome.org/show_bug.cgi?id=597076
2009-10-02 14:18:51 -04:00
cbb3a3aec8 Fix make dist by including all the private files and the tests in the tarball 2009-10-02 17:02:25 +01:00
e382da9708 [AppSwitcher] Update colors/border
Switch from Big.Box to St.Bin and update styling per latest spec

https://bugzilla.gnome.org/show_bug.cgi?id=590563
2009-10-02 09:04:04 -04:00
ab1fbbde92 add a missing (optional) ";" 2009-10-02 08:49:22 -04:00
5f5266ca60 [AppSwitcher] Display a separator between apps on this workspace and others.
This makes a visible distinction between the apps that only have minimized
windows on the current workspace and the ones that have no window on the
workspace.

https://bugzilla.gnome.org/show_bug.cgi?id=590563
2009-10-02 14:17:00 +02:00
68e8b14b8b [AppSwitcher] Use GenericContainer instead of BigBox.
This allows defining some custom policy for size allocation.
Currently, the minimum width is always used, but it can be tweaked
afterwards when a sizing policy has been defined.

https://bugzilla.gnome.org/show_bug.cgi?id=590563
2009-10-02 14:15:01 +02:00
abdd8b330c Don't show all sections each time the search is updated
We shouldn't show all sections each time the search is updated because that
breaks viewing search results for a single section and flickers headers for
sections with no results.
2009-10-01 18:21:27 -04:00
eb8176deeb Fix regressions in the item displays
Fix displaying documents in the document browse and refreshing the selection
when the results have changed.

Make sure we are passing the appropriate flag to _redisplay() in GenericDisplay.

Make sure we set this._appsStale to true if there was a change in the
applications set.

Don't call _refreshCache() from the AppDisplay constructor.

Don't short-circuit the call to _refreshCache() from _redisplay() on initial load.

Rename _redisplayFull() to recreateDisplayItems() and remove adding an
actor to the actual result list in _addDisplayItem() because we redo adding the
actors to the list in _redisplayReordering() anyway to ensure that we add
them in the right order.

Based on a patch from Colin Walters.
2009-10-01 17:41:17 -04:00
956f89f377 Add dep of clutter on gir-repository to moduleset
Clutter requires Pango.gir. Until Pango is universally available
in a version that builds its own GIR, Pango.gir is built as part
of gir-repository.

(Found by Adel Gadllah)
2009-10-01 17:34:28 -04:00
1c69380923 [AppSwitcher] Put apps with no window on current workspace at the end.
Following the idea expressed in bug 590563 by mccann ("Minimized or
hidden applications should appear at the end of the list"), we should
also put applications that have no visible window in the active
workspace at the end of the alt-tab window list, after apps which have
minimized windows in the active workspace.
2009-10-01 23:25:15 +02:00
d2bc7b200e Update Catalan translation. 2009-10-01 23:23:54 +02:00
afb3b1e718 Fixes for Calendar widget
Miscellaneous fixes from review:

- Distribute calendar.js and the interactive test
- Make the pointless protection against leap seconds actually work
  by starting in the middle of the day so that forward/back always
  move a day.
- Use a variable instead of an inline '8' to know where to start
  when removing old day actors.
- Remove a stray comment from the test

https://bugzilla.gnome.org/show_bug.cgi?id=596432
2009-10-01 16:48:24 -04:00
061a2cfbfb Add scroll-wheel support to the calendar
Make the calendar reactive and handle scroll events to change the month.
(GtkCalendar and hence the old gnome-panel calendar supported this and
it is apparently a handy way to flip through months.)

The padding is moved from the CalenderPopup to the Calendar so that the
scroll region extends all the way to the edge of the popup.

https://bugzilla.gnome.org/show_bug.cgi?id=596432
2009-10-01 16:48:24 -04:00
243824ab80 Replace "round(x)" with "(int)(0.5 + x)"
round() is a C99 addition, so causes portability problems:
different C library versions require different #defines to
enable it. So simply avoid using it.
2009-10-01 22:39:04 +02:00
90ddad7ba1 Make Connect string translatable
Signed-off-by: Adel Gadllah <adel.gadllah@gmail.com>

https://bugzilla.gnome.org/show_bug.cgi?id=597038
2009-10-01 16:11:33 -04:00
20b29ff48f Sync clone stacking order with MetaStackTracker
Previously, we initialized actor stacking order from the return
value of global.get_windows() once, which is defined to be in
stack order.  However it was not updated later.  Furthermore,
the way stackAbove was called from onAnimationComplete in
WindowClone was highly dubious, since there are lots of animations
which apply to the clones, and we want the stacking to be right
all of the time, not when some animation completes.

Fix this by connecting to 'restacked' on the screen and syncing
the clones.

I also snuck in another bugfix here; we weren't disconnecting
from the 'showing' signal handler, which had various bad
consequences.

https://bugzilla.gnome.org/show_bug.cgi?id=596263
2009-10-01 15:59:05 -04:00
96f4d318c5 Add libcroco to gnome-shell-build-setup.sh
Check for the new build dependency "libcroco" when running on any
of the distros supported by the script.
2009-10-01 21:57:53 +02:00
6f7da264ba Support LinuxMint in the build script
LinuxMint is based on Ubuntu, so can be handled with the
same package list as Debian and Ubuntu.

https://bugzilla.gnome.org/show_bug.cgi?id=596447
2009-10-01 15:40:04 -04:00
640e45c12a Make gnome-shell depend on clutter-imcontext
The gnome-shell module requires clutter-imcontext to build.
2009-10-01 15:05:18 -04:00
04e28cd7c4 Add a calendar pop-down to the clock
js/ui/calendar.js: Generic calendar widget
tests/interactive/calendar.js: Basic test of the calendar

js/ui/panel.js: Add a pop-down from the clock that shows a
  calendar widget. The pop-down is not menu-like to allow the user to
  interact with an application while looking at the calendar.
gnome-shell.css: Add theming for calendar, calendar popup, and for
  buttons on the panel

https://bugzilla.gnome.org/show_bug.cgi?id=596432
2009-10-01 15:05:11 -04:00
2cc41c6726 StButton: Fix property enumeration names
Property enumeration names should correspond exactly to the property names;
in particular the ACTIVE vs :checked disparity was confusing reading the
code.

http://bugzilla.moblin.org/show_bug.cgi?id=6504
2009-10-01 14:46:33 -04:00
03a45b665c testcommon.css: Don't theme all buttons
StButton is used for many things - scrollbar steppers, etc. Theming
all buttons to look like push-buttons breaks that. So in testcommon.css
just theme a .push-button class to look vaguely button-like.

https://bugzilla.gnome.org/show_bug.cgi?id=596432
2009-10-01 14:46:33 -04:00
5a42179a96 Port StTable to StThemeNode
Convert the StTable code from StStylable to StThemeNode. The
:row-spacing and :col-spacing GObject properties are converted
into spacing-rows and spacing-columns style properties.

A new interactive test is added for StTable.

https://bugzilla.gnome.org/show_bug.cgi?id=596811
2009-10-01 14:46:33 -04:00
af3ec56ca1 Handle adding children to StTable from Javascript
Remove the StTable specific methods to add actors:

 st_table_add_actor()
 st_table_add_actor_with_properties()

Since they shadow the generic ClutterContainer add_actor() method,
and patch in our add() convenience function as we do for
StBoxLayout.

https://bugzilla.gnome.org/show_bug.cgi?id=596811
2009-10-01 14:46:33 -04:00
289b19aa31 Import MxTable as StTable
Import table code from Mx library

https://bugzilla.gnome.org/show_bug.cgi?id=596811
2009-10-01 14:46:33 -04:00
91eb613d69 Turn StBoxLayout:spacing into a style property
Remove the StBoxLayout:spacing GObject property, and instead make
BoxLayout look up the spacing from the CSS style. This makes it
consistent with padding and will allow the use of units. (The
removal of the GObject property entirely instead of making it an
override is consistent with how we handle color, font, padding, etc.)

https://bugzilla.gnome.org/show_bug.cgi?id=596803
2009-10-01 14:46:32 -04:00
1c7c53d19f lookingGlass: Get font from GConf
Instead of using "Monospace", pick the users configured monospace font
name up from GConf. (This is a nice touch, but is more done here to
demonstrate that we can do it rather than for any great utility.)

https://bugzilla.gnome.org/show_bug.cgi?id=591245
2009-10-01 14:46:32 -04:00
d4304495c6 Port LookingGlass console to ST widgets
* Style aspects like colors and fonts are moved into gnome-shell.css.
* Scrolling is adding using StScrollView.

Based on a patch from Colin Walters
https://bugzilla.gnome.org/show_bug.cgi?id=591245
2009-10-01 14:46:32 -04:00
b77b205d37 Add clutter-text properties to StEntry and StLabel
Add clutter-text properties to allow getting access to the underlying
ClutterText actor. This corresponds to the get_clutter_text() methods.

The PROP_LABEL and PROP_ENTRY enum values are renamed to PROP_TEXT to
match the names of the properties that they correspond to, and the
properties of StEntry are reordered into alphabetical order.

Based on a patch from Colin Walters
https://bugzilla.gnome.org/show_bug.cgi?id=591245
http://bugzilla.moblin.org/show_bug.cgi?id=6313
2009-10-01 14:41:20 -04:00
a15205e6c4 Fix interaction of borders/background and scrolling
StBoxLayout: Make consistent that the area scrolled and clipped
to is the content area (excluding borders and padding.) Translate
back appropriately when chaining up so that the parent background
is drawn at the right place and picking on the box (if it's reactive)
picks at the right place on the screen.

clip-to-allocation is removed from StScrollView since it's just
not right - if the child has any non-moving elements, like headers or
borders, it will need to set a narrower clip. And even if the entire
child scrolls, we want to clip to an arrow that excludes the scrollbars.

https://bugzilla.gnome.org/show_bug.cgi?id=595997
2009-10-01 14:41:19 -04:00
28dbf7a06e Allocate children as wide as the scrolled area
When we are scrolling a vertical box horizontally , children should be
allocated horizontally as wide as the full horizontal scrolled area,
not just to the size of the "viewport". Similarly for a horizontal box.

http://bugzilla.moblin.org/show_bug.cgi?id=6312
https://bugzilla.gnome.org/show_bug.cgi?id=595996
2009-10-01 14:41:19 -04:00
1ec8e9eb6b Allow StBoxLayout to shrink down to its minimum size
When a StBoxLayout is allocated a size less than its natural size,
think "shrink" needs to be divided among the children that have
a smaller minimum size than natural size.

This is done by preferentially shrinking the children that are most
expanded from their minimum size and then increasing that set of
children until we've found enough total shrink.

A new method is used of allocating children at integral sizes - instead
of rounding the per-child extra amount to an integer (which causes
cumulative round-off errors), compute the position as we go along in
floats and round individually for each child widget.

Extend the box-layout test to include of a test of a box being set
to various widths, starting quite narrow.

http://bugzilla.moblin.org/show_bug.cgi?id=6311
https://bugzilla.gnome.org/show_bug.cgi?id=595995
2009-10-01 14:41:19 -04:00
fa09f7a6da Don't count not-visible children among expand children
When counting how many children we should divide extra space among,
don't count not-visible children.

http://bugzilla.moblin.org/show_bug.cgi?id=6310
https://bugzilla.gnome.org/show_bug.cgi?id=595995
2009-10-01 14:41:19 -04:00
d67e54d3ee Don't use the default stage when setting up adjustments
If the actor isn't in a stage, then setting up the adjustment
based on the actor's size (which we can't compute) and the
size of the default stage (which isn't relevant), doesn't make
sense. Just use arbitrary default values.

The adjustments will be updated to reasonable values when first
the box is first allocated.

It's not entirely clear to me why we ever want to compute the
adjustment settings this way; perhaps we should always use
default values.

http://bugzilla.moblin.org/show_bug.cgi?id=6307
https://bugzilla.gnome.org/show_bug.cgi?id=595996
2009-10-01 14:41:19 -04:00
d263c12e2e Match CSS for background extents
The CSS specification says that the background extends to the
edge of the border (settable in CSS3 with border-clip), make
BigRectangle match this by computing an "effective border color"
as 'border OVER background'.

(If we don't want this behavior - e.g., to be able to use the
transparent borders as margins, then alternatively transparent
border handling would have to be fixed in st-widget.c, since
prior to this transparent and translucent borders were handled
differently.)

https://bugzilla.gnome.org/show_bug.cgi?id=595993
2009-10-01 14:41:19 -04:00
4d55ccff39 Rename StThemeImage to StBorderImage
The current CSS3 border-image is close to a superset of what we were
doing for -hippo-background-image. Woot! rename StThemeImage to
StBorderImage and change parsing to look for:

 border-image: <url> <number>...

Rather than

 -st-background-image: <url> <length>...

percentanges for the border sizes are not currently supported, neither
are the keywords for handling of the middle part. We always do 'stretch'
for now.

https://bugzilla.gnome.org/show_bug.cgi?id=595990
2009-10-01 14:41:19 -04:00
2a0adc0fc8 Add support for colored borders
Use BigRectangle to draw the border and background if there's
a border width or border radius and no border image. (Only
uniform borders are supported for now with some deviations
from the CSS model noted in the comments.)

The background color and image parameters are removed from
StWidget's draw_background() method since they were not used
for StButton (the only current user) and the encapsulation
break that they presented caused some minor problems.

Add a test case for borders, and also use borders to style
the buttons in the 'inline-style' test case.

https://bugzilla.gnome.org/show_bug.cgi?id=595993
2009-10-01 14:41:19 -04:00
076e902b2c Centralize computations of border and padding into StThemeNode
Rather than repeating the computation of borders in many different
widget subclasses, add helper functions:

 st_theme_node_adjust_for_height()
 st_theme_node_adjust_preferred_width()
 st_theme_node_adjust_for_width()
 st_theme_node_adjust_preferred_height()
 st_theme_node_get_content_box()

That are used in get_preferred_width()/get_preferred_height() and
allocate() methods to consistently apply the necessary adjustments.
This allows removing the StPadding type.

Queueing a relayout when the borders/padding change is moved from
st_widget_real_style_changed() to the invoking code to allow access
to the old StThemeNode for comparison. (Should this be added as
a parameter to the signal?)

Borders are included in the geometry adjustments, but borders
are not yet drawn.

https://bugzilla.gnome.org/show_bug.cgi?id=595993
2009-10-01 14:41:19 -04:00
8c72623da3 StThemeNode: Add border-radius support
Add support for parsing and caching the border-radius property.
Different radii for the 4 corners are supported; elliptical corners
are not supported.

https://bugzilla.gnome.org/show_bug.cgi?id=595993
2009-10-01 14:41:18 -04:00
1fd25573e5 Fix problems with 4-sided padding: specifiers
The test for identifying such a specifier was wrong, and the last
value was assigned to the wrong sides.

https://bugzilla.gnome.org/show_bug.cgi?id=595990
2009-10-01 14:41:18 -04:00
6527dbc8b7 Add support for inline styles
Add support for passing an inline-style string when creating a
StThemeNode.

Hook this up to a new 'style' property of StWidget.

Add a test case that demonstrates using this to update font sizes
on the fly.

https://bugzilla.gnome.org/show_bug.cgi?id=595991
2009-10-01 14:41:18 -04:00
3c646ec516 run-test.sh: support running tests under gdb
As with the 'gnome-shell' -g/--debug can be passed to run under
the debugger.

https://bugzilla.gnome.org/show_bug.cgi?id=595987
2009-10-01 14:41:18 -04:00
a9fd350396 Port our imported parts of Mx to ShellTheme
ShellTheme replaces both StStyle and ccss_stylesheet_t.

The interface StStylable is replaced by usage of ShellThemeNode.
A concrete node class allows some significant optimizations of property
inheritance that would have been much more difficult to achieve with
the highly abstract pair of StStylable and ccss_node_t.

Some operations that were previously on StStylable (like the
::style-changed signal) are directly on NtkWidget.

Custom properties are no longer registered as param-specs; instead you
call directly into shell theme node to look up a length or color:

shell_theme_node_get_length (theme_node, "border-spacing", FALSE, &spacing);

The dependency on libccss is dropped, while preserving all existing
functionality and adding proper parsing and inheritance of font properties
and proper inheritance for the 'color' property.

Some more javascript tests for CSS functionality are added; workarounds for
a CSS bug where *.some-class was needed instead of .some-class are removed.

https://bugzilla.gnome.org/show_bug.cgi?id=595990
2009-10-01 14:41:18 -04:00
e91e8e993d Add emacs mode-lines to ST sources
To each .c and .h file, add:

 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */

'gnu' is the default anyways for Emacs, but indent-tabs-mode is not,
so this sets things up to correspond to the policy of no-tabs.

http://bugzilla.moblin.org/show_bug.cgi?id=6467
2009-10-01 14:41:18 -04:00
276d9a9302 Import stylesheet code from hippo-canvas
Import:

  HippoCanvasTheme      => StTheme
  HippoCanvasThemeImage => StThemeImage
  HippoCanvasStyle      => StThemeNode

StThemeContext is a new class managing the theme for a stage and
global properties like resolution.

test-theme.c is a newly written test program to do verification of the
style matching and property handling rules.

Various changes are made in the import:

 - Comprehensive reindentation
 - guint32 pixels replaced with ClutterColor
 - General pseudo-class support added
 - Old-fashioned (non-bordered) background image support added, though
   with no support for repeat, etc.
 - Bug fixes for problems revealed by test program

https://bugzilla.gnome.org/show_bug.cgi?id=595990
2009-10-01 14:41:18 -04:00
6b95864076 Fix installation and distribution of stylesheet data
Install and distribute gnome-shell.css and theme images. They are moved
down from $datadir to $datadir/theme to avoid a weirdness where we have
images in $datadir and then also in $datadir/images.

(Also moved in the source tree to avoid adding another difference between
installed and uninstalled operation.)

https://bugzilla.gnome.org/show_bug.cgi?id=595989
2009-10-01 14:41:18 -04:00
d4c577a299 Remove stale C files
Remove several stale C files that we are no longer using; now
that we have a distcheck hook to catch non-distributed files, these
would otherwise prevent distchecking.

https://bugzilla.gnome.org/show_bug.cgi?id=595988
2009-10-01 14:41:17 -04:00
b90fc1e194 Extend distcheck for files in Git to all files
Instead of just checking that we distribute all Javascript files, check
that we distribute everything that is in Git.

The toplevel Makefile.am has a variable DIST_EXCLUDE that lists patterns
of files that we actually don't want to distribute.

https://bugzilla.gnome.org/show_bug.cgi?id=595988
2009-10-01 14:41:17 -04:00
55497899dd Add some structure for interactive tests of UI components
js/ui/environment.js: Split out initial UI setup (Tweener initialization,
  ClutterContainer monkey-patching) into a separate file we can import from tests.

tests/: Directory for various types of tests
tests/run-test.sh: Shell script that to run tests with an appropriate
  environment set up.

tests/testcommon/: Common modules and data for tests
tests/interactive/: Interactive tests

tests/interactive/box-layout.js: A sample test of StLayout

https://bugzilla.gnome.org/show_bug.cgi?id=595987
2009-10-01 14:41:17 -04:00
e37790fdf9 Monkey-patch in ClutterContainer methods for StBoxLayout
Setting options for children added to StBoxLayout is not convenient
since we are missing the varargs methods of clutter_container.

Patch in:

 child_set() - set properties of a child
 add() - add a child and set properties (this is different from
         clutter_container_add()! I think the deviation is
         with avoiding the awkward name add_with_properties()
         which is what might be expected. ClutterContainer
         currently doesn't have a method like this at all.)

The code is written to allow patching into multiple ClutterContainer
classes but for now only StBoxLayout is patched, since it's the only
container we are using where we need to set options as properties.

https://bugzilla.gnome.org/show_bug.cgi?id=595419
2009-10-01 14:41:17 -04:00
83402957bb Add GObject Introspection annotations
Add GObject Introspection annotations to methods where needed, in
particular adding (transfer none) to return values that don't transfer
ownership.

st_texture_cache_get_actor() and st_texture_cache_get_texture()
are annotated as (transfer none) since they return a newly
created *floating* texture.

https://bugzilla.gnome.org/show_bug.cgi?id=591245
2009-10-01 14:41:17 -04:00
58325fca76 Import MxEntry, MxLabel, MxClipboard
For now this commit introduces an external dependency on clutter-imcontext.

https://bugzilla.gnome.org/show_bug.cgi?id=591245
2009-10-01 14:41:17 -04:00
8b6962f3bf Import MxBoxLayout, MxBoxLayoutChild
https://bugzilla.gnome.org/show_bug.cgi?id=591245
2009-10-01 14:41:17 -04:00
c1f91def74 Add css for scrolling
Port bits of Mx's default.css into gnome-shell.css, and use some
hand-rolled .pngs with colors from
http://live.gnome.org/GnomeShell/DesignerPlayground/ExpandedViewMockups

https://bugzilla.gnome.org/show_bug.cgi?id=591245
2009-10-01 14:41:17 -04:00
529f74c0e5 Load gnome-shell.css at startup
https://bugzilla.gnome.org/show_bug.cgi?id=591245
2009-10-01 14:41:17 -04:00
459a3b18f2 Add a "datadir" property
Will be used to load stylesheets from main.js.

https://bugzilla.gnome.org/show_bug.cgi?id=591245
2009-10-01 14:41:16 -04:00
ac2be7f0d1 Remove hardcoded '28' from StScrollView
https://bugzilla.gnome.org/show_bug.cgi?id=591245
2009-10-01 14:41:16 -04:00
f6b80d5ed4 Import MxScrollView and dependencies
https://bugzilla.gnome.org/show_bug.cgi?id=591245
2009-10-01 14:41:16 -04:00
d291e568fd Import Mx core as ST
Import the core MxWidget/MxBin and their dependencies; we use the
namespace "St" (Shell Toolkit) because it is the same length as Mx
so enabling easy sharing of code, but makes it clear that this is
a friendly fork and not a literal import.

Based on a patch by Colin Walters <walters@verbum.org>

https://bugzilla.gnome.org/show_bug.cgi?id=591245
2009-10-01 14:41:16 -04:00
2b78d5bd5d Improve support for multihead setups
Fix panel, app switcher, and looking glass to limit themselves to the
primary monitor, and run dialog to limit itself to the monitor
containing the currently-focused window.

The overview is also limited to the primary monitor now (with the
other monitors being blacked out), although the workspaces within the
overview are shaped like the full "screen" (the bounding box of all
monitors). To be fixed later.

https://bugzilla.gnome.org/show_bug.cgi?id=593060
2009-10-01 13:46:03 -04:00
c9d9846759 Add Desktop in Places
Updated by Colin Walters <walters@verbum.org to monitor gconf
and use g_get_user_special_dir.

Signed-off-by: Adel Gadllah <adel.gadllah@gmail.com>
Signed-off-by: Colin Walters <walters@verbum.org>

https://bugzilla.gnome.org/show_bug.cgi?id=596933
2009-10-01 11:53:30 -04:00
6baafaa530 [places] Fix double spacing between actions and devices
https://bugzilla.gnome.org/show_bug.cgi?id=596991
2009-10-01 10:43:57 -04:00
c2af05f753 [AppSwitcher] Allow use of arrow keys in the popup
https://bugzilla.gnome.org/show_bug.cgi?id=590563
2009-10-01 10:35:40 -04:00
97df305a6d [AppSwitcher] Drop the line wrapping code.
Also rename _grid into _appsBox as grid is not an appropriate word
anymore for what this box is.

https://bugzilla.gnome.org/show_bug.cgi?id=590563
2009-09-30 23:32:36 +02:00
fee385ba35 [AppSwitcher] Do not show the glow for icons in alt+tab dialog.
Unlike icons in the application well, do not show the glow used to
indicate running apps.  It is somewhat redundant here.  These are all
running apps and it is fairly clear from the window list if there are
multiple instances available, according to mccann.

https://bugzilla.gnome.org/show_bug.cgi?id=590563
2009-09-29 18:43:35 +02:00
52 changed files with 3086 additions and 1034 deletions

View File

@ -1,4 +1,4 @@
AC_INIT(gnome-shell, 2.27.3)
AC_INIT(gnome-shell, 2.28.0)
AC_CONFIG_AUX_DIR(config)
@ -57,7 +57,7 @@ PKG_CHECK_MODULES(MUTTER_PLUGIN, gio-unix-2.0 gtk+-2.0 dbus-glib-1 mutter-plugin
gnome-desktop-2.0 >= 2.26 libstartup-notification-1.0
gobject-introspection-1.0 >= 0.6.5)
PKG_CHECK_MODULES(TIDY, clutter-1.0)
PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-2.0 clutter-imcontext-0.1 libcroco-0.6)
PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-2.0 libcroco-0.6)
PKG_CHECK_MODULES(BIG, clutter-1.0 gtk+-2.0 librsvg-2.0)
PKG_CHECK_MODULES(GDMUSER, dbus-glib-1 gtk+-2.0)
PKG_CHECK_MODULES(TRAY, gtk+-2.0)

View File

@ -61,6 +61,19 @@ StScrollBar StButton#vhandle:hover
border-image: url("scroll-vhandle.png") 5;
}
/* Panel */
.panel-button {
padding: 4px 12px 3px;
border-radius: 5px;
font: 16px sans-serif;
color: white;
}
.panel-button:active, .panel-button:checked {
background-color: #314a6c;
}
/* LookingGlass */
#LookingGlassDialog
@ -95,3 +108,69 @@ StScrollBar StButton#vhandle:hover
padding: 4px;
spacing: 4px;
}
/* Calendar popup */
#calendarPopup {
border-radius: 5px;
background: rgba(0,0,0,0.9);
border: 1px solid rgba(128,128,128,0.45);
color: white;
}
#calendarPopup .calendar {
padding: 10px;
}
.calendar {
spacing-rows: 5px;
spacing-columns: 3px;
}
.calendar-change-month {
padding: 2px;
}
.calendar-change-month:hover {
background: #314a6c;
border-radius: 5px;
}
.calendar-change-month:active {
background: #213050;
border-radius: 5px;
}
.calendar-day {
padding: 1px 2px;
}
.calendar-today {
font-weight: bold;
background: #ffffff;
color: black;
border-radius: 5px;
}
.calendar-other-month-day {
color: #cccccc;
}
/* App Switcher */
.switcher-list {
background: rgba(0,0,0,0.8);
border: 1px solid rgba(128,128,128,0.40);
border-radius: 8px;
padding: 18px;
}
.switcher-list .item-box {
padding: 8px;
border-radius: 4px;
}
.switcher-list .selected-item-box {
padding: 8px;
border-radius: 4px;
background: rgba(255,255,255,0.33);
}

View File

@ -1,4 +1,5 @@
jsmiscdir = $(pkgdatadir)/js/misc
dist_jsmisc_DATA = \
docInfo.js
docInfo.js \
format.js

44
js/misc/format.js Normal file
View File

@ -0,0 +1,44 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* This function is intended to extend the String object and provide
* an String.format API for string formatting.
* It has to be set up using String.prototype.format = Format.format;
* Usage:
* "somestring %s %d".format('hello', 5);
* It supports %s, %d and %f, for %f it also support precisions like
* "%.2f".format(1.526)
*/
function format() {
let str = this;
let i = 0;
let args = arguments;
return str.replace(/%(?:\.([0-9]+))?(.)/g, function (str, precisionGroup, genericGroup) {
if (precisionGroup != '' && genericGroup != 'f')
throw new Error("Precision can only be specified for 'f'");
switch (genericGroup) {
case '%':
return '%';
break;
case 's':
return args[i++].toString();
break;
case 'd':
return parseInt(args[i++]);
break;
case 'f':
if (precisionGroup == '')
return parseFloat(args[i++]);
else
return parseFloat(args[i++]).toFixed(parseInt(precisionGroup));
break;
default:
throw new Error('Unsupported conversion character %' + genericGroup);
}
return ""; // Suppress warning
});
}

View File

@ -5,6 +5,7 @@ dist_jsui_DATA = \
appDisplay.js \
appIcon.js \
button.js \
calendar.js \
chrome.js \
dash.js \
dnd.js \

View File

@ -4,84 +4,61 @@ const Big = imports.gi.Big;
const Clutter = imports.gi.Clutter;
const Gdk = imports.gi.Gdk;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta;
const Pango = imports.gi.Pango;
const Shell = imports.gi.Shell;
const Signals = imports.signals;
const St = imports.gi.St;
const AppIcon = imports.ui.appIcon;
const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main;
const POPUP_BG_COLOR = new Clutter.Color();
POPUP_BG_COLOR.from_pixel(0x00000080);
const POPUP_APPICON_BORDER_COLOR = new Clutter.Color();
POPUP_APPICON_BORDER_COLOR.from_pixel(0xffffffff);
const POPUP_ARROW_COLOR = new Clutter.Color();
POPUP_ARROW_COLOR.from_pixel(0xffffffff);
const TRANSPARENT_COLOR = new Clutter.Color();
TRANSPARENT_COLOR.from_pixel(0x00000000);
const POPUP_SEPARATOR_COLOR = new Clutter.Color();
POPUP_SEPARATOR_COLOR.from_pixel(0x80808066);
const POPUP_GRID_SPACING = 8;
const POPUP_ICON_SIZE = 48;
const POPUP_NUM_COLUMNS = 5;
const POPUP_APPICON_SIZE = 96;
const POPUP_LIST_SPACING = 8;
const POPUP_POINTER_SELECTION_THRESHOLD = 3;
const THUMBNAIL_SIZE = 256;
const THUMBNAIL_POPUP_TIME = 1000; // milliseconds
const HOVER_TIME = 500; // milliseconds
function mod(a, b) {
return (a + b) % b;
}
function AltTabPopup() {
this._init();
}
AltTabPopup.prototype = {
_init : function() {
this.actor = new Big.Box({ background_color : POPUP_BG_COLOR,
corner_radius: POPUP_GRID_SPACING,
padding: POPUP_GRID_SPACING,
spacing: POPUP_GRID_SPACING,
orientation: Big.BoxOrientation.VERTICAL,
reactive: true });
this.actor = new Clutter.Group({ reactive: true,
x: 0,
y: 0,
width: global.screen_width,
height: global.screen_height });
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
// Icon grid. TODO: Investigate Nbtk.Grid once that lands. Currently
// just implemented using a chain of Big.Box.
this._grid = new Big.Box({ spacing: POPUP_GRID_SPACING,
orientation: Big.BoxOrientation.VERTICAL });
let gcenterbox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
x_align: Big.BoxAlignment.CENTER });
gcenterbox.append(this._grid, Big.BoxPackFlags.NONE);
this.actor.append(gcenterbox, Big.BoxPackFlags.NONE);
this._icons = [];
this._currentWindows = [];
this._haveModal = false;
this._selected = 0;
this._highlightedWindow = null;
this._toplevels = global.window_group.get_children();
this._currentApp = 0;
this._currentWindows = [];
this._thumbnailTimeoutId = 0;
global.stage.add_actor(this.actor);
},
_addIcon : function(appIcon) {
appIcon.connect('activate', Lang.bind(this, this._appClicked));
appIcon.connect('activate-window', Lang.bind(this, this._windowClicked));
appIcon.connect('highlight-window', Lang.bind(this, this._windowHovered));
appIcon.connect('menu-popped-up', Lang.bind(this, this._menuPoppedUp));
appIcon.connect('menu-popped-down', Lang.bind(this, this._menuPoppedDown));
appIcon.actor.connect('enter-event', Lang.bind(this, this._iconEntered));
// FIXME?
appIcon.actor.border = 2;
appIcon.highlight_border_color = POPUP_APPICON_BORDER_COLOR;
this._icons.push(appIcon);
this._currentWindows.push(appIcon.windows[0]);
// Add it to the grid
if (!this._gridRow || this._gridRow.get_children().length == POPUP_NUM_COLUMNS) {
this._gridRow = new Big.Box({ spacing: POPUP_GRID_SPACING,
orientation: Big.BoxOrientation.HORIZONTAL });
this._grid.append(this._gridRow, Big.BoxPackFlags.NONE);
}
this._gridRow.append(appIcon.actor, Big.BoxPackFlags.NONE);
},
show : function(initialSelection) {
show : function(backward) {
let appMonitor = Shell.AppMonitor.get_default();
let apps = appMonitor.get_running_apps ("");
@ -99,32 +76,52 @@ AltTabPopup.prototype = {
this._mouseActive = false;
this._mouseMovement = 0;
// Contruct the AppIcons, sort by time, add to the popup
let icons = [];
for (let i = 0; i < apps.length; i++)
icons.push(new AppIcon.AppIcon(apps[i], AppIcon.MenuType.BELOW));
icons.sort(Lang.bind(this, this._sortAppIcon));
for (let i = 0; i < icons.length; i++)
this._addIcon(icons[i]);
this._appSwitcher = new AppSwitcher(apps);
this.actor.add_actor(this._appSwitcher.actor);
this._appSwitcher.connect('item-activated', Lang.bind(this, this._appActivated));
this._appSwitcher.connect('item-hovered', Lang.bind(this, this._appHovered));
// Need to specify explicit width and height because the
// window_group may not actually cover the whole screen
this._lightbox = new Lightbox.Lightbox(global.window_group,
global.screen_width,
global.screen_height);
let primary = global.get_primary_monitor();
this._appSwitcher.actor.x = primary.x + Math.floor((primary.width - this._appSwitcher.actor.width) / 2);
this._appSwitcher.actor.y = primary.y + Math.floor((primary.height - this._appSwitcher.actor.height) / 2);
this.actor.show_all();
this.actor.x = Math.floor((global.screen_width - this.actor.width) / 2);
this.actor.y = Math.floor((global.screen_height - this.actor.height) / 2);
this._appIcons = this._appSwitcher.icons;
this._updateSelection(initialSelection);
// _currentWindows give the index of the selected window for
// each app; they all start at 0.
this._currentWindows = this._appIcons.map(function (app) { return 0; });
// Make the initial selection
if (this._appIcons.length == 1) {
if (!backward && this._appIcons[0].windows.length > 1) {
// For compatibility with the multi-app case below
this._select(0, 1);
} else
this._select(0);
} else if (backward) {
this._select(this._appIcons.length - 1);
} else {
if (this._appIcons[0].windows.length > 1) {
let curAppNextWindow = this._appIcons[0].windows[1];
let nextAppWindow = this._appIcons[1].windows[0];
// If the next window of the current app is more-recently-used
// than the first window of the next app, then select it.
if (curAppNextWindow.get_workspace() == global.screen.get_active_workspace() &&
curAppNextWindow.get_user_time() > nextAppWindow.get_user_time())
this._select(0, 1);
else
this._select(1);
} else
this._select(1);
}
// There's a race condition; if the user released Alt before
// we got the grab, then we won't be notified. (See
// https://bugzilla.gnome.org/show_bug.cgi?id=596695 for
// details.) So we check now. (Have to do this after calling
// _updateSelection.)
let [screen, x, y, mods] = Gdk.Display.get_default().get_pointer();
// details.) So we check now. (Have to do this after updating
// selection.)
let mods = global.get_modifier_keys();
if (!(mods & Gdk.ModifierType.MOD1_MASK)) {
this._finish();
return false;
@ -133,6 +130,502 @@ AltTabPopup.prototype = {
return true;
},
_nextApp : function() {
return mod(this._currentApp + 1, this._appIcons.length);
},
_previousApp : function() {
return mod(this._currentApp - 1, this._appIcons.length);
},
_nextWindow : function() {
return mod(this._currentWindows[this._currentApp] + 1,
this._appIcons[this._currentApp].windows.length);
},
_previousWindow : function() {
return mod(this._currentWindows[this._currentApp] - 1,
this._appIcons[this._currentApp].windows.length);
},
_keyPressEvent : function(actor, event) {
let keysym = event.get_key_symbol();
let shift = (Shell.get_event_state(event) & Clutter.ModifierType.SHIFT_MASK);
// The WASD stuff is for debugging in Xephyr, where the arrow
// keys aren't mapped correctly
if (keysym == Clutter.Tab)
this._select(shift ? this._previousApp() : this._nextApp());
else if (keysym == Clutter.grave)
this._select(this._currentApp, shift ? this._previousWindow() : this._nextWindow());
else if (keysym == Clutter.Escape)
this.destroy();
else if (this._thumbnails) {
if (keysym == Clutter.Left || keysym == Clutter.a)
this._select(this._currentApp, this._previousWindow());
else if (keysym == Clutter.Right || keysym == Clutter.d)
this._select(this._currentApp, this._nextWindow());
else if (keysym == Clutter.Up || keysym == Clutter.w)
this._select(this._currentApp, null, true);
} else {
if (keysym == Clutter.Left || keysym == Clutter.a)
this._select(this._previousApp());
else if (keysym == Clutter.Right || keysym == Clutter.d)
this._select(this._nextApp());
else if (keysym == Clutter.Down || keysym == Clutter.s)
this._select(this._currentApp, this._currentWindows[this._currentApp]);
}
return true;
},
_keyReleaseEvent : function(actor, event) {
let keysym = event.get_key_symbol();
if (keysym == Clutter.Alt_L || keysym == Clutter.Alt_R)
this._finish();
return true;
},
_appActivated : function(appSwitcher, n) {
Main.activateWindow(this._appIcons[n].windows[this._currentWindows[n]]);
this.destroy();
},
_appHovered : function(appSwitcher, n) {
if (!this._mouseActive)
return;
this._select(n, this._currentWindows[n]);
},
_windowActivated : function(thumbnailList, n) {
Main.activateWindow(this._appIcons[this._currentApp].windows[n]);
this.destroy();
},
_windowHovered : function(thumbnailList, n) {
if (!this._mouseActive)
return;
this._select(this._currentApp, n);
},
_mouseMoved : function(actor, event) {
if (++this._mouseMovement < POPUP_POINTER_SELECTION_THRESHOLD)
return;
this.actor.disconnect(this._motionEventId);
this._mouseActive = true;
this._appSwitcher.checkHover();
if (this._thumbnails)
this._thumbnails.checkHover();
},
_finish : function() {
let app = this._appIcons[this._currentApp];
let window = app.windows[this._currentWindows[this._currentApp]];
Main.activateWindow(window);
this.destroy();
},
destroy : function() {
this.actor.destroy();
},
_onDestroy : function() {
if (this._haveModal)
Main.popModal(this.actor);
if (this._keyPressEventId)
global.stage.disconnect(this._keyPressEventId);
if (this._keyReleaseEventId)
global.stage.disconnect(this._keyReleaseEventId);
if (this._thumbnailTimeoutId != 0)
Mainloop.source_remove(this._thumbnailTimeoutId);
},
_select : function(app, window, noTimeout) {
if ((app != this._currentApp || !window) && this._thumbnails) {
this._thumbnails.actor.destroy();
this._thumbnails = null;
this._appSwitcher.showArrow(-1);
}
if (this._thumbnailTimeoutId != 0) {
Mainloop.source_remove(this._thumbnailTimeoutId);
this._thumbnailTimeoutId = 0;
}
this._currentApp = app;
if (window != null) {
this._appSwitcher.highlight(-1);
this._appSwitcher.showArrow(app);
} else {
this._appSwitcher.highlight(app);
if (this._appIcons[this._currentApp].windows.length > 1)
this._appSwitcher.showArrow(app);
}
if (window != null) {
if (!this._thumbnails)
this._createThumbnails();
this._currentWindows[this._currentApp] = window;
this._thumbnails.highlight(window);
} else if (this._appIcons[this._currentApp].windows.length > 1 &&
!noTimeout) {
this._thumbnailTimeoutId = Mainloop.timeout_add (
THUMBNAIL_POPUP_TIME,
Lang.bind(this, function () {
this._select(this._currentApp,
this._currentWindows[this._currentApp]);
return false;
}));
}
},
_createThumbnails : function() {
this._thumbnails = new ThumbnailList (this._appIcons[this._currentApp].windows);
this._thumbnails.connect('item-activated', Lang.bind(this, this._windowActivated));
this._thumbnails.connect('item-hovered', Lang.bind(this, this._windowHovered));
this.actor.add_actor(this._thumbnails.actor);
let thumbnailCenter;
if (this._thumbnails.actor.width < this._appSwitcher.actor.width) {
// Center the thumbnails under the corresponding AppIcon.
// If this is being called when the switcher is first
// being brought up, then nothing will have been assigned
// an allocation yet, and the get_transformed_position()
// call will return 0,0.
// (http://bugzilla.openedhand.com/show_bug.cgi?id=1115).
// Calling clutter_actor_get_allocation_box() would force
// it to properly allocate itself, but we can't call that
// because it has an out-caller-allocates arg. So we use
// clutter_stage_get_actor_at_pos(), which will force a
// reallocation as a side effect.
global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, 0, 0);
let icon = this._appIcons[this._currentApp].actor;
let [stageX, stageY] = icon.get_transformed_position();
thumbnailCenter = stageX + icon.width / 2;
} else {
// Center the thumbnails on the monitor
let primary = global.get_primary_monitor();
thumbnailCenter = primary.x + primary.width / 2;
}
this._thumbnails.actor.x = Math.floor(thumbnailCenter - this._thumbnails.actor.width / 2);
this._thumbnails.actor.y = this._appSwitcher.actor.y + this._appSwitcher.actor.height + POPUP_LIST_SPACING;
}
};
function SwitcherList(squareItems) {
this._init(squareItems);
}
SwitcherList.prototype = {
_init : function(squareItems) {
this.actor = new St.Bin({ style_class: 'switcher-list' });
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
// Here we use a GenericContainer so that we can force all the
// children except the separator to have the same width.
this._list = new Shell.GenericContainer();
this._list.spacing = POPUP_LIST_SPACING;
this._list.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
this._list.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
this._list.connect('allocate', Lang.bind(this, this._allocate));
this.actor.add_actor(this._list);
this._items = [];
this._highlighted = -1;
this._separator = null;
this._squareItems = squareItems;
this._hoverTimeout = 0;
},
_onDestroy: function() {
if (this._hoverTimeout != 0) {
Mainloop.source_remove(this._hoverTimeout);
this._hoverTimeout = 0;
}
},
addItem : function(item) {
let box = new St.Bin({ style_class: 'item-box' });
let bbox;
if (item instanceof Shell.ButtonBox)
bbox = item;
else {
bbox = new Shell.ButtonBox({ reactive: true });
bbox.append(item, Big.BoxPackFlags.NONE);
}
box.add_actor(bbox);
this._list.add_actor(box);
let n = this._items.length;
bbox.connect('activate', Lang.bind(this, function () {
this._itemActivated(n);
}));
bbox.connect('notify::hover', Lang.bind(this, function () {
this._hoverChanged(bbox, n);
}));
this._items.push(box);
},
addSeparator: function () {
// FIXME: make this work with StWidgets and CSS
let box = new Big.Box({ padding_top: 2, padding_bottom: 2 });
box.append(new Clutter.Rectangle({ width: 1,
color: POPUP_SEPARATOR_COLOR }),
Big.BoxPackFlags.EXPAND);
this._separator = box;
this._list.add_actor(box);
},
highlight: function(index) {
if (this._highlighted != -1)
this._items[this._highlighted].style_class = 'item-box';
this._highlighted = index;
if (this._highlighted != -1)
this._items[this._highlighted].style_class = 'selected-item-box';
},
// Used after the mouse movement exceeds the threshold, to check
// if it's already hovering over an icon
checkHover: function() {
for (let i = 0; i < this._items.length; i++) {
if (this._items[i].get_child().hover) {
this._hoverChanged(this._items[i].get_child(), i);
return;
}
}
},
_itemActivated: function(n) {
this.emit('item-activated', n);
},
_hoverChanged: function(box, n) {
if (this._hoverTimeout != 0) {
Mainloop.source_remove(this._hoverTimeout);
this._hoverTimeout = 0;
}
if (box.hover) {
this._hoverTimeout = Mainloop.timeout_add(
HOVER_TIME,
Lang.bind (this, function () {
this._itemHovered(n);
}));
}
},
_itemHovered: function(n) {
this.emit('item-hovered', n);
},
_maxChildWidth: function (forHeight) {
let maxChildMin = 0;
let maxChildNat = 0;
for (let i = 0; i < this._items.length; i++) {
let [childMin, childNat] = this._items[i].get_preferred_width(forHeight);
maxChildMin = Math.max(childMin, maxChildMin);
maxChildNat = Math.max(childNat, maxChildNat);
if (this._squareItems) {
let [childMin, childNat] = this._items[i].get_preferred_height(-1);
maxChildMin = Math.max(childMin, maxChildMin);
maxChildNat = Math.max(childNat, maxChildNat);
}
}
return [maxChildMin, maxChildNat];
},
_getPreferredWidth: function (actor, forHeight, alloc) {
let [maxChildMin, maxChildNat] = this._maxChildWidth(forHeight);
let separatorWidth = 0;
if (this._separator) {
let [sepMin, sepNat] = this._separator.get_preferred_width(forHeight);
separatorWidth = sepNat + this._list.spacing;
}
let totalSpacing = this._list.spacing * (this._items.length - 1);
alloc.min_size = this._items.length * maxChildMin + separatorWidth + totalSpacing;
alloc.nat_size = this._items.length * maxChildNat + separatorWidth + totalSpacing;
},
_getPreferredHeight: function (actor, forWidth, alloc) {
let maxChildMin = 0;
let maxChildNat = 0;
for (let i = 0; i < this._items.length; i++) {
let [childMin, childNat] = this._items[i].get_preferred_height(-1);
maxChildMin = Math.max(childMin, maxChildMin);
maxChildNat = Math.max(childNat, maxChildNat);
}
if (this._squareItems) {
let [childMin, childNat] = this._maxChildWidth(-1);
maxChildMin = Math.max(childMin, maxChildMin);
maxChildNat = Math.max(childNat, maxChildNat);
}
alloc.min_size = maxChildMin;
alloc.nat_size = maxChildNat;
},
_allocate: function (actor, box, flags) {
let childHeight = box.y2 - box.y1;
let [maxChildMin, maxChildNat] = this._maxChildWidth(childHeight);
let totalSpacing = this._list.spacing * (this._items.length - 1);
let separatorWidth = 0;
if (this._separator) {
let [sepMin, sepNat] = this._separator.get_preferred_width(childHeight);
separatorWidth = sepNat;
totalSpacing += this._list.spacing;
}
let childWidth = Math.floor(Math.max(0, box.x2 - box.x1 - totalSpacing - separatorWidth) / this._items.length);
let x = 0;
let children = this._list.get_children();
let childBox = new Clutter.ActorBox();
for (let i = 0; i < children.length; i++) {
if (this._items.indexOf(children[i]) != -1) {
let [childMin, childNat] = children[i].get_preferred_height(childWidth);
let vSpacing = (childHeight - childNat) / 2;
childBox.x1 = x;
childBox.y1 = vSpacing;
childBox.x2 = x + childWidth;
childBox.y2 = childBox.y1 + childNat;
children[i].allocate(childBox, flags);
x += this._list.spacing + childWidth;
} else if (children[i] == this._separator) {
// We want the separator to be more compact than the rest.
childBox.x1 = x;
childBox.y1 = 0;
childBox.x2 = x + separatorWidth;
childBox.y2 = childHeight;
children[i].allocate(childBox, flags);
x += this._list.spacing + separatorWidth;
} else {
// Something else, eg, AppSwitcher's arrows;
// we don't allocate it.
}
}
}
};
Signals.addSignalMethods(SwitcherList.prototype);
function AppSwitcher(apps) {
this._init(apps);
}
AppSwitcher.prototype = {
__proto__ : SwitcherList.prototype,
_init : function(apps) {
SwitcherList.prototype._init.call(this, true);
// Construct the AppIcons, sort by time, add to the popup
let activeWorkspace = global.screen.get_active_workspace();
let workspaceIcons = [];
let otherIcons = [];
for (let i = 0; i < apps.length; i++) {
let appIcon = new AppIcon.AppIcon({ appInfo: apps[i],
size: POPUP_APPICON_SIZE });
if (this._hasWindowsOnWorkspace(appIcon, activeWorkspace))
workspaceIcons.push(appIcon);
else
otherIcons.push(appIcon);
}
workspaceIcons.sort(Lang.bind(this, this._sortAppIcon));
otherIcons.sort(Lang.bind(this, this._sortAppIcon));
this.icons = [];
this._arrows = [];
for (let i = 0; i < workspaceIcons.length; i++)
this._addIcon(workspaceIcons[i]);
if (workspaceIcons.length > 0 && otherIcons.length > 0)
this.addSeparator();
for (let i = 0; i < otherIcons.length; i++)
this._addIcon(otherIcons[i]);
this._shownArrow = -1;
},
_allocate: function (actor, box, flags) {
// Allocate the main list items
SwitcherList.prototype._allocate.call(this, actor, box, flags);
let arrowHeight = Math.floor(this.actor.get_theme_node().get_padding(St.Side.BOTTOM) / 3);
let arrowWidth = arrowHeight * 2;
// Now allocate each arrow underneath its item
let childBox = new Clutter.ActorBox();
for (let i = 0; i < this._items.length; i++) {
let itemBox = this._items[i].allocation;
childBox.x1 = Math.floor(itemBox.x1 + (itemBox.x2 - itemBox.x1 - arrowWidth) / 2);
childBox.x2 = childBox.x1 + arrowWidth;
childBox.y1 = itemBox.y2 + arrowHeight;
childBox.y2 = childBox.y1 + arrowHeight;
this._arrows[i].allocate(childBox, flags);
}
},
showArrow : function(n) {
if (this._shownArrow != -1)
this._arrows[this._shownArrow].hide();
this._shownArrow = n;
if (this._shownArrow != -1)
this._arrows[this._shownArrow].show();
},
_addIcon : function(appIcon) {
this.icons.push(appIcon);
this.addItem(appIcon.actor);
let arrow = new Shell.DrawingArea();
arrow.connect('redraw', Lang.bind(this,
function (area, texture) {
Shell.draw_box_pointer(texture, Shell.PointerDirection.DOWN,
TRANSPARENT_COLOR,
POPUP_ARROW_COLOR);
}));
this._list.add_actor(arrow);
this._arrows.push(arrow);
arrow.hide();
},
_hasWindowsOnWorkspace: function(appIcon, workspace) {
for (let i = 0; i < appIcon.windows.length; i++) {
if (appIcon.windows[i].get_workspace() == workspace)
return true;
}
return false;
},
_hasVisibleWindows : function(appIcon) {
for (let i = 0; i < appIcon.windows.length; i++) {
if (appIcon.windows[i].showing_on_its_workspace())
@ -155,133 +648,32 @@ AltTabPopup.prototype = {
return (appIcon2.windows[0].get_user_time() -
appIcon1.windows[0].get_user_time());
}
},
_keyPressEvent : function(actor, event) {
let keysym = event.get_key_symbol();
let backwards = (event.get_state() & Clutter.ModifierType.SHIFT_MASK);
if (keysym == Clutter.Tab)
this._updateSelection(backwards ? -1 : 1);
else if (keysym == Clutter.grave)
this._updateWindowSelection(backwards ? -1 : 1);
else if (keysym == Clutter.Escape)
this.destroy();
return true;
},
_keyReleaseEvent : function(actor, event) {
let keysym = event.get_key_symbol();
if (keysym == Clutter.Alt_L || keysym == Clutter.Alt_R)
this._finish();
return true;
},
_appClicked : function(icon) {
Main.activateWindow(icon.windows[0]);
this.destroy();
},
_windowClicked : function(icon, window) {
if (window)
Main.activateWindow(window);
this.destroy();
},
_windowHovered : function(icon, window) {
if (window)
this._highlightWindow(window);
},
_mouseMoved : function(actor, event) {
if (++this._mouseMovement < POPUP_POINTER_SELECTION_THRESHOLD)
return;
this.actor.disconnect(this._motionEventId);
this._mouseActive = true;
actor = event.get_source();
while (actor) {
if (actor._delegate instanceof AppIcon.AppIcon) {
this._iconEntered(actor, event);
return;
}
actor = actor.get_parent();
}
},
_iconEntered : function(actor, event) {
let index = this._icons.indexOf(actor._delegate);
if (this._mouseActive)
this._updateSelection(index - this._selected);
},
_finish : function() {
if (this._highlightedWindow)
Main.activateWindow(this._highlightedWindow);
this.destroy();
},
destroy : function() {
this.actor.destroy();
},
_onDestroy : function() {
if (this._haveModal)
Main.popModal(this.actor);
if (this._lightbox)
this._lightbox.destroy();
if (this._keyPressEventId)
global.stage.disconnect(this._keyPressEventId);
if (this._keyReleaseEventId)
global.stage.disconnect(this._keyReleaseEventId);
},
_updateSelection : function(delta) {
this._icons[this._selected].setHighlight(false);
if (delta != 0 && this._selectedMenu)
this._selectedMenu.popdown();
this._selected = (this._selected + this._icons.length + delta) % this._icons.length;
this._icons[this._selected].setHighlight(true);
this._highlightWindow(this._currentWindows[this._selected]);
},
_menuPoppedUp : function(icon, menu) {
this._selectedMenu = menu;
},
_menuPoppedDown : function(icon, menu) {
this._selectedMenu = null;
},
_updateWindowSelection : function(delta) {
let icon = this._icons[this._selected];
if (!this._selectedMenu)
icon.popupMenu();
if (!this._selectedMenu)
return;
let next = 0;
for (let i = 0; i < icon.windows.length; i++) {
if (icon.windows[i] == this._highlightedWindow) {
next = (i + icon.windows.length + delta) % icon.windows.length;
break;
}
}
this._selectedMenu.selectWindow(icon.windows[next]);
},
_highlightWindow : function(metaWin) {
this._highlightedWindow = metaWin;
this._currentWindows[this._selected] = metaWin;
this._lightbox.highlight(this._highlightedWindow.get_compositor_private());
}
};
function ThumbnailList(windows) {
this._init(windows);
}
ThumbnailList.prototype = {
__proto__ : SwitcherList.prototype,
_init : function(windows) {
SwitcherList.prototype._init.call(this);
for (let i = 0; i < windows.length; i++) {
let mutterWindow = windows[i].get_compositor_private();
let windowTexture = mutterWindow.get_texture ();
let [width, height] = windowTexture.get_size();
let scale = Math.min(1.0, THUMBNAIL_SIZE / width, THUMBNAIL_SIZE / height);
let clone = new Clutter.Clone ({ source: windowTexture,
reactive: true,
width: width * scale,
height: height * scale });
let box = new Big.Box({ padding: AppIcon.APPICON_BORDER_WIDTH + AppIcon.APPICON_PADDING });
box.append(clone, Big.BoxPackFlags.NONE);
this.addItem(box);
}
}
};

View File

@ -177,31 +177,21 @@ AppDisplay.prototype = {
this._menus = [];
this._menuDisplays = [];
// map<itemId, array of category names>
this._appCategories = {};
// map<search term, map<appId, true>>
// We use a map of appIds instead of an array to ensure that we don't have duplicates and for easier lookup.
this._menuSearchAppMatches = {};
this._appMonitor = Shell.AppMonitor.get_default();
this._appSystem = Shell.AppSystem.get_default();
this._appsStale = true;
this._appSystem.connect('installed-changed', Lang.bind(this, function(appSys) {
this._appsStale = true;
this._redisplay(0);
this._redisplayMenus();
this._redisplay(GenericDisplay.RedisplayFlags.NONE);
}));
this._appSystem.connect('favorites-changed', Lang.bind(this, function(appSys) {
this._redisplay(0);
this._appsStale = true;
this._redisplay(GenericDisplay.RedisplayFlags.NONE);
}));
this._appMonitor.connect('app-added', Lang.bind(this, function(monitor) {
this._redisplay(0);
}));
this._appMonitor.connect('app-removed', Lang.bind(this, function(monitor) {
this._redisplay(0);
}));
// Load the apps now so it doesn't slow down the first
// transition into the Overview
this._refreshCache();
this._focusInMenus = true;
this._activeMenuIndex = -1;
@ -210,7 +200,6 @@ AppDisplay.prototype = {
this._menuDisplay = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
spacing: MENU_SPACING
});
this._redisplayMenus();
this.connect('expanded', Lang.bind(this, function (self) {
this._filterReset();
@ -253,6 +242,39 @@ AppDisplay.prototype = {
return true;
},
setSearch: function(text) {
let lowertext = text.toLowerCase();
if (lowertext == this._search)
return;
// We prepare menu matches up-front, so that we don't
// need to go over all menu items for each application
// and then get all applications for a matching menu
// to see if a particular application passed to
// _isInfoMatching() is a match.
let terms = lowertext.split(/\s+/);
this._menuSearchAppMatches = {};
for (let i = 0; i < terms.length; i++) {
let term = terms[i];
this._menuSearchAppMatches[term] = {};
for (let j = 0; j < this._menus.length; j++) {
let menuItem = this._menus[j];
// Match only on the beginning of the words in category names,
// because otherwise it introduces unnecessary noise in the results.
if (menuItem.name.toLowerCase().indexOf(term) == 0 ||
menuItem.name.toLowerCase().indexOf(" " + term) > 0) {
let menuApps = this._appSystem.get_applications_for_menu(menuItem.id);
for (let k = 0; k < menuApps.length; k++) {
let menuApp = menuApps[k];
this._menuSearchAppMatches[term][menuApp.get_id()] = true;
}
}
}
}
GenericDisplay.GenericDisplay.prototype.setSearch.call(this, text);
},
// Protected overrides
_filterActive: function() {
@ -303,7 +325,7 @@ AppDisplay.prototype = {
this._activeMenuApps = this._appSystem.get_applications_for_menu(id);
}
}
this._redisplay(true);
this._redisplay(GenericDisplay.RedisplayFlags.FULL);
}));
this._menuDisplay.append(display.actor, 0);
},
@ -333,7 +355,6 @@ AppDisplay.prototype = {
if (!this._appsStale)
return true;
this._allItems = {};
this._appCategories = {};
if (this._showPrefs) {
// Get the desktop file ids for settings/preferences.
@ -361,6 +382,7 @@ AppDisplay.prototype = {
this._addApp(app);
}
}
this._redisplayMenus();
}
this._appsStale = false;
@ -383,7 +405,7 @@ AppDisplay.prototype = {
},
// Checks if the item info can be a match for the search string by checking
// the name, description, execution command, and categories for the application.
// the name, description, execution command, and category for the application.
// Item info is expected to be Shell.AppInfo.
// Returns a boolean flag indicating if itemInfo is a match.
_isInfoMatching : function(itemInfo, search) {
@ -395,10 +417,10 @@ AppDisplay.prototype = {
if (this._activeMenu == null || search != "")
return this._isInfoMatchingSearch(itemInfo, search);
else
return this._isInfoMatchingMenu(itemInfo, search);
return this._isInfoMatchingMenu(itemInfo);
},
_isInfoMatchingMenu : function(itemInfo, search) {
_isInfoMatchingMenu: function(itemInfo) {
let id = itemInfo.get_id();
for (let i = 0; i < this._activeMenuApps.length; i++) {
let activeApp = this._activeMenuApps[i];
@ -436,11 +458,11 @@ AppDisplay.prototype = {
return true;
}
let categories = itemInfo.get_categories();
for (let i = 0; i < categories.length; i++) {
let category = fold(categories[i]);
if (category.indexOf(search) >= 0)
if (this._menuSearchAppMatches[search]) {
if (this._menuSearchAppMatches[search].hasOwnProperty(itemInfo.get_id()))
return true;
} else {
log("Missing an entry for search term " + search + " in this._menuSearchAppMatches");
}
return false;
@ -461,8 +483,10 @@ function BaseWellItem(appInfo, isFavorite, hasMenu) {
BaseWellItem.prototype = {
__proto__: AppIcon.AppIcon.prototype,
_init: function(appInfo, isFavorite, hasMenu) {
AppIcon.AppIcon.prototype._init.call(this, appInfo, hasMenu ? AppIcon.MenuType.ON_RIGHT : AppIcon.MenuType.NONE);
_init: function(appInfo, isFavorite) {
AppIcon.AppIcon.prototype._init.call(this, { appInfo: appInfo,
menuType: AppIcon.MenuType.ON_RIGHT,
glow: true });
this.isFavorite = isFavorite;
@ -521,7 +545,7 @@ RunningWellItem.prototype = {
__proto__: BaseWellItem.prototype,
_init: function(appInfo, isFavorite) {
BaseWellItem.prototype._init.call(this, appInfo, isFavorite, true);
BaseWellItem.prototype._init.call(this, appInfo, isFavorite);
this._dragStartX = 0;
this._dragStartY = 0;
@ -530,7 +554,7 @@ RunningWellItem.prototype = {
},
_onActivate: function (actor, event) {
let modifiers = event.get_state();
let modifiers = Shell.get_event_state(event);
if (modifiers & Clutter.ModifierType.CONTROL_MASK) {
this.appInfo.launch();
@ -594,6 +618,12 @@ InactiveWellItem.prototype = {
return true;
}
return false;
},
menuPoppedUp: function() {
},
menuPoppedDown: function() {
}
};
@ -661,10 +691,8 @@ WellGrid.prototype = {
childBox.y2 = childBox.y1 + itemHeight;
children[i].allocate(childBox, flags);
let atSeparator = (i == this._separatorIndex - 1);
columnIndex++;
if (columnIndex == columns || atSeparator) {
if (columnIndex == columns) {
columnIndex = 0;
}
@ -674,27 +702,7 @@ WellGrid.prototype = {
} else {
x += itemWidth;
}
if (atSeparator) {
y += separatorNatural + WELL_ITEM_VSPACING;
}
}
let separatorRowIndex = Math.ceil(this._separatorIndex / columns);
/* Allocate the separator */
let childBox = new Clutter.ActorBox();
childBox.x1 = box.x1;
childBox.y1 = (itemHeight + WELL_ITEM_VSPACING) * separatorRowIndex;
this._cachedSeparatorY = childBox.y1;
childBox.x2 = box.x2;
childBox.y2 = childBox.y1+separatorNatural;
this._separator.allocate(childBox, flags);
},
setSeparatorIndex: function (index) {
this._separatorIndex = index;
this.actor.queue_relayout();
},
removeAll: function () {
@ -702,11 +710,6 @@ WellGrid.prototype = {
for (let i = 0; i < itemChildren.length; i++) {
itemChildren[i].destroy();
}
this._separatorIndex = 0;
},
isBeforeSeparator: function(x, y) {
return y < this._cachedSeparatorY;
},
_getItemChildren: function () {
@ -724,7 +727,10 @@ WellGrid.prototype = {
return [0, WELL_DEFAULT_COLUMNS, 0, 0];
let nColumns = 0;
let usedWidth = 0;
if (forWidth < 0) {
// Big.Box will allocate us at 0x0 if we are not visible; this is probably a
// Big.Box bug but it can't be fixed because if children are skipped in allocate()
// Clutter gets confused (see http://bugzilla.openedhand.com/show_bug.cgi?id=1831)
if (forWidth <= 0) {
nColumns = WELL_DEFAULT_COLUMNS;
} else {
while (nColumns < WELL_DEFAULT_COLUMNS &&
@ -746,11 +752,10 @@ WellGrid.prototype = {
let minWidth = itemMinWidth * nColumns;
let lastColumnIndex = nColumns - 1;
let separatorColumns = lastColumnIndex - ((lastColumnIndex + this._separatorIndex) % nColumns);
let rows = Math.ceil((children.length + separatorColumns) / nColumns);
let rows = Math.ceil(children.length / nColumns);
let itemWidth;
if (forWidth < 0) {
if (forWidth <= 0) {
itemWidth = itemNaturalWidth;
} else {
itemWidth = Math.floor(forWidth / nColumns);
@ -794,6 +799,9 @@ AppWell.prototype = {
x_align: Big.BoxAlignment.CENTER });
this.actor._delegate = this;
this._pendingRedisplay = false;
this.actor.connect('notify::mapped', Lang.bind(this, this._onMappedNotify));
this._grid = new WellGrid();
this.actor.append(this._grid.actor, Big.BoxPackFlags.EXPAND);
@ -833,7 +841,20 @@ AppWell.prototype = {
values[id] = index; return values; }, {});
},
_onMappedNotify: function() {
let mapped = this.actor.mapped;
if (mapped && this._pendingRedisplay)
this._redisplay();
},
_redisplay: function () {
let mapped = this.actor.mapped;
if (!mapped) {
this._pendingRedisplay = true;
return;
}
this._pendingRedisplay = false;
this._grid.removeAll();
let favoriteIds = this._appSystem.get_favorites();
@ -849,7 +870,6 @@ AppWell.prototype = {
let displays = []
this._addApps(favorites, true);
this._grid.setSeparatorIndex(favorites.length);
this._addApps(running, false);
this._displays = displays;
},
@ -872,9 +892,7 @@ AppWell.prototype = {
let appSystem = Shell.AppSystem.get_default();
let app = null;
if (source instanceof BaseWellItem) {
app = source.appInfo;
} else if (source instanceof AppDisplayItem) {
if (source instanceof AppDisplayItem) {
app = appSystem.lookup_cached_app(source.getId());
} else if (source instanceof Workspaces.WindowClone) {
let appMonitor = Shell.AppMonitor.get_default();
@ -891,22 +909,15 @@ AppWell.prototype = {
let favoriteIds = this._appSystem.get_favorites();
let favoriteIdsObject = this._arrayValues(favoriteIds);
let dropIsFavorite = this._grid.isBeforeSeparator(x - this._grid.actor.x,
y - this._grid.actor.y);
let srcIsFavorite = (id in favoriteIdsObject);
if (srcIsFavorite && (!dropIsFavorite)) {
Mainloop.idle_add(function () {
appSystem.remove_favorite(id);
return false;
});
} else if ((!srcIsFavorite) && dropIsFavorite) {
if (srcIsFavorite) {
return false;
} else {
Mainloop.idle_add(function () {
appSystem.add_favorite(id);
return false;
});
} else {
return false;
}
return true;

View File

@ -20,7 +20,7 @@ GLOW_COLOR.from_pixel(0x4f6ba4ff);
const GLOW_PADDING_HORIZONTAL = 3;
const GLOW_PADDING_VERTICAL = 3;
const APPICON_ICON_SIZE = 48;
const APPICON_DEFAULT_ICON_SIZE = 48;
const APPICON_PADDING = 1;
const APPICON_BORDER_WIDTH = 1;
@ -49,14 +49,19 @@ TRANSPARENT_COLOR.from_pixel(0x00000000);
const MenuType = { NONE: 0, ON_RIGHT: 1, BELOW: 2 };
function AppIcon(appInfo, menuType) {
this._init(appInfo, menuType || MenuType.NONE);
function AppIcon(params) {
this._init(params);
}
AppIcon.prototype = {
_init : function(appInfo, menuType) {
this.appInfo = appInfo;
this._menuType = menuType;
_init : function(params) {
this.appInfo = params.appInfo;
if (!this.appInfo)
throw new Error('AppIcon constructor requires "appInfo" param');
this._menuType = ('menuType' in params) ? params.menuType : MenuType.NONE;
this._iconSize = ('size' in params) ? params.size : APPICON_DEFAULT_ICON_SIZE;
let showGlow = ('glow' in params) ? params.glow : false;
this.actor = new Shell.ButtonBox({ orientation: Big.BoxOrientation.VERTICAL,
border: APPICON_BORDER_WIDTH,
@ -65,29 +70,36 @@ AppIcon.prototype = {
reactive: true });
this.actor._delegate = this;
this.highlight_border_color = APPICON_DEFAULT_BORDER_COLOR;
this._signalIds = [];
if (menuType != MenuType.NONE) {
this.windows = Shell.AppMonitor.get_default().get_windows_for_app(appInfo.get_id());
for (let i = 0; i < this.windows.length; i++) {
this.windows[i].connect('notify::user-time', Lang.bind(this, this._resortWindows));
}
this._resortWindows();
// Note, we don't presently update the window list dynamically here; this actor
// gets destroyed and recreated by AppWell, and in alt-tab the whole thing is
// created each time
this.windows = Shell.AppMonitor.get_default().get_windows_for_app(this.appInfo.get_id());
for (let i = 0; i < this.windows.length; i++) {
let sigId = this.windows[i].connect('notify::user-time', Lang.bind(this, this._resortWindows));
this._signalIds.push([this.windows[i], sigId]);
}
this._resortWindows();
this.actor.connect('destroy', Lang.bind(this, this._destroy));
this.actor.connect('notify::mapped', Lang.bind(this, this._onMappedChanged));
if (this._menuType != MenuType.NONE) {
this.actor.connect('button-press-event', Lang.bind(this, this._updateMenuOnButtonPress));
this.actor.connect('notify::hover', Lang.bind(this, this._updateMenuOnHoverChanged));
this.actor.connect('activate', Lang.bind(this, this._updateMenuOnActivate));
this._menuTimeoutId = 0;
this._menu = null;
} else
this.windows = [];
}
let iconBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
x_align: Big.BoxAlignment.CENTER,
y_align: Big.BoxAlignment.CENTER,
width: APPICON_ICON_SIZE,
height: APPICON_ICON_SIZE });
this.icon = appInfo.create_icon_texture(APPICON_ICON_SIZE);
width: this._iconSize,
height: this._iconSize });
this.icon = this.appInfo.create_icon_texture(this._iconSize);
iconBox.append(this.icon, Big.BoxPackFlags.NONE);
this.actor.append(iconBox, Big.BoxPackFlags.EXPAND);
@ -102,18 +114,23 @@ AppIcon.prototype = {
font_name: "Sans 12px",
line_alignment: Pango.Alignment.CENTER,
ellipsize: Pango.EllipsizeMode.END,
text: appInfo.get_name() });
text: this.appInfo.get_name() });
nameBox.add_actor(this._name);
this._glowBox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
let glowPath = GLib.filename_to_uri(global.imagedir + 'app-well-glow.png', '');
for (let i = 0; i < this.windows.length && i < 3; i++) {
let glow = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
glowPath, -1, -1);
glow.keep_aspect_ratio = false;
this._glowBox.append(glow, Big.BoxPackFlags.EXPAND);
if (showGlow) {
this._glowBox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
let glowPath = GLib.filename_to_uri(global.imagedir + 'app-well-glow.png', '');
for (let i = 0; i < this.windows.length && i < 3; i++) {
let glow = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
glowPath, -1, -1);
glow.keep_aspect_ratio = false;
this._glowBox.append(glow, Big.BoxPackFlags.EXPAND);
}
this._nameBox.add_actor(this._glowBox);
this._glowBox.lower(this._name);
}
this._nameBox.add_actor(this._glowBox);
this._glowBox.lower(this._name);
else
this._glowBox = null;
this.actor.append(nameBox, Big.BoxPackFlags.NONE);
},
@ -158,8 +175,36 @@ AppIcon.prototype = {
}
},
_destroy: function() {
for (let i = 0; i < this._signalIds.length; i++) {
let [obj, sigId] = this._signalIds[i];
obj.disconnect(sigId);
}
},
_onMappedChanged: function() {
let mapped = this.actor.mapped;
if (mapped && this._windowSortStale)
this._resortWindows();
},
_resortWindows: function() {
let mapped = this.actor.mapped;
if (!mapped) {
this._windowSortStale = true;
return;
}
this._windowSortStale = false;
this.windows.sort(function (a, b) {
let activeWorkspace = global.screen.get_active_workspace();
let wsA = a.get_workspace() == activeWorkspace;
let wsB = b.get_workspace() == activeWorkspace;
if (wsA && !wsB)
return -1;
else if (wsB && !wsA)
return 1;
let visA = a.showing_on_its_workspace();
let visB = b.showing_on_its_workspace();
@ -176,7 +221,7 @@ AppIcon.prototype = {
// a subclass of it draggable, you can use this method to create
// a drag actor
createDragActor: function() {
return this.appInfo.create_icon_texture(APPICON_ICON_SIZE);
return this.appInfo.create_icon_texture(this._iconSize);
},
setHighlight: function(highlight) {
@ -205,14 +250,19 @@ AppIcon.prototype = {
},
_updateMenuOnButtonPress: function(actor, event) {
if (this._menuTimeoutId != 0)
Mainloop.source_remove(this._menuTimeoutId);
this._menuTimeoutId = Mainloop.timeout_add(APPICON_MENU_POPUP_TIMEOUT_MS,
Lang.bind(this, this.popupMenu));
let button = event.get_button();
if (button == 1) {
if (this._menuTimeoutId != 0)
Mainloop.source_remove(this._menuTimeoutId);
this._menuTimeoutId = Mainloop.timeout_add(APPICON_MENU_POPUP_TIMEOUT_MS,
Lang.bind(this, function () { this.popupMenu(button); }));
} else if (button == 3) {
this.popupMenu(button);
}
return false;
},
popupMenu: function() {
popupMenu: function(activatingButton) {
if (this._menuTimeoutId != 0) {
Mainloop.source_remove(this._menuTimeoutId);
this._menuTimeoutId = 0;
@ -236,7 +286,7 @@ AppIcon.prototype = {
}));
}
this._menu.popup();
this._menu.popup(activatingButton);
return false;
},
@ -299,7 +349,7 @@ AppIconMenu.prototype = {
this._arrow = new Shell.DrawingArea();
this._arrow.connect('redraw', Lang.bind(this, function (area, texture) {
Shell.draw_box_pointer(texture,
this._type == MenuType.ON_RIGHT ? Clutter.Gravity.WEST : Clutter.Gravity.NORTH,
this._type == MenuType.ON_RIGHT ? Shell.PointerDirection.LEFT : Shell.PointerDirection.UP,
source.highlight_border_color,
APPICON_MENU_BACKGROUND_COLOR);
}));
@ -377,32 +427,53 @@ AppIconMenu.prototype = {
let iconsDiffer = false;
let texCache = Shell.TextureCache.get_default();
let firstIcon = windows[0].mini_icon;
for (let i = 1; i < windows.length; i++) {
if (!texCache.pixbuf_equal(windows[i].mini_icon, firstIcon)) {
iconsDiffer = true;
break;
if (windows.length > 0) {
let firstIcon = windows[0].mini_icon;
for (let i = 1; i < windows.length; i++) {
if (!texCache.pixbuf_equal(windows[i].mini_icon, firstIcon)) {
iconsDiffer = true;
break;
}
}
}
// Display the app windows menu items and the separator between windows
// of the current desktop and other windows.
let activeWorkspace = global.screen.get_active_workspace();
let separatorShown = windows.length > 0 && windows[0].get_workspace() != activeWorkspace;
let currentWorkspaceWindows = windows.filter(function (w) {
return w.get_workspace() == activeWorkspace;
});
let otherWorkspaceWindows = windows.filter(function (w) {
return w.get_workspace() != activeWorkspace;
});
for (let i = 0; i < windows.length; i++) {
if (!separatorShown && windows[i].get_workspace() != activeWorkspace) {
this._appendSeparator();
separatorShown = true;
}
this._appendWindows(currentWorkspaceWindows, iconsDiffer);
if (currentWorkspaceWindows.length > 0 && otherWorkspaceWindows.length > 0) {
this._appendSeparator();
let icon = null;
if (iconsDiffer)
icon = Shell.TextureCache.get_default().bind_pixbuf_property(windows[i], "mini-icon");
let box = this._appendMenuItem(icon, windows[i].title);
box._window = windows[i];
}
this._appendWindows(otherWorkspaceWindows, iconsDiffer);
this._appendSeparator();
if (windows.length > 0)
this._appendSeparator();
this._newWindowMenuItem = this._appendMenuItem(null, _("New Window"));
this._newWindowMenuItem = windows.length > 0 ? this._appendMenuItem(null, _("New Window")) : null;
let favorites = Shell.AppSystem.get_default().get_favorites();
let id = this._source.appInfo.get_id();
this._isFavorite = false;
for (let i = 0; i < favorites.length; i++) {
if (id == favorites[i]) {
this._isFavorite = true;
break;
}
}
if (windows.length > 0)
this._appendSeparator();
this._toggleFavoriteMenuItem = this._appendMenuItem(null, this._isFavorite ? _("Remove from favorites")
: _("Add to favorites"));
this._highlightedItem = null;
},
@ -441,26 +512,13 @@ AppIconMenu.prototype = {
return box;
},
_appendWindows: function (windows, iconsDiffer) {
for (let i = 0; i < windows.length; i++) {
let metaWindow = windows[i];
let icon = null;
if (iconsDiffer) {
icon = Shell.TextureCache.get_default().bind_pixbuf_property(metaWindow, "mini-icon");
}
let box = this._appendMenuItem(icon, metaWindow.title);
box._window = metaWindow;
}
},
popup: function() {
popup: function(activatingButton) {
let [stageX, stageY] = this._source.actor.get_transformed_position();
let [stageWidth, stageHeight] = this._source.actor.get_transformed_size();
this._redisplay();
this._windowContainer.popup(0, Main.currentTime());
this._windowContainer.popup(activatingButton, Main.currentTime());
this.emit('popup', true);
@ -559,6 +617,12 @@ AppIconMenu.prototype = {
} else if (child == this._newWindowMenuItem) {
this._source.appInfo.launch();
this.emit('activate-window', null);
} else if (child == this._toggleFavoriteMenuItem) {
let appSys = Shell.AppSystem.get_default();
if (this._isFavorite)
appSys.remove_favorite(this._source.appInfo.get_id());
else
appSys.add_favorite(this._source.appInfo.get_id());
}
this.popdown();
},

177
js/ui/calendar.js Normal file
View File

@ -0,0 +1,177 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
const Clutter = imports.gi.Clutter;
const Lang = imports.lang;
const St = imports.gi.St;
const Gettext_gtk20 = imports.gettext.domain('gtk20');
const MSECS_IN_DAY = 24 * 60 * 60 * 1000;
function _sameDay(dateA, dateB) {
return (dateA.getDate() == dateB.getDate() &&
dateA.getMonth() == dateB.getMonth() &&
dateA.getYear() == dateB.getYear());
}
function Calendar() {
this._init();
};
Calendar.prototype = {
_init: function() {
// FIXME: This is actually the fallback method for GTK+ for the week start;
// GTK+ by preference uses nl_langinfo (NL_TIME_FIRST_WEEKDAY). We probably
// should add a C function so we can do the full handling.
this._weekStart = NaN;
let weekStartString = Gettext_gtk20.gettext("calendar:week_start:0");
if (weekStartString.indexOf("calendar:week_start:") == 0) {
this._weekStart = parseInt(weekStartString.substring(20));
}
if (isNaN(this._weekStart) || this._weekStart < 0 || this._weekStart > 6) {
log("Translation of 'calendar:week_start:0' in GTK+ is not correct");
this.weekStart = 0;
}
// Find the ordering for month/year in the calendar heading
switch (Gettext_gtk20.gettext("calendar:MY")) {
case "calendar:MY":
this._headerFormat = "%B %Y";
break;
case "calendar:YM":
this._headerFormat = "%Y %B";
break;
default:
log("Translation of 'calendar:MY' in GTK+ is not correct");
this._headerFormat = "%B %Y";
break;
}
// Start off with the current date
this.date = new Date();
this.actor = new St.Table({ homogeneous: false,
style_class: "calendar",
reactive: true });
this.actor.connect('scroll-event',
Lang.bind(this, this._onScroll));
// Top line of the calendar '<| September 2009 |>'
this._topBox = new St.BoxLayout();
this.actor.add(this._topBox,
{ row: 0, col: 0, col_span: 7 });
let back = new St.Button({ label: "&lt;", style_class: 'calendar-change-month' });
this._topBox.add(back);
back.connect("clicked", Lang.bind(this, this._prevMonth));
this._dateLabel = new St.Label();
this._topBox.add(this._dateLabel, { expand: true, x_fill: false, x_align: St.Align.MIDDLE });
let forward = new St.Button({ label: "&gt;", style_class: 'calendar-change-month' });
this._topBox.add(forward);
forward.connect("clicked", Lang.bind(this, this._nextMonth));
// We need to figure out the abbreviated localized names for the days of the week;
// we do this by just getting the next 7 days starting from right now and then putting
// them in the right cell in the table. It doesn't matter if we add them in order
let iter = new Date(this.date);
iter.setSeconds(0); // Leap second protection. Hah!
iter.setHours(12);
for (let i = 0; i < 7; i++) {
this.actor.add(new St.Label({ text: iter.toLocaleFormat("%a") }),
{ row: 1,
col: (7 + iter.getDay() - this._weekStart) % 7,
x_fill: false, x_align: 1.0 });
iter.setTime(iter.getTime() + MSECS_IN_DAY);
}
// All the children after this are days, and get removed when we update the calendar
this._firstDayIndex = this.actor.get_children().length;
this._update();
},
// Sets the calendar to show a specific date
setDate: function(date) {
if (!_sameDay(date, this.date)) {
this.date = date;
this._update();
}
},
_onScroll : function(actor, event) {
switch (event.get_scroll_direction()) {
case Clutter.ScrollDirection.UP:
case Clutter.ScrollDirection.LEFT:
this._prevMonth();
break;
case Clutter.ScrollDirection.DOWN:
case Clutter.ScrollDirection.RIGHT:
this._nextMonth();
break;
}
},
_prevMonth: function() {
if (this.date.getMonth() == 0) {
this.date.setMonth(11);
this.date.setFullYear(this.date.getFullYear() - 1);
} else {
this.date.setMonth(this.date.getMonth() - 1);
}
this._update();
},
_nextMonth: function() {
if (this.date.getMonth() == 11) {
this.date.setMonth(0);
this.date.setFullYear(this.date.getFullYear() + 1);
} else {
this.date.setMonth(this.date.getMonth() + 1);
}
this._update();
},
_update: function() {
this._dateLabel.text = this.date.toLocaleFormat(this._headerFormat);
// Remove everything but the topBox and the weekday labels
let children = this.actor.get_children();
for (let i = this._firstDayIndex; i < children.length; i++)
children[i].destroy();
// Start at the beginning of the week before the start of the month
let iter = new Date(this.date);
iter.setDate(1);
iter.setSeconds(0);
iter.setHours(12);
iter.setTime(iter.getTime() - (iter.getDay() - this._weekStart) * MSECS_IN_DAY);
let now = new Date();
let row = 2;
while (true) {
let label = new St.Label({ text: iter.getDate().toString() });
if (_sameDay(now, iter))
label.style_class = "calendar-day calendar-today";
else if (iter.getMonth() != this.date.getMonth())
label.style_class = "calendar-day calendar-other-month-day";
else
label.style_class = "calendar-day";
this.actor.add(label,
{ row: row, col: (7 + iter.getDay() - this._weekStart) % 7,
x_fill: false, x_align: 1.0 });
iter.setTime(iter.getTime() + MSECS_IN_DAY);
if (iter.getDay() == this._weekStart) {
// We stop on the first "first day of the week" after the month we are displaying
if (iter.getMonth() > this.date.getMonth() || iter.getYear() > this.date.getYear())
break;
row++;
}
}
}
};

View File

@ -889,6 +889,9 @@ Dash.prototype = {
_updateDashActors: function() {
if (this._searchPending) {
this._searchResultsSection.actor.show();
// We initially hide all sections when we start a search. When the search timeout
// first runs, the sections that have matching results are shown. As the search
// is refined, only the sections that have matching results will be shown.
for (let i = 0; i < this._searchSections.length; i++) {
let section = this._searchSections[i];
section.header.actor.hide();
@ -897,13 +900,7 @@ Dash.prototype = {
this._appsSection.actor.hide();
this._placesSection.actor.hide();
this._docsSection.actor.hide();
} else if (this._searchActive) {
for (let i = 0; i < this._searchSections.length; i++) {
let section = this._searchSections[i];
section.header.actor.show();
section.resultArea.actor.show();
}
} else {
} else if (!this._searchActive) {
this._showAllSearchSections();
this._searchResultsSection.actor.hide();
this._appsSection.actor.show();

View File

@ -136,7 +136,7 @@ DocDisplay.prototype = {
// but redisplaying right away is cool when we use Zephyr.
// Also, we might be displaying remote documents, like Google Docs, in the future
// which might be edited by someone else.
this._redisplay(false);
this._redisplay(GenericDisplay.RedisplayFlags.NONE);
}));
this.connect('destroy', Lang.bind(this, function (o) {

View File

@ -4,6 +4,8 @@ const St = imports.gi.St;
const Tweener = imports.ui.tweener;
const Format = imports.misc.format;
// "monkey patch" in some varargs ClutterContainer methods; we need
// to do this per-container class since there is no representation
// of interfaces in Javascript
@ -30,4 +32,5 @@ _patchContainerClass(St.Table);
function init() {
Tweener.init();
String.prototype.format = Format.format;
}

View File

@ -370,7 +370,11 @@ GenericDisplay.prototype = {
return;
let flags = RedisplayFlags.RESET_CONTROLS;
if (this._search != '') {
if (lowertext.indexOf(this._search) == 0)
// Because we combine search terms with OR, we have to be sure that no new term
// was introduced before deciding that the new search results will be a subset of
// the existing search results.
if (lowertext.indexOf(this._search) == 0 &&
lowertext.split(/\s+/).length == this._search.split(/\s+/).length)
flags |= RedisplayFlags.SUBSEARCH;
}
this._search = lowertext;
@ -486,15 +490,16 @@ GenericDisplay.prototype = {
//// Protected methods ////
_redisplayFull: function() {
_recreateDisplayItems: function() {
this._removeAllDisplayItems();
this._setDefaultList();
for (let itemId in this._allItems) {
this._addDisplayItem(itemId);
}
},
// Creates a display item based on the information associated with itemId
// and adds it to the displayed items.
// and adds it to the list of displayed items, but does not yet display it.
_addDisplayItem : function(itemId) {
if (this._displayedItems.hasOwnProperty(itemId)) {
log("Tried adding a display item for " + itemId + ", but an item with this item id is already among displayed items.");
@ -525,7 +530,6 @@ GenericDisplay.prototype = {
this.emit('show-details', index);
}
}));
this._list.add_actor(displayItem.actor);
this._displayedItems[itemId] = displayItem;
},
@ -635,6 +639,7 @@ GenericDisplay.prototype = {
* their own while the user was browsing through the result pages.
* SUBSEARCH - Indicates that the current _search is a superstring of the previous
* one, which implies we only need to re-search through previous results.
* FULL - Indicates that we need refresh all displayed items.
*/
_redisplay: function(flags) {
let resetPage = (flags & RedisplayFlags.RESET_CONTROLS) > 0;
@ -642,13 +647,20 @@ GenericDisplay.prototype = {
let fullReload = (flags & RedisplayFlags.FULL) > 0;
let hadSelected = this.hasSelected();
this.unsetSelected();
if (!this._initialLoadComplete || !this._refreshCache())
if (!this._initialLoadComplete)
fullReload = true;
if (!this._refreshCache())
fullReload = true;
if (fullReload) {
this._recreateDisplayItems();
this._initialLoadComplete = true;
this._redisplayFull();
} if (isSubSearch) {
}
if (isSubSearch) {
this._redisplaySubSearch();
} else {
this._redisplayReordering();

View File

@ -231,12 +231,13 @@ function Inspector() {
Inspector.prototype = {
_init: function() {
let width = 150;
let primary = global.get_primary_monitor();
let eventHandler = new St.BoxLayout({ name: "LookingGlassDialog",
vertical: false,
y: Math.floor(global.stage.height/2),
y: primary.y + Math.floor(primary.height / 2),
reactive: true });
eventHandler.connect('notify::allocation', Lang.bind(this, function () {
eventHandler.x = Math.floor((global.stage.width)/2 - (eventHandler.width)/2);
eventHandler.x = primary.x + Math.floor((primary.width - eventHandler.width) / 2);
}));
global.stage.add_actor(eventHandler);
let displayText = new St.Label();
@ -501,11 +502,11 @@ LookingGlass.prototype = {
},
_resizeTo: function(actor) {
let stage = global.stage;
let myWidth = stage.width * 0.7;
let myHeight = stage.height * 0.7;
let primary = global.get_primary_monitor();
let myWidth = primary.width * 0.7;
let myHeight = primary.height * 0.7;
let [srcX, srcY] = actor.get_transformed_position();
this.actor.x = srcX + (stage.width-myWidth)/2;
this.actor.x = srcX + (primary.width - myWidth) / 2;
this._hiddenY = srcY + actor.height - myHeight - 4; // -4 to hide the top corners
this._targetY = this._hiddenY + myHeight;
this.actor.y = this._hiddenY;

View File

@ -127,7 +127,9 @@ function start() {
}
function _relayout() {
panel.actor.set_size(global.screen_width, Panel.PANEL_HEIGHT);
let primary = global.get_primary_monitor();
panel.actor.set_position(primary.x, primary.y);
panel.actor.set_size(primary.width, Panel.PANEL_HEIGHT);
overview.relayout();
}
@ -199,7 +201,7 @@ function _globalKeyPressHandler(actor, event) {
overview.hide();
return true;
} else if (symbol == Clutter.F2 && (event.get_state() & Clutter.ModifierType.MOD1_MASK)) {
} else if (symbol == Clutter.F2 && (Shell.get_event_state(event) & Clutter.ModifierType.MOD1_MASK)) {
getRunDialog().open();
}
}

View File

@ -142,42 +142,44 @@ Overview.prototype = {
},
_recalculateGridSizes: function () {
wideScreen = (global.screen_width/global.screen_height > WIDE_SCREEN_CUT_OFF_RATIO) &&
(global.screen_height >= WIDE_SCREEN_MINIMUM_HEIGHT);
let primary = global.get_primary_monitor();
wideScreen = (primary.width/primary.height > WIDE_SCREEN_CUT_OFF_RATIO) &&
(primary.height >= WIDE_SCREEN_MINIMUM_HEIGHT);
// We divide the screen into an imaginary grid which helps us determine the layout of
// different visual components.
if (wideScreen) {
displayGridColumnWidth = global.screen_width / COLUMNS_WIDE_SCREEN;
displayGridRowHeight = global.screen_height / ROWS_WIDE_SCREEN;
displayGridColumnWidth = primary.width / COLUMNS_WIDE_SCREEN;
displayGridRowHeight = primary.height / ROWS_WIDE_SCREEN;
} else {
displayGridColumnWidth = global.screen_width / COLUMNS_REGULAR_SCREEN;
displayGridRowHeight = global.screen_height / ROWS_REGULAR_SCREEN;
displayGridColumnWidth = primary.width / COLUMNS_REGULAR_SCREEN;
displayGridRowHeight = primary.height / ROWS_REGULAR_SCREEN;
}
},
relayout: function () {
let screenHeight = global.screen_height;
let screenWidth = global.screen_width;
let primary = global.get_primary_monitor();
this._group.set_position(primary.x, primary.y);
let contentY = Panel.PANEL_HEIGHT;
let contentHeight = screenHeight - contentY;
let contentHeight = primary.height - contentY;
this._coverPane.set_position(0, contentY);
this._coverPane.set_size(screenWidth, contentHeight);
this._coverPane.set_size(primary.width, contentHeight);
let workspaceColumnsUsed = wideScreen ? COLUMNS_FOR_WORKSPACES_WIDE_SCREEN : COLUMNS_FOR_WORKSPACES_REGULAR_SCREEN;
let workspaceRowsUsed = wideScreen ? ROWS_FOR_WORKSPACES_WIDE_SCREEN : ROWS_FOR_WORKSPACES_REGULAR_SCREEN;
this._workspacesWidth = displayGridColumnWidth * workspaceColumnsUsed
- WORKSPACE_GRID_PADDING * 2;
// We scale the vertical padding by (screenHeight / screenWidth)
// We scale the vertical padding by (primary.height / primary.width)
// so that the workspace preserves its aspect ratio.
this._workspacesHeight = displayGridRowHeight * workspaceRowsUsed
- WORKSPACE_GRID_PADDING * (screenHeight / screenWidth) * 2;
- WORKSPACE_GRID_PADDING * (primary.height / primary.width) * 2;
this._workspacesX = displayGridColumnWidth + WORKSPACE_GRID_PADDING;
this._workspacesY = displayGridRowHeight + WORKSPACE_GRID_PADDING * (screenHeight / screenWidth);
this._workspacesY = displayGridRowHeight + WORKSPACE_GRID_PADDING * (primary.height / primary.width);
this._dash.actor.set_position(0, contentY);
this._dash.actor.set_size(displayGridColumnWidth, contentHeight);
@ -187,10 +189,12 @@ Overview.prototype = {
// place the 'Add Workspace' button in the bottom row of the grid
addRemoveButtonSize = Math.floor(displayGridRowHeight * 3/5);
this._addButtonX = this._workspacesX + this._workspacesWidth - addRemoveButtonSize;
this._addButtonY = screenHeight - Math.floor(displayGridRowHeight * 4/5);
this._addButtonY = primary.height - Math.floor(displayGridRowHeight * 4/5);
this._backOver.set_position(0, contentY);
this._backOver.set_size(global.screen_width, contentHeight);
// The parent (this._group) is positioned at the top left of the primary monitor
// while this._backOver occupies the entire screen.
this._backOver.set_position(- primary.x, - primary.y);
this._backOver.set_size(global.screen_width, global.screen_height);
this._paneContainer.set_position(this._dash.actor.x + this._dash.actor.width + DEFAULT_PADDING,
contentY);
@ -198,7 +202,7 @@ Overview.prototype = {
this._paneContainer.height = contentHeight;
this._transparentBackground.set_position(this._paneContainer.x, this._paneContainer.y);
this._transparentBackground.set_size(global.screen_width - this._paneContainer.x,
this._transparentBackground.set_size(primary.width - this._paneContainer.x,
this._paneContainer.height);
if (this._activeDisplayPane != null)
@ -322,9 +326,10 @@ Overview.prototype = {
// The opposite transition is used in hide().
this._group.scaleX = this._group.scaleY = this.getZoomedInScale();
[this._group.x, this._group.y] = this.getZoomedInPosition();
let primary = global.get_primary_monitor();
Tweener.addTween(this._group,
{ x: 0,
y: 0,
{ x: primary.x,
y: primary.y,
scaleX: 1,
scaleY: 1,
transition: 'easeOutQuad',
@ -355,6 +360,10 @@ Overview.prototype = {
this._activeDisplayPane.close();
this._workspaces.hide();
this._addButton.actor.destroy();
this._addButton.actor = null;
this._addButton = null;
// Create a zoom in effect by transforming the Overview group so that
// the active workspace fills up the whole screen. The opposite
// transition is used in show().

View File

@ -7,12 +7,14 @@ const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Tweener = imports.ui.tweener;
const Signals = imports.signals;
const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext;
const Button = imports.ui.button;
const Calendar = imports.ui.calendar;
const Main = imports.ui.main;
const PANEL_HEIGHT = 26;
@ -53,12 +55,13 @@ const TRAY_SPACING_MIN = 8;
// Used for the tray icon container with gtk pre-2.16, which doesn't
// fully support tray icon transparency
const TRAY_BACKGROUND_COLOR = new Clutter.Color();
TRAY_BACKGROUND_COLOR.from_pixel(0xefefefff);
TRAY_BACKGROUND_COLOR.from_pixel(0x0b0b0bff);
const TRAY_BORDER_COLOR = new Clutter.Color();
TRAY_BORDER_COLOR.from_pixel(0x00000033);
const TRAY_CORNER_RADIUS = 5;
const TRAY_BORDER_WIDTH = 0;
const HOT_CORNER_ACTIVATION_TIMEOUT = 0.5;
function AppPanelMenu() {
this._init();
@ -288,6 +291,8 @@ Panel.prototype = {
opacity: 0,
reactive: true });
this._hotCornerActivationTime = 0;
this._hotCornerEnvirons.connect('leave-event',
Lang.bind(this, this._onHotCornerEnvironsLeft));
// Clicking on the hot corner environs should result in the same bahavior
@ -313,10 +318,17 @@ Panel.prototype = {
/* center */
let clockButton = new St.Button({ style_class: "panel-button",
toggle_mode: true });
this._centerBox.append(clockButton, Big.BoxPackFlags.NONE);
clockButton.connect('clicked', Lang.bind(this, this._toggleCalendar));
this._clock = new Clutter.Text({ font_name: DEFAULT_FONT,
color: PANEL_FOREGROUND_COLOR,
text: "" });
this._centerBox.append(this._clock, Big.BoxPackFlags.NONE);
clockButton.add_actor(this._clock);
this._calendarPopup = null;
/* right */
@ -395,14 +407,14 @@ Panel.prototype = {
// We get into the Overview mode on button-press-event as opposed to button-release-event because eventually we'll probably
// have the Overview act like a menu that allows the user to release the mouse on the activity the user wants
// to switch to.
this.button.actor.connect('button-press-event', function(b, e) {
if (e.get_button() == 1 && e.get_click_count() == 1) {
Main.overview.toggle();
this.button.actor.connect('button-press-event', Lang.bind(this, function(b, e) {
if (e.get_button() == 1 && e.get_click_count() == 1 && !Main.overview.animationInProgress) {
this._maybeToggleOverviewOnClick();
return true;
} else {
return false;
}
});
}));
// In addition to pressing the button, the Overview can be entered and exited by other means, such as
// pressing the System key, Alt+F1 or Esc. We want the button to be pressed in when the Overview is entered
// and to be released when it is exited regardless of how it was triggered.
@ -454,10 +466,21 @@ Panel.prototype = {
return false;
},
_toggleCalendar: function(clockButton) {
if (clockButton.checked) {
if (this._calendarPopup == null)
this._calendarPopup = new CalendarPopup();
this._calendarPopup.show();
} else {
this._calendarPopup.hide();
}
},
_onHotCornerEntered : function() {
if (!this._hotCornerEntered) {
this._hotCornerEntered = true;
if (!Main.overview.animationInProgress) {
this._hotCornerActivationTime = Date.now() / 1000;
Main.overview.toggle();
}
}
@ -466,7 +489,7 @@ Panel.prototype = {
_onHotCornerClicked : function() {
if (!Main.overview.animationInProgress) {
Main.overview.toggle();
this._maybeToggleOverviewOnClick();
}
return false;
},
@ -483,5 +506,65 @@ Panel.prototype = {
this._hotCornerEntered = false;
}
return false;
},
// Toggles the overview unless this is the first click on the Activities button within the HOT_CORNER_ACTIVATION_TIMEOUT time
// of the hot corner being triggered. This check avoids opening and closing the overview if the user both triggered the hot corner
// and clicked the Activities button.
_maybeToggleOverviewOnClick: function() {
if (this._hotCornerActivationTime == 0 || Date.now() / 1000 - this._hotCornerActivationTime > HOT_CORNER_ACTIVATION_TIMEOUT)
Main.overview.toggle();
this._hotCornerActivationTime = 0;
}
};
function CalendarPopup() {
this._init();
}
CalendarPopup.prototype = {
_init: function() {
let panelActor = Main.panel.actor;
this.actor = new St.BoxLayout({ name: 'calendarPopup' });
this.calendar = new Calendar.Calendar();
this.actor.add(this.calendar.actor);
// Directly adding the actor to Main.chrome.actor is a hack to
// work around the fact that there is no way to add an actor that
// affects the input region but not the shape.
// See: https://bugzilla.gnome.org/show_bug.cgi?id=597044
Main.chrome.actor.add_actor(this.actor);
Main.chrome.addInputRegionActor(this.actor);
this.actor.y = (panelActor.y + panelActor.height - this.actor.height);
},
show: function() {
let panelActor = Main.panel.actor;
// Reset the calendar to today's date
this.calendar.setDate(new Date());
this.actor.x = Math.round(panelActor.x + (panelActor.width - this.actor.width) / 2);
this.actor.lower(panelActor);
this.actor.show();
Tweener.addTween(this.actor,
{ y: panelActor.y + panelActor.height,
time: 0.2,
transition: "easeOutQuad"
});
},
hide: function() {
let panelActor = Main.panel.actor;
Tweener.addTween(this.actor,
{ y: panelActor.y + panelActor.height - this.actor.height,
time: 0.2,
transition: "easeOutQuad",
onComplete: function() { this.actor.hide(); },
onCompleteScope: this
});
}
};

View File

@ -9,11 +9,16 @@ const Shell = imports.gi.Shell;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Signals = imports.signals;
const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext;
const DND = imports.ui.dnd;
const Main = imports.ui.main;
const GenericDisplay = imports.ui.genericDisplay;
const NAUTILUS_PREFS_DIR = '/apps/nautilus/preferences';
const DESKTOP_IS_HOME_KEY = NAUTILUS_PREFS_DIR + '/desktop_is_home_dir';
const PLACES_VSPACING = 8;
const PLACES_ICON_SIZE = 16;
@ -75,17 +80,33 @@ function Places() {
Places.prototype = {
_init : function() {
// Places is divided semi-arbitrarily into left and right; a grid would
// look better in that there would be an even number of items left+right,
// but it seems like we want some sort of differentiation between actions
// like "Connect to server..." and regular folders
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
spacing: 4 });
this._menuBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
spacing: PLACES_VSPACING });
this.actor.append(this._menuBox, Big.BoxPackFlags.EXPAND);
this._devBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
spacing: PLACES_VSPACING });
this._leftBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
this.actor.append(this._leftBox, Big.BoxPackFlags.EXPAND);
this._rightBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
this.actor.append(this._rightBox, Big.BoxPackFlags.EXPAND);
// Subdivide left into actions and devices
this._actionsBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
spacing: PLACES_VSPACING });
this._leftBox.append(this._actionsBox, Big.BoxPackFlags.NONE);
this._devBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
spacing: PLACES_VSPACING,
padding_top: 6 });
this._leftBox.append(this._devBox, Big.BoxPackFlags.NONE);
// Right is bookmarks
this._dirsBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
spacing: PLACES_VSPACING });
this.actor.append(this._dirsBox, Big.BoxPackFlags.EXPAND);
this._rightBox.append(this._dirsBox, Big.BoxPackFlags.NONE);
let gconf = Shell.GConf.get_default();
gconf.watch_directory(NAUTILUS_PREFS_DIR);
let homeFile = Gio.file_new_for_path (GLib.get_home_dir());
let homeUri = homeFile.get_uri();
@ -99,13 +120,27 @@ Places.prototype = {
Gio.app_info_launch_default_for_uri(homeUri, Main.createAppLaunchContext());
});
this._menuBox.append(home.actor, Big.BoxPackFlags.NONE);
this._actionsBox.append(home.actor, Big.BoxPackFlags.NONE);
let desktopPath = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP);
let desktopFile = Gio.file_new_for_path (desktopPath);
let desktopUri = desktopFile.get_uri();
let desktopLabel = Shell.util_get_label_for_uri (desktopUri);
let desktopIcon = Shell.util_get_icon_for_uri (desktopUri);
this._desktopMenu = new PlaceDisplay(desktopLabel,
function() {
return Shell.TextureCache.get_default().load_gicon(desktopIcon, PLACES_ICON_SIZE);
},
function() {
Gio.app_info_launch_default_for_uri(desktopUri, Main.createAppLaunchContext());
});
this._actionsBox.append(this._desktopMenu.actor, Big.BoxPackFlags.NONE);
this._updateDesktopMenuVisibility();
gconf.connect('changed::' + DESKTOP_IS_HOME_KEY, Lang.bind(this, this._updateDesktopMenuVisibility));
/*
* Show devices, code more or less ported from nautilus-places-sidebar.c
*/
this._menuBox.append(this._devBox, Big.BoxPackFlags.NONE);
this._volumeMonitor = Gio.VolumeMonitor.get();
this._volumeMonitor.connect('volume-added', Lang.bind(this, this._updateDevices));
this._volumeMonitor.connect('volume-removed',Lang.bind(this, this._updateDevices));
@ -137,17 +172,17 @@ Places.prototype = {
function () {
networkApp.launch();
});
this._menuBox.append(network.actor, Big.BoxPackFlags.NONE);
this._actionsBox.append(network.actor, Big.BoxPackFlags.NONE);
}
let connect = new PlaceDisplay('Connect to...',
let connect = new PlaceDisplay(_("Connect to..."),
function () {
return Shell.TextureCache.get_default().load_icon_name("applications-internet", PLACES_ICON_SIZE);
},
function () {
new Shell.Process({ args: ['nautilus-connect-server'] }).run();
});
this._menuBox.append(connect.actor, Big.BoxPackFlags.NONE);
this._actionsBox.append(connect.actor, Big.BoxPackFlags.NONE);
this._bookmarksPath = GLib.build_filenamev([GLib.get_home_dir(), ".gtk-bookmarks"]);
this._bookmarksFile = Gio.file_new_for_path(this._bookmarksPath);
@ -272,7 +307,15 @@ Places.prototype = {
Gio.app_info_launch_default_for_uri(mountUri, Main.createAppLaunchContext());
});
this._devBox.append(devItem.actor, Big.BoxPackFlags.NONE);
}
},
_updateDesktopMenuVisibility: function() {
let gconf = Shell.GConf.get_default();
let desktopIsHome = gconf.get_boolean(DESKTOP_IS_HOME_KEY);
if (desktopIsHome)
this._desktopMenu.actor.hide();
else
this._desktopMenu.actor.show();
}
};
Signals.addSignalMethods(Places.prototype);

View File

@ -60,24 +60,26 @@ RunDialog.prototype = {
// All actors are inside _group. We create it initially
// hidden then show it in show()
this._group = new Clutter.Group({ visible: false });
this._group = new Clutter.Group({ visible: false,
x: 0,
y: 0,
width: global.screen_width,
height: global.screen_height });
global.stage.add_actor(this._group);
this._lightbox = new Lightbox.Lightbox(this._group);
let lightbox = new Lightbox.Lightbox(this._group);
let boxH = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
x_align: Big.BoxAlignment.CENTER,
y_align: Big.BoxAlignment.CENTER,
width: global.screen_width,
height: global.screen_height });
this._boxH = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
x_align: Big.BoxAlignment.CENTER,
y_align: Big.BoxAlignment.CENTER });
this._group.add_actor(boxH);
this._lightbox.highlight(boxH);
this._group.add_actor(this._boxH);
lightbox.highlight(this._boxH);
let boxV = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
y_align: Big.BoxAlignment.CENTER });
boxH.append(boxV, Big.BoxPackFlags.NONE);
this._boxH.append(boxV, Big.BoxPackFlags.NONE);
let dialogBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
@ -87,7 +89,7 @@ RunDialog.prototype = {
padding: DIALOG_PADDING,
width: DIALOG_WIDTH });
boxH.append(dialogBox, Big.BoxPackFlags.NONE);
this._boxH.append(dialogBox, Big.BoxPackFlags.NONE);
let label = new Clutter.Text({ color: BOX_TEXT_COLOR,
font_name: '18px Sans',
@ -146,6 +148,7 @@ RunDialog.prototype = {
},
_run : function(command) {
this._commandError = false;
let f;
if (this._enableInternalCommands)
f = this._internalCommands[command];
@ -155,7 +158,6 @@ RunDialog.prototype = {
f();
} else if (command) {
try {
this._commandError = false;
let [ok, len, args] = GLib.shell_parse_argv(command);
let p = new Shell.Process({'args' : args});
p.run();
@ -168,7 +170,7 @@ RunDialog.prototype = {
* We are only interested in the actual error, so parse that out.
*/
let m = /.+\((.+)\)/.exec(e);
let errorStr = "Execution of '" + command + "' failed:\n" + m[1];
let errorStr = _("Execution of '%s' failed:").format(command) + "\n" + m[1];
this._errorMessage.set_text(errorStr);
this._errorBox.show();
}
@ -182,6 +184,12 @@ RunDialog.prototype = {
if (!Main.pushModal(this._group))
return;
// Position the dialog on the current monitor
let monitor = global.get_focus_monitor();
this._boxH.set_position(monitor.x, monitor.y);
this._boxH.set_size(monitor.width, monitor.height);
this._isOpen = true;
this._group.show();

View File

@ -274,7 +274,7 @@ WindowManager.prototype = {
_startAppSwitcher : function(shellwm, binding, window, backwards) {
let tabPopup = new AltTab.AltTabPopup();
if (!tabPopup.show(backwards ? -1 : 1))
if (!tabPopup.show(backwards))
tabPopup.destroy();
}
};

View File

@ -111,6 +111,8 @@ WindowClone.prototype = {
this.origX = realWindow.x;
this.origY = realWindow.y;
this._stackAbove = null;
this._title = null;
this.actor.connect('button-release-event',
@ -129,6 +131,8 @@ WindowClone.prototype = {
this._draggable.connect('drag-begin', Lang.bind(this, this._onDragBegin));
this._draggable.connect('drag-end', Lang.bind(this, this._onDragEnd));
this._inDrag = false;
this._zooming = false;
},
setVisibleWithChrome: function(visible) {
@ -143,6 +147,14 @@ WindowClone.prototype = {
}
},
setStackAbove: function (actor) {
this._stackAbove = actor;
if (this._inDrag || this._zooming)
// We'll fix up the stack after the drag/zooming
return;
this.actor.raise(this._stackAbove);
},
destroy: function () {
this.actor.destroy();
if (this._title)
@ -157,7 +169,6 @@ WindowClone.prototype = {
this._havePointer = true;
actor.raise_top();
this._updateTitle();
},
@ -206,6 +217,8 @@ WindowClone.prototype = {
},
_zoomStart : function () {
this._zooming = true;
this._zoomLightbox = new Lightbox.Lightbox(global.stage);
this._zoomLocalOrig = new ScaledPoint(this.actor.x, this.actor.y, this.actor.scale_x, this.actor.scale_y);
@ -232,7 +245,10 @@ WindowClone.prototype = {
},
_zoomEnd : function () {
this._zooming = false;
this.actor.reparent(this._origParent);
this.actor.raise(this._stackAbove);
[this.actor.x, this.actor.y] = this._zoomLocalOrig.getPosition();
[this.actor.scale_x, this.actor.scale_y] = this._zoomLocalOrig.getScale();
@ -272,6 +288,12 @@ WindowClone.prototype = {
// mysteriously present
this._havePointer = false;
// We may not have a parent if DnD completed successfully, in
// which case our clone will shortly be destroyed and replaced
// with a new one on the target workspace.
if (this.actor.get_parent() != null)
this.actor.raise(this._stackAbove);
this.emit('drag-end');
},
@ -283,7 +305,6 @@ WindowClone.prototype = {
// Called by Tweener
onAnimationComplete : function () {
this._updateTitle();
this.actor.raise(this.stackAbove);
},
_createTitle : function () {
@ -618,12 +639,43 @@ Workspace.prototype = {
this._desktop.actor.height + 2 * FRAME_SIZE / this.actor.scale_y);
},
_isCloneVisible: function(clone) {
return this._showOnlyWindows == null || (clone.metaWindow in this._showOnlyWindows);
},
/**
* _getVisibleClones:
*
* Returns a list WindowClone objects where the clone isn't filtered
* out by any application filter. The clone for the desktop is excluded.
* The returned array will always be newly allocated; it is not in any
* defined order, and thus it's convenient to call .sort() with your
* choice of sorting function.
*/
_getVisibleClones: function() {
let visible = [];
for (let i = 1; i < this._windows.length; i++) {
let clone = this._windows[i];
if (!this._isCloneVisible(clone))
continue;
visible.push(clone);
}
return visible;
},
_getVisibleWindows: function() {
return this._getVisibleClones().map(function (clone) { return clone.metaWindow; });
},
_resetCloneVisibility: function () {
for (let i = 1; i < this._windows.length; i++) {
let clone = this._windows[i];
let icon = this._windowIcons[i];
if (this._showOnlyWindows != null && !(clone.metaWindow in this._showOnlyWindows)) {
if (!this._isCloneVisible(clone)) {
clone.setVisibleWithChrome(false);
icon.hide();
} else {
@ -853,21 +905,12 @@ Workspace.prototype = {
positionWindows : function(workspaceZooming) {
let totalVisible = 0;
let visibleWindows = [];
for (let i = 1; i < this._windows.length; i++) {
let clone = this._windows[i];
if (this._showOnlyWindows != null && !(clone.metaWindow in this._showOnlyWindows))
continue;
visibleWindows.push(clone.metaWindow);
}
let visibleWindows = this._getVisibleWindows();
// Start the animations
let slots = this._computeAllWindowSlots(visibleWindows.length);
visibleWindows = this._orderWindowsByMotionAndStartup(visibleWindows, slots);
let previousWindow = this._windows[0];
for (let i = 0; i < visibleWindows.length; i++) {
let slot = slots[i];
let metaWindow = visibleWindows[i];
@ -875,9 +918,6 @@ Workspace.prototype = {
let clone = metaWindow._delegate;
let icon = this._windowIcons[mainIndex];
clone.stackAbove = previousWindow.actor;
previousWindow = clone;
let [x, y, scale] = this._computeWindowRelativeLayout(metaWindow, slot);
icon.hide();
@ -896,6 +936,24 @@ Workspace.prototype = {
}
},
syncStacking: function(stackIndices) {
let desktopClone = this._windows[0];
let visibleClones = this._getVisibleClones();
visibleClones.sort(function (a, b) { return stackIndices[a.metaWindow.get_stable_sequence()] - stackIndices[b.metaWindow.get_stable_sequence()]; });
for (let i = 0; i < visibleClones.length; i++) {
let clone = visibleClones[i];
let metaWindow = clone.metaWindow;
if (i == 0) {
clone.setStackAbove(desktopClone.actor);
} else {
let previousClone = visibleClones[i - 1];
clone.setStackAbove(previousClone.actor);
}
}
},
_fadeInWindowIcon: function (clone, icon) {
icon.opacity = 0;
icon.show();
@ -1320,10 +1378,12 @@ Workspaces.prototype = {
// workspaces have been created. This cannot be done first because
// window movement depends on the Workspaces object being accessible
// as an Overview member.
Main.overview.connect('showing',
Lang.bind(this, function() {
for (let w = 0; w < this._workspaces.length; w++)
this._workspaces[w].zoomToOverview();
this._overviewShowingId =
Main.overview.connect('showing',
Lang.bind(this, function() {
this._onRestacked();
for (let w = 0; w < this._workspaces.length; w++)
this._workspaces[w].zoomToOverview();
}));
// Track changes to the number of workspaces
@ -1333,6 +1393,9 @@ Workspaces.prototype = {
this._switchWorkspaceNotifyId =
global.window_manager.connect('switch-workspace',
Lang.bind(this, this._activeWorkspaceChanged));
this._restackedNotifyId =
global.screen.connect('restacked',
Lang.bind(this, this._onRestacked));
},
_lookupWorkspaceForMetaWindow: function (metaWindow) {
@ -1422,9 +1485,6 @@ Workspaces.prototype = {
this._clearApplicationWindowSelection(false);
}
let clone = this._lookupCloneForMetaWindow (metaWindow);
clone.actor.raise_top();
Main.activateWindow(metaWindow, time);
Main.overview.hide();
},
@ -1448,8 +1508,10 @@ Workspaces.prototype = {
this.actor.destroy();
this.actor = null;
Main.overview.disconnect(this._overviewShowingId);
global.screen.disconnect(this._nWorkspacesNotifyId);
global.window_manager.disconnect(this._switchWorkspaceNotifyId);
global.screen.disconnect(this._restackedNotifyId);
},
getScale : function() {
@ -1595,6 +1657,19 @@ Workspaces.prototype = {
this.actor.add_actor(workspace.actor);
},
_onRestacked: function() {
let stack = global.get_windows();
let stackIndices = {};
for (let i = 0; i < stack.length; i++) {
// Use the stable sequence for an integer to use as a hash key
stackIndices[stack[i].get_meta_window().get_stable_sequence()] = i;
}
for (let i = 0; i < this._workspaces.length; i++)
this._workspaces[i].syncStacking(stackIndices);
},
// Handles a drop onto the (+) button; assumes the new workspace
// has already been added
acceptNewWorkspaceDrop : function(source, dropActor, x, y, time) {

View File

@ -6,14 +6,14 @@ msgid ""
msgstr ""
"Project-Id-Version: HEAD\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-09-25 04:22+0200\n"
"PO-Revision-Date: 2009-09-25 04:21+0300\n"
"POT-Creation-Date: 2009-10-03 19:19+0200\n"
"PO-Revision-Date: 2009-10-03 19:20+0300\n"
"Last-Translator: Khaled Hosny <khaledhosny@eglug.org>\n"
"Language-Team: Arabic <doc@arabeyes.org>\n"
"Language: ar\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ar\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
"X-Generator: Virtaal 0.4.0\n"
@ -27,12 +27,12 @@ msgid "Window management and application launching"
msgstr "إدارة النوافذ وإطلاق التطبيقات"
#. left side
#: ../js/ui/panel.js:269
#: ../js/ui/panel.js:271
msgid "Activities"
msgstr "الأنشطة"
#. Translators: This is a time format.
#: ../js/ui/panel.js:452
#: ../js/ui/panel.js:461
msgid "%a %l:%M %p"
msgstr "%A %Ol:%OM %p"
@ -41,48 +41,48 @@ msgid "Find..."
msgstr "ابحث..."
#: ../js/ui/dash.js:400
msgid "Browse"
msgstr "استعرض"
msgid "More"
msgstr "المزيد"
#: ../js/ui/dash.js:536
#: ../js/ui/dash.js:543
msgid "(see all)"
msgstr "(انظر الكل)"
#. **** Applications ****
#: ../js/ui/dash.js:753 ../js/ui/dash.js:809
#: ../js/ui/dash.js:763 ../js/ui/dash.js:825
msgid "APPLICATIONS"
msgstr "التطبيقات"
#. **** Places ****
#. Translators: This is in the sense of locations for documents,
#. network locations, etc.
#: ../js/ui/dash.js:773
#: ../js/ui/dash.js:783
msgid "PLACES"
msgstr "الأماكن"
#. **** Documents ****
#: ../js/ui/dash.js:780 ../js/ui/dash.js:819
#: ../js/ui/dash.js:790 ../js/ui/dash.js:835
msgid "RECENT DOCUMENTS"
msgstr "المستندات الحديثة"
#. **** Search Results ****
#: ../js/ui/dash.js:799 ../js/ui/dash.js:931
#: ../js/ui/dash.js:815 ../js/ui/dash.js:955
msgid "SEARCH RESULTS"
msgstr "نتائج البحث"
#: ../js/ui/dash.js:814
#: ../js/ui/dash.js:830
msgid "PREFERENCES"
msgstr "التفضيلات"
#: ../js/ui/runDialog.js:95
#: ../js/ui/runDialog.js:96
msgid "Please enter a command:"
msgstr "من فضلك اكتب أمرا:"
#: ../src/shell-global.c:799
#: ../src/shell-global.c:812
msgid "Less than a minute ago"
msgstr "منذ أقل من دقيقة"
#: ../src/shell-global.c:802
#: ../src/shell-global.c:815
#, c-format
msgid "%d minute ago"
msgid_plural "%d minutes ago"
@ -93,7 +93,7 @@ msgstr[3] "منذ %d دقائق"
msgstr[4] "منذ %d دقيقة"
msgstr[5] "منذ %d دقيقة"
#: ../src/shell-global.c:805
#: ../src/shell-global.c:818
#, c-format
msgid "%d hour ago"
msgid_plural "%d hours ago"
@ -104,7 +104,7 @@ msgstr[3] "منذ %d ساعات"
msgstr[4] "منذ %d ساعة"
msgstr[5] "منذ %d ساعة"
#: ../src/shell-global.c:808
#: ../src/shell-global.c:821
#, c-format
msgid "%d day ago"
msgid_plural "%d days ago"
@ -115,7 +115,7 @@ msgstr[3] "منذ %d أيام"
msgstr[4] "منذ %d يوما"
msgstr[5] "منذ %d يوم"
#: ../src/shell-global.c:811
#: ../src/shell-global.c:824
#, c-format
msgid "%d week ago"
msgid_plural "%d weeks ago"
@ -199,3 +199,6 @@ msgstr "ابحث"
#, c-format
msgid "%1$s: %2$s"
msgstr "%1$s: %2$s"
#~ msgid "Browse"
#~ msgstr "استعرض"

View File

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: HEAD\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-08-30 18:53+0200\n"
"PO-Revision-Date: 2009-08-30 18:57+0100\n"
"POT-Creation-Date: 2009-10-01 23:16+0200\n"
"PO-Revision-Date: 2009-10-01 23:20+0100\n"
"Last-Translator: Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@ -30,76 +30,81 @@ msgid "Activities"
msgstr "Activitats"
#. Translators: This is a time format.
#: ../js/ui/panel.js:454
#: ../js/ui/panel.js:461
msgid "%a %l:%M %p"
msgstr "%a %H:%M"
#: ../js/ui/dash.js:256
#: ../js/ui/dash.js:283
msgid "Find..."
msgstr "Cerca..."
#: ../js/ui/dash.js:374
msgid "Browse"
msgstr "Navega"
#: ../js/ui/dash.js:400
msgid "More"
msgstr "Més"
#: ../js/ui/dash.js:451
#: ../js/ui/dash.js:543
msgid "(see all)"
msgstr "(mostra tot)"
#. **** Applications ****
#: ../js/ui/dash.js:633
#: ../js/ui/dash.js:681
#: ../js/ui/dash.js:763
#: ../js/ui/dash.js:825
msgid "APPLICATIONS"
msgstr "APLICACIONS"
#. **** Places ****
#. Translators: This is in the sense of locations for documents,
#. network locations, etc.
#: ../js/ui/dash.js:653
#: ../js/ui/dash.js:783
msgid "PLACES"
msgstr "LLOCS"
#. **** Documents ****
#: ../js/ui/dash.js:660
#: ../js/ui/dash.js:692
#: ../js/ui/dash.js:790
#: ../js/ui/dash.js:835
msgid "RECENT DOCUMENTS"
msgstr "DOCUMENTS RECENTS"
#. **** Search Results ****
#: ../js/ui/dash.js:679
#: ../js/ui/dash.js:815
#: ../js/ui/dash.js:958
msgid "SEARCH RESULTS"
msgstr "RESULTATS DE LA CERCA"
#: ../js/ui/runDialog.js:82
#: ../js/ui/dash.js:830
msgid "PREFERENCES"
msgstr "PREFERÈNCIES"
#: ../js/ui/runDialog.js:96
msgid "Please enter a command:"
msgstr "Introduïu una ordre:"
#: ../src/shell-global.c:840
#: ../src/shell-global.c:812
msgid "Less than a minute ago"
msgstr "Fa menys d'un minut"
#: ../src/shell-global.c:843
#: ../src/shell-global.c:815
#, c-format
msgid "%d minute ago"
msgid_plural "%d minutes ago"
msgstr[0] "Fa %d minut"
msgstr[1] "Fa %d minuts"
#: ../src/shell-global.c:846
#: ../src/shell-global.c:818
#, c-format
msgid "%d hour ago"
msgid_plural "%d hours ago"
msgstr[0] "Fa %d hora"
msgstr[1] "Fa %d hores"
#: ../src/shell-global.c:849
#: ../src/shell-global.c:821
#, c-format
msgid "%d day ago"
msgid_plural "%d days ago"
msgstr[0] "Fa %d dia"
msgstr[1] "Fa %d dies"
#: ../src/shell-global.c:852
#: ../src/shell-global.c:824
#, c-format
msgid "%d week ago"
msgid_plural "%d weeks ago"
@ -180,10 +185,3 @@ msgstr "Cerca"
msgid "%1$s: %2$s"
msgstr "%1$s: %2$s"
#~ msgid "Find apps or documents"
#~ msgstr "Cerca aplicacions o documents"
#~ msgid "Manager"
#~ msgstr "Gestor"
#~ msgid "The user manager object this user is controlled by."
#~ msgstr "L'objecte gestor d'usuaris que controla a aquest usuari."

View File

@ -4,18 +4,20 @@
#
# Hendrik Brandt <heb@gnome-de.org>, 2009.
# Hendrik Richter <hendrikr@gnome.org>, 2009.
# Christian Kirbach <Christian.Kirbach@googlemail.com>, 2009.
#
msgid ""
msgstr ""
"Project-Id-Version: HEAD\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-08-20 16:47+0200\n"
"PO-Revision-Date: 2009-08-20 16:49+0200\n"
"Last-Translator: Hendrik Richter <hendrikr@gnome.org>\n"
"Language-Team: Deutsch <gnome-de@gnome.org>\n"
"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-"
"shell&component=general\n"
"POT-Creation-Date: 2009-10-01 22:22+0000\n"
"PO-Revision-Date: 2009-10-01 14:32+0200\n"
"Last-Translator: Christian Kirbach <Christian.Kirbach@googlemail.com>\n"
"Language-Team: German <gnome-de@gnome.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: ../data/gnome-shell.desktop.in.in.h:1
@ -27,78 +29,83 @@ msgid "Window management and application launching"
msgstr "Fenster verwalten und Anwendungen starten"
#. left side
#: ../js/ui/panel.js:266
#: ../js/ui/panel.js:271
msgid "Activities"
msgstr "Aktivitäten"
#. Translators: This is a time format.
#: ../js/ui/panel.js:433
#: ../js/ui/panel.js:461
msgid "%a %l:%M %p"
msgstr "%a %H:%M"
#: ../js/ui/dash.js:251
msgid "Find apps or documents"
msgstr "Anwendungen oder Dokumente suchen"
#: ../js/ui/dash.js:283
msgid "Find..."
msgstr "Suchen"
#: ../js/ui/dash.js:369
msgid "Browse"
msgstr "Durchsuchen"
#: ../js/ui/dash.js:400
msgid "More"
msgstr "Mehr"
#: ../js/ui/dash.js:543
msgid "(see all)"
msgstr "(alle sehen)"
#. **** Applications ****
#: ../js/ui/dash.js:505 ../js/ui/dash.js:578
#: ../js/ui/dash.js:763 ../js/ui/dash.js:825
msgid "APPLICATIONS"
msgstr "ANWENDUNGEN"
#. **** Documents ****
#: ../js/ui/dash.js:510 ../js/ui/dash.js:605
msgid "RECENT DOCUMENTS"
msgstr "ZULETZT GEÖFFNETE DOKUMENTE"
#. **** Places ****
#. Translators: This is in the sense of locations for documents,
#. network locations, etc.
#: ../js/ui/dash.js:598
#: ../js/ui/dash.js:783
msgid "PLACES"
msgstr "ORTE"
#: ../js/ui/runDialog.js:75
#. **** Documents ****
#: ../js/ui/dash.js:790 ../js/ui/dash.js:835
msgid "RECENT DOCUMENTS"
msgstr "ZULETZT GEÖFFNETE DOKUMENTE"
#. **** Search Results ****
#: ../js/ui/dash.js:815 ../js/ui/dash.js:955
msgid "SEARCH RESULTS"
msgstr "SUCHERGEBNISSE"
#: ../js/ui/dash.js:830
msgid "PREFERENCES"
msgstr "EINSTELLUNGEN"
#: ../js/ui/runDialog.js:96
msgid "Please enter a command:"
msgstr "Bitte geben Sie einen Befehl ein:"
#: ../src/gdmuser/gdm-user.c:243
msgid "Manager"
msgstr "Verwaltung"
#: ../src/gdmuser/gdm-user.c:244
msgid "The user manager object this user is controlled by."
msgstr "Das Benutzerverwaltungsobjekt welches diesen Benutzer überwacht."
#: ../src/shell-global.c:841
#: ../src/shell-global.c:812
msgid "Less than a minute ago"
msgstr "Vor weniger als einer Minute"
#: ../src/shell-global.c:844
#: ../src/shell-global.c:815
#, c-format
msgid "%d minute ago"
msgid_plural "%d minutes ago"
msgstr[0] "Vor %d Minute"
msgstr[1] "Vor %d Minuten"
#: ../src/shell-global.c:847
#: ../src/shell-global.c:818
#, c-format
msgid "%d hour ago"
msgid_plural "%d hours ago"
msgstr[0] "Vor %d Stunde"
msgstr[1] "Vor %d Stunden"
#: ../src/shell-global.c:850
#: ../src/shell-global.c:821
#, c-format
msgid "%d day ago"
msgid_plural "%d days ago"
msgstr[0] "Vor %d Tag"
msgstr[1] "Vor %d Tagen"
#: ../src/shell-global.c:853
#: ../src/shell-global.c:824
#, c-format
msgid "%d week ago"
msgid_plural "%d weeks ago"
@ -180,3 +187,15 @@ msgstr "Suchen"
#, c-format
msgid "%1$s: %2$s"
msgstr "%1$s: %2$s"
#~ msgid "Find apps or documents"
#~ msgstr "Anwendungen oder Dokumente suchen"
#~ msgid "Browse"
#~ msgstr "Durchsuchen"
#~ msgid "Manager"
#~ msgstr "Verwaltung"
#~ msgid "The user manager object this user is controlled by."
#~ msgstr "Das Benutzerverwaltungsobjekt welches diesen Benutzer überwacht."

View File

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gnome-shell 0.4\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-08-21 12:36+0200\n"
"PO-Revision-Date: 2009-08-21 12:44+0200\n"
"POT-Creation-Date: 2009-10-03 10:47+0200\n"
"PO-Revision-Date: 2009-10-03 10:48+0200\n"
"Last-Translator: Kjartan Maraas <kmaraas@broadpark.no>\n"
"Language-Team: Norwegian bokmål <i18n-nb@lister.ping.uio.no>\n"
"MIME-Version: 1.0\n"
@ -25,70 +25,83 @@ msgid "Window management and application launching"
msgstr "Vindushåndtering og oppstart av programmer"
#. left side
#: ../js/ui/panel.js:266
#: ../js/ui/panel.js:271
msgid "Activities"
msgstr "Aktiviteter"
#. Translators: This is a time format.
#: ../js/ui/panel.js:433
#: ../js/ui/panel.js:461
msgid "%a %l:%M %p"
msgstr "%a %l:%M"
#: ../js/ui/dash.js:250
msgid "Find apps or documents"
msgstr "Finn programmer eller dokumenter"
#: ../js/ui/dash.js:283
msgid "Find..."
msgstr "Finn..."
#: ../js/ui/dash.js:368
msgid "Browse"
msgstr "Bla gjennom"
#: ../js/ui/dash.js:400
msgid "More"
msgstr "Mer"
#: ../js/ui/dash.js:543
msgid "(see all)"
msgstr "(se alle)"
#. **** Applications ****
#: ../js/ui/dash.js:504 ../js/ui/dash.js:577
#: ../js/ui/dash.js:763 ../js/ui/dash.js:825
msgid "APPLICATIONS"
msgstr "PROGRAMMER"
#. **** Documents ****
#: ../js/ui/dash.js:509 ../js/ui/dash.js:604
msgid "RECENT DOCUMENTS"
msgstr "SISTE DOKUMENTER"
#. **** Places ****
#. Translators: This is in the sense of locations for documents,
#. network locations, etc.
#: ../js/ui/dash.js:597
#: ../js/ui/dash.js:783
msgid "PLACES"
msgstr "STEDER"
#: ../js/ui/runDialog.js:75
#. **** Documents ****
#: ../js/ui/dash.js:790 ../js/ui/dash.js:835
msgid "RECENT DOCUMENTS"
msgstr "SISTE DOKUMENTER"
#. **** Search Results ****
#: ../js/ui/dash.js:815 ../js/ui/dash.js:955
msgid "SEARCH RESULTS"
msgstr "SØKERESULTATER"
#: ../js/ui/dash.js:830
msgid "PREFERENCES"
msgstr "BRUKERVALG"
#: ../js/ui/runDialog.js:96
msgid "Please enter a command:"
msgstr "Oppgi en kommando:"
#: ../src/shell-global.c:841
#: ../src/shell-global.c:812
msgid "Less than a minute ago"
msgstr "Mindre enn ett minutt siden"
#: ../src/shell-global.c:844
#: ../src/shell-global.c:815
#, c-format
msgid "%d minute ago"
msgid_plural "%d minutes ago"
msgstr[0] "%d minutt siden"
msgstr[1] "%d minutter siden"
#: ../src/shell-global.c:847
#: ../src/shell-global.c:818
#, c-format
msgid "%d hour ago"
msgid_plural "%d hours ago"
msgstr[0] "%d time siden"
msgstr[1] "%d timer siden"
#: ../src/shell-global.c:850
#: ../src/shell-global.c:821
#, c-format
msgid "%d day ago"
msgid_plural "%d days ago"
msgstr[0] "%d dag siden"
msgstr[1] "%d dager siden"
#: ../src/shell-global.c:853
#: ../src/shell-global.c:824
#, c-format
msgid "%d week ago"
msgid_plural "%d weeks ago"

View File

@ -18,6 +18,7 @@ st_built_sources = \
BUILT_SOURCES += $(st_built_sources)
EXTRA_DIST += \
st/test-theme.css \
st/st-marshal.list \
st/st-enum-types.h.in \
st/st-enum-types.c.in
@ -73,18 +74,22 @@ st_source_h = \
st/st-button.h \
st/st-clipboard.h \
st/st-entry.h \
st/st-im-text.h \
st/st-label.h \
st/st-private.h \
st/st-scrollable.h \
st/st-scroll-bar.h \
st/st-scroll-view.h \
st/st-subtexture.h \
st/st-table.h \
st/st-table-child.h \
st/st-table-private.h \
st/st-texture-cache.h \
st/st-texture-frame.h \
st/st-theme.h \
st/st-theme-context.h \
st/st-theme-node.h \
st/st-theme-private.h \
st/st-tooltip.h \
st/st-types.h \
st/st-widget.h \
@ -105,6 +110,7 @@ st_source_c = \
st/st-button.c \
st/st-clipboard.c \
st/st-entry.c \
st/st-im-text.c \
st/st-label.c \
st/st-private.c \
st/st-scrollable.c \

View File

@ -166,7 +166,7 @@ Shell-0.1.gir: $(mutter) $(G_IR_SCANNER) Big-1.0.gir St-1.0.gir libgnome-shell.l
--nsversion=0.1 \
--add-include-path=$(MUTTER_LIB_DIR)/mutter/ \
--include=Clutter-1.0 \
--include=Meta-2.27 \
--include=Meta-2.28 \
--libtool="$(LIBTOOL)" \
--add-include-path=$(builddir) \
--include=Big-1.0 \
@ -221,7 +221,7 @@ St-1.0.gir: $(mutter) $(G_IR_SCANNER) libgnome-shell.la libst-1.0.la Makefile
$(addprefix $(srcdir)/,$(st_source_h)) \
$(addprefix $(srcdir)/,$(st_source_c)) \
$(srcdir)/st-enum-types.h \
$(ST_CFLAGS) \
$(st_cflags) \
-o $@
CLEANFILES += St-1.0.gir

View File

@ -45,6 +45,7 @@ struct _ShellAppSystemPrivate {
GHashTable *app_id_to_app;
GHashTable *cached_menu_contents; /* <char *id, GSList<ShellAppInfo*>> */
GSList *cached_app_menus; /* ShellAppMenuEntry */
GSList *cached_settings; /* ShellAppInfo */
@ -56,6 +57,7 @@ struct _ShellAppSystemPrivate {
guint app_change_timeout_id;
};
static void free_appinfo_gslist (gpointer list);
static void shell_app_system_finalize (GObject *object);
static gboolean on_tree_changed (gpointer user_data);
static void on_tree_changed_cb (GMenuTree *tree, gpointer user_data);
@ -229,6 +231,9 @@ shell_app_system_init (ShellAppSystem *self)
priv->app_id_to_app = g_hash_table_new_full (g_str_hash, g_str_equal,
NULL, (GDestroyNotify) shell_app_info_unref);
priv->cached_menu_contents = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, free_appinfo_gslist);
/* For now, we want to pick up Evince, Nautilus, etc. We'll
* handle NODISPLAY semantics at a higher level or investigate them
* case by case.
@ -262,6 +267,8 @@ shell_app_system_finalize (GObject *object)
gmenu_tree_unref (priv->apps_tree);
gmenu_tree_unref (priv->settings_tree);
g_hash_table_destroy (priv->cached_menu_contents);
g_hash_table_destroy (priv->app_id_to_app);
g_slist_foreach (priv->cached_app_menus, (GFunc)shell_app_menu_entry_free, NULL);
@ -279,6 +286,14 @@ shell_app_system_finalize (GObject *object)
G_OBJECT_CLASS (shell_app_system_parent_class)->finalize(object);
}
static void
free_appinfo_gslist (gpointer listp)
{
GSList *list = listp;
g_slist_foreach (list, (GFunc) shell_app_info_unref, NULL);
g_slist_free (list);
}
static void
reread_directories (ShellAppSystem *self, GSList **cache, GMenuTree *tree)
{
@ -419,8 +434,12 @@ static gboolean
on_tree_changed (gpointer user_data)
{
ShellAppSystem *self = SHELL_APP_SYSTEM (user_data);
g_signal_emit (self, signals[INSTALLED_CHANGED], 0);
reread_menus (self);
g_hash_table_remove_all (self->priv->cached_menu_contents);
g_signal_emit (self, signals[INSTALLED_CHANGED], 0);
self->priv->app_change_timeout_id = 0;
return FALSE;
}
@ -537,26 +556,32 @@ shell_app_menu_entry_get_type (void)
* shell_app_system_get_applications_for_menu:
*
* Traverses a toplevel menu, and returns all items under it. Nested items
* are flattened.
* are flattened. This value is computed on initial call and cached thereafter
* until the set of installed applications changes.
*
* Return value: (transfer full) (element-type ShellAppInfo): List of applications
* Return value: (transfer none) (element-type ShellAppInfo): List of applications
*/
GSList *
shell_app_system_get_applications_for_menu (ShellAppSystem *monitor,
shell_app_system_get_applications_for_menu (ShellAppSystem *self,
const char *menu)
{
char *path;
GMenuTreeDirectory *menu_entry;
GSList *apps;
path = g_strdup_printf ("/%s", menu);
menu_entry = gmenu_tree_get_directory_from_path (monitor->priv->apps_tree, path);
g_free (path);
g_assert (menu_entry != NULL);
apps = g_hash_table_lookup (self->priv->cached_menu_contents, menu);
if (!apps)
{
char *path;
GMenuTreeDirectory *menu_entry;
path = g_strdup_printf ("/%s", menu);
menu_entry = gmenu_tree_get_directory_from_path (self->priv->apps_tree, path);
g_free (path);
g_assert (menu_entry != NULL);
apps = gather_entries_recurse (monitor, NULL, menu_entry);
apps = gather_entries_recurse (self, NULL, menu_entry);
g_hash_table_insert (self->priv->cached_menu_contents, g_strdup (menu), apps);
gmenu_tree_item_unref (menu_entry);
gmenu_tree_item_unref (menu_entry);
}
return apps;
}

View File

@ -147,17 +147,14 @@ shell_draw_clock (ClutterCairoTexture *texture,
}
void
shell_draw_box_pointer (ClutterCairoTexture *texture,
ClutterGravity pointing_towards,
ClutterColor *border_color,
ClutterColor *background_color)
shell_draw_box_pointer (ClutterCairoTexture *texture,
ShellPointerDirection direction,
ClutterColor *border_color,
ClutterColor *background_color)
{
guint width, height;
cairo_t *cr;
g_return_if_fail (pointing_towards == CLUTTER_GRAVITY_NORTH ||
pointing_towards == CLUTTER_GRAVITY_WEST);
clutter_cairo_texture_get_surface_size (texture, &width, &height);
clutter_cairo_texture_clear (texture);
@ -167,17 +164,34 @@ shell_draw_box_pointer (ClutterCairoTexture *texture,
clutter_cairo_set_source_color (cr, border_color);
if (pointing_towards == CLUTTER_GRAVITY_WEST)
{
cairo_move_to (cr, width, 0);
cairo_line_to (cr, 0, floor (height * 0.5));
cairo_line_to (cr, width, height);
}
else /* CLUTTER_GRAVITY_NORTH */
switch (direction)
{
case SHELL_POINTER_UP:
cairo_move_to (cr, 0, height);
cairo_line_to (cr, floor (width * 0.5), 0);
cairo_line_to (cr, width, height);
break;
case SHELL_POINTER_DOWN:
cairo_move_to (cr, width, 0);
cairo_line_to (cr, floor (width * 0.5), height);
cairo_line_to (cr, 0, 0);
break;
case SHELL_POINTER_LEFT:
cairo_move_to (cr, width, height);
cairo_line_to (cr, 0, floor (height * 0.5));
cairo_line_to (cr, width, 0);
break;
case SHELL_POINTER_RIGHT:
cairo_move_to (cr, 0, 0);
cairo_line_to (cr, width, floor (height * 0.5));
cairo_line_to (cr, 0, height);
break;
default:
g_assert_not_reached();
}
cairo_stroke_preserve (cr);

View File

@ -13,10 +13,17 @@ ClutterCairoTexture *shell_create_vertical_gradient (ClutterColor *top,
ClutterCairoTexture *shell_create_horizontal_gradient (ClutterColor *left,
ClutterColor *right);
void shell_draw_box_pointer (ClutterCairoTexture *texture,
ClutterGravity pointing_towards,
ClutterColor *border_color,
ClutterColor *background_color);
typedef enum {
SHELL_POINTER_UP,
SHELL_POINTER_DOWN,
SHELL_POINTER_LEFT,
SHELL_POINTER_RIGHT
} ShellPointerDirection;
void shell_draw_box_pointer (ClutterCairoTexture *texture,
ShellPointerDirection direction,
ClutterColor *border_color,
ClutterColor *background_color);
void shell_draw_clock (ClutterCairoTexture *texture,
int hour,

View File

@ -882,3 +882,138 @@ shell_global_create_root_pixmap_actor (ShellGlobal *global)
return clutter_clone_new (global->root_pixmap);
}
/**
* shell_global_get_monitors:
* @global: the #ShellGlobal
*
* Gets a list of the bounding boxes of the active screen's monitors.
*
* Return value: (transfer full) (element-type GdkRectangle): a list
* of monitor bounding boxes.
*/
GSList *
shell_global_get_monitors (ShellGlobal *global)
{
MetaScreen *screen = shell_global_get_screen (global);
GSList *monitors = NULL;
MetaRectangle rect;
int i;
g_assert (sizeof (MetaRectangle) == sizeof (GdkRectangle) &&
G_STRUCT_OFFSET (MetaRectangle, x) == G_STRUCT_OFFSET (GdkRectangle, x) &&
G_STRUCT_OFFSET (MetaRectangle, y) == G_STRUCT_OFFSET (GdkRectangle, y) &&
G_STRUCT_OFFSET (MetaRectangle, width) == G_STRUCT_OFFSET (GdkRectangle, width) &&
G_STRUCT_OFFSET (MetaRectangle, height) == G_STRUCT_OFFSET (GdkRectangle, height));
for (i = meta_screen_get_n_monitors (screen) - 1; i >= 0; i--)
{
meta_screen_get_monitor_geometry (screen, i, &rect);
monitors = g_slist_prepend (monitors,
g_boxed_copy (GDK_TYPE_RECTANGLE, &rect));
}
return monitors;
}
/**
* shell_global_get_primary_monitor:
* @global: the #ShellGlobal
*
* Gets the bounding box of the primary monitor (the one that the
* panel is on).
*
* Return value: the bounding box of the primary monitor
*/
GdkRectangle *
shell_global_get_primary_monitor (ShellGlobal *global)
{
MetaScreen *screen = shell_global_get_screen (global);
MetaRectangle rect;
g_assert (sizeof (MetaRectangle) == sizeof (GdkRectangle) &&
G_STRUCT_OFFSET (MetaRectangle, x) == G_STRUCT_OFFSET (GdkRectangle, x) &&
G_STRUCT_OFFSET (MetaRectangle, y) == G_STRUCT_OFFSET (GdkRectangle, y) &&
G_STRUCT_OFFSET (MetaRectangle, width) == G_STRUCT_OFFSET (GdkRectangle, width) &&
G_STRUCT_OFFSET (MetaRectangle, height) == G_STRUCT_OFFSET (GdkRectangle, height));
meta_screen_get_monitor_geometry (screen, 0, &rect);
return g_boxed_copy (GDK_TYPE_RECTANGLE, &rect);
}
/**
* shell_global_get_focus_monitor:
* @global: the #ShellGlobal
*
* Gets the bounding box of the monitor containing the window that
* currently contains the keyboard focus.
*
* Return value: the bounding box of the focus monitor
*/
GdkRectangle *
shell_global_get_focus_monitor (ShellGlobal *global)
{
MetaScreen *screen = shell_global_get_screen (global);
MetaDisplay *display = meta_screen_get_display (screen);
MetaWindow *focus = meta_display_get_focus_window (display);
MetaRectangle rect, wrect;
int nmonitors, i;
if (focus)
{
meta_window_get_outer_rect (focus, &wrect);
nmonitors = meta_screen_get_n_monitors (screen);
/* Find the monitor that the top-left corner of @focus is on. */
for (i = 0; i < nmonitors; i++)
{
meta_screen_get_monitor_geometry (screen, i, &rect);
if (rect.x < wrect.x && rect.y < wrect.y &&
rect.x + rect.width > wrect.x &&
rect.y + rect.height > wrect.y)
return g_boxed_copy (GDK_TYPE_RECTANGLE, &rect);
}
}
meta_screen_get_monitor_geometry (screen, 0, &rect);
return g_boxed_copy (GDK_TYPE_RECTANGLE, &rect);
}
/**
* shell_global_get_modifier_keys:
* @global: the #ShellGlobal
*
* Gets the current set of modifier keys that are pressed down;
* this is a wrapper around gdk_display_get_pointer() that strips
* out any un-declared modifier flags, to make gjs happy; see
* https://bugzilla.gnome.org/show_bug.cgi?id=597292.
*
* Return value: the current modifiers
*/
GdkModifierType
shell_global_get_modifier_keys (ShellGlobal *global)
{
GdkModifierType mods;
gdk_display_get_pointer (gdk_display_get_default (), NULL, NULL, NULL, &mods);
return mods & GDK_MODIFIER_MASK;
}
/**
* shell_get_event_state:
* @event: a #ClutterEvent
*
* Gets the current state of the event (the set of modifier keys that
* are pressed down). Thhis is a wrapper around
* clutter_event_get_state() that strips out any un-declared modifier
* flags, to make gjs happy; see
* https://bugzilla.gnome.org/show_bug.cgi?id=597292.
*
* Return value: the state from the event
*/
ClutterModifierType
shell_get_event_state (ClutterEvent *event)
{
ClutterModifierType state = clutter_event_get_state (event);
return state & CLUTTER_MODIFIER_MASK;
}

View File

@ -73,6 +73,14 @@ void shell_global_format_time_relative_pretty (ShellGlobal *global, guint delta,
ClutterActor *shell_global_create_root_pixmap_actor (ShellGlobal *global);
GSList *shell_global_get_monitors (ShellGlobal *global);
GdkRectangle *shell_global_get_primary_monitor (ShellGlobal *global);
GdkRectangle *shell_global_get_focus_monitor (ShellGlobal *global);
GdkModifierType shell_global_get_modifier_keys (ShellGlobal *global);
ClutterModifierType shell_get_event_state (ClutterEvent *event);
G_END_DECLS
#endif /* __SHELL_GLOBAL_H__ */

View File

@ -15,6 +15,7 @@ G_DEFINE_TYPE(ShellMenu, shell_menu, BIG_TYPE_BOX);
struct _ShellMenuPrivate {
gboolean popped_up;
gboolean have_grab;
guint activating_button;
gboolean released_on_source;
ClutterActor *source_actor;
@ -117,9 +118,15 @@ shell_menu_button_release_event (ClutterActor *actor,
{
ShellMenu *box = SHELL_MENU (actor);
if (event->button != 1)
/* Until the user releases the button that brought up the menu, we just
* ignore other button press/releass.
* See https://bugzilla.gnome.org/show_bug.cgi?id=596371
*/
if (box->priv->activating_button > 0 && box->priv->activating_button != event->button)
return FALSE;
box->priv->activating_button = 0;
if (box->priv->source_actor && !box->priv->released_on_source)
{
if (box->priv->source_actor == event->source ||
@ -134,13 +141,8 @@ shell_menu_button_release_event (ClutterActor *actor,
shell_menu_popdown_nosignal (box);
if (!container_contains (CLUTTER_CONTAINER (box), event->source))
{
g_signal_emit (G_OBJECT (box), shell_menu_signals[CANCELLED], 0);
return FALSE;
}
if (box->priv->selected == NULL)
if (!container_contains (CLUTTER_CONTAINER (box), event->source) ||
box->priv->selected == NULL)
{
g_signal_emit (G_OBJECT (box), shell_menu_signals[CANCELLED], 0);
return FALSE;
@ -158,6 +160,7 @@ shell_menu_popup (ShellMenu *box,
{
if (box->priv->popped_up)
return;
box->priv->activating_button = button;
box->priv->popped_up = TRUE;
box->priv->have_grab = TRUE;
box->priv->released_on_source = FALSE;

View File

@ -603,7 +603,7 @@ compute_shrinks (StBoxLayout *self,
* to expand from their minimum size up to the natural size. Or to put
* it a different way, we want to start by shrinking only the child that
* can shrink most, then shrink that and the next most shrinkable child,
* to the point where we are shrinling everything.
* to the point where we are shrinking everything.
*/
/* Find the amount of possible shrink for each child */

View File

@ -35,7 +35,6 @@
#include "config.h"
#endif
#include <math.h>
#include <stdlib.h>
#include <string.h>
@ -165,7 +164,7 @@ st_button_style_changed (StWidget *widget)
spacing = 6;
st_theme_node_get_length (theme_node, "border-spacing", FALSE, &spacing);
priv->spacing = round (spacing);
priv->spacing = (int)(0.5 + spacing);
/* update the label styling */
st_button_update_label_style (button);

View File

@ -53,10 +53,10 @@
#include <glib.h>
#include <clutter/clutter.h>
#include <clutter-imcontext/clutter-imtext.h>
#include "st-entry.h"
#include "st-im-text.h"
#include "st-widget.h"
#include "st-texture-cache.h"
#include "st-marshal.h"
@ -645,7 +645,7 @@ st_entry_init (StEntry *entry)
priv = entry->priv = ST_ENTRY_GET_PRIVATE (entry);
priv->entry = g_object_new (CLUTTER_TYPE_IMTEXT,
priv->entry = g_object_new (ST_TYPE_IM_TEXT,
"line-alignment", PANGO_ALIGN_LEFT,
"editable", TRUE,
"reactive", TRUE,

477
src/st/st-im-text.c Normal file
View File

@ -0,0 +1,477 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* st-im-text.c
*
* This started as a copy of ClutterIMText converted to use
* GtkIMContext rather than ClutterIMContext. Original code:
*
* Author: raymond liu <raymond.liu@intel.com>
*
* Copyright (C) 2009, Intel Corporation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
/**
* SECTION:StIMText
* @short_description: Text widget with input method support
* @stability: Unstable
* @see_also: #ClutterText
* @include: st-imtext/st-imtext.h
*
* #StIMText derives from ClutterText and hooks up better text input
* via #GtkIMContext. It is meant to be a drop-in replacement for
* ClutterIMText but using GtkIMContext rather than ClutterIMContext.
*/
/* Places where this actor doesn't support all of GtkIMContext:
*
* A) It doesn't support preedit. This makes it fairly useless for
* most complicated input methods. Fixing this requires support
* directly in ClutterText, since there is no way to wedge a
* preedit string in externally.
* B) It doesn't support surrounding context via the
* :retrieve-surrounding and :delete-surrounding signals. This could
* be added here, but only affects a small number of input methods
* and really doesn't make a lot of sense without A)
*
* Another problem that will show up with usage in GNOME Shell's overview
* is that the user may have trouble seeing and interacting with ancilliary
* windows shown by the IM.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <clutter/clutter.h>
#include <clutter/x11/clutter-x11.h>
#include <glib.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <X11/extensions/XKB.h>
#include "st-im-text.h"
#define ST_IM_TEXT_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_IM_TEXT, StIMTextPrivate))
struct _StIMTextPrivate
{
GtkIMContext *im_context;
GdkWindow *window;
guint need_im_reset : 1;
};
static void st_im_text_commit_cb (GtkIMContext *context,
const gchar *str,
StIMText *imtext);
G_DEFINE_TYPE (StIMText, st_im_text, CLUTTER_TYPE_TEXT)
static void
st_im_text_dispose (GObject *object)
{
StIMTextPrivate *priv = ST_IM_TEXT (object)->priv;
g_signal_handlers_disconnect_by_func (priv->im_context,
(void *) st_im_text_commit_cb,
object);
g_object_unref (priv->im_context);
priv->im_context = NULL;
}
static void
update_im_cursor_location (StIMText *self)
{
StIMTextPrivate *priv = self->priv;
ClutterText *clutter_text = CLUTTER_TEXT (self);
ClutterActor *parent;
gint position;
gfloat cursor_x, cursor_y, cursor_height;
gfloat actor_x, actor_y;
GdkRectangle area;
position = clutter_text_get_cursor_position (clutter_text);
clutter_text_position_to_coords (clutter_text, position,
&cursor_x, &cursor_y, &cursor_height);
/* This is a workaround for a bug in Clutter where
* clutter_actor_get_transformed_position doesn't work during
* clutter_actor_paint() because the actor has already set up
* a model-view matrix.
*
* http://bugzilla.openedhand.com/show_bug.cgi?id=1115
*/
actor_x = actor_y = 0.;
parent = CLUTTER_ACTOR (self);
while (parent)
{
gfloat x, y;
clutter_actor_get_position (parent, &x, &y);
actor_x += x;
actor_y += y;
parent = clutter_actor_get_parent (parent);
}
area.x = (int)(0.5 + cursor_x + actor_x);
area.y = (int)(0.5 + cursor_y + actor_y);
area.width = 0;
area.height = (int)(0.5 + cursor_height);
gtk_im_context_set_cursor_location (priv->im_context, &area);
}
static void
st_im_text_commit_cb (GtkIMContext *context,
const gchar *str,
StIMText *imtext)
{
ClutterText *clutter_text = CLUTTER_TEXT (imtext);
if (clutter_text_get_editable (clutter_text))
{
clutter_text_delete_selection (clutter_text);
clutter_text_insert_text (clutter_text, str,
clutter_text_get_cursor_position (clutter_text));
}
}
static void
reset_im_context (StIMText *self)
{
StIMTextPrivate *priv = self->priv;
if (priv->need_im_reset)
{
gtk_im_context_reset (priv->im_context);
priv->need_im_reset = FALSE;
}
}
static void
st_im_text_paint (ClutterActor *actor)
{
StIMText *self = ST_IM_TEXT (actor);
ClutterText *clutter_text = CLUTTER_TEXT (actor);
/* This updates the cursor position as a side-effect */
if (CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->paint)
CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->paint (actor);
if (clutter_text_get_editable (clutter_text))
update_im_cursor_location (self);
}
/* Returns a new reference to window */
static GdkWindow *
window_for_actor (ClutterActor *actor)
{
GdkDisplay *display = gdk_display_get_default ();
ClutterActor *stage;
Window xwindow;
GdkWindow *window;
stage = clutter_actor_get_stage (actor);
xwindow = clutter_x11_get_stage_window ((ClutterStage *)stage);
window = gdk_window_lookup_for_display (display, xwindow);
if (window)
g_object_ref (window);
else
window = gdk_window_foreign_new_for_display (display, xwindow);
return window;
}
static void
st_im_text_realize (ClutterActor *actor)
{
StIMTextPrivate *priv = ST_IM_TEXT (actor)->priv;
priv->window = window_for_actor (actor);
gtk_im_context_set_client_window (priv->im_context, priv->window);
}
static void
st_im_text_unrealize (ClutterActor *actor)
{
StIMText *self = ST_IM_TEXT (actor);
StIMTextPrivate *priv = self->priv;
reset_im_context (self);
gtk_im_context_set_client_window (priv->im_context, NULL);
g_object_unref (priv->window);
priv->window = NULL;
}
static gboolean
key_is_modifier (guint16 keyval)
{
/* See gdkkeys-x11.c:_gdk_keymap_key_is_modifier() for how this
* really should be implemented */
switch (keyval)
{
case GDK_Shift_L:
case GDK_Shift_R:
case GDK_Control_L:
case GDK_Control_R:
case GDK_Caps_Lock:
case GDK_Shift_Lock:
case GDK_Meta_L:
case GDK_Meta_R:
case GDK_Alt_L:
case GDK_Alt_R:
case GDK_Super_L:
case GDK_Super_R:
case GDK_Hyper_L:
case GDK_Hyper_R:
case GDK_ISO_Lock:
case GDK_ISO_Level2_Latch:
case GDK_ISO_Level3_Shift:
case GDK_ISO_Level3_Latch:
case GDK_ISO_Level3_Lock:
case GDK_ISO_Level5_Shift:
case GDK_ISO_Level5_Latch:
case GDK_ISO_Level5_Lock:
case GDK_ISO_Group_Shift:
case GDK_ISO_Group_Latch:
case GDK_ISO_Group_Lock:
return TRUE;
default:
return FALSE;
}
}
static GdkEventKey *
key_event_to_gdk (ClutterKeyEvent *event_clutter)
{
GdkDisplay *display = gdk_display_get_default ();
GdkKeymap *keymap = gdk_keymap_get_for_display (display);
GdkEventKey *event_gdk;
event_gdk = (GdkEventKey *)gdk_event_new ((event_clutter->type == CLUTTER_KEY_PRESS) ?
GDK_KEY_PRESS : GDK_KEY_RELEASE);
event_gdk->window = window_for_actor ((ClutterActor *)event_clutter->stage);
event_gdk->send_event = FALSE;
event_gdk->time = event_clutter->time;
/* This depends on ClutterModifierType and GdkModifierType being
* identical, which they are currently. (They both match the X
* modifier state in the low 16-bits and have the same extensions.) */
event_gdk->state = event_clutter->modifier_state;
event_gdk->keyval = event_clutter->keyval;
event_gdk->hardware_keycode = event_clutter->hardware_keycode;
/* For non-proper non-XKB support, we'd need a huge cut-and-paste
* from gdkkeys-x11.c; this is a macro that just shifts a few bits
* out of state, so won't make the situation worse if the server
* doesn't support XKB; we'll just end up with group == 0 */
event_gdk->group = XkbGroupForCoreState (event_gdk->state);
gdk_keymap_translate_keyboard_state (keymap, event_gdk->hardware_keycode,
event_gdk->state, event_gdk->group,
&event_gdk->keyval, NULL, NULL, NULL);
if (event_clutter->unicode_value)
{
/* This is not particularly close to what GDK does - event_gdk->string
* is supposed to be in the locale encoding, and have control keys
* as control characters, etc. See gdkevents-x11.c:translate_key_event().
* Hopefully no input method is using event.string.
*/
char buf[6];
event_gdk->length = g_unichar_to_utf8 (event_clutter->unicode_value, buf);
event_gdk->string = g_strndup (buf, event_gdk->length);
}
event_gdk->is_modifier = key_is_modifier (event_gdk->keyval);
return event_gdk;
}
static gboolean
st_im_text_button_press_event (ClutterActor *actor,
ClutterButtonEvent *event)
{
/* The button press indicates the user moving the cursor, or selecting
* etc, so we should abort any current preedit operation. ClutterText
* treats all buttons identically, so so do we.
*/
reset_im_context (ST_IM_TEXT (actor));
if (CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->button_press_event)
return CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->button_press_event (actor, event);
else
return FALSE;
}
static gboolean
st_im_text_key_press_event (ClutterActor *actor,
ClutterKeyEvent *event)
{
StIMText *self = ST_IM_TEXT (actor);
StIMTextPrivate *priv = self->priv;
ClutterText *clutter_text = CLUTTER_TEXT (actor);
gboolean result = FALSE;
int old_position;
if (clutter_text_get_editable (clutter_text))
{
GdkEventKey *event_gdk = key_event_to_gdk (event);
if (gtk_im_context_filter_keypress (priv->im_context, event_gdk))
{
priv->need_im_reset = TRUE;
result = TRUE;
}
gdk_event_free ((GdkEvent *)event_gdk);
}
/* ClutterText:position isn't properly notified, so we have to
* check before/after to catch a keypress (like an arrow key)
* moving the cursor position, which should reset the IM context.
* (Resetting on notify::position would require a sentinel when
* committing text)
*
* http://bugzilla.openedhand.com/show_bug.cgi?id=1830
*/
old_position = clutter_text_get_cursor_position (clutter_text);
if (!result &&
CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_press_event)
result = CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_press_event (actor, event);
if (clutter_text_get_cursor_position (clutter_text) != old_position)
reset_im_context (self);
return result;
}
static gboolean
st_im_text_key_release_event (ClutterActor *actor,
ClutterKeyEvent *event)
{
StIMText *self = ST_IM_TEXT (actor);
StIMTextPrivate *priv = self->priv;
ClutterText *clutter_text = CLUTTER_TEXT (actor);
GdkEventKey *event_gdk;
gboolean result = FALSE;
if (clutter_text_get_editable (clutter_text))
{
event_gdk = key_event_to_gdk (event);
if (gtk_im_context_filter_keypress (priv->im_context, event_gdk))
{
priv->need_im_reset = TRUE;
result = TRUE;
}
gdk_event_free ((GdkEvent *)event_gdk);
}
if (!result &&
CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_release_event)
result = CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_release_event (actor, event);
return result;
}
static void
st_im_text_key_focus_in (ClutterActor *actor)
{
StIMTextPrivate *priv = ST_IM_TEXT (actor)->priv;
ClutterText *clutter_text = CLUTTER_TEXT (actor);
if (clutter_text_get_editable (clutter_text))
{
priv->need_im_reset = TRUE;
gtk_im_context_focus_in (priv->im_context);
}
if (CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_focus_in)
CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_focus_in (actor);
}
static void
st_im_text_key_focus_out (ClutterActor *actor)
{
StIMTextPrivate *priv = ST_IM_TEXT (actor)->priv;
ClutterText *clutter_text = CLUTTER_TEXT (actor);
if (clutter_text_get_editable (clutter_text))
{
priv->need_im_reset = TRUE;
gtk_im_context_focus_out (priv->im_context);
}
if (CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_focus_out)
CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_focus_out (actor);
}
static void
st_im_text_class_init (StIMTextClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
g_type_class_add_private (klass, sizeof (StIMTextPrivate));
object_class->dispose = st_im_text_dispose;
actor_class->paint = st_im_text_paint;
actor_class->realize = st_im_text_realize;
actor_class->unrealize = st_im_text_unrealize;
actor_class->button_press_event = st_im_text_button_press_event;
actor_class->key_press_event = st_im_text_key_press_event;
actor_class->key_release_event = st_im_text_key_release_event;
actor_class->key_focus_in = st_im_text_key_focus_in;
actor_class->key_focus_out = st_im_text_key_focus_out;
}
static void
st_im_text_init (StIMText *self)
{
StIMTextPrivate *priv;
self->priv = priv = ST_IM_TEXT_GET_PRIVATE (self);
priv->im_context = gtk_im_multicontext_new ();
g_signal_connect (priv->im_context, "commit",
G_CALLBACK (st_im_text_commit_cb), self);
}
/**
* st_im_text_new:
* @text: text to set to
*
* Create a new #StIMText with the specified text
*
* Returns: a new #ClutterActor
*/
ClutterActor *
st_im_text_new (const gchar *text)
{
return g_object_new (ST_TYPE_IM_TEXT,
"text", text,
NULL);
}

69
src/st/st-im-text.h Normal file
View File

@ -0,0 +1,69 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* st-imtext.h
*
* This is a copy of ClutterIMText converted to use GtkIMContext rather
* than ClutterIMContext. Original code:
*
* Author: raymond liu <raymond.liu@intel.com>
*
* Copyright (C) 2009, Intel Corporation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#if !defined(ST_H_INSIDE) && !defined(ST_COMPILATION)
#error "Only <st/st.h> can be included directly.h"
#endif
#ifndef __ST_IM_TEXT_H__
#define __ST_IM_TEXT_H__
G_BEGIN_DECLS
#include <clutter/clutter.h>
#define ST_TYPE_IM_TEXT (st_im_text_get_type ())
#define ST_IM_TEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_IM_TEXT, StIMText))
#define ST_IS_IM_TEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ST_TYPE_IM_TEXT))
#define ST_IM_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_IM_TEXT, StIMTextClass))
#define ST_IS_IM_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_IM_TEXT))
#define ST_IM_TEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_IM_TEXT, StIMTextClass))
typedef struct _StIMText StIMText;
typedef struct _StIMTextPrivate StIMTextPrivate;
typedef struct _StIMTextClass StIMTextClass;
struct _StIMText
{
ClutterText parent_instance;
StIMTextPrivate *priv;
};
struct _StIMTextClass
{
ClutterTextClass parent_class;
};
GType st_im_text_get_type (void) G_GNUC_CONST;
ClutterActor *st_im_text_new (const gchar *text);
void st_im_text_set_autoshow_im (StIMText *self,
gboolean autoshow);
G_END_DECLS
#endif /* __ST_IM_TEXT_H__ */

View File

@ -18,6 +18,9 @@ struct _StThemeContextClass {
GObjectClass parent_class;
};
#define DEFAULT_RESOLUTION 96.
#define DEFAULT_FONT "sans-serif 10"
enum
{
CHANGED,
@ -64,10 +67,18 @@ st_theme_context_class_init (StThemeContextClass *klass)
static void
st_theme_context_init (StThemeContext *context)
{
context->resolution = 96.;
context->font = pango_font_description_from_string ("sans-serif 10");
context->resolution = DEFAULT_RESOLUTION;
context->font = pango_font_description_from_string (DEFAULT_FONT);
}
/**
* st_theme_context_new:
*
* Create a new theme context not associated with any #ClutterStage.
* This can be useful in testing scenarios, or if using StThemeContext
* with something other than #ClutterActor objects, but you generally
* should use st_theme_context_get_for_stage() instead.
*/
StThemeContext *
st_theme_context_new (void)
{
@ -87,6 +98,18 @@ on_stage_destroy (ClutterStage *stage)
g_object_unref (context);
}
static void
st_theme_context_changed (StThemeContext *context)
{
StThemeNode *old_root = context->root_node;
context->root_node = NULL;
g_signal_emit (context, signals[CHANGED], 0);
if (old_root)
g_object_unref (old_root);
}
/**
* st_theme_context_get_for_stage:
* @stage: a #ClutterStage
@ -139,7 +162,7 @@ st_theme_context_set_theme (StThemeContext *context,
if (context->theme)
g_object_ref (context->theme);
g_signal_emit (context, signals[CHANGED], 0);
st_theme_context_changed (context);
}
}
@ -159,36 +182,82 @@ st_theme_context_get_theme (StThemeContext *context)
return context->theme;
}
/**
* st_theme_context_set_resolution:
* @context: a #StThemeContext
* @resolution: resolution of the context (number of pixels in an "inch")
*
* Sets the resolution of the theme context. This is the scale factor
* used to convert between points and the length units pt, in, and cm.
* This does not necessarily need to correspond to the actual number
* resolution of the device. A value of 72. means that points and
* pixels are identical. The default value is 96.
*/
void
st_theme_context_set_resolution (StThemeContext *context,
double resolution)
{
g_return_if_fail (ST_IS_THEME_CONTEXT (context));
if (resolution == context->resolution)
return;
context->resolution = resolution;
st_theme_context_changed (context);
}
/**
* st_theme_context_set_resolution:
* @context: a #StThemeContext
*
* Gets the current resolution of the theme context.
* See st_theme_context_set_resolution().
*
* Return value: the resolution (in dots-per-"inch")
*/
double
st_theme_context_get_resolution (StThemeContext *context)
{
g_return_val_if_fail (ST_IS_THEME_CONTEXT (context), 96.);
g_return_val_if_fail (ST_IS_THEME_CONTEXT (context), DEFAULT_RESOLUTION);
return context->resolution;
}
/**
* st_theme_context_set_font:
* @context: a #StThemeContext
* @font: the default font for theme context
*
* Sets the default font for the theme context. This is the font that
* is inherited by the root node of the tree of theme nodes. If the
* font is not overriden, then this font will be used. If the font is
* partially modified (for example, with 'font-size: 110%', then that
* modification is based on this font.
*/
void
st_theme_context_set_font (StThemeContext *context,
const PangoFontDescription *font)
{
g_return_if_fail (ST_IS_THEME_CONTEXT (context));
g_return_if_fail (font != NULL);
if (context->font == font)
if (context->font == font ||
pango_font_description_equal (context->font, font))
return;
pango_font_description_free (context->font);
context->font = pango_font_description_copy (font);
st_theme_context_changed (context);
}
/**
* st_theme_context_get_font:
* @context: a #StThemeContext
*
* Gets the default font for the theme context. See st_theme_context_set_font().
*
* Return value: the default font for the theme context.
*/
const PangoFontDescription *
st_theme_context_get_font (StThemeContext *context)
{

View File

@ -8,6 +8,16 @@
G_BEGIN_DECLS
/**
* SECTION:StThemeContext
* @short_description: holds global information about a tree of styled objects
*
* #StThemeContext is responsible for managing information global to a tree of styled objects,
* such as the set of stylesheets or the default font. In normal usage, a #StThemeContext
* is bound to a #ClutterStage; a singleton #StThemeContext can be obtained for a #ClutterStage
* by using st_theme_context_get_for_stage().
*/
typedef struct _StThemeContextClass StThemeContextClass;
#define ST_TYPE_THEME_CONTEXT (st_theme_context_get_type ())

View File

@ -1,6 +1,5 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
#include <math.h>
#include <stdlib.h>
#include <string.h>
@ -10,18 +9,8 @@
static void st_theme_node_init (StThemeNode *node);
static void st_theme_node_class_init (StThemeNodeClass *klass);
static void st_theme_node_dispose (GObject *object);
static void st_theme_node_finalize (GObject *object);
#if 0
enum {
LAST_SIGNAL
};
static int signals[LAST_SIGNAL];
#endif
struct _StThemeNode {
GObject parent;
@ -50,8 +39,7 @@ struct _StThemeNode {
CRDeclaration **properties;
int n_properties;
/* We hold onto these separately so we can unref them; the alternative
* would be to ref everything in ->properties */
/* We hold onto these separately so we can destroy them on finalize */
CRDeclaration *inline_properties;
guint properties_computed : 1;
@ -82,23 +70,13 @@ st_theme_node_class_init (StThemeNodeClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = st_theme_node_dispose;
object_class->finalize = st_theme_node_finalize;
}
static void
st_theme_node_dispose (GObject *object)
{
/* StThemeNode *node = ST_THEME_NODE (object); */
G_OBJECT_CLASS (st_theme_node_parent_class)->dispose (object);
}
static void
st_theme_node_finalize (GObject *object)
{
StThemeNode *node = ST_THEME_NODE (object);
CRDeclaration *cur_decl;
g_free (node->element_id);
g_free (node->element_class);
@ -112,8 +90,11 @@ st_theme_node_finalize (GObject *object)
node->n_properties = 0;
}
for (cur_decl = node->inline_properties; cur_decl; cur_decl = cur_decl->next)
cr_declaration_unref (cur_decl);
if (node->inline_properties)
{
/* This destroys the list, not just the head of the list */
cr_declaration_destroy (node->inline_properties);
}
if (node->font_desc)
{
@ -133,6 +114,28 @@ st_theme_node_finalize (GObject *object)
G_OBJECT_CLASS (st_theme_node_parent_class)->finalize (object);
}
/**
* st_theme_node_new:
* @context: the context representing global state for this themed tree
* @parent_node: (allow-none): the parent node of this node
* @theme: (allow-none): a theme (stylesheet set) that overrides the
* theme inherited from the parent node
* @element_type: the type of the GObject represented by this node
* in the tree (corresponding to an element if we were theming an XML
* document. %G_TYPE_NONE means this style was created for the stage
* actor and matches a selector element name of 'stage'.
* @element_id: (allow-none): the ID to match CSS rules against
* @element_class: (allow-none): a whitespace-separated list of classes
* to match CSS rules against
* @pseudo_class: (allow-none): a whitespace-separated list of pseudo-classes
* (like 'hover' or 'visited') to match CSS rules against
*
* Creates a new #StThemeNode. Once created, a node is immutable. Of any
* of the attributes of the node (like the @element_class) change the node
* and its child nodes must be destroyed and recreated.
*
* Return value: (transfer full): the theme node
*/
StThemeNode *
st_theme_node_new (StThemeContext *context,
StThemeNode *parent_node,
@ -255,9 +258,7 @@ ensure_properties (StThemeNode *node)
if (!properties)
properties = g_ptr_array_new ();
node->inline_properties = cr_declaration_parse_list_from_buf ((const guchar *)node->inline_style,
CR_UTF_8);
node->inline_properties = _st_theme_parse_declaration_list (node->inline_style);
for (cur_decl = node->inline_properties; cur_decl; cur_decl = cur_decl->next)
g_ptr_array_add (properties, cur_decl);
}
@ -300,7 +301,10 @@ term_is_transparent (CRTerm *term)
static int
color_component_from_double (double component)
{
/* http://people.redhat.com/otaylor/pixel-converting.html */
/* We want to spread the range 0-1 equally over 0..255, but
* 1.0 should map to 255 not 256, so we need to special-case it.
* See http://people.redhat.com/otaylor/pixel-converting.html
* for (very) detailed discussion of related issues. */
if (component >= 1.0)
return 255;
else
@ -340,17 +344,11 @@ get_color_from_rgba_term (CRTerm *term,
if (i < 3)
{
if (num->type == NUM_PERCENTAGE)
{
value = num->val / 100;
}
value = num->val / 100;
else if (num->type == NUM_GENERIC)
{
value = num->val / 255;
}
value = num->val / 255;
else
{
return VALUE_NOT_FOUND;
}
return VALUE_NOT_FOUND;
}
else
{
@ -408,12 +406,21 @@ get_color_from_term (StThemeNode *node,
/* rgba () colors - a CSS3 addition, are not supported by libcroco,
* but they are parsed as a "function", so we can emulate the
* functionality.
*
* libcroco < 0.6.2 has a bug where functions starting with 'r' are
* misparsed. We workaround this by pre-converting 'rgba' to 'RGBA'
* before parsing the stylesheet. Since libcroco isn't
* case-insensitive (a bug), it's fine with functions starting with
* 'R'. (In theory, we should be doing a case-insensitive compare
* everywhere, not just here, but that doesn't make much sense when
* the built-in parsing of libcroco is case-sensitive and things
* like 10PX don't work.)
*/
else if (term->type == TERM_FUNCTION &&
term->content.str &&
term->content.str->stryng &&
term->content.str->stryng->str &&
strcmp (term->content.str->stryng->str, "rgba") == 0)
g_ascii_strcasecmp (term->content.str->stryng->str, "rgba") == 0)
{
return get_color_from_rgba_term (term, color);
}
@ -436,6 +443,27 @@ get_color_from_term (StThemeNode *node,
return VALUE_FOUND;
}
/**
* st_theme_node_get_color:
* @node: a #StThemeNode
* @property_name: The name of the color property
* @inherit: if %TRUE, if a value is not found for the property on the
* node, then it will be looked up on the parent node, and then on the
* parent's parent, and so forth. Note that if the property has a
* value of 'inherit' it will be inherited even if %FALSE is passed
* in for @inherit; this only affects the default behavior for inheritance.
* @color: (out): location to store the color that was determined.
* If the property is not found, the value in this location
* will not be changed.
*
* Generically looks up a property containing a single color value. When
* specific getters (like st_theme_node_get_background_color()) exist, they
* should be used instead. They are cached, so more efficient, and have
* handling for shortcut properties and other details of CSS.
*
* Return value: %TRUE if the property was found in the properties for this
* theme node (or in the properties of parent nodes when inheriting.)
*/
gboolean
st_theme_node_get_color (StThemeNode *node,
const char *property_name,
@ -471,6 +499,25 @@ st_theme_node_get_color (StThemeNode *node,
return FALSE;
}
/**
* st_theme_node_get_double:
* @node: a #StThemeNode
* @property_name: The name of the numeric property
* @inherit: if %TRUE, if a value is not found for the property on the
* node, then it will be looked up on the parent node, and then on the
* parent's parent, and so forth. Note that if the property has a
* value of 'inherit' it will be inherited even if %FALSE is passed
* in for @inherit; this only affects the default behavior for inheritance.
* @value: (out): location to store the value that was determined.
* If the property is not found, the value in this location
* will not be changed.
*
* Generically looks up a property containing a single numeric value
* without units.
*
* Return value: %TRUE if the property was found in the properties for this
* theme node (or in the properties of parent nodes when inheriting.)
*/
gboolean
st_theme_node_get_double (StThemeNode *node,
const char *property_name,
@ -679,6 +726,28 @@ get_length_internal (StThemeNode *node,
return VALUE_NOT_FOUND;
}
/**
* st_theme_node_get_length:
* @node: a #StThemeNode
* @property_name: The name of the length property
* @inherit: if %TRUE, if a value is not found for the property on the
* node, then it will be looked up on the parent node, and then on the
* parent's parent, and so forth. Note that if the property has a
* value of 'inherit' it will be inherited even if %FALSE is passed
* in for @inherit; this only affects the default behavior for inheritance.
* @length: (out): location to store the length that was determined.
* If the property is not found, the value in this location
* will not be changed. The returned length is resolved
* to pixels.
*
* Generically looks up a property containing a single length value. When
* specific getters (like st_theme_node_get_border_width()) exist, they
* should be used instead. They are cached, so more efficient, and have
* handling for shortcut properties and other details of CSS.
*
* Return value: %TRUE if the property was found in the properties for this
* theme node (or in the properties of parent nodes when inheriting.)
*/
gboolean
st_theme_node_get_length (StThemeNode *node,
const char *property_name,
@ -770,21 +839,13 @@ do_border_radius (StThemeNode *node,
return;
if (strcmp (property_name, "-topleft") == 0)
{
do_border_radius_term (node, decl->value, TRUE, FALSE, FALSE, FALSE);
}
do_border_radius_term (node, decl->value, TRUE, FALSE, FALSE, FALSE);
else if (strcmp (property_name, "-topright") == 0)
{
do_border_radius_term (node, decl->value, FALSE, TRUE, FALSE, FALSE);
}
do_border_radius_term (node, decl->value, FALSE, TRUE, FALSE, FALSE);
else if (strcmp (property_name, "-bottomright") == 0)
{
do_border_radius_term (node, decl->value, FALSE, FALSE, TRUE, FALSE);
}
do_border_radius_term (node, decl->value, FALSE, FALSE, TRUE, FALSE);
else if (strcmp (property_name, "-bottomleft") == 0)
{
do_border_radius_term (node, decl->value, FALSE, FALSE, FALSE, TRUE);
}
do_border_radius_term (node, decl->value, FALSE, FALSE, FALSE, TRUE);
}
}
@ -890,9 +951,8 @@ do_border_property (StThemeNode *node,
return;
if (get_color_from_term (node, decl->value, &color) == VALUE_FOUND)
{ /* Ignore inherit */
color_set = TRUE;
}
/* Ignore inherit */
color_set = TRUE;
}
else if (strcmp (property_name, "-width") == 0)
{
@ -900,9 +960,8 @@ do_border_property (StThemeNode *node,
return;
if (get_length_from_term (node, decl->value, FALSE, &width) == VALUE_FOUND)
{ /* Ignore inherit */
width_set = TRUE;
}
/* Ignore inherit */
width_set = TRUE;
}
if (side == (StSide)-1)
@ -996,21 +1055,13 @@ do_padding_property (StThemeNode *node,
return;
if (strcmp (property_name, "-left") == 0)
{
do_padding_property_term (node, decl->value, TRUE, FALSE, FALSE, FALSE);
}
do_padding_property_term (node, decl->value, TRUE, FALSE, FALSE, FALSE);
else if (strcmp (property_name, "-right") == 0)
{
do_padding_property_term (node, decl->value, FALSE, TRUE, FALSE, FALSE);
}
do_padding_property_term (node, decl->value, FALSE, TRUE, FALSE, FALSE);
else if (strcmp (property_name, "-top") == 0)
{
do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE);
}
do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE);
else if (strcmp (property_name, "-bottom") == 0)
{
do_padding_property_term (node, decl->value, FALSE, FALSE, FALSE, TRUE);
}
do_padding_property_term (node, decl->value, FALSE, FALSE, FALSE, TRUE);
}
}
@ -1038,19 +1089,15 @@ ensure_borders (StThemeNode *node)
const char *property_name = decl->property->stryng->str;
if (g_str_has_prefix (property_name, "border"))
{
do_border_property (node, decl);
}
do_border_property (node, decl);
else if (g_str_has_prefix (property_name, "padding"))
{
do_padding_property (node, decl);
}
do_padding_property (node, decl);
}
}
double
st_theme_node_get_border_width (StThemeNode *node,
StSide side)
StSide side)
{
g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
g_return_val_if_fail (side >= ST_SIDE_TOP && side <= ST_SIDE_LEFT, 0.);
@ -1132,6 +1179,7 @@ ensure_background (StThemeNode *node)
GetFromTermResult result = get_background_color_from_term (node, term, &node->background_color);
if (result == VALUE_FOUND)
{
/* color stored in node->background_color */
}
else if (result == VALUE_INHERIT)
{
@ -1143,6 +1191,7 @@ ensure_background (StThemeNode *node)
}
else if (term_is_none (term))
{
/* leave node->background_color as transparent */
}
else if (term->type == TERM_URI)
{
@ -1162,6 +1211,7 @@ ensure_background (StThemeNode *node)
result = get_background_color_from_term (node, decl->value, &node->background_color);
if (result == VALUE_FOUND)
{
/* color stored in node->background_color */
}
else if (result == VALUE_INHERIT)
{
@ -1178,8 +1228,8 @@ ensure_background (StThemeNode *node)
{
g_free (node->background_image);
node->background_image = _st_theme_resolve_url (node->theme,
decl->parent_statement->parent_sheet,
decl->value->content.str->stryng->str);
decl->parent_statement->parent_sheet,
decl->value->content.str->stryng->str);
}
else if (term_is_inherit (decl->value))
{
@ -1370,18 +1420,14 @@ font_family_from_terms (CRTerm *term,
{
if (term->the_operator != COMMA && term->the_operator != NO_OP)
goto out;
/* Can concetenate to bare words, but not two quoted strings */
/* Can concatenate two bare words, but not two quoted strings */
if ((term->the_operator == NO_OP && last_was_quoted) || term->type == TERM_STRING)
goto out;
if (term->the_operator == NO_OP)
{
g_string_append (family_string, " ");
}
g_string_append (family_string, " ");
else
{
g_string_append (family_string, ", ");
}
g_string_append (family_string, ", ");
}
else
{
@ -1434,33 +1480,19 @@ font_size_from_term (StThemeNode *node,
int size_points = (int)(0.5 + *size * (72. / resolution));
if (strcmp (term->content.str->stryng->str, "xx-small") == 0)
{
size_points = font_sizes[0];
}
size_points = font_sizes[0];
else if (strcmp (term->content.str->stryng->str, "x-small") == 0)
{
size_points = font_sizes[1];
}
size_points = font_sizes[1];
else if (strcmp (term->content.str->stryng->str, "small") == 0)
{
size_points = font_sizes[2];
}
size_points = font_sizes[2];
else if (strcmp (term->content.str->stryng->str, "medium") == 0)
{
size_points = font_sizes[3];
}
size_points = font_sizes[3];
else if (strcmp (term->content.str->stryng->str, "large") == 0)
{
size_points = font_sizes[4];
}
size_points = font_sizes[4];
else if (strcmp (term->content.str->stryng->str, "x-large") == 0)
{
size_points = font_sizes[5];
}
size_points = font_sizes[5];
else if (strcmp (term->content.str->stryng->str, "xx-large") == 0)
{
size_points = font_sizes[6];
}
size_points = font_sizes[6];
else if (strcmp (term->content.str->stryng->str, "smaller") == 0)
{
/* Find the standard size equal to or smaller than the current size */
@ -1470,7 +1502,8 @@ font_size_from_term (StThemeNode *node,
i++;
if (i > 6)
{ /* original size greater than any standard size */
{
/* original size greater than any standard size */
size_points = (int)(0.5 + size_points / 1.2);
}
else
@ -1538,7 +1571,7 @@ font_weight_from_term (CRTerm *term,
if (term->content.num->type != NUM_GENERIC)
return FALSE;
weight_int = (int)(term->content.num->val + 0.5);
weight_int = (int)(0.5 + term->content.num->val);
*weight = weight_int;
*weight_absolute = TRUE;
@ -1671,13 +1704,13 @@ st_theme_node_get_font (StThemeNode *node)
for (; term; term = term->next)
{
if (font_style_from_term (term, &tmp_style))
;
else if (font_variant_from_term (term, &tmp_variant))
;
else if (font_weight_from_term (term, &tmp_weight, &tmp_weight_absolute))
;
else
break;
continue;
if (font_variant_from_term (term, &tmp_variant))
continue;
if (font_weight_from_term (term, &tmp_weight, &tmp_weight_absolute))
continue;
break;
}
/* The size is mandatory */
@ -1872,7 +1905,7 @@ st_theme_node_get_border_image (StThemeNode *node)
if (term->content.num->type == NUM_GENERIC)
{
borders[n_borders] = round (0.5 + term->content.num->val);
borders[n_borders] = (int)(0.5 + term->content.num->val);
n_borders++;
}
else if (term->content.num->type == NUM_PERCENTAGE)
@ -1935,15 +1968,15 @@ st_theme_node_get_border_image (StThemeNode *node)
static float
get_width_inc (StThemeNode *node)
{
return (round (node->border_width[ST_SIDE_LEFT]) + node->padding[ST_SIDE_LEFT] +
round (node->border_width[ST_SIDE_RIGHT]) + node->padding[ST_SIDE_RIGHT]);
return ((int)(0.5 + node->border_width[ST_SIDE_LEFT]) + node->padding[ST_SIDE_LEFT] +
(int)(0.5 + node->border_width[ST_SIDE_RIGHT]) + node->padding[ST_SIDE_RIGHT]);
}
static float
get_height_inc (StThemeNode *node)
{
return (round (node->border_width[ST_SIDE_TOP]) + node->padding[ST_SIDE_TOP] +
round (node->border_width[ST_SIDE_BOTTOM]) + node->padding[ST_SIDE_BOTTOM]);
return ((int)(0.5 + node->border_width[ST_SIDE_TOP]) + node->padding[ST_SIDE_TOP] +
(int)(0.5 + node->border_width[ST_SIDE_BOTTOM]) + node->padding[ST_SIDE_BOTTOM]);
}
/**
@ -2076,10 +2109,10 @@ st_theme_node_get_content_box (StThemeNode *node,
ensure_borders (node);
content_box->x1 = round (node->border_width[ST_SIDE_LEFT]) + node->padding[ST_SIDE_LEFT];
content_box->y1 = round (node->border_width[ST_SIDE_TOP]) + node->padding[ST_SIDE_TOP];
content_box->x2 = allocation->x2 - allocation->x1 - (round (node->border_width[ST_SIDE_RIGHT]) + node->padding[ST_SIDE_RIGHT]);
content_box->y2 = allocation->y2 - allocation->y1 - (round (node->border_width[ST_SIDE_BOTTOM]) + node->padding[ST_SIDE_BOTTOM]);
content_box->x1 = (int)(0.5 + node->border_width[ST_SIDE_LEFT]) + node->padding[ST_SIDE_LEFT];
content_box->y1 = (int)(0.5 + node->border_width[ST_SIDE_TOP]) + node->padding[ST_SIDE_TOP];
content_box->x2 = allocation->x2 - allocation->x1 - ((int)(0.5 + node->border_width[ST_SIDE_RIGHT]) + node->padding[ST_SIDE_RIGHT]);
content_box->y2 = allocation->y2 - allocation->y1 - ((int)(0.5 + node->border_width[ST_SIDE_BOTTOM]) + node->padding[ST_SIDE_BOTTOM]);
}

View File

@ -7,6 +7,21 @@
G_BEGIN_DECLS
/**
* SECTION:StThemeNode
* @short_description: style information for one node in a tree of themed objects
*
* A #StThemeNode represents the CSS style information (the set of CSS properties) for one
* node in a tree of themed objects. In typical usage, it represents the style information
* for a single #ClutterActor. A #StThemeNode is immutable: attributes such as the
* CSS classes for the node are passed in at construction. If the attributes of the node
* or any parent node change, the node should be discarded and a new node created.
* #StThemeNode has generic accessors to look up properties by name and specific
* accessors for standard CSS properties that add caching and handling of various
* details of the CSS specification. #StThemeNode also has convenience functions to help
* in implementing a #ClutterActor with borders and padding.
*/
typedef struct _StTheme StTheme;
typedef struct _StThemeContext StThemeContext;
@ -44,9 +59,6 @@ typedef enum {
GType st_theme_node_get_type (void) G_GNUC_CONST;
/* An element_type of G_TYPE_NONE means this style was created for the stage
* actor and matches a selector element name of 'stage'
*/
StThemeNode *st_theme_node_new (StThemeContext *context,
StThemeNode *parent_node, /* can be null */
StTheme *theme, /* can be null */
@ -80,8 +92,6 @@ gboolean st_theme_node_get_double (StThemeNode *node,
gboolean inherit,
double *value);
/* The length here is already resolved to pixels
*/
gboolean st_theme_node_get_length (StThemeNode *node,
const char *property_name,
gboolean inherit,

View File

@ -15,6 +15,8 @@ char *_st_theme_resolve_url (StTheme *theme,
CRStyleSheet *base_stylesheet,
const char *url);
CRDeclaration *_st_theme_parse_declaration_list (const char *str);
G_END_DECLS
#endif /* __ST_THEME_PRIVATE_H__ */

View File

@ -9,14 +9,14 @@
* make sense in our context.
* - The code to get a list of matching properties works quite differently;
* we order things in priority order, but we don't actually try to
* coelesce properties with the same name.
* coalesce properties with the same name.
*
* In moving it to GNOME Shell:
* - Renamed again to StTheme
* - Reformatted to match the gnome-st coding style
* - Reformatted to match the gnome-shell coding style
* - Removed notion of "theme engine" from hippo-canvas
* - pseudo-class matching changed from link enum to strings
* - Some code simplication
* - Some code simplification
*/
/*
@ -51,7 +51,6 @@ static GObject *st_theme_constructor (GType type,
guint n_construct_properties,
GObjectConstructParam *construct_properties);
static void st_theme_dispose (GObject *object);
static void st_theme_finalize (GObject *object);
static void st_theme_set_property (GObject *object,
guint prop_id,
@ -62,15 +61,6 @@ static void st_theme_get_property (GObject *object,
GValue *value,
GParamSpec *pspec);
#if 0
enum
{
LAST_SIGNAL
};
static int signals[LAST_SIGNAL];
#endif
struct _StTheme
{
GObject parent;
@ -104,7 +94,8 @@ G_DEFINE_TYPE (StTheme, st_theme, G_TYPE_OBJECT)
#define strqcmp(str,lit,lit_len) \
(strlen (str) != (lit_len) || memcmp (str, lit, lit_len))
static void st_theme_init (StTheme * theme)
static void
st_theme_init (StTheme *theme)
{
theme->stylesheets_by_filename = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify)g_free, (GDestroyNotify)cr_stylesheet_unref);
@ -112,60 +103,145 @@ static void st_theme_init (StTheme * theme)
}
static void
st_theme_class_init (StThemeClass * klass)
st_theme_class_init (StThemeClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructor = st_theme_constructor;
object_class->dispose = st_theme_dispose;
object_class->finalize = st_theme_finalize;
object_class->set_property = st_theme_set_property;
object_class->get_property = st_theme_get_property;
/**
* StTheme:application-stylesheet
* StTheme:application-stylesheet:
*
* The highest priority stylesheet, representing application-specific
* styling; this is associated with the CSS "author" stylesheet.
*/
g_object_class_install_property (object_class,
PROP_APPLICATION_STYLESHEET,
g_param_spec_string ("application-stylesheet",
PROP_APPLICATION_STYLESHEET,
g_param_spec_string ("application-stylesheet",
"Application Stylesheet",
"Stylesheet with application-specific styling",
NULL,
G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
/**
* StTheme:theme-stylesheet
* StTheme:theme-stylesheet:
*
* The second priority stylesheet, representing theme-specific styling;
* this is associated with the CSS "user" stylesheet.
*/
g_object_class_install_property (object_class,
PROP_THEME_STYLESHEET,
g_param_spec_string ("theme-stylesheet",
"Theme Stylesheet",
"Stylesheet with theme-specific styling",
NULL,
G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
PROP_THEME_STYLESHEET,
g_param_spec_string ("theme-stylesheet",
"Theme Stylesheet",
"Stylesheet with theme-specific styling",
NULL,
G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
/**
* StTheme:default-stylesheet
* StTheme:default-stylesheet:
*
* The lowest priority stylesheet, representing global default
* styling; this is associated with the CSS "user agent" stylesheet.
*/
g_object_class_install_property (object_class,
PROP_DEFAULT_STYLESHEET,
g_param_spec_string ("default-stylesheet",
"Default Stylesheet",
"Stylesheet with global default styling",
NULL,
G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
PROP_DEFAULT_STYLESHEET,
g_param_spec_string ("default-stylesheet",
"Default Stylesheet",
"Stylesheet with global default styling",
NULL,
G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
}
/* This is a workaround for a bug in libcroco < 0.6.2 where
* function starting with 'r' (and 'u') are misparsed. We work
* around this by exploiting the fact that libcroco is incomformant
* with the CSS-spec and case sensitive and pre-convert all
* occurrences of rgba to RGBA. Then we make our own parsing
* code check for RGBA as well.
*/
#if LIBCROCO_VERSION_NUMBER < 602
static gboolean
is_identifier_character (char c)
{
/* Actual CSS rules allow for unicode > 0x00a1 and escaped
* characters, but we'll assume we won't do that in our stylesheets
* or at least not next to the string 'rgba'.
*/
return g_ascii_isalnum(c) || c == '-' || c == '_';
}
static void
convert_rgba_RGBA (char *buf)
{
char *p;
p = strstr (buf, "rgba");
while (p)
{
/* Check if this looks like a complete token; this is to
* avoiding mangling, say, a selector '.rgba-entry' */
if (!((p > buf && is_identifier_character (*(p - 1))) ||
(is_identifier_character (*(p + 4)))))
memcpy(p, "RGBA", 4);
p += 4;
p = strstr (p, "rgba");
}
}
static CRStyleSheet *
parse_stylesheet (const char *filename)
{
enum CRStatus status;
char *contents;
gsize length;
GError *error = NULL;
CRStyleSheet *stylesheet = NULL;
if (filename == NULL)
return NULL;
if (!g_file_get_contents (filename, &contents ,&length, &error))
{
g_warning("Couldn't read stylesheet: %s", error->message);
g_error_free (error);
return NULL;
}
convert_rgba_RGBA (contents);
status = cr_om_parser_simply_parse_buf ((const guchar *) contents,
length,
CR_UTF_8,
&stylesheet);
if (status != CR_OK)
g_warning ("Error parsing stylesheet '%s'", filename);
g_free (contents);
return stylesheet;
}
CRDeclaration *
_st_theme_parse_declaration_list (const char *str)
{
char *copy = g_strdup (str);
CRDeclaration *result;
convert_rgba_RGBA (copy);
result = cr_declaration_parse_list_from_buf ((const guchar *)copy,
CR_UTF_8);
g_free (copy);
return result;
}
#else /* LIBCROCO_VERSION_NUMBER >= 602 */
static CRStyleSheet *
parse_stylesheet (const char *filename)
{
@ -176,7 +252,7 @@ parse_stylesheet (const char *filename)
return NULL;
status = cr_om_parser_simply_parse_file ((const guchar *) filename,
CR_UTF_8,
CR_UTF_8,
&stylesheet);
if (status != CR_OK)
@ -188,9 +264,17 @@ parse_stylesheet (const char *filename)
return stylesheet;
}
CRDeclaration *
_st_theme_parse_declaration_list (const char *str)
{
return cr_declaration_parse_list_from_buf ((const guchar *)str,
CR_UTF_8);
}
#endif /* LIBCROCO_VERSION_NUMBER < 602 */
static void
insert_stylesheet (StTheme *theme,
const char *filename,
const char *filename,
CRStyleSheet *stylesheet)
{
char *filename_copy;
@ -217,8 +301,8 @@ st_theme_constructor (GType type,
CRStyleSheet *default_stylesheet;
object = (*G_OBJECT_CLASS (st_theme_parent_class)->constructor) (type,
n_construct_properties,
construct_properties);
n_construct_properties,
construct_properties);
theme = ST_THEME (object);
application_stylesheet = parse_stylesheet (theme->application_stylesheet);
@ -226,7 +310,7 @@ st_theme_constructor (GType type,
default_stylesheet = parse_stylesheet (theme->default_stylesheet);
theme->cascade = cr_cascade_new (application_stylesheet,
theme_stylesheet,
theme_stylesheet,
default_stylesheet);
if (theme->cascade == NULL)
@ -239,14 +323,6 @@ st_theme_constructor (GType type,
return object;
}
static void
st_theme_dispose (GObject * object)
{
/* StTheme *theme = ST_THEME(object); */
G_OBJECT_CLASS (st_theme_parent_class)->dispose (object);
}
static void
st_theme_finalize (GObject * object)
{
@ -280,39 +356,39 @@ st_theme_set_property (GObject *object,
{
case PROP_APPLICATION_STYLESHEET:
{
const char *path = g_value_get_string (value);
const char *path = g_value_get_string (value);
if (path != theme->application_stylesheet)
{
g_free (theme->application_stylesheet);
theme->application_stylesheet = g_strdup (path);
}
if (path != theme->application_stylesheet)
{
g_free (theme->application_stylesheet);
theme->application_stylesheet = g_strdup (path);
}
break;
break;
}
case PROP_THEME_STYLESHEET:
{
const char *path = g_value_get_string (value);
const char *path = g_value_get_string (value);
if (path != theme->theme_stylesheet)
{
g_free (theme->theme_stylesheet);
theme->theme_stylesheet = g_strdup (path);
}
if (path != theme->theme_stylesheet)
{
g_free (theme->theme_stylesheet);
theme->theme_stylesheet = g_strdup (path);
}
break;
break;
}
case PROP_DEFAULT_STYLESHEET:
{
const char *path = g_value_get_string (value);
const char *path = g_value_get_string (value);
if (path != theme->default_stylesheet)
{
g_free (theme->default_stylesheet);
theme->default_stylesheet = g_strdup (path);
}
if (path != theme->default_stylesheet)
{
g_free (theme->default_stylesheet);
theme->default_stylesheet = g_strdup (path);
}
break;
break;
}
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@ -362,10 +438,10 @@ st_theme_new (const char *application_stylesheet,
const char *default_stylesheet)
{
StTheme *theme = g_object_new (ST_TYPE_THEME,
"application-stylesheet", application_stylesheet,
"theme-stylesheet", theme_stylesheet,
"default-stylesheet", default_stylesheet,
NULL);
"application-stylesheet", application_stylesheet,
"theme-stylesheet", theme_stylesheet,
"default-stylesheet", default_stylesheet,
NULL);
return theme;
}
@ -398,18 +474,18 @@ string_in_list (GString *stryng,
static gboolean
pseudo_class_add_sel_matches_style (StTheme *a_this,
CRAdditionalSel *a_add_sel,
StThemeNode *a_node)
CRAdditionalSel *a_add_sel,
StThemeNode *a_node)
{
const char *node_pseudo_class;
g_return_val_if_fail (a_this
&& a_add_sel
&& a_add_sel->content.pseudo
&& a_add_sel->content.pseudo->name
&& a_add_sel->content.pseudo->name->stryng
&& a_add_sel->content.pseudo->name->stryng->str
&& a_node, FALSE);
&& a_add_sel
&& a_add_sel->content.pseudo
&& a_add_sel->content.pseudo->name
&& a_add_sel->content.pseudo->name->stryng
&& a_add_sel->content.pseudo->name->stryng->str
&& a_node, FALSE);
node_pseudo_class = st_theme_node_get_pseudo_class (a_node);
@ -427,16 +503,16 @@ pseudo_class_add_sel_matches_style (StTheme *a_this,
*/
static gboolean
class_add_sel_matches_style (CRAdditionalSel *a_add_sel,
StThemeNode *a_node)
StThemeNode *a_node)
{
const char *element_class;
g_return_val_if_fail (a_add_sel
&& a_add_sel->type == CLASS_ADD_SELECTOR
&& a_add_sel->content.class_name
&& a_add_sel->content.class_name->stryng
&& a_add_sel->content.class_name->stryng->str
&& a_node, FALSE);
&& a_add_sel->type == CLASS_ADD_SELECTOR
&& a_add_sel->content.class_name
&& a_add_sel->content.class_name->stryng
&& a_add_sel->content.class_name->stryng->str
&& a_node, FALSE);
element_class = st_theme_node_get_element_class (a_node);
if (element_class == NULL)
@ -453,30 +529,30 @@ class_add_sel_matches_style (CRAdditionalSel *a_add_sel,
*/
static gboolean
id_add_sel_matches_style (CRAdditionalSel *a_add_sel,
StThemeNode *a_node)
StThemeNode *a_node)
{
gboolean result = FALSE;
const char *id;
g_return_val_if_fail (a_add_sel
&& a_add_sel->type == ID_ADD_SELECTOR
&& a_add_sel->content.id_name
&& a_add_sel->content.id_name->stryng
&& a_add_sel->content.id_name->stryng->str
&& a_node, FALSE);
&& a_add_sel->type == ID_ADD_SELECTOR
&& a_add_sel->content.id_name
&& a_add_sel->content.id_name->stryng
&& a_add_sel->content.id_name->stryng->str
&& a_node, FALSE);
g_return_val_if_fail (a_add_sel
&& a_add_sel->type == ID_ADD_SELECTOR
&& a_node, FALSE);
&& a_add_sel->type == ID_ADD_SELECTOR
&& a_node, FALSE);
id = st_theme_node_get_element_id (a_node);
if (id != NULL)
{
if (!strqcmp (id, a_add_sel->content.id_name->stryng->str,
a_add_sel->content.id_name->stryng->len))
{
result = TRUE;
}
a_add_sel->content.id_name->stryng->len))
{
result = TRUE;
}
}
return result;
@ -490,8 +566,8 @@ id_add_sel_matches_style (CRAdditionalSel *a_add_sel,
*/
static gboolean
additional_selector_matches_style (StTheme *a_this,
CRAdditionalSel *a_add_sel,
StThemeNode *a_node)
CRAdditionalSel *a_add_sel,
StThemeNode *a_node)
{
CRAdditionalSel *cur_add_sel = NULL;
@ -502,20 +578,20 @@ additional_selector_matches_style (StTheme *a_this,
switch (cur_add_sel->type)
{
case NO_ADD_SELECTOR:
return FALSE;
return FALSE;
case CLASS_ADD_SELECTOR:
if (!class_add_sel_matches_style (cur_add_sel, a_node))
if (!class_add_sel_matches_style (cur_add_sel, a_node))
return FALSE;
break;
case ID_ADD_SELECTOR:
if (!id_add_sel_matches_style (cur_add_sel, a_node))
if (!id_add_sel_matches_style (cur_add_sel, a_node))
return FALSE;
break;
case ATTRIBUTE_ADD_SELECTOR:
g_warning ("Attribute selectors not supported");
return FALSE;
g_warning ("Attribute selectors not supported");
return FALSE;
case PSEUDO_CLASS_ADD_SELECTOR:
if (!pseudo_class_add_sel_matches_style (a_this, cur_add_sel, a_node))
if (!pseudo_class_add_sel_matches_style (a_this, cur_add_sel, a_node))
return FALSE;
break;
}
@ -524,64 +600,21 @@ additional_selector_matches_style (StTheme *a_this,
return TRUE;
}
/* This is a workaround for:
* - Python encodes '.' as '+' in type names
* - libcroco doesn't correctly parse the \+ escape in types
* - it's far too ugly to write foo\2b\bar\2bWidget...
*
* So we consider an element name of foo-bar-Widget to match a type
* of foo+bar+Widget
*/
static gboolean
element_name_matches_type_name (const char *element_name,
const char *type_name)
{
const char *p = element_name;
const char *q = type_name;
while (*p || *q)
{
if (*p == '-')
{
if (*q != '-' && *q != '+')
return FALSE;
}
else
{
if (*p != *q)
return FALSE;
}
p++;
q++;
}
return TRUE;
}
static gboolean
element_name_matches_type (const char *element_name,
GType element_type)
{
/* This is not efficient, but the number of selectors with an element_name
* is probably fairly small.
*/
if (element_type == G_TYPE_NONE)
{
return strcmp (element_name, "stage") == 0;
}
else
{
while (TRUE)
{
if (element_name_matches_type_name (element_name, g_type_name (element_type)))
return TRUE;
GType match_type = g_type_from_name (element_name);
if (match_type == G_TYPE_INVALID)
return FALSE;
if (element_type == G_TYPE_OBJECT)
return FALSE;
element_type = g_type_parent (element_type);
}
return g_type_is_a (element_type, match_type);
}
}
@ -605,10 +638,10 @@ element_name_matches_type (const char *element_name,
*/
static enum CRStatus
sel_matches_style_real (StTheme *a_this,
CRSimpleSel *a_sel,
StThemeNode *a_node,
gboolean *a_result,
gboolean a_eval_sel_list_from_end,
CRSimpleSel *a_sel,
StThemeNode *a_node,
gboolean *a_result,
gboolean a_eval_sel_list_from_end,
gboolean a_recurse)
{
CRSimpleSel *cur_sel = NULL;
@ -634,51 +667,51 @@ sel_matches_style_real (StTheme *a_this,
while (cur_sel)
{
if (((cur_sel->type_mask & TYPE_SELECTOR)
&& (cur_sel->name
&& cur_sel->name->stryng
&& cur_sel->name->stryng->str)
&&
(element_name_matches_type (cur_sel->name->stryng->str, cur_type)))
|| (cur_sel->type_mask & UNIVERSAL_SELECTOR))
{
/*
*this simple selector
*matches the current style node
*Let's see if the preceding
*simple selectors also match
*their style node counterpart.
*/
if (cur_sel->add_sel)
{
if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node))
&& (cur_sel->name
&& cur_sel->name->stryng
&& cur_sel->name->stryng->str)
&&
(element_name_matches_type (cur_sel->name->stryng->str, cur_type)))
|| (cur_sel->type_mask & UNIVERSAL_SELECTOR))
{
/*
*this simple selector
*matches the current style node
*Let's see if the preceding
*simple selectors also match
*their style node counterpart.
*/
if (cur_sel->add_sel)
{
if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node))
goto walk_a_step_in_expr;
else
else
goto done;
}
else
}
else
goto walk_a_step_in_expr;
}
}
if (!(cur_sel->type_mask & TYPE_SELECTOR)
&& !(cur_sel->type_mask & UNIVERSAL_SELECTOR))
{
if (!cur_sel->add_sel)
&& !(cur_sel->type_mask & UNIVERSAL_SELECTOR))
{
if (!cur_sel->add_sel)
goto done;
if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node))
if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node))
goto walk_a_step_in_expr;
else
else
goto done;
}
}
else
{
goto done;
}
{
goto done;
}
walk_a_step_in_expr:
if (a_recurse == FALSE)
{
*a_result = TRUE;
goto done;
}
{
*a_result = TRUE;
goto done;
}
/*
*here, depending on the combinator of cur_sel
@ -686,72 +719,72 @@ sel_matches_style_real (StTheme *a_this,
*and walk one step in the element tree.
*/
if (!cur_sel->prev)
break;
break;
switch (cur_sel->combinator)
{
case NO_COMBINATOR:
break;
{
case NO_COMBINATOR:
break;
case COMB_WS: /*descendant selector */
{
StThemeNode *n = NULL;
case COMB_WS: /*descendant selector */
{
StThemeNode *n = NULL;
/*
*walk the element tree upward looking for a parent
*style that matches the preceding selector.
*/
for (n = st_theme_node_get_parent (a_node); n; n = st_theme_node_get_parent (n))
{
enum CRStatus status;
gboolean matches = FALSE;
/*
*walk the element tree upward looking for a parent
*style that matches the preceding selector.
*/
for (n = st_theme_node_get_parent (a_node); n; n = st_theme_node_get_parent (n))
{
enum CRStatus status;
gboolean matches = FALSE;
status = sel_matches_style_real (a_this, cur_sel->prev, n, &matches, FALSE, TRUE);
status = sel_matches_style_real (a_this, cur_sel->prev, n, &matches, FALSE, TRUE);
if (status != CR_OK)
goto done;
if (status != CR_OK)
goto done;
if (matches)
{
cur_node = n;
cur_type = st_theme_node_get_element_type (cur_node);
break;
}
}
if (matches)
{
cur_node = n;
cur_type = st_theme_node_get_element_type (cur_node);
break;
}
}
if (!n)
{
/*
*didn't find any ancestor that matches
*the previous simple selector.
*/
goto done;
}
/*
*in this case, the preceding simple sel
*will have been interpreted twice, which
*is a cpu and mem waste ... I need to find
*another way to do this. Anyway, this is
*my first attempt to write this function and
*I am a bit clueless.
*/
break;
}
if (!n)
{
/*
*didn't find any ancestor that matches
*the previous simple selector.
*/
goto done;
}
/*
*in this case, the preceding simple sel
*will have been interpreted twice, which
*is a cpu and mem waste ... I need to find
*another way to do this. Anyway, this is
*my first attempt to write this function and
*I am a bit clueless.
*/
break;
}
case COMB_PLUS:
g_warning ("+ combinators are not supported");
goto done;
case COMB_PLUS:
g_warning ("+ combinators are not supported");
goto done;
case COMB_GT:
cur_node = st_theme_node_get_parent (cur_node);
if (!cur_node)
goto done;
cur_type = st_theme_node_get_element_type (cur_node);
break;
case COMB_GT:
cur_node = st_theme_node_get_parent (cur_node);
if (!cur_node)
goto done;
cur_type = st_theme_node_get_element_type (cur_node);
break;
default:
goto done;
}
default:
goto done;
}
cur_sel = cur_sel->prev;
}
@ -768,8 +801,8 @@ done:
static void
add_matched_properties (StTheme *a_this,
CRStyleSheet *a_nodesheet,
StThemeNode *a_node,
CRStyleSheet *a_nodesheet,
StThemeNode *a_node,
GPtrArray *props)
{
CRStatement *cur_stmt = NULL;
@ -797,46 +830,46 @@ add_matched_properties (StTheme *a_this,
*which we have to look
*/
switch (cur_stmt->type)
{
case RULESET_STMT:
if (cur_stmt->kind.ruleset && cur_stmt->kind.ruleset->sel_list)
{
sel_list = cur_stmt->kind.ruleset->sel_list;
}
break;
{
case RULESET_STMT:
if (cur_stmt->kind.ruleset && cur_stmt->kind.ruleset->sel_list)
{
sel_list = cur_stmt->kind.ruleset->sel_list;
}
break;
case AT_MEDIA_RULE_STMT:
if (cur_stmt->kind.media_rule
&& cur_stmt->kind.media_rule->rulesets
&& cur_stmt->kind.media_rule->rulesets->kind.ruleset
&& cur_stmt->kind.media_rule->rulesets->kind.ruleset->sel_list)
{
sel_list = cur_stmt->kind.media_rule->rulesets->kind.ruleset->sel_list;
}
break;
case AT_MEDIA_RULE_STMT:
if (cur_stmt->kind.media_rule
&& cur_stmt->kind.media_rule->rulesets
&& cur_stmt->kind.media_rule->rulesets->kind.ruleset
&& cur_stmt->kind.media_rule->rulesets->kind.ruleset->sel_list)
{
sel_list = cur_stmt->kind.media_rule->rulesets->kind.ruleset->sel_list;
}
break;
case AT_IMPORT_RULE_STMT:
{
CRAtImportRule *import_rule = cur_stmt->kind.import_rule;
case AT_IMPORT_RULE_STMT:
{
CRAtImportRule *import_rule = cur_stmt->kind.import_rule;
if (import_rule->sheet == NULL)
{
char *filename = NULL;
if (import_rule->sheet == NULL)
{
char *filename = NULL;
if (import_rule->url->stryng && import_rule->url->stryng->str)
filename = _st_theme_resolve_url (a_this,
if (import_rule->url->stryng && import_rule->url->stryng->str)
filename = _st_theme_resolve_url (a_this,
a_nodesheet,
import_rule->url->stryng->str);
if (filename)
import_rule->sheet = parse_stylesheet (filename);
if (filename)
import_rule->sheet = parse_stylesheet (filename);
if (import_rule->sheet)
{
insert_stylesheet (a_this, filename, import_rule->sheet);
/* refcount of stylesheets starts off at zero, so we don't need to unref! */
}
else
if (import_rule->sheet)
{
insert_stylesheet (a_this, filename, import_rule->sheet);
/* refcount of stylesheets starts off at zero, so we don't need to unref! */
}
else
{
/* Set a marker to avoid repeatedly trying to parse a non-existent or
* broken stylesheet
@ -910,9 +943,9 @@ get_origin (const CRDeclaration * decl)
if (decl->important)
{
if (origin == ORIGIN_AUTHOR)
return ORIGIN_AUTHOR_IMPORTANT;
return ORIGIN_AUTHOR_IMPORTANT;
else if (origin == ORIGIN_USER)
return ORIGIN_USER_IMPORTANT;
return ORIGIN_USER_IMPORTANT;
}
return origin;
@ -955,7 +988,7 @@ _st_theme_get_matched_properties (StTheme *theme,
{
sheet = cr_cascade_get_sheet (theme->cascade, origin);
if (!sheet)
continue;
continue;
add_matched_properties (theme, sheet, node, props);
}
@ -990,10 +1023,10 @@ _st_theme_resolve_url (StTheme *theme,
filename = g_filename_from_uri (url, NULL, &error);
if (filename == NULL)
{
g_warning ("%s", error->message);
g_error_free (error);
}
{
g_warning ("%s", error->message);
g_error_free (error);
}
return NULL;
}

View File

@ -8,6 +8,16 @@
G_BEGIN_DECLS
/**
* SECTION:StTheme
* @short_description: a set of stylesheets
*
* #StTheme holds a set of stylesheets. (The "cascade" of the name
* Cascading Stylesheets.) A #StTheme can be set to apply to all the actors
* in a stage using st_theme_context_set_theme() or applied to a subtree
* of actors using st_widget_set_theme().
*/
typedef struct _StThemeClass StThemeClass;
#define ST_TYPE_THEME (st_theme_get_type ())

View File

@ -28,7 +28,6 @@
#include "config.h"
#endif
#include <math.h>
#include <stdlib.h>
#include <string.h>
@ -533,7 +532,7 @@ st_widget_real_style_changed (StWidget *self)
double width = st_theme_node_get_border_width (theme_node, side);
if (width > 0.5)
{
border_width = round (width);
border_width = (int)(0.5 + width);
st_theme_node_get_border_color (theme_node, side, &border_color);
break;
}
@ -544,7 +543,7 @@ st_widget_real_style_changed (StWidget *self)
double radius = st_theme_node_get_border_radius (theme_node, corner);
if (radius > 0.5)
{
border_radius = round (radius);
border_radius = (int)(0.5 + radius);
break;
}
}
@ -700,6 +699,20 @@ get_root_theme_node (ClutterStage *stage)
return st_theme_context_get_root_node (context);
}
/**
* st_widget_get_theme_node:
* @widget: a #StWidget
*
* Gets the theme node holding style information for the widget.
* The theme node is used to access standard and custom CSS
* properties of the widget.
*
* Return value: (transfer none): the theme node for the widget.
* This is owned by the widget. When attributes of the widget
* or the environment that affect the styling change (for example
* the style_class property of the widget), it will be recreated,
* and the ::style-changed signal will be emitted on the widget.
*/
StThemeNode *
st_widget_get_theme_node (StWidget *widget)
{

View File

@ -2,16 +2,23 @@ noinst_SCRIPTS = run-test.sh
EXTRA_DIST = run-test.sh.in
TEST_JS = \
testcommon/ui.js \
interactive/box-layout.js
interactive/borders.js \
interactive/box-layout.js \
interactive/calendar.js \
interactive/css-fonts.js \
interactive/entry.js \
interactive/inline-style.js \
interactive/scrolling.js \
interactive/table.js \
testcommon/border-image.png \
testcommon/ui.js \
unit/format.js
EXTRA_DIST += $(TEST_JS)
TEST_MISC = \
testcommon/test.css
EXTRA_DIST += $(TEST_MISC)
# We substitute in bindir so it works as an autostart
# file when built in a non-system prefix
run-test.sh: run-test.sh.in
$(AM_V_GEN) sed \
-e "s|@GJS_JS_DIR[@]|$(GJS_JS_DIR)|" \
@ -19,3 +26,5 @@ run-test.sh: run-test.sh.in
-e "s|@MUTTER_LIB_DIR[@]|$(MUTTER_LIB_DIR)|" \
-e "s|@srcdir[@]|$(srcdir)|" \
$< > $@ && chmod a+x $@
CLEANFILES = run-test.sh

View File

@ -0,0 +1,29 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
const Clutter = imports.gi.Clutter;
const Lang = imports.lang;
const St = imports.gi.St;
const Calendar = imports.ui.calendar;
const UI = imports.testcommon.ui;
UI.init();
let stage = Clutter.Stage.get_default();
stage.width = stage.height = 400;
stage.show();
let vbox = new St.BoxLayout({ vertical: true,
width: stage.width,
height: stage.height,
style: 'padding: 10px; spacing: 10px; font: 15px sans-serif;' });
stage.add_actor(vbox);
let calendar = new Calendar.Calendar();
vbox.add(calendar.actor,
{ expand: true,
x_fill: false, x_align: St.Align.MIDDLE,
y_fill: false, y_align: St.Align.START });
stage.show();
Clutter.main();
stage.destroy();

View File

@ -0,0 +1,32 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
const Clutter = imports.gi.Clutter;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const St = imports.gi.St;
const Calendar = imports.ui.calendar;
const UI = imports.testcommon.ui;
Gtk.init(null, null);
UI.init();
let stage = Clutter.Stage.get_default();
stage.width = stage.height = 400;
stage.show();
let vbox = new St.BoxLayout({ vertical: true,
width: stage.width,
height: stage.height,
style: 'padding: 10px; spacing: 10px; font: 15px sans-serif;' });
stage.add_actor(vbox);
let entry = new St.Entry({ style: 'border: 1px solid black;' });
vbox.add(entry,
{ expand: true,
y_fill: false, y_align: St.Align.MIDDLE });
entry.grab_key_focus();
stage.show();
Clutter.main();
stage.destroy();

29
tests/unit/format.js Normal file
View File

@ -0,0 +1,29 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* Test cases for the Format module
*/
const JsUnit = imports.jsUnit;
const assertEquals = JsUnit.assertEquals;
const assertRaises = JsUnit.assertRaises;
// We can't depend on environment.js to set up the String.prototype.format,
// because the tests run in one JS context, and the imports run in the GJS
// "load context" which has its own copy of the String class
const Format = imports.misc.format;
String.prototype.format = Format.format;
// Test common usage and %% handling
assertEquals("foo", "%s".format('foo'));
assertEquals("%s", "%%s".format('foo'));
assertEquals("%%s", "%%%%s".format('foo'));
assertEquals("foo 5", "%s %d".format('foo', 5));
assertEquals("8", "%d".format(8));
assertEquals("2.58 6.96", "%f %.2f".format(2.58, 6.958));
// Precision is only allowed for %f
assertRaises(function() { "%.2d".format(5.21) });
// Wrong conversion character ' '
assertRaises( function() { "%s is 50% done".format('foo') });

View File

@ -74,7 +74,7 @@ dpkg_is_installed() {
return 1
}
if test x$system = xUbuntu -o x$system = xDebian ; then
if test x$system = xUbuntu -o x$system = xDebian -o x$system = xLinuxMint ; then
reqd=""
for pkg in \
build-essential curl \
@ -82,7 +82,7 @@ if test x$system = xUbuntu -o x$system = xDebian ; then
libdbus-glib-1-dev libgconf2-dev libgtk2.0-dev libffi-dev \
libgnome-menu-dev libgnome-desktop-dev librsvg2-dev libwnck-dev libgl1-mesa-dev \
libreadline5-dev mesa-common-dev mesa-utils python-dev python-gconf python-gobject \
xulrunner-dev xserver-xephyr \
xulrunner-dev xserver-xephyr libcroco3-dev \
libgstreamer0.10-dev gstreamer0.10-plugins-base gstreamer0.10-plugins-good \
; do
if ! dpkg_is_installed $pkg; then
@ -104,7 +104,7 @@ if test x$system = xFedora ; then
libtool pkgconfig \
dbus-glib-devel GConf2-devel gnome-menus-devel gnome-python2-gconf gtk2-devel libffi-devel \
gnome-desktop-devel librsvg2-devel libwnck-devel mesa-libGL-devel python-devel pygobject2 \
readline-devel xulrunner-devel libXdamage-devel \
readline-devel xulrunner-devel libXdamage-devel libcroco-devel \
gstreamer-devel gstreamer-plugins-base gstreamer-plugins-good \
glx-utils xorg-x11-apps xorg-x11-server-Xephyr xterm zenity \
; do
@ -124,7 +124,7 @@ if test x$system = xSUSE ; then
bison flex gnome-doc-utils-devel \
gconf2-devel libffi-devel gnome-desktop-devel librsvg-devel libwnck-devel \
xorg-x11-proto-devel readline-devel mozilla-xulrunner190-devel \
xorg-x11-devel xterm xorg-x11 xorg-x11-server-extra \
libcroco-devel xorg-x11-devel xterm xorg-x11 xorg-x11-server-extra \
; do
if ! rpm -q $pkg > /dev/null 2>&1; then
reqd="$pkg $reqd"
@ -145,6 +145,7 @@ if test x$system = xMandrivaLinux ; then
libGConf2-devel ffi5-devel libgnomeui2-devel librsvg2-devel \
libwnck-1-devel GL-devel readline-devel libxulrunner-devel \
libxdamage-devel mesa-demos x11-server-xephyr x11-apps xterm zenity \
libcroco0.6-devel \
; do
if ! rpm -q --whatprovides $pkg > /dev/null 2>&1; then
reqd="$pkg $reqd"

View File

@ -35,13 +35,7 @@
<branch repo="git.clutter-project.org" module="clutter" revision="clutter-1.0"/>
<dependencies>
<dep package="gobject-introspection"/>
</dependencies>
</autotools>
<autotools id="clutter-imcontext">
<branch repo="git.moblin.org" module="clutter-imcontext"/>
<dependencies>
<dep package="clutter"/>
<dep package="gir-repository"/>
</dependencies>
</autotools>